@diegopetrucci/pi-oracle 0.1.11 → 0.1.13

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.ts +49 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -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 Together; see the provider matrix for the current provider-by-provider top picks.
30
+ The hardcoded rankings now cover pi's built-in provider set, including Claude Fable on Anthropic-compatible providers and 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
 
package/index.ts CHANGED
@@ -75,6 +75,7 @@ const ORACLE_CONFIG_FILE = "oracle.json";
75
75
 
76
76
  const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
77
77
  "amazon-bedrock": [
78
+ "claude-fable-5",
78
79
  "claude-opus-4-8",
79
80
  "claude-opus-4-7",
80
81
  "claude-opus-4-6",
@@ -92,6 +93,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
92
93
  "zai.glm-5",
93
94
  ],
94
95
  anthropic: [
96
+ "claude-fable-5",
95
97
  "claude-opus-4-8",
96
98
  "claude-opus-4.8",
97
99
  "claude-opus-4-7",
@@ -132,6 +134,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
132
134
  ],
133
135
  cerebras: ["gpt-oss-120b", "zai-glm-4.7", "llama3.1-8b"],
134
136
  "cloudflare-ai-gateway": [
137
+ "claude-fable-5",
135
138
  "claude-opus-4-7",
136
139
  "claude-opus-4-6",
137
140
  "claude-opus-4-5",
@@ -159,6 +162,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
159
162
  "accounts/fireworks/models/gpt-oss-120b",
160
163
  ],
161
164
  "github-copilot": [
165
+ "claude-fable-5",
162
166
  "claude-opus-4.8",
163
167
  "claude-opus-4.7",
164
168
  "claude-opus-4.6",
@@ -257,6 +261,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
257
261
  "big-pickle",
258
262
  ],
259
263
  opencode: [
264
+ "claude-fable-5",
260
265
  "gpt-5.5-pro",
261
266
  "gpt-5.5",
262
267
  "gpt-5.4-pro",
@@ -289,6 +294,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
289
294
  "kimi-k2.5",
290
295
  ],
291
296
  openrouter: [
297
+ "anthropic/claude-fable-5",
298
+ "~anthropic/claude-fable-latest",
292
299
  "anthropic/claude-opus-4.8",
293
300
  "anthropic/claude-opus-4.8-fast",
294
301
  "anthropic/claude-opus-4.7",
@@ -327,6 +334,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
327
334
  "MiniMaxAI/MiniMax-M2.5",
328
335
  ],
329
336
  "vercel-ai-gateway": [
337
+ "anthropic/claude-fable-5",
330
338
  "anthropic/claude-opus-4.8",
331
339
  "anthropic/claude-opus-4.7",
332
340
  "anthropic/claude-opus-4.6",
@@ -549,6 +557,26 @@ function extractTextFromContent(content: unknown): string {
549
557
  return parts.join("\n\n").trim();
550
558
  }
551
559
 
560
+ // Errors that typically resolve on retry: provider overload, rate limiting,
561
+ // transient 5xx, gateway/network failures, and request timeouts. Keep this list
562
+ // pattern-based so we recognize variants across providers without enumerating them.
563
+ const TRANSIENT_ERROR_PATTERN =
564
+ /\b(overload(?:ed)?|rate[ _-]?limit(?:ed)?|too many requests|429|500|502|503|504|bad gateway|service unavailable|gateway timeout|temporarily unavailable|timed?[ _-]?out|timeout|econnreset|econnrefused|etimedout|enetunreach|socket hang up|fetch failed)\b/i;
565
+
566
+ function isTransientErrorMessage(message: string): boolean {
567
+ return TRANSIENT_ERROR_PATTERN.test(message);
568
+ }
569
+
570
+ function formatOracleModelError(stopReason: "error" | "aborted", errorMessage: string | undefined): string {
571
+ const trimmed = errorMessage?.trim();
572
+ if (stopReason === "aborted") {
573
+ return trimmed ? `Oracle model turn aborted: ${trimmed}` : "Oracle model turn aborted.";
574
+ }
575
+ if (!trimmed) return "Oracle model error (no detail provided by provider).";
576
+ const base = `Oracle model error: ${trimmed}`;
577
+ return isTransientErrorMessage(trimmed) ? `${base} (transient; retry may succeed)` : base;
578
+ }
579
+
552
580
  function parseVersionScore(text: string): number {
553
581
  const matches = text.match(/\d+(?:\.\d+){0,2}/g) ?? [];
554
582
  let best = 0;
@@ -809,6 +837,9 @@ async function runOracle(
809
837
  let finalOutput = "";
810
838
  let stderr = "";
811
839
 
840
+ let lastStopReason: string | undefined;
841
+ let lastErrorMessage: string | undefined;
842
+
812
843
  const details: OracleDetails = {
813
844
  ...selection,
814
845
  includeBash,
@@ -880,6 +911,11 @@ async function runOracle(
880
911
  if (text) finalOutput = text;
881
912
  currentText = "";
882
913
 
914
+ const stopReason = event.message.stopReason;
915
+ lastStopReason = typeof stopReason === "string" ? stopReason : undefined;
916
+ const errorMessageField = event.message.errorMessage;
917
+ lastErrorMessage = typeof errorMessageField === "string" ? errorMessageField : undefined;
918
+
883
919
  const messageUsage = event.message.usage;
884
920
  if (messageUsage) {
885
921
  usage.turns += 1;
@@ -938,6 +974,19 @@ async function runOracle(
938
974
  return { ok: false, error: "Oracle was aborted.", details };
939
975
  }
940
976
 
977
+ // The pi subprocess in `--mode json` does not promote an errored assistant
978
+ // turn to a non-zero exit code or stderr; the error is only carried on the
979
+ // streamed assistant message via stopReason/errorMessage. Surface that here
980
+ // so callers can distinguish transient provider errors from a genuinely
981
+ // empty response.
982
+ if (lastStopReason === "error" || lastStopReason === "aborted") {
983
+ return {
984
+ ok: false,
985
+ error: formatOracleModelError(lastStopReason, lastErrorMessage),
986
+ details,
987
+ };
988
+ }
989
+
941
990
  if (exitCode !== 0) {
942
991
  return {
943
992
  ok: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-oracle",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
5
5
  "keywords": [
6
6
  "pi-package",