openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
327 lines (326 loc) • 10.5 kB
JavaScript
import "./worker-protocol-primitives-Dh40okcI.js";
import { t as event_stream_exports } from "./event-stream-CPdae0II.js";
import { n as invalidateComputerFrameIfMissing } from "./computer-tool-B5vR3VVG.js";
import { i as isWorkerTranscriptMessageFrameSafe, t as WORKER_PROVIDER_REPLAY_LOCAL_RETRY_MESSAGE } from "./transcript-message-DP74Q5JC.js";
import { t as fitWorkerReplayImages } from "./replay-message-window-OY0cC2EN.js";
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: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0
}
},
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": {
const content = partial.content[event.contentIndex];
if (content?.type !== "text") {
if (tolerateMissingState) return;
throw new Error("worker inference text delta has no active text block");
}
content.text += event.delta;
return {
type: "text_delta",
contentIndex: event.contentIndex,
delta: event.delta,
partial
};
}
case "text_end": {
const content = partial.content[event.contentIndex];
if (content?.type !== "text") {
if (tolerateMissingState) return;
throw new Error("worker inference text end has no active text block");
}
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": {
const content = partial.content[event.contentIndex];
if (content?.type !== "thinking") {
if (tolerateMissingState) return;
throw new Error("worker inference thinking delta has no active thinking block");
}
content.thinking += event.delta;
return {
type: "thinking_delta",
contentIndex: event.contentIndex,
delta: event.delta,
partial
};
}
case "thinking_end": {
const content = partial.content[event.contentIndex];
if (content?.type !== "thinking") {
if (tolerateMissingState) return;
throw new Error("worker inference thinking end has no active thinking block");
}
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": {
const content = partial.content[event.contentIndex];
if (content?.type !== "toolCall") {
if (tolerateMissingState) return;
throw new Error("worker inference tool delta has no active tool call");
}
const streaming = content;
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
};
}
case "toolcall_end": {
const content = partial.content[event.contentIndex];
if (content?.type !== "toolCall") {
if (tolerateMissingState) return;
throw new Error("worker inference tool end has no active tool call");
}
content.arguments = parseTerminalToolCallArguments(content.partialJson);
toolArgumentPreviewSchedules.delete(event.contentIndex);
delete content.partialJson;
return {
type: "toolcall_end",
contentIndex: event.contentIndex,
toolCall: content,
partial
};
}
}
}
function terminalErrorMessage(partial, outcome) {
partial.stopReason = outcome.reason === "cancelled" ? "aborted" : "error";
partial.errorMessage = outcome.message;
if (outcome.usage) partial.usage = structuredClone(outcome.usage);
return partial;
}
function transcriptSafeErrorMessage(modelRef, message) {
if (isWorkerTranscriptMessageFrameSafe(message)) return message;
const replacement = emptyAssistantMessage(modelRef);
replacement.stopReason = message.stopReason === "aborted" ? "aborted" : "error";
replacement.errorMessage = "Worker inference result exceeds the transcript message limit.";
return replacement;
}
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 fail = (error) => {
if (settled) return;
settled = true;
partial.stopReason = inferenceRequest.signal?.aborted ? "aborted" : "error";
partial.errorMessage = error instanceof Error ? error.message : String(error);
stream.push({
type: "error",
reason: partial.stopReason,
error: transcriptSafeErrorMessage(adapter.modelRef, partial)
});
stream.end();
};
const finishAborted = () => {
if (settled) return;
settled = true;
partial.stopReason = "aborted";
partial.errorMessage = "Worker inference aborted.";
stream.push({
type: "error",
reason: "aborted",
error: transcriptSafeErrorMessage(adapter.modelRef, partial)
});
stream.end();
};
const abort = () => {
adapter.client.cancel(identity).catch(() => void 0).finally(finishAborted);
};
if (inferenceRequest.signal?.aborted) {
partial.stopReason = "aborted";
partial.errorMessage = "Worker inference aborted before start.";
stream.push({
type: "error",
reason: "aborted",
error: transcriptSafeErrorMessage(adapter.modelRef, partial)
});
stream.end();
return stream;
}
try {
const messages = fitWorkerReplayImages(request.context.messages, (candidateMessages) => Buffer.byteLength(JSON.stringify({
type: "req",
id: "00000000-0000-4000-8000-000000000000",
method: "worker.inference.start",
params: {
...request,
context: {
...request.context,
messages: candidateMessages
}
}
}), "utf8"), 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;
settled = true;
if (outcome.type === "done") {
if (!isWorkerTranscriptMessageFrameSafe(outcome.message)) {
const message = emptyAssistantMessage(adapter.modelRef);
message.stopReason = "error";
message.errorMessage = "Worker inference result exceeds the transcript message limit.";
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;
}
const message = transcriptSafeErrorMessage(adapter.modelRef, terminalErrorMessage(partial, outcome));
const reason = outcome.reason === "cancelled" ? "aborted" : "error";
stream.push({
type: "error",
reason,
error: message
});
stream.end();
}).catch(fail).finally(() => {
inferenceRequest.signal?.removeEventListener("abort", abort);
});
return stream;
};
}
//#endregion
export { createWorkerInferenceStreamAdapter };