chore: pi family 0.85.1 -> 0.87.1 (finishTurn, transcript contexts, typebox 1.3.27)
Mechanical fallout only; semantic deltas flagged in the wave report:
- shouldStopAfterTurn -> finishTurn ({action:'end'} wrap; loop order verified
equivalent for Wave C stranding)
- transcript rework: seeded leading SystemMessage filtered at both
state.messages read seams (ui-stream settleWith, pi-run slice); prompt
and tool declarations now ride the transcript
- streamOnce sites normalizeContext() (Context -> TranscriptContext)
- AgentToolResult details / ToolCall.arguments JsonValue narrowing (casts)
- AUTH_PROVIDER_KEYS regenerated (meta added); glm-5.3-flash price pin
follows the 0.87.1 catalog (0.15/0.5/0.05)
- vanish probe: 0/12 vs 2/6 baseline; live smoke green on PORT=3004
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { AssistantMessage, Message, ToolResultMessage, Usage, Tool } from "@earendil-works/pi-ai";
|
||||
import { normalizeContext } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { piEngineFor, type PiEngine, type ThinkingLevel } from "../models.ts";
|
||||
import type { PdfResult, PdfToolInput } from "../../core/contracts.ts";
|
||||
@@ -392,7 +393,10 @@ async function streamOnce(
|
||||
reasoning: ThinkingLevel | undefined,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<AssistantMessage> {
|
||||
const stream = engine.streamFn(engine.model, { systemPrompt, messages, tools }, {
|
||||
// 0.87.1: provider-facing contexts are TranscriptContexts — the prompt
|
||||
// and tool declarations fold into a leading system message.
|
||||
// normalizeContext is the library's only Context→TranscriptContext entry.
|
||||
const stream = engine.streamFn(engine.model, normalizeContext({ systemPrompt, messages, tools }), {
|
||||
reasoning: reasoning && reasoning !== "off" ? reasoning : undefined,
|
||||
apiKey: process.env.OPENROUTER_API_KEY || undefined,
|
||||
signal: abortSignal,
|
||||
|
||||
@@ -65,9 +65,13 @@ export async function runSubagent(tools: AgentTool<any, any>[], opts: SubagentRu
|
||||
messages: opts.messages ?? [],
|
||||
},
|
||||
streamFn: engine.streamFn,
|
||||
shouldStopAfterTurn: (context) => {
|
||||
// 0.87.1: shouldStopAfterTurn → finishTurn (same call point; "end"
|
||||
// exits before the steering poll, exactly the old boolean-true).
|
||||
finishTurn: (turn) => {
|
||||
turns++;
|
||||
return turns >= opts.maxTurns || opts.shouldStop?.(context.message, context.toolResults) === true;
|
||||
return turns >= opts.maxTurns || opts.shouldStop?.(turn.message, turn.toolResults) === true
|
||||
? { action: "end" }
|
||||
: undefined;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -119,7 +123,12 @@ export async function runSubagent(tools: AgentTool<any, any>[], opts: SubagentRu
|
||||
}
|
||||
if (errorMessage !== undefined) throw new Error(errorMessage);
|
||||
|
||||
const newMessages = agent.state.messages.slice(before);
|
||||
// 0.87.1: drop the seeded leading SystemMessage (transcript rework —
|
||||
// see ui-stream's settleWith) before slicing past the input count, or
|
||||
// the seed displaces the slice and the last INPUT message leaks in.
|
||||
const newMessages = agent.state.messages
|
||||
.filter((m) => (m as { role?: string }).role !== "system")
|
||||
.slice(before);
|
||||
const assistants = newMessages.filter((m) => (m as { role?: string }).role === "assistant") as AssistantMessage[];
|
||||
const last = assistants[assistants.length - 1];
|
||||
const text = last
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AssistantMessage, Message, Tool, ToolResultMessage, Usage } from "@earendil-works/pi-ai";
|
||||
import { normalizeContext } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { piEngineFor, type PiEngine, type ThinkingLevel } from "../models.ts";
|
||||
import type { VisualResult } from "../../core/contracts.ts";
|
||||
@@ -307,7 +308,10 @@ async function streamOnce(
|
||||
reasoning: ThinkingLevel | undefined,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<AssistantMessage> {
|
||||
const stream = engine.streamFn(engine.model, { systemPrompt, messages, tools }, {
|
||||
// 0.87.1: provider-facing contexts are TranscriptContexts — the prompt
|
||||
// and tool declarations fold into a leading system message.
|
||||
// normalizeContext is the library's only Context→TranscriptContext entry.
|
||||
const stream = engine.streamFn(engine.model, normalizeContext({ systemPrompt, messages, tools }), {
|
||||
reasoning: reasoning && reasoning !== "off" ? reasoning : undefined,
|
||||
apiKey: process.env.OPENROUTER_API_KEY || undefined,
|
||||
signal: abortSignal,
|
||||
|
||||
@@ -122,7 +122,8 @@ export interface AgentBuildOptions {
|
||||
*
|
||||
* Question results also carry terminate: true (tool-execute's
|
||||
* questionResult), so single-question batches stop natively at the pi
|
||||
* loop's own batch check; this shouldStopAfterTurn hook is the
|
||||
* loop's own batch check; this finishTurn hook (0.87.1 rename of
|
||||
* shouldStopAfterTurn) is the
|
||||
* belt-and-suspenders HALF for the mixed batch (a question + other calls
|
||||
* in one message) that terminate-only semantics would let run on — the
|
||||
* learner cannot answer while a turn is in flight (per-chat 409), so a
|
||||
@@ -164,7 +165,12 @@ function baseAgentOptions(kind: AgentKind, opts: AgentBuildOptions, tools: Agent
|
||||
messages: opts.messages ?? [],
|
||||
},
|
||||
streamFn: engine.streamFn,
|
||||
shouldStopAfterTurn: stopper,
|
||||
// 0.87.1: shouldStopAfterTurn → finishTurn. Same call point (after
|
||||
// tool results attach, before turn_end) and `{ action: "end" }` still
|
||||
// exits BEFORE the steering poll — the parked-marker stranding our
|
||||
// Wave C registerLiveAgent-before-runPiTurn design depends on.
|
||||
// undefined preserves normal scheduling (the old boolean false).
|
||||
finishTurn: (turn) => (stopper(turn) ? { action: "end" } : undefined),
|
||||
// Wave C (2026-09-26, owner correction same day): markers handed to a
|
||||
// live run drain from pi's STEERING queue — the lesson loop is one
|
||||
// continuous teach→quiz→teach run, so delivery must inject at turn
|
||||
|
||||
@@ -95,6 +95,7 @@ export const AUTH_PROVIDER_KEYS: readonly AuthProviderInfo[] = [
|
||||
{ id: "groq", envVar: "GROQ_API_KEY", label: "Groq" },
|
||||
{ id: "huggingface", envVar: "HF_TOKEN", label: "Hugging Face" },
|
||||
{ id: "kimi-coding", envVar: "KIMI_API_KEY", label: "Kimi For Coding" },
|
||||
{ id: "meta", envVar: "META_API_KEY", label: "Meta" },
|
||||
{ id: "minimax", envVar: "MINIMAX_API_KEY", label: "MiniMax" },
|
||||
{ id: "minimax-cn", envVar: "MINIMAX_CN_API_KEY", label: "MiniMax CN" },
|
||||
{ id: "mistral", envVar: "MISTRAL_API_KEY", label: "Mistral" },
|
||||
|
||||
@@ -117,7 +117,9 @@ export function newAnswerResult(
|
||||
toolCallId,
|
||||
toolName,
|
||||
content: [{ type: "text", text: JSON.stringify(answer) }],
|
||||
details: answer,
|
||||
// 0.87.1 narrowed ToolResultMessage.details to JsonRepresentation —
|
||||
// our payloads are JSON-safe answer structures, so the cast is honest.
|
||||
details: answer as ToolResultMessage["details"],
|
||||
isError: false,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
@@ -115,8 +115,18 @@ export function runPiTurn(spec: PiTurnSpec): PiTurnHandle {
|
||||
// The settle seam: the record write is the agent's OWN final state
|
||||
// (native history — signatures, usage, provider stamps all real).
|
||||
// Copy the array: the agent may outlive this call's read.
|
||||
// 0.87.1 (transcript rework): the Agent seeds a leading SystemMessage
|
||||
// from initialState.systemPrompt (prompt + tool declarations) — it is
|
||||
// synthesized per turn and must NEVER persist (the record would bloat
|
||||
// with tool declarations AND replay would see messages[0] === system,
|
||||
// suppressing the fresh per-turn re-seed). Our app mints no system
|
||||
// messages of its own (the prompt is composed per turn), so dropping
|
||||
// the role wholesale is exact; revisit if prompt-updates ever land.
|
||||
const settleWith = () => {
|
||||
onFinish([...agent.state.messages], sawAbort);
|
||||
onFinish(
|
||||
[...agent.state.messages].filter((m) => (m as { role?: string }).role !== "system"),
|
||||
sawAbort,
|
||||
);
|
||||
};
|
||||
|
||||
const handle = (event: AgentEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user