pi-adaptive-thinking 0.1.0 → 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright 2026 Ian Pascoe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,7 +6,9 @@ Bring adaptive reasoning-effort control to Pi agents.
6
6
 
7
7
  </div>
8
8
 
9
- `pi-adaptive-thinking` is a Pi extension that lets the agent change Pi's thinking level through a tool named `set_reasoning_effort`.
9
+ `pi-adaptive-thinking` is a Pi extension that lets the agent change Pi's thinking level through a tool named `set_thinking_level`.
10
+
11
+ Requires Node `>=22.19.0` and Pi `>=0.84.1`.
10
12
 
11
13
  It mirrors the user-facing behavior of `opencode-adaptive-thinking` while using Pi-native APIs: `pi.getThinkingLevel()`, `pi.setThinkingLevel()`, and `thinking_level_select`.
12
14
 
@@ -20,8 +22,7 @@ For local development:
20
22
 
21
23
  ```bash
22
24
  pnpm install
23
- pnpm build
24
- pi -e ./dist/index.js
25
+ pi -e ./src/index.ts
25
26
  ```
26
27
 
27
28
  ## Behavior
@@ -37,7 +38,7 @@ Temporary changes:
37
38
  { "level": "high", "persist": false }
38
39
  ```
39
40
 
40
- This changes reasoning effort for the current agent turn and restores the prior/baseline level when the turn ends.
41
+ This changes thinking level for the current agent turn and restores the prior/baseline level when the turn ends.
41
42
 
42
43
  Persistent changes:
43
44
 
@@ -61,9 +62,9 @@ Project configuration takes precedence over global configuration.
61
62
  {
62
63
  "enabled": true,
63
64
  "quiet": false,
64
- "toolName": "set_reasoning_effort",
65
- "toolDescription": "Set your reasoning effort",
66
- "systemPrompt": "You MUST manage reasoning effort actively. Lower it before trivial or routine turns; raise it for ambiguity, debugging, risky changes, or multi-step synthesis. Reassess at turn start, after meaningful new evidence, and when the task shifts. NEVER leave the current level unchanged by inertia, and NEVER reply to a trivial turn before considering a downshift."
65
+ "toolName": "set_thinking_level",
66
+ "toolDescription": "Set your thinking level",
67
+ "systemPrompt": "You MUST manage thinking level actively. Lower it before trivial or routine turns; raise it for ambiguity, debugging, risky changes, or multi-step synthesis. Reassess at turn start, after meaningful new evidence, and when the task shifts. NEVER leave the current level unchanged by inertia, and NEVER reply to a trivial turn before considering a downshift."
67
68
  }
68
69
  ```
69
70
 
@@ -75,7 +76,6 @@ pnpm format:check
75
76
  pnpm lint
76
77
  pnpm typecheck
77
78
  pnpm test
78
- pnpm build
79
79
  ```
80
80
 
81
81
  Run all checks:
package/package.json CHANGED
@@ -1,79 +1,57 @@
1
1
  {
2
2
  "name": "pi-adaptive-thinking",
3
- "version": "0.1.0",
4
- "description": "Pi Adaptive Thinking extension",
3
+ "version": "0.1.2",
4
+ "private": false,
5
+ "description": "Pi extension for adaptive reasoning-effort control",
5
6
  "keywords": [
6
7
  "adaptive-thinking",
7
8
  "pi",
9
+ "pi-extension",
8
10
  "pi-package",
9
- "reasoning",
10
11
  "thinking"
11
12
  ],
12
- "homepage": "https://github.com/ian-pascoe/pi-adaptive-thinking#readme",
13
+ "homepage": "https://github.com/ian-pascoe/pi-extensions#readme",
13
14
  "bugs": {
14
- "url": "https://github.com/ian-pascoe/pi-adaptive-thinking/issues"
15
+ "url": "https://github.com/ian-pascoe/pi-extensions/issues"
15
16
  },
16
17
  "license": "MIT",
17
- "author": "Ian Pascoe",
18
+ "author": "Ian Pascoe <ian.g.pascoe@gmail.com>",
18
19
  "repository": {
19
20
  "type": "git",
20
- "url": "git+https://github.com/ian-pascoe/pi-adaptive-thinking.git"
21
+ "url": "git+https://github.com/ian-pascoe/pi-extensions.git",
22
+ "directory": "packages/pi-adaptive-thinking"
21
23
  },
22
24
  "files": [
23
- "dist",
24
- "README.md"
25
+ "src",
26
+ "README.md",
27
+ "LICENSE"
25
28
  ],
26
29
  "type": "module",
27
- "main": "dist/index.js",
28
- "types": "dist/index.d.ts",
29
- "exports": {
30
- ".": {
31
- "types": "./dist/index.d.ts",
32
- "import": "./dist/index.js",
33
- "default": "./dist/index.js"
34
- }
35
- },
36
30
  "publishConfig": {
37
31
  "access": "public",
38
32
  "provenance": true
39
33
  },
34
+ "dependencies": {
35
+ "proper-lockfile": "^4.1.2"
36
+ },
40
37
  "devDependencies": {
41
- "@changesets/changelog-github": "^0.7.0",
42
- "@changesets/cli": "^2.31.0",
43
- "@types/node": "^25.9.1",
44
- "@typescript/native-preview": "7.0.0-dev.20260526.1",
45
- "husky": "^9.1.7",
46
- "oxfmt": "^0.52.0",
47
- "oxlint": "^1.67.0",
48
- "rolldown": "1.0.2",
49
- "typescript": "^6.0.3",
50
- "vitest": "^4.1.7"
38
+ "@types/proper-lockfile": "4.1.4"
51
39
  },
52
40
  "peerDependencies": {
53
41
  "@earendil-works/pi-ai": "*",
54
42
  "@earendil-works/pi-coding-agent": "*",
55
43
  "typebox": "*"
56
44
  },
45
+ "engines": {
46
+ "node": ">=22.19.0"
47
+ },
57
48
  "pi": {
58
49
  "extensions": [
59
- "./dist/index.js"
50
+ "./src/index.ts"
60
51
  ]
61
52
  },
62
53
  "scripts": {
63
- "build": "pnpm run clean && pnpm run build:js && pnpm run build:types",
64
- "build:js": "rolldown -c",
65
- "build:types": "tsgo --project tsconfig.build.json --emitDeclarationOnly",
66
- "build:watch": "rolldown -c -w",
67
- "changeset": "changeset",
68
- "clean": "rm -rf dist",
69
- "format": "oxfmt",
70
- "format:check": "oxfmt --check",
71
- "lint": "oxlint",
72
- "lint:fix": "oxlint --fix",
73
- "release": "pnpm run build && changeset publish",
74
- "test": "vitest run",
75
- "typecheck": "tsgo --noEmit",
76
- "verify": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build",
77
- "version-packages": "changeset version"
54
+ "test": "vitest run --config ../../vitest.config.ts --root .",
55
+ "typecheck": "tsc --noEmit -p tsconfig.json"
78
56
  }
79
57
  }
@@ -0,0 +1,55 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { parseConfig, type AdaptiveThinkingConfig } from "./config.js";
5
+
6
+ export type { AdaptiveThinkingConfig } from "./config.js";
7
+
8
+ export type LoadConfigOptions = {
9
+ cwd: string;
10
+ homeDir?: string;
11
+ };
12
+
13
+ export type LoadConfigResult =
14
+ | { success: true; config: AdaptiveThinkingConfig; source?: string }
15
+ | { success: false; source: string; error: Error };
16
+
17
+ const hasCode = (error: unknown, code: string) => {
18
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
19
+ };
20
+
21
+ const errorMessage = (error: unknown) => (error instanceof Error ? error.message : String(error));
22
+
23
+ const invalidConfig = (source: string, error: unknown): LoadConfigResult => ({
24
+ success: false,
25
+ source,
26
+ error: new Error(`Invalid Adaptive Thinking configuration in ${source}: ${errorMessage(error)}`),
27
+ });
28
+
29
+ export const loadConfig = async ({
30
+ cwd,
31
+ homeDir = homedir(),
32
+ }: LoadConfigOptions): Promise<LoadConfigResult> => {
33
+ const candidates = [
34
+ join(cwd, ".pi", "adaptive-thinking.json"),
35
+ join(homeDir, ".pi", "agent", "adaptive-thinking.json"),
36
+ ];
37
+
38
+ for (const source of candidates) {
39
+ let raw: string;
40
+ try {
41
+ raw = await readFile(source, "utf8");
42
+ } catch (error) {
43
+ if (hasCode(error, "ENOENT")) continue;
44
+ return invalidConfig(source, error);
45
+ }
46
+
47
+ try {
48
+ return { success: true, source, config: parseConfig(JSON.parse(raw)) };
49
+ } catch (error) {
50
+ return invalidConfig(source, error);
51
+ }
52
+ }
53
+
54
+ return { success: true, config: parseConfig(undefined) };
55
+ };
package/src/config.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { Type, type Static } from "typebox";
2
+ import { Parse } from "typebox/value";
3
+
4
+ export const defaultSystemPrompt =
5
+ "You MUST manage thinking level actively. " +
6
+ "Lower it before trivial or routine turns; raise it for ambiguity, debugging, risky changes, or multi-step synthesis. " +
7
+ "Reassess at turn start, after meaningful new evidence, and when the task shifts. " +
8
+ "NEVER leave the current level unchanged by inertia, and NEVER reply to a trivial turn before considering a downshift.";
9
+
10
+ export const configDefaults = {
11
+ enabled: true,
12
+ quiet: false,
13
+ toolName: "set_thinking_level",
14
+ toolDescription: "Set your thinking level",
15
+ systemPrompt: defaultSystemPrompt,
16
+ };
17
+
18
+ export const ConfigSchema = Type.Object(
19
+ {
20
+ enabled: Type.Boolean(),
21
+ quiet: Type.Boolean(),
22
+ toolName: Type.String({ minLength: 1 }),
23
+ toolDescription: Type.String({ minLength: 1 }),
24
+ systemPrompt: Type.String({ minLength: 1 }),
25
+ },
26
+ { additionalProperties: false },
27
+ );
28
+
29
+ export type AdaptiveThinkingConfig = Static<typeof ConfigSchema>;
30
+
31
+ export const parseConfig = (input: unknown): AdaptiveThinkingConfig => {
32
+ const merged = input === undefined ? configDefaults : { ...configDefaults, ...input };
33
+ return Parse(ConfigSchema, merged);
34
+ };
package/src/index.ts ADDED
@@ -0,0 +1,333 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import lockfile from "proper-lockfile";
5
+ import type {
6
+ AgentToolResult,
7
+ BeforeAgentStartEvent,
8
+ BeforeAgentStartEventResult,
9
+ ExtensionAPI,
10
+ ExtensionContext,
11
+ } from "@earendil-works/pi-coding-agent";
12
+ import { type Static, Type } from "typebox";
13
+ import { loadConfig, type AdaptiveThinkingConfig } from "./config-loader.js";
14
+ import {
15
+ isThinkingLevel,
16
+ resolveSupportedThinkingLevels,
17
+ type PiThinkingLevel,
18
+ } from "./thinking-levels.js";
19
+
20
+ type NotifyType = "info" | "warning" | "error";
21
+
22
+ type RuntimeState = {
23
+ config: AdaptiveThinkingConfig;
24
+ currentLevel?: PiThinkingLevel;
25
+ persistedLevel?: PiThinkingLevel;
26
+ temporaryResetLevel?: PiThinkingLevel;
27
+ lastToolCallWasReasoningTool?: boolean;
28
+ reasoningToolCallBackToBackById: Map<string, boolean>;
29
+ };
30
+
31
+ const ToolParameters = Type.Object(
32
+ {
33
+ level: Type.String({
34
+ minLength: 1,
35
+ description:
36
+ "The Pi thinking level to apply. Higher levels may improve hard-task quality but may take more time and resources.",
37
+ }),
38
+ persist: Type.Optional(
39
+ Type.Boolean({
40
+ default: false,
41
+ description:
42
+ "Whether to persist the setting for this session; otherwise it applies only for the current turn.",
43
+ }),
44
+ ),
45
+ },
46
+ { additionalProperties: false },
47
+ );
48
+
49
+ type ToolParameters = Static<typeof ToolParameters>;
50
+
51
+ const textResult = (text: string): AgentToolResult<undefined> => ({
52
+ content: [{ type: "text", text }],
53
+ details: undefined,
54
+ });
55
+
56
+ const errorMessage = (error: unknown) => (error instanceof Error ? error.message : String(error));
57
+
58
+ const agentDir = () => process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
59
+
60
+ const globalSettingsPath = () => join(agentDir(), "settings.json");
61
+
62
+ const sleepSync = (milliseconds: number) => {
63
+ const end = Date.now() + milliseconds;
64
+ while (Date.now() < end) {
65
+ // Synchronous ExtensionAPI methods require a synchronous retry loop.
66
+ }
67
+ };
68
+
69
+ const acquireSettingsLock = (lockPath: string) => {
70
+ const maxAttempts = 100;
71
+ const delayMs = 20;
72
+
73
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
74
+ try {
75
+ return lockfile.lockSync(lockPath, { realpath: false });
76
+ } catch (error) {
77
+ const code =
78
+ typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
79
+ if (code !== "ELOCKED" || attempt === maxAttempts) throw error;
80
+ sleepSync(delayMs);
81
+ }
82
+ }
83
+
84
+ throw new Error(`Failed to acquire settings lock: ${lockPath}`);
85
+ };
86
+
87
+ const withSettingsLock = <T>(settingsPath: string, fn: () => T): T => {
88
+ mkdirSync(join(settingsPath, ".."), { recursive: true });
89
+ const lockPath = `${settingsPath}.adaptive-thinking`;
90
+ if (!existsSync(lockPath)) writeFileSync(lockPath, "");
91
+
92
+ const release = acquireSettingsLock(lockPath);
93
+
94
+ try {
95
+ return fn();
96
+ } finally {
97
+ release();
98
+ }
99
+ };
100
+
101
+ const readDefaultThinkingLevel = (settingsPath: string): PiThinkingLevel | undefined => {
102
+ if (!existsSync(settingsPath)) return undefined;
103
+
104
+ try {
105
+ const parsed = JSON.parse(readFileSync(settingsPath, "utf-8")) as {
106
+ defaultThinkingLevel?: unknown;
107
+ };
108
+ return typeof parsed.defaultThinkingLevel === "string" &&
109
+ isThinkingLevel(parsed.defaultThinkingLevel)
110
+ ? parsed.defaultThinkingLevel
111
+ : undefined;
112
+ } catch {
113
+ return undefined;
114
+ }
115
+ };
116
+
117
+ const restoreDefaultThinkingLevel = (
118
+ settingsPath: string,
119
+ previousDefaultThinkingLevel: PiThinkingLevel | undefined,
120
+ ) => {
121
+ if (!existsSync(settingsPath)) return;
122
+
123
+ try {
124
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8")) as Record<string, unknown>;
125
+ if (previousDefaultThinkingLevel === undefined) {
126
+ delete settings.defaultThinkingLevel;
127
+ } else {
128
+ settings.defaultThinkingLevel = previousDefaultThinkingLevel;
129
+ }
130
+ writeFileSync(settingsPath, JSON.stringify(settings, undefined, 2) + "\n");
131
+ } catch {
132
+ return;
133
+ }
134
+ };
135
+
136
+ const withSessionOnlyThinkingLevelChange = (changeThinkingLevel: () => void) => {
137
+ const settingsPath = globalSettingsPath();
138
+
139
+ return withSettingsLock(settingsPath, () => {
140
+ const previousDefaultThinkingLevel = readDefaultThinkingLevel(settingsPath);
141
+
142
+ changeThinkingLevel();
143
+
144
+ restoreDefaultThinkingLevel(settingsPath, previousDefaultThinkingLevel);
145
+ });
146
+ };
147
+
148
+ const notify = (
149
+ ctx: ExtensionContext,
150
+ type: NotifyType,
151
+ message: string,
152
+ config?: Pick<AdaptiveThinkingConfig, "quiet">,
153
+ ) => {
154
+ if (config?.quiet) return;
155
+ if (!ctx.hasUI) return;
156
+ ctx.ui.notify(message, type);
157
+ };
158
+
159
+ const appendSystemPromptBlock = (systemPrompt: string, block: string) => {
160
+ const trimmedBlock = block.trim();
161
+ if (!trimmedBlock) return systemPrompt;
162
+ if (!systemPrompt.trim()) return trimmedBlock;
163
+ return `${systemPrompt.trimEnd()}\n\n${trimmedBlock}`;
164
+ };
165
+
166
+ const formatGuidance = (
167
+ config: AdaptiveThinkingConfig,
168
+ currentLevel: string | undefined,
169
+ validLevels: string[],
170
+ ) => {
171
+ return (
172
+ config.systemPrompt.trim() +
173
+ " " +
174
+ (currentLevel ? `Current thinking level: ${currentLevel}. ` : "") +
175
+ `Valid thinking levels for this session: ${validLevels.join(", ")}. ` +
176
+ `To change the thinking level, use the \`${config.toolName}\` tool with one of the valid levels. ` +
177
+ "Only call it when the task complexity justifies changing levels. " +
178
+ `Do not call ${config.toolName} if the current thinking level already matches the target level. ` +
179
+ `Do not call ${config.toolName} twice in a row; reassess only after new evidence from other tool calls or user input.`
180
+ );
181
+ };
182
+
183
+ export default function adaptiveThinking(pi: ExtensionAPI) {
184
+ let runtime: RuntimeState | undefined;
185
+ let runtimeHandlersRegistered = false;
186
+
187
+ const registerRuntimeHandlers = () => {
188
+ if (runtimeHandlersRegistered) return;
189
+ runtimeHandlersRegistered = true;
190
+
191
+ pi.on("thinking_level_select", async (event) => {
192
+ if (!runtime) return;
193
+ if (isThinkingLevel(event.level)) runtime.currentLevel = event.level;
194
+ });
195
+
196
+ pi.on("tool_call", async (event) => {
197
+ const state = runtime;
198
+ if (!state) return;
199
+
200
+ if (event.toolName === state.config.toolName) {
201
+ state.reasoningToolCallBackToBackById.set(
202
+ event.toolCallId,
203
+ state.lastToolCallWasReasoningTool ?? false,
204
+ );
205
+ state.lastToolCallWasReasoningTool = true;
206
+ } else {
207
+ state.lastToolCallWasReasoningTool = false;
208
+ }
209
+ });
210
+
211
+ pi.on("before_agent_start", async (event, ctx) => beforeAgentStart(event, ctx));
212
+
213
+ pi.on("agent_end", async (_event, ctx) => {
214
+ await resetTemporaryLevel(ctx);
215
+ if (!runtime) return;
216
+ runtime.lastToolCallWasReasoningTool = false;
217
+ runtime.reasoningToolCallBackToBackById.clear();
218
+ });
219
+ };
220
+
221
+ const beforeAgentStart = async (
222
+ event: BeforeAgentStartEvent,
223
+ ctx: ExtensionContext,
224
+ ): Promise<BeforeAgentStartEventResult> => {
225
+ const state = runtime;
226
+ if (!state) return { systemPrompt: event.systemPrompt };
227
+
228
+ state.lastToolCallWasReasoningTool = false;
229
+ state.reasoningToolCallBackToBackById.clear();
230
+
231
+ const currentLevel = state.currentLevel ?? pi.getThinkingLevel();
232
+ if (isThinkingLevel(currentLevel)) state.currentLevel = currentLevel;
233
+
234
+ const validLevels = resolveSupportedThinkingLevels(ctx.model);
235
+ return {
236
+ systemPrompt: appendSystemPromptBlock(
237
+ event.systemPrompt,
238
+ formatGuidance(state.config, state.currentLevel, validLevels),
239
+ ),
240
+ };
241
+ };
242
+
243
+ const resetTemporaryLevel = async (ctx: ExtensionContext) => {
244
+ const state = runtime;
245
+ const resetLevel = state?.temporaryResetLevel;
246
+ if (!state || !resetLevel) return;
247
+
248
+ try {
249
+ withSessionOnlyThinkingLevelChange(() => pi.setThinkingLevel(resetLevel));
250
+ state.currentLevel = resetLevel;
251
+ delete state.temporaryResetLevel;
252
+ } catch (error) {
253
+ notify(ctx, "error", `Failed to reset thinking level: ${errorMessage(error)}`, state.config);
254
+ }
255
+ };
256
+
257
+ pi.on("session_start", async (_event, ctx) => {
258
+ const configResult = await loadConfig({ cwd: ctx.cwd });
259
+ if (!configResult.success) {
260
+ runtime = undefined;
261
+ notify(ctx, "error", configResult.error.message);
262
+ return;
263
+ }
264
+
265
+ const { config } = configResult;
266
+ if (!config.enabled) {
267
+ runtime = undefined;
268
+ return;
269
+ }
270
+
271
+ const initialLevel = pi.getThinkingLevel();
272
+ runtime = { config, reasoningToolCallBackToBackById: new Map() };
273
+ if (isThinkingLevel(initialLevel)) runtime.currentLevel = initialLevel;
274
+
275
+ pi.registerTool({
276
+ name: config.toolName,
277
+ label: "Set Thinking Level",
278
+ description: config.toolDescription,
279
+ promptSnippet: "Set the current Pi thinking level.",
280
+ promptGuidelines: [
281
+ `Use ${config.toolName} to change the thinking level when task complexity justifies a different level.`,
282
+ ],
283
+ parameters: ToolParameters,
284
+ execute: async (toolCallId, params: ToolParameters, _signal, _onUpdate, ctx) => {
285
+ const state = runtime;
286
+ if (!state) return textResult("Adaptive Thinking is not enabled for this session.");
287
+
288
+ const level = params.level.trim();
289
+ const validLevels = resolveSupportedThinkingLevels(ctx.model);
290
+ if (!isThinkingLevel(level) || !validLevels.includes(level)) {
291
+ return textResult(
292
+ `Invalid thinking level: ${level}. Valid levels: ${validLevels.join(", ")}.`,
293
+ );
294
+ }
295
+
296
+ const persist = params.persist ?? false;
297
+ const currentLevel = state.currentLevel ?? pi.getThinkingLevel();
298
+ if (currentLevel === level) {
299
+ return textResult(`Thinking level is already ${level}; no change made.`);
300
+ }
301
+
302
+ if (state.reasoningToolCallBackToBackById.get(toolCallId) ?? false) {
303
+ return textResult(
304
+ `Thinking level change skipped because the previous tool call was also ${state.config.toolName}. Reassess after another tool call or new user input.`,
305
+ );
306
+ }
307
+
308
+ const resetLevel =
309
+ state.persistedLevel ?? (isThinkingLevel(currentLevel) ? currentLevel : undefined);
310
+
311
+ try {
312
+ withSessionOnlyThinkingLevelChange(() => pi.setThinkingLevel(level));
313
+ } catch (error) {
314
+ return textResult(`Failed to set thinking level: ${errorMessage(error)}`);
315
+ }
316
+
317
+ state.currentLevel = level;
318
+ if (persist) {
319
+ state.persistedLevel = level;
320
+ delete state.temporaryResetLevel;
321
+ } else if (resetLevel && resetLevel !== level) {
322
+ state.temporaryResetLevel = resetLevel;
323
+ } else {
324
+ delete state.temporaryResetLevel;
325
+ }
326
+
327
+ return textResult(`Thinking level set to ${level}`);
328
+ },
329
+ });
330
+
331
+ registerRuntimeHandlers();
332
+ });
333
+ }
@@ -0,0 +1,28 @@
1
+ import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
2
+ import type { Api, Model, ModelThinkingLevel } from "@earendil-works/pi-ai";
3
+
4
+ export const fallbackThinkingLevels = [
5
+ "off",
6
+ "minimal",
7
+ "low",
8
+ "medium",
9
+ "high",
10
+ "xhigh",
11
+ "max",
12
+ ] as const;
13
+
14
+ export type PiThinkingLevel = (typeof fallbackThinkingLevels)[number];
15
+
16
+ const levelSet = new Set<string>(fallbackThinkingLevels);
17
+
18
+ export const isThinkingLevel = (level: string): level is PiThinkingLevel => levelSet.has(level);
19
+
20
+ export const resolveSupportedThinkingLevels = (
21
+ model: Model<Api> | undefined,
22
+ ): PiThinkingLevel[] => {
23
+ if (!model) return [...fallbackThinkingLevels];
24
+
25
+ return getSupportedThinkingLevels(model).filter(
26
+ (level: ModelThinkingLevel): level is PiThinkingLevel => isThinkingLevel(level),
27
+ );
28
+ };
@@ -1,16 +0,0 @@
1
- import { type AdaptiveThinkingConfig } from "./config";
2
- export type { AdaptiveThinkingConfig } from "./config";
3
- export type LoadConfigOptions = {
4
- cwd: string;
5
- homeDir?: string;
6
- };
7
- export type LoadConfigResult = {
8
- success: true;
9
- config: AdaptiveThinkingConfig;
10
- source?: string;
11
- } | {
12
- success: false;
13
- source: string;
14
- error: Error;
15
- };
16
- export declare const loadConfig: ({ cwd, homeDir, }: LoadConfigOptions) => Promise<LoadConfigResult>;
package/dist/config.d.ts DELETED
@@ -1,18 +0,0 @@
1
- import { Type, type Static } from "typebox";
2
- export declare const defaultSystemPrompt: string;
3
- export declare const configDefaults: {
4
- enabled: boolean;
5
- quiet: boolean;
6
- toolName: string;
7
- toolDescription: string;
8
- systemPrompt: string;
9
- };
10
- export declare const ConfigSchema: Type.TObject<{
11
- enabled: Type.TBoolean;
12
- quiet: Type.TBoolean;
13
- toolName: Type.TString;
14
- toolDescription: Type.TString;
15
- systemPrompt: Type.TString;
16
- }>;
17
- export type AdaptiveThinkingConfig = Static<typeof ConfigSchema>;
18
- export declare const parseConfig: (input: unknown) => AdaptiveThinkingConfig;
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- export default function adaptiveThinking(pi: ExtensionAPI): void;