pi-ui-extend 1.0.14 → 1.0.16
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/schemas/pi-tools-suite-schema.d.ts +1 -0
- package/dist/schemas/pi-tools-suite-schema.js +10 -0
- package/external/pi-tools-suite/README.md +12 -0
- package/external/pi-tools-suite/src/codex-reasoning-fix/index.ts +88 -8
- package/external/pi-tools-suite/src/config.ts +25 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +6 -0
- package/external/pi-tools-suite/src/todo/index.ts +55 -3
- package/package.json +1 -1
- package/schemas/pi-tools-suite.json +41 -0
|
@@ -12,6 +12,7 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
|
|
|
12
12
|
enabledModules: Type.TOptional<Type.TArray<Type.TString>>;
|
|
13
13
|
modules: Type.TOptional<Type.TRecord<"^.*$", Type.TBoolean>>;
|
|
14
14
|
todoThinking: Type.TOptional<Type.TBoolean>;
|
|
15
|
+
todoThinkingOverrides: Type.TOptional<Type.TRecord<"^.*$", Type.TUnion<[Type.TLiteral<"off">, Type.TLiteral<"minimal">, Type.TLiteral<"low">, Type.TLiteral<"medium">, Type.TLiteral<"high">, Type.TLiteral<"xhigh">, Type.TLiteral<"max">, Type.TNull]>>>;
|
|
15
16
|
lookupModel: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
|
|
16
17
|
terminalBell: Type.TOptional<Type.TObject<{
|
|
17
18
|
sound: Type.TOptional<Type.TBoolean>;
|
|
@@ -221,6 +221,16 @@ export const PiToolsSuiteConfigSchema = Type.Object({
|
|
|
221
221
|
enabledModules: Type.Optional(Type.Array(Type.String(), { description: "List of module names to explicitly enable, including modules that are disabled by default." })),
|
|
222
222
|
modules: Type.Optional(Type.Record(Type.String(), Type.Boolean(), { description: "Per-module enable/disable map. credential-firewall is disabled by default and can be enabled here." })),
|
|
223
223
|
todoThinking: Type.Optional(Type.Boolean({ description: "Enable per-todo thinking levels and automatic thinking switch/restore when tasks become in-progress/completed." })),
|
|
224
|
+
todoThinkingOverrides: Type.Optional(Type.Record(Type.String(), Type.Union([
|
|
225
|
+
Type.Literal("off"),
|
|
226
|
+
Type.Literal("minimal"),
|
|
227
|
+
Type.Literal("low"),
|
|
228
|
+
Type.Literal("medium"),
|
|
229
|
+
Type.Literal("high"),
|
|
230
|
+
Type.Literal("xhigh"),
|
|
231
|
+
Type.Literal("max"),
|
|
232
|
+
Type.Null(),
|
|
233
|
+
]), { description: "Force per-todo thinking for matching provider/model or bare-model keys. Keys support * and ? wildcards; null removes an inherited override." })),
|
|
224
234
|
lookupModel: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Vision-capable provider/model used by GLM's lookup tool; unset or null disables lookup." })),
|
|
225
235
|
terminalBell: Type.Optional(TerminalBellConfig),
|
|
226
236
|
dcp: Type.Optional(DcpConfig),
|
|
@@ -62,6 +62,18 @@ Saved prompt slash commands are stored under `promptCommands`. Use `/prompt-comm
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
Todo thinking can be enabled globally and forced to a fixed level for selected models. `todoThinkingOverrides` keys accept exact `provider/model` or bare-model names plus `*` and `?` wildcards. Full provider/model matches beat bare-model matches, exact matches beat wildcards, and the more specific wildcard wins. The override is applied at runtime to create/update and batch create/update mutations even when the model requests another level or omits `thinking`. Unsupported levels are normalized to the nearest level supported by the current model. Later config layers can remove an inherited entry with `null`.
|
|
66
|
+
|
|
67
|
+
```jsonc
|
|
68
|
+
{
|
|
69
|
+
"todoThinking": true,
|
|
70
|
+
"todoThinkingOverrides": {
|
|
71
|
+
"zai/glm-5.3": "max",
|
|
72
|
+
"cheap-provider/*": "high"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
65
77
|
DCP settings are stored only under `dcp` in the user shared config file `~/.config/pi/pi-tools-suite.jsonc`. Legacy standalone `dcp.jsonc`, `$PI_CONFIG_DIR`, and project-local `.pi/pi-tools-suite.jsonc` DCP settings are intentionally ignored by the ported headless DCP module.
|
|
66
78
|
|
|
67
79
|
```jsonc
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* WORKAROUNDS for Codex / OpenAI Responses payload compatibility:
|
|
3
|
+
* - non-message items (reasoning, function_call_output) must not carry the
|
|
4
|
+
* spurious `content` field rejected with HTTP 400;
|
|
5
|
+
* - the Codex backend rejects the legacy `prompt_cache_retention` field for
|
|
6
|
+
* all current models, while direct OpenAI GPT-5.6+ uses the newer prompt
|
|
7
|
+
* cache options shape.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* LAST `before_provider_request` handler registered by pi-tools-suite.
|
|
9
|
+
* The sanitizer must be the LAST `before_provider_request` handler registered
|
|
10
|
+
* by pi-tools-suite so another payload hook cannot undo these guards.
|
|
9
11
|
*
|
|
10
12
|
* In pi-ai >= 0.80.6 the final `before_provider_request` payload feeds both the
|
|
11
13
|
* WebSocket delta builder and the zstd-compressed SSE fallback body. Running the
|
|
@@ -27,6 +29,9 @@ type ProviderRequestContext = {
|
|
|
27
29
|
model?: unknown;
|
|
28
30
|
};
|
|
29
31
|
|
|
32
|
+
const OPENAI_CODEX_PROVIDER = "openai-codex";
|
|
33
|
+
const OPENAI_PROVIDER = "openai";
|
|
34
|
+
|
|
30
35
|
/**
|
|
31
36
|
* Strip spurious `content` from any object that carries an `input` or
|
|
32
37
|
* `messages` array. Shared core for payload sanitization.
|
|
@@ -75,12 +80,21 @@ export default function codexReasoningFix(pi: ExtensionAPI): void {
|
|
|
75
80
|
// src/index.ts deliberately registers this module last. A later payload
|
|
76
81
|
// modifier could otherwise reintroduce invalid content after sanitization,
|
|
77
82
|
// and transport encoding happens after this hook.
|
|
78
|
-
pi.on("before_provider_request", async (event: ProviderRequestEvent,
|
|
79
|
-
const result =
|
|
83
|
+
pi.on("before_provider_request", async (event: ProviderRequestEvent, ctx: ProviderRequestContext) => {
|
|
84
|
+
const result = sanitizeCodexProviderPayload(event.payload, ctx.model);
|
|
80
85
|
return result === event.payload ? undefined : result;
|
|
81
86
|
});
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Apply all final Codex payload compatibility guards. Returns the original
|
|
91
|
+
* reference when no guard changes the payload.
|
|
92
|
+
*/
|
|
93
|
+
export function sanitizeCodexProviderPayload(payload: unknown, model: unknown): unknown {
|
|
94
|
+
const contentSanitized = stripReasoningContentFromPayload(payload);
|
|
95
|
+
return stripUnsupportedPromptCacheRetention(contentSanitized, model);
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
/**
|
|
85
99
|
* Strip spurious `content` from non-message items in a full payload. Returns the same reference
|
|
86
100
|
* when nothing changed; exported for unit testing.
|
|
@@ -90,6 +104,72 @@ export function stripReasoningContentFromPayload(payload: unknown): unknown {
|
|
|
90
104
|
return result ? result.obj : payload;
|
|
91
105
|
}
|
|
92
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Remove legacy prompt-cache retention only where it is known to be rejected:
|
|
109
|
+
* every current Codex model, and direct OpenAI GPT-5.6 or newer. Match the
|
|
110
|
+
* selected model rather than a bare payload id so other providers keep the
|
|
111
|
+
* field untouched.
|
|
112
|
+
*/
|
|
113
|
+
export function stripUnsupportedPromptCacheRetention(payload: unknown, model: unknown): unknown {
|
|
114
|
+
if (!isRecord(payload) || !Object.prototype.hasOwnProperty.call(payload, "prompt_cache_retention")) {
|
|
115
|
+
return payload;
|
|
116
|
+
}
|
|
117
|
+
if (!rejectsLegacyPromptCacheRetention(model, payload.model)) return payload;
|
|
118
|
+
|
|
119
|
+
const { prompt_cache_retention: _drop, ...rest } = payload;
|
|
120
|
+
return rest;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function rejectsLegacyPromptCacheRetention(model: unknown, payloadModel: unknown): boolean {
|
|
124
|
+
const selected = modelIdentity(model);
|
|
125
|
+
if (selected.provider !== undefined) {
|
|
126
|
+
return isAffectedProviderModel(selected.provider, selected.id);
|
|
127
|
+
}
|
|
128
|
+
if (selected.id?.includes("/")) {
|
|
129
|
+
return isAffectedQualifiedModel(selected.id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return typeof payloadModel === "string" && isAffectedQualifiedModel(payloadModel.trim().toLowerCase());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isAffectedQualifiedModel(modelRef: string): boolean {
|
|
136
|
+
const slash = modelRef.indexOf("/");
|
|
137
|
+
if (slash <= 0 || slash === modelRef.length - 1) return false;
|
|
138
|
+
return isAffectedProviderModel(modelRef.slice(0, slash), modelRef.slice(slash + 1));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isAffectedProviderModel(provider: string, modelId: string | undefined): boolean {
|
|
142
|
+
if (provider === OPENAI_CODEX_PROVIDER) return true;
|
|
143
|
+
return provider === OPENAI_PROVIDER && modelId !== undefined && isGpt56OrNewer(modelId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isGpt56OrNewer(modelId: string): boolean {
|
|
147
|
+
const match = /^gpt-(\d+)(?:\.(\d+))?(?:-|$)/u.exec(modelId);
|
|
148
|
+
if (!match) return false;
|
|
149
|
+
const major = Number(match[1]);
|
|
150
|
+
const minor = Number(match[2] ?? 0);
|
|
151
|
+
return major > 5 || (major === 5 && minor >= 6);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function modelIdentity(model: unknown): { provider?: string; id?: string } {
|
|
155
|
+
if (typeof model === "string") return { id: model.trim().toLowerCase() };
|
|
156
|
+
if (!isRecord(model)) return {};
|
|
157
|
+
|
|
158
|
+
const provider = firstString(model.provider, model.providerId, model.providerID);
|
|
159
|
+
const id = firstString(model.id, model.modelId, model.modelID, model.model);
|
|
160
|
+
return {
|
|
161
|
+
provider: provider?.toLowerCase(),
|
|
162
|
+
id: id?.toLowerCase(),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function firstString(...values: unknown[]): string | undefined {
|
|
167
|
+
for (const value of values) {
|
|
168
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
93
173
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
94
174
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
95
175
|
}
|
|
@@ -9,6 +9,7 @@ export interface PiToolsSuiteConfig {
|
|
|
9
9
|
enabled: boolean;
|
|
10
10
|
disabledModules: string[];
|
|
11
11
|
todoThinking: boolean;
|
|
12
|
+
todoThinkingOverrides: Record<string, TodoThinkingLevel>;
|
|
12
13
|
/** Vision-capable model used by the coding-discipline lookup tool; unset disables lookup. */
|
|
13
14
|
lookupModel?: string;
|
|
14
15
|
/**
|
|
@@ -25,6 +26,7 @@ type MutableConfig = {
|
|
|
25
26
|
enabled: boolean;
|
|
26
27
|
disabledModules: Set<string>;
|
|
27
28
|
todoThinking: boolean;
|
|
29
|
+
todoThinkingOverrides: Map<string, TodoThinkingLevel>;
|
|
28
30
|
lookupModel: string | undefined;
|
|
29
31
|
codingDisciplineStrictness: CodingDisciplineStrictness;
|
|
30
32
|
};
|
|
@@ -32,6 +34,8 @@ type MutableConfig = {
|
|
|
32
34
|
export const CODING_DISCIPLINE_STRICTNESS_VALUES = ["strict", "lenient"] as const;
|
|
33
35
|
export type CodingDisciplineStrictness = (typeof CODING_DISCIPLINE_STRICTNESS_VALUES)[number];
|
|
34
36
|
export const DEFAULT_CODING_DISCIPLINE_STRICTNESS: CodingDisciplineStrictness = "lenient";
|
|
37
|
+
const TODO_THINKING_OVERRIDE_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
38
|
+
export type TodoThinkingLevel = (typeof TODO_THINKING_OVERRIDE_LEVELS)[number];
|
|
35
39
|
|
|
36
40
|
type Env = Record<string, string | undefined>;
|
|
37
41
|
|
|
@@ -42,6 +46,7 @@ const DISABLED_LIST_KEYS = ["disabledModules", "disabledExtensions"];
|
|
|
42
46
|
const ENABLED_LIST_KEYS = ["enabledModules", "enabledExtensions"];
|
|
43
47
|
const MODULE_MAP_KEYS = ["modules", "extensions"];
|
|
44
48
|
const DEFAULT_DISABLED_MODULES = new Set<string>(["credential-firewall"]);
|
|
49
|
+
const DEFAULT_TODO_THINKING_OVERRIDES = new Map<string, TodoThinkingLevel>([["zai/glm-5.3", "max"]]);
|
|
45
50
|
|
|
46
51
|
export function getPiToolsSuiteUserConfigPath(homeDir = homedir()): string {
|
|
47
52
|
return join(homeDir, ".config", "pi", "pi-tools-suite.jsonc");
|
|
@@ -79,6 +84,23 @@ function normalizeCodingDisciplineStrictness(raw: unknown): CodingDisciplineStri
|
|
|
79
84
|
return raw === "strict" ? "strict" : "lenient";
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
function isTodoThinkingLevel(raw: unknown): raw is TodoThinkingLevel {
|
|
88
|
+
return TODO_THINKING_OVERRIDE_LEVELS.includes(raw as TodoThinkingLevel);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function mergeTodoThinkingOverrides(config: MutableConfig, raw: unknown): void {
|
|
92
|
+
if (!isRecord(raw)) return;
|
|
93
|
+
for (const [rawPattern, value] of Object.entries(raw)) {
|
|
94
|
+
const pattern = rawPattern.trim().toLowerCase();
|
|
95
|
+
if (!pattern) continue;
|
|
96
|
+
if (value === null) {
|
|
97
|
+
config.todoThinkingOverrides.delete(pattern);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (isTodoThinkingLevel(value)) config.todoThinkingOverrides.set(pattern, value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
function boolFromEnv(value: string | undefined): boolean | undefined {
|
|
83
105
|
if (value === undefined) return undefined;
|
|
84
106
|
const normalized = value.trim().toLowerCase();
|
|
@@ -137,6 +159,7 @@ function removeDisabled(config: MutableConfig, value: unknown, knownModules: Rea
|
|
|
137
159
|
function mergeConfigLayer(config: MutableConfig, raw: Record<string, unknown>, knownModules: ReadonlySet<string>): MutableConfig {
|
|
138
160
|
if (typeof raw.enabled === "boolean") config.enabled = raw.enabled;
|
|
139
161
|
if (typeof raw.todoThinking === "boolean") config.todoThinking = raw.todoThinking;
|
|
162
|
+
mergeTodoThinkingOverrides(config, raw.todoThinkingOverrides);
|
|
140
163
|
if (Object.prototype.hasOwnProperty.call(raw, "lookupModel")) config.lookupModel = normalizeLookupModel(raw.lookupModel);
|
|
141
164
|
if (Object.prototype.hasOwnProperty.call(raw, "codingDisciplineStrictness")) {
|
|
142
165
|
config.codingDisciplineStrictness = normalizeCodingDisciplineStrictness(raw.codingDisciplineStrictness);
|
|
@@ -197,6 +220,7 @@ export function loadPiToolsSuiteConfig(moduleNames: readonly string[], options:
|
|
|
197
220
|
enabled: true,
|
|
198
221
|
disabledModules: new Set([...DEFAULT_DISABLED_MODULES].filter((name) => knownModules.has(name))),
|
|
199
222
|
todoThinking: false,
|
|
223
|
+
todoThinkingOverrides: new Map(DEFAULT_TODO_THINKING_OVERRIDES),
|
|
200
224
|
lookupModel: undefined,
|
|
201
225
|
codingDisciplineStrictness: DEFAULT_CODING_DISCIPLINE_STRICTNESS,
|
|
202
226
|
};
|
|
@@ -217,6 +241,7 @@ export function loadPiToolsSuiteConfig(moduleNames: readonly string[], options:
|
|
|
217
241
|
enabled: config.enabled,
|
|
218
242
|
disabledModules: [...config.disabledModules].sort(),
|
|
219
243
|
todoThinking: config.todoThinking,
|
|
244
|
+
todoThinkingOverrides: Object.fromEntries(config.todoThinkingOverrides),
|
|
220
245
|
...(config.lookupModel ? { lookupModel: config.lookupModel } : {}),
|
|
221
246
|
codingDisciplineStrictness: config.codingDisciplineStrictness,
|
|
222
247
|
};
|
|
@@ -15,6 +15,12 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
|
|
|
15
15
|
// When true, todo items may carry a per-task thinking level and the todo
|
|
16
16
|
// module will switch/restore Pi's thinking level as in-progress tasks change.
|
|
17
17
|
"todoThinking": true,
|
|
18
|
+
// Force every todo mutation made under matching models to the configured
|
|
19
|
+
// thinking level. Supports provider/model or bare-model keys with * / ? globs.
|
|
20
|
+
// Set an inherited key to null in a later config layer to remove it.
|
|
21
|
+
"todoThinkingOverrides": {
|
|
22
|
+
"zai/glm-5.3": "max"
|
|
23
|
+
},
|
|
18
24
|
// Vision-capable model used by the coding-discipline lookup tool for blind-model
|
|
19
25
|
// screenshot/image questions. Remove or set to null to disable lookup.
|
|
20
26
|
"lookupModel": "openai-codex/gpt-5.4-mini",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { loadPiToolsSuiteConfig } from "../config.js";
|
|
2
|
+
import { loadPiToolsSuiteConfig, type TodoThinkingLevel as ConfigTodoThinkingLevel } from "../config.js";
|
|
3
3
|
import { isAgentBusyRaceError } from "../context-usage.js";
|
|
4
4
|
import { autoClearCompletedTodos } from "./state/auto-clear.js";
|
|
5
5
|
import { loadPersistedPlan, syncPersistedPlan } from "./state/persistence.js";
|
|
@@ -31,6 +31,7 @@ function isStaleExtensionContextError(error: unknown): boolean {
|
|
|
31
31
|
|
|
32
32
|
type ModelLike = {
|
|
33
33
|
provider?: string;
|
|
34
|
+
providerId?: string;
|
|
34
35
|
id?: string;
|
|
35
36
|
modelId?: string;
|
|
36
37
|
reasoning?: boolean;
|
|
@@ -38,6 +39,51 @@ type ModelLike = {
|
|
|
38
39
|
compat?: { thinkingFormat?: unknown };
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
function escapeRegExp(text: string): string {
|
|
43
|
+
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function modelPatternMatches(pattern: string, candidate: string): boolean {
|
|
47
|
+
let source = "^";
|
|
48
|
+
for (const char of pattern) {
|
|
49
|
+
if (char === "*") source += ".*";
|
|
50
|
+
else if (char === "?") source += ".";
|
|
51
|
+
else source += escapeRegExp(char);
|
|
52
|
+
}
|
|
53
|
+
return new RegExp(`${source}$`, "i").test(candidate);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function modelKeys(model: unknown): { bare?: string; full?: string } {
|
|
57
|
+
const candidate = model as ModelLike | undefined;
|
|
58
|
+
const provider = candidate?.provider ?? candidate?.providerId;
|
|
59
|
+
const rawId = candidate?.modelId ?? candidate?.id;
|
|
60
|
+
if (!rawId) return {};
|
|
61
|
+
if (rawId.includes("/")) {
|
|
62
|
+
const slash = rawId.indexOf("/");
|
|
63
|
+
return { bare: rawId.slice(slash + 1), full: rawId };
|
|
64
|
+
}
|
|
65
|
+
return { bare: rawId, ...(provider ? { full: `${provider}/${rawId}` } : {}) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveTodoThinkingOverride(
|
|
69
|
+
model: unknown,
|
|
70
|
+
overrides: Record<string, ConfigTodoThinkingLevel>,
|
|
71
|
+
): TodoThinkingLevel | undefined {
|
|
72
|
+
const keys = modelKeys(model);
|
|
73
|
+
let best: { level: TodoThinkingLevel; score: number } | undefined;
|
|
74
|
+
for (const [rawPattern, level] of Object.entries(overrides)) {
|
|
75
|
+
const pattern = rawPattern.trim();
|
|
76
|
+
const isFull = pattern.includes("/");
|
|
77
|
+
const candidate = isFull ? keys.full : keys.bare;
|
|
78
|
+
if (!candidate || !modelPatternMatches(pattern, candidate)) continue;
|
|
79
|
+
const exact = !pattern.includes("*") && !pattern.includes("?");
|
|
80
|
+
const literalLength = pattern.replace(/[?*]/g, "").length;
|
|
81
|
+
const score = (isFull ? 30_000 : 10_000) + (exact ? 10_000 : 0) + literalLength;
|
|
82
|
+
if (!best || score >= best.score) best = { level, score };
|
|
83
|
+
}
|
|
84
|
+
return best?.level;
|
|
85
|
+
}
|
|
86
|
+
|
|
41
87
|
function isTodoThinkingLevel(value: unknown): value is TodoThinkingLevel {
|
|
42
88
|
return TODO_THINKING_LEVEL_VALUES.includes(value as TodoThinkingLevel);
|
|
43
89
|
}
|
|
@@ -167,7 +213,9 @@ function emitPersistedPlanPrompt(pi: ExtensionAPI, ctx: ExtensionContext, prompt
|
|
|
167
213
|
|
|
168
214
|
export default function (pi: ExtensionAPI) {
|
|
169
215
|
let currentModel: unknown;
|
|
170
|
-
const
|
|
216
|
+
const todoConfig = loadPiToolsSuiteConfig(["todo"]);
|
|
217
|
+
const todoThinkingEnabled = todoConfig.todoThinking;
|
|
218
|
+
const todoThinkingOverrides = todoConfig.todoThinkingOverrides;
|
|
171
219
|
const rememberedThinkingByTaskId = new Map<number, TodoThinkingLevel>();
|
|
172
220
|
let lastNudgedSignature: string | undefined;
|
|
173
221
|
let nudgeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -237,7 +285,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
237
285
|
|
|
238
286
|
function prepareTodoThinkingMutation(state: ReturnType<typeof getState>, params: TaskMutationParams): TaskMutationParams {
|
|
239
287
|
let nextParams = params;
|
|
240
|
-
|
|
288
|
+
const configuredOverride = resolveTodoThinkingOverride(currentModel, todoThinkingOverrides);
|
|
289
|
+
if (configuredOverride !== undefined) {
|
|
290
|
+
const forced = normalizeTodoThinkingLevelForModel(currentModel, configuredOverride);
|
|
291
|
+
nextParams = { ...nextParams, thinking: forced };
|
|
292
|
+
} else if (params.thinking !== undefined) {
|
|
241
293
|
const normalized = normalizeTodoThinkingLevelForModel(currentModel, params.thinking);
|
|
242
294
|
if (normalized !== params.thinking) nextParams = { ...nextParams, thinking: normalized };
|
|
243
295
|
}
|
package/package.json
CHANGED
|
@@ -36,6 +36,47 @@
|
|
|
36
36
|
"type": "boolean",
|
|
37
37
|
"description": "Enable per-todo thinking levels and automatic thinking switch/restore when tasks become in-progress/completed."
|
|
38
38
|
},
|
|
39
|
+
"todoThinkingOverrides": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"patternProperties": {
|
|
42
|
+
"^.*$": {
|
|
43
|
+
"anyOf": [
|
|
44
|
+
{
|
|
45
|
+
"type": "string",
|
|
46
|
+
"const": "off"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "string",
|
|
50
|
+
"const": "minimal"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"type": "string",
|
|
54
|
+
"const": "low"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "string",
|
|
58
|
+
"const": "medium"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "string",
|
|
62
|
+
"const": "high"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "string",
|
|
66
|
+
"const": "xhigh"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "string",
|
|
70
|
+
"const": "max"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type": "null"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"description": "Force per-todo thinking for matching provider/model or bare-model keys. Keys support * and ? wildcards; null removes an inherited override."
|
|
79
|
+
},
|
|
39
80
|
"lookupModel": {
|
|
40
81
|
"anyOf": [
|
|
41
82
|
{
|