@sema-agent/core
Version:
Stateless, task-oriented AI agent core
64 lines (63 loc) • 3.75 kB
JavaScript
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 },
});
};
}