UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

302 lines (301 loc) • 10.3 kB
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import "./worker-protocol-primitives-BajTgPT4.mjs"; import { l as makeZeroUsageSnapshot } from "./usage-XmAH1cfG.mjs"; import { n as invalidateComputerFrameIfMissing } from "./computer-tool-xn2S9vJM.mjs"; import { t as event_stream_exports } from "./event-stream-xGBL9CyU.mjs"; import { n as isWorkerTranscriptMessageFrameSafe, t as WORKER_PROVIDER_REPLAY_LOCAL_RETRY_MESSAGE } from "./transcript-message-Dz1c-AeG.mjs"; import { t as fitWorkerReplayImages } from "./replay-message-window-CB5m9HXa.mjs"; import { createToolArgumentPreviewSchedule, parseStreamingJson, parseTerminalToolCallArguments } from "@openclaw/ai/internal/runtime"; //#region src/worker/inference-stream.runtime.ts function emptyAssistantMessage(modelRef) { return { role: "assistant", content: [], api: "openai-responses", provider: modelRef.provider, model: modelRef.model, stopReason: "stop", usage: makeZeroUsageSnapshot(), timestamp: Date.now() }; } function processInferenceEvent(payload, partial, toolArgumentPreviewSchedules, tolerateMissingState) { const event = payload.event; switch (event.type) { case "start": partial.api = event.resolvedModel.api; partial.provider = event.resolvedModel.provider; partial.model = event.resolvedModel.model; partial.timestamp = event.timestamp; return { type: "start", partial }; case "text_start": partial.content[event.contentIndex] = { type: "text", text: "", ...event.contentSignature === void 0 ? {} : { textSignature: event.contentSignature } }; return { type: "text_start", contentIndex: event.contentIndex, partial }; case "text_delta": case "text_end": { const content = partial.content[event.contentIndex]; if (content?.type !== "text") { if (tolerateMissingState) return; throw new Error(`worker inference text ${event.type === "text_delta" ? "delta" : "end"} has no active text block`); } if (event.type === "text_delta") { content.text += event.delta; return { type: "text_delta", contentIndex: event.contentIndex, delta: event.delta, partial }; } if (event.contentSignature !== void 0) content.textSignature = event.contentSignature; return { type: "text_end", contentIndex: event.contentIndex, content: content.text, partial }; } case "thinking_start": partial.content[event.contentIndex] = { type: "thinking", thinking: "" }; return { type: "thinking_start", contentIndex: event.contentIndex, partial }; case "thinking_delta": case "thinking_end": { const content = partial.content[event.contentIndex]; if (content?.type !== "thinking") { if (tolerateMissingState) return; throw new Error(`worker inference thinking ${event.type === "thinking_delta" ? "delta" : "end"} has no active thinking block`); } if (event.type === "thinking_delta") { content.thinking += event.delta; return { type: "thinking_delta", contentIndex: event.contentIndex, delta: event.delta, partial }; } if (event.contentSignature !== void 0) content.thinkingSignature = event.contentSignature; return { type: "thinking_end", contentIndex: event.contentIndex, content: content.thinking, partial }; } case "toolcall_start": { const content = { type: "toolCall", id: event.id, name: event.toolName, arguments: {}, partialJson: "" }; partial.content[event.contentIndex] = content; toolArgumentPreviewSchedules.set(event.contentIndex, createToolArgumentPreviewSchedule()); return { type: "toolcall_start", contentIndex: event.contentIndex, partial }; } case "toolcall_delta": case "toolcall_end": { const content = partial.content[event.contentIndex]; if (content?.type !== "toolCall") { if (tolerateMissingState) return; throw new Error(`worker inference tool ${event.type === "toolcall_delta" ? "delta" : "end"} has no active tool call`); } const streaming = content; if (event.type === "toolcall_delta") { streaming.partialJson += event.delta; const previewSchedule = toolArgumentPreviewSchedules.get(event.contentIndex); if (!previewSchedule) throw new Error("worker inference tool delta has no preview schedule"); if (previewSchedule(streaming.partialJson.length)) content.arguments = parseStreamingJson(streaming.partialJson); return { type: "toolcall_delta", contentIndex: event.contentIndex, delta: event.delta, partial }; } content.arguments = parseTerminalToolCallArguments(streaming.partialJson); toolArgumentPreviewSchedules.delete(event.contentIndex); delete content.partialJson; return { type: "toolcall_end", contentIndex: event.contentIndex, toolCall: content, partial }; } } } function transcriptSafeErrorMessage(modelRef, message) { if (isWorkerTranscriptMessageFrameSafe(message)) return message; const replacement = emptyAssistantMessage(modelRef); replacement.api = message.api; replacement.provider = message.provider; replacement.model = message.model; replacement.timestamp = message.timestamp; replacement.stopReason = message.stopReason === "aborted" ? "aborted" : "error"; replacement.errorMessage = truncateUtf16Safe(message.errorMessage ?? "Worker inference result exceeds the transcript message limit.", 256); replacement.usage = structuredClone(message.usage); return replacement; } function createInferenceRequestMeasure(request) { const measureFull = (messages) => Buffer.byteLength(JSON.stringify({ type: "req", id: "00000000-0000-4000-8000-000000000000", method: "worker.inference.start", params: { ...request, context: { ...request.context, messages } } }), "utf8"); let envelopeBytes = 0; let messageBytes; return (messages) => { if (messages === request.context.messages) return measureFull(messages); if (!messageBytes) { envelopeBytes = measureFull([]); messageBytes = /* @__PURE__ */ new WeakMap(); } let bytes = envelopeBytes + Math.max(0, messages.length - 1); for (const message of messages) { let size = messageBytes.get(message); if (size === void 0) { size = Buffer.byteLength(JSON.stringify(message), "utf8"); messageBytes.set(message, size); } bytes += size; } return bytes; }; } function createWorkerInferenceStreamAdapter(adapter) { let modelCallSeq = 0; return (inferenceRequest) => { const stream = (0, event_stream_exports.createAssistantMessageEventStream)(); const partial = emptyAssistantMessage(adapter.modelRef); const toolArgumentPreviewSchedules = /* @__PURE__ */ new Map(); let streamHasGap = false; let settled = false; modelCallSeq += 1; const turnSuffix = `:${modelCallSeq}`; const identity = { runEpoch: adapter.runEpoch, sessionId: adapter.sessionId, runId: adapter.runId, turnId: `${adapter.turnId.slice(0, 256 - turnSuffix.length)}${turnSuffix}` }; let request = { ...identity, modelRef: inferenceRequest.modelRef, context: structuredClone(inferenceRequest.context), options: structuredClone(inferenceRequest.options) }; const finishError = (error, reason, usage) => { if (settled) return; settled = true; partial.stopReason = reason; partial.errorMessage = error instanceof Error ? error.message : String(error); if (usage) partial.usage = structuredClone(usage); stream.push({ type: "error", reason, error: transcriptSafeErrorMessage(adapter.modelRef, partial) }); stream.end(); }; const fail = (error) => finishError(error, inferenceRequest.signal?.aborted ? "aborted" : "error"); const abort = () => { adapter.client.cancel(identity).catch(() => void 0).finally(() => finishError("Worker inference aborted.", "aborted")); }; if (inferenceRequest.signal?.aborted) { finishError("Worker inference aborted before start.", "aborted"); return stream; } try { const messages = fitWorkerReplayImages(request.context.messages, createInferenceRequestMeasure(request), adapter.computerContextEpoch?.frameToolCallId); if (!messages) throw new Error(request.context.messages.some((message) => message.role === "assistant" && message.providerReplay !== void 0) ? `${WORKER_PROVIDER_REPLAY_LOCAL_RETRY_MESSAGE} (inference payload limit)` : "Worker inference context exceeds the image transport limit. Use fewer or smaller images in this turn, then retry."); request = { ...request, context: { ...request.context, messages } }; if (adapter.computerContextEpoch) invalidateComputerFrameIfMissing({ contextEpoch: adapter.computerContextEpoch, messages: messages.filter((message) => message.role === "toolResult") }); } catch (error) { fail(error); return stream; } inferenceRequest.signal?.addEventListener("abort", abort, { once: true }); adapter.client.start(request, { onStreamGap: () => { streamHasGap = true; }, onEvent: (event) => { const projected = processInferenceEvent(event, partial, toolArgumentPreviewSchedules, streamHasGap); if (projected) stream.push(projected); } }).then((outcome) => { if (settled) return; if (outcome.type === "done") { settled = true; if (!isWorkerTranscriptMessageFrameSafe(outcome.message)) { const message = emptyAssistantMessage(adapter.modelRef); message.stopReason = "error"; message.errorMessage = "Worker inference result exceeds the transcript message limit."; message.usage = structuredClone(outcome.message.usage); stream.push({ type: "error", reason: "error", error: message }); stream.end(); return; } const reason = outcome.message.stopReason; const message = structuredClone(outcome.message); stream.push({ type: "done", reason, message }); stream.end(); return; } finishError(outcome.message, outcome.reason === "cancelled" ? "aborted" : "error", outcome.usage); }).catch(fail).finally(() => { inferenceRequest.signal?.removeEventListener("abort", abort); }); return stream; }; } //#endregion export { createWorkerInferenceStreamAdapter };