diff --git a/src/config/options.ts b/src/config/options.ts index a5beb5d..f029002 100644 --- a/src/config/options.ts +++ b/src/config/options.ts @@ -189,8 +189,10 @@ export async function buildConfigOptions( server: ZcodeAcpServer, zcodeSid: string | null, ): Promise { - let currentProviderId = ""; - let currentModelId = "GLM-5.2"; + const configuredModels = loadAllModels(); + const defaultModel = configuredModels[0]; + let currentProviderId = defaultModel?.providerId ?? ""; + let currentModelId = defaultModel?.modelId ?? "GLM-5.2"; let currentMode = zcodeSid === null ? "yolo" : "build"; let currentThought = "high"; let thoughtOptions: Array<{ value: string; name: string }> | null = null; @@ -222,14 +224,14 @@ export async function buildConfigOptions( // right provider (and its apiKey). Fall back to the first enabled provider // when settings omits providerId (legacy sessions). const currentModel = formatModelValue( - currentProviderId || loadAllModels()[0]?.providerId || "builtin:bigmodel-coding-plan", + currentProviderId || defaultModel?.providerId || "builtin:bigmodel-coding-plan", currentModelId, ); // Model options: config.json enabled providers are authoritative. Builtin // models show as the bare modelId (clean dropdown for the common case); // third-party models prefix the provider name so they're distinguishable. - let modelOptions = loadAllModels().map((m) => ({ + let modelOptions = configuredModels.map((m) => ({ value: formatModelValue(m.providerId, m.modelId), name: isBuiltinProvider(m.providerId) ? m.modelId : `${m.providerName} › ${m.modelId}`, })); diff --git a/src/utils.ts b/src/utils.ts index c44017b..6a72c94 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -60,12 +60,12 @@ export const SLASH_COMMANDS = [ { name: "model", description: "Switch the session model", - input: { hint: "GLM-5.2|GLM-5-Turbo" }, + input: { hint: "model id" }, }, { name: "thought", description: "Set the reasoning effort", - input: { hint: "max|high|nothink" }, + input: { hint: "reasoning effort" }, }, { name: "quota", description: "Show remaining usage quota (5h / weekly / MCP)" }, { name: "mcp", description: "List available MCP servers" }, diff --git a/tests/bugfixes.test.ts b/tests/bugfixes.test.ts index 3a9d25f..a794c07 100644 --- a/tests/bugfixes.test.ts +++ b/tests/bugfixes.test.ts @@ -14,7 +14,7 @@ import { EventStreamListener } from "../src/backend/listener.js"; import { ZcodeBackend } from "../src/backend/client.js"; import { ProjectionDiffer } from "../src/translators/projection-differ.js"; import { flattenTodos } from "../src/handlers/session.js"; -import { CONFIG_META } from "../src/utils.js"; +import { CONFIG_META, SLASH_COMMANDS } from "../src/utils.js"; import type { ZcodeEvent, ZcodeResponse } from "../src/backend/types.js"; /** Build a listener over a fake backend (no subprocess; we drive handleEvent). */ @@ -246,6 +246,16 @@ describe("Bug 3: thought configOption metadata matches Python", () => { }); }); +describe("slash command argument hints survive ZCode catalog updates", () => { + it("does not hardcode model ids or reasoning variants", () => { + const modelCommand = SLASH_COMMANDS.find((command) => command.name === "model"); + const thoughtCommand = SLASH_COMMANDS.find((command) => command.name === "thought"); + + expect(modelCommand?.input?.hint).toBe("model id"); + expect(thoughtCommand?.input?.hint).toBe("reasoning effort"); + }); +}); + describe("Bug 5: usage fallback treats contextUsed=0 as falsy", () => { it("ProjectionDiffer falls back to totalTokenCount when contextUsed is 0", () => { const d = new ProjectionDiffer(); diff --git a/tests/runtime-model.test.ts b/tests/runtime-model.test.ts index 8a4a252..7a8d4c2 100644 --- a/tests/runtime-model.test.ts +++ b/tests/runtime-model.test.ts @@ -80,12 +80,18 @@ vi.mock("node:fs", async () => { }); // Import AFTER vi.mock is set up. -const { loadAllModels, modelContextWindow, parseModelValue, formatModelValue, buildRuntimeModel } = - await import("../src/config/options.js").then(async () => { - const opts = await import("../src/config/options.js"); - const rm = await import("../src/config/runtime-model.js"); - return { ...opts, buildRuntimeModel: rm.buildRuntimeModel }; - }); +const { + buildConfigOptions, + loadAllModels, + modelContextWindow, + parseModelValue, + formatModelValue, + buildRuntimeModel, +} = await import("../src/config/options.js").then(async () => { + const opts = await import("../src/config/options.js"); + const rm = await import("../src/config/runtime-model.js"); + return { ...opts, buildRuntimeModel: rm.buildRuntimeModel }; +}); describe("loadAllModels", () => { it("collects enabled builtins + active custom providers", () => { @@ -119,6 +125,16 @@ describe("loadAllModels", () => { }); }); +describe("pending session config", () => { + it("uses the first configured model instead of a version-pinned fallback", async () => { + const options = await buildConfigOptions({} as Parameters[0], null); + + expect(options.find((option) => option.id === "model")).toMatchObject({ + currentValue: "model-a", + }); + }); +}); + describe("modelContextWindow", () => { it("looks up context by provider+model (not hardcoded provider)", () => { expect(modelContextWindow("builtin:primary", "model-a")).toBe(1000000);