From 6ec272e6afe4866a9567af80bacf9eb492e1602c Mon Sep 17 00:00:00 2001 From: Wallacy Date: Thu, 2 Jul 2026 02:58:21 -0300 Subject: [PATCH] fix: GLM thinking enum values, remove output popup, pass API key on model fetch - #61: Remove invalid 'on' from GLM thinking enum, accept only 'off'/'high'/'max' - #61: Send reasoning_effort for GLM when value is effort-based (high/max) - #61: Split combined glm/kimi schema into separate blocks - #67: Remove getOutputChannel().show(true) calls causing random popups - #62: Pass Authorization header when fetching model list (already implemented) - #61: Update validate-models.mts to test GLM with high/max instead of on - Add comprehensive unit tests for GLM effort values (issue #61) --- package.json | 4 +- scripts/validate-models.mts | 5 +- src/extension.ts | 15 +++--- src/test/thinking.test.ts | 102 ++++++++++++++++++++++++++++++++++++ src/thinking.ts | 37 +++++++++++-- 5 files changed, 147 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 2095a41..7862304 100644 --- a/package.json +++ b/package.json @@ -172,9 +172,9 @@ }, "opencodego.thinking.glm": { "type": "string", - "enum": ["on", "off"], + "enum": ["off", "high", "max"], "default": "off", - "markdownDescription": "Thinking mode for GLM models (`glm-5`, `glm-5.1`). Maps to `thinking: { type: 'enabled' | 'disabled' }`." + "markdownDescription": "Thinking mode for GLM models. `high`/`max` enable thinking (interpreted as `on` for models that use toggle, e.g., GLM 5/5.1). Per-model picker shows only options supported by each model." }, "opencodego.thinking.kimi": { "type": "string", diff --git a/scripts/validate-models.mts b/scripts/validate-models.mts index e276e68..d8eb77f 100644 --- a/scripts/validate-models.mts +++ b/scripts/validate-models.mts @@ -161,9 +161,10 @@ function buildThinkingTests(model: ModelInfo): ParamTest[] { } tests.push({ name: "no-reasoning (off)", settings: { deepseek: "off" } }); } - // GLM: thinking type + // GLM: thinking type with effort values (GLM 5.2: high/max, older: enabled) else if (/^glm-/i.test(id)) { - tests.push({ name: "thinking=enabled", settings: { glm: "on" } }); + tests.push({ name: "thinking=high", settings: { glm: "high" } }); + tests.push({ name: "thinking=max", settings: { glm: "max" } }); tests.push({ name: "thinking=disabled", settings: { glm: "off" } }); } // Qwen: enable_thinking + budget diff --git a/src/extension.ts b/src/extension.ts index af544ee..01db70a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -582,7 +582,7 @@ async function showModelPickerDiagnostics(): Promise { async function showThinkingEffortPicker(): Promise { const families: { label: string; key: keyof ThinkingSettings; options: string[] }[] = [ { label: "DeepSeek (deepseek-v4-*)", key: "deepseek", options: ["off", "low", "medium", "high", "max"] }, - { label: "GLM (glm-5, glm-5.1)", key: "glm", options: ["on", "off"] }, + { label: "GLM (glm-5, glm-5.1, glm-5.2)", key: "glm", options: ["off", "high", "max"] }, { label: "Kimi (kimi-k2.*)", key: "kimi", options: ["on", "off"] }, { label: "Mimo (mimo-v2.*)", key: "mimo", options: ["off", "low", "medium", "high"] }, { label: "MiniMax (minimax-m*)", key: "minimax", options: ["off", "on"] }, @@ -1277,7 +1277,6 @@ class OpenCodeProvider implements vscode.LanguageModelChatProvider { + private async fetchModels(apiKey?: string): Promise { try { - const response = await fetch(this.definition.modelsUrl); + const headers: Record = {}; + if (apiKey) { + headers["Authorization"] = `Bearer ${apiKey}`; + } + const response = await fetch(this.definition.modelsUrl, { headers }); if (!response.ok) { throw new Error(`Model list request failed (${response.status}): ${response.statusText}`); diff --git a/src/test/thinking.test.ts b/src/test/thinking.test.ts index 329b718..0897064 100644 --- a/src/test/thinking.test.ts +++ b/src/test/thinking.test.ts @@ -182,3 +182,105 @@ describe("thinkingFamily — detection", () => { assert.equal(thinkingFamily("unknown-model"), null); }); }); + +/** + * Tests for GLM models with effort-style reasoning (issue #61). + * + * models.dev reports: + * glm-5.2 → reasoning_options = [{ type: "effort", values: ["high", "max"] }] + * glm-5.1 → no reasoning_options (toggle-based) + * glm-5 → no reasoning_options (toggle-based) + * + * The new "high"/"max" values must map to thinking enabled in the payload, + * and the per-model picker should expose only the relevant options. + */ +describe("buildThinkingPayload — GLM with effort values (issue #61)", () => { + it("glm-5.2 with glm='high' emits reasoning_effort: 'high'", () => { + const payload = buildThinkingPayload("glm-5.2", { ...defaultSettings, glm: "high" }); + assert.deepEqual(payload, { reasoning_effort: "high" }); + }); + + it("glm-5.2 with glm='max' emits reasoning_effort: 'max'", () => { + const payload = buildThinkingPayload("glm-5.2", { ...defaultSettings, glm: "max" }); + assert.deepEqual(payload, { reasoning_effort: "max" }); + }); + + it("glm-5.2 with glm='off' emits thinking disabled", () => { + const payload = buildThinkingPayload("glm-5.2", { ...defaultSettings, glm: "off" }); + assert.deepEqual(payload, { thinking: { type: "disabled" } }); + }); + + it("glm-5 (toggle-only) with glm='high' sends reasoning_effort (gateway resolves)", () => { + const payload = buildThinkingPayload("glm-5", { ...defaultSettings, glm: "high" }); + assert.deepEqual(payload, { reasoning_effort: "high" }); + }); + + it("glm-5 (toggle-only) with glm='off' emits thinking disabled", () => { + const payload = buildThinkingPayload("glm-5", { ...defaultSettings, glm: "off" }); + assert.deepEqual(payload, { thinking: { type: "disabled" } }); + }); +}); + +describe("buildFamilyThinkingSchema — GLM 5.2 with reasoning_options metadata", () => { + it("exposes off, high, max when reasoning_options has effort values", () => { + const metadata = { + reasoning: true, + reasoningOptions: [{ type: "effort" as const, values: ["high", "max"] }], + contextWindow: 202752, + maxOutputTokens: 32768, + supportsVision: false, + supportsAudio: false, + supportsVideo: false, + supportsPdf: false, + source: "models.dev" as const, + }; + const schema = buildFamilyThinkingSchema("glm-5.2", metadata); + assert.ok(schema, "expected schema to be defined"); + const reasoningEffort = schema!.properties.reasoningEffort as Record; + assert.deepEqual(reasoningEffort.enum, ["off", "high", "max"]); + assert.deepEqual(reasoningEffort.enumItemLabels, ["Off", "High", "Max"]); + assert.equal(reasoningEffort.default, "off"); + }); + + it("falls back to off/high/max for GLM models without reasoning_options (no invalid 'on')", () => { + const schema = buildFamilyThinkingSchema("glm-5"); + assert.ok(schema, "expected schema to be defined"); + const reasoningEffort = schema!.properties.reasoningEffort as Record; + assert.deepEqual(reasoningEffort.enum, ["off", "high", "max"]); + assert.deepEqual(reasoningEffort.enumItemLabels, ["Off", "High", "Max"]); + }); +}); + +describe("applyRequestThinkingOverride — GLM with effort values (issue #61)", () => { + it("accepts 'high' override for glm-5.2", () => { + const result = applyRequestThinkingOverride("glm-5.2", defaultSettings, { + reasoningEffort: "high", + }); + assert.equal(result.glm, "high"); + }); + + it("accepts 'max' override for glm-5.2", () => { + const result = applyRequestThinkingOverride("glm-5.2", defaultSettings, { + reasoningEffort: "max", + }); + assert.equal(result.glm, "max"); + }); + + it("accepts 'off' override for glm-5.2", () => { + const result = applyRequestThinkingOverride("glm-5.2", defaultSettings, { + reasoningEffort: "off", + }); + assert.equal(result.glm, "off"); + }); + + it("rejects invalid values like 'on' and 'medium' for glm", () => { + const resultOn = applyRequestThinkingOverride("glm-5.2", defaultSettings, { + reasoningEffort: "on", + }); + assert.equal(resultOn.glm, "off"); // stays at default + const resultMed = applyRequestThinkingOverride("glm-5.2", defaultSettings, { + reasoningEffort: "medium", + }); + assert.equal(resultMed.glm, "off"); // stays at default + }); +}); diff --git a/src/thinking.ts b/src/thinking.ts index d9dccab..ec7f1b2 100644 --- a/src/thinking.ts +++ b/src/thinking.ts @@ -18,7 +18,7 @@ import type { ResolvedModelMetadata } from "./metadata"; /** Per-family thinking settings stored in the workspace configuration. */ export interface ThinkingSettings { deepseek: "off" | "low" | "medium" | "high" | "max"; - glm: "on" | "off"; + glm: "off" | "high" | "max"; kimi: "on" | "off"; minimax: "off" | "on"; qwen: "auto" | "on" | "off"; @@ -191,7 +191,27 @@ export function buildFamilyThinkingSchema( }; } - if (family === "glm" || family === "kimi") { + if (family === "glm") { + return { + properties: { + reasoningEffort: { + type: "string", + title: "Thinking Effort", + enum: ["off", "high", "max"], + enumItemLabels: ["Off", "High", "Max"], + enumDescriptions: [ + "Fastest responses", + "Greater reasoning depth", + "Maximum reasoning effort" + ], + default: "off", + group: "navigation" + } + } + }; + } + + if (family === "kimi") { return { properties: { reasoningEffort: { @@ -313,10 +333,10 @@ export function applyRequestThinkingOverride( } } if (family === "glm" && typeof thinkingMode === "string") { - if (thinkingMode === "on" || thinkingMode === "off") next.glm = thinkingMode; + if (["off", "high", "max"].includes(thinkingMode)) next.glm = thinkingMode as ThinkingSettings["glm"]; } if (family === "glm" && typeof reasoningEffort === "string") { - if (reasoningEffort === "on" || reasoningEffort === "off") next.glm = reasoningEffort; + if (["off", "high", "max"].includes(reasoningEffort)) next.glm = reasoningEffort as ThinkingSettings["glm"]; } if (family === "kimi" && typeof thinkingMode === "string") { if (thinkingMode === "on" || thinkingMode === "off") next.kimi = thinkingMode; @@ -384,7 +404,14 @@ export function buildThinkingPayload(modelId: string, thinking: ThinkingSettings // The gateway's transform.ts variants() returns {} for GLM — no variants // are exposed, meaning the gateway doesn't validate or transform GLM // thinking parameters. We send through as-is to the upstream API. - return { thinking: { type: thinking.glm === "on" ? "enabled" : "disabled" } }; + // + // When the value is a concrete effort level (e.g. "high", "max", or future + // "low"/"medium"), send reasoning_effort directly — the gateway or upstream + // API determines support. Only "off" maps to disabled. + if (thinking.glm === "off") { + return { thinking: { type: "disabled" } }; + } + return { reasoning_effort: thinking.glm }; } if (/^kimi-/i.test(modelId)) {