@oh-my-pi/pi-coding-agent 13.9.2 → 13.9.4

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/examples/sdk/02-custom-model.ts +2 -1
  3. package/package.json +7 -7
  4. package/src/cli/args.ts +10 -6
  5. package/src/cli/list-models.ts +2 -2
  6. package/src/commands/launch.ts +3 -3
  7. package/src/config/model-registry.ts +136 -38
  8. package/src/config/model-resolver.ts +47 -21
  9. package/src/config/settings-schema.ts +56 -2
  10. package/src/discovery/helpers.ts +3 -3
  11. package/src/extensibility/custom-tools/types.ts +2 -0
  12. package/src/extensibility/extensions/loader.ts +3 -2
  13. package/src/extensibility/extensions/types.ts +10 -7
  14. package/src/extensibility/hooks/types.ts +2 -0
  15. package/src/main.ts +5 -22
  16. package/src/memories/index.ts +7 -3
  17. package/src/modes/components/footer.ts +10 -8
  18. package/src/modes/components/model-selector.ts +33 -38
  19. package/src/modes/components/settings-defs.ts +32 -3
  20. package/src/modes/components/settings-selector.ts +16 -5
  21. package/src/modes/components/status-line/context-thresholds.ts +68 -0
  22. package/src/modes/components/status-line/segments.ts +11 -12
  23. package/src/modes/components/status-line.ts +2 -6
  24. package/src/modes/components/thinking-selector.ts +7 -7
  25. package/src/modes/components/tree-selector.ts +3 -2
  26. package/src/modes/controllers/command-controller.ts +11 -26
  27. package/src/modes/controllers/event-controller.ts +16 -3
  28. package/src/modes/controllers/input-controller.ts +4 -2
  29. package/src/modes/controllers/selector-controller.ts +5 -4
  30. package/src/modes/interactive-mode.ts +2 -2
  31. package/src/modes/rpc/rpc-client.ts +5 -10
  32. package/src/modes/rpc/rpc-types.ts +5 -5
  33. package/src/modes/theme/theme.ts +8 -3
  34. package/src/priority.json +1 -0
  35. package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
  36. package/src/prompts/system/system-prompt.md +18 -2
  37. package/src/prompts/tools/hashline.md +139 -83
  38. package/src/sdk.ts +24 -16
  39. package/src/session/agent-session.ts +261 -118
  40. package/src/session/agent-storage.ts +14 -14
  41. package/src/session/compaction/compaction.ts +500 -13
  42. package/src/session/messages.ts +12 -1
  43. package/src/session/session-manager.ts +77 -19
  44. package/src/slash-commands/builtin-registry.ts +48 -0
  45. package/src/task/agents.ts +3 -2
  46. package/src/task/executor.ts +2 -2
  47. package/src/task/types.ts +2 -1
  48. package/src/thinking.ts +87 -0
  49. package/src/tools/browser.ts +15 -6
  50. package/src/tools/fetch.ts +118 -100
  51. package/src/tools/index.ts +2 -1
  52. package/src/web/kagi.ts +62 -7
  53. package/src/web/search/providers/exa.ts +74 -3
@@ -1,17 +1,20 @@
1
1
  /**
2
2
  * Model resolution, scoping, and initial selection
3
3
  */
4
+
5
+ import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
6
  import {
5
7
  type Api,
8
+ clampThinkingLevelForModel,
6
9
  DEFAULT_MODEL_PER_PROVIDER,
10
+ type Effort,
7
11
  type KnownProvider,
8
12
  type Model,
9
13
  modelsAreEqual,
10
- parseThinkingLevel,
11
- type ThinkingLevel,
12
14
  } from "@oh-my-pi/pi-ai";
13
15
  import chalk from "chalk";
14
16
  import MODEL_PRIO from "../priority.json" with { type: "json" };
17
+ import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
15
18
  import { fuzzyMatch } from "../utils/fuzzy";
16
19
  import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
17
20
  import type { Settings } from "./settings";
@@ -377,7 +380,14 @@ export function resolveModelRoleValue(
377
380
  options?.matchPreferences,
378
381
  );
379
382
 
380
- return { model, thinkingLevel, explicitThinkingLevel, warning };
383
+ return {
384
+ model,
385
+ thinkingLevel: explicitThinkingLevel
386
+ ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
387
+ : thinkingLevel,
388
+ explicitThinkingLevel,
389
+ warning,
390
+ };
381
391
  }
382
392
 
383
393
  export function extractExplicitThinkingSelector(
@@ -393,10 +403,10 @@ export function extractExplicitThinkingSelector(
393
403
  while (!visited.has(current)) {
394
404
  visited.add(current);
395
405
  const lastColonIndex = current.lastIndexOf(":");
396
- const hasThinkingSuffix =
397
- lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(current.slice(lastColonIndex + 1));
398
- if (hasThinkingSuffix) {
399
- return current.slice(lastColonIndex + 1) as ThinkingLevel;
406
+ const thinkingSelector =
407
+ lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(current.slice(lastColonIndex + 1)) : undefined;
408
+ if (thinkingSelector) {
409
+ return thinkingSelector;
400
410
  }
401
411
  const expanded = expandRoleAlias(current, settings).trim();
402
412
  if (!expanded || expanded === current) break;
@@ -520,7 +530,13 @@ export async function resolveModelScope(
520
530
 
521
531
  for (const model of matchingModels) {
522
532
  if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
523
- scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
533
+ scopedModels.push({
534
+ model,
535
+ thinkingLevel: explicitThinkingLevel
536
+ ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
537
+ : thinkingLevel,
538
+ explicitThinkingLevel,
539
+ });
524
540
  }
525
541
  }
526
542
  continue;
@@ -543,7 +559,13 @@ export async function resolveModelScope(
543
559
 
544
560
  // Avoid duplicates
545
561
  if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
546
- scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
562
+ scopedModels.push({
563
+ model,
564
+ thinkingLevel: explicitThinkingLevel
565
+ ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
566
+ : thinkingLevel,
567
+ explicitThinkingLevel,
568
+ });
547
569
  }
548
570
  }
549
571
 
@@ -644,7 +666,7 @@ export function resolveCliModel(options: {
644
666
 
645
667
  export interface InitialModelResult {
646
668
  model: Model<Api> | undefined;
647
- thinkingLevel: ThinkingLevel;
669
+ thinkingLevel?: ThinkingLevel;
648
670
  fallbackMessage: string | undefined;
649
671
  }
650
672
 
@@ -663,7 +685,7 @@ export async function findInitialModel(options: {
663
685
  isContinuing: boolean;
664
686
  defaultProvider?: string;
665
687
  defaultModelId?: string;
666
- defaultThinkingSelector?: ThinkingLevel;
688
+ defaultThinkingSelector?: Effort;
667
689
  modelRegistry: ModelRegistry;
668
690
  }): Promise<InitialModelResult> {
669
691
  const {
@@ -678,7 +700,7 @@ export async function findInitialModel(options: {
678
700
  } = options;
679
701
 
680
702
  let model: Model<Api> | undefined;
681
- let thinkingLevel: ThinkingLevel = "off";
703
+ let thinkingLevel: Effort | undefined;
682
704
 
683
705
  // 1. CLI args take priority
684
706
  if (cliProvider && cliModel) {
@@ -687,16 +709,22 @@ export async function findInitialModel(options: {
687
709
  console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));
688
710
  process.exit(1);
689
711
  }
690
- return { model: found, thinkingLevel: "off", fallbackMessage: undefined };
712
+ return { model: found, thinkingLevel: undefined, fallbackMessage: undefined };
691
713
  }
692
714
 
693
715
  // 2. Use first model from scoped models (skip if continuing/resuming)
694
716
  if (scopedModels.length > 0 && !isContinuing) {
695
717
  const scoped = scopedModels[0];
696
- const scopedThinkingSelector = scoped.thinkingLevel ?? defaultThinkingSelector ?? "off";
718
+ const scopedThinkingSelector =
719
+ scoped.thinkingLevel === ThinkingLevel.Inherit
720
+ ? defaultThinkingSelector
721
+ : (scoped.thinkingLevel ?? defaultThinkingSelector);
697
722
  return {
698
723
  model: scoped.model,
699
- thinkingLevel: scopedThinkingSelector,
724
+ thinkingLevel:
725
+ scopedThinkingSelector === ThinkingLevel.Off
726
+ ? ThinkingLevel.Off
727
+ : clampThinkingLevelForModel(scoped.model, scopedThinkingSelector),
700
728
  fallbackMessage: undefined,
701
729
  };
702
730
  }
@@ -706,9 +734,7 @@ export async function findInitialModel(options: {
706
734
  const found = modelRegistry.find(defaultProvider, defaultModelId);
707
735
  if (found) {
708
736
  model = found;
709
- if (defaultThinkingSelector) {
710
- thinkingLevel = defaultThinkingSelector;
711
- }
737
+ thinkingLevel = clampThinkingLevelForModel(found, defaultThinkingSelector);
712
738
  return { model, thinkingLevel, fallbackMessage: undefined };
713
739
  }
714
740
  }
@@ -722,16 +748,16 @@ export async function findInitialModel(options: {
722
748
  const defaultId = defaultModelPerProvider[provider];
723
749
  const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
724
750
  if (match) {
725
- return { model: match, thinkingLevel: "off", fallbackMessage: undefined };
751
+ return { model: match, thinkingLevel: undefined, fallbackMessage: undefined };
726
752
  }
727
753
  }
728
754
 
729
755
  // If no default found, use first available
730
- return { model: availableModels[0], thinkingLevel: "off", fallbackMessage: undefined };
756
+ return { model: availableModels[0], thinkingLevel: undefined, fallbackMessage: undefined };
731
757
  }
732
758
 
733
759
  // 5. No model found
734
- return { model: undefined, thinkingLevel: "off", fallbackMessage: undefined };
760
+ return { model: undefined, thinkingLevel: undefined, fallbackMessage: undefined };
735
761
  }
736
762
 
737
763
  /**
@@ -1,4 +1,4 @@
1
- import { getAvailableThinkingLevels } from "@oh-my-pi/pi-ai";
1
+ import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
2
2
 
3
3
  /** Unified settings schema - single source of truth for all settings.
4
4
  * Unified settings schema - single source of truth for all settings.
@@ -192,7 +192,7 @@ export const SETTINGS_SCHEMA = {
192
192
  },
193
193
  defaultThinkingLevel: {
194
194
  type: "enum",
195
- values: getAvailableThinkingLevels(),
195
+ values: THINKING_EFFORTS,
196
196
  default: "high",
197
197
  ui: {
198
198
  tab: "agent",
@@ -345,9 +345,48 @@ export const SETTINGS_SCHEMA = {
345
345
  description: "Automatically compact context when it gets too large",
346
346
  },
347
347
  },
348
+ "compaction.strategy": {
349
+ type: "enum",
350
+ values: ["context-full", "handoff", "off"] as const,
351
+ default: "context-full",
352
+ ui: {
353
+ tab: "agent",
354
+ label: "Context-full strategy",
355
+ description: "Choose in-place context-full maintenance, auto-handoff, or disable auto maintenance (off)",
356
+ submenu: true,
357
+ },
358
+ },
359
+ "compaction.thresholdPercent": {
360
+ type: "number",
361
+ default: -1,
362
+ ui: {
363
+ tab: "agent",
364
+ label: "Context threshold",
365
+ description: "Percent threshold for context maintenance; set to Default to use legacy reserve-based behavior",
366
+ submenu: true,
367
+ },
368
+ },
369
+ "compaction.handoffSaveToDisk": {
370
+ type: "boolean",
371
+ default: false,
372
+ ui: {
373
+ tab: "agent",
374
+ label: "Save auto-handoff docs",
375
+ description: "Save generated handoff documents to markdown files for the auto-handoff flow",
376
+ },
377
+ },
348
378
  "compaction.reserveTokens": { type: "number", default: 16384 },
349
379
  "compaction.keepRecentTokens": { type: "number", default: 20000 },
350
380
  "compaction.autoContinue": { type: "boolean", default: true },
381
+ "compaction.remoteEnabled": {
382
+ type: "boolean",
383
+ default: true,
384
+ ui: {
385
+ tab: "agent",
386
+ label: "Remote compaction",
387
+ description: "Use remote compaction endpoints when available instead of local summarization",
388
+ },
389
+ },
351
390
  "compaction.remoteEndpoint": { type: "string", default: undefined },
352
391
 
353
392
  // ─────────────────────────────────────────────────────────────────────────
@@ -1186,6 +1225,17 @@ export const SETTINGS_SCHEMA = {
1186
1225
  submenu: true,
1187
1226
  },
1188
1227
  },
1228
+ serviceTier: {
1229
+ type: "enum",
1230
+ values: ["none", "auto", "default", "flex", "scale", "priority"] as const,
1231
+ default: "none",
1232
+ ui: {
1233
+ tab: "agent",
1234
+ label: "Service tier",
1235
+ description: "OpenAI processing priority (none = omit parameter)",
1236
+ submenu: true,
1237
+ },
1238
+ },
1189
1239
  } as const;
1190
1240
 
1191
1241
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1265,9 +1315,13 @@ export type StatusLineSeparatorStyle = SettingValue<"statusLine.separator">;
1265
1315
 
1266
1316
  export interface CompactionSettings {
1267
1317
  enabled: boolean;
1318
+ strategy: "context-full" | "handoff" | "off";
1319
+ thresholdPercent: number;
1268
1320
  reserveTokens: number;
1269
1321
  keepRecentTokens: number;
1322
+ handoffSaveToDisk: boolean;
1270
1323
  autoContinue: boolean;
1324
+ remoteEnabled: boolean;
1271
1325
  remoteEndpoint: string | undefined;
1272
1326
  }
1273
1327
 
@@ -1,13 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
4
- import { parseThinkingLevel } from "@oh-my-pi/pi-ai";
3
+ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
5
4
  import { FileType, glob } from "@oh-my-pi/pi-natives";
6
5
  import { CONFIG_DIR_NAME, tryParseJson } from "@oh-my-pi/pi-utils";
7
6
  import { readFile } from "../capability/fs";
8
7
  import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../capability/rule";
9
8
  import type { Skill, SkillFrontmatter } from "../capability/skill";
10
9
  import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
10
+ import { parseThinkingLevel } from "../thinking";
11
11
  import { parseFrontmatter } from "../utils/frontmatter";
12
12
 
13
13
  /**
@@ -206,7 +206,7 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
206
206
  return null;
207
207
  }
208
208
 
209
- let tools = parseArrayOrCSV(frontmatter.tools);
209
+ let tools = parseArrayOrCSV(frontmatter.tools)?.map(tool => tool.toLowerCase());
210
210
 
211
211
  // Subagents with explicit tool lists always need submit_result
212
212
  if (tools && !tools.includes("submit_result")) {
@@ -90,9 +90,11 @@ export type CustomToolSessionEvent =
90
90
  | {
91
91
  reason: "auto_compaction_start";
92
92
  trigger: "threshold" | "overflow";
93
+ action: "context-full" | "handoff";
93
94
  }
94
95
  | {
95
96
  reason: "auto_compaction_end";
97
+ action: "context-full" | "handoff";
96
98
  result: CompactionResult | undefined;
97
99
  aborted: boolean;
98
100
  willRetry: boolean;
@@ -4,7 +4,8 @@
4
4
  import type * as fs1 from "node:fs";
5
5
  import * as fs from "node:fs/promises";
6
6
  import * as path from "node:path";
7
- import type { ImageContent, Model, TextContent, ThinkingLevel } from "@oh-my-pi/pi-ai";
7
+ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
+ import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
8
9
  import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
9
10
  import type { KeyId } from "@oh-my-pi/pi-tui";
10
11
  import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
@@ -214,7 +215,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
214
215
  return this.runtime.setModel(model);
215
216
  }
216
217
 
217
- getThinkingLevel(): ThinkingLevel {
218
+ getThinkingLevel(): ThinkingLevel | undefined {
218
219
  return this.runtime.getThinkingLevel();
219
220
  }
220
221
 
@@ -7,7 +7,7 @@
7
7
  * - Register commands, keyboard shortcuts, and CLI flags
8
8
  * - Interact with the user via UI primitives
9
9
  */
10
- import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
10
+ import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
11
11
  import type {
12
12
  Api,
13
13
  AssistantMessageEvent,
@@ -19,7 +19,6 @@ import type {
19
19
  OAuthLoginCallbacks,
20
20
  SimpleStreamOptions,
21
21
  TextContent,
22
- ThinkingLevel,
23
22
  ToolResultMessage,
24
23
  } from "@oh-my-pi/pi-ai";
25
24
  import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
@@ -538,11 +537,13 @@ export interface ToolExecutionEndEvent {
538
537
  export interface AutoCompactionStartEvent {
539
538
  type: "auto_compaction_start";
540
539
  reason: "threshold" | "overflow";
540
+ action: "context-full" | "handoff";
541
541
  }
542
542
 
543
543
  /** Fired when auto-compaction ends */
544
544
  export interface AutoCompactionEndEvent {
545
545
  type: "auto_compaction_end";
546
+ action: "context-full" | "handoff";
546
547
  result: CompactionResult | undefined;
547
548
  aborted: boolean;
548
549
  willRetry: boolean;
@@ -1056,9 +1057,9 @@ export interface ExtensionAPI {
1056
1057
  setModel(model: Model): Promise<boolean>;
1057
1058
 
1058
1059
  /** Get current thinking level. */
1059
- getThinkingLevel(): ThinkingLevel;
1060
+ getThinkingLevel(): ThinkingLevel | undefined;
1060
1061
 
1061
- /** Set thinking level (clamped to model capabilities). */
1062
+ /** Set thinking level for the current session. */
1062
1063
  setThinkingLevel(level: ThinkingLevel): void;
1063
1064
 
1064
1065
  // =========================================================================
@@ -1084,11 +1085,11 @@ export interface ExtensionAPI {
1084
1085
  * id: "claude-sonnet-4@20250514",
1085
1086
  * name: "Claude Sonnet 4 (Vertex)",
1086
1087
  * reasoning: true,
1088
+ * thinking: { mode: "anthropic-adaptive", minLevel: "minimal", maxLevel: "high" },
1087
1089
  * input: ["text", "image"],
1088
1090
  * cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1089
1091
  * contextWindow: 200000,
1090
1092
  * maxTokens: 64000,
1091
- * }
1092
1093
  * ]
1093
1094
  * });
1094
1095
  *
@@ -1147,8 +1148,10 @@ export interface ProviderModelConfig {
1147
1148
  name: string;
1148
1149
  /** API type override for this model. */
1149
1150
  api?: Api;
1150
- /** Whether the model supports extended thinking. */
1151
+ /** Whether the model supports extended thinking at all. */
1151
1152
  reasoning: boolean;
1153
+ /** Optional canonical thinking capability metadata for per-model effort support. */
1154
+ thinking?: Model["thinking"];
1152
1155
  /** Supported input types. */
1153
1156
  input: ("text" | "image")[];
1154
1157
  /** Cost per million tokens. */
@@ -1216,7 +1219,7 @@ export type SetActiveToolsHandler = (toolNames: string[]) => Promise<void>;
1216
1219
 
1217
1220
  export type SetModelHandler = (model: Model) => Promise<boolean>;
1218
1221
 
1219
- export type GetThinkingLevelHandler = () => ThinkingLevel;
1222
+ export type GetThinkingLevelHandler = () => ThinkingLevel | undefined;
1220
1223
 
1221
1224
  export type SetThinkingLevelHandler = (level: ThinkingLevel, persist?: boolean) => void;
1222
1225
 
@@ -395,11 +395,13 @@ export interface TurnEndEvent {
395
395
  export interface AutoCompactionStartEvent {
396
396
  type: "auto_compaction_start";
397
397
  reason: "threshold" | "overflow";
398
+ action: "context-full" | "handoff";
398
399
  }
399
400
 
400
401
  /** Event data for auto_compaction_end event. */
401
402
  export interface AutoCompactionEndEvent {
402
403
  type: "auto_compaction_end";
404
+ action: "context-full" | "handoff";
403
405
  result: CompactionResult | undefined;
404
406
  aborted: boolean;
405
407
  willRetry: boolean;
package/src/main.ts CHANGED
@@ -10,7 +10,7 @@ import * as fs from "node:fs/promises";
10
10
  import * as os from "node:os";
11
11
  import * as path from "node:path";
12
12
  import { createInterface } from "node:readline/promises";
13
- import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
13
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
14
14
  import { $env, getProjectDir, logger, postmortem, setProjectDir, VERSION } from "@oh-my-pi/pi-utils";
15
15
  import chalk from "chalk";
16
16
  import type { Args } from "./cli/args";
@@ -334,11 +334,10 @@ async function buildSessionOptions(
334
334
  scopedModels: ScopedModel[],
335
335
  sessionManager: SessionManager | undefined,
336
336
  modelRegistry: ModelRegistry,
337
- ): Promise<{ options: CreateAgentSessionOptions; cliThinkingFromModel: boolean }> {
337
+ ): Promise<{ options: CreateAgentSessionOptions }> {
338
338
  const options: CreateAgentSessionOptions = {
339
339
  cwd: parsed.cwd ?? getProjectDir(),
340
340
  };
341
- let cliThinkingFromModel = false;
342
341
 
343
342
  // Auto-discover SYSTEM.md if no CLI system prompt provided
344
343
  const systemPromptSource = parsed.systemPrompt ?? discoverSystemPromptFile();
@@ -380,7 +379,6 @@ async function buildSessionOptions(
380
379
  settings.overrideModelRoles({ default: `${resolved.model.provider}/${resolved.model.id}` });
381
380
  if (!parsed.thinking && resolved.thinkingLevel) {
382
381
  options.thinkingLevel = resolved.thinkingLevel;
383
- cliThinkingFromModel = true;
384
382
  }
385
383
  }
386
384
  } else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
@@ -483,7 +481,7 @@ async function buildSessionOptions(
483
481
  options.additionalExtensionPaths = [];
484
482
  }
485
483
 
486
- return { options, cliThinkingFromModel };
484
+ return { options };
487
485
  }
488
486
 
489
487
  export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<void> {
@@ -618,7 +616,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
618
616
  sessionManager = await SessionManager.open(selectedPath);
619
617
  }
620
618
 
621
- const { options: sessionOptions, cliThinkingFromModel } = await logger.timeAsync("buildSessionOptions", () =>
619
+ const { options: sessionOptions } = await logger.timeAsync("buildSessionOptions", () =>
622
620
  buildSessionOptions(parsedArgs, scopedModels, sessionManager, modelRegistry),
623
621
  );
624
622
  sessionOptions.authStorage = authStorage;
@@ -692,21 +690,6 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
692
690
  process.exit(1);
693
691
  }
694
692
 
695
- // Clamp thinking level to model capabilities for CLI-provided thinking levels.
696
- // This covers both --thinking <level> and --model <pattern>:<thinking>.
697
- const cliThinkingOverride = parsedArgs.thinking !== undefined || cliThinkingFromModel;
698
- if (session.model && cliThinkingOverride) {
699
- let effectiveThinking = session.thinkingLevel;
700
- if (!session.model.reasoning) {
701
- effectiveThinking = "off";
702
- } else if (effectiveThinking === "xhigh" && !supportsXhigh(session.model)) {
703
- effectiveThinking = "high";
704
- }
705
- if (effectiveThinking !== session.thinkingLevel) {
706
- session.setThinkingLevel(effectiveThinking);
707
- }
708
- }
709
-
710
693
  if (mode === "rpc") {
711
694
  await runRpcMode(session);
712
695
  } else if (isInteractive) {
@@ -717,7 +700,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
717
700
  if (scopedModelsForDisplay.length > 0) {
718
701
  const modelList = scopedModelsForDisplay
719
702
  .map(scopedModel => {
720
- const thinkingStr = scopedModel.thinkingLevel !== "off" ? `:${scopedModel.thinkingLevel}` : "";
703
+ const thinkingStr = !scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : "";
721
704
  return `${scopedModel.model.id}${thinkingStr}`;
722
705
  })
723
706
  .join(", ");
@@ -3,7 +3,7 @@ import type * as fsNode from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
- import { completeSimple, type Model } from "@oh-my-pi/pi-ai";
6
+ import { completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai";
7
7
  import { getAgentDbPath, logger, parseJsonlLenient } from "@oh-my-pi/pi-utils";
8
8
  import type { ModelRegistry } from "../config/model-registry";
9
9
  import { parseModelString } from "../config/model-resolver";
@@ -583,7 +583,11 @@ async function runStage1Job(options: {
583
583
  systemPrompt: stageOneSystemTemplate,
584
584
  messages: [{ role: "user", content: [{ type: "text", text: inputPrompt }], timestamp: Date.now() }],
585
585
  },
586
- { apiKey, maxTokens: Math.max(1024, Math.min(4096, Math.floor(modelMaxTokens * 0.2))), reasoning: "low" },
586
+ {
587
+ apiKey,
588
+ maxTokens: Math.max(1024, Math.min(4096, Math.floor(modelMaxTokens * 0.2))),
589
+ reasoning: Effort.Low,
590
+ },
587
591
  );
588
592
 
589
593
  if (response.stopReason === "error") {
@@ -709,7 +713,7 @@ async function runConsolidationModel(options: { memoryRoot: string; model: Model
709
713
  {
710
714
  messages: [{ role: "user", content: [{ type: "text", text: input }], timestamp: Date.now() }],
711
715
  },
712
- { apiKey, maxTokens: 8192, reasoning: "medium" },
716
+ { apiKey, maxTokens: 8192, reasoning: Effort.Medium },
713
717
  );
714
718
  if (response.stopReason === "error") {
715
719
  throw new Error(response.errorMessage || "phase2 model error");
@@ -1,10 +1,12 @@
1
1
  import * as fs from "node:fs";
2
+ import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
3
  import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
3
4
  import { formatNumber, getProjectDir } from "@oh-my-pi/pi-utils";
4
5
  import { theme } from "../../modes/theme/theme";
5
6
  import type { AgentSession } from "../../session/agent-session";
6
7
  import { shortenPath } from "../../tools/render-utils";
7
8
  import { findGitHeadPathAsync, sanitizeStatusText } from "../shared";
9
+ import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
8
10
 
9
11
  /**
10
12
  * Footer component that shows pwd, token stats, and context usage
@@ -197,10 +199,10 @@ export class FooterComponent implements Component {
197
199
  contextPercent === "?"
198
200
  ? `?/${formatNumber(contextWindow)}${autoIndicator}`
199
201
  : `${contextPercent}%/${formatNumber(contextWindow)}${autoIndicator}`;
200
- if (contextPercentValue > 90) {
201
- contextPercentStr = theme.fg("error", contextPercentDisplay);
202
- } else if (contextPercentValue > 70) {
203
- contextPercentStr = theme.fg("warning", contextPercentDisplay);
202
+ if (contextUsage?.percent !== null && contextUsage?.percent !== undefined) {
203
+ const color = getContextUsageThemeColor(getContextUsageLevel(contextPercentValue, contextWindow));
204
+ contextPercentStr =
205
+ color === "statusLineContext" ? contextPercentDisplay : theme.fg(color, contextPercentDisplay);
204
206
  } else {
205
207
  contextPercentStr = contextPercentDisplay;
206
208
  }
@@ -211,11 +213,11 @@ export class FooterComponent implements Component {
211
213
  // Add model name on the right side, plus thinking level if model supports it
212
214
  const modelName = state.model?.id || "no-model";
213
215
 
214
- // Add thinking level hint if model supports reasoning and thinking is enabled
216
+ // Add thinking level hint when the current model advertises supported efforts
215
217
  let rightSide = modelName;
216
- if (state.model?.reasoning) {
217
- const thinkingLevel = state.thinkingLevel || "off";
218
- if (thinkingLevel !== "off") {
218
+ if (state.model?.thinking) {
219
+ const thinkingLevel = state.thinkingLevel ?? ThinkingLevel.Off;
220
+ if (thinkingLevel !== ThinkingLevel.Off) {
219
221
  rightSide = `${modelName} • ${thinkingLevel}`;
220
222
  }
221
223
  }