@co0ontty/wand 4.73.0 → 4.75.1-beta.gbf7dafd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +4 -4
- package/dist/claude-sdk-runner.d.ts +2 -3
- package/dist/config.d.ts +1 -1
- package/dist/config.js +28 -0
- package/dist/message-truncator.js +3 -3
- package/dist/models.d.ts +39 -2
- package/dist/models.js +302 -9
- package/dist/native/darwin-arm64/wand-render +0 -0
- package/dist/native/darwin-arm64/wand-render.sha256 +1 -1
- package/dist/native/darwin-arm64/wand-render.version +1 -1
- package/dist/native/darwin-arm64/wand-structured-renderd +0 -0
- package/dist/native/darwin-arm64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/darwin-arm64/wand-structured-renderd.version +1 -0
- package/dist/native/darwin-x64/wand-render +0 -0
- package/dist/native/darwin-x64/wand-render.sha256 +1 -1
- package/dist/native/darwin-x64/wand-render.version +1 -1
- package/dist/native/darwin-x64/wand-structured-renderd +0 -0
- package/dist/native/darwin-x64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/darwin-x64/wand-structured-renderd.version +1 -0
- package/dist/native/linux-arm64/wand-render +0 -0
- package/dist/native/linux-arm64/wand-render.sha256 +1 -1
- package/dist/native/linux-arm64/wand-render.version +1 -1
- package/dist/native/linux-arm64/wand-structured-renderd +0 -0
- package/dist/native/linux-arm64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/linux-arm64/wand-structured-renderd.version +1 -0
- package/dist/native/linux-x64/wand-render +0 -0
- package/dist/native/linux-x64/wand-render.sha256 +1 -1
- package/dist/native/linux-x64/wand-render.version +1 -1
- package/dist/native/linux-x64/wand-structured-renderd +0 -0
- package/dist/native/linux-x64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/linux-x64/wand-structured-renderd.version +1 -0
- package/dist/process-manager.d.ts +2 -1
- package/dist/process-manager.js +15 -3
- package/dist/pty-terminal-state.d.ts +17 -0
- package/dist/pty-terminal-state.js +51 -1
- package/dist/render-daemon-client.d.ts +33 -2
- package/dist/render-daemon-client.js +66 -16
- package/dist/render-host.d.ts +27 -3
- package/dist/render-host.js +156 -17
- package/dist/server-session-routes.js +42 -3
- package/dist/server-task-routes.js +3 -3
- package/dist/server.js +12 -4
- package/dist/session-ai-context.d.ts +1 -1
- package/dist/session-ai-context.js +15 -4
- package/dist/session-logger.d.ts +2 -0
- package/dist/session-logger.js +44 -1
- package/dist/structured-grok-adapter.js +22 -0
- package/dist/structured-provider-common.d.ts +11 -9
- package/dist/structured-provider-common.js +60 -18
- package/dist/task-types.d.ts +2 -1
- package/dist/terminal-daemon-server.js +27 -4
- package/dist/tui/commands.js +183 -10
- package/dist/types.d.ts +11 -9
- package/dist/wand-task-sync.js +2 -1
- package/dist/web-ui/content/scripts.js +78 -72
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +9 -0
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/web-ui/thinking-efforts.d.ts +24 -0
- package/dist/web-ui/thinking-efforts.js +81 -0
- package/dist/ws-broadcast.js +11 -4
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-09-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "bf7dafd6a08e8ab3b71cc46a3cb5711585888fdb",
|
|
3
|
+
"builtAt": "2026-09-24T07:24:48.362Z",
|
|
4
|
+
"version": "4.75.1-beta.gbf7dafd",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type Options as SdkOptions } from "@anthropic-ai/claude-agent-sdk";
|
|
2
1
|
export type ClaudeRunErrorCode = "CLAUDE_CLI_MISSING" | "CLAUDE_TIMEOUT" | "CLAUDE_CLI_FAILED" | "CLAUDE_EMPTY_RESULT";
|
|
3
2
|
export declare class ClaudeRunError extends Error {
|
|
4
3
|
readonly code: ClaudeRunErrorCode;
|
|
@@ -9,8 +8,8 @@ export interface RunClaudePrintOptions {
|
|
|
9
8
|
timeoutMs: number;
|
|
10
9
|
/** Optional Claude model id / alias. Empty or "default" keeps Claude Code's own default. */
|
|
11
10
|
model?: string;
|
|
12
|
-
/** Optional adaptive-thinking effort
|
|
13
|
-
effort?:
|
|
11
|
+
/** Optional adaptive-thinking effort. CLI 动态档位(high / xhigh 等)原样传给 SDK。 */
|
|
12
|
+
effort?: string;
|
|
14
13
|
/**
|
|
15
14
|
* 用户偏好的回复语言(取自 config.language)。传进来时会以
|
|
16
15
|
* `appendSystemPrompt` 形式灌给 Claude,保证 quick-commit / prompt-optimizer
|
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
7
7
|
* 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
|
|
8
8
|
* 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
|
|
9
9
|
*/
|
|
10
|
-
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
12
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
13
13
|
/**
|
package/dist/config.js
CHANGED
|
@@ -33,6 +33,8 @@ export const PREFERENCE_KEYS = [
|
|
|
33
33
|
"commitModel",
|
|
34
34
|
"commitAiSource",
|
|
35
35
|
"systemAi",
|
|
36
|
+
"systemAiCli",
|
|
37
|
+
"systemAiModel",
|
|
36
38
|
"defaultThinkingEffort",
|
|
37
39
|
"structuredRunner",
|
|
38
40
|
"language",
|
|
@@ -113,6 +115,7 @@ export const defaultConfig = () => ({
|
|
|
113
115
|
authHeader: "bearer",
|
|
114
116
|
source: "custom",
|
|
115
117
|
},
|
|
118
|
+
systemAiModel: "",
|
|
116
119
|
defaultThinkingEffort: "off",
|
|
117
120
|
structuredRunner: "cli",
|
|
118
121
|
inheritEnv: true,
|
|
@@ -382,6 +385,16 @@ export function applyStoragePreferences(config, storage) {
|
|
|
382
385
|
const v = storage.getPreference(preferenceStorageKey("systemAi"), defaults.systemAi);
|
|
383
386
|
config.systemAi = normalizeSystemAiConfig(v, defaults.systemAi);
|
|
384
387
|
}
|
|
388
|
+
if (storage.hasPreference(preferenceStorageKey("systemAiCli"))) {
|
|
389
|
+
const v = storage.getPreference(preferenceStorageKey("systemAiCli"), defaults.systemAiCli);
|
|
390
|
+
if (isSessionProvider(v))
|
|
391
|
+
config.systemAiCli = v;
|
|
392
|
+
}
|
|
393
|
+
if (storage.hasPreference(preferenceStorageKey("systemAiModel"))) {
|
|
394
|
+
const v = storage.getPreference(preferenceStorageKey("systemAiModel"), defaults.systemAiModel);
|
|
395
|
+
if (typeof v === "string")
|
|
396
|
+
config.systemAiModel = v.trim();
|
|
397
|
+
}
|
|
385
398
|
if (storage.hasPreference(preferenceStorageKey("defaultThinkingEffort"))) {
|
|
386
399
|
const v = storage.getPreference(preferenceStorageKey("defaultThinkingEffort"), defaults.defaultThinkingEffort ?? "off");
|
|
387
400
|
if (isThinkingEffort(v))
|
|
@@ -520,6 +533,19 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
520
533
|
config.systemAi = normalized;
|
|
521
534
|
break;
|
|
522
535
|
}
|
|
536
|
+
case "systemAiCli": {
|
|
537
|
+
if (!isSessionProvider(value))
|
|
538
|
+
throw new Error(`无效系统 AI CLI: ${String(value)}`);
|
|
539
|
+
storage.setPreference(dbKey, value);
|
|
540
|
+
config.systemAiCli = value;
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case "systemAiModel": {
|
|
544
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
545
|
+
storage.setPreference(dbKey, v);
|
|
546
|
+
config.systemAiModel = v;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
523
549
|
case "defaultThinkingEffort": {
|
|
524
550
|
if (!isThinkingEffort(value))
|
|
525
551
|
throw new Error(`无效思考深度: ${String(value)}`);
|
|
@@ -770,6 +796,8 @@ function mergeWithDefaults(input) {
|
|
|
770
796
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
771
797
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
772
798
|
commitAiSource: input.commitAiSource === "api" ? "api" : "cli",
|
|
799
|
+
systemAiCli: isSessionProvider(input.systemAiCli) ? input.systemAiCli : undefined,
|
|
800
|
+
systemAiModel: typeof input.systemAiModel === "string" ? input.systemAiModel.trim() : defaults.systemAiModel,
|
|
773
801
|
defaultThinkingEffort: isThinkingEffort(input.defaultThinkingEffort) ? input.defaultThinkingEffort : "off",
|
|
774
802
|
structuredRunner: (input.structuredRunner === "sdk" || input.structuredRunner === "cli") ? input.structuredRunner : defaults.structuredRunner,
|
|
775
803
|
inheritEnv: typeof input.inheritEnv === "boolean" ? input.inheritEnv : (defaults.inheritEnv ?? true),
|
|
@@ -13,12 +13,12 @@ const SUMMARY_LENGTH = 100;
|
|
|
13
13
|
*/
|
|
14
14
|
const MESSAGE_WINDOW_SIZE = 40;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* 块级窗口的默认预算:显式启用时 init/REST 只下发最近这么多个「内容块」(跨 turn 累计,
|
|
17
17
|
* 必要时切掉最旧那条 turn 的头部),更早的块由客户端滚动到顶时按需分页拉取。
|
|
18
18
|
* turn 级窗口(MESSAGE_WINDOW_SIZE)对「单条 turn 携带上百块」的长任务无能为力——
|
|
19
19
|
* 一条流式 assistant turn 可膨胀到 1MB+,整条下发会撑爆移动端 WS 帧、拖慢打开。
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* 块级窗口是对这种会话的根治手段。客户端显式带 blockBudget 时启用(Web/iOS),
|
|
21
|
+
* 未携带的原生客户端仍走 turn 级路径。
|
|
22
22
|
*/
|
|
23
23
|
const MESSAGE_BLOCK_WINDOW = 60;
|
|
24
24
|
/**
|
package/dist/models.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ interface ModelCacheStorage {
|
|
|
12
12
|
interface ModelCommandOptions {
|
|
13
13
|
env: NodeJS.ProcessEnv;
|
|
14
14
|
timeout: number;
|
|
15
|
+
/** Written to stdin, then closed. Pi RPC needs this. */
|
|
16
|
+
input?: string;
|
|
15
17
|
}
|
|
16
18
|
interface ModelCommandResult {
|
|
17
19
|
stdout: string;
|
|
@@ -36,6 +38,18 @@ export interface ModelRefreshOptions {
|
|
|
36
38
|
verifyClaudeCandidates?: boolean;
|
|
37
39
|
now?: () => Date;
|
|
38
40
|
}
|
|
41
|
+
export interface ThinkingEffortLevel {
|
|
42
|
+
effort: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
}
|
|
45
|
+
/** 各 CLI 自己报出来的思考档位。Codex / 部分 OpenCode 模型另有 per-model 列表。 */
|
|
46
|
+
export interface ProviderThinkingEfforts {
|
|
47
|
+
claude: ThinkingEffortLevel[];
|
|
48
|
+
opencode: ThinkingEffortLevel[];
|
|
49
|
+
grok: ThinkingEffortLevel[];
|
|
50
|
+
qoder: ThinkingEffortLevel[];
|
|
51
|
+
pi: ThinkingEffortLevel[];
|
|
52
|
+
}
|
|
39
53
|
export interface ModelCache {
|
|
40
54
|
models: ClaudeModelInfo[];
|
|
41
55
|
codexModels: ClaudeModelInfo[];
|
|
@@ -43,10 +57,13 @@ export interface ModelCache {
|
|
|
43
57
|
grokModels: ClaudeModelInfo[];
|
|
44
58
|
qoderModels: ClaudeModelInfo[];
|
|
45
59
|
piModels: ClaudeModelInfo[];
|
|
60
|
+
thinkingEfforts: ProviderThinkingEfforts;
|
|
46
61
|
claudeVersion: string | null;
|
|
47
62
|
opencodeVersion: string | null;
|
|
48
63
|
refreshedAt: string;
|
|
49
64
|
}
|
|
65
|
+
/** 探针失败时的已知档位。下一次 CLI 成功应答会把它换掉。 */
|
|
66
|
+
export declare const FALLBACK_PROVIDER_THINKING: ProviderThinkingEfforts;
|
|
50
67
|
/** Immutable-looking snapshot returned to API clients. */
|
|
51
68
|
export interface ModelCatalogSnapshot extends ModelCache {
|
|
52
69
|
/** SHA-256 of the catalog excluding `refreshedAt`. Changes only with content. */
|
|
@@ -62,6 +79,13 @@ export interface ModelCatalogRefreshRequest {
|
|
|
62
79
|
/** Administrator-triggered refreshes may also validate Claude candidates. */
|
|
63
80
|
verifyClaudeCandidates?: boolean;
|
|
64
81
|
}
|
|
82
|
+
/** 从 CLI 的 help / 非法档位报错里抽出 `low, medium, high` 这种列表。 */
|
|
83
|
+
export declare function parseCliEffortList(text: string): string[];
|
|
84
|
+
/**
|
|
85
|
+
* `opencode models --verbose` 是「一行 id + 一段 JSON」。
|
|
86
|
+
* variants 是这个模型自己的推理档位;没有 JSON 时退回一行一个 id。
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseOpenCodeModelCatalog(stdout: string): ClaudeModelInfo[];
|
|
65
89
|
/**
|
|
66
90
|
* Parse `grok models` human-readable output:
|
|
67
91
|
*
|
|
@@ -79,12 +103,22 @@ export declare function parseGrokModels(stdout: string): ClaudeModelInfo[];
|
|
|
79
103
|
* safe ID below the `MODEL` header.
|
|
80
104
|
*/
|
|
81
105
|
export declare function parseQoderModels(stdout: string): ClaudeModelInfo[];
|
|
106
|
+
/**
|
|
107
|
+
* Same rule as Pi's `getSupportedThinkingLevels`: no reasoning means only off;
|
|
108
|
+
* `xhigh` / `max` exist only when `thinkingLevelMap` defines them; `null` drops a level.
|
|
109
|
+
*/
|
|
110
|
+
export declare function piThinkingLevelsForModel(model: {
|
|
111
|
+
reasoning?: boolean;
|
|
112
|
+
thinkingLevelMap?: Record<string, unknown> | null;
|
|
113
|
+
}): string[];
|
|
114
|
+
/** `pi --mode rpc` + `get_available_models`. One JSON response holds every model's thinking map. */
|
|
115
|
+
export declare function parsePiRpcModels(stdout: string): ClaudeModelInfo[];
|
|
82
116
|
/**
|
|
83
117
|
* Parse `pi --list-models`.
|
|
84
118
|
*
|
|
85
119
|
* Pi prints a padEnd-aligned table whose selectable `--model` value is
|
|
86
|
-
* `provider/id` (for example `xai/grok-4.6`). The
|
|
87
|
-
*
|
|
120
|
+
* `provider/id` (for example `xai/grok-4.6`). The table only says thinking
|
|
121
|
+
* yes/no, so per-model levels come from `parsePiRpcModels` instead.
|
|
88
122
|
*/
|
|
89
123
|
export declare function parsePiModels(stdout: string): ClaudeModelInfo[];
|
|
90
124
|
/** Parse `opencode models`, whose stable machine-friendly output is one provider/model id per line. */
|
|
@@ -106,8 +140,11 @@ export declare class ModelCatalogService {
|
|
|
106
140
|
private hasPersistedSnapshot;
|
|
107
141
|
private refreshPromise;
|
|
108
142
|
private inFlightIncludesVerification;
|
|
143
|
+
private readonly changeListeners;
|
|
109
144
|
constructor(getOptions: () => ModelRefreshOptions);
|
|
110
145
|
snapshot(): ModelCatalogSnapshot;
|
|
146
|
+
/** 目录内容真正变化时通知。客户端靠它刷新下拉,不用自己再跑一遍 CLI。 */
|
|
147
|
+
onChanged(listener: (result: ModelCatalogRefreshResult) => void): () => void;
|
|
111
148
|
refresh(request?: ModelCatalogRefreshRequest): Promise<ModelCatalogRefreshResult>;
|
|
112
149
|
private performRefresh;
|
|
113
150
|
}
|
package/dist/models.js
CHANGED
|
@@ -57,6 +57,28 @@ const QODER_FALLBACK_MODELS = [
|
|
|
57
57
|
const PI_FALLBACK_MODELS = [
|
|
58
58
|
{ id: "default", label: "跟随 Pi 默认", alias: true },
|
|
59
59
|
];
|
|
60
|
+
const EFFORT_ID_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
61
|
+
function effortLevels(ids) {
|
|
62
|
+
return ids.map((effort) => ({ effort }));
|
|
63
|
+
}
|
|
64
|
+
/** 探针失败时的已知档位。下一次 CLI 成功应答会把它换掉。 */
|
|
65
|
+
export const FALLBACK_PROVIDER_THINKING = {
|
|
66
|
+
claude: effortLevels(["low", "medium", "high", "xhigh", "max"]),
|
|
67
|
+
opencode: effortLevels(["low", "medium", "high", "max"]),
|
|
68
|
+
grok: effortLevels(["low", "medium", "high", "xhigh"]),
|
|
69
|
+
qoder: effortLevels(["auto", "none", "low", "medium", "high", "xhigh", "max", "ultracode"]),
|
|
70
|
+
pi: effortLevels(["minimal", "low", "medium", "high", "xhigh", "max"]),
|
|
71
|
+
};
|
|
72
|
+
function cloneThinkingEfforts(efforts) {
|
|
73
|
+
const copy = (levels) => levels.map((level) => ({ ...level }));
|
|
74
|
+
return {
|
|
75
|
+
claude: copy(efforts.claude),
|
|
76
|
+
opencode: copy(efforts.opencode),
|
|
77
|
+
grok: copy(efforts.grok),
|
|
78
|
+
qoder: copy(efforts.qoder),
|
|
79
|
+
pi: copy(efforts.pi),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
60
82
|
function cloneModels(models) {
|
|
61
83
|
return models.map((model) => ({
|
|
62
84
|
...model,
|
|
@@ -73,6 +95,7 @@ function cloneCache(cache) {
|
|
|
73
95
|
grokModels: cloneModels(cache.grokModels),
|
|
74
96
|
qoderModels: cloneModels(cache.qoderModels),
|
|
75
97
|
piModels: cloneModels(cache.piModels),
|
|
98
|
+
thinkingEfforts: cloneThinkingEfforts(cache.thinkingEfforts),
|
|
76
99
|
claudeVersion: cache.claudeVersion,
|
|
77
100
|
opencodeVersion: cache.opencodeVersion,
|
|
78
101
|
refreshedAt: cache.refreshedAt,
|
|
@@ -82,7 +105,8 @@ function defaultCommandRunner(file, args, options) {
|
|
|
82
105
|
return execFileAsync(file, args, {
|
|
83
106
|
env: options.env,
|
|
84
107
|
timeout: options.timeout,
|
|
85
|
-
maxBuffer: 1024 * 1024,
|
|
108
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
109
|
+
...(options.input !== undefined ? { input: options.input } : {}),
|
|
86
110
|
}).then(({ stdout, stderr }) => ({ stdout: String(stdout), stderr: String(stderr) }));
|
|
87
111
|
}
|
|
88
112
|
function normalizeClaudeModelId(value) {
|
|
@@ -238,6 +262,7 @@ function createInitialCache(options) {
|
|
|
238
262
|
grokModels: cloneModels(GROK_FALLBACK_MODELS),
|
|
239
263
|
qoderModels: cloneModels(QODER_FALLBACK_MODELS),
|
|
240
264
|
piModels: cloneModels(PI_FALLBACK_MODELS),
|
|
265
|
+
thinkingEfforts: cloneThinkingEfforts(FALLBACK_PROVIDER_THINKING),
|
|
241
266
|
claudeVersion: null,
|
|
242
267
|
opencodeVersion: null,
|
|
243
268
|
refreshedAt: now.toISOString(),
|
|
@@ -275,11 +300,11 @@ async function probeCodexModels(runner, env) {
|
|
|
275
300
|
}
|
|
276
301
|
async function probeOpenCode(runner, env) {
|
|
277
302
|
const [modelsResult, versionResult] = await Promise.allSettled([
|
|
278
|
-
runner("opencode", ["models"], { env, timeout: 8000 }),
|
|
303
|
+
runner("opencode", ["models", "--verbose"], { env, timeout: 8000 }),
|
|
279
304
|
runner("opencode", ["--version"], { env, timeout: 5000 }),
|
|
280
305
|
]);
|
|
281
306
|
const models = modelsResult.status === "fulfilled"
|
|
282
|
-
? { ok: true, value:
|
|
307
|
+
? { ok: true, value: parseOpenCodeModelCatalog(modelsResult.value.stdout) }
|
|
283
308
|
: { ok: false };
|
|
284
309
|
const version = versionResult.status === "fulfilled"
|
|
285
310
|
? {
|
|
@@ -308,14 +333,143 @@ async function probeQoderModels(runner, env) {
|
|
|
308
333
|
}
|
|
309
334
|
}
|
|
310
335
|
async function probePiModels(runner, env) {
|
|
336
|
+
const rpcText = await commandText(runner, "pi", ["--mode", "rpc", "--no-session"], env, 12_000, `${JSON.stringify({ id: "wand-models", type: "get_available_models" })}\n`);
|
|
337
|
+
const fromRpc = parsePiRpcModels(rpcText);
|
|
338
|
+
if (fromRpc.length > 1 || (fromRpc.length === 1 && fromRpc[0]?.id !== "default")) {
|
|
339
|
+
return { ok: true, value: fromRpc };
|
|
340
|
+
}
|
|
311
341
|
try {
|
|
312
342
|
const { stdout } = await runner("pi", ["--list-models"], { env, timeout: 8000 });
|
|
313
|
-
|
|
343
|
+
const parsed = parsePiModels(stdout);
|
|
344
|
+
return parsed.some((model) => model.id !== "default") ? { ok: true, value: parsed } : { ok: false };
|
|
314
345
|
}
|
|
315
346
|
catch {
|
|
316
347
|
return { ok: false };
|
|
317
348
|
}
|
|
318
349
|
}
|
|
350
|
+
/** 从 CLI 的 help / 非法档位报错里抽出 `low, medium, high` 这种列表。 */
|
|
351
|
+
export function parseCliEffortList(text) {
|
|
352
|
+
const patterns = [
|
|
353
|
+
/valid values(?:\s+are)?\s*:\s*([^.`\n]+)/i,
|
|
354
|
+
/use one of:\s*([^.`\n]+)/i,
|
|
355
|
+
/set thinking level:\s*([^.`\n]+)/i,
|
|
356
|
+
];
|
|
357
|
+
for (const pattern of patterns) {
|
|
358
|
+
const match = text.match(pattern);
|
|
359
|
+
if (!match?.[1])
|
|
360
|
+
continue;
|
|
361
|
+
const ids = match[1]
|
|
362
|
+
.split(/[,/]/)
|
|
363
|
+
.map((part) => part.trim().toLowerCase())
|
|
364
|
+
.filter((part) => EFFORT_ID_PATTERN.test(part));
|
|
365
|
+
if (ids.length)
|
|
366
|
+
return Array.from(new Set(ids));
|
|
367
|
+
}
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
function effortLevelsFromIds(ids) {
|
|
371
|
+
return ids.map((effort) => ({ effort }));
|
|
372
|
+
}
|
|
373
|
+
async function commandText(runner, file, args, env, timeout, input) {
|
|
374
|
+
try {
|
|
375
|
+
const result = await runner(file, args, { env, timeout, ...(input !== undefined ? { input } : {}) });
|
|
376
|
+
return `${result.stderr}\n${result.stdout}`;
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
const record = error;
|
|
380
|
+
return `${String(record.stderr ?? "")}\n${String(record.stdout ?? "")}\n${String(record.message ?? "")}`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function probeEffortList(runner, env, file, args) {
|
|
384
|
+
const text = await commandText(runner, file, args, env, 8000);
|
|
385
|
+
const ids = parseCliEffortList(text);
|
|
386
|
+
return ids.length ? { ok: true, value: effortLevelsFromIds(ids) } : { ok: false };
|
|
387
|
+
}
|
|
388
|
+
function variantsToEfforts(value) {
|
|
389
|
+
const ids = [];
|
|
390
|
+
if (Array.isArray(value)) {
|
|
391
|
+
for (const item of value) {
|
|
392
|
+
if (typeof item === "string")
|
|
393
|
+
ids.push(item);
|
|
394
|
+
else if (isRecord(item) && typeof item.effort === "string")
|
|
395
|
+
ids.push(item.effort);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else if (isRecord(value)) {
|
|
399
|
+
ids.push(...Object.keys(value));
|
|
400
|
+
}
|
|
401
|
+
const unique = Array.from(new Set(ids.map((id) => id.trim().toLowerCase()).filter((id) => EFFORT_ID_PATTERN.test(id))));
|
|
402
|
+
return effortLevelsFromIds(unique);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* `opencode models --verbose` 是「一行 id + 一段 JSON」。
|
|
406
|
+
* variants 是这个模型自己的推理档位;没有 JSON 时退回一行一个 id。
|
|
407
|
+
*/
|
|
408
|
+
export function parseOpenCodeModelCatalog(stdout) {
|
|
409
|
+
const verbose = parseOpenCodeVerboseModels(stdout);
|
|
410
|
+
if (verbose.length)
|
|
411
|
+
return verbose;
|
|
412
|
+
return parseOpenCodeModels(stdout);
|
|
413
|
+
}
|
|
414
|
+
function parseOpenCodeVerboseModels(stdout) {
|
|
415
|
+
const lines = stdout.split(/\r?\n/);
|
|
416
|
+
const found = [];
|
|
417
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
418
|
+
const header = lines[index]?.trim() ?? "";
|
|
419
|
+
if (!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:/-]*$/i.test(header))
|
|
420
|
+
continue;
|
|
421
|
+
if ((lines[index + 1] ?? "").trim() !== "{")
|
|
422
|
+
continue;
|
|
423
|
+
let depth = 0;
|
|
424
|
+
let end = index + 1;
|
|
425
|
+
for (; end < lines.length; end += 1) {
|
|
426
|
+
for (const character of lines[end] ?? "") {
|
|
427
|
+
if (character === "{")
|
|
428
|
+
depth += 1;
|
|
429
|
+
else if (character === "}")
|
|
430
|
+
depth -= 1;
|
|
431
|
+
}
|
|
432
|
+
if (depth === 0)
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
if (depth !== 0)
|
|
436
|
+
continue;
|
|
437
|
+
let record = null;
|
|
438
|
+
try {
|
|
439
|
+
record = JSON.parse(lines.slice(index + 1, end + 1).join("\n"));
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
record = null;
|
|
443
|
+
}
|
|
444
|
+
const reasoningEfforts = record ? variantsToEfforts(record.variants) : [];
|
|
445
|
+
found.push({
|
|
446
|
+
id: header,
|
|
447
|
+
label: header,
|
|
448
|
+
...(reasoningEfforts.length ? { reasoningEfforts } : {}),
|
|
449
|
+
});
|
|
450
|
+
index = end;
|
|
451
|
+
}
|
|
452
|
+
if (!found.length)
|
|
453
|
+
return [];
|
|
454
|
+
return [
|
|
455
|
+
{ id: "default", label: "跟随 OpenCode 默认", alias: true },
|
|
456
|
+
...found,
|
|
457
|
+
];
|
|
458
|
+
}
|
|
459
|
+
function unionModelEfforts(models) {
|
|
460
|
+
const seen = new Set();
|
|
461
|
+
const levels = [];
|
|
462
|
+
for (const model of models) {
|
|
463
|
+
for (const level of model.reasoningEfforts ?? []) {
|
|
464
|
+
const effort = level.effort.trim().toLowerCase();
|
|
465
|
+
if (!EFFORT_ID_PATTERN.test(effort) || seen.has(effort))
|
|
466
|
+
continue;
|
|
467
|
+
seen.add(effort);
|
|
468
|
+
levels.push({ effort, ...(level.description ? { description: level.description } : {}) });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return levels;
|
|
472
|
+
}
|
|
319
473
|
function createOfficialModelsApi(apiKey) {
|
|
320
474
|
const client = new Anthropic({ apiKey });
|
|
321
475
|
return {
|
|
@@ -400,7 +554,7 @@ export function parseGrokModels(stdout) {
|
|
|
400
554
|
defaultModel = defaultMatch[1];
|
|
401
555
|
continue;
|
|
402
556
|
}
|
|
403
|
-
const bulletMatch = line.match(
|
|
557
|
+
const bulletMatch = line.match(/^[-*•]+\s+([A-Za-z0-9][A-Za-z0-9._:-]{0,127})(?:\s*\(.*\))?\s*$/);
|
|
404
558
|
if (bulletMatch) {
|
|
405
559
|
ids.push(bulletMatch[1]);
|
|
406
560
|
continue;
|
|
@@ -460,12 +614,88 @@ export function parseQoderModels(stdout) {
|
|
|
460
614
|
}
|
|
461
615
|
return [...cloneModels(QODER_FALLBACK_MODELS), ...discovered];
|
|
462
616
|
}
|
|
617
|
+
const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
618
|
+
/**
|
|
619
|
+
* Same rule as Pi's `getSupportedThinkingLevels`: no reasoning means only off;
|
|
620
|
+
* `xhigh` / `max` exist only when `thinkingLevelMap` defines them; `null` drops a level.
|
|
621
|
+
*/
|
|
622
|
+
export function piThinkingLevelsForModel(model) {
|
|
623
|
+
if (!model.reasoning)
|
|
624
|
+
return ["off"];
|
|
625
|
+
const map = model.thinkingLevelMap ?? {};
|
|
626
|
+
return PI_THINKING_LEVELS.filter((level) => {
|
|
627
|
+
if (!Object.prototype.hasOwnProperty.call(map, level)) {
|
|
628
|
+
return level !== "xhigh" && level !== "max";
|
|
629
|
+
}
|
|
630
|
+
return map[level] !== null;
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/** `pi --mode rpc` + `get_available_models`. One JSON response holds every model's thinking map. */
|
|
634
|
+
export function parsePiRpcModels(stdout) {
|
|
635
|
+
let models = null;
|
|
636
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
637
|
+
const line = rawLine.trim();
|
|
638
|
+
if (!line.startsWith("{"))
|
|
639
|
+
continue;
|
|
640
|
+
let record = null;
|
|
641
|
+
try {
|
|
642
|
+
record = JSON.parse(line);
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
record = null;
|
|
646
|
+
}
|
|
647
|
+
if (!record || record.type !== "response" || record.command !== "get_available_models" || record.success !== true) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const data = isRecord(record.data) ? record.data : null;
|
|
651
|
+
if (data && Array.isArray(data.models))
|
|
652
|
+
models = data.models;
|
|
653
|
+
}
|
|
654
|
+
if (!models)
|
|
655
|
+
return [];
|
|
656
|
+
const discovered = [];
|
|
657
|
+
const seen = new Set();
|
|
658
|
+
for (const entry of models) {
|
|
659
|
+
if (!isRecord(entry))
|
|
660
|
+
continue;
|
|
661
|
+
const provider = typeof entry.provider === "string" ? entry.provider.trim() : "";
|
|
662
|
+
const modelId = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
663
|
+
const id = provider && modelId ? `${provider}/${modelId}` : "";
|
|
664
|
+
if (!id || !PI_MODEL_ID_PATTERN.test(id) || id.length > 128 || seen.has(id))
|
|
665
|
+
continue;
|
|
666
|
+
seen.add(id);
|
|
667
|
+
const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : id;
|
|
668
|
+
const reasoningEfforts = effortLevelsFromIds(piThinkingLevelsForModel({
|
|
669
|
+
reasoning: entry.reasoning === true,
|
|
670
|
+
thinkingLevelMap: isRecord(entry.thinkingLevelMap) || entry.thinkingLevelMap === null
|
|
671
|
+
? entry.thinkingLevelMap
|
|
672
|
+
: undefined,
|
|
673
|
+
}));
|
|
674
|
+
discovered.push({
|
|
675
|
+
id,
|
|
676
|
+
label: name === id ? id : `${name} · ${id}`,
|
|
677
|
+
...(reasoningEfforts.length ? { reasoningEfforts } : {}),
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (!discovered.length)
|
|
681
|
+
return [];
|
|
682
|
+
const union = unionModelEfforts(discovered);
|
|
683
|
+
return [
|
|
684
|
+
{
|
|
685
|
+
id: "default",
|
|
686
|
+
label: "跟随 Pi 默认",
|
|
687
|
+
alias: true,
|
|
688
|
+
...(union.length ? { reasoningEfforts: union } : {}),
|
|
689
|
+
},
|
|
690
|
+
...discovered,
|
|
691
|
+
];
|
|
692
|
+
}
|
|
463
693
|
/**
|
|
464
694
|
* Parse `pi --list-models`.
|
|
465
695
|
*
|
|
466
696
|
* Pi prints a padEnd-aligned table whose selectable `--model` value is
|
|
467
|
-
* `provider/id` (for example `xai/grok-4.6`). The
|
|
468
|
-
*
|
|
697
|
+
* `provider/id` (for example `xai/grok-4.6`). The table only says thinking
|
|
698
|
+
* yes/no, so per-model levels come from `parsePiRpcModels` instead.
|
|
469
699
|
*/
|
|
470
700
|
export function parsePiModels(stdout) {
|
|
471
701
|
const discovered = [];
|
|
@@ -566,6 +796,7 @@ function catalogRevision(cache) {
|
|
|
566
796
|
grokModels: cache.grokModels,
|
|
567
797
|
qoderModels: cache.qoderModels,
|
|
568
798
|
piModels: cache.piModels,
|
|
799
|
+
thinkingEfforts: cache.thinkingEfforts,
|
|
569
800
|
claudeVersion: cache.claudeVersion,
|
|
570
801
|
opencodeVersion: cache.opencodeVersion,
|
|
571
802
|
});
|
|
@@ -637,6 +868,35 @@ function parsePersistedModelList(value) {
|
|
|
637
868
|
}
|
|
638
869
|
return result;
|
|
639
870
|
}
|
|
871
|
+
function parsePersistedThinkingEfforts(value) {
|
|
872
|
+
const fallback = cloneThinkingEfforts(FALLBACK_PROVIDER_THINKING);
|
|
873
|
+
if (!isRecord(value))
|
|
874
|
+
return fallback;
|
|
875
|
+
const providers = ["claude", "opencode", "grok", "qoder", "pi"];
|
|
876
|
+
for (const provider of providers) {
|
|
877
|
+
const levels = parsePersistedEffortLevels(value[provider]);
|
|
878
|
+
if (levels)
|
|
879
|
+
fallback[provider] = levels;
|
|
880
|
+
}
|
|
881
|
+
return fallback;
|
|
882
|
+
}
|
|
883
|
+
function parsePersistedEffortLevels(value) {
|
|
884
|
+
if (!Array.isArray(value))
|
|
885
|
+
return null;
|
|
886
|
+
const levels = [];
|
|
887
|
+
const seen = new Set();
|
|
888
|
+
for (const entry of value) {
|
|
889
|
+
if (!isRecord(entry))
|
|
890
|
+
return null;
|
|
891
|
+
const effort = safePersistedString(entry.effort, 32)?.toLowerCase() ?? "";
|
|
892
|
+
if (!EFFORT_ID_PATTERN.test(effort) || seen.has(effort))
|
|
893
|
+
return null;
|
|
894
|
+
seen.add(effort);
|
|
895
|
+
const description = safePersistedString(entry.description);
|
|
896
|
+
levels.push({ effort, ...(description ? { description } : {}) });
|
|
897
|
+
}
|
|
898
|
+
return levels.length ? levels : null;
|
|
899
|
+
}
|
|
640
900
|
function parsePersistedModelCatalog(value) {
|
|
641
901
|
if (!isRecord(value) || value.version !== MODEL_CATALOG_CACHE_VERSION || !isRecord(value.catalog))
|
|
642
902
|
return null;
|
|
@@ -664,6 +924,7 @@ function parsePersistedModelCatalog(value) {
|
|
|
664
924
|
grokModels,
|
|
665
925
|
qoderModels,
|
|
666
926
|
piModels,
|
|
927
|
+
thinkingEfforts: parsePersistedThinkingEfforts(catalog.thinkingEfforts),
|
|
667
928
|
claudeVersion,
|
|
668
929
|
opencodeVersion,
|
|
669
930
|
refreshedAt,
|
|
@@ -703,7 +964,7 @@ async function discoverModelCache(options, previous) {
|
|
|
703
964
|
const now = options.now?.() ?? new Date();
|
|
704
965
|
const env = resolveChildEnv(options);
|
|
705
966
|
const runner = options.commandRunner ?? defaultCommandRunner;
|
|
706
|
-
const [claudeVersionProbe, codexProbe, opencodeProbe, grokProbe, qoderProbe, piProbe, apiProbe] = await Promise.all([
|
|
967
|
+
const [claudeVersionProbe, codexProbe, opencodeProbe, grokProbe, qoderProbe, piProbe, apiProbe, claudeEffortProbe, grokEffortProbe, qoderEffortProbe, piEffortProbe,] = await Promise.all([
|
|
707
968
|
probeClaudeVersion(runner, env),
|
|
708
969
|
probeCodexModels(runner, env),
|
|
709
970
|
probeOpenCode(runner, env),
|
|
@@ -711,6 +972,10 @@ async function discoverModelCache(options, previous) {
|
|
|
711
972
|
probeQoderModels(runner, env),
|
|
712
973
|
probePiModels(runner, env),
|
|
713
974
|
listClaudeModelsFromApi(options, env),
|
|
975
|
+
probeEffortList(runner, env, "claude", ["--effort", "__wand_probe__", "--help"]),
|
|
976
|
+
probeEffortList(runner, env, "grok", ["--effort", "__wand_probe__", "-p", "x", "--output-format", "streaming-json", "--max-turns", "1"]),
|
|
977
|
+
probeEffortList(runner, env, "qodercli", ["-p", "x", "--reasoning-effort", "__wand_probe__"]),
|
|
978
|
+
probeEffortList(runner, env, "pi", ["--help"]),
|
|
714
979
|
]);
|
|
715
980
|
const claudeVersion = claudeVersionProbe.ok ? claudeVersionProbe.value : previous.claudeVersion;
|
|
716
981
|
const priorVerifications = loadClaudeVerifications(options.storage);
|
|
@@ -743,6 +1008,19 @@ async function discoverModelCache(options, previous) {
|
|
|
743
1008
|
grokModels: grokProbe.ok ? grokProbe.value : cloneModels(previous.grokModels),
|
|
744
1009
|
qoderModels: qoderProbe.ok ? qoderProbe.value : cloneModels(previous.qoderModels),
|
|
745
1010
|
piModels: piProbe.ok ? piProbe.value : cloneModels(previous.piModels),
|
|
1011
|
+
thinkingEfforts: {
|
|
1012
|
+
claude: claudeEffortProbe.ok ? claudeEffortProbe.value : cloneThinkingEfforts(previous.thinkingEfforts).claude,
|
|
1013
|
+
opencode: opencodeProbe.models.ok && unionModelEfforts(opencodeProbe.models.value).length
|
|
1014
|
+
? unionModelEfforts(opencodeProbe.models.value)
|
|
1015
|
+
: cloneThinkingEfforts(previous.thinkingEfforts).opencode,
|
|
1016
|
+
grok: grokEffortProbe.ok ? grokEffortProbe.value : cloneThinkingEfforts(previous.thinkingEfforts).grok,
|
|
1017
|
+
qoder: qoderEffortProbe.ok ? qoderEffortProbe.value : cloneThinkingEfforts(previous.thinkingEfforts).qoder,
|
|
1018
|
+
pi: piProbe.ok && unionModelEfforts(piProbe.value).length
|
|
1019
|
+
? unionModelEfforts(piProbe.value)
|
|
1020
|
+
: piEffortProbe.ok
|
|
1021
|
+
? piEffortProbe.value
|
|
1022
|
+
: cloneThinkingEfforts(previous.thinkingEfforts).pi,
|
|
1023
|
+
},
|
|
746
1024
|
claudeVersion,
|
|
747
1025
|
opencodeVersion: opencodeProbe.version.ok ? opencodeProbe.version.value : previous.opencodeVersion,
|
|
748
1026
|
refreshedAt: now.toISOString(),
|
|
@@ -763,6 +1041,7 @@ export class ModelCatalogService {
|
|
|
763
1041
|
hasPersistedSnapshot;
|
|
764
1042
|
refreshPromise = null;
|
|
765
1043
|
inFlightIncludesVerification = false;
|
|
1044
|
+
changeListeners = new Set();
|
|
766
1045
|
constructor(getOptions) {
|
|
767
1046
|
this.getOptions = getOptions;
|
|
768
1047
|
const initialOptions = getOptions();
|
|
@@ -774,6 +1053,11 @@ export class ModelCatalogService {
|
|
|
774
1053
|
snapshot() {
|
|
775
1054
|
return { ...cloneCache(this.cache), revision: this.revision };
|
|
776
1055
|
}
|
|
1056
|
+
/** 目录内容真正变化时通知。客户端靠它刷新下拉,不用自己再跑一遍 CLI。 */
|
|
1057
|
+
onChanged(listener) {
|
|
1058
|
+
this.changeListeners.add(listener);
|
|
1059
|
+
return () => { this.changeListeners.delete(listener); };
|
|
1060
|
+
}
|
|
777
1061
|
refresh(request = {}) {
|
|
778
1062
|
const verifyClaudeCandidates = request.verifyClaudeCandidates === true;
|
|
779
1063
|
if (this.refreshPromise) {
|
|
@@ -811,7 +1095,16 @@ export class ModelCatalogService {
|
|
|
811
1095
|
savePersistedModelCatalog(options.storage, this.cache, this.revision);
|
|
812
1096
|
this.hasPersistedSnapshot = Boolean(options.storage);
|
|
813
1097
|
}
|
|
814
|
-
|
|
1098
|
+
const result = { ...this.snapshot(), changed, checkedAt };
|
|
1099
|
+
if (changed) {
|
|
1100
|
+
for (const listener of this.changeListeners) {
|
|
1101
|
+
try {
|
|
1102
|
+
listener(result);
|
|
1103
|
+
}
|
|
1104
|
+
catch { /* 通知失败不影响目录本身 */ }
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return result;
|
|
815
1108
|
}
|
|
816
1109
|
}
|
|
817
1110
|
/**
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
27b881febe0177ec8c610a0e3b2884c425434adfc79ecc40e938fd8a1e375de5 wand-render
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.3
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e0e940747fbe5af0da033763fdbb786c063443d3207aee4b2934e3fe98139776 wand-structured-renderd
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.3
|
|
Binary file
|