UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

64 lines (63 loc) • 3.75 kB
import { deliverEngineNotice } from "../engine-notice.js"; import { isProviderEchoShape } from "../../brain/errors.js"; import { streamDropsOf } from "../../internal/llm.js"; const FORM_SENTENCE = { trailing_text: "a block after an assistant's tool calls was moved ahead of them", late_result: "a result recorded later was moved up behind its call", missing_result: "a call with no result was closed as interrupted (outcome unknown)", duplicate_result: "a second result recorded for one call was folded into the first (an interrupted close beside a recorded result is left out)", duplicate_call: "a second call with the same id in one assistant message was left out (its results answer the first)", orphan_result: "a result that answers no call before it was left out", }; const INCOMPLETE_SENTENCE = { truncated: (n) => `${n} tool call(s) the model made were cut off before their arguments were complete, so they did not run; the model was asked to re-issue them`, unnamed: (n) => `${n} tool call(s) the model made arrived with no tool name, so they did not run; the model was asked to re-issue them`, bad_frame: (n) => `${n} frame(s) of the model's streamed response could not be read and were dropped, so the response may be missing content; the model was told`, missing_result_synthesized: (n) => `${n} earlier tool call(s) have no recorded result, so the model was told their outcome is unknown`, }; export function createToolTurnIncompleteSeat(onNotice, ids) { const announced = new Set(); const announce = (cause, count, toolCallIds) => { if (announced.has(cause)) return; announced.add(cause); const shown = (toolCallIds ?? []).filter((id) => isProviderEchoShape(id)); deliverEngineNotice(onNotice, { code: "task.tool_turn_incomplete", message: `${INCOMPLETE_SENTENCE[cause](count)}. Announced once per run for this cause.`, detail: { cause, count, ...(shown.length > 0 ? { toolCallIds: shown } : {}), sessionId: ids.sessionId, runId: ids.runId }, }); }; return { announce, observe(message) { for (const drop of streamDropsOf(message)) announce(drop.cause, drop.count); }, }; } export function createToolTurnRepairSeat(onNotice, ids) { const announced = new Set(); const incomplete = createToolTurnIncompleteSeat(onNotice, ids); return (repairs) => { const missing = repairs.filter((repair) => repair.form === "missing_result" && repair.beforeRun !== true).flatMap((repair) => repair.toolCallIds); if (missing.length > 0) incomplete.announce("missing_result_synthesized", missing.length, missing); const fresh = repairs.filter((repair) => { const key = `${repair.form}\u0000${[...repair.toolCallIds].sort().join("\u0000")}`; if (announced.has(key)) return false; announced.add(key); return true; }); if (fresh.length === 0) return; deliverEngineNotice(onNotice, { code: "context.tool_turn_repaired", message: `The session transcript carries ${fresh.length === 1 ? "a tool turn" : `${fresh.length} tool turns`} in a shape a provider lane ` + `rejects, so the request was repaired on its way out: ${[...new Set(fresh.map((r) => FORM_SENTENCE[r.form]))].join("; ")}. ` + `The transcript is unchanged and every later request is repaired the same way; announced once per run.`, detail: { repairs: fresh.map((r) => ({ form: r.form, toolCallIds: [...r.toolCallIds] })), sessionId: ids.sessionId, runId: ids.runId }, }); }; }