From f7b32195cb1c6f8d5ca3ec8bc899ff2c2b0917ac Mon Sep 17 00:00:00 2001 From: mneves75 Date: Wed, 7 Jan 2026 23:57:40 -0300 Subject: [PATCH] feat(agent): auto-enable GLM-4.7 thinking mode Add automatic thinking mode support for Z.AI GLM-4.x models: - GLM-4.7: Preserved thinking (clear_thinking: false) - GLM-4.5/4.6: Interleaved thinking (clear_thinking: true) Uses Z.AI Cloud API format: thinking: { type: "enabled", clear_thinking: boolean } Includes patches for pi-ai, pi-agent-core, and pi-coding-agent to pass extraParams through the stream pipeline. User can override via config or disable via --thinking off. Co-Authored-By: Claude Opus 4.5 --- package.json | 4 + patches/@mariozechner__pi-agent-core.patch | 46 ++ patches/@mariozechner__pi-ai.patch | 38 ++ patches/@mariozechner__pi-coding-agent.patch | 28 ++ pnpm-lock.yaml | 26 +- .../pi-embedded-runner-extraparams.test.ts | 446 ++++++++++++++++++ src/agents/pi-embedded-runner.ts | 80 ++++ src/config/types.ts | 2 + src/config/zod-schema.ts | 2 + 9 files changed, 662 insertions(+), 10 deletions(-) create mode 100644 patches/@mariozechner__pi-agent-core.patch create mode 100644 patches/@mariozechner__pi-coding-agent.patch create mode 100644 src/agents/pi-embedded-runner-extraparams.test.ts diff --git a/package.json b/package.json index e70fe7d2e..c6fcc4ba6 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,8 @@ }, "patchedDependencies": { "@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.patch", + "@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch", + "@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch", "qrcode-terminal": "patches/qrcode-terminal.patch", "playwright-core@1.57.0": "patches/playwright-core@1.57.0.patch" } @@ -196,6 +198,8 @@ }, "patchedDependencies": { "@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.patch", + "@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch", + "@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch", "qrcode-terminal": "patches/qrcode-terminal.patch", "playwright-core@1.57.0": "patches/playwright-core@1.57.0.patch" } diff --git a/patches/@mariozechner__pi-agent-core.patch b/patches/@mariozechner__pi-agent-core.patch new file mode 100644 index 000000000..e754f6191 --- /dev/null +++ b/patches/@mariozechner__pi-agent-core.patch @@ -0,0 +1,46 @@ +diff --git a/dist/agent.js b/dist/agent.js +index 0000000..1111111 100644 +--- a/dist/agent.js ++++ b/dist/agent.js +@@ -42,6 +42,8 @@ export class Agent { + this.followUpMode = opts.followUpMode || "one-at-a-time"; + this.streamFn = opts.streamFn || streamSimple; + this.getApiKey = opts.getApiKey; ++ // PATCH: Support extraParams for provider-specific features (e.g., GLM-4.7 thinking mode) ++ this.extraParams = opts.extraParams; + } + get state() { + return this._state; +@@ -193,6 +195,8 @@ export class Agent { + convertToLlm: this.convertToLlm, + transformContext: this.transformContext, + getApiKey: this.getApiKey, ++ // PATCH: Pass extraParams through to stream function ++ extraParams: this.extraParams, + getSteeringMessages: async () => { + if (this.steeringMode === "one-at-a-time") { + if (this.steeringQueue.length > 0) { +diff --git a/dist/agent.d.ts b/dist/agent.d.ts +index 0000000..1111111 100644 +--- a/dist/agent.d.ts ++++ b/dist/agent.d.ts +@@ -33,6 +33,10 @@ export interface AgentOptions { + * Useful for expiring tokens (e.g., GitHub Copilot OAuth). + */ + getApiKey?: (provider: string) => Promise | string | undefined; ++ /** ++ * Extra params to pass to the provider API (e.g., Z.AI GLM thinking mode params). ++ */ ++ extraParams?: Record; + } + export declare class Agent { + private _state; +@@ -45,6 +49,8 @@ export declare class Agent { + private followUpMode; + streamFn: StreamFn; + getApiKey?: (provider: string) => Promise | string | undefined; ++ /** Extra params to pass to the provider API. */ ++ extraParams?: Record; + private runningPrompt?; + private resolveRunningPrompt?; + constructor(opts?: AgentOptions); diff --git a/patches/@mariozechner__pi-ai.patch b/patches/@mariozechner__pi-ai.patch index 91a31a42b..d96487a02 100644 --- a/patches/@mariozechner__pi-ai.patch +++ b/patches/@mariozechner__pi-ai.patch @@ -241,3 +241,41 @@ diff --git a/dist/providers/google-gemini-cli.js b/dist/providers/google-gemini- lastError = error instanceof Error ? error : new Error(String(error)); // Network errors are retryable if (attempt < MAX_RETRIES) { +diff --git a/dist/stream.js b/dist/stream.js +--- a/dist/stream.js ++++ b/dist/stream.js +@@ -105,6 +105,8 @@ function mapOptionsForApi(model, options, apiKey) { + maxTokens: options?.maxTokens || Math.min(model.maxTokens, 32000), + signal: options?.signal, + apiKey: apiKey || options?.apiKey, ++ // PATCH: Pass extraParams through to provider-specific API handlers ++ extraParams: options?.extraParams, + }; + // Helper to clamp xhigh to high for providers that don't support it + const clampReasoning = (effort) => (effort === "xhigh" ? "high" : effort); +diff --git a/dist/providers/openai-completions.js b/dist/providers/openai-completions.js +--- a/dist/providers/openai-completions.js ++++ b/dist/providers/openai-completions.js +@@ -333,6 +333,11 @@ function buildParams(model, context, options) { + if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) { + params.reasoning_effort = options.reasoningEffort; + } ++ // PATCH: Support arbitrary extra params for provider-specific features ++ // (e.g., Z.AI GLM-4.7 thinking: { type: "enabled", clear_thinking: boolean }) ++ if (options?.extraParams && typeof options.extraParams === 'object') { ++ Object.assign(params, options.extraParams); ++ } + return params; + } + function convertMessages(model, context, compat) { +diff --git a/dist/providers/openai-completions.d.ts b/dist/providers/openai-completions.d.ts +--- a/dist/providers/openai-completions.d.ts ++++ b/dist/providers/openai-completions.d.ts +@@ -7,5 +7,7 @@ export interface OpenAICompletionsOptions extends StreamOptions { + }; + }; + reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh"; ++ /** Extra params to pass directly to the API (e.g., Z.AI GLM thinking mode params) */ ++ extraParams?: Record; + } + export declare const streamOpenAICompletions: StreamFunction<"openai-completions">; diff --git a/patches/@mariozechner__pi-coding-agent.patch b/patches/@mariozechner__pi-coding-agent.patch new file mode 100644 index 000000000..08270770e --- /dev/null +++ b/patches/@mariozechner__pi-coding-agent.patch @@ -0,0 +1,28 @@ +diff --git a/dist/core/sdk.js b/dist/core/sdk.js +index 0000000..1111111 100644 +--- a/dist/core/sdk.js ++++ b/dist/core/sdk.js +@@ -441,6 +441,8 @@ export async function createAgentSession(options = {}) { + } + return key; + }, ++ // PATCH: Pass extraParams through for provider-specific features (e.g., GLM-4.7 thinking mode) ++ extraParams: options.extraParams, + }); + time("createAgent"); + // Restore messages if session has existing data +diff --git a/dist/core/sdk.d.ts b/dist/core/sdk.d.ts +index 0000000..1111111 100644 +--- a/dist/core/sdk.d.ts ++++ b/dist/core/sdk.d.ts +@@ -79,6 +79,10 @@ export interface CreateAgentSessionOptions { + sessionManager?: SessionManager; + /** Settings manager. Default: SettingsManager.create(cwd, agentDir) */ + settingsManager?: SettingsManager; ++ /** ++ * Extra params to pass to the provider API (e.g., Z.AI GLM thinking mode params). ++ */ ++ extraParams?: Record; + } + /** Result from createAgentSession */ + export interface CreateAgentSessionResult { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d567fe2c..6a277c6a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ overrides: '@sinclair/typebox': 0.34.46 patchedDependencies: + '@mariozechner/pi-agent-core': + hash: 5bb74b722de3e2889b9bb016211b3df2685829a7177c9cb12d2dbcf45f82e48d + path: patches/@mariozechner__pi-agent-core.patch '@mariozechner/pi-ai': - hash: 31ccee9eb1fc5053d766ccbf3abc8340a5fb3bea1c52b491a385b53468b81819 + hash: d6d88e60ea5c3261c2cfc131ecc47694803803608080c76f3dcf90dba4347122 path: patches/@mariozechner__pi-ai.patch + '@mariozechner/pi-coding-agent': + hash: 16dc564765691877942a8c03e057f73343403819170508356666b18f7243dd0c + path: patches/@mariozechner__pi-coding-agent.patch playwright-core@1.57.0: hash: 66f1f266424dbe354068aaa5bba87bfb0e1d7d834a938c25dd70d43cdf1c1b02 path: patches/playwright-core@1.57.0.patch @@ -39,13 +45,13 @@ importers: version: 1.3.4 '@mariozechner/pi-agent-core': specifier: ^0.37.2 - version: 0.37.2(ws@8.19.0)(zod@4.3.5) + version: 0.37.2(patch_hash=5bb74b722de3e2889b9bb016211b3df2685829a7177c9cb12d2dbcf45f82e48d)(ws@8.19.0)(zod@4.3.5) '@mariozechner/pi-ai': specifier: ^0.37.2 - version: 0.37.2(patch_hash=31ccee9eb1fc5053d766ccbf3abc8340a5fb3bea1c52b491a385b53468b81819)(ws@8.19.0)(zod@4.3.5) + version: 0.37.2(patch_hash=d6d88e60ea5c3261c2cfc131ecc47694803803608080c76f3dcf90dba4347122)(ws@8.19.0)(zod@4.3.5) '@mariozechner/pi-coding-agent': specifier: ^0.37.2 - version: 0.37.2(ws@8.19.0)(zod@4.3.5) + version: 0.37.2(patch_hash=16dc564765691877942a8c03e057f73343403819170508356666b18f7243dd0c)(ws@8.19.0)(zod@4.3.5) '@mariozechner/pi-tui': specifier: ^0.37.2 version: 0.37.2 @@ -3615,9 +3621,9 @@ snapshots: transitivePeerDependencies: - tailwindcss - '@mariozechner/pi-agent-core@0.37.2(ws@8.19.0)(zod@4.3.5)': + '@mariozechner/pi-agent-core@0.37.2(patch_hash=5bb74b722de3e2889b9bb016211b3df2685829a7177c9cb12d2dbcf45f82e48d)(ws@8.19.0)(zod@4.3.5)': dependencies: - '@mariozechner/pi-ai': 0.37.2(patch_hash=31ccee9eb1fc5053d766ccbf3abc8340a5fb3bea1c52b491a385b53468b81819)(ws@8.19.0)(zod@4.3.5) + '@mariozechner/pi-ai': 0.37.2(patch_hash=d6d88e60ea5c3261c2cfc131ecc47694803803608080c76f3dcf90dba4347122)(ws@8.19.0)(zod@4.3.5) '@mariozechner/pi-tui': 0.37.2 transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -3627,7 +3633,7 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.37.2(patch_hash=31ccee9eb1fc5053d766ccbf3abc8340a5fb3bea1c52b491a385b53468b81819)(ws@8.19.0)(zod@4.3.5)': + '@mariozechner/pi-ai@0.37.2(patch_hash=d6d88e60ea5c3261c2cfc131ecc47694803803608080c76f3dcf90dba4347122)(ws@8.19.0)(zod@4.3.5)': dependencies: '@anthropic-ai/sdk': 0.71.2(zod@4.3.5) '@google/genai': 1.34.0 @@ -3647,11 +3653,11 @@ snapshots: - ws - zod - '@mariozechner/pi-coding-agent@0.37.2(ws@8.19.0)(zod@4.3.5)': + '@mariozechner/pi-coding-agent@0.37.2(patch_hash=16dc564765691877942a8c03e057f73343403819170508356666b18f7243dd0c)(ws@8.19.0)(zod@4.3.5)': dependencies: '@crosscopy/clipboard': 0.2.8 - '@mariozechner/pi-agent-core': 0.37.2(ws@8.19.0)(zod@4.3.5) - '@mariozechner/pi-ai': 0.37.2(patch_hash=31ccee9eb1fc5053d766ccbf3abc8340a5fb3bea1c52b491a385b53468b81819)(ws@8.19.0)(zod@4.3.5) + '@mariozechner/pi-agent-core': 0.37.2(patch_hash=5bb74b722de3e2889b9bb016211b3df2685829a7177c9cb12d2dbcf45f82e48d)(ws@8.19.0)(zod@4.3.5) + '@mariozechner/pi-ai': 0.37.2(patch_hash=d6d88e60ea5c3261c2cfc131ecc47694803803608080c76f3dcf90dba4347122)(ws@8.19.0)(zod@4.3.5) '@mariozechner/pi-tui': 0.37.2 chalk: 5.6.2 cli-highlight: 2.1.11 diff --git a/src/agents/pi-embedded-runner-extraparams.test.ts b/src/agents/pi-embedded-runner-extraparams.test.ts new file mode 100644 index 000000000..8ffe0352f --- /dev/null +++ b/src/agents/pi-embedded-runner-extraparams.test.ts @@ -0,0 +1,446 @@ +import { describe, expect, it } from "vitest"; +import { resolveExtraParams } from "./pi-embedded-runner.js"; + +/** + * Tests for resolveExtraParams - the function that auto-enables GLM-4.x thinking mode. + * + * Z.AI Cloud API format: thinking: { type: "enabled", clear_thinking: boolean } + * - GLM-4.7: Preserved thinking (clear_thinking: false) - reasoning kept across turns + * - GLM-4.5/4.6: Interleaved thinking (clear_thinking: true) - reasoning cleared each turn + * + * @see https://docs.z.ai/guides/capabilities/thinking-mode + */ + +describe("resolveExtraParams", () => { + describe("GLM-4.7 preserved thinking (clear_thinking: false)", () => { + it("auto-enables preserved thinking for zai/glm-4.7 with no config", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.7", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, // Preserved thinking for GLM-4.7 + }, + }); + }); + + it("auto-enables preserved thinking for zai/GLM-4.7 (case insensitive)", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "GLM-4.7", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + }); + + describe("GLM-4.5/4.6 interleaved thinking (clear_thinking: true)", () => { + it("auto-enables interleaved thinking for zai/glm-4.5", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.5", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: true, // Interleaved thinking for GLM-4.5 + }, + }); + }); + + it("auto-enables interleaved thinking for zai/glm-4.6", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.6", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: true, // Interleaved thinking for GLM-4.6 + }, + }); + }); + + it("auto-enables interleaved thinking for zai/glm-4-flash", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4-flash", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: true, // Non-4.7 gets interleaved + }, + }); + }); + + it("auto-enables interleaved thinking for zai/glm-4.5-air", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.5-air", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: true, + }, + }); + }); + }); + + describe("config overrides", () => { + it("respects explicit thinking config from user (disable thinking)", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "zai/glm-4.7": { + params: { + thinking: { + type: "disabled", + }, + }, + }, + }, + }, + }, + provider: "zai", + modelId: "glm-4.7", + }); + + expect(result).toEqual({ + thinking: { + type: "disabled", + }, + }); + }); + + it("preserves other params while adding thinking config", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "zai/glm-4.7": { + params: { + temperature: 0.7, + max_tokens: 4096, + }, + }, + }, + }, + }, + provider: "zai", + modelId: "glm-4.7", + }); + + expect(result).toEqual({ + temperature: 0.7, + max_tokens: 4096, + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + + it("does not override explicit thinking config even if partial", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "zai/glm-4.7": { + params: { + thinking: { + type: "enabled", + // User explicitly omitted clear_thinking + }, + }, + }, + }, + }, + }, + provider: "zai", + modelId: "glm-4.7", + }); + + // Should use user's config exactly, not merge defaults + expect(result).toEqual({ + thinking: { + type: "enabled", + }, + }); + }); + }); + + describe("non-GLM models", () => { + it("returns undefined for anthropic/claude with no config", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "anthropic", + modelId: "claude-3-opus", + }); + + expect(result).toBeUndefined(); + }); + + it("returns undefined for openai/gpt-4 with no config", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "openai", + modelId: "gpt-4", + }); + + expect(result).toBeUndefined(); + }); + + it("passes through params for non-GLM models without modification", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "openai/gpt-4": { + params: { + logprobs: true, + top_logprobs: 5, + }, + }, + }, + }, + }, + provider: "openai", + modelId: "gpt-4", + }); + + expect(result).toEqual({ + logprobs: true, + top_logprobs: 5, + }); + }); + + it("does not auto-enable thinking for non-zai provider even with glm-4 model id", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "openai", + modelId: "glm-4.7", // Even if model ID contains glm-4 + }); + + expect(result).toBeUndefined(); + }); + }); + + describe("edge cases", () => { + it("handles empty config gracefully", () => { + const result = resolveExtraParams({ + cfg: {}, + provider: "zai", + modelId: "glm-4.7", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + + it("handles config with empty models gracefully", () => { + const result = resolveExtraParams({ + cfg: { agent: { models: {} } }, + provider: "zai", + modelId: "glm-4.7", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + + it("model alias lookup uses exact provider/model key", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "zai/glm-4.7": { + alias: "smart", + params: { + custom_param: "value", + }, + }, + }, + }, + }, + provider: "zai", + modelId: "glm-4.7", + }); + + expect(result).toEqual({ + custom_param: "value", + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + + it("treats thinking: null as explicit config (no auto-enable)", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "zai/glm-4.7": { + params: { + thinking: null, + }, + }, + }, + }, + }, + provider: "zai", + modelId: "glm-4.7", + }); + + // null is !== undefined, so we respect the explicit null config + expect(result).toEqual({ + thinking: null, + }); + }); + + it("handles GLM-4.7 variants (glm-4.7-flash, glm-4.7-plus)", () => { + // GLM-4.7-flash should get preserved thinking (contains "glm-4.7") + const flashResult = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.7-flash", + }); + + expect(flashResult).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, // Preserved thinking for GLM-4.7 variants + }, + }); + + // GLM-4.7-plus should also get preserved thinking + const plusResult = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.7-plus", + }); + + expect(plusResult).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + }); + + describe("thinkLevel parameter", () => { + it("thinkLevel: 'off' disables auto-enable for GLM-4.x", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.7", + thinkLevel: "off", + }); + + // Should NOT auto-enable thinking when user explicitly disabled it + expect(result).toBeUndefined(); + }); + + it("thinkLevel: 'off' still passes through explicit config", () => { + const result = resolveExtraParams({ + cfg: { + agent: { + models: { + "zai/glm-4.7": { + params: { + custom_param: "value", + }, + }, + }, + }, + }, + provider: "zai", + modelId: "glm-4.7", + thinkLevel: "off", + }); + + // Should pass through config params but NOT auto-add thinking + expect(result).toEqual({ + custom_param: "value", + }); + }); + + it("thinkLevel: 'low' allows auto-enable", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.7", + thinkLevel: "low", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + + it("thinkLevel: 'high' allows auto-enable", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.5", + thinkLevel: "high", + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: true, + }, + }); + }); + + it("thinkLevel: undefined (not specified) allows auto-enable", () => { + const result = resolveExtraParams({ + cfg: undefined, + provider: "zai", + modelId: "glm-4.7", + // thinkLevel not specified + }); + + expect(result).toEqual({ + thinking: { + type: "enabled", + clear_thinking: false, + }, + }); + }); + }); +}); diff --git a/src/agents/pi-embedded-runner.ts b/src/agents/pi-embedded-runner.ts index b0f7694e0..0f7ace97a 100644 --- a/src/agents/pi-embedded-runner.ts +++ b/src/agents/pi-embedded-runner.ts @@ -93,6 +93,70 @@ import { normalizeUsage, type UsageLike } from "./usage.js"; import { loadWorkspaceBootstrapFiles } from "./workspace.js"; // Optional features can be implemented as Pi extensions that run in the same Node process. + +/** + * Resolve provider-specific extraParams from model config. + * Auto-enables thinking mode for GLM-4.x models unless explicitly disabled. + * + * For ZAI GLM-4.x models, we auto-enable thinking via the Z.AI Cloud API format: + * thinking: { type: "enabled", clear_thinking: boolean } + * + * - GLM-4.7: Preserved thinking (clear_thinking: false) - reasoning kept across turns + * - GLM-4.5/4.6: Interleaved thinking (clear_thinking: true) - reasoning cleared each turn + * + * Users can override via config: + * agent.models["zai/glm-4.7"].params.thinking = { type: "disabled" } + * + * Or disable via runtime flag: --thinking off + * + * @see https://docs.z.ai/guides/capabilities/thinking-mode + * @internal Exported for testing only + */ +export function resolveExtraParams(params: { + cfg: ClawdbotConfig | undefined; + provider: string; + modelId: string; + thinkLevel?: string; +}): Record | undefined { + const modelKey = `${params.provider}/${params.modelId}`; + const modelConfig = params.cfg?.agent?.models?.[modelKey]; + let extraParams = modelConfig?.params ? { ...modelConfig.params } : undefined; + + // Auto-enable thinking for ZAI GLM-4.x models when not explicitly configured + // Skip if user explicitly disabled thinking via --thinking off + if (params.provider === "zai" && params.thinkLevel !== "off") { + const modelIdLower = params.modelId.toLowerCase(); + const isGlm4 = modelIdLower.includes("glm-4"); + + if (isGlm4) { + // Check if user has explicitly configured thinking params + const hasThinkingConfig = extraParams?.thinking !== undefined; + + if (!hasThinkingConfig) { + // GLM-4.7 supports preserved thinking (reasoning kept across turns) + // GLM-4.5/4.6 use interleaved thinking (reasoning cleared each turn) + // Z.AI Cloud API format: thinking: { type: "enabled", clear_thinking: boolean } + const isGlm47 = modelIdLower.includes("glm-4.7"); + const clearThinking = !isGlm47; + + extraParams = { + ...extraParams, + thinking: { + type: "enabled", + clear_thinking: clearThinking, + }, + }; + + log.debug( + `auto-enabled thinking for ${modelKey}: type=enabled, clear_thinking=${clearThinking}`, + ); + } + } + } + + return extraParams; +} + // We configure context pruning per-session via a WeakMap registry keyed by the SessionManager instance. function resolvePiExtensionPath(id: string): string { @@ -837,6 +901,13 @@ export async function compactEmbeddedPiSession(params: { sandboxEnabled: !!sandbox?.enabled, }); + const extraParams = resolveExtraParams({ + cfg: params.config, + provider, + modelId, + thinkLevel: params.thinkLevel, + }); + let session: Awaited>["session"]; ({ session } = await createAgentSession({ cwd: resolvedWorkspace, @@ -853,6 +924,7 @@ export async function compactEmbeddedPiSession(params: { skills: [], contextFiles: [], additionalExtensionPaths, + extraParams, })); try { @@ -1148,6 +1220,13 @@ export async function runEmbeddedPiAgent(params: { sandboxEnabled: !!sandbox?.enabled, }); + const extraParams = resolveExtraParams({ + cfg: params.config, + provider, + modelId, + thinkLevel, + }); + let session: Awaited< ReturnType >["session"]; @@ -1168,6 +1247,7 @@ export async function runEmbeddedPiAgent(params: { skills: [], contextFiles: [], additionalExtensionPaths, + extraParams, })); try { diff --git a/src/config/types.ts b/src/config/types.ts index abccd28a2..0d665dba0 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -982,6 +982,8 @@ export type AuthConfig = { export type AgentModelEntryConfig = { alias?: string; + /** Provider-specific API parameters (e.g., GLM-4.7 thinking mode). */ + params?: Record; }; export type AgentModelListConfig = { diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 778bab915..5ea893db8 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -910,6 +910,8 @@ export const ClawdbotSchema = z.object({ z.string(), z.object({ alias: z.string().optional(), + /** Provider-specific API parameters (e.g., GLM-4.7 thinking mode). */ + params: z.record(z.string(), z.unknown()).optional(), }), ) .optional(),