Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/config/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,10 @@ export async function buildConfigOptions(
server: ZcodeAcpServer,
zcodeSid: string | null,
): Promise<acp.SessionConfigOption[]> {
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;
Expand Down Expand Up @@ -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}`,
}));
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
12 changes: 11 additions & 1 deletion tests/bugfixes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down Expand Up @@ -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();
Expand Down
28 changes: 22 additions & 6 deletions tests/runtime-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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<typeof buildConfigOptions>[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);
Expand Down