@diegopetrucci/pi-oracle 0.1.7 → 0.1.9
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/README.md +23 -6
- package/index.ts +299 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ It adds an `oracle` tool that spins up a separate read-only pi subprocess and se
|
|
|
11
11
|
- creates an isolated read-only subprocess
|
|
12
12
|
- auto-picks the strongest reasoning model on the current provider
|
|
13
13
|
- uses provider-specific hardcoded rankings first, then a heuristic fallback
|
|
14
|
-
-
|
|
14
|
+
- requests `xhigh` by default for reasoning models, then clamps to the model-supported thinking level
|
|
15
15
|
- defaults to `read,grep,find,ls`
|
|
16
16
|
- can optionally allow non-mutating `bash` inspection
|
|
17
17
|
- shows a live oracle status line and widget while the subprocess is running
|
|
@@ -27,7 +27,7 @@ By default, the extension:
|
|
|
27
27
|
4. tries a provider-specific hardcoded priority list first
|
|
28
28
|
5. falls back to a heuristic that favors stronger tiers like `opus`, `pro`, newer versions, and penalizes `mini`, `flash`, `haiku`, `spark`, etc.
|
|
29
29
|
|
|
30
|
-
The hardcoded rankings now cover pi's built-in provider set, including
|
|
30
|
+
The hardcoded rankings now cover pi's built-in provider set, including Together; see the provider matrix for the current provider-by-provider top picks.
|
|
31
31
|
|
|
32
32
|
If no reasoning model exists on the current provider, it falls back to the best available model on that provider.
|
|
33
33
|
|
|
@@ -35,9 +35,10 @@ If no reasoning model exists on the current provider, it falls back to the best
|
|
|
35
35
|
|
|
36
36
|
Yes — the extension explicitly sets the oracle reasoning level.
|
|
37
37
|
|
|
38
|
-
- reasoning models
|
|
38
|
+
- reasoning models request `xhigh` by default, then use the Pi-compatible effective thinking level supported by the matched model
|
|
39
39
|
- non-reasoning models default to `off`
|
|
40
|
-
- you can override it with the tool's optional `thinkingLevel` parameter
|
|
40
|
+
- you can override it with the tool's optional `thinkingLevel` parameter; matched models still clamp unsupported overrides and report the effective level
|
|
41
|
+
- you can persist a default thinking level with `/oracle thinking <level>` so future automatic oracle tool calls use it when the agent does not pass a per-call override
|
|
41
42
|
|
|
42
43
|
Use `/oracle-model` inside pi to see what it would pick right now.
|
|
43
44
|
|
|
@@ -81,12 +82,28 @@ Ask pi normally, for example:
|
|
|
81
82
|
|
|
82
83
|
The main agent can call the tool directly.
|
|
83
84
|
|
|
85
|
+
## User defaults
|
|
86
|
+
|
|
87
|
+
Use `/oracle` to set persisted defaults that apply to future oracle tool calls, including calls the agent launches automatically without per-call overrides.
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
/oracle status
|
|
91
|
+
/oracle model anthropic/claude-opus-4-5
|
|
92
|
+
/oracle thinking high
|
|
93
|
+
/oracle thinking auto
|
|
94
|
+
/oracle clear model
|
|
95
|
+
/oracle clear thinking
|
|
96
|
+
/oracle clear
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Tool-call parameters still win over these defaults. `auto` clears the configured default and restores the built-in selection behavior. Preferences are saved under pi's agent directory in `extensions/oracle.json`.
|
|
100
|
+
|
|
84
101
|
## Tool parameters
|
|
85
102
|
|
|
86
103
|
- `task` - required prompt for the oracle
|
|
87
104
|
- `includeBash` - optional, adds `bash` for non-mutating inspection
|
|
88
|
-
- `model` - optional explicit model override
|
|
89
|
-
- `thinkingLevel` - optional reasoning/thinking override
|
|
105
|
+
- `model` - optional explicit model override; falls back to the `/oracle model` default, then auto-selection
|
|
106
|
+
- `thinkingLevel` - optional reasoning/thinking override; falls back to the `/oracle thinking` default, then built-in defaults
|
|
90
107
|
- `cwd` - optional working directory override
|
|
91
108
|
|
|
92
109
|
## Notes
|
package/index.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
4
5
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
5
|
-
import { getMarkdownTheme, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { getAgentDir, getMarkdownTheme, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
7
8
|
import { Type } from "typebox";
|
|
8
9
|
|
|
9
10
|
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
11
|
+
type ThinkingLevelMap = Partial<Record<ThinkingLevel, unknown | null>>;
|
|
10
12
|
|
|
11
13
|
type PiModel = {
|
|
12
14
|
provider: string;
|
|
@@ -15,6 +17,7 @@ type PiModel = {
|
|
|
15
17
|
reasoning?: boolean;
|
|
16
18
|
contextWindow?: number;
|
|
17
19
|
maxTokens?: number;
|
|
20
|
+
thinkingLevelMap?: ThinkingLevelMap;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
interface UsageStats {
|
|
@@ -33,6 +36,8 @@ interface OracleSelection {
|
|
|
33
36
|
modelId: string;
|
|
34
37
|
modelName?: string;
|
|
35
38
|
thinkingLevel: ThinkingLevel;
|
|
39
|
+
requestedThinkingLevel?: ThinkingLevel;
|
|
40
|
+
thinkingLevelClamped?: boolean;
|
|
36
41
|
autoSelected: boolean;
|
|
37
42
|
selectionReason: string;
|
|
38
43
|
}
|
|
@@ -54,6 +59,11 @@ interface OracleUiRun {
|
|
|
54
59
|
preview?: string;
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
interface OraclePreferences {
|
|
63
|
+
model?: string;
|
|
64
|
+
thinkingLevel?: ThinkingLevel;
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
const READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
58
68
|
const READ_ONLY_PLUS_BASH_TOOLS = [...READ_ONLY_TOOLS, "bash"];
|
|
59
69
|
const DEFAULT_THINKING_LEVEL: ThinkingLevel = "xhigh";
|
|
@@ -61,6 +71,7 @@ const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as
|
|
|
61
71
|
const COLLAPSED_LINE_LIMIT = 8;
|
|
62
72
|
const ORACLE_STATUS_ID = "oracle";
|
|
63
73
|
const ORACLE_WIDGET_ID = "oracle";
|
|
74
|
+
const ORACLE_CONFIG_FILE = "oracle.json";
|
|
64
75
|
|
|
65
76
|
const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
66
77
|
"amazon-bedrock": [
|
|
@@ -294,6 +305,20 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
294
305
|
"minimax/minimax-m2.1",
|
|
295
306
|
"z-ai/glm-5.1",
|
|
296
307
|
],
|
|
308
|
+
together: [
|
|
309
|
+
"deepseek-ai/DeepSeek-V4-Pro",
|
|
310
|
+
"zai-org/GLM-5.1",
|
|
311
|
+
"moonshotai/Kimi-K2.6",
|
|
312
|
+
"Qwen/Qwen3.6-Plus",
|
|
313
|
+
"MiniMaxAI/MiniMax-M2.7",
|
|
314
|
+
"Qwen/Qwen3.5-397B-A17B",
|
|
315
|
+
"Qwen/Qwen3-Coder-Next-FP8",
|
|
316
|
+
"Qwen/Qwen3-235B-A22B-Instruct-2507-tput",
|
|
317
|
+
"openai/gpt-oss-120b",
|
|
318
|
+
"moonshotai/Kimi-K2.5",
|
|
319
|
+
"deepseek-ai/DeepSeek-V3-1",
|
|
320
|
+
"MiniMaxAI/MiniMax-M2.5",
|
|
321
|
+
],
|
|
297
322
|
"vercel-ai-gateway": [
|
|
298
323
|
"anthropic/claude-opus-4.7",
|
|
299
324
|
"anthropic/claude-opus-4.6",
|
|
@@ -393,7 +418,7 @@ const OracleParams = Type.Object({
|
|
|
393
418
|
thinkingLevel: Type.Optional(
|
|
394
419
|
StringEnum(THINKING_LEVELS, {
|
|
395
420
|
description:
|
|
396
|
-
"Optional reasoning level override for the oracle subprocess.
|
|
421
|
+
"Optional reasoning level override for the oracle subprocess. Defaults request xhigh for reasoning models and off for non-reasoning models, then clamp to matched model capabilities.",
|
|
397
422
|
}),
|
|
398
423
|
),
|
|
399
424
|
cwd: Type.Optional(Type.String({ description: "Optional working directory for the oracle subprocess." })),
|
|
@@ -411,6 +436,74 @@ function createEmptyUsage(): UsageStats {
|
|
|
411
436
|
};
|
|
412
437
|
}
|
|
413
438
|
|
|
439
|
+
function getOracleConfigPath(): string {
|
|
440
|
+
return path.join(getAgentDir(), "extensions", ORACLE_CONFIG_FILE);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function parseThinkingLevel(value: unknown): ThinkingLevel | undefined {
|
|
444
|
+
if (typeof value !== "string") return undefined;
|
|
445
|
+
const normalized = value.trim().toLowerCase();
|
|
446
|
+
return (THINKING_LEVELS as readonly string[]).includes(normalized) ? (normalized as ThinkingLevel) : undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function normalizeModelPreference(value: unknown): string | undefined {
|
|
450
|
+
if (typeof value !== "string") return undefined;
|
|
451
|
+
const trimmed = value.trim();
|
|
452
|
+
return trimmed ? trimmed : undefined;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function parseModelPreference(value: unknown): { model?: string; thinkingLevel?: ThinkingLevel } {
|
|
456
|
+
const model = normalizeModelPreference(value);
|
|
457
|
+
if (!model) return {};
|
|
458
|
+
const match = model.match(/^(.*):(off|minimal|low|medium|high|xhigh)$/i);
|
|
459
|
+
if (!match?.[1]) return { model };
|
|
460
|
+
return { model: match[1], thinkingLevel: parseThinkingLevel(match[2]) };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function readOraclePreferences(): Promise<OraclePreferences> {
|
|
464
|
+
try {
|
|
465
|
+
const raw = await fs.readFile(getOracleConfigPath(), "utf8");
|
|
466
|
+
const parsed = JSON.parse(raw) as {
|
|
467
|
+
model?: unknown;
|
|
468
|
+
defaultModel?: unknown;
|
|
469
|
+
thinkingLevel?: unknown;
|
|
470
|
+
defaultThinkingLevel?: unknown;
|
|
471
|
+
};
|
|
472
|
+
return {
|
|
473
|
+
model: parseModelPreference(parsed.model).model ?? parseModelPreference(parsed.defaultModel).model,
|
|
474
|
+
thinkingLevel:
|
|
475
|
+
parseThinkingLevel(parsed.thinkingLevel) ??
|
|
476
|
+
parseThinkingLevel(parsed.defaultThinkingLevel) ??
|
|
477
|
+
parseModelPreference(parsed.model).thinkingLevel ??
|
|
478
|
+
parseModelPreference(parsed.defaultModel).thinkingLevel,
|
|
479
|
+
};
|
|
480
|
+
} catch {
|
|
481
|
+
return {};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function writeOraclePreferences(preferences: OraclePreferences): Promise<void> {
|
|
486
|
+
const configPath = getOracleConfigPath();
|
|
487
|
+
const config = {
|
|
488
|
+
...(preferences.model ? { model: preferences.model } : {}),
|
|
489
|
+
...(preferences.thinkingLevel ? { thinkingLevel: preferences.thinkingLevel } : {}),
|
|
490
|
+
updatedAt: new Date().toISOString(),
|
|
491
|
+
};
|
|
492
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
493
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function formatOraclePreferences(preferences: OraclePreferences): string {
|
|
497
|
+
const model = preferences.model ?? "auto";
|
|
498
|
+
const thinkingLevel = preferences.thinkingLevel ?? "auto";
|
|
499
|
+
return `Oracle defaults: model=${model}, thinkingLevel=${thinkingLevel}. Config: ${getOracleConfigPath()}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function notifyCommand(ctx: any, message: string, kind = "info"): void {
|
|
503
|
+
if (ctx.hasUI && ctx.ui) ctx.ui.notify(message, kind);
|
|
504
|
+
else console.log(message);
|
|
505
|
+
}
|
|
506
|
+
|
|
414
507
|
function formatTokens(count: number): string {
|
|
415
508
|
if (count < 1000) return count.toString();
|
|
416
509
|
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
@@ -516,7 +609,7 @@ function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
|
516
609
|
return { command: process.execPath, args: [currentScript, ...args] };
|
|
517
610
|
}
|
|
518
611
|
|
|
519
|
-
const execName = basename(process.execPath).toLowerCase();
|
|
612
|
+
const execName = path.basename(process.execPath).toLowerCase();
|
|
520
613
|
const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
|
|
521
614
|
if (!isGenericRuntime) {
|
|
522
615
|
return { command: process.execPath, args };
|
|
@@ -530,9 +623,48 @@ function withThinking(modelRef: string, thinkingLevel: ThinkingLevel): string {
|
|
|
530
623
|
return `${modelRef}:${thinkingLevel}`;
|
|
531
624
|
}
|
|
532
625
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
626
|
+
// Keep these local so the extension stays compatible with older pi peer installs that do not export clamp helpers.
|
|
627
|
+
function isThinkingLevelSupported(model: PiModel, level: ThinkingLevel): boolean {
|
|
628
|
+
if (!model.reasoning) return level === "off";
|
|
629
|
+
|
|
630
|
+
const map = model.thinkingLevelMap;
|
|
631
|
+
if (level === "xhigh") {
|
|
632
|
+
return !!map && Object.prototype.hasOwnProperty.call(map, "xhigh") && map.xhigh != null;
|
|
633
|
+
}
|
|
634
|
+
return map?.[level] !== null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function clampThinkingLevel(model: PiModel, requested: ThinkingLevel): ThinkingLevel {
|
|
638
|
+
if (isThinkingLevelSupported(model, requested)) return requested;
|
|
639
|
+
|
|
640
|
+
const requestedIndex = THINKING_LEVELS.indexOf(requested);
|
|
641
|
+
for (let index = requestedIndex + 1; index < THINKING_LEVELS.length; index++) {
|
|
642
|
+
const level = THINKING_LEVELS[index];
|
|
643
|
+
if (isThinkingLevelSupported(model, level)) return level;
|
|
644
|
+
}
|
|
645
|
+
for (let index = requestedIndex - 1; index >= 0; index--) {
|
|
646
|
+
const level = THINKING_LEVELS[index];
|
|
647
|
+
if (isThinkingLevelSupported(model, level)) return level;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return "off";
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function resolveThinkingLevel(
|
|
654
|
+
model: PiModel | undefined,
|
|
655
|
+
override: ThinkingLevel | undefined,
|
|
656
|
+
): { requested: ThinkingLevel; effective: ThinkingLevel; clamped: boolean } {
|
|
657
|
+
const requested = override ?? (model?.reasoning ? DEFAULT_THINKING_LEVEL : "off");
|
|
658
|
+
const effective = model ? clampThinkingLevel(model, requested) : requested;
|
|
659
|
+
return { requested, effective, clamped: effective !== requested };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function appendThinkingLevelClampReason(
|
|
663
|
+
reason: string,
|
|
664
|
+
resolution: { requested: ThinkingLevel; effective: ThinkingLevel; clamped: boolean },
|
|
665
|
+
): string {
|
|
666
|
+
if (!resolution.clamped) return reason;
|
|
667
|
+
return `${reason} Requested thinking level ${resolution.requested} was clamped to ${resolution.effective} based on the matched model's capabilities.`;
|
|
536
668
|
}
|
|
537
669
|
|
|
538
670
|
async function findAvailableModel(
|
|
@@ -599,9 +731,11 @@ async function selectOracleModel(
|
|
|
599
731
|
|
|
600
732
|
const preferred = selectPreferredModel(candidates, providerForPreferences);
|
|
601
733
|
const winner = preferred ?? [...candidates].sort((a, b) => rankModel(b) - rankModel(a))[0];
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
: reason
|
|
734
|
+
const thinking = resolveThinkingLevel(winner, thinkingLevelOverride);
|
|
735
|
+
const selectionReason = appendThinkingLevelClampReason(
|
|
736
|
+
preferred ? `Selected ${winner.id} via the hardcoded preference list for ${winner.provider}.` : reason,
|
|
737
|
+
thinking,
|
|
738
|
+
);
|
|
605
739
|
|
|
606
740
|
return {
|
|
607
741
|
ok: true,
|
|
@@ -610,7 +744,8 @@ async function selectOracleModel(
|
|
|
610
744
|
provider: winner.provider,
|
|
611
745
|
modelId: winner.id,
|
|
612
746
|
modelName: winner.name,
|
|
613
|
-
thinkingLevel:
|
|
747
|
+
thinkingLevel: thinking.effective,
|
|
748
|
+
...(thinking.clamped ? { requestedThinkingLevel: thinking.requested, thinkingLevelClamped: true } : {}),
|
|
614
749
|
autoSelected: true,
|
|
615
750
|
selectionReason,
|
|
616
751
|
},
|
|
@@ -833,26 +968,147 @@ function renderCollapsedText(text: string, lineLimit = COLLAPSED_LINE_LIMIT): st
|
|
|
833
968
|
|
|
834
969
|
export default function oracleExtension(pi: ExtensionAPI) {
|
|
835
970
|
const activeRuns = new Map<string, OracleUiRun>();
|
|
971
|
+
let preferences: OraclePreferences = {};
|
|
836
972
|
|
|
837
973
|
pi.on("session_start", async (_event, ctx) => {
|
|
974
|
+
preferences = await readOraclePreferences();
|
|
838
975
|
activeRuns.clear();
|
|
839
976
|
updateOracleUi(ctx, activeRuns);
|
|
840
977
|
});
|
|
841
978
|
|
|
979
|
+
pi.registerCommand("oracle", {
|
|
980
|
+
description: "Configure Oracle default model and thinking level for future oracle tool calls",
|
|
981
|
+
getArgumentCompletions: (prefix) => {
|
|
982
|
+
const parts = prefix.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
983
|
+
const first = parts[0] ?? "";
|
|
984
|
+
if (parts.length <= 1) {
|
|
985
|
+
const commands = ["status", "model", "thinking", "clear"];
|
|
986
|
+
const matches = commands.filter((command) => command.startsWith(first));
|
|
987
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
988
|
+
}
|
|
989
|
+
if (parts[0] === "thinking") {
|
|
990
|
+
const query = parts[1] ?? "";
|
|
991
|
+
const values = ["auto", ...THINKING_LEVELS];
|
|
992
|
+
const matches = values.filter((value) => value.startsWith(query));
|
|
993
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
994
|
+
}
|
|
995
|
+
if (parts[0] === "clear") {
|
|
996
|
+
const query = parts[1] ?? "";
|
|
997
|
+
const values = ["all", "model", "thinking"];
|
|
998
|
+
const matches = values.filter((value) => value.startsWith(query));
|
|
999
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1002
|
+
},
|
|
1003
|
+
handler: async (args, ctx) => {
|
|
1004
|
+
const raw = args.trim();
|
|
1005
|
+
const tokens = raw ? raw.split(/\s+/) : [];
|
|
1006
|
+
const [command = "status", ...rest] = tokens;
|
|
1007
|
+
const action = command.toLowerCase();
|
|
1008
|
+
const configPath = getOracleConfigPath();
|
|
1009
|
+
|
|
1010
|
+
const save = async (next: OraclePreferences): Promise<string | undefined> => {
|
|
1011
|
+
preferences = next;
|
|
1012
|
+
try {
|
|
1013
|
+
await writeOraclePreferences(preferences);
|
|
1014
|
+
return undefined;
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1017
|
+
return `Preference changed for this process, but could not save ${configPath}: ${message}`;
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
if (action === "status" || action === "show") {
|
|
1022
|
+
notifyCommand(ctx, formatOraclePreferences(preferences));
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (action === "model") {
|
|
1027
|
+
const model = rest.join(" ").trim();
|
|
1028
|
+
const normalizedModelAction = model.toLowerCase();
|
|
1029
|
+
if (!model) {
|
|
1030
|
+
notifyCommand(ctx, "Usage: /oracle model <provider/model|auto>", "warning");
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (normalizedModelAction === "auto" || normalizedModelAction === "clear" || normalizedModelAction === "default") {
|
|
1034
|
+
const warning = await save({ ...preferences, model: undefined });
|
|
1035
|
+
notifyCommand(ctx, `Oracle default model cleared; future oracle calls will auto-select. ${warning ?? formatOraclePreferences(preferences)}`, warning ? "warning" : "info");
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const parsedModel = parseModelPreference(model);
|
|
1039
|
+
const next = {
|
|
1040
|
+
...preferences,
|
|
1041
|
+
model: parsedModel.model,
|
|
1042
|
+
thinkingLevel: parsedModel.thinkingLevel ?? preferences.thinkingLevel,
|
|
1043
|
+
};
|
|
1044
|
+
const warning = await save(next);
|
|
1045
|
+
notifyCommand(ctx, `Oracle default model set to ${parsedModel.model}. ${warning ?? formatOraclePreferences(preferences)}`, warning ? "warning" : "info");
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (action === "thinking" || action === "think" || action === "thinking-level") {
|
|
1050
|
+
const value = rest[0]?.trim().toLowerCase();
|
|
1051
|
+
if (!value) {
|
|
1052
|
+
notifyCommand(ctx, `Usage: /oracle thinking ${THINKING_LEVELS.join(" | ")} | auto`, "warning");
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (value === "auto" || value === "clear" || value === "default") {
|
|
1056
|
+
const warning = await save({ ...preferences, thinkingLevel: undefined });
|
|
1057
|
+
notifyCommand(ctx, `Oracle default thinking level cleared; future oracle calls will use built-in defaults. ${warning ?? formatOraclePreferences(preferences)}`, warning ? "warning" : "info");
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const thinkingLevel = parseThinkingLevel(value);
|
|
1061
|
+
if (!thinkingLevel) {
|
|
1062
|
+
notifyCommand(ctx, `Usage: /oracle thinking ${THINKING_LEVELS.join(" | ")} | auto`, "warning");
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const warning = await save({ ...preferences, thinkingLevel });
|
|
1066
|
+
notifyCommand(ctx, `Oracle default thinking level set to ${thinkingLevel}. ${warning ?? formatOraclePreferences(preferences)}`, warning ? "warning" : "info");
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (action === "clear" || action === "reset") {
|
|
1071
|
+
const target = rest[0]?.trim().toLowerCase() || "all";
|
|
1072
|
+
let next: OraclePreferences;
|
|
1073
|
+
if (target === "all") next = {};
|
|
1074
|
+
else if (target === "model") next = { ...preferences, model: undefined };
|
|
1075
|
+
else if (target === "thinking" || target === "thinking-level") next = { ...preferences, thinkingLevel: undefined };
|
|
1076
|
+
else {
|
|
1077
|
+
notifyCommand(ctx, "Usage: /oracle clear [all|model|thinking]", "warning");
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const warning = await save(next);
|
|
1081
|
+
notifyCommand(ctx, `Oracle defaults cleared (${target}). ${warning ?? formatOraclePreferences(preferences)}`, warning ? "warning" : "info");
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
notifyCommand(ctx, "Usage: /oracle status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]", "warning");
|
|
1086
|
+
},
|
|
1087
|
+
});
|
|
1088
|
+
|
|
842
1089
|
pi.registerCommand("oracle-model", {
|
|
843
1090
|
description: "Show which model the oracle would use right now",
|
|
844
1091
|
handler: async (_args, ctx) => {
|
|
845
|
-
const
|
|
1092
|
+
const defaultModel = parseModelPreference(preferences.model);
|
|
1093
|
+
if (defaultModel.model) {
|
|
1094
|
+
const matched = await findAvailableModel(ctx, defaultModel.model);
|
|
1095
|
+
const thinking = resolveThinkingLevel(matched, preferences.thinkingLevel ?? defaultModel.thinkingLevel);
|
|
1096
|
+
const modelRef = matched ? `${matched.provider}/${matched.id}` : defaultModel.model;
|
|
1097
|
+
const suffix = matched
|
|
1098
|
+
? appendThinkingLevelClampReason("Configured default oracle model is active.", thinking)
|
|
1099
|
+
: "Configured default oracle model is active, but it was not matched against the authenticated model list.";
|
|
1100
|
+
notifyCommand(ctx, `Oracle: ${modelRef} (${thinking.effective}) — ${suffix}`);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const selectionResult = await selectOracleModel(ctx, preferences.thinkingLevel);
|
|
846
1104
|
if (!selectionResult.ok) {
|
|
847
|
-
|
|
848
|
-
else console.log(selectionResult.error);
|
|
1105
|
+
notifyCommand(ctx, selectionResult.error, "error");
|
|
849
1106
|
return;
|
|
850
1107
|
}
|
|
851
1108
|
|
|
852
1109
|
const { selection } = selectionResult;
|
|
853
1110
|
const message = `Oracle: ${selection.modelRef} (${selection.thinkingLevel}) — ${selection.selectionReason}`;
|
|
854
|
-
|
|
855
|
-
else console.log(message);
|
|
1111
|
+
notifyCommand(ctx, message);
|
|
856
1112
|
},
|
|
857
1113
|
});
|
|
858
1114
|
|
|
@@ -867,11 +1123,16 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
867
1123
|
"Use this tool sparingly when you want a second opinion, deeper analysis, code review, debugging help, or a higher-reasoning pass.",
|
|
868
1124
|
"Do not use it for routine low-value work; it is slower than the main agent.",
|
|
869
1125
|
"The oracle is read-only by default. Set includeBash only when shell-based inspection is genuinely useful.",
|
|
870
|
-
"The oracle
|
|
1126
|
+
"The oracle requests xhigh by default for reasoning models; defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
|
|
871
1127
|
],
|
|
872
1128
|
parameters: OracleParams,
|
|
873
1129
|
|
|
874
1130
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1131
|
+
const explicitModel = parseModelPreference(params.model);
|
|
1132
|
+
const defaultModel = parseModelPreference(preferences.model);
|
|
1133
|
+
const configuredModel = explicitModel.model ?? defaultModel.model;
|
|
1134
|
+
const configuredThinkingLevel =
|
|
1135
|
+
params.thinkingLevel ?? explicitModel.thinkingLevel ?? preferences.thinkingLevel ?? defaultModel.thinkingLevel;
|
|
875
1136
|
const uiRun: OracleUiRun = {
|
|
876
1137
|
task: params.task,
|
|
877
1138
|
includeBash: params.includeBash ?? false,
|
|
@@ -882,25 +1143,38 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
882
1143
|
|
|
883
1144
|
try {
|
|
884
1145
|
let selection: OracleSelection;
|
|
885
|
-
if (
|
|
886
|
-
const modelRef =
|
|
1146
|
+
if (configuredModel) {
|
|
1147
|
+
const modelRef = configuredModel;
|
|
887
1148
|
const matched = await findAvailableModel(ctx, modelRef);
|
|
888
1149
|
const provider =
|
|
889
1150
|
matched?.provider ?? (modelRef.includes("/") ? modelRef.split("/")[0] : ctx.model?.provider ?? "unknown");
|
|
890
1151
|
const modelId = matched?.id ?? (modelRef.includes("/") ? modelRef.split("/").slice(1).join("/") : modelRef);
|
|
1152
|
+
const thinking = resolveThinkingLevel(matched, configuredThinkingLevel);
|
|
1153
|
+
const usedToolOverride = !!explicitModel.model;
|
|
1154
|
+
const selectionReason = matched
|
|
1155
|
+
? appendThinkingLevelClampReason(
|
|
1156
|
+
usedToolOverride
|
|
1157
|
+
? "Used the explicit model override provided in the tool call."
|
|
1158
|
+
: "Used the configured default oracle model.",
|
|
1159
|
+
thinking,
|
|
1160
|
+
)
|
|
1161
|
+
: usedToolOverride
|
|
1162
|
+
? "Used the explicit model override provided in the tool call. The model was not matched against the authenticated model list, so the reasoning level fallback was applied."
|
|
1163
|
+
: "Used the configured default oracle model. The model was not matched against the authenticated model list, so the reasoning level fallback was applied.";
|
|
891
1164
|
selection = {
|
|
892
1165
|
modelRef: matched ? `${matched.provider}/${matched.id}` : modelRef,
|
|
893
1166
|
provider,
|
|
894
1167
|
modelId,
|
|
895
1168
|
modelName: matched?.name,
|
|
896
|
-
thinkingLevel:
|
|
1169
|
+
thinkingLevel: thinking.effective,
|
|
1170
|
+
...(matched && thinking.clamped
|
|
1171
|
+
? { requestedThinkingLevel: thinking.requested, thinkingLevelClamped: true }
|
|
1172
|
+
: {}),
|
|
897
1173
|
autoSelected: false,
|
|
898
|
-
selectionReason
|
|
899
|
-
? "Used the explicit model override provided in the tool call."
|
|
900
|
-
: "Used the explicit model override provided in the tool call. The model was not matched against the authenticated model list, so the reasoning level fallback was applied.",
|
|
1174
|
+
selectionReason,
|
|
901
1175
|
};
|
|
902
1176
|
} else {
|
|
903
|
-
const selectionResult = await selectOracleModel(ctx,
|
|
1177
|
+
const selectionResult = await selectOracleModel(ctx, configuredThinkingLevel);
|
|
904
1178
|
if (!selectionResult.ok) {
|
|
905
1179
|
return {
|
|
906
1180
|
content: [{ type: "text", text: selectionResult.error }],
|
|
@@ -909,7 +1183,7 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
909
1183
|
provider: ctx.model?.provider ?? "unknown",
|
|
910
1184
|
modelId: "",
|
|
911
1185
|
modelName: undefined,
|
|
912
|
-
thinkingLevel:
|
|
1186
|
+
thinkingLevel: configuredThinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
913
1187
|
autoSelected: true,
|
|
914
1188
|
selectionReason: selectionResult.error,
|
|
915
1189
|
includeBash: params.includeBash ?? false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-oracle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "oracle", "reasoning", "subagent"],
|
|
6
6
|
"license": "MIT",
|