UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,196 lines (1,195 loc) 45.9 kB
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js"; import { d as supportsClaudeNativeMaxEffort, f as supportsClaudeNativeXhighEffort, l as resolveClaudeNativeThinkingLevelMap, u as supportsClaudeAdaptiveThinking } from "./src-M7TBQdDX.js"; import { A as resolveProviderStreamFn } from "./provider-runtime-CJtW0nDR.js"; import { c as hasCopilotVisionInput, o as buildCopilotDynamicHeaders } from "./copilot-dynamic-headers-CF9zPORX.js"; import { n as resolveProviderEndpoint } from "./provider-attribution-GEHCfopl.js"; import { i as getModelProviderRequestTransport } from "./provider-request-config-0Zg8QIAI.js"; import { a as usesClaudeFable5MessagesContract, l as calculateCost, r as requiresClaudeAdaptiveThinking, u as clampThinkingLevel } from "./sanitize-unicode-CgzZKr22.js"; import { c as registerApiProvider, o as getApiProvider } from "./stream-4H1GSjKe.js"; import { n as getEnvApiKey } from "./env-api-keys-8q9bEA0v.js"; import { n as parseStreamingJson } from "./json-parse-CydVzlvP.js"; import { t as MALFORMED_STREAMING_FRAGMENT_ERROR_MESSAGE } from "./assistant-error-format-CuUvHfKt.js"; import { a as coerceTransportToolCallArguments, c as failTransportStream, d as sanitizeNonEmptyTransportPayloadText, f as sanitizeTransportPayloadText, i as createOpenAIResponsesTransportStreamFn, l as finalizeTransportStream, m as buildGuardedModelFetch, n as createAzureOpenAIResponsesTransportStreamFn, o as createEmptyTransportUsage, p as transformTransportMessages, r as createOpenAICompletionsTransportStreamFn, s as createWritableTransportEventStream, u as mergeTransportHeaders } from "./openai-transport-stream-BVQx4NgZ.js"; import { r as getModelProviderLocalService } from "./provider-local-service-ZBHHbJ8A.js"; import { i as applyAnthropicRefusal, r as createDeferredEventBuffer, t as notifyLlmRequestActivity } from "./llm-request-activity-wsKalIGX.js"; import { i as resolveAnthropicPayloadPolicy, n as applyAnthropicPayloadPolicyToParams } from "./anthropic-payload-policy-ZFzBdZJv.js"; import { t as parseJsonObjectPreservingUnsafeIntegers } from "./json-unsafe-integers-DpTiDHBw.js"; //#region src/agents/anthropic-transport-stream.ts /** * Native Anthropic Messages streaming transport. * Converts OpenClaw contexts/tools into Anthropic payloads, streams SSE events * back into runtime output blocks, and applies provider request policy. */ const CLAUDE_CODE_VERSION = "2.1.75"; const CLAUDE_CODE_TOOL_LOOKUP = new Map([ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch" ].map((tool) => [normalizeLowercaseStringOrEmpty(tool), tool])); function resolveAnthropicRequestModelId(model) { if (isDirectAnthropicModel(model) && /^anthropic\//i.test(model.id)) return model.id.replace(/^anthropic\//i, ""); return model.id; } const EMPTY_ANTHROPIC_MESSAGES_FALLBACK_TEXT = "."; function normalizeAnthropicToolChoice(model, toolChoice) { if (requiresClaudeAdaptiveThinking(model) && (toolChoice === "any" || typeof toolChoice === "object" && toolChoice.type === "tool")) return { type: "auto" }; return typeof toolChoice === "string" ? { type: toolChoice } : toolChoice; } function supportsNativeXhighEffort(model) { return supportsClaudeNativeXhighEffort(model); } function supportsAdaptiveThinking(model) { return supportsClaudeAdaptiveThinking(model); } function mapThinkingLevelToEffort(level, model) { const thinkingLevelMap = resolveClaudeNativeThinkingLevelMap(model); const resolvedLevel = clampThinkingLevel({ ...model, ...typeof model.params?.canonicalModelId === "string" ? { reasoning: true } : {}, ...thinkingLevelMap ? { thinkingLevelMap } : {} }, level); const mapped = thinkingLevelMap?.[resolvedLevel]; if (typeof mapped === "string") return mapped; switch (resolvedLevel) { case "off": case "minimal": case "low": return "low"; case "medium": return "medium"; case "xhigh": return supportsNativeXhighEffort(model) ? "xhigh" : "high"; case "max": return supportsClaudeNativeMaxEffort(model) ? "max" : "high"; default: return "high"; } } function clampReasoningLevel(level) { return level === "xhigh" || level === "max" ? "high" : level; } function resolvePositiveAnthropicMaxTokens(value) { if (typeof value !== "number" || !Number.isFinite(value)) return; const floored = Math.floor(value); return floored > 0 ? floored : void 0; } function resolveAnthropicMessagesMaxTokens(params) { const requested = resolvePositiveAnthropicMaxTokens(params.requestedMaxTokens); if (requested !== void 0) return requested; const modelMax = resolvePositiveAnthropicMaxTokens(params.modelMaxTokens); return modelMax !== void 0 ? Math.min(modelMax, 32e3) : void 0; } function adjustMaxTokensForThinking(params) { const budgets = { minimal: 1024, low: 2048, medium: 8192, high: 16384, ...params.customBudgets }; const minOutputTokens = 1024; let thinkingBudget = budgets[clampReasoningLevel(params.reasoningLevel)]; const maxTokens = Math.min(params.baseMaxTokens + thinkingBudget, params.modelMaxTokens); if (maxTokens <= thinkingBudget) thinkingBudget = Math.max(0, maxTokens - minOutputTokens); return { maxTokens, thinkingBudget }; } function isAnthropicOAuthToken(apiKey) { return apiKey.includes("sk-ant-oat"); } function hasBearerAuthorizationHeader(headers) { if (!headers) return false; return Object.entries(headers).some(([key, value]) => key.toLowerCase() === "authorization" && /^bearer\s+\S+/i.test(value.trim())); } function usesFoundryBearerAuth(model) { return model.provider === "microsoft-foundry" && (model.authHeader === true || hasBearerAuthorizationHeader(model.headers)); } function omitFoundryBearerCredentialHeaders(headers) { if (!headers) return; const next = {}; for (const [key, value] of Object.entries(headers)) { const lower = key.toLowerCase(); if (lower === "authorization" || lower === "x-api-key" || lower === "api-key") continue; next[key] = value; } return Object.keys(next).length > 0 ? next : void 0; } function isDirectAnthropicModel(model) { if (normalizeLowercaseStringOrEmpty(model.provider) !== "anthropic") return false; const endpointClass = resolveProviderEndpoint(model.baseUrl).endpointClass; return endpointClass === "default" || endpointClass === "anthropic-public"; } function isKimiAnthropicProvider(provider) { return /^kimi(?:-|$)/.test(normalizeLowercaseStringOrEmpty(provider ?? "")); } function supportsReasoningContentReplay(model) { return resolveProviderEndpoint(model.baseUrl).endpointClass === "xiaomi-native"; } function buildAnthropicBetaHeader(model, betaFeatures, params) { if (!isDirectAnthropicModel(model)) return; return params.oauth ? `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}` : betaFeatures.join(","); } function toClaudeCodeName(name) { return CLAUDE_CODE_TOOL_LOOKUP.get(normalizeLowercaseStringOrEmpty(name)) ?? name; } function fromClaudeCodeName(name, tools) { if (tools && tools.length > 0) { const lowerName = normalizeLowercaseStringOrEmpty(name); const matchedTool = tools.find((tool) => normalizeLowercaseStringOrEmpty(tool.name) === lowerName); if (matchedTool) return matchedTool.name; } return name; } function convertContentBlocks(content) { if (!content.some((item) => item.type === "image")) return sanitizeNonEmptyTransportPayloadText(content.map((item) => "text" in item ? item.text : "").join("\n")); const blocks = []; let hasTextBlock = false; for (const block of content) if (block.type === "text") { const text = sanitizeTransportPayloadText(block.text); if (text.trim().length > 0) { blocks.push({ type: "text", text }); hasTextBlock = true; } } else blocks.push({ type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } }); if (!hasTextBlock) return [{ type: "text", text: "(see attached image)" }, ...blocks]; return blocks; } function normalizeToolCallId(id) { return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64); } function convertAnthropicMessages(messages, model, isOAuthToken, options) { const params = []; const allowReasoningContentReplay = options?.allowReasoningContentReplay === true; const transformedMessages = transformTransportMessages(messages, model, normalizeToolCallId); for (let i = 0; i < transformedMessages.length; i += 1) { const msg = transformedMessages[i]; if (msg.role === "user") { if (typeof msg.content === "string") { if (msg.content.trim().length > 0) params.push({ role: "user", content: sanitizeTransportPayloadText(msg.content) }); continue; } const blocks = msg.content.map((item) => item.type === "text" ? { type: "text", text: sanitizeTransportPayloadText(item.text) } : { type: "image", source: { type: "base64", media_type: item.mimeType, data: item.data } }); let filteredBlocks = model.input.includes("image") ? blocks : blocks.filter((block) => block.type !== "image"); filteredBlocks = filteredBlocks.filter((block) => block.type !== "text" || block.text.trim().length > 0); if (filteredBlocks.length === 0) continue; params.push({ role: "user", content: filteredBlocks }); continue; } if (msg.role === "assistant") { const blocks = []; const reasoningContent = []; for (const block of msg.content) { if (block.type === "text") { if (block.text.trim().length > 0) blocks.push({ type: "text", text: sanitizeTransportPayloadText(block.text) }); continue; } if (block.type === "thinking") { if (block.redacted) { blocks.push({ type: "redacted_thinking", data: block.thinkingSignature }); continue; } const thinkingSignature = block.thinkingSignature?.trim(); const hasNativeThinkingSignature = Boolean(thinkingSignature) && thinkingSignature !== "reasoning_content"; if (block.thinking.trim().length === 0 && !hasNativeThinkingSignature) continue; if (!thinkingSignature) blocks.push({ type: "text", text: sanitizeTransportPayloadText(block.thinking) }); else { const thinking = thinkingSignature === "reasoning_content" ? sanitizeTransportPayloadText(block.thinking) : block.thinking; if (thinkingSignature === "reasoning_content") { if (allowReasoningContentReplay) { blocks.push({ type: "thinking", thinking, signature: thinkingSignature }); reasoningContent.push(thinking); } continue; } blocks.push({ type: "thinking", thinking, signature: thinkingSignature }); } continue; } if (block.type === "toolCall") blocks.push({ type: "tool_use", id: block.id, name: isOAuthToken ? toClaudeCodeName(block.name) : block.name, input: coerceTransportToolCallArguments(block.arguments) }); } if (blocks.length > 0) { const assistantMsg = { role: "assistant", content: blocks }; if (reasoningContent.length > 0) assistantMsg.reasoning_content = reasoningContent.join("\n"); else if (allowReasoningContentReplay) blocks.unshift({ type: "thinking", thinking: "", signature: "reasoning_content" }); params.push(assistantMsg); } continue; } if (msg.role === "toolResult") { const toolResult = msg; const toolResults = [{ type: "tool_result", tool_use_id: toolResult.toolCallId, content: convertContentBlocks(toolResult.content), is_error: toolResult.isError }]; let j = i + 1; while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") { const nextMsg = transformedMessages[j]; toolResults.push({ type: "tool_result", tool_use_id: nextMsg.toolCallId, content: convertContentBlocks(nextMsg.content), is_error: nextMsg.isError }); j += 1; } i = j - 1; params.push({ role: "user", content: toolResults }); } } return params; } function ensureNonEmptyAnthropicMessages(messages) { return messages.length > 0 ? messages : [{ role: "user", content: EMPTY_ANTHROPIC_MESSAGES_FALLBACK_TEXT }]; } function convertAnthropicTools(tools, isOAuthToken) { if (!tools) return []; const converted = []; for (const tool of tools) { const parameters = tool.parameters && typeof tool.parameters === "object" && !Array.isArray(tool.parameters) ? tool.parameters : void 0; if (!parameters) continue; converted.push({ name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name, description: tool.description, input_schema: { type: "object", properties: parameters.properties || {}, required: parameters.required || [] } }); } return converted; } function parseAnthropicToolCallArguments(inputJson) { return parseJsonObjectPreservingUnsafeIntegers(inputJson) ?? parseStreamingJson(inputJson); } function mapStopReason(reason) { switch (reason) { case "end_turn": return "stop"; case "max_tokens": return "length"; case "tool_use": return "toolUse"; case "pause_turn": return "stop"; case "refusal": case "sensitive": return "error"; case "stop_sequence": return "stop"; default: throw new Error(`Unhandled stop reason: ${String(reason)}`); } } const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com"; /** Resolve the effective Anthropic API base URL from model or environment. */ function resolveAnthropicBaseUrl(baseUrl) { return baseUrl?.trim() || process.env.ANTHROPIC_BASE_URL?.trim() || DEFAULT_ANTHROPIC_BASE_URL; } /** Resolve the Anthropic Messages endpoint URL for the effective base URL. */ function resolveAnthropicMessagesUrl(baseUrl) { const normalized = resolveAnthropicBaseUrl(baseUrl).replace(/\/+$/, ""); return normalized.endsWith("/v1") ? `${normalized}/messages` : `${normalized}/v1/messages`; } function withEffectiveAnthropicBaseUrl(model) { const baseUrl = resolveAnthropicBaseUrl(model.baseUrl); return baseUrl === model.baseUrl ? model : { ...model, baseUrl }; } function createAbortError(signal) { const reason = signal.reason; if (reason instanceof Error) return reason; const error = reason === void 0 ? /* @__PURE__ */ new Error("Request was aborted") : new Error("Request was aborted", { cause: reason }); error.name = "AbortError"; return error; } function readAnthropicSseChunk(reader, signal) { if (!signal) return reader.read(); return new Promise((resolve, reject) => { let settled = false; const onAbort = () => { if (settled) return; settled = true; signal.removeEventListener("abort", onAbort); reader.cancel(signal.reason).catch(() => void 0); reject(createAbortError(signal)); }; if (signal.aborted) { onAbort(); return; } signal.addEventListener("abort", onAbort, { once: true }); reader.read().then((result) => { if (settled) return; settled = true; signal.removeEventListener("abort", onAbort); resolve(result); }, (error) => { if (settled) return; settled = true; signal.removeEventListener("abort", onAbort); reject(toLintErrorObject(error, "Non-Error rejection")); }); }); } function parseAnthropicSseEventData(data) { try { return JSON.parse(data); } catch (error) { if (error instanceof SyntaxError) throw new Error(MALFORMED_STREAMING_FRAGMENT_ERROR_MESSAGE, { cause: error }); throw error; } } async function* parseAnthropicSseBody(body, signal) { const reader = body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let completed = false; try { while (true) { const { done, value } = await readAnthropicSseChunk(reader, signal); if (done) { completed = true; break; } buffer = `${buffer}${decoder.decode(value, { stream: true })}`.replaceAll("\r\n", "\n"); let frameEnd = buffer.indexOf("\n\n"); while (frameEnd >= 0) { const frame = buffer.slice(0, frameEnd); buffer = buffer.slice(frameEnd + 2); const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n"); if (data && data !== "[DONE]") yield parseAnthropicSseEventData(data); frameEnd = buffer.indexOf("\n\n"); } } const tail = `${buffer}${decoder.decode()}`.replaceAll("\r\n", "\n").trim(); if (tail) { const data = tail.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n"); if (data && data !== "[DONE]") yield parseAnthropicSseEventData(data); } } finally { if (!completed) await reader.cancel(signal?.reason).catch(() => void 0); reader.releaseLock(); } } function createAnthropicMessagesClient(params) { const url = resolveAnthropicMessagesUrl(params.baseURL); return { messages: { async *stream(body, options) { const headers = mergeTransportHeaders({ "content-type": "application/json", "anthropic-version": "2023-06-01", ...params.apiKey ? { "x-api-key": params.apiKey } : {}, ...params.authToken ? { authorization: `Bearer ${params.authToken}` } : {} }, params.defaultHeaders); const response = await params.fetch(url, { method: "POST", headers, body: JSON.stringify(body), signal: options?.signal }); if (!response.ok) { const detail = await response.text().catch(() => ""); throw new Error(detail || `Anthropic Messages request failed with HTTP ${response.status}`); } if (!response.body) return; yield* parseAnthropicSseBody(response.body, options?.signal); } } }; } function createAnthropicTransportClient(params) { const { model, context, apiKey, options } = params; const needsInterleavedBeta = (options?.interleavedThinking ?? true) && !supportsAdaptiveThinking(model); const fetch = isKimiAnthropicProvider(model.provider) && options?.thinkingEnabled === true ? buildGuardedModelFetch(model, void 0, { sanitizeSse: false }) : buildGuardedModelFetch(model); if (model.provider === "github-copilot") { const betaFeatures = needsInterleavedBeta ? ["interleaved-thinking-2025-05-14"] : []; return { client: createAnthropicMessagesClient({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, defaultHeaders: mergeTransportHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {} }, model.headers, buildCopilotDynamicHeaders({ messages: context.messages, hasImages: hasCopilotVisionInput(context.messages) }), options?.headers), fetch }), isOAuthToken: false }; } if (usesFoundryBearerAuth(model)) { const betaFeatures = needsInterleavedBeta ? ["interleaved-thinking-2025-05-14"] : []; return { client: createAnthropicMessagesClient({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, defaultHeaders: mergeTransportHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {} }, omitFoundryBearerCredentialHeaders(model.headers), options?.headers), fetch }), isOAuthToken: false }; } const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"]; if (needsInterleavedBeta) betaFeatures.push("interleaved-thinking-2025-05-14"); if (isAnthropicOAuthToken(apiKey)) { const betaHeader = buildAnthropicBetaHeader(model, betaFeatures, { oauth: true }); return { client: createAnthropicMessagesClient({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, defaultHeaders: mergeTransportHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaHeader ? { "anthropic-beta": betaHeader } : {}, "user-agent": `claude-cli/${CLAUDE_CODE_VERSION}`, "x-app": "cli" }, model.headers, options?.headers), fetch }), isOAuthToken: true }; } const betaHeader = buildAnthropicBetaHeader(model, betaFeatures, { oauth: false }); return { client: createAnthropicMessagesClient({ apiKey, baseURL: model.baseUrl, defaultHeaders: mergeTransportHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaHeader ? { "anthropic-beta": betaHeader } : {} }, model.headers, options?.headers), fetch }), isOAuthToken: false }; } function buildAnthropicParams(model, context, isOAuthToken, options) { const maxTokens = resolveAnthropicMessagesMaxTokens({ modelMaxTokens: model.maxTokens, requestedMaxTokens: options?.maxTokens }); if (maxTokens === void 0) throw new Error(`Anthropic Messages transport requires a positive maxTokens value for ${model.provider}/${model.id}`); const payloadPolicy = resolveAnthropicPayloadPolicy({ provider: model.provider, api: model.api, baseUrl: model.baseUrl, cacheRetention: options?.cacheRetention, enableCacheControl: true }); const params = { model: resolveAnthropicRequestModelId(model), messages: ensureNonEmptyAnthropicMessages(convertAnthropicMessages(context.messages, model, isOAuthToken, { allowReasoningContentReplay: supportsReasoningContentReplay(model) })), max_tokens: maxTokens, stream: true }; if (isOAuthToken) params.system = [{ type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude." }, ...context.systemPrompt ? [{ type: "text", text: sanitizeTransportPayloadText(context.systemPrompt) }] : []]; else if (context.systemPrompt) params.system = [{ type: "text", text: sanitizeTransportPayloadText(context.systemPrompt) }]; if (options?.temperature !== void 0 && !options.thinkingEnabled && !supportsNativeXhighEffort(model)) params.temperature = options.temperature; if (options?.stop !== void 0 && options.stop.length > 0) params.stop_sequences = options.stop; if (context.tools) params.tools = convertAnthropicTools(context.tools, isOAuthToken); const fable5 = usesClaudeFable5MessagesContract(model); if (fable5 || model.reasoning || supportsAdaptiveThinking(model)) { if (fable5 || options?.thinkingEnabled) if (supportsAdaptiveThinking(model)) { params.thinking = fable5 ? { type: "adaptive", display: "summarized" } : { type: "adaptive" }; const effort = options?.effort ?? (fable5 ? "high" : void 0); if (effort) params.output_config = { effort }; } else params.thinking = { type: "enabled", budget_tokens: options?.thinkingBudgetTokens || 1024 }; else if (options?.thinkingEnabled === false) params.thinking = { type: "disabled" }; } if (options?.metadata && typeof options.metadata.user_id === "string") params.metadata = { user_id: options.metadata.user_id }; if (options?.toolChoice) params.tool_choice = normalizeAnthropicToolChoice(model, options.toolChoice); applyAnthropicPayloadPolicyToParams(params, payloadPolicy); return params; } function resolveAnthropicTransportOptions(model, options, apiKey) { const baseMaxTokens = resolveAnthropicMessagesMaxTokens({ modelMaxTokens: model.maxTokens, requestedMaxTokens: options?.maxTokens }); if (baseMaxTokens === void 0) throw new Error(`Anthropic Messages transport requires a positive maxTokens value for ${model.provider}/${model.id}`); const reasoningModelMaxTokens = resolvePositiveAnthropicMaxTokens(model.maxTokens) ?? baseMaxTokens; const resolved = { temperature: options?.temperature, stop: options?.stop, maxTokens: baseMaxTokens, signal: options?.signal, apiKey, cacheRetention: options?.cacheRetention, sessionId: options?.sessionId, headers: options?.headers, onPayload: options?.onPayload, maxRetryDelayMs: options?.maxRetryDelayMs, metadata: options?.metadata, interleavedThinking: options?.interleavedThinking, toolChoice: options?.toolChoice, thinkingBudgets: options?.thinkingBudgets, reasoning: options?.reasoning }; if (!options?.reasoning) { resolved.thinkingEnabled = requiresClaudeAdaptiveThinking(model); if (resolved.thinkingEnabled) resolved.effort = "high"; return resolved; } if (supportsAdaptiveThinking(model)) { resolved.thinkingEnabled = true; resolved.effort = mapThinkingLevelToEffort(options.reasoning, model); return resolved; } const adjusted = adjustMaxTokensForThinking({ baseMaxTokens, modelMaxTokens: reasoningModelMaxTokens, reasoningLevel: options.reasoning, customBudgets: options.thinkingBudgets }); resolved.maxTokens = adjusted.maxTokens; resolved.thinkingEnabled = true; resolved.thinkingBudgetTokens = adjusted.thinkingBudget; return resolved; } /** Create the stream function used by Anthropic Messages transport models. */ function createAnthropicMessagesTransportStreamFn() { return (rawModel, context, rawOptions) => { const model = withEffectiveAnthropicBaseUrl(rawModel); const options = rawOptions; const { eventStream, stream } = createWritableTransportEventStream(); (async () => { const output = { role: "assistant", content: [], api: "anthropic-messages", provider: model.provider, model: model.id, usage: createEmptyTransportUsage(), stopReason: "stop", timestamp: Date.now() }; const refusalBuffer = usesClaudeFable5MessagesContract(model) ? createDeferredEventBuffer(stream, () => notifyLlmRequestActivity(options?.signal)) : void 0; const eventSink = refusalBuffer ?? stream; try { const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? ""; if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`); const transportOptions = resolveAnthropicTransportOptions(model, options, apiKey); const { client, isOAuthToken } = createAnthropicTransportClient({ model, context, apiKey, options: transportOptions }); let params = buildAnthropicParams(model, context, isOAuthToken, transportOptions); const nextParams = await transportOptions.onPayload?.(params, model); if (nextParams !== void 0) params = nextParams; const anthropicStream = client.messages.stream({ ...params, stream: true }, transportOptions.signal ? { signal: transportOptions.signal } : void 0); const blocks = output.content; const signatureDeltaIndexes = /* @__PURE__ */ new Set(); const allowReasoningContentReplay = supportsReasoningContentReplay(model); const reasoningContentThinkingBlocks = /* @__PURE__ */ new Map(); const reasoningContentTextBlocks = /* @__PURE__ */ new Map(); let sawMessageStop = false; const eventIndexKey = (eventIndex) => typeof eventIndex === "number" ? eventIndex : -1; const appendReasoningContentThinkingDelta = (eventIndex, rawText) => { if (typeof rawText !== "string") return false; const text = sanitizeTransportPayloadText(rawText); if (text.length === 0) return false; const key = eventIndexKey(eventIndex); let contentIndex = reasoningContentThinkingBlocks.get(key); let block = contentIndex === void 0 ? void 0 : output.content[contentIndex]; if (!block || block.type !== "thinking") { block = { type: "thinking", thinking: "", thinkingSignature: "reasoning_content" }; output.content.push(block); contentIndex = output.content.length - 1; reasoningContentThinkingBlocks.set(key, contentIndex); eventSink.push({ type: "thinking_start", contentIndex, partial: output }); } block.thinking += text; block.thinkingSignature = "reasoning_content"; eventSink.push({ type: "thinking_delta", contentIndex, delta: text, partial: output }); return true; }; const appendReasoningContentTextDelta = (eventIndex, rawText) => { if (typeof rawText !== "string") return false; const text = sanitizeTransportPayloadText(rawText); if (text.length === 0) return false; const key = eventIndexKey(eventIndex); let contentIndex = reasoningContentTextBlocks.get(key); let block = contentIndex === void 0 ? void 0 : output.content[contentIndex]; if (!block || block.type !== "text") { block = { type: "text", text: "" }; output.content.push(block); contentIndex = output.content.length - 1; reasoningContentTextBlocks.set(key, contentIndex); eventSink.push({ type: "text_start", contentIndex, partial: output }); } block.text += text; eventSink.push({ type: "text_delta", contentIndex, delta: text, partial: output }); return true; }; const finishReasoningContentSidecars = (eventIndex) => { const key = eventIndexKey(eventIndex); const thinkingContentIndex = reasoningContentThinkingBlocks.get(key); if (thinkingContentIndex !== void 0) { reasoningContentThinkingBlocks.delete(key); const block = output.content[thinkingContentIndex]; if (block?.type === "thinking") eventSink.push({ type: "thinking_end", contentIndex: thinkingContentIndex, content: block.thinking, partial: output }); } const textContentIndex = reasoningContentTextBlocks.get(key); if (textContentIndex === void 0) return; reasoningContentTextBlocks.delete(key); const block = output.content[textContentIndex]; if (block?.type === "text") eventSink.push({ type: "text_end", contentIndex: textContentIndex, content: block.text, partial: output }); }; for await (const event of anthropicStream) { if (event.type === "error") { const error = event.error; throw new Error(error?.message || "Anthropic Messages stream failed"); } if (event.type === "message_start") { const message = event.message; const usage = message?.usage ?? {}; output.responseId = typeof message?.id === "string" ? message.id : void 0; output.responseModel = typeof message?.model === "string" ? message.model : void 0; output.usage.input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0; output.usage.output = typeof usage.output_tokens === "number" ? usage.output_tokens : 0; output.usage.cacheRead = typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0; output.usage.cacheWrite = typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0; output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite; calculateCost(model, output.usage); eventSink.push({ type: "start", partial: output }); continue; } if (event.type === "message_stop") { sawMessageStop = true; continue; } if (event.type === "content_block_start") { const contentBlock = event.content_block; const index = typeof event.index === "number" ? event.index : -1; if (contentBlock?.type === "text") { const text = typeof contentBlock.text === "string" ? sanitizeTransportPayloadText(contentBlock.text) : ""; const block = { type: "text", text, index }; output.content.push(block); const contentIndex = output.content.length - 1; eventSink.push({ type: "text_start", contentIndex, partial: output }); if (text.length > 0) eventSink.push({ type: "text_delta", contentIndex, delta: text, partial: output }); continue; } if (contentBlock?.type === "thinking") { const thinking = typeof contentBlock.thinking === "string" ? contentBlock.thinking : ""; const block = { type: "thinking", thinking, thinkingSignature: typeof contentBlock.signature === "string" ? contentBlock.signature : "", index }; output.content.push(block); const contentIndex = output.content.length - 1; eventSink.push({ type: "thinking_start", contentIndex, partial: output }); if (thinking.length > 0) eventSink.push({ type: "thinking_delta", contentIndex, delta: thinking, partial: output }); continue; } if (contentBlock?.type === "redacted_thinking") { const block = { type: "thinking", thinking: "[Reasoning redacted]", thinkingSignature: typeof contentBlock.data === "string" ? contentBlock.data : "", redacted: true, index }; output.content.push(block); eventSink.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output }); continue; } if (contentBlock?.type === "tool_use") { const block = { type: "toolCall", id: typeof contentBlock.id === "string" ? contentBlock.id : "", name: typeof contentBlock.name === "string" ? isOAuthToken ? fromClaudeCodeName(contentBlock.name, context.tools) : contentBlock.name : "", arguments: contentBlock.input && typeof contentBlock.input === "object" ? contentBlock.input : {}, partialJson: "", index }; output.content.push(block); eventSink.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output }); } continue; } if (event.type === "content_block_delta") { const delta = event.delta; let index = blocks.findIndex((block) => block.index === event.index); let block = blocks[index]; if (allowReasoningContentReplay) { const appendedThinking = appendReasoningContentThinkingDelta(event.index, delta?.reasoning_content); const hasNativeAnthropicDelta = delta?.type === "text_delta" && typeof delta.text === "string" || delta?.type === "thinking_delta" && typeof delta.thinking === "string" || delta?.type === "input_json_delta" && typeof delta.partial_json === "string" || delta?.type === "signature_delta" && typeof delta.signature === "string"; let appendedContent = false; if (!hasNativeAnthropicDelta && typeof delta?.content === "string" && delta.content.length > 0) { const text = sanitizeTransportPayloadText(delta.content); if (text.length > 0) if (block?.type === "text") { block.text += text; eventSink.push({ type: "text_delta", contentIndex: index, delta: text, partial: output }); appendedContent = true; } else appendedContent = appendReasoningContentTextDelta(event.index, text); } if ((appendedThinking || appendedContent) && !hasNativeAnthropicDelta) continue; } if (!block && delta?.type === "text_delta" && typeof delta.text === "string") { block = { type: "text", text: "", index: typeof event.index === "number" ? event.index : blocks.length }; output.content.push(block); index = output.content.length - 1; eventSink.push({ type: "text_start", contentIndex: index, partial: output }); } if (block?.type === "text" && delta?.type === "text_delta" && typeof delta.text === "string") { block.text += delta.text; eventSink.push({ type: "text_delta", contentIndex: index, delta: delta.text, partial: output }); continue; } if (block?.type === "thinking" && delta?.type === "thinking_delta" && typeof delta.thinking === "string") { block.thinking += delta.thinking; eventSink.push({ type: "thinking_delta", contentIndex: index, delta: delta.thinking, partial: output }); continue; } if (block?.type === "toolCall" && delta?.type === "input_json_delta" && typeof delta.partial_json === "string") { const partialJson = `${block.partialJson ?? ""}${delta.partial_json}`; block.partialJson = partialJson; block.arguments = parseAnthropicToolCallArguments(partialJson); eventSink.push({ type: "toolcall_delta", contentIndex: index, delta: delta.partial_json, partial: output }); continue; } if (block?.type === "thinking" && delta?.type === "signature_delta" && typeof delta.signature === "string") { const signatureIndex = eventIndexKey(event.index); if (!signatureDeltaIndexes.has(signatureIndex)) { signatureDeltaIndexes.add(signatureIndex); block.thinkingSignature = ""; } block.thinkingSignature = (block.thinkingSignature || "") + delta.signature; } continue; } if (event.type === "content_block_stop") { const index = blocks.findIndex((block) => block.index === event.index); const block = blocks[index]; if (!block) { finishReasoningContentSidecars(event.index); continue; } delete block.index; if (block.type === "text") { eventSink.push({ type: "text_end", contentIndex: index, content: block.text, partial: output }); finishReasoningContentSidecars(event.index); continue; } if (block.type === "thinking") { eventSink.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: output }); finishReasoningContentSidecars(event.index); continue; } if (block.type === "toolCall") { if (typeof block.partialJson === "string" && block.partialJson.length > 0) block.arguments = parseAnthropicToolCallArguments(block.partialJson); delete block.partialJson; eventSink.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: output }); finishReasoningContentSidecars(event.index); } continue; } if (event.type === "message_delta") { const delta = event.delta; const usage = event.usage; if (delta?.stop_reason) if (delta.stop_reason === "refusal") applyAnthropicRefusal(output, delta.stop_details, model.provider); else output.stopReason = mapStopReason(delta.stop_reason); if (typeof usage?.input_tokens === "number") output.usage.input = usage.input_tokens; if (typeof usage?.output_tokens === "number") output.usage.output = usage.output_tokens; if (typeof usage?.cache_read_input_tokens === "number") output.usage.cacheRead = usage.cache_read_input_tokens; if (typeof usage?.cache_creation_input_tokens === "number") output.usage.cacheWrite = usage.cache_creation_input_tokens; output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite; calculateCost(model, output.usage); } } if (refusalBuffer && !sawMessageStop) throw new Error("Anthropic stream ended before message_stop"); if (transportOptions.signal?.aborted) throw new Error("Request was aborted"); if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error(output.errorMessage ?? "An unknown error occurred"); refusalBuffer?.flush(); finalizeTransportStream({ stream, output }); } catch (error) { if (refusalBuffer) { refusalBuffer.discard(); output.content = []; } failTransportStream({ stream, output, signal: options?.signal, error, cleanup: () => { for (const block of output.content) delete block.index; } }); } })(); return eventStream; }; } function toLintErrorObject(value, fallbackMessage) { if (value instanceof Error) return value; if (typeof value === "string") return new Error(value); const error = new Error(fallbackMessage, { cause: value }); if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value); return error; } //#endregion //#region src/agents/custom-api-registry.ts /** * Registers caller-supplied custom API stream functions with the LLM registry. */ 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}`; } /** Registers a custom API stream function when no provider already owns it. */ function ensureCustomApiRegistered(api, streamFn) { if (getApiProvider(api)) return false; registerApiProvider({ api, stream: (model, context, options) => streamFn(model, context, options), streamSimple: (model, context, options) => streamFn(model, context, options) }, getCustomApiRegistrySourceId(api)); return true; } //#endregion //#region src/agents/provider-transport-stream.ts const SUPPORTED_TRANSPORT_APIS = new Set([ "openai-responses", "openai-chatgpt-responses", "openai-completions", "azure-openai-responses", "anthropic-messages", "google-generative-ai" ]); const SIMPLE_TRANSPORT_API_ALIAS = { "openai-responses": "openclaw-openai-responses-transport", "openai-chatgpt-responses": "openclaw-openai-responses-transport", "openai-completions": "openclaw-openai-completions-transport", "azure-openai-responses": "openclaw-azure-openai-responses-transport", "anthropic-messages": "openclaw-anthropic-messages-transport", "google-generative-ai": "openclaw-google-generative-ai-transport" }; function createProviderOwnedGoogleTransportStreamFn(model, ctx) { return resolveProviderStreamFn({ provider: model.provider, config: ctx?.cfg, workspaceDir: ctx?.workspaceDir, env: ctx?.env, context: { config: ctx?.cfg, agentDir: ctx?.agentDir, workspaceDir: ctx?.workspaceDir, provider: model.provider, modelId: model.id, model } }) ?? resolveProviderStreamFn({ provider: "google", config: ctx?.cfg, workspaceDir: ctx?.workspaceDir, env: ctx?.env, context: { config: ctx?.cfg, agentDir: ctx?.agentDir, workspaceDir: ctx?.workspaceDir, provider: model.provider, modelId: model.id, model } }) ?? void 0; } function createSupportedTransportStreamFn(model, ctx) { switch (model.api) { case "openai-responses": case "openai-chatgpt-responses": return createOpenAIResponsesTransportStreamFn(); case "openai-completions": return createOpenAICompletionsTransportStreamFn(); case "azure-openai-responses": return createAzureOpenAIResponsesTransportStreamFn(); case "anthropic-messages": return createAnthropicMessagesTransportStreamFn(); case "google-generative-ai": return createProviderOwnedGoogleTransportStreamFn(model, ctx); default: return; } } function hasOpenClawTransportRequirement(model) { const request = getModelProviderRequestTransport(model); return Boolean(request?.proxy || request?.tls || getModelProviderLocalService(model)); } /** Returns whether OpenClaw has a managed transport implementation for this API. */ function isTransportAwareApiSupported(api) { return SUPPORTED_TRANSPORT_APIS.has(api); } /** Maps public model APIs to the internal transport API id used by simple runtime dispatch. */ function resolveTransportAwareSimpleApi(api) { return SIMPLE_TRANSPORT_API_ALIAS[api]; } /** Creates a managed transport stream only when request overrides require it. */ function createTransportAwareStreamFnForModel(model, ctx) { if (!hasOpenClawTransportRequirement(model)) return; if (!isTransportAwareApiSupported(model.api)) throw new Error(`Model-provider request.proxy/request.tls/localService is not yet supported for api "${model.api}"`); return createSupportedTransportStreamFn(model, ctx); } /** Creates a managed OpenClaw transport stream for explicit fallback/runtime callers. */ function createOpenClawTransportStreamFnForModel(model, ctx) { if (!isTransportAwareApiSupported(model.api)) return; return createSupportedTransportStreamFn(model, ctx); } function createBoundaryAwareStreamFnForModel(model, ctx) { if (!isTransportAwareApiSupported(model.api)) return; return createSupportedTransportStreamFn(model, ctx); } function prepareTransportAwareSimpleModel(model, ctx) { const streamFn = createTransportAwareStreamFnForModel(model, ctx); const alias = resolveTransportAwareSimpleApi(model.api); if (!streamFn || !alias) return model; return { ...model, api: alias }; } function buildTransportAwareSimpleStreamFn(model, ctx) { return createTransportAwareStreamFnForModel(model, ctx); } //#endregion //#region src/agents/provider-stream.ts /** Resolves and registers the stream function for a provider-backed model. */ function registerProviderStreamForModel(params) { const streamFn = resolveProviderStreamFn({ provider: params.model.provider, config: params.cfg, workspaceDir: params.workspaceDir, env: params.env, allowRuntimePluginLoad: params.allowRuntimePluginLoad, context: { config: params.cfg, agentDir: params.agentDir, workspaceDir: params.workspaceDir, provider: params.model.provider, modelId: params.model.id, model: params.model } }) ?? createTransportAwareStreamFnForModel(params.model, { cfg: params.cfg, agentDir: params.agentDir, workspaceDir: params.workspaceDir, env: params.env }); if (!streamFn) return; ensureCustomApiRegistered(params.model.api, streamFn); return streamFn; } //#endregion export { prepareTransportAwareSimpleModel as a, resolveAnthropicMessagesUrl as c, createOpenClawTransportStreamFnForModel as i, buildTransportAwareSimpleStreamFn as n, resolveTransportAwareSimpleApi as o, createBoundaryAwareStreamFnForModel as r, ensureCustomApiRegistered as s, registerProviderStreamForModel as t };