fix: polish opencode-zen onboarding (#623) (thanks @magimetal)

This commit is contained in:
Peter Steinberger
2026-01-10 01:07:56 +01:00
parent 05bd100f7a
commit c69c4caa33
9 changed files with 166 additions and 5 deletions

View File

@@ -97,4 +97,57 @@ describe("applyAuthChoice", () => {
};
expect(parsed.profiles?.["minimax:default"]?.key).toBe("sk-minimax-test");
});
it("does not override the default model when selecting opencode-zen without setDefaultModel", async () => {
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-auth-"));
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "agent");
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
const text = vi.fn().mockResolvedValue("sk-opencode-zen-test");
const select: WizardPrompter["select"] = vi.fn(
async (params) => params.options[0]?.value as never,
);
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
const prompter: WizardPrompter = {
intro: vi.fn(noopAsync),
outro: vi.fn(noopAsync),
note: vi.fn(noopAsync),
select,
multiselect,
text,
confirm: vi.fn(async () => false),
progress: vi.fn(() => ({ update: noop, stop: noop })),
};
const runtime: RuntimeEnv = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn((code: number) => {
throw new Error(`exit:${code}`);
}),
};
const result = await applyAuthChoice({
authChoice: "opencode-zen",
config: {
agents: {
defaults: {
model: { primary: "anthropic/claude-opus-4-5" },
},
},
},
prompter,
runtime,
setDefaultModel: false,
});
expect(text).toHaveBeenCalledWith(
expect.objectContaining({ message: "Enter OpenCode Zen API key" }),
);
expect(result.config.agents?.defaults?.model?.primary).toBe(
"anthropic/claude-opus-4-5",
);
expect(result.config.models?.providers?.["opencode-zen"]).toBeDefined();
expect(result.agentModelOverride).toBe("opencode-zen/claude-opus-4-5");
});
});

View File

@@ -43,6 +43,7 @@ import {
applyMinimaxHostedProviderConfig,
applyMinimaxProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
MINIMAX_HOSTED_MODEL_REF,
setAnthropicApiKey,
setGeminiApiKey,
@@ -678,7 +679,7 @@ export async function applyAuthChoice(params: {
"Model configured",
);
} else {
nextConfig = applyOpencodeZenConfig(nextConfig);
nextConfig = applyOpencodeZenProviderConfig(nextConfig);
agentModelOverride = OPENCODE_ZEN_DEFAULT_MODEL;
await noteAgentModel(OPENCODE_ZEN_DEFAULT_MODEL);
}

View File

@@ -9,6 +9,8 @@ import {
applyAuthProfileConfig,
applyMinimaxApiConfig,
applyMinimaxApiProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
writeOAuthCredentials,
} from "./onboard-auth.js";
@@ -250,3 +252,61 @@ describe("applyMinimaxApiProviderConfig", () => {
);
});
});
describe("applyOpencodeZenProviderConfig", () => {
it("adds opencode-zen provider with correct settings", () => {
const cfg = applyOpencodeZenProviderConfig({});
expect(cfg.models?.providers?.["opencode-zen"]).toMatchObject({
baseUrl: "https://opencode.ai/zen/v1",
apiKey: "opencode-zen",
api: "openai-completions",
});
expect(
cfg.models?.providers?.["opencode-zen"]?.models.length,
).toBeGreaterThan(0);
});
it("adds allowlist entries for fallback models", () => {
const cfg = applyOpencodeZenProviderConfig({});
const models = cfg.agents?.defaults?.models ?? {};
expect(Object.keys(models)).toContain("opencode-zen/claude-opus-4-5");
expect(Object.keys(models)).toContain("opencode-zen/gpt-5.2");
});
it("preserves existing alias for the default model", () => {
const cfg = applyOpencodeZenProviderConfig({
agents: {
defaults: {
models: {
"opencode-zen/claude-opus-4-5": { alias: "My Opus" },
},
},
},
});
expect(
cfg.agents?.defaults?.models?.["opencode-zen/claude-opus-4-5"]?.alias,
).toBe("My Opus");
});
});
describe("applyOpencodeZenConfig", () => {
it("sets correct primary model", () => {
const cfg = applyOpencodeZenConfig({});
expect(cfg.agents?.defaults?.model?.primary).toBe(
"opencode-zen/claude-opus-4-5",
);
});
it("preserves existing model fallbacks", () => {
const cfg = applyOpencodeZenConfig({
agents: {
defaults: {
model: { fallbacks: ["anthropic/claude-opus-4-5"] },
},
},
});
expect(cfg.agents?.defaults?.model?.fallbacks).toEqual([
"anthropic/claude-opus-4-5",
]);
});
});

View File

@@ -402,18 +402,24 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) {
export function applyOpencodeZenProviderConfig(
cfg: ClawdbotConfig,
): ClawdbotConfig {
const opencodeModels = getOpencodeZenStaticFallbackModels();
const providers = { ...cfg.models?.providers };
providers["opencode-zen"] = {
baseUrl: OPENCODE_ZEN_API_BASE_URL,
apiKey: "opencode-zen",
api: "openai-completions",
models: getOpencodeZenStaticFallbackModels(),
models: opencodeModels,
};
const models = { ...cfg.agents?.defaults?.models };
for (const model of opencodeModels) {
const key = `opencode-zen/${model.id}`;
models[key] = models[key] ?? {};
}
models[OPENCODE_ZEN_DEFAULT_MODEL_REF] = {
...models[OPENCODE_ZEN_DEFAULT_MODEL_REF],
alias: "Opus",
alias: models[OPENCODE_ZEN_DEFAULT_MODEL_REF]?.alias ?? "Opus",
};
return {