import { createHash, randomUUID } from "node:crypto"; import { getStainlessTimeoutSeconds } from "@/shared/utils/runtimeTimeouts"; import { ANTHROPIC_VERSION_HEADER } from "../config/anthropicHeaders.ts"; import { supportsClaudeMaxEffort, supportsXHighEffort } from "../config/providerModels.ts"; import { prepareClaudeRequest } from "../translator/helpers/claudeHelper.ts"; import { signRequestBody } from "./claudeCodeCCH.ts"; import { resolveClaudeCodeCompatibleAnthropicBeta } from "./claudeCodeCompatibleBeta.ts"; import { remapToolNamesInRequest } from "./claudeCodeToolRemapper.ts"; import { enforceThinkingTemperature, disableThinkingIfToolChoiceForced, enforceCacheControlLimit, } from "./claudeCodeConstraints.ts"; import { applyClaudeCodeCompatibleThinkingDisplay } from "./claudeCodeCompatibleThinkingDisplay.ts"; import { obfuscateInBody } from "./claudeCodeObfuscation.ts"; import { applySystemTransformPipeline, PROVIDER_CC_BRIDGE } from "./systemTransforms.ts"; import { usesCcWireImage } from "./ccWireImageBuiltins.ts"; import { fixToolPairs, fixToolAdjacency, stripTrailingAssistantOrphanToolUse, } from "./contextManager.ts"; /** * `anthropic-compatible-cc-*` targets Anthropic relay gateways that only accept * traffic which looks like the official Claude Code client, often because those * gateways resell the same models at materially lower prices than the direct API. * * This bridge is intentionally compatibility-first while still preserving as * much Claude-native structure as possible. Third-party relays are sensitive to * wire-image details, so we only synthesize the minimum required defaults when * the caller did not already provide Claude-shaped fields. */ export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH = "/models"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS = 64000; export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION = ANTHROPIC_VERSION_HEADER; export { CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA, CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA, resolveClaudeCodeCompatibleAnthropicBeta, } from "./claudeCodeCompatibleBeta.ts"; export const CLAUDE_CODE_COMPATIBLE_VERSION = "2.1.195"; export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = "claude-cli/2.1.195 (external, sdk-cli)"; export const CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION = "0.94.0"; export const CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION = "v24.3.0"; export const CONTEXT_1M_BETA_HEADER = "context-1m-2025-08-07"; const CLAUDE_CODE_COMPATIBLE_DEFAULT_SYSTEM_BLOCKS = [ { type: "text", text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.", }, ]; const CONTEXT_1M_SUPPORTED_MODELS = [ "claude-fable-5", "claude-sonnet-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", ]; export const CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS = getStainlessTimeoutSeconds( process.env ); type HeaderLike = | Headers | Record | { get?: (name: string) => string | null } | null | undefined; type MessageLike = { role?: string; content?: unknown; }; type BuildRequestOptions = { sourceBody?: Record | null; normalizedBody?: Record | null; claudeBody?: Record | null; model: string; stream?: boolean; cwd?: string; now?: Date; sessionId?: string | null; preserveCacheControl?: boolean; preserveClaudeMessages?: boolean; redactThinking?: boolean; summarizeThinking?: boolean; }; function supportsClaudeXHighEffort(model: string | null | undefined): boolean { return typeof model === "string" && supportsXHighEffort("claude", model); } export function isClaudeCodeCompatibleProvider(provider: string | null | undefined): boolean { return ( (typeof provider === "string" && provider.startsWith(CLAUDE_CODE_COMPATIBLE_PREFIX)) || // Built-in providers (e.g. agentrouter) that adopt the dynamic CC wire image // while keeping their own registry baseUrl + auth (#6056). usesCcWireImage(provider) ); } export function stripAnthropicMessagesSuffix(baseUrl: string | null | undefined): string { const normalized = String(baseUrl || "") .trim() .replace(/\/$/, ""); if (!normalized) return ""; return normalized.split("?")[0].replace(/\/messages$/i, ""); } export function stripClaudeCodeCompatibleEndpointSuffix( baseUrl: string | null | undefined ): string { const normalized = String(baseUrl || "") .trim() .replace(/\/$/, ""); if (!normalized) return ""; return normalized.split("?")[0].replace(/\/(?:v\d+\/)?messages$/i, ""); } function joinNormalizedBaseUrlAndPath(baseUrl: string, path: string): string { const normalizedBase = String(baseUrl || "").replace(/\/$/, ""); const normalizedPath = String(path || "").startsWith("/") ? String(path) : `/${String(path || "")}`; const versionMatch = normalizedBase.match(/(\/v\d+)$/i); if ( versionMatch && normalizedPath.toLowerCase().startsWith(`${versionMatch[1].toLowerCase()}/`) ) { return `${normalizedBase}${normalizedPath.slice(versionMatch[1].length)}`; } return `${normalizedBase}${normalizedPath}`; } export function joinBaseUrlAndPath(baseUrl: string, path: string): string { return joinNormalizedBaseUrlAndPath(stripAnthropicMessagesSuffix(baseUrl), path); } export function joinClaudeCodeCompatibleUrl(baseUrl: string, path: string): string { return joinNormalizedBaseUrlAndPath(stripClaudeCodeCompatibleEndpointSuffix(baseUrl), path); } export function appendAnthropicBetaHeader( headers: Record, betaHeader: string ): void { const existingKey = Object.keys(headers).find((key) => key.toLowerCase() === "anthropic-beta"); if (!existingKey) { headers["anthropic-beta"] = betaHeader; return; } const existingValues = String(headers[existingKey] || "") .split(",") .map((value) => value.trim()) .filter(Boolean); if (!existingValues.includes(betaHeader)) { headers[existingKey] = [...existingValues, betaHeader].join(","); } } export function modelSupportsContext1mBeta(model: string | null | undefined): boolean { const normalizedModel = String(model || "") .trim() .toLowerCase() .replace(/-\d{8}$/, ""); return CONTEXT_1M_SUPPORTED_MODELS.some( (supported) => normalizedModel === supported || normalizedModel.startsWith(`${supported}-`) ); } export function buildClaudeCodeCompatibleHeaders( apiKey: string, stream = false, sessionId?: string | null, options: { redactThinking?: boolean } = {} ): Record { // These headers intentionally mirror Claude Code's wire image closely. // For CC-compatible relays, passing the upstream's client-gating checks is // more important than forwarding arbitrary caller-specific header shapes. return { "Content-Type": "application/json", Accept: stream ? "text/event-stream" : "application/json", Authorization: `Bearer ${apiKey}`, "anthropic-version": CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION, "anthropic-beta": resolveClaudeCodeCompatibleAnthropicBeta({ redactThinking: options.redactThinking === true, }), "anthropic-dangerous-direct-browser-access": "true", "x-app": "cli", "User-Agent": CLAUDE_CODE_COMPATIBLE_USER_AGENT, "X-Stainless-Retry-Count": "0", "X-Stainless-Timeout": String(CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS), "X-Stainless-Lang": "js", "X-Stainless-Package-Version": CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION, "X-Stainless-OS": "MacOS", "X-Stainless-Arch": "arm64", "X-Stainless-Runtime": "node", "X-Stainless-Runtime-Version": CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION, "accept-encoding": "gzip, deflate, br, zstd", ...(sessionId ? { "X-Claude-Code-Session-Id": sessionId } : {}), }; } export function buildClaudeCodeCompatibleValidationPayload(model = "claude-sonnet-4-6") { const sessionId = randomUUID(); return buildClaudeCodeCompatibleRequest({ sourceBody: { max_tokens: 1 }, normalizedBody: { messages: [{ role: "user", content: "ok" }], max_tokens: 1, }, model, stream: true, sessionId, cwd: process.cwd(), now: new Date(), }); } export function resolveClaudeCodeCompatibleSessionId(headers?: HeaderLike): string { const raw = getHeader(headers, "x-claude-code-session-id") || getHeader(headers, "x-session-id") || getHeader(headers, "x_session_id") || getHeader(headers, "x-omniroute-session") || null; return (raw && raw.trim()) || randomUUID(); } export function buildClaudeCodeCompatibleRequest({ sourceBody, normalizedBody, claudeBody, model, stream = false, cwd = process.cwd(), sessionId, preserveCacheControl = false, preserveClaudeMessages = false, summarizeThinking = false, }: BuildRequestOptions) { const normalized = normalizedBody || {}; const preparedClaudeBody = claudeBody ? preserveClaudeMessages ? prepareClaudeCodeCompatibleSemanticBody(claudeBody) : prepareClaudeCodeCompatibleBody(claudeBody, preserveCacheControl) : null; const normalizedMessages = Array.isArray(normalized.messages) ? (normalized.messages as MessageLike[]) : []; const extractedClaudeBody = !preparedClaudeBody && sourceBody ? extractClaudeBodyFromSource(sourceBody, preserveCacheControl) : null; const effectiveClaudeBody = preparedClaudeBody || extractedClaudeBody; const messages = effectiveClaudeBody ? preserveClaudeMessages && preparedClaudeBody ? cloneClaudeCodeCompatibleMessagesFromClaude( effectiveClaudeBody.messages as MessageLike[], preserveCacheControl ) : buildClaudeCodeCompatibleMessagesFromClaude( effectiveClaudeBody.messages as MessageLike[], preserveCacheControl ) : buildClaudeCodeCompatibleMessages(normalizedMessages); const system = buildClaudeCodeCompatibleSystemBlocks({ messages: preserveClaudeMessages ? [] : normalizedMessages, systemBlocks: effectiveClaudeBody?.system as Record[] | undefined, preserveCacheControl, }); const resolvedSessionId = sessionId || randomUUID(); const effort = resolveClaudeCodeCompatibleEffort(sourceBody, normalizedBody, model); const maxTokens = resolveClaudeCodeCompatibleMaxTokens(sourceBody, normalizedBody); const tools = preparedClaudeBody?.tools ? buildClaudeCodeCompatibleToolsFromClaude( preparedClaudeBody.tools as Record[], preserveCacheControl ) : buildClaudeCodeCompatibleTools(normalizedBody, sourceBody); const toolChoice = tools.length > 0 ? buildClaudeCodeCompatibleToolChoice( normalizedBody?.["tool_choice"] ?? sourceBody?.["tool_choice"] ) : undefined; const metadata = resolveClaudeCodeCompatibleMetadata({ claudeBody, sourceBody, normalizedBody, cwd, sessionId: resolvedSessionId, }); const thinking = resolveClaudeCodeCompatibleThinking({ claudeBody: preparedClaudeBody ?? claudeBody, sourceBody, normalizedBody, summarizeThinking, }); const outputConfig = resolveClaudeCodeCompatibleOutputConfig({ claudeBody, sourceBody, normalizedBody, model, effort, }); return { model, messages, system, tools, metadata, max_tokens: maxTokens, thinking, output_config: outputConfig, ...(toolChoice ? { tool_choice: toolChoice } : {}), ...(stream ? { stream: true } : {}), }; } export async function buildAndSignClaudeCodeRequest( options: BuildRequestOptions & { apiKey: string; enableObfuscation?: boolean } ): Promise<{ bodyString: string; headers: Record }> { const { apiKey, enableObfuscation = false, ...buildOptions } = options; // Step 1: Build base request const body = buildClaudeCodeCompatibleRequest(buildOptions); // Step 2: Remap tool names remapToolNamesInRequest(body); // Step 3-4: Thinking constraints enforceThinkingTemperature(body); disableThinkingIfToolChoiceForced(body); // Step 5: Cache control enforceCacheControlLimit(body); // Step 5b: Config-driven system transforms (issue #2260, v2) // Normalizes system blocks to classifier-correct structure regardless of // source client (OpenCode, Cline, Cursor, Continue, Open WebUI, raw API). // Routed via the generic per-provider DSL so the same pipeline shape covers // the CC bridge, the native `claude` path, and any other configured // provider. Idempotent on re-run. { const transformResult = applySystemTransformPipeline( PROVIDER_CC_BRIDGE, body as Parameters[1] ); if (transformResult.appliedOpKinds.length > 0) { console.log(`[SystemTransforms] cc-bridge: ${transformResult.appliedOpKinds.join(", ")}`); } } // Step 5c: Guard against orphan tool_use / tool_result blocks. // Anthropic rejects requests where a tool_use has no matching tool_result // in the next user message (e.g. `messages.N: tool_use ids were found // without tool_result blocks immediately after: toolu_...`). Clients can // ship truncated histories mid-tool-call; fixToolPairs strips orphans // (preserving final-message tool_use for in-flight rounds), then // stripTrailingAssistantOrphanToolUse catches the case where the request // body itself ends on an unmatched assistant(tool_use) — invalid for an // upstream-send turn since the body must end on a user message. // Both are idempotent on clean histories. { const b = body as Record; if (Array.isArray(b.messages)) { const fixed = fixToolPairs(b.messages as Record[]); const adjacent = fixToolAdjacency(fixed); // fixToolAdjacency can leave orphan tool_result blocks behind when it // strips a tool_use whose tool_result wasn't in the next message. // Re-pair to drop those orphans (discussion #2410). const cleaned = fixToolPairs(adjacent); b.messages = stripTrailingAssistantOrphanToolUse(cleaned); } } // Step 6: Obfuscation (optional, per-provider setting) if (enableObfuscation) { obfuscateInBody(body); } // Step 7: Serialize with CCH placeholder (strip internal sentinel fields) delete (body as Record)["_claudeCodeRequiresLowercaseToolNames"]; const serialized = JSON.stringify(body); // Step 8: Sign with xxHash64 const bodyString = await signRequestBody(serialized); // Build headers const sessionId = options.sessionId || resolveClaudeCodeCompatibleSessionId(); const headers = buildClaudeCodeCompatibleHeaders(apiKey, options.stream ?? false, sessionId, { redactThinking: buildOptions.redactThinking === true, }); return { bodyString, headers }; } /** * Re-export for consumers that need to post-process SSE response chunks. */ export { remapToolNamesInResponse } from "./claudeCodeToolRemapper.ts"; export { signRequestBody } from "./claudeCodeCCH.ts"; export { computeFingerprint } from "./claudeCodeFingerprint.ts"; export { obfuscateSensitiveWords, setSensitiveWords } from "./claudeCodeObfuscation.ts"; export { enforceThinkingTemperature, disableThinkingIfToolChoiceForced, enforceCacheControlLimit, } from "./claudeCodeConstraints.ts"; // Preferred (v2): generic per-provider DSL. export { applySystemTransformPipeline, setSystemTransformsConfig, getSystemTransformsConfig, resetSystemTransformsConfig, DEFAULT_SYSTEM_TRANSFORMS_CONFIG, DEFAULT_CLAUDE_PIPELINE, DEFAULT_CC_BRIDGE_PROVIDER_PIPELINE, DEFAULT_OBFUSCATE_WORDS, OPENWEBUI_PARAGRAPH_ANCHORS, OPENWEBUI_IDENTITY_PREFIXES, PROVIDER_CLAUDE, PROVIDER_CC_BRIDGE, } from "./systemTransforms.ts"; export type { SystemTransformsConfig, ProviderTransformsConfig } from "./systemTransforms.ts"; // Legacy (deprecated, kept for transitional API consumers). // The base executor is still used internally by systemTransforms.ts; // these exports let downstream code reference the building blocks directly // while we migrate UI + settings to the v2 shape. export { applyCcBridgeTransformPipeline, buildBillingHeaderValue, setCcBridgeTransformsConfig, getCcBridgeTransformsConfig, resetCcBridgeTransformsConfig, DEFAULT_CC_BRIDGE_PIPELINE, DEFAULT_PARAGRAPH_REMOVAL_ANCHORS, DEFAULT_IDENTITY_PREFIXES, DEFAULT_TEXT_REPLACEMENTS, CLAUDE_AGENT_SDK_IDENTITY, } from "./ccBridgeTransforms.ts"; export type { TransformOp, CcBridgeTransformsConfig } from "./ccBridgeTransforms.ts"; export function resolveClaudeCodeCompatibleEffort( sourceBody?: Record | null, normalizedBody?: Record | null, model?: string | null ): "low" | "medium" | "high" | "xhigh" | "max" { const raw = readNestedString(sourceBody, ["output_config", "effort"]) || readNestedString(sourceBody, ["reasoning", "effort"]) || toNonEmptyString(sourceBody?.["reasoning_effort"]) || readNestedString(normalizedBody, ["output_config", "effort"]) || readNestedString(normalizedBody, ["reasoning", "effort"]) || toNonEmptyString(normalizedBody?.["reasoning_effort"]) || ""; const normalizedEffort = raw.toLowerCase(); if (!normalizedEffort) { return supportsClaudeXHighEffort(model) ? "xhigh" : "high"; } if (normalizedEffort === "low") return "low"; if (normalizedEffort === "medium") return "medium"; if (normalizedEffort === "high") return "high"; if (normalizedEffort === "none" || normalizedEffort === "disabled") return "low"; if (normalizedEffort === "xhigh") { return supportsClaudeXHighEffort(model) ? "xhigh" : "high"; } if (normalizedEffort === "max") { return supportsClaudeMaxEffort(model) ? "max" : "high"; } return supportsClaudeXHighEffort(model) ? "xhigh" : "high"; } export function resolveClaudeCodeCompatibleMaxTokens( sourceBody?: Record | null, normalizedBody?: Record | null ): number { const candidates = [ sourceBody?.["max_tokens"], sourceBody?.["max_completion_tokens"], sourceBody?.["max_output_tokens"], normalizedBody?.["max_tokens"], normalizedBody?.["max_completion_tokens"], normalizedBody?.["max_output_tokens"], ]; for (const candidate of candidates) { const value = Number(candidate); if (Number.isFinite(value) && value > 0) { return Math.floor(value); } } return CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS; } function buildClaudeCodeCompatibleMessages(messages: MessageLike[]) { const converted = messages .map((message) => convertClaudeCodeCompatibleMessage(message)) .filter( ( message ): message is { role: "user" | "assistant"; content: Array<{ type: string; text: string }>; } => !!message && message.content.length > 0 ); const merged: Array<{ role: "user" | "assistant"; content: Array<{ type: string; text: string }>; }> = []; for (const message of converted) { const last = merged[merged.length - 1]; if (last && last.role === message.role) { last.content.push(...message.content); continue; } merged.push({ role: message.role, content: [...message.content] }); } // CC-compatible sites we tested reject assistant-prefill shaped requests even // when Anthropic would normally allow them. Keep assistant/model history, but // drop trailing assistant turns so the upstream request ends on a user turn. while (merged.length > 0 && merged[merged.length - 1].role === "assistant") { merged.pop(); } if (merged.length === 0) { const fallbackText = converted .flatMap((message) => message.content) .map((block) => toNonEmptyString(block.text)) .filter(Boolean) .join("\n") .trim(); if (fallbackText) { return [ { role: "user" as const, content: [{ type: "text", text: fallbackText }], }, ]; } } return merged; } function buildClaudeCodeCompatibleMessagesFromClaude( messages: MessageLike[] | undefined, preserveCacheControl: boolean ) { const converted = Array.isArray(messages) ? messages .map((message) => convertClaudeCodeCompatibleClaudeMessage(message, preserveCacheControl)) .filter( ( message ): message is { role: "user" | "assistant"; content: Array> } => !!message && message.content.length > 0 ) : []; const merged: Array<{ role: "user" | "assistant"; content: Array> }> = []; let previousAssistantHadToolUse = false; for (const message of converted) { const hasToolUse = message.content.some((block) => block.type === "tool_use"); const hasToolResult = message.content.some((block) => block.type === "tool_result"); const last = merged[merged.length - 1]; const shouldKeepSeparate = hasToolUse || hasToolResult || previousAssistantHadToolUse || last?.content?.some((block) => block.type === "tool_use") || last?.content?.some((block) => block.type === "tool_result"); if (last && last.role === message.role && !shouldKeepSeparate) { last.content.push(...message.content); } else { merged.push({ role: message.role, content: [...message.content] }); } previousAssistantHadToolUse = message.role === "assistant" && hasToolUse; } while (merged.length > 0) { const last = merged[merged.length - 1]; const hasToolUse = last.content.some((block) => block.type === "tool_use"); if (last.role !== "assistant" || hasToolUse) { break; } merged.pop(); } if (!preserveCacheControl) { for (const message of merged) { stripCacheControlFromContentBlocks(message.content); } } if (merged.length === 0) { const fallbackText = converted .flatMap((message) => message.content) .map((block) => contentToText(block)) .filter(Boolean) .join("\n") .trim(); if (fallbackText) { return [ { role: "user" as const, content: [{ type: "text", text: fallbackText }], }, ]; } } return merged; } function cloneClaudeCodeCompatibleMessagesFromClaude( messages: MessageLike[] | undefined, preserveCacheControl: boolean ) { const cloned = Array.isArray(messages) ? messages .map((message) => cloneValue(message) as MessageLike) .filter((message) => { const role = String(message?.role || "").toLowerCase(); return role !== "system" && role !== "developer"; }) : []; if (!preserveCacheControl) { for (const message of cloned) { if (Array.isArray(message.content)) { stripCacheControlFromContentBlocks(message.content as Array>); } } } return cloned; } function buildClaudeCodeCompatibleSystemBlocks({ messages, systemBlocks, preserveCacheControl, }: { messages: MessageLike[] | undefined; systemBlocks?: Array> | undefined; preserveCacheControl: boolean; }) { const customSystemBlocks = Array.isArray(systemBlocks) && systemBlocks.length > 0 ? systemBlocks.map((block) => ({ ...block })) : extractCustomSystemBlocks(messages); const preparedCustomSystemBlocks = customSystemBlocks.map((systemBlock) => { const preparedBlock = { ...systemBlock } as Record; if (!preserveCacheControl) { delete preparedBlock["cache_control"]; } return preparedBlock; }); const hasDefaultSystemBlock = containsDefaultSystemSkeleton(preparedCustomSystemBlocks); if (hasDefaultSystemBlock) return preparedCustomSystemBlocks; return [ ...CLAUDE_CODE_COMPATIBLE_DEFAULT_SYSTEM_BLOCKS.map((block) => ({ ...block })), ...preparedCustomSystemBlocks, ]; } function containsDefaultSystemSkeleton(blocks: Array>) { const skeleton = CLAUDE_CODE_COMPATIBLE_DEFAULT_SYSTEM_BLOCKS; if (skeleton.length === 0) return true; if (blocks.length < skeleton.length) return false; return blocks.some((_, startIndex) => skeleton.every((defaultBlock, offset) => { const candidateBlock = blocks[startIndex + offset]; if (!candidateBlock) return false; return Object.entries(defaultBlock).every(([key, value]) => candidateBlock[key] === value); }) ); } function convertClaudeCodeCompatibleMessage(message: MessageLike | null | undefined) { const rawRole = String(message?.role || "").toLowerCase(); const role = rawRole === "user" ? "user" : rawRole === "assistant" || rawRole === "model" ? "assistant" : null; if (!role) return null; const text = contentToText(message?.content); if (!text) return null; return { role, content: [{ type: "text", text }], }; } function buildClaudeCodeCompatibleTools( normalizedBody?: Record | null, sourceBody?: Record | null ) { const rawTools = Array.isArray(normalizedBody?.["tools"]) ? normalizedBody?.["tools"] : Array.isArray(sourceBody?.["tools"]) ? sourceBody?.["tools"] : []; return rawTools .map((tool) => convertClaudeCodeCompatibleTool(tool)) .filter((tool): tool is Record => !!tool) .map((tool) => ({ ...tool })); } function buildClaudeCodeCompatibleToolsFromClaude( tools: Record[] | undefined, preserveCacheControl: boolean ) { if (!Array.isArray(tools)) return []; return tools.map((tool) => { const preparedTool = { ...tool }; if (!preserveCacheControl) { delete preparedTool.cache_control; } return preparedTool; }); } function convertClaudeCodeCompatibleTool(tool: unknown) { const rawTool = readRecord(tool); if (!rawTool) return null; const toolData = rawTool.type === "function" ? readRecord(rawTool.function) || rawTool : rawTool; const name = toNonEmptyString(toolData.name); if (!name) return null; const rawSchema = readRecord(toolData.parameters) || readRecord(toolData.input_schema) || { type: "object", properties: {}, required: [] }; const inputSchema = rawSchema.type === "object" && !readRecord(rawSchema.properties) ? { ...rawSchema, properties: {} } : rawSchema; const converted: Record = { name, description: toNonEmptyString(toolData.description) || "", input_schema: inputSchema, }; if (typeof toolData.defer_loading === "boolean") { converted.defer_loading = toolData.defer_loading; } return converted; } function buildClaudeCodeCompatibleToolChoice(choice: unknown) { if (!choice) return null; if (typeof choice === "string") { if (choice === "required") return { type: "any" }; return null; } const rawChoice = readRecord(choice); if (!rawChoice) return null; if (rawChoice.type === "tool") { const name = toNonEmptyString(rawChoice.name); return name ? { type: "tool", name } : null; } if (rawChoice.type === "function") { const functionName = toNonEmptyString(readRecord(rawChoice.function)?.name) || toNonEmptyString(rawChoice.name); return functionName ? { type: "tool", name: functionName } : null; } if (rawChoice.type === "required" || rawChoice.type === "any") { return { type: "any" }; } return null; } function prepareClaudeCodeCompatibleBody( claudeBody: Record, preserveCacheControl: boolean ) { void preserveCacheControl; const prepared = prepareClaudeRequest( { system: normalizeClaudeSystemInput(claudeBody.system), messages: normalizeClaudeMessageInput(claudeBody.messages) as Array<{ role?: string; content?: string | Array>; }>, tools: normalizeClaudeToolInput(claudeBody.tools), thinking: (readRecord(claudeBody.thinking) || null) as Record | null, }, CLAUDE_CODE_COMPATIBLE_PREFIX, true ); return readRecord(prepared); } function prepareClaudeCodeCompatibleSemanticBody(claudeBody: Record) { const rawMessages = Array.isArray(claudeBody.messages) ? (claudeBody.messages as MessageLike[]) : []; const systemBlocks = normalizeClaudeSystemInput(claudeBody.system); const systemFromMessages = extractCustomSystemBlocks(rawMessages); const mergedSystem = [...systemBlocks, ...systemFromMessages]; const normalizedMessages = rawMessages.filter((message) => { const role = String(message?.role || "").toLowerCase(); return role !== "system" && role !== "developer"; }); const prepared: Record = { system: mergedSystem, messages: normalizedMessages, tools: normalizeClaudeToolInput(claudeBody.tools), thinking: (readRecord(cloneValue(claudeBody.thinking)) || null) as Record< string, unknown > | null, }; const metadata = readRecord(cloneValue(claudeBody.metadata)); if (metadata) prepared.metadata = metadata; const outputConfig = readRecord(cloneValue(claudeBody.output_config)); if (outputConfig) prepared.output_config = outputConfig; return prepared; } function extractClaudeBodyFromSource( sourceBody: Record, preserveCacheControl: boolean ): Record | null { const rawMessages = Array.isArray(sourceBody.messages) ? (sourceBody.messages as MessageLike[]) : []; const hasSystemRoleMessages = rawMessages.some((message) => { const role = String(message?.role || "").toLowerCase(); return role === "system" || role === "developer"; }); const hasClaudeSystem = typeof sourceBody.system === "string" || (Array.isArray(sourceBody.system) && sourceBody.system.length > 0); if (!hasClaudeSystem && !hasSystemRoleMessages) { return null; } const normalizedMessages = rawMessages.filter((message) => { const role = String(message?.role || "").toLowerCase(); return role !== "system" && role !== "developer"; }); return prepareClaudeCodeCompatibleBody( { ...sourceBody, ...(hasClaudeSystem ? {} : { system: extractCustomSystemBlocks(rawMessages) }), messages: normalizedMessages, }, preserveCacheControl ); } function normalizeClaudeSystemInput(system: unknown) { if (typeof system === "string") { const text = system.trim(); return text ? [{ type: "text", text }] : []; } if (!Array.isArray(system)) return []; return system .map((block) => normalizeClaudeContentBlock(block)) .filter((block): block is Record => !!block); } function normalizeClaudeMessageInput(messages: unknown) { if (!Array.isArray(messages)) return []; return messages .map((message) => { const record = readRecord(message); if (!record) return null; return { ...record, content: normalizeClaudeContentInput(record.content), }; }) .filter((message): message is Record & { content: unknown } => !!message); } function normalizeClaudeToolInput(tools: unknown) { if (!Array.isArray(tools)) return []; return tools .map((tool) => readRecord(cloneValue(tool))) .filter((tool): tool is Record => !!tool); } function normalizeClaudeContentInput(content: unknown) { const blocks = normalizeClaudeContentBlocks(content); return blocks.length > 0 ? blocks : content; } function normalizeClaudeContentBlocks(content: unknown) { if (typeof content === "string") { const text = content.trim(); return text ? [{ type: "text", text }] : []; } if (!Array.isArray(content)) { const block = normalizeClaudeContentBlock(content); return block ? [block] : []; } return content .map((block) => normalizeClaudeContentBlock(block)) .filter((block): block is Record => !!block); } function normalizeClaudeContentBlock(block: unknown) { const record = readRecord(cloneValue(block)); if (!record) return null; if ( record.type === "text" || (typeof record.type !== "string" && typeof record.text === "string") ) { const text = toNonEmptyString(record.text); if (!text) return null; return { ...record, type: "text", text, }; } return record; } function convertClaudeCodeCompatibleClaudeMessage( message: MessageLike | null | undefined, preserveCacheControl: boolean ) { const rawRole = String(message?.role || "").toLowerCase(); const role = rawRole === "user" ? "user" : rawRole === "assistant" ? "assistant" : null; if (!role) return null; const content = normalizeClaudeContentBlocks(message?.content).map((block) => { if (preserveCacheControl) return block; const { cache_control, ...rest } = block; return rest; }); if (content.length === 0) return null; return { role, content, }; } function extractCustomSystemBlocks(messages: MessageLike[] | undefined) { if (!Array.isArray(messages)) return []; return messages .filter((message) => { const role = String(message?.role || "").toLowerCase(); return role === "system" || role === "developer"; }) .map((message) => contentToText(message?.content)) .filter(Boolean) .map((text) => ({ type: "text", text, })); } function stripCacheControlFromContentBlocks(content: Array>) { for (const block of content) { delete block.cache_control; } } function resolveClaudeCodeCompatibleMetadata({ claudeBody, sourceBody, normalizedBody, cwd, sessionId, }: { claudeBody?: Record | null; sourceBody?: Record | null; normalizedBody?: Record | null; cwd: string; sessionId: string; }) { const metadata = readRecord(cloneValue(claudeBody?.metadata)) || readRecord(cloneValue(sourceBody?.metadata)) || readRecord(cloneValue(normalizedBody?.metadata)) || {}; if (!toNonEmptyString(metadata.user_id)) { metadata.user_id = JSON.stringify({ device_id: createHash("sha256") .update(String(cwd || "")) .digest("hex"), account_uuid: "", session_id: sessionId, }); } return metadata; } function resolveClaudeCodeCompatibleThinking({ claudeBody, sourceBody, normalizedBody, summarizeThinking = false, }: { claudeBody?: Record | null; sourceBody?: Record | null; normalizedBody?: Record | null; summarizeThinking?: boolean; }) { const thinking = readRecord(cloneValue(claudeBody?.thinking)) || readRecord(cloneValue(sourceBody?.thinking)) || readRecord(cloneValue(normalizedBody?.thinking)); if (thinking) { return applyClaudeCodeCompatibleThinkingDisplay(thinking, { normalizedBody, summarizeThinking, }); } return applyClaudeCodeCompatibleThinkingDisplay( { type: "adaptive", }, { normalizedBody, summarizeThinking, } ); } function resolveClaudeCodeCompatibleOutputConfig({ claudeBody, sourceBody, normalizedBody, model, effort, }: { claudeBody?: Record | null; sourceBody?: Record | null; normalizedBody?: Record | null; model?: string | null; effort: "low" | "medium" | "high" | "xhigh" | "max"; }) { const outputConfig = readRecord(cloneValue(claudeBody?.output_config)) || readRecord(cloneValue(sourceBody?.output_config)) || readRecord(cloneValue(normalizedBody?.output_config)) || {}; return { ...outputConfig, effort: resolveClaudeCodeCompatibleEffort(sourceBody, normalizedBody, model) || effort, }; } function cloneValue(value: T): T { if (typeof structuredClone === "function") { return structuredClone(value); } return JSON.parse(JSON.stringify(value)) as T; } function contentToText(content: unknown): string { if (typeof content === "string") { return content.trim(); } if (Array.isArray(content)) { return content .map((part) => { if (!part || typeof part !== "object") return ""; const record = part as Record; if (record.type === "text" && typeof record.text === "string") { return record.text.trim(); } if (typeof record.text === "string") { return record.text.trim(); } return ""; }) .filter(Boolean) .join("\n") .trim(); } if (content && typeof content === "object") { const record = content as Record; if (typeof record.text === "string") return record.text.trim(); } return ""; } function getHeader(headers: HeaderLike, name: string): string | null { if (!headers) return null; if (typeof (headers as Headers).get === "function") { return (headers as Headers).get(name); } const record = headers as Record; const target = name.toLowerCase(); for (const [key, value] of Object.entries(record)) { if (key.toLowerCase() === target) { return value ?? null; } } return null; } function toNonEmptyString(value: unknown): string | null { if (typeof value !== "string") return null; const trimmed = value.trim(); return trimmed || null; } function readRecord(value: unknown): Record | null { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : null; } function readNestedString( source: Record | null | undefined, path: string[] ): string | null { let current: unknown = source; for (const key of path) { if (!current || typeof current !== "object" || Array.isArray(current)) { return null; } if (key === "__proto__" || key === "constructor" || key === "prototype") return null; if (!Object.prototype.hasOwnProperty.call(current, key)) return null; current = Reflect.get(current as object, key); } return toNonEmptyString(current); }