import { BaseExecutor, setUserAgentHeader, type ExecuteInput, type ProviderCredentials, } from "./base.ts"; import { PROVIDERS } from "../config/constants.ts"; import { getModelTargetFormat } from "../config/providerModels.ts"; export class OpencodeExecutor extends BaseExecutor { _requestFormat: string | null = null; constructor(provider: string) { super(provider, PROVIDERS[provider] || PROVIDERS.openai); } async execute(input: ExecuteInput) { this._requestFormat = getModelTargetFormat(this.provider, input.model) || "openai"; try { return await super.execute(input); } finally { this._requestFormat = null; } } buildUrl( model: string, stream: boolean, urlIndex = 0, credentials: ProviderCredentials | null = null ) { void urlIndex; void credentials; const base = this.config.baseUrl; switch (this._requestFormat) { case "claude": return `${base}/messages`; case "openai-responses": return `${base}/responses`; case "gemini": return `${base}/models/${model}:${stream ? "streamGenerateContent?alt=sse" : "generateContent"}`; default: return `${base}/chat/completions`; } } buildHeaders( credentials: ProviderCredentials | null, stream = true, clientHeaders?: Record | null, model?: string ) { const headers: Record = { "Content-Type": "application/json" }; const key = credentials?.apiKey || credentials?.accessToken; if (key) { if (this._requestFormat === "claude") { headers["x-api-key"] = key; } else { headers["Authorization"] = `Bearer ${key}`; } } if (this._requestFormat === "claude") { headers["anthropic-version"] = "2023-06-01"; } if (stream) { headers["Accept"] = "text/event-stream"; } if (clientHeaders) { const clientUA = clientHeaders["User-Agent"] || clientHeaders["user-agent"]; if (clientUA) { setUserAgentHeader(headers, clientUA); } // Forward OpenCode request metadata headers from client const opencodeHeaderKeys = [ "x-opencode-session", "x-opencode-request", "x-opencode-project", "x-opencode-client", ]; for (const headerName of opencodeHeaderKeys) { const value = Object.entries(clientHeaders).find( ([key]) => key.toLowerCase() === headerName.toLowerCase() )?.[1]; if (value) { headers[headerName] = value; } } } void model; return headers; } transformRequest( model: string, body: any, stream: boolean, credentials: ProviderCredentials ): any { const modifiedBody = super.transformRequest(model, body, stream, credentials); if ( modifiedBody && typeof modifiedBody === "object" && Array.isArray(modifiedBody.tools) && modifiedBody.tools.length > 128 ) { modifiedBody.tools = modifiedBody.tools.slice(0, 128); } return modifiedBody; } }