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 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
3f93781b4b
commit
f7b32195cb
@@ -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"
|
||||
}
|
||||
|
||||
46
patches/@mariozechner__pi-agent-core.patch
Normal file
46
patches/@mariozechner__pi-agent-core.patch
Normal file
@@ -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> | string | undefined;
|
||||
+ /**
|
||||
+ * Extra params to pass to the provider API (e.g., Z.AI GLM thinking mode params).
|
||||
+ */
|
||||
+ extraParams?: Record<string, unknown>;
|
||||
}
|
||||
export declare class Agent {
|
||||
private _state;
|
||||
@@ -45,6 +49,8 @@ export declare class Agent {
|
||||
private followUpMode;
|
||||
streamFn: StreamFn;
|
||||
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
||||
+ /** Extra params to pass to the provider API. */
|
||||
+ extraParams?: Record<string, unknown>;
|
||||
private runningPrompt?;
|
||||
private resolveRunningPrompt?;
|
||||
constructor(opts?: AgentOptions);
|
||||
@@ -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<string, unknown>;
|
||||
}
|
||||
export declare const streamOpenAICompletions: StreamFunction<"openai-completions">;
|
||||
|
||||
28
patches/@mariozechner__pi-coding-agent.patch
Normal file
28
patches/@mariozechner__pi-coding-agent.patch
Normal file
@@ -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<string, unknown>;
|
||||
}
|
||||
/** Result from createAgentSession */
|
||||
export interface CreateAgentSessionResult {
|
||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
446
src/agents/pi-embedded-runner-extraparams.test.ts
Normal file
446
src/agents/pi-embedded-runner-extraparams.test.ts
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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<string, unknown> | 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<ReturnType<typeof createAgentSession>>["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<typeof createAgentSession>
|
||||
>["session"];
|
||||
@@ -1168,6 +1247,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
skills: [],
|
||||
contextFiles: [],
|
||||
additionalExtensionPaths,
|
||||
extraParams,
|
||||
}));
|
||||
|
||||
try {
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
};
|
||||
|
||||
export type AgentModelListConfig = {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user