UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

292 lines (291 loc) 12 kB
import { r as loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime-BjmVS_9G.js"; import { m as wrapProviderSimpleCompletionStreamFn, r as getModelProviderRuntimePluginHandle } from "./provider-hook-runtime-DgKv_Z8O.js"; import { r as resolveProviderRequestCapabilities } from "./provider-attribution-CDS-94hQ.js"; import { a as getModelProviderRequestRouteFacts, f as resolveProviderRequestPolicyConfig, o as getModelProviderRequestTransport, r as attachModelProviderRequestTransport, s as inheritModelProviderRequestRouteFacts } from "./provider-request-config-DIOYidiO.js"; import { D as resolveProviderStreamFn, j as resolveProviderTransportTurnStateWithPlugin } from "./provider-runtime-BRJDPNgk.js"; import "./ai-transport-host-XkVlFqBU.js"; import { o as getModelProviderLocalService, t as attachModelProviderLocalService } from "./provider-local-service-D9Gl9HJT.js"; import { t as event_stream_exports } from "./event-stream-CPdae0II.js"; import { c as hasCopilotVisionInput, o as buildCopilotDynamicHeaders } from "./copilot-dynamic-headers-C42FH9jo.js"; import { r as buildStreamErrorAssistantMessage } from "./stream-message-shared-Cyrn1UHN.js"; import { n as repairToolUseResultPairing } from "./session-transcript-repair-CzBxxYMT.js"; import { configureAiTransportHost, getAiTransportHost } from "@openclaw/ai"; import { resolveModelBoundThinkingReplayMode } from "@openclaw/ai/internal/anthropic"; //#region src/agents/anthropic-vertex-stream.ts /** * Anthropic Vertex stream facade. * Keeps Vertex-specific provider implementation in the bundled provider plugin * while core imports a small stable factory. */ function loadAnthropicVertexStreamFacade() { return loadBundledPluginPublicSurfaceModuleSync({ dirName: "anthropic-vertex", artifactBasename: "api.js" }); } /** Creates an Anthropic Vertex stream function through the bundled provider facade. */ function createAnthropicVertexStreamFnForModel(model, env = process.env) { return loadAnthropicVertexStreamFacade().createAnthropicVertexStreamFnForModel(model, env); } //#endregion //#region src/agents/custom-api-registry.ts const CUSTOM_API_SOURCE_PREFIX = "openclaw-custom-api:"; /** Returns the registry source id used for a custom API stream function. */ function getCustomApiRegistrySourceId(api) { return `${CUSTOM_API_SOURCE_PREFIX}${api}`; } function adaptCustomStream(model, stream) { if (!(stream instanceof Promise)) return stream; const adapted = (0, event_stream_exports.createAssistantMessageEventStream)(); (async () => { try { const resolved = await stream; for await (const event of resolved) adapted.push(event); adapted.end(await resolved.result()); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const message = buildStreamErrorAssistantMessage({ model, errorMessage }); adapted.push({ type: "error", reason: "error", error: message }); } })(); return adapted; } /** Registers a custom API stream function when no provider already owns it. */ function ensureCustomApiRegistered(registry, api, streamFn) { if (registry.getApiProvider(api)) return false; registry.registerApiProvider({ api, stream: (model, context, options) => adaptCustomStream(model, streamFn(model, context, options)), streamSimple: (model, context, options) => adaptCustomStream(model, streamFn(model, context, options)) }, getCustomApiRegistrySourceId(api)); return true; } //#endregion //#region src/agents/replay-turn-classification.ts /** Returns true when an assistant turn contains only provider reasoning and blank text. */ function hasOnlyAssistantReasoningContent(message) { if (message.role !== "assistant") return false; const content = Array.isArray(message.content) ? message.content : message.content != null && typeof message.content === "object" ? [message.content] : []; let hasThinking = false; for (const block of content) { if (!block || typeof block !== "object") return false; const record = block; if (record.type === "thinking" || record.type === "redacted_thinking") { hasThinking = true; continue; } if (record.type === "text" && typeof record.text === "string" && !record.text.trim()) continue; return false; } return hasThinking; } /** Returns true when a token-limited turn contains only incomplete provider reasoning. */ function isReasoningOnlyLengthAssistantTurn(message) { return message.stopReason === "length" && hasOnlyAssistantReasoningContent(message); } //#endregion //#region src/agents/transport-message-transform.ts const SYNTHETIC_TOOL_RESULT_APIS = /* @__PURE__ */ new Set([ "anthropic-messages", "openclaw-anthropic-messages-transport", "bedrock-converse-stream", "google-generative-ai", "openclaw-google-generative-ai-transport", "openai-responses", "openai-chatgpt-responses", "azure-openai-responses", "openclaw-openai-responses-transport", "openclaw-openai-chatgpt-responses-transport", "openclaw-azure-openai-responses-transport" ]); const OPENAI_RESPONSES_ABORTED_OUTPUT_APIS = /* @__PURE__ */ new Set([ "openai-responses", "openai-chatgpt-responses", "azure-openai-responses", "openclaw-openai-responses-transport", "openclaw-openai-chatgpt-responses-transport", "openclaw-azure-openai-responses-transport" ]); function defaultAllowSyntheticToolResults(modelApi) { return SYNTHETIC_TOOL_RESULT_APIS.has(modelApi); } function isFailedAssistantTurn(message) { if (message.role !== "assistant") return false; return message.stopReason === "error" || message.stopReason === "aborted" || isReasoningOnlyLengthAssistantTurn(message); } function failedAssistantHasToolCalls(message) { return message.role === "assistant" && (message.stopReason === "error" || message.stopReason === "aborted") && Array.isArray(message.content) && message.content.some((block) => block.type === "toolCall"); } /** Transforms transcript messages into a provider-safe replay context. */ function transformTransportMessages(messages, model, normalizeToolCallId, options) { const allowSyntheticToolResults = defaultAllowSyntheticToolResults(model.api); const syntheticToolResultText = OPENAI_RESPONSES_ABORTED_OUTPUT_APIS.has(model.api) ? "aborted" : "No result provided"; const toolCallIdMap = /* @__PURE__ */ new Map(); let hasCrossModelAsyncCalls = false; const transformed = messages.map((msg) => { if (msg.role === "user") return msg; if (msg.role === "toolResult") { const normalizedId = toolCallIdMap.get(msg.toolCallId); return normalizedId && normalizedId !== msg.toolCallId ? { ...msg, toolCallId: normalizedId } : msg; } if (msg.role !== "assistant") return msg; const modelBoundThinkingReplayMode = resolveModelBoundThinkingReplayMode({ source: { provider: msg.provider, api: msg.api, modelId: msg.model, responseModelId: msg.responseModel }, target: { provider: model.provider, api: model.api, modelId: model.id, modelParams: model.params } }); const isSameModel = modelBoundThinkingReplayMode === "preserve" || msg.provider === model.provider && msg.api === model.api && msg.model === model.id; const sourceContent = Array.isArray(msg.content) ? msg.content : msg.content != null && typeof msg.content === "object" ? [msg.content] : []; const content = []; for (const block of sourceContent) { if (block.type === "thinking") { if (modelBoundThinkingReplayMode === "drop") continue; if (block.redacted) { if (isSameModel) content.push(block); continue; } if (isSameModel && block.thinkingSignature) { content.push(block); continue; } if (!block.thinking.trim()) continue; content.push(isSameModel ? block : { type: "text", text: block.thinking }); continue; } if (block.type === "text") { content.push(isSameModel ? block : { type: "text", text: block.text }); continue; } if (block.type !== "toolCall") { content.push(block); continue; } let normalizedToolCall = block; if (!isSameModel && block.async) { hasCrossModelAsyncCalls = true; normalizedToolCall = { ...normalizedToolCall }; delete normalizedToolCall.async; } if (!isSameModel && block.thoughtSignature && options?.preserveCrossModelToolCallThoughtSignature !== true) { normalizedToolCall = { ...normalizedToolCall }; delete normalizedToolCall.thoughtSignature; } if ((!isSameModel || options?.normalizeSameModelToolCallIds === true) && normalizeToolCallId) { const normalizedId = normalizeToolCallId(block.id, model, msg); if (normalizedId !== block.id) { toolCallIdMap.set(block.id, normalizedId); normalizedToolCall = { ...normalizedToolCall, id: normalizedId }; } } content.push(normalizedToolCall); } return { ...msg, content }; }); const requiresPairing = allowSyntheticToolResults || hasCrossModelAsyncCalls; const replayable = transformed.filter((_, index) => { const original = messages[index]; if (!original) return true; return requiresPairing ? !isFailedAssistantTurn(original) || failedAssistantHasToolCalls(original) : !isFailedAssistantTurn(original); }); if (!requiresPairing) return replayable; return repairToolUseResultPairing(replayable, { erroredAssistantResultPolicy: "drop", missingToolResultText: syntheticToolResultText, preserveUnframedToolResults: options?.preserveUnframedToolResults }).messages; } //#endregion //#region src/agents/ai-transport-runtime-host.ts let configured = false; /** Installs the agent and plugin ports only on paths that execute provider runtime. */ function configureAiTransportRuntimeHost() { if (configured) return; const host = getAiTransportHost(); configureAiTransportHost({ ...host, plugin: { ...host.plugin, resolveProviderStream: (params) => resolveProviderStreamFn({ ...params, config: params.config, context: { ...params.context, config: params.context.config, model: params.context.model } }), resolveTransportTurnState: (params) => resolveProviderTransportTurnStateWithPlugin({ ...params, config: params.config, runtimeHandle: getModelProviderRuntimePluginHandle(params.context.model), context: { ...params.context, model: params.context.model } }), wrapSimpleCompletionStream: (params) => wrapProviderSimpleCompletionStreamFn({ ...params, config: params.config, context: { ...params.context, config: params.context.config, model: params.context.model } }), createAnthropicVertexStream: createAnthropicVertexStreamFnForModel }, buildCopilotDynamicHeaders: (messages) => buildCopilotDynamicHeaders({ messages, hasImages: hasCopilotVisionInput(messages) }), resolveProviderRequestCapabilities: (input) => getModelProviderRequestRouteFacts(input.model ?? {})?.capabilities ?? resolveProviderRequestCapabilities(input), resolveProviderRequestHeaders: (input) => resolveProviderRequestPolicyConfig({ ...input, routeFacts: getModelProviderRequestRouteFacts(input.model ?? {}), capability: "llm", transport: "stream" }).headers, requiresManagedTransport: (model) => { const request = getModelProviderRequestTransport(model); return Boolean(request?.proxy || request?.tls || getModelProviderLocalService(model)); }, inheritManagedTransport: (source, target) => inheritModelProviderRequestRouteFacts(source, attachModelProviderLocalService(attachModelProviderRequestTransport(target, getModelProviderRequestTransport(source)), getModelProviderLocalService(source))), transformTransportMessages, registerCustomApi: ensureCustomApiRegistered }); configured = true; } configureAiTransportRuntimeHost(); //#endregion export { ensureCustomApiRegistered as a, isReasoningOnlyLengthAssistantTurn as i, transformTransportMessages as n, createAnthropicVertexStreamFnForModel as o, hasOnlyAssistantReasoningContent as r, configureAiTransportRuntimeHost as t };