UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

900 lines (899 loc) 32.9 kB
import { d as supportsClaudeNativeMaxEffort, f as supportsClaudeNativeXhighEffort, l as resolveClaudeNativeThinkingLevelMap, u as supportsClaudeAdaptiveThinking } from "./src-M7TBQdDX.js"; import { t as AssistantMessageEventStream } from "./event-stream-ReMmOTzX.js"; import { a as usesClaudeFable5MessagesContract, l as calculateCost, n as transformMessages, o as adjustMaxTokensForThinking, r as requiresClaudeAdaptiveThinking, s as buildBaseOptions, t as sanitizeSurrogates, u as clampThinkingLevel } from "./sanitize-unicode-CgzZKr22.js"; import { n as getEnvApiKey } from "./env-api-keys-8q9bEA0v.js"; import { n as parseStreamingJson, t as parseJsonWithRepair } from "./json-parse-CydVzlvP.js"; import { a as stripSystemPromptCacheBoundary, i as splitSystemPromptCacheBoundary } from "./system-prompt-cache-boundary-vl0D_wqS.js"; import { t as headersToRecord } from "./headers-CaXpIDsu.js"; import { i as resolveCloudflareBaseUrl, n as hasCopilotVisionInput, t as buildCopilotDynamicHeaders } from "./github-copilot-headers-zVCJiu45.js"; import { i as applyAnthropicRefusal, r as createDeferredEventBuffer, t as notifyLlmRequestActivity } from "./llm-request-activity-wsKalIGX.js"; import Anthropic from "@anthropic-ai/sdk"; //#region src/llm/providers/anthropic.ts const ANTHROPIC_CACHE_CONTROL_LIMIT = 4; /** * Resolve cache retention preference. * Defaults to "short" and uses OPENCLAW_CACHE_RETENTION for backward compatibility. */ function resolveCacheRetention(cacheRetention) { if (cacheRetention) return cacheRetention; if (typeof process !== "undefined" && process.env.OPENCLAW_CACHE_RETENTION === "long") return "long"; return "short"; } function getCacheControl(model, cacheRetention) { const retention = resolveCacheRetention(cacheRetention); if (retention === "none") return { retention }; const ttl = retention === "long" && getAnthropicCompat(model).supportsLongCacheRetention ? "1h" : void 0; return { retention, cacheControl: { type: "ephemeral", ...ttl && { ttl } } }; } const claudeCodeVersion = "2.1.75"; const ccToolLookup = new Map([ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch" ].map((t) => [t.toLowerCase(), t])); const toClaudeCodeName = (name) => ccToolLookup.get(name.toLowerCase()) ?? name; const fromClaudeCodeName = (name, tools) => { if (tools && tools.length > 0) { const lowerName = name.toLowerCase(); const matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName); if (matchedTool) return matchedTool.name; } return name; }; /** * Convert content blocks to Anthropic API format */ function convertContentBlocks(content) { if (!content.some((c) => c.type === "image")) return sanitizeSurrogates(content.map((c) => c.text).join("\n")); const blocks = content.map((block) => { if (block.type === "text") return { type: "text", text: sanitizeSurrogates(block.text) }; return { type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } }; }); if (!blocks.some((b) => b.type === "text")) blocks.unshift({ type: "text", text: "(see attached image)" }); return blocks; } const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14"; const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14"; function getAnthropicCompat(model) { const isFireworks = model.provider === "fireworks"; const isCloudflareAiGatewayAnthropic = model.provider === "cloudflare-ai-gateway" && model.baseUrl.includes("anthropic"); return { supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? !isFireworks, supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? !isFireworks, sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? (isFireworks || isCloudflareAiGatewayAnthropic), supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? !isFireworks }; } function mergeHeaders(...headerSources) { const merged = {}; for (const headers of headerSources) if (headers) Object.assign(merged, headers); return merged; } 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; } const ANTHROPIC_MESSAGE_EVENTS = new Set([ "message_start", "message_delta", "message_stop", "content_block_start", "content_block_delta", "content_block_stop" ]); function flushSseEvent(state) { if (!state.event && state.data.length === 0) return null; const event = { event: state.event, data: state.data.join("\n"), raw: [...state.raw] }; state.event = null; state.data = []; state.raw = []; return event; } function decodeSseLine(line, state) { if (line === "") return flushSseEvent(state); state.raw.push(line); if (line.startsWith(":")) return null; const delimiterIndex = line.indexOf(":"); const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex); let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1); if (value.startsWith(" ")) value = value.slice(1); if (fieldName === "event") state.event = value; else if (fieldName === "data") state.data.push(value); return null; } function nextLineBreakIndex(text) { const carriageReturnIndex = text.indexOf("\r"); const newlineIndex = text.indexOf("\n"); if (carriageReturnIndex === -1) return newlineIndex; if (newlineIndex === -1) return carriageReturnIndex; return Math.min(carriageReturnIndex, newlineIndex); } function consumeLine(text) { const lineBreakIndex = nextLineBreakIndex(text); if (lineBreakIndex === -1) return null; let nextIndex = lineBreakIndex + 1; if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") nextIndex += 1; return { line: text.slice(0, lineBreakIndex), rest: text.slice(nextIndex) }; } async function* iterateSseMessages(body, signal) { const reader = body.getReader(); const decoder = new TextDecoder(); const state = { event: null, data: [], raw: [] }; let buffer = ""; try { while (true) { if (signal?.aborted) throw new Error("Request was aborted"); const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); let consumed = consumeLine(buffer); while (consumed) { buffer = consumed.rest; const event = decodeSseLine(consumed.line, state); if (event) yield event; consumed = consumeLine(buffer); } } buffer += decoder.decode(); let consumed = consumeLine(buffer); while (consumed) { buffer = consumed.rest; const event = decodeSseLine(consumed.line, state); if (event) yield event; consumed = consumeLine(buffer); } if (buffer.length > 0) { const event = decodeSseLine(buffer, state); if (event) yield event; } const trailingEvent = flushSseEvent(state); if (trailingEvent) yield trailingEvent; } finally { reader.releaseLock(); } } async function* iterateAnthropicEvents(response, signal, requireMessageStop = false) { if (!response.body) throw new Error("Attempted to iterate over an Anthropic response with no body"); let sawMessageStart = false; let sawMessageEnd = false; for await (const sse of iterateSseMessages(response.body, signal)) { if (sse.event === "error") throw new Error(sse.data); if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) continue; try { const event = parseJsonWithRepair(sse.data); if (event.type === "message_start") sawMessageStart = true; else if (event.type === "message_stop") sawMessageEnd = true; yield event; } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join("\\n")}`, { cause: error }); } } if ((sawMessageStart || requireMessageStop) && !sawMessageEnd) throw new Error("Anthropic stream ended before message_stop"); } const streamAnthropic = (model, context, options) => { const stream = new AssistantMessageEventStream(); (async () => { const output = { role: "assistant", content: [], api: model.api, provider: model.provider, model: model.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop", timestamp: Date.now() }; const refusalBuffer = usesClaudeFable5MessagesContract(model) ? createDeferredEventBuffer(stream, () => notifyLlmRequestActivity(options?.signal)) : void 0; const eventSink = refusalBuffer ?? stream; try { let client; let isOAuth; if (options?.client) { client = options.client; isOAuth = false; } else { const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? ""; let copilotDynamicHeaders; if (model.provider === "github-copilot") { const hasImages = hasCopilotVisionInput(context.messages); copilotDynamicHeaders = buildCopilotDynamicHeaders({ messages: context.messages, hasImages }); } const cacheSessionId = (options?.cacheRetention ?? resolveCacheRetention()) === "none" ? void 0 : options?.sessionId; const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId); client = created.client; isOAuth = created.isOAuthToken; } let params = buildParams(model, context, isOAuth, options); const nextParams = await options?.onPayload?.(params, model); if (nextParams !== void 0) params = nextParams; const requestOptions = { ...options?.signal ? { signal: options.signal } : {}, ...options?.timeoutMs !== void 0 ? { timeout: options.timeoutMs } : {}, ...options?.maxRetries !== void 0 ? { maxRetries: options.maxRetries } : {} }; const response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse(); await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model); const blocks = output.content; for await (const event of iterateAnthropicEvents(response, options?.signal, refusalBuffer !== void 0)) if (event.type === "message_start") { output.responseId = event.message.id; output.responseModel = event.message.model; output.usage.input = event.message.usage.input_tokens || 0; output.usage.output = event.message.usage.output_tokens || 0; output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0; output.usage.cacheWrite = event.message.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 }); } else if (event.type === "content_block_start") { if (event.content_block.type === "text") { const block = { type: "text", text: "", index: event.index }; output.content.push(block); eventSink.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output }); } else if (event.content_block.type === "thinking") { const block = { type: "thinking", thinking: "", thinkingSignature: "", index: event.index }; output.content.push(block); eventSink.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output }); } else if (event.content_block.type === "redacted_thinking") { const block = { type: "thinking", thinking: "[Reasoning redacted]", thinkingSignature: event.content_block.data, redacted: true, index: event.index }; output.content.push(block); eventSink.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output }); } else if (event.content_block.type === "tool_use") { const block = { type: "toolCall", id: event.content_block.id, name: isOAuth ? fromClaudeCodeName(event.content_block.name, context.tools) : event.content_block.name, arguments: event.content_block.input ?? {}, partialJson: "", index: event.index }; output.content.push(block); eventSink.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output }); } } else if (event.type === "content_block_delta") { if (event.delta.type === "text_delta") { const index = blocks.findIndex((b) => b.index === event.index); const block = blocks[index]; if (block && block.type === "text") { block.text += event.delta.text; eventSink.push({ type: "text_delta", contentIndex: index, delta: event.delta.text, partial: output }); } } else if (event.delta.type === "thinking_delta") { const index = blocks.findIndex((b) => b.index === event.index); const block = blocks[index]; if (block && block.type === "thinking") { block.thinking += event.delta.thinking; eventSink.push({ type: "thinking_delta", contentIndex: index, delta: event.delta.thinking, partial: output }); } } else if (event.delta.type === "input_json_delta") { const index = blocks.findIndex((b) => b.index === event.index); const block = blocks[index]; if (block && block.type === "toolCall") { block.partialJson += event.delta.partial_json; block.arguments = parseStreamingJson(block.partialJson); eventSink.push({ type: "toolcall_delta", contentIndex: index, delta: event.delta.partial_json, partial: output }); } } else if (event.delta.type === "signature_delta") { const block = blocks[blocks.findIndex((b) => b.index === event.index)]; if (block && block.type === "thinking") { block.thinkingSignature = block.thinkingSignature || ""; block.thinkingSignature += event.delta.signature; } } } else if (event.type === "content_block_stop") { const index = blocks.findIndex((b) => b.index === event.index); const block = blocks[index]; if (block) { delete block.index; if (block.type === "text") eventSink.push({ type: "text_end", contentIndex: index, content: block.text, partial: output }); else if (block.type === "thinking") eventSink.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: output }); else if (block.type === "toolCall") { block.arguments = parseStreamingJson(block.partialJson); delete block.partialJson; eventSink.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: output }); } } } else if (event.type === "message_delta") { if (event.delta.stop_reason) if (event.delta.stop_reason === "refusal") applyAnthropicRefusal(output, event.delta.stop_details, model.provider); else output.stopReason = mapStopReason(event.delta.stop_reason); if (event.usage.input_tokens != null) output.usage.input = event.usage.input_tokens; if (event.usage.output_tokens != null) output.usage.output = event.usage.output_tokens; if (event.usage.cache_read_input_tokens != null) output.usage.cacheRead = event.usage.cache_read_input_tokens; if (event.usage.cache_creation_input_tokens != null) output.usage.cacheWrite = event.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 (options?.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(); stream.push({ type: "done", reason: output.stopReason, message: output }); stream.end(); } catch (error) { for (const block of output.content) { delete block.index; delete block.partialJson; } if (refusalBuffer) { refusalBuffer.discard(); output.content = []; } output.stopReason = options?.signal?.aborted ? "aborted" : "error"; output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error); stream.push({ type: "error", reason: output.stopReason, error: output }); stream.end(); } })(); return stream; }; 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; } /** * Check if a model supports adaptive thinking (Fable 5, Opus 4.6+, Sonnet 4.6). */ function supportsAdaptiveThinking(model) { return supportsClaudeAdaptiveThinking(model); } function supportsNativeXhighEffort(model) { return supportsClaudeNativeXhighEffort(model); } /** * Map ThinkingLevel to Anthropic effort levels for adaptive thinking. * Model metadata owns the provider-specific extended effort mapping. */ function mapThinkingLevelToEffort(model, level) { const requestedLevel = level; const hasCanonicalAlias = typeof model.params?.canonicalModelId === "string"; const thinkingLevelMap = resolveClaudeNativeThinkingLevelMap(model); const clampModel = { ...model, ...hasCanonicalAlias ? { reasoning: true } : {}, ...thinkingLevelMap ? { thinkingLevelMap } : {} }; const clampedLevel = requestedLevel ? clampThinkingLevel(clampModel, requestedLevel) : requestedLevel; const mapped = clampedLevel ? thinkingLevelMap?.[clampedLevel] : void 0; if (typeof mapped === "string") return mapped; switch (clampedLevel) { case "off": case "minimal": case "low": return "low"; case "medium": return "medium"; case "high": return "high"; case "xhigh": return supportsNativeXhighEffort(model) ? "xhigh" : "high"; case "max": return supportsClaudeNativeMaxEffort(model) ? "max" : "high"; default: return "high"; } } const streamSimpleAnthropic = (model, context, options) => { const apiKey = options?.apiKey || getEnvApiKey(model.provider); if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`); const base = buildBaseOptions(model, options, apiKey); if (!options?.reasoning) { const mandatoryAdaptiveThinking = requiresClaudeAdaptiveThinking(model); return streamAnthropic(model, context, { ...base, thinkingEnabled: mandatoryAdaptiveThinking, ...mandatoryAdaptiveThinking ? { effort: "high" } : {} }); } if (supportsAdaptiveThinking(model)) { const effort = mapThinkingLevelToEffort(model, options.reasoning); return streamAnthropic(model, context, { ...base, thinkingEnabled: true, effort }); } const adjusted = adjustMaxTokensForThinking(base.maxTokens, model.maxTokens, options.reasoning, options.thinkingBudgets); return streamAnthropic(model, context, { ...base, maxTokens: adjusted.maxTokens, thinkingEnabled: true, thinkingBudgetTokens: adjusted.thinkingBudget }); }; function isOAuthToken(apiKey) { return apiKey.includes("sk-ant-oat"); } function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId) { const needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model); const betaFeatures = []; if (useFineGrainedToolStreamingBeta) betaFeatures.push(FINE_GRAINED_TOOL_STREAMING_BETA); if (needsInterleavedBeta) betaFeatures.push(INTERLEAVED_THINKING_BETA); if (model.provider === "cloudflare-ai-gateway") return { client: new Anthropic({ apiKey, authToken: null, baseURL: resolveCloudflareBaseUrl(model), dangerouslyAllowBrowser: true, defaultHeaders: mergeHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", Authorization: null, ...betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {} }, model.headers, optionsHeaders) }), isOAuthToken: false }; if (model.provider === "github-copilot") return { client: new Anthropic({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, dangerouslyAllowBrowser: true, defaultHeaders: mergeHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {} }, model.headers, dynamicHeaders, optionsHeaders) }), isOAuthToken: false }; if (usesFoundryBearerAuth(model)) return { client: new Anthropic({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, dangerouslyAllowBrowser: true, defaultHeaders: mergeHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {} }, omitFoundryBearerCredentialHeaders(model.headers), dynamicHeaders, optionsHeaders) }), isOAuthToken: false }; if (isOAuthToken(apiKey)) return { client: new Anthropic({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, dangerouslyAllowBrowser: true, defaultHeaders: mergeHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", "anthropic-beta": [ "claude-code-20250219", "oauth-2025-04-20", ...betaFeatures ].join(","), "user-agent": `claude-cli/${claudeCodeVersion}`, "x-app": "cli" }, model.headers, optionsHeaders) }), isOAuthToken: true }; const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {}; return { client: new Anthropic({ apiKey, authToken: null, baseURL: model.baseUrl, dangerouslyAllowBrowser: true, defaultHeaders: mergeHeaders({ accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", ...betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {} }, sessionAffinityHeaders, model.headers, optionsHeaders) }), isOAuthToken: false }; } function buildParams(model, context, isOAuthTokenResult, options) { const { cacheControl } = getCacheControl(model, options?.cacheRetention); const system = buildAnthropicSystemBlocks(context.systemPrompt, isOAuthTokenResult, cacheControl); const compat = context.tools?.length ? getAnthropicCompat(model) : void 0; const tools = context.tools && compat ? convertTools(context.tools, isOAuthTokenResult, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : void 0) : void 0; const systemCacheControlCount = countNativeCacheControlMarkers(system); const toolCacheControlCount = countNativeCacheControlMarkers(tools); const messageCacheControlLimit = Math.max(0, ANTHROPIC_CACHE_CONTROL_LIMIT - systemCacheControlCount - toolCacheControlCount); const params = { model: model.id, messages: convertMessages(context.messages, model, isOAuthTokenResult, cacheControl, messageCacheControlLimit), max_tokens: options?.maxTokens ?? model.maxTokens, stream: true }; if (system) params.system = system; 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 (tools && tools.length > 0) params.tools = tools; const fable5 = usesClaudeFable5MessagesContract(model); if (fable5 || model.reasoning || supportsAdaptiveThinking(model)) { if (fable5 || options?.thinkingEnabled) { const display = options?.thinkingDisplay ?? "summarized"; if (supportsAdaptiveThinking(model)) { params.thinking = { type: "adaptive", display }; const effort = options?.effort ?? (fable5 ? "high" : void 0); if (effort) params.output_config = effort === "xhigh" ? { effort } : { effort }; } else params.thinking = { type: "enabled", budget_tokens: options?.thinkingBudgetTokens || 1024, display }; } else if (options?.thinkingEnabled === false) params.thinking = { type: "disabled" }; } if (options?.metadata) { const userId = options.metadata.user_id; if (typeof userId === "string") params.metadata = { user_id: userId }; } if (options?.toolChoice) params.tool_choice = normalizeAnthropicToolChoice(model, options.toolChoice); return params; } function normalizeToolCallId(id) { return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64); } function convertMessages(messages, model, isOAuthTokenValue, cacheControl, messageCacheControlLimit = 4) { const params = []; const transformedMessages = transformMessages(messages, model, normalizeToolCallId); for (let i = 0; i < transformedMessages.length; i++) { const msg = transformedMessages[i]; if (msg.role === "user") if (typeof msg.content === "string") { if (msg.content.trim().length > 0) params.push({ role: "user", content: sanitizeSurrogates(msg.content) }); } else { const filteredBlocks = msg.content.map((item) => { if (item.type === "text") return { type: "text", text: sanitizeSurrogates(item.text) }; return { type: "image", source: { type: "base64", media_type: item.mimeType, data: item.data } }; }).filter((b) => { if (b.type === "text") return b.text.trim().length > 0; return true; }); if (filteredBlocks.length === 0) continue; params.push({ role: "user", content: filteredBlocks }); } else if (msg.role === "assistant") { const blocks = []; for (const block of msg.content) if (block.type === "text") { if (block.text.trim().length === 0) continue; blocks.push({ type: "text", text: sanitizeSurrogates(block.text) }); } else 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: sanitizeSurrogates(block.thinking) }); else { if (thinkingSignature === "reasoning_content") continue; blocks.push({ type: "thinking", thinking: block.thinking, signature: thinkingSignature }); } } else if (block.type === "toolCall") blocks.push({ type: "tool_use", id: block.id, name: isOAuthTokenValue ? toClaudeCodeName(block.name) : block.name, input: block.arguments ?? {} }); if (blocks.length === 0) continue; params.push({ role: "assistant", content: blocks }); } else if (msg.role === "toolResult") { const toolResults = []; toolResults.push({ type: "tool_result", tool_use_id: msg.toolCallId, content: convertContentBlocks(msg.content), is_error: msg.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++; } i = j - 1; params.push({ role: "user", content: toolResults }); } } if (cacheControl && params.length > 0 && messageCacheControlLimit > 0) { let fallbackToolResult; for (let i = params.length - 1; i >= 0; i--) { const message = params[i]; if (message.role !== "user") continue; if (Array.isArray(message.content)) { for (let j = message.content.length - 1; j >= 0; j--) { const block = message.content[j]; if (block.type === "text" || block.type === "image") { if (fallbackToolResult && messageCacheControlLimit === 1) { applyContentBlockCacheControl(fallbackToolResult, cacheControl); return params; } applyContentBlockCacheControl(block, cacheControl); if (fallbackToolResult && messageCacheControlLimit > 1) applyContentBlockCacheControl(fallbackToolResult, cacheControl); return params; } if (block.type === "tool_result" && fallbackToolResult === void 0) fallbackToolResult = block; } continue; } if (typeof message.content === "string") { if (fallbackToolResult && messageCacheControlLimit === 1) { applyContentBlockCacheControl(fallbackToolResult, cacheControl); return params; } message.content = [{ type: "text", text: message.content, cache_control: cacheControl }]; if (fallbackToolResult && messageCacheControlLimit > 1) applyContentBlockCacheControl(fallbackToolResult, cacheControl); return params; } } if (fallbackToolResult) applyContentBlockCacheControl(fallbackToolResult, cacheControl); } return params; } function applyContentBlockCacheControl(block, cacheControl) { block.cache_control = cacheControl; } function buildAnthropicSystemBlocks(systemPrompt, isOAuthTokenResult, cacheControl) { const blocks = []; if (isOAuthTokenResult) blocks.push({ type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude.", ...cacheControl ? { cache_control: cacheControl } : {} }); if (systemPrompt) blocks.push(...buildSystemPromptBlocks(systemPrompt, cacheControl)); return blocks.length > 0 ? blocks : void 0; } function buildSystemPromptBlocks(systemPrompt, cacheControl) { if (!cacheControl) return [{ type: "text", text: sanitizeSurrogates(stripSystemPromptCacheBoundary(systemPrompt)) }]; const split = splitSystemPromptCacheBoundary(systemPrompt); if (!split) return [{ type: "text", text: sanitizeSurrogates(systemPrompt), cache_control: cacheControl }]; const blocks = []; if (split.stablePrefix) blocks.push({ type: "text", text: sanitizeSurrogates(split.stablePrefix), cache_control: cacheControl }); if (split.dynamicSuffix) blocks.push({ type: "text", text: sanitizeSurrogates(split.dynamicSuffix) }); return blocks.length > 0 ? blocks : [{ type: "text", text: "" }]; } function countNativeCacheControlMarkers(blocks) { if (!Array.isArray(blocks)) return 0; let count = 0; for (const block of blocks) if (block && typeof block === "object" && "cache_control" in block) count += 1; return count; } function shouldUseFineGrainedToolStreamingBeta(model, context) { return Boolean(context.tools?.length) && !getAnthropicCompat(model).supportsEagerToolInputStreaming; } function convertTools(tools, isOAuthTokenLocal, supportsEagerToolInputStreaming, cacheControl) { if (!tools) return []; return tools.map((tool, index) => { const schema = tool.parameters; return { name: isOAuthTokenLocal ? toClaudeCodeName(tool.name) : tool.name, description: tool.description, ...supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}, input_schema: { type: "object", properties: schema.properties ?? {}, required: schema.required ?? [] }, ...cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {} }; }); } function mapStopReason(reason) { switch (reason) { case "end_turn": return "stop"; case "max_tokens": return "length"; case "tool_use": return "toolUse"; case "refusal": return "error"; case "pause_turn": return "stop"; case "stop_sequence": return "stop"; case "sensitive": return "error"; default: throw new Error(`Unhandled stop reason: ${reason}`); } } //#endregion export { streamAnthropic, streamSimpleAnthropic };