@oh-my-pi/pi-coding-agent 15.7.1 → 15.7.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/CHANGELOG.md +20 -0
- package/dist/types/auto-thinking/classifier.d.ts +35 -0
- package/dist/types/config/settings-schema.d.ts +24 -4
- package/dist/types/edit/hashline/diff.d.ts +6 -0
- package/dist/types/modes/components/model-selector.d.ts +3 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/sdk.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +22 -9
- package/dist/types/thinking.d.ts +39 -1
- package/dist/types/tiny/device.d.ts +3 -3
- package/dist/types/tiny/models.d.ts +19 -0
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +180 -0
- package/src/config/settings-schema.ts +24 -4
- package/src/edit/hashline/diff.ts +10 -2
- package/src/edit/streaming.ts +17 -6
- package/src/eval/__tests__/shared-executors.test.ts +32 -0
- package/src/eval/js/shared/local-module-loader.ts +75 -10
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +6 -1
- package/src/modes/acp/acp-agent.ts +13 -3
- package/src/modes/components/footer.ts +10 -3
- package/src/modes/components/model-selector.ts +20 -11
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/settings-selector.ts +4 -1
- package/src/modes/components/status-line/segments.ts +13 -5
- package/src/modes/controllers/event-controller.ts +5 -1
- package/src/modes/controllers/selector-controller.ts +20 -6
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +12 -0
- package/src/sdk.ts +25 -7
- package/src/session/agent-session.ts +193 -32
- package/src/thinking.ts +73 -1
- package/src/tiny/device.ts +4 -10
- package/src/tiny/models.ts +24 -0
|
@@ -52,7 +52,6 @@ import { DEFAULT_PRUNE_CONFIG, pruneToolOutputs } from "@oh-my-pi/pi-agent-core/
|
|
|
52
52
|
import type {
|
|
53
53
|
AssistantMessage,
|
|
54
54
|
Context,
|
|
55
|
-
Effort,
|
|
56
55
|
ImageContent,
|
|
57
56
|
Message,
|
|
58
57
|
MessageAttribution,
|
|
@@ -69,6 +68,7 @@ import type {
|
|
|
69
68
|
import {
|
|
70
69
|
calculateRateLimitBackoffMs,
|
|
71
70
|
clearAnthropicFastModeFallback,
|
|
71
|
+
Effort,
|
|
72
72
|
getSupportedEfforts,
|
|
73
73
|
isContextOverflow,
|
|
74
74
|
isUsageLimitError,
|
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
Snowflake,
|
|
89
89
|
} from "@oh-my-pi/pi-utils";
|
|
90
90
|
import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
|
|
91
|
+
import { classifyDifficulty } from "../auto-thinking/classifier";
|
|
91
92
|
import { reset as resetCapabilities } from "../capability";
|
|
92
93
|
import type { Rule } from "../capability/rule";
|
|
93
94
|
import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
|
|
@@ -165,7 +166,14 @@ import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" w
|
|
|
165
166
|
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
166
167
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
167
168
|
import { invalidateHostMetadata } from "../ssh/connection-manager";
|
|
168
|
-
import {
|
|
169
|
+
import {
|
|
170
|
+
AUTO_THINKING,
|
|
171
|
+
type ConfiguredThinkingLevel,
|
|
172
|
+
clampAutoThinkingEffort,
|
|
173
|
+
resolveProvisionalAutoLevel,
|
|
174
|
+
resolveThinkingLevelForModel,
|
|
175
|
+
toReasoningEffort,
|
|
176
|
+
} from "../thinking";
|
|
169
177
|
import { shutdownTinyTitleClient } from "../tiny/title-client";
|
|
170
178
|
import {
|
|
171
179
|
buildDiscoverableToolSearchIndex,
|
|
@@ -241,7 +249,14 @@ export type AgentSessionEvent =
|
|
|
241
249
|
| { type: "todo_auto_clear" }
|
|
242
250
|
| { type: "irc_message"; message: CustomMessage }
|
|
243
251
|
| { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string }
|
|
244
|
-
| {
|
|
252
|
+
| {
|
|
253
|
+
type: "thinking_level_changed";
|
|
254
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
255
|
+
/** The user-configured selector when it differs from the effective level (e.g. `auto`). */
|
|
256
|
+
configured?: ConfiguredThinkingLevel;
|
|
257
|
+
/** The level `auto` resolved to this turn, once classified. */
|
|
258
|
+
resolved?: Effort;
|
|
259
|
+
}
|
|
245
260
|
| { type: "goal_updated"; goal: Goal | null; state?: GoalModeState };
|
|
246
261
|
|
|
247
262
|
/** Listener function for agent session events */
|
|
@@ -265,7 +280,7 @@ export interface AgentSessionConfig {
|
|
|
265
280
|
/** Models to cycle through with Ctrl+P (from --models flag) */
|
|
266
281
|
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
267
282
|
/** Initial session thinking selector. */
|
|
268
|
-
thinkingLevel?:
|
|
283
|
+
thinkingLevel?: ConfiguredThinkingLevel;
|
|
269
284
|
/** Prompt templates for expansion */
|
|
270
285
|
promptTemplates?: PromptTemplate[];
|
|
271
286
|
/** File-based slash commands for expansion */
|
|
@@ -445,8 +460,8 @@ interface RetryFallbackSelector {
|
|
|
445
460
|
interface ActiveRetryFallbackState {
|
|
446
461
|
role: string;
|
|
447
462
|
originalSelector: string;
|
|
448
|
-
originalThinkingLevel:
|
|
449
|
-
lastAppliedFallbackThinkingLevel:
|
|
463
|
+
originalThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
464
|
+
lastAppliedFallbackThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
450
465
|
}
|
|
451
466
|
|
|
452
467
|
function parseRetryFallbackSelector(selector: string): RetryFallbackSelector | undefined {
|
|
@@ -782,7 +797,12 @@ export class AgentSession {
|
|
|
782
797
|
readonly configWarnings: string[] = [];
|
|
783
798
|
|
|
784
799
|
#scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
800
|
+
/** Effective, metadata-clamped thinking level applied to the agent (never `auto`). */
|
|
785
801
|
#thinkingLevel: ThinkingLevel | undefined;
|
|
802
|
+
/** True when the user configured `auto`; the effective level is resolved per turn. */
|
|
803
|
+
#autoThinking: boolean = false;
|
|
804
|
+
/** The level `auto` last resolved to (for UI); undefined until a turn is classified. */
|
|
805
|
+
#autoResolvedLevel: Effort | undefined;
|
|
786
806
|
#promptTemplates: PromptTemplate[];
|
|
787
807
|
#slashCommands: FileSlashCommand[];
|
|
788
808
|
|
|
@@ -1041,7 +1061,15 @@ export class AgentSession {
|
|
|
1041
1061
|
this.#parentEvalSessionId = config.parentEvalSessionId;
|
|
1042
1062
|
this.#ownedAsyncJobManager = config.ownedAsyncJobManager;
|
|
1043
1063
|
this.#scopedModels = config.scopedModels ?? [];
|
|
1044
|
-
|
|
1064
|
+
if (config.thinkingLevel === AUTO_THINKING) {
|
|
1065
|
+
// `auto` is session-level: keep the flag and show a provisional concrete
|
|
1066
|
+
// level (the agent's initial effort was already set by the caller) until
|
|
1067
|
+
// the first user turn is classified.
|
|
1068
|
+
this.#autoThinking = true;
|
|
1069
|
+
this.#thinkingLevel = resolveProvisionalAutoLevel(this.model);
|
|
1070
|
+
} else {
|
|
1071
|
+
this.#thinkingLevel = config.thinkingLevel;
|
|
1072
|
+
}
|
|
1045
1073
|
this.#promptTemplates = config.promptTemplates ?? [];
|
|
1046
1074
|
this.#slashCommands = config.slashCommands ?? [];
|
|
1047
1075
|
this.#extensionRunner = config.extensionRunner;
|
|
@@ -2935,11 +2963,26 @@ export class AgentSession {
|
|
|
2935
2963
|
return this.agent.state.model;
|
|
2936
2964
|
}
|
|
2937
2965
|
|
|
2938
|
-
/**
|
|
2966
|
+
/** Effective thinking level applied to the agent (the resolved level when `auto`). */
|
|
2939
2967
|
get thinkingLevel(): ThinkingLevel | undefined {
|
|
2940
2968
|
return this.#thinkingLevel;
|
|
2941
2969
|
}
|
|
2942
2970
|
|
|
2971
|
+
/** The selector the user configured: `auto` when auto mode is active, else the effective level. */
|
|
2972
|
+
configuredThinkingLevel(): ConfiguredThinkingLevel | undefined {
|
|
2973
|
+
return this.#autoThinking ? AUTO_THINKING : this.#thinkingLevel;
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
/** True when `auto` thinking mode is active. */
|
|
2977
|
+
get isAutoThinking(): boolean {
|
|
2978
|
+
return this.#autoThinking;
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
/** The level `auto` resolved to for the current turn (undefined until classified). */
|
|
2982
|
+
autoResolvedThinkingLevel(): Effort | undefined {
|
|
2983
|
+
return this.#autoResolvedLevel;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2943
2986
|
get serviceTier(): ServiceTier | undefined {
|
|
2944
2987
|
return this.agent.serviceTier;
|
|
2945
2988
|
}
|
|
@@ -4304,6 +4347,17 @@ export class AgentSession {
|
|
|
4304
4347
|
return;
|
|
4305
4348
|
}
|
|
4306
4349
|
|
|
4350
|
+
// Auto thinking: classify this real user turn and set the effective level
|
|
4351
|
+
// before the model request. Synthetic/tool-continuation turns (developer/
|
|
4352
|
+
// custom roles) and non-auto sessions are skipped. Never blocks the turn —
|
|
4353
|
+
// failures fall back to a concrete level inside the helper.
|
|
4354
|
+
if (this.#autoThinking && message.role === "user") {
|
|
4355
|
+
await this.#applyAutoThinkingLevel(expandedText, generation);
|
|
4356
|
+
if (this.#promptGeneration !== generation) {
|
|
4357
|
+
return;
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4307
4361
|
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
4308
4362
|
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions);
|
|
4309
4363
|
if (!options?.skipPostPromptRecoveryWait) {
|
|
@@ -5061,8 +5115,8 @@ export class AgentSession {
|
|
|
5061
5115
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
5062
5116
|
|
|
5063
5117
|
// Re-apply thinking for the newly selected model. Prefer the model's
|
|
5064
|
-
// configured defaultLevel; otherwise preserve the current level.
|
|
5065
|
-
this
|
|
5118
|
+
// configured defaultLevel; otherwise preserve the current level (or auto).
|
|
5119
|
+
this.#reapplyThinkingLevel(model.thinking?.defaultLevel);
|
|
5066
5120
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
5067
5121
|
}
|
|
5068
5122
|
|
|
@@ -5084,8 +5138,12 @@ export class AgentSession {
|
|
|
5084
5138
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
5085
5139
|
|
|
5086
5140
|
// Apply explicit thinking level if given; otherwise prefer the model's
|
|
5087
|
-
// configured defaultLevel; otherwise re-clamp the current level.
|
|
5088
|
-
|
|
5141
|
+
// configured defaultLevel; otherwise re-clamp the current level (or auto).
|
|
5142
|
+
if (thinkingLevel !== undefined) {
|
|
5143
|
+
this.setThinkingLevel(thinkingLevel);
|
|
5144
|
+
} else {
|
|
5145
|
+
this.#reapplyThinkingLevel(model.thinking?.defaultLevel);
|
|
5146
|
+
}
|
|
5089
5147
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
5090
5148
|
}
|
|
5091
5149
|
|
|
@@ -5233,8 +5291,8 @@ export class AgentSession {
|
|
|
5233
5291
|
this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model));
|
|
5234
5292
|
this.settings.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`);
|
|
5235
5293
|
|
|
5236
|
-
// Apply the scoped model's configured thinking level
|
|
5237
|
-
this.setThinkingLevel(next.thinkingLevel);
|
|
5294
|
+
// Apply the scoped model's configured thinking level, preserving auto.
|
|
5295
|
+
this.setThinkingLevel(this.#autoThinking ? AUTO_THINKING : next.thinkingLevel);
|
|
5238
5296
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
5239
5297
|
|
|
5240
5298
|
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
|
@@ -5263,8 +5321,8 @@ export class AgentSession {
|
|
|
5263
5321
|
this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
|
|
5264
5322
|
this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel));
|
|
5265
5323
|
this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
|
|
5266
|
-
// Re-apply the current thinking level for the newly selected model
|
|
5267
|
-
this
|
|
5324
|
+
// Re-apply the current thinking level (or auto) for the newly selected model
|
|
5325
|
+
this.#reapplyThinkingLevel();
|
|
5268
5326
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
5269
5327
|
|
|
5270
5328
|
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
|
@@ -5282,10 +5340,29 @@ export class AgentSession {
|
|
|
5282
5340
|
// =========================================================================
|
|
5283
5341
|
|
|
5284
5342
|
/**
|
|
5285
|
-
* Set thinking level.
|
|
5286
|
-
*
|
|
5343
|
+
* Set the thinking level. `auto` enables per-turn classification (session-level,
|
|
5344
|
+
* never written to the session log); a concrete level clears auto. The effective
|
|
5345
|
+
* metadata-clamped level is saved to the session/settings only when it changes.
|
|
5287
5346
|
*/
|
|
5288
|
-
setThinkingLevel(level:
|
|
5347
|
+
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist: boolean = false): void {
|
|
5348
|
+
if (level === AUTO_THINKING) {
|
|
5349
|
+
const provisional = resolveProvisionalAutoLevel(this.model);
|
|
5350
|
+
const wasAuto = this.#autoThinking;
|
|
5351
|
+
this.#autoThinking = true;
|
|
5352
|
+
this.#autoResolvedLevel = undefined;
|
|
5353
|
+
this.#thinkingLevel = provisional;
|
|
5354
|
+
this.agent.setThinkingLevel(toReasoningEffort(provisional));
|
|
5355
|
+
if (persist) {
|
|
5356
|
+
this.settings.set("defaultThinkingLevel", AUTO_THINKING);
|
|
5357
|
+
}
|
|
5358
|
+
if (!wasAuto || this.#thinkingLevel !== provisional) {
|
|
5359
|
+
this.#emit({ type: "thinking_level_changed", thinkingLevel: provisional, configured: AUTO_THINKING });
|
|
5360
|
+
}
|
|
5361
|
+
return;
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5364
|
+
this.#autoThinking = false;
|
|
5365
|
+
this.#autoResolvedLevel = undefined;
|
|
5289
5366
|
const effectiveLevel = resolveThinkingLevelForModel(this.model, level);
|
|
5290
5367
|
const isChanging = effectiveLevel !== this.#thinkingLevel;
|
|
5291
5368
|
|
|
@@ -5302,14 +5379,28 @@ export class AgentSession {
|
|
|
5302
5379
|
}
|
|
5303
5380
|
|
|
5304
5381
|
/**
|
|
5305
|
-
*
|
|
5306
|
-
*
|
|
5382
|
+
* Re-apply the active thinking selection after a model change. Preserves `auto`
|
|
5383
|
+
* (re-clamping the provisional level to the new model); otherwise re-applies the
|
|
5384
|
+
* preferred default or the current effective level.
|
|
5307
5385
|
*/
|
|
5308
|
-
|
|
5386
|
+
#reapplyThinkingLevel(preferredDefault?: ThinkingLevel): void {
|
|
5387
|
+
this.setThinkingLevel(this.#autoThinking ? AUTO_THINKING : (preferredDefault ?? this.#thinkingLevel));
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5390
|
+
/**
|
|
5391
|
+
* Cycle to next thinking level: off → auto → minimal..xhigh → off.
|
|
5392
|
+
* @returns New selector, or undefined if model doesn't support thinking
|
|
5393
|
+
*/
|
|
5394
|
+
cycleThinkingLevel(): ConfiguredThinkingLevel | undefined {
|
|
5309
5395
|
if (!this.model?.reasoning) return undefined;
|
|
5310
5396
|
|
|
5311
|
-
const levels = [
|
|
5312
|
-
|
|
5397
|
+
const levels: ConfiguredThinkingLevel[] = [
|
|
5398
|
+
ThinkingLevel.Off,
|
|
5399
|
+
AUTO_THINKING,
|
|
5400
|
+
...this.getAvailableThinkingLevels(),
|
|
5401
|
+
];
|
|
5402
|
+
const configured = this.configuredThinkingLevel();
|
|
5403
|
+
const currentLevel = configured === ThinkingLevel.Inherit ? ThinkingLevel.Off : configured;
|
|
5313
5404
|
const currentIndex = currentLevel ? levels.indexOf(currentLevel) : -1;
|
|
5314
5405
|
const nextIndex = (currentIndex + 1) % levels.length;
|
|
5315
5406
|
const nextLevel = levels[nextIndex];
|
|
@@ -5319,6 +5410,61 @@ export class AgentSession {
|
|
|
5319
5410
|
return nextLevel;
|
|
5320
5411
|
}
|
|
5321
5412
|
|
|
5413
|
+
/** Timeout (ms) for per-turn auto-thinking classification before falling back. */
|
|
5414
|
+
static readonly #AUTO_THINKING_TIMEOUT_MS = 4000;
|
|
5415
|
+
|
|
5416
|
+
/**
|
|
5417
|
+
* Classify the current user turn and set the effective thinking level for it.
|
|
5418
|
+
* Bounded by a timeout + abort; on any failure (no smol model, timeout, parse
|
|
5419
|
+
* error) it falls back to the provisional concrete level and continues. Never
|
|
5420
|
+
* throws into the turn, and never clears `#autoThinking` (auto stays active).
|
|
5421
|
+
*/
|
|
5422
|
+
async #applyAutoThinkingLevel(promptText: string, generation: number): Promise<void> {
|
|
5423
|
+
const model = this.model;
|
|
5424
|
+
if (!model?.reasoning) return;
|
|
5425
|
+
|
|
5426
|
+
let resolved: Effort | undefined;
|
|
5427
|
+
if (containsUltrathink(promptText)) {
|
|
5428
|
+
// The user explicitly asked for maximum thinking; bypass the classifier
|
|
5429
|
+
// and jump straight to the highest auto-supported level for this model.
|
|
5430
|
+
resolved = clampAutoThinkingEffort(model, Effort.XHigh);
|
|
5431
|
+
} else {
|
|
5432
|
+
const controller = new AbortController();
|
|
5433
|
+
const timer = setTimeout(() => controller.abort(), AgentSession.#AUTO_THINKING_TIMEOUT_MS);
|
|
5434
|
+
try {
|
|
5435
|
+
resolved = await classifyDifficulty(promptText, {
|
|
5436
|
+
settings: this.settings,
|
|
5437
|
+
registry: this.#modelRegistry,
|
|
5438
|
+
model,
|
|
5439
|
+
sessionId: this.sessionId,
|
|
5440
|
+
signal: controller.signal,
|
|
5441
|
+
metadataResolver: provider => this.agent.metadataForProvider(provider),
|
|
5442
|
+
});
|
|
5443
|
+
} catch (error) {
|
|
5444
|
+
logger.debug("auto-thinking: classification failed; using fallback level", {
|
|
5445
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5446
|
+
});
|
|
5447
|
+
} finally {
|
|
5448
|
+
clearTimeout(timer);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
|
|
5452
|
+
// Drop the result if the turn was aborted/superseded while classifying.
|
|
5453
|
+
if (this.#promptGeneration !== generation || !this.#autoThinking) return;
|
|
5454
|
+
|
|
5455
|
+
const effort = resolved ?? resolveProvisionalAutoLevel(model);
|
|
5456
|
+
if (effort === undefined) return;
|
|
5457
|
+
this.#autoResolvedLevel = effort;
|
|
5458
|
+
this.#thinkingLevel = effort;
|
|
5459
|
+
this.agent.setThinkingLevel(toReasoningEffort(effort));
|
|
5460
|
+
this.#emit({
|
|
5461
|
+
type: "thinking_level_changed",
|
|
5462
|
+
thinkingLevel: effort,
|
|
5463
|
+
configured: AUTO_THINKING,
|
|
5464
|
+
resolved: effort,
|
|
5465
|
+
});
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5322
5468
|
/**
|
|
5323
5469
|
* True when *any* fast-mode-granting service tier is configured, regardless
|
|
5324
5470
|
* of whether the active model's provider actually realizes it. Used by the
|
|
@@ -7260,7 +7406,9 @@ export class AgentSession {
|
|
|
7260
7406
|
throw new Error(`No API key for retry fallback ${selector.raw}`);
|
|
7261
7407
|
}
|
|
7262
7408
|
|
|
7263
|
-
|
|
7409
|
+
// Capture the configured selector (auto-aware) so a fallback chain preserves
|
|
7410
|
+
// `auto` instead of collapsing it to the level it resolved to this turn.
|
|
7411
|
+
const currentThinkingLevel = this.configuredThinkingLevel();
|
|
7264
7412
|
const nextThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;
|
|
7265
7413
|
|
|
7266
7414
|
this.#setModelWithProviderSessionReset(candidate);
|
|
@@ -7333,7 +7481,7 @@ export class AgentSession {
|
|
|
7333
7481
|
const apiKey = await this.#modelRegistry.getApiKey(primaryModel, this.sessionId);
|
|
7334
7482
|
if (!apiKey) return;
|
|
7335
7483
|
|
|
7336
|
-
const currentThinkingLevel = this.
|
|
7484
|
+
const currentThinkingLevel = this.configuredThinkingLevel();
|
|
7337
7485
|
const thinkingToApply =
|
|
7338
7486
|
currentThinkingLevel === lastAppliedFallbackThinkingLevel ? originalThinkingLevel : currentThinkingLevel;
|
|
7339
7487
|
this.#setModelWithProviderSessionReset(primaryModel);
|
|
@@ -8244,6 +8392,8 @@ export class AgentSession {
|
|
|
8244
8392
|
const previousScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
|
|
8245
8393
|
const previousModel = this.model;
|
|
8246
8394
|
const previousThinkingLevel = this.#thinkingLevel;
|
|
8395
|
+
const previousAutoThinking = this.#autoThinking;
|
|
8396
|
+
const previousAutoResolvedLevel = this.#autoResolvedLevel;
|
|
8247
8397
|
const previousServiceTier = this.agent.serviceTier;
|
|
8248
8398
|
const previousSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
|
|
8249
8399
|
const previousTools = [...this.agent.state.tools];
|
|
@@ -8321,12 +8471,21 @@ export class AgentSession {
|
|
|
8321
8471
|
.some(entry => entry.type === "service_tier_change");
|
|
8322
8472
|
const defaultThinkingLevel = this.settings.get("defaultThinkingLevel");
|
|
8323
8473
|
const configuredServiceTier = this.settings.get("serviceTier");
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8474
|
+
// Session log entries only ever store concrete levels (auto is never
|
|
8475
|
+
// written), so `auto` can only arrive via the settings default.
|
|
8476
|
+
const restoredThinkingLevel: ConfiguredThinkingLevel | undefined = hasThinkingEntry
|
|
8477
|
+
? (sessionContext.thinkingLevel as ThinkingLevel | undefined)
|
|
8478
|
+
: defaultThinkingLevel;
|
|
8479
|
+
if (restoredThinkingLevel === AUTO_THINKING) {
|
|
8480
|
+
this.#autoThinking = true;
|
|
8481
|
+
this.#autoResolvedLevel = undefined;
|
|
8482
|
+
this.#thinkingLevel = resolveProvisionalAutoLevel(this.model);
|
|
8483
|
+
} else {
|
|
8484
|
+
this.#autoThinking = false;
|
|
8485
|
+
this.#autoResolvedLevel = undefined;
|
|
8486
|
+
this.#thinkingLevel = resolveThinkingLevelForModel(this.model, restoredThinkingLevel);
|
|
8487
|
+
}
|
|
8488
|
+
this.agent.setThinkingLevel(toReasoningEffort(this.#thinkingLevel));
|
|
8330
8489
|
this.agent.serviceTier = hasServiceTierEntry
|
|
8331
8490
|
? sessionContext.serviceTier
|
|
8332
8491
|
: configuredServiceTier === "none"
|
|
@@ -8375,6 +8534,8 @@ export class AgentSession {
|
|
|
8375
8534
|
this.#syncToolCallBatchCap(undefined);
|
|
8376
8535
|
}
|
|
8377
8536
|
this.#thinkingLevel = previousThinkingLevel;
|
|
8537
|
+
this.#autoThinking = previousAutoThinking;
|
|
8538
|
+
this.#autoResolvedLevel = previousAutoResolvedLevel;
|
|
8378
8539
|
this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
|
|
8379
8540
|
this.agent.serviceTier = previousServiceTier;
|
|
8380
8541
|
this.#syncTodoPhasesFromBranch();
|
package/src/thinking.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ResolvedThinkingLevel, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import { clampThinkingLevelForModel,
|
|
2
|
+
import { clampThinkingLevelForModel, Effort, getSupportedEfforts, type Model, THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Metadata used to render thinking selector values in the coding-agent UI.
|
|
@@ -85,3 +85,75 @@ export function resolveThinkingLevelForModel(
|
|
|
85
85
|
}
|
|
86
86
|
return clampThinkingLevelForModel(model, level);
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sentinel selector for the coding-agent "auto" thinking mode. Kept entirely
|
|
91
|
+
* inside the coding-agent layer: it is never an {@link Effort} or
|
|
92
|
+
* {@link ThinkingLevel}, so provider mapping/clamping keeps seeing concrete
|
|
93
|
+
* efforts. The session resolves `auto` to a concrete effort each turn.
|
|
94
|
+
*/
|
|
95
|
+
export const AUTO_THINKING = "auto" as const;
|
|
96
|
+
|
|
97
|
+
/** A thinking selector as configured by the user — a concrete level or `auto`. */
|
|
98
|
+
export type ConfiguredThinkingLevel = ThinkingLevel | typeof AUTO_THINKING;
|
|
99
|
+
|
|
100
|
+
/** Metadata used to render the `auto` selector value alongside concrete levels. */
|
|
101
|
+
export interface ConfiguredThinkingLevelMetadata {
|
|
102
|
+
value: ConfiguredThinkingLevel;
|
|
103
|
+
label: string;
|
|
104
|
+
description: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const AUTO_THINKING_METADATA: ConfiguredThinkingLevelMetadata = {
|
|
108
|
+
value: AUTO_THINKING,
|
|
109
|
+
label: "auto",
|
|
110
|
+
description: "Auto-detect per prompt (low–xhigh)",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parses a configured thinking selector, accepting `auto` in addition to every
|
|
115
|
+
* value {@link parseThinkingLevel} accepts. {@link parseThinkingLevel} itself
|
|
116
|
+
* stays strict so model-suffix parsing (`model:high`) keeps rejecting `auto`.
|
|
117
|
+
*/
|
|
118
|
+
export function parseConfiguredThinkingLevel(value: string | null | undefined): ConfiguredThinkingLevel | undefined {
|
|
119
|
+
if (value === AUTO_THINKING) return AUTO_THINKING;
|
|
120
|
+
return parseThinkingLevel(value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Returns display metadata for a configured selector, including `auto`. */
|
|
124
|
+
export function getConfiguredThinkingLevelMetadata(level: ConfiguredThinkingLevel): ConfiguredThinkingLevelMetadata {
|
|
125
|
+
return level === AUTO_THINKING ? AUTO_THINKING_METADATA : getThinkingLevelMetadata(level);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolves an auto-classified effort against the active model's supported
|
|
130
|
+
* range. Unlike {@link clampThinkingLevelForModel}, `auto` never resolves below
|
|
131
|
+
* {@link Effort.Low}: the eligible pool is the model's supported efforts at or
|
|
132
|
+
* above Low (falling back to the full supported set only when the model maxes
|
|
133
|
+
* out below Low). Within that pool the request snaps to the highest level not
|
|
134
|
+
* exceeding it, or the pool minimum when the request is below the pool.
|
|
135
|
+
*/
|
|
136
|
+
export function clampAutoThinkingEffort(model: Model | undefined, effort: Effort): Effort {
|
|
137
|
+
const supported = model ? getSupportedEfforts(model) : THINKING_EFFORTS;
|
|
138
|
+
if (supported.length === 0) return effort;
|
|
139
|
+
const lowIndex = THINKING_EFFORTS.indexOf(Effort.Low);
|
|
140
|
+
const eligible = supported.filter(level => THINKING_EFFORTS.indexOf(level) >= lowIndex);
|
|
141
|
+
const pool = eligible.length > 0 ? eligible : supported;
|
|
142
|
+
const requestedIndex = THINKING_EFFORTS.indexOf(effort);
|
|
143
|
+
let chosen = pool[0];
|
|
144
|
+
for (const candidate of pool) {
|
|
145
|
+
if (THINKING_EFFORTS.indexOf(candidate) > requestedIndex) break;
|
|
146
|
+
chosen = candidate;
|
|
147
|
+
}
|
|
148
|
+
return chosen;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The provisional concrete level shown while `auto` is configured but before a
|
|
153
|
+
* turn has been classified. Prefers the model's `defaultLevel`, otherwise High,
|
|
154
|
+
* clamped into the auto range. Returns `undefined` for non-reasoning models.
|
|
155
|
+
*/
|
|
156
|
+
export function resolveProvisionalAutoLevel(model: Model | undefined): Effort | undefined {
|
|
157
|
+
if (!model?.reasoning) return undefined;
|
|
158
|
+
return clampAutoThinkingEffort(model, model.thinking?.defaultLevel ?? Effort.High);
|
|
159
|
+
}
|
package/src/tiny/device.ts
CHANGED
|
@@ -27,12 +27,6 @@ const DEVICE_VALUES: Record<TinyModelDevice, true> = {
|
|
|
27
27
|
"webnn-cpu": true,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
function defaultTinyModelDevice(): TinyModelDevice {
|
|
31
|
-
if (process.platform === "win32") return "dml";
|
|
32
|
-
if (process.platform === "linux" && process.arch === "x64") return "cuda";
|
|
33
|
-
return CPU_DEVICE;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
30
|
function usesDarwinWorkerWebGpu(device: TinyModelDevice): boolean {
|
|
37
31
|
return process.platform === "darwin" && (device === "gpu" || device === "webgpu" || device === "auto");
|
|
38
32
|
}
|
|
@@ -51,7 +45,7 @@ export function resolveTinyModelDevicePreference(
|
|
|
51
45
|
value: string | undefined = $env.PI_TINY_DEVICE,
|
|
52
46
|
): TinyModelDevicePreference {
|
|
53
47
|
return {
|
|
54
|
-
device: normalizeTinyModelDevice(value) ??
|
|
48
|
+
device: normalizeTinyModelDevice(value) ?? CPU_DEVICE,
|
|
55
49
|
raw: value,
|
|
56
50
|
};
|
|
57
51
|
}
|
|
@@ -62,7 +56,7 @@ export function tinyModelDeviceLoadOrder(preference: TinyModelDevicePreference):
|
|
|
62
56
|
return [preference.device, CPU_DEVICE];
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
/** Sentinel `providers.tinyModelDevice` value meaning "use the built-in
|
|
59
|
+
/** Sentinel `providers.tinyModelDevice` value meaning "use the built-in CPU default". */
|
|
66
60
|
export const TINY_MODEL_DEVICE_DEFAULT = "default";
|
|
67
61
|
|
|
68
62
|
/** Accepted values for the `providers.tinyModelDevice` setting (validation + UI). */
|
|
@@ -85,7 +79,7 @@ export const TINY_MODEL_DEVICE_SETTING_VALUES = [
|
|
|
85
79
|
|
|
86
80
|
/** Submenu metadata for the `providers.tinyModelDevice` setting. */
|
|
87
81
|
export const TINY_MODEL_DEVICE_SETTING_OPTIONS = [
|
|
88
|
-
{ value: "default", label: "Default", description: "
|
|
82
|
+
{ value: "default", label: "Default", description: "CPU-only inference" },
|
|
89
83
|
{ value: "gpu", label: "GPU", description: "Accelerated provider (WebGPU/Metal, CUDA, or DirectML)" },
|
|
90
84
|
{ value: "cpu", label: "CPU", description: "CPU-only inference" },
|
|
91
85
|
{ value: "metal", label: "Metal", description: "WebGPU alias for Apple GPUs" },
|
|
@@ -108,7 +102,7 @@ export const TINY_MODEL_DEVICE_SETTING_OPTIONS = [
|
|
|
108
102
|
/**
|
|
109
103
|
* Map a `providers.tinyModelDevice` setting value onto a `PI_TINY_DEVICE` env
|
|
110
104
|
* value for the worker. Returns `undefined` for the default sentinel so the
|
|
111
|
-
* worker keeps its built-in
|
|
105
|
+
* worker keeps its built-in CPU default; the worker still validates the
|
|
112
106
|
* forwarded value via {@link normalizeTinyModelDevice}.
|
|
113
107
|
*/
|
|
114
108
|
export function tinyModelDeviceSettingToEnv(value: string | undefined): string | undefined {
|
package/src/tiny/models.ts
CHANGED
|
@@ -216,3 +216,27 @@ export const TINY_LOCAL_MODELS = [
|
|
|
216
216
|
...TINY_TITLE_LOCAL_MODELS,
|
|
217
217
|
...TINY_MEMORY_LOCAL_MODELS,
|
|
218
218
|
] as const satisfies readonly TinyTitleLocalModelSpec[];
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Difficulty-classifier model for the `auto` thinking level. Defaults to the
|
|
222
|
+
* online smol path; the local options reuse the memory-model registry because
|
|
223
|
+
* the shared worker's `complete()` only accepts memory local keys, and the
|
|
224
|
+
* 1B+ memory models classify coding difficulty far more reliably than the
|
|
225
|
+
* sub-1B title models.
|
|
226
|
+
*/
|
|
227
|
+
export const ONLINE_AUTO_THINKING_MODEL_KEY = ONLINE_MEMORY_MODEL_KEY;
|
|
228
|
+
export const AUTO_THINKING_MODEL_VALUES = TINY_MEMORY_MODEL_VALUES;
|
|
229
|
+
export type AutoThinkingModelKey = TinyMemoryModelKey;
|
|
230
|
+
|
|
231
|
+
export const AUTO_THINKING_MODEL_OPTIONS = [
|
|
232
|
+
{
|
|
233
|
+
value: ONLINE_AUTO_THINKING_MODEL_KEY,
|
|
234
|
+
label: "Online (smol)",
|
|
235
|
+
description: "Classify prompt difficulty with the online smol model; no local download or on-device inference.",
|
|
236
|
+
},
|
|
237
|
+
...TINY_MEMORY_LOCAL_MODELS.map(model => ({
|
|
238
|
+
value: model.key,
|
|
239
|
+
label: model.label,
|
|
240
|
+
description: model.description,
|
|
241
|
+
})),
|
|
242
|
+
] satisfies ReadonlyArray<{ value: AutoThinkingModelKey; label: string; description: string }>;
|