UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

264 lines (263 loc) • 11.3 kB
import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs"; import "./string-coerce-runtime-BbIJhy7G.mjs"; import { h as withSessionTranscriptWrite, r as appendSessionTranscriptMessageByIdentityStrict, s as publishSessionTranscriptUpdateByIdentity } from "./session-transcript-runtime-B9IYtIbl.mjs"; import { i as createAgentHarnessToolResultMessage, r as createAgentHarnessToolCallMessage } from "./agent-harness-attempt-runtime-DkrHp41D.mjs"; import { i as agentsApiNativeToolOutput, n as agentsApiNativeToolDetails, r as agentsApiNativeToolOutcome, t as agentsApiNativeTool } from "./agentsapi-native-items-D8LDzz_y.mjs"; import { t as requireAgentsApiSessionTarget } from "./agentsapi-target-ClP2ww2I.mjs"; //#region extensions/agentsapi/agentsapi-transcript.ts function bindAgentsApiTranscriptAuthority(owner, signal) { const assertCurrent = () => { owner(); signal.throwIfAborted(); }; const prepare = owner.prepareSessionSource; return prepare ? Object.assign(assertCurrent, { async prepareSessionSource() { const source = await prepare(); return { ...source, assertCurrent: () => { source.assertCurrent(); signal.throwIfAborted(); }, assertPreparedCurrent: () => { (source.assertPreparedCurrent ?? source.assertCurrent)(); signal.throwIfAborted(); } }; } }) : assertCurrent; } async function assertTranscriptCurrent(assertCurrent) { if (!assertCurrent.prepareSessionSource) { assertCurrent(); return; } const source = await assertCurrent.prepareSessionSource(); try { source.assertCurrent(); } finally { await source.release?.(); } } /** Canonical native facts use the same durable identities during live and historical repair. */ async function recordAgentsApiNativeToolTranscript(params, sessionId, turnId, item, assertCurrent, nextTimestamp, options = {}) { await assertTranscriptCurrent(assertCurrent); const tool = agentsApiNativeTool(item, params); if (!tool || ![ "completed", "failed", "incomplete" ].includes(item.status ?? "")) return false; const id = `agentsapi:${sessionId}:${turnId}:${item.id}`; const outcome = agentsApiNativeToolOutcome(item, options.enclosingStatus); const output = agentsApiNativeToolOutput(item, options.capturedOutput); const details = agentsApiNativeToolDetails(sessionId, turnId, item, outcome, options.capturedOutput); const text = output ?? (item.type === "web_search_call" ? `Web search ${outcome.status}; native search results are unavailable.` : outcome.error ?? `${tool.name} ${outcome.status}`); await recordAgentsApiNativeToolInvocation(params, sessionId, turnId, item, assertCurrent, nextTimestamp, options.historical); await appendAgentsApiTranscriptMessage(params, { ...createAgentHarnessToolResultMessage({ id, name: tool.name, text, isError: outcome.isError, details }, nextTimestamp()), __openclaw: { toolOutput: { source: "execution", modelInput: "unverified", ...outcome.outcomeUnknown ? { outcome: "unknown" } : {}, ...options.captureTruncated ? { captureTruncated: true } : {} }, ...item.type === "web_search_call" ? { resultContentSource: "network" } : {} }, idempotencyKey: `${id}:result` }, assertCurrent, void 0, options.historical); return true; } /** Canonical invocation facts can precede completion of the native tool. */ async function recordAgentsApiNativeToolInvocation(params, sessionId, turnId, item, assertCurrent, nextTimestamp, historical = false) { await assertTranscriptCurrent(assertCurrent); const tool = agentsApiNativeTool(item, params); if (!tool || !canRecordAgentsApiNativeToolInvocation(item)) return false; const id = `agentsapi:${sessionId}:${turnId}:${item.id}`; await appendAgentsApiTranscriptMessage(params, { ...createAgentHarnessToolCallMessage({ api: "openai-agents", provider: "openai", modelId: params.model.id }, { id, name: tool.name, arguments: tool.args }, nextTimestamp()), idempotencyKey: `${id}:call` }, assertCurrent, void 0, historical); return true; } /** Persist host tool evidence before its result is acknowledged by the native session. */ async function recordAgentsApiToolTranscript(params, call, result, isError, assertCurrent) { const identity = `agentsapi:tool:${call.turn_id}:${call.call_id}`; const attribution = { api: "openai-agents", provider: "openai", modelId: params.model.id }; const toolCall = { ...createAgentHarnessToolCallMessage(attribution, { id: call.call_id, name: call.name, arguments: asOptionalRecord(call.arguments) ?? {} }, Date.now()), idempotencyKey: `${identity}:call` }; const toolResult = { ...createAgentHarnessToolResultMessage({ id: call.call_id, name: call.name, content: result.content, details: result.details, isError }, Date.now()), idempotencyKey: `${identity}:result` }; await appendAgentsApiTranscriptMessage(params, toolCall, assertCurrent); await appendAgentsApiTranscriptMessage(params, toolResult, assertCurrent); } async function appendAgentsApiTranscriptMessage(params, message, assertCurrent, assistantItemIds, historical = false) { await assertTranscriptCurrent(assertCurrent); const target = requireAgentsApiSessionTarget(params); let runId = historical ? void 0 : params.runId; let attributedMessage = message; const idempotencyKey = asOptionalRecord(message)?.idempotencyKey; if (typeof idempotencyKey === "string") { const facts = await withSessionTranscriptWrite(target, (transcript) => transcript.readMessageFacts({ idempotencyKeys: [idempotencyKey] })); await assertTranscriptCurrent(assertCurrent); if (facts.existingIdempotencyKeys.has(idempotencyKey)) { const stored = asOptionalRecord(facts.messagesByIdempotencyKey.get(idempotencyKey)); const storedRunId = asOptionalRecord(stored?.["__openclaw"])?.runId; runId = typeof storedRunId === "string" ? storedRunId : void 0; const metadata = asOptionalRecord(asOptionalRecord(message)?.["__openclaw"]); if (metadata) { const { runId: _currentRunId, ...retained } = metadata; attributedMessage = { ...message, __openclaw: { ...retained, ...runId ? { runId } : {} } }; } } } const append = await appendSessionTranscriptMessageByIdentityStrict({ ...target, config: params.config, runId, message: attributedMessage, preparation: { source: assertCurrent } }); await assertTranscriptCurrent(assertCurrent); if (append.kind !== "result") throw new Error("Agents API transcript append was refused"); if (assistantItemIds) { await publishSessionTranscriptUpdateByIdentity({ ...target, update: { message: append.result.message, messageId: append.result.messageId, ...append.result.anchor ? { messageSeq: append.result.anchor.activeMessagePosition + 1 } : {}, runId: params.runId, assistantItemIds } }); await assertTranscriptCurrent(assertCurrent); } return append.result.message; } /** Walk the retrieved native prefix without another transcript queue or cursor. */ function* iterateAgentsApiTranscriptItems(turnId, items, enclosingStatus, terminalTurn, recordedGatewayCallIds, hasObservedCompletion) { let transcriptReady = true; let deferredFinalSeen = false; for (const item of items) { if (item.turn_id && item.turn_id !== turnId) throw new Error("Agents API saved item belongs to a different turn"); const completionObserved = hasObservedCompletion(item.id); const terminal = terminalTurn || [ "completed", "failed", "incomplete" ].includes(item.status ?? "") || item.status == null && completionObserved; if (transcriptReady && (deferredFinalSeen && hasAgentsApiTranscriptRecord(item) || !canRecordAgentsApiTranscriptItem(turnId, item, enclosingStatus, recordedGatewayCallIds, completionObserved))) transcriptReady = false; deferredFinalSeen ||= isAgentsApiDeferredFinalText(item); yield { item, terminal, transcriptReady }; } } /** Nonterminal calls require retrieved invocation fields, rather than streamed guesses. */ function canRecordAgentsApiNativeToolInvocation(item) { if ([ "completed", "failed", "incomplete" ].includes(item.status ?? "")) return [ "command_execution", "mcp_call", "web_search_call" ].includes(item.type); if (item.type === "command_execution") return typeof item.command === "string" && (item.cwd === null || typeof item.cwd === "string"); return false; } /** Backend publication stops before invocation or text facts that are still incomplete. */ function canRecordAgentsApiTranscriptItem(turnId, item, enclosingStatus, recordedGatewayCallIds, completionObserved = false) { if ([ "command_execution", "mcp_call", "web_search_call" ].includes(item.type)) return canRecordAgentsApiNativeToolInvocation(item); if (item.type === "function_call") return typeof item.call_id === "string" && recordedGatewayCallIds.has(`${turnId}:${item.call_id}`); if (item.type === "reasoning" || item.type === "message" && item.role === "assistant" && item.phase === "commentary") return canRecordAgentsApiTranscriptText(item, enclosingStatus, completionObserved); return true; } function canRecordAgentsApiTranscriptText(item, enclosingStatus, completionObserved = false) { if ([ "completed", "failed", "incomplete" ].includes(item.status ?? "")) return true; return item.status == null && (completionObserved || item.type === "reasoning" && enclosingStatus === "completed"); } function isAgentsApiDeferredFinalText(item) { return item.type === "message" && item.role === "assistant" && item.phase !== "commentary" && item.status === "completed" && Boolean(item.content?.some((part) => part.type === "output_text" && part.text)); } function hasAgentsApiTranscriptRecord(item) { return [ "command_execution", "mcp_call", "web_search_call", "function_call" ].includes(item.type) || item.type === "reasoning" && Boolean(item.summary?.some((part) => part.type === "summary_text" && part.text)) || item.type === "message" && item.role === "assistant" && item.phase === "commentary" && Boolean(item.content?.some((part) => part.type === "output_text" && part.text)); } function readTextParts(parts, type) { const texts = /* @__PURE__ */ new Map(); parts?.forEach((part, index) => { if (part.type === type) texts.set(index, part.text ?? ""); }); return texts; } function joinTextParts(parts) { return [...parts.entries()].toSorted(([left], [right]) => left - right).map(([, text]) => text).join(""); } /** Historical items keep their native identity without acquiring the current run's attribution. */ async function recordAgentsApiNativeHistory(params, nativeSessionId, entries, assertCurrent) { for (const { turn, items } of entries) for (const item of items) { assertCurrent(); await recordAgentsApiNativeToolTranscript(params, nativeSessionId, turn.id, item, assertCurrent, Date.now, { enclosingStatus: turn.status, historical: true }); assertCurrent(); } } //#endregion export { joinTextParts as a, recordAgentsApiNativeToolInvocation as c, iterateAgentsApiTranscriptItems as i, recordAgentsApiNativeToolTranscript as l, bindAgentsApiTranscriptAuthority as n, readTextParts as o, canRecordAgentsApiTranscriptText as r, recordAgentsApiNativeHistory as s, appendAgentsApiTranscriptMessage as t, recordAgentsApiToolTranscript as u };