b94c0c7d04
OpenCode Go mixes request protocols by model family: - `glm-5` and `kimi-k2.5` use OpenAI-style `/chat/completions` - `minimax-m2.5` and `minimax-m2.7` use Anthropic-style `/messages` OmniRoute already routed MiniMax Go models to `/messages`, but the executor still sent `Authorization: Bearer ...`, which caused upstream `401 Missing API key` errors. This changes `OpencodeExecutor` to send: - `x-api-key` + `anthropic-version` for Claude-targeted OpenCode Go requests - `Authorization: Bearer ...` for the remaining OpenCode Go request formats Also updates unit coverage to assert the correct header behavior for MiniMax Go models. Validated with: - direct curl repro against OpenCode Go endpoints - `node --import tsx/esm --test tests/unit/opencode-executor.test.mjs` - `npm run typecheck:core` - `npm run build`
66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
import { BaseExecutor, 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) {
|
|
const headers: Record<string, string> = { "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";
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
}
|