import type { StreamFn } from "@mariozechner/pi-agent-core"; import { streamSimple } from "@mariozechner/pi-ai"; import type { ThinkLevel } from "../../auto-reply/thinking.js"; import { resolveProviderAttributionHeaders } from "../provider-attribution.js"; const KILOCODE_FEATURE_HEADER = "X-KILOCODE-FEATURE"; const KILOCODE_FEATURE_DEFAULT = "openclaw"; const KILOCODE_FEATURE_ENV_VAR = "KILOCODE_FEATURE"; function resolveKilocodeAppHeaders(): Record { const feature = process.env[KILOCODE_FEATURE_ENV_VAR]?.trim() || KILOCODE_FEATURE_DEFAULT; return { [KILOCODE_FEATURE_HEADER]: feature }; } function isOpenRouterAnthropicModel(provider: string, modelId: string): boolean { return provider.toLowerCase() === "openrouter" && modelId.toLowerCase().startsWith("anthropic/"); } function mapThinkingLevelToOpenRouterReasoningEffort( thinkingLevel: ThinkLevel, ): "none" | "minimal" | "low" | "medium" | "high" | "xhigh" { if (thinkingLevel === "off") { return "none"; } if (thinkingLevel === "adaptive") { return "medium"; } return thinkingLevel; } function normalizeProxyReasoningPayload(payload: unknown, thinkingLevel?: ThinkLevel): void { if (!payload || typeof payload !== "object") { return; } const payloadObj = payload as Record; delete payloadObj.reasoning_effort; if (!thinkingLevel || thinkingLevel === "off") { return; } const existingReasoning = payloadObj.reasoning; if ( existingReasoning && typeof existingReasoning === "object" && !Array.isArray(existingReasoning) ) { const reasoningObj = existingReasoning as Record; if (!("max_tokens" in reasoningObj) && !("effort" in reasoningObj)) { reasoningObj.effort = mapThinkingLevelToOpenRouterReasoningEffort(thinkingLevel); } } else if (!existingReasoning) { payloadObj.reasoning = { effort: mapThinkingLevelToOpenRouterReasoningEffort(thinkingLevel), }; } } export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | undefined): StreamFn { const underlying = baseStreamFn ?? streamSimple; return (model, context, options) => { if ( typeof model.provider !== "string" || typeof model.id !== "string" || !isOpenRouterAnthropicModel(model.provider, model.id) ) { return underlying(model, context, options); } const originalOnPayload = options?.onPayload; return underlying(model, context, { ...options, onPayload: (payload) => { const messages = (payload as Record)?.messages; if (Array.isArray(messages)) { for (const msg of messages as Array<{ role?: string; content?: unknown }>) { if (msg.role !== "system" && msg.role !== "developer") { continue; } if (typeof msg.content === "string") { msg.content = [ { type: "text", text: msg.content, cache_control: { type: "ephemeral" } }, ]; } else if (Array.isArray(msg.content) && msg.content.length > 0) { const last = msg.content[msg.content.length - 1]; if (last && typeof last === "object") { (last as Record).cache_control = { type: "ephemeral" }; } } } } return originalOnPayload?.(payload, model); }, }); }; } export function createOpenRouterWrapper( baseStreamFn: StreamFn | undefined, thinkingLevel?: ThinkLevel, ): StreamFn { const underlying = baseStreamFn ?? streamSimple; return (model, context, options) => { const onPayload = options?.onPayload; const attributionHeaders = resolveProviderAttributionHeaders("openrouter"); return underlying(model, context, { ...options, headers: { ...attributionHeaders, ...options?.headers, }, onPayload: (payload) => { normalizeProxyReasoningPayload(payload, thinkingLevel); return onPayload?.(payload, model); }, }); }; } export function isProxyReasoningUnsupported(modelId: string): boolean { return modelId.toLowerCase().startsWith("x-ai/"); } export function createKilocodeWrapper( baseStreamFn: StreamFn | undefined, thinkingLevel?: ThinkLevel, ): StreamFn { const underlying = baseStreamFn ?? streamSimple; return (model, context, options) => { const onPayload = options?.onPayload; return underlying(model, context, { ...options, headers: { ...options?.headers, ...resolveKilocodeAppHeaders(), }, onPayload: (payload) => { normalizeProxyReasoningPayload(payload, thinkingLevel); return onPayload?.(payload, model); }, }); }; }