oira666_pi-limits-wait 0.4.1 → 0.4.3
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/package.json +49 -49
- package/src/index.ts +138 -15
package/package.json
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "oira666_pi-limits-wait",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Extension for PI that allows to automatically wait for limits to reset and retry the request",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"files": [
|
|
8
|
-
"src",
|
|
9
|
-
"!src/__tests__",
|
|
10
|
-
"README.md",
|
|
11
|
-
"LICENSE"
|
|
12
|
-
],
|
|
13
|
-
"engines": {
|
|
14
|
-
"node": ">=18"
|
|
15
|
-
},
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "https://github.com/gee666/pi-limits-wait.git"
|
|
19
|
-
},
|
|
20
|
-
"bugs": {
|
|
21
|
-
"url": "https://github.com/gee666/pi-limits-wait/issues"
|
|
22
|
-
},
|
|
23
|
-
"homepage": "https://github.com/gee666/pi-limits-wait#readme",
|
|
24
|
-
"publishConfig": {
|
|
25
|
-
"access": "public"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"check": "tsc -p tsconfig.json --noEmit",
|
|
29
|
-
"test": "tsx src/__tests__/test-extension.ts",
|
|
30
|
-
"pack:check": "npm pack --dry-run",
|
|
31
|
-
"prepublishOnly": "npm run check && npm test && npm run pack:check"
|
|
32
|
-
},
|
|
33
|
-
"pi": {
|
|
34
|
-
"extensions": [
|
|
35
|
-
"./src/index.ts"
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {},
|
|
39
|
-
"peerDependencies": {
|
|
40
|
-
"@mariozechner/pi-ai": "*",
|
|
41
|
-
"@mariozechner/pi-coding-agent": "*"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@mariozechner/pi-ai": "^0.66.1",
|
|
45
|
-
"@mariozechner/pi-coding-agent": "^0.66.1",
|
|
46
|
-
"tsx": "^4.20.6",
|
|
47
|
-
"typescript": "^5.9.3"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "oira666_pi-limits-wait",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Extension for PI that allows to automatically wait for limits to reset and retry the request",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"!src/__tests__",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/gee666/pi-limits-wait.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/gee666/pi-limits-wait/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/gee666/pi-limits-wait#readme",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
29
|
+
"test": "tsx src/__tests__/test-extension.ts",
|
|
30
|
+
"pack:check": "npm pack --dry-run",
|
|
31
|
+
"prepublishOnly": "npm run check && npm test && npm run pack:check"
|
|
32
|
+
},
|
|
33
|
+
"pi": {
|
|
34
|
+
"extensions": [
|
|
35
|
+
"./src/index.ts"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@mariozechner/pi-ai": "*",
|
|
41
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@mariozechner/pi-ai": "^0.66.1",
|
|
45
|
+
"@mariozechner/pi-coding-agent": "^0.66.1",
|
|
46
|
+
"tsx": "^4.20.6",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -28,13 +28,14 @@ const PI_IDENTITY_SENTENCE_PATTERN =
|
|
|
28
28
|
|
|
29
29
|
const DEFAULT_RATE_LIMIT_WAIT_MS = 30 * 60 * 1_000; // 30 minutes
|
|
30
30
|
const DEFAULT_OVERLOADED_WAIT_MS = 5 * 60 * 1_000; // 5 minutes
|
|
31
|
+
const DEFAULT_NON_RETRYABLE_FREEZE_MS = 60 * 60 * 1_000; // 1 hour
|
|
31
32
|
const SETTINGS_KEY = "oira666_pi-limits-wait";
|
|
32
33
|
|
|
33
34
|
/** Key used with ctx.ui.setStatus() for the countdown line. */
|
|
34
35
|
const STATUS_KEY = "limits-wait";
|
|
35
36
|
const MODELS_STATUS_KEY = "limits-wait-models";
|
|
36
37
|
|
|
37
|
-
type RetryReason = "rate-limit" | "overloaded";
|
|
38
|
+
type RetryReason = "rate-limit" | "overloaded" | "authentication" | "model-frozen";
|
|
38
39
|
|
|
39
40
|
type RetryableError = {
|
|
40
41
|
reason: RetryReason;
|
|
@@ -47,7 +48,7 @@ type ConfiguredModel = {
|
|
|
47
48
|
reasoningEffort?: ThinkingLevel;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
type FallbackModel = {
|
|
51
|
+
export type FallbackModel = {
|
|
51
52
|
model: Model<Api>;
|
|
52
53
|
reasoningEffort?: ThinkingLevel;
|
|
53
54
|
};
|
|
@@ -74,6 +75,7 @@ let primaryThinkingLevel: ThinkingLevel | undefined;
|
|
|
74
75
|
let suppressNextModelSelect = false;
|
|
75
76
|
let settingsSignature: string | undefined;
|
|
76
77
|
const rateLimitMemory = new Map<string, RateLimitMemory>();
|
|
78
|
+
const nonRetryableFailureMemory = new Map<string, { failedAt: number; deadline: number; errorMessage: string }>();
|
|
77
79
|
|
|
78
80
|
// ─── Anthropic subscription prompt sanitisation ──────────────────────────────
|
|
79
81
|
|
|
@@ -113,6 +115,16 @@ function formatModel(model: Pick<Model<Api>, "provider" | "id">): string {
|
|
|
113
115
|
return `${model.provider}/${model.id}`;
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
function isInternalSyntheticModel(model: Pick<Model<Api>, "provider" | "id">): boolean {
|
|
119
|
+
return model.provider.startsWith("pi-") || model.id.startsWith("synthetic-");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isFallbackEligibleModel(model: Model<Api>): boolean {
|
|
123
|
+
if (isInternalSyntheticModel(model)) return false;
|
|
124
|
+
const key = modelKey(model);
|
|
125
|
+
return primaryModel ? key === modelKey(primaryModel) || fallbackModels.some((entry) => modelKey(entry.model) === key) : true;
|
|
126
|
+
}
|
|
127
|
+
|
|
116
128
|
function isThinkingLevel(value: unknown): value is ThinkingLevel {
|
|
117
129
|
return ["off", "minimal", "low", "medium", "high", "xhigh"].includes(String(value));
|
|
118
130
|
}
|
|
@@ -247,6 +259,13 @@ function updateRateLimitedModelsStatus(): void {
|
|
|
247
259
|
}
|
|
248
260
|
lines.push(`${key}: ${formatDuration(entry.deadline - now)}`);
|
|
249
261
|
}
|
|
262
|
+
for (const [key, entry] of nonRetryableFailureMemory) {
|
|
263
|
+
if (now >= entry.deadline) {
|
|
264
|
+
nonRetryableFailureMemory.delete(key);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
lines.push(`${key}: frozen ${formatDuration(entry.deadline - now)}`);
|
|
268
|
+
}
|
|
250
269
|
if (lines.length === 0) {
|
|
251
270
|
modelStatusCleanup?.();
|
|
252
271
|
return;
|
|
@@ -272,13 +291,34 @@ function rememberRateLimit(model: Model<Api>, retryable: RetryableError): void {
|
|
|
272
291
|
ensureRateLimitedModelsStatus();
|
|
273
292
|
}
|
|
274
293
|
|
|
294
|
+
function activeNonRetryableFailure(model: Model<Api>): { failedAt: number; deadline: number; errorMessage: string } | undefined {
|
|
295
|
+
const entry = nonRetryableFailureMemory.get(modelKey(model));
|
|
296
|
+
if (!entry) return undefined;
|
|
297
|
+
if (Date.now() >= entry.deadline) {
|
|
298
|
+
nonRetryableFailureMemory.delete(modelKey(model));
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
return entry;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function hasNonRetryableFailure(model: Model<Api>): boolean {
|
|
305
|
+
return Boolean(activeNonRetryableFailure(model));
|
|
306
|
+
}
|
|
307
|
+
|
|
275
308
|
function nextAvailableCandidate(current: Model<Api>): FallbackModel | undefined {
|
|
276
|
-
return candidateOrder(current).find((entry) => !activeLimit(entry.model));
|
|
309
|
+
return candidateOrder(current).find((entry) => !activeLimit(entry.model) && !hasNonRetryableFailure(entry.model));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function rememberNonRetryableFailure(model: Model<Api>, errorMessage: string): void {
|
|
313
|
+
const deadline = Date.now() + DEFAULT_NON_RETRYABLE_FREEZE_MS;
|
|
314
|
+
nonRetryableFailureMemory.set(modelKey(model), { failedAt: Date.now(), deadline, errorMessage });
|
|
315
|
+
sharedCtx?.ui.notify(`${formatModel(model)} failed; freezing it for ${formatDuration(DEFAULT_NON_RETRYABLE_FREEZE_MS)} and trying another configured model if available.`, "warning");
|
|
316
|
+
ensureRateLimitedModelsStatus();
|
|
277
317
|
}
|
|
278
318
|
|
|
279
319
|
function earliestCandidateDeadline(current: Model<Api>): number | undefined {
|
|
280
320
|
const deadlines = candidateOrder(current)
|
|
281
|
-
.map((entry) => activeLimit(entry.model)?.deadline)
|
|
321
|
+
.map((entry) => activeLimit(entry.model)?.deadline ?? activeNonRetryableFailure(entry.model)?.deadline)
|
|
282
322
|
.filter((deadline): deadline is number => typeof deadline === "number");
|
|
283
323
|
return deadlines.length > 0 ? Math.min(...deadlines) : undefined;
|
|
284
324
|
}
|
|
@@ -286,7 +326,7 @@ function earliestCandidateDeadline(current: Model<Api>): number | undefined {
|
|
|
286
326
|
function initialAttempt(model: Model<Api>): FallbackModel {
|
|
287
327
|
const configured = fallbackModels.find((entry) => modelKey(entry.model) === modelKey(model));
|
|
288
328
|
const current = configured ?? { model, reasoningEffort: primaryThinkingLevel };
|
|
289
|
-
if (!fallbackEnabled() || !activeLimit(model)) return current;
|
|
329
|
+
if (!fallbackEnabled() || (!activeLimit(model) && !hasNonRetryableFailure(model))) return current;
|
|
290
330
|
return nextAvailableCandidate(model) ?? current;
|
|
291
331
|
}
|
|
292
332
|
|
|
@@ -351,6 +391,18 @@ export function isServerOverloadedError(msg: string): boolean {
|
|
|
351
391
|
);
|
|
352
392
|
}
|
|
353
393
|
|
|
394
|
+
export function isAuthenticationRefreshError(msg: string): boolean {
|
|
395
|
+
const lower = msg.toLowerCase();
|
|
396
|
+
return (
|
|
397
|
+
/(?:^|\D)401(?:\D|$)/.test(msg) &&
|
|
398
|
+
(
|
|
399
|
+
lower.includes("authentication_error") ||
|
|
400
|
+
lower.includes("invalid authentication credentials") ||
|
|
401
|
+
lower.includes("invalid authentication")
|
|
402
|
+
)
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
354
406
|
export function parseRetryDelayMs(msg: string): number | undefined {
|
|
355
407
|
const retryAfter = msg.match(/retry-after(?:-ms)?[^0-9]*(\d+(?:\.\d+)?)/i);
|
|
356
408
|
if (retryAfter?.[1]) {
|
|
@@ -416,7 +468,10 @@ export function retryAfterHeaderMs(headers: Headers): number | undefined {
|
|
|
416
468
|
|
|
417
469
|
export function getRetryableError(msg: string): RetryableError | undefined {
|
|
418
470
|
if (isServerOverloadedError(msg)) {
|
|
419
|
-
return { reason: "overloaded", waitMs: DEFAULT_OVERLOADED_WAIT_MS };
|
|
471
|
+
return { reason: "overloaded", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_OVERLOADED_WAIT_MS };
|
|
472
|
+
}
|
|
473
|
+
if (isAuthenticationRefreshError(msg)) {
|
|
474
|
+
return { reason: "authentication", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_RATE_LIMIT_WAIT_MS };
|
|
420
475
|
}
|
|
421
476
|
if (isRateLimitError(msg)) {
|
|
422
477
|
return { reason: "rate-limit", waitMs: rateLimitWaitMs(msg) };
|
|
@@ -427,7 +482,10 @@ export function getRetryableError(msg: string): RetryableError | undefined {
|
|
|
427
482
|
// ─── Countdown UI ─────────────────────────────────────────────────────────────
|
|
428
483
|
|
|
429
484
|
function reasonLabel(reason: RetryReason): string {
|
|
430
|
-
|
|
485
|
+
if (reason === "overloaded") return "Server overloaded";
|
|
486
|
+
if (reason === "authentication") return "Authentication refresh pending";
|
|
487
|
+
if (reason === "model-frozen") return "Model frozen after error";
|
|
488
|
+
return "Rate limited";
|
|
431
489
|
}
|
|
432
490
|
|
|
433
491
|
function statusText(reason: RetryReason, deadline: number, allowSkip: boolean): string {
|
|
@@ -580,6 +638,20 @@ type StreamSimpleFn = (
|
|
|
580
638
|
options?: SimpleStreamOptions,
|
|
581
639
|
) => AssistantMessageEventStream;
|
|
582
640
|
|
|
641
|
+
export function __configureFallbackModelsForTests(
|
|
642
|
+
models: FallbackModel[],
|
|
643
|
+
ctx?: ExtensionContext,
|
|
644
|
+
): void {
|
|
645
|
+
fallbackModels = models;
|
|
646
|
+
sharedCtx = ctx;
|
|
647
|
+
primaryModel = undefined;
|
|
648
|
+
primaryThinkingLevel = undefined;
|
|
649
|
+
rateLimitMemory.clear();
|
|
650
|
+
nonRetryableFailureMemory.clear();
|
|
651
|
+
ambientStatusCleanup?.();
|
|
652
|
+
modelStatusCleanup?.();
|
|
653
|
+
}
|
|
654
|
+
|
|
583
655
|
function freshMessage(model: Model<Api>): AssistantMessage {
|
|
584
656
|
return {
|
|
585
657
|
role: "assistant",
|
|
@@ -615,7 +687,8 @@ export function streamWithLimitsRetry(
|
|
|
615
687
|
|
|
616
688
|
void (async () => {
|
|
617
689
|
let committed = false;
|
|
618
|
-
|
|
690
|
+
const allowFallback = fallbackEnabled() && isFallbackEligibleModel(model);
|
|
691
|
+
let attempt: FallbackModel = allowFallback ? initialAttempt(model) : { model, reasoningEffort: primaryThinkingLevel };
|
|
619
692
|
|
|
620
693
|
const flush = (buffer: AssistantMessageEvent[]) => {
|
|
621
694
|
for (const event of buffer) output.push(event);
|
|
@@ -627,10 +700,11 @@ export function streamWithLimitsRetry(
|
|
|
627
700
|
installFetchRateLimitObserver();
|
|
628
701
|
const buffer: AssistantMessageEvent[] = [];
|
|
629
702
|
let retryable: RetryableError | undefined;
|
|
703
|
+
let nonRetryableError: { message: string; event?: AssistantMessageEvent } | undefined;
|
|
630
704
|
|
|
631
705
|
try {
|
|
632
706
|
try {
|
|
633
|
-
const attemptOptions =
|
|
707
|
+
const attemptOptions = allowFallback
|
|
634
708
|
? await optionsForModel(model, attempt, options)
|
|
635
709
|
: options;
|
|
636
710
|
const attemptDelegate = attempt.model.api === model.api
|
|
@@ -646,7 +720,12 @@ export function streamWithLimitsRetry(
|
|
|
646
720
|
retryable = !options?.signal?.aborted ? getRetryableError(errMsg) : undefined;
|
|
647
721
|
if (retryable) break;
|
|
648
722
|
|
|
649
|
-
|
|
723
|
+
if (allowFallback) {
|
|
724
|
+
nonRetryableError = { message: errMsg, event };
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Non-retryable error without fallbacks: ensure start came first, then forward error.
|
|
650
729
|
if (buffer.length > 0) {
|
|
651
730
|
flush(buffer);
|
|
652
731
|
} else {
|
|
@@ -664,7 +743,7 @@ export function streamWithLimitsRetry(
|
|
|
664
743
|
}
|
|
665
744
|
|
|
666
745
|
// First non-start, non-error event means this attempt is real.
|
|
667
|
-
if (
|
|
746
|
+
if (allowFallback) await switchPiModel(attempt);
|
|
668
747
|
if (buffer.length > 0) {
|
|
669
748
|
flush(buffer);
|
|
670
749
|
} else {
|
|
@@ -690,6 +769,9 @@ export function streamWithLimitsRetry(
|
|
|
690
769
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
691
770
|
retryable = !options?.signal?.aborted ? getRetryableError(errMsg) : undefined;
|
|
692
771
|
if (!retryable) {
|
|
772
|
+
if (allowFallback && !options?.signal?.aborted) {
|
|
773
|
+
nonRetryableError = { message: errMsg };
|
|
774
|
+
} else {
|
|
693
775
|
if (!committed) {
|
|
694
776
|
// Synthetic start+error so the stream protocol stays valid.
|
|
695
777
|
output.push({ type: "start", partial: freshMessage(attempt.model) });
|
|
@@ -705,6 +787,7 @@ export function streamWithLimitsRetry(
|
|
|
705
787
|
});
|
|
706
788
|
output.end();
|
|
707
789
|
return;
|
|
790
|
+
}
|
|
708
791
|
}
|
|
709
792
|
}
|
|
710
793
|
} finally {
|
|
@@ -715,6 +798,45 @@ export function streamWithLimitsRetry(
|
|
|
715
798
|
}
|
|
716
799
|
}
|
|
717
800
|
|
|
801
|
+
if (nonRetryableError) {
|
|
802
|
+
if (allowFallback && !committed) {
|
|
803
|
+
rememberNonRetryableFailure(attempt.model, nonRetryableError.message);
|
|
804
|
+
const next = nextAvailableCandidate(attempt.model);
|
|
805
|
+
if (next) {
|
|
806
|
+
attempt = next;
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const deadline = earliestCandidateDeadline(attempt.model);
|
|
811
|
+
if (deadline) {
|
|
812
|
+
const waitResult = await waitForRetry("model-frozen", Math.max(0, deadline - Date.now()), options?.signal);
|
|
813
|
+
if (waitResult !== "aborted") {
|
|
814
|
+
attempt = nextAvailableCandidate(attempt.model) ?? { model: getPrimaryModel(attempt.model), reasoningEffort: primaryThinkingLevel };
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (!committed) {
|
|
821
|
+
if (buffer.length > 0) {
|
|
822
|
+
flush(buffer);
|
|
823
|
+
} else {
|
|
824
|
+
output.push({ type: "start", partial: freshMessage(attempt.model) });
|
|
825
|
+
committed = true;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (nonRetryableError.event) {
|
|
829
|
+
output.push(nonRetryableError.event);
|
|
830
|
+
} else {
|
|
831
|
+
const error = freshMessage(attempt.model);
|
|
832
|
+
error.stopReason = "error";
|
|
833
|
+
error.errorMessage = nonRetryableError.message;
|
|
834
|
+
output.push({ type: "error", reason: "error", error });
|
|
835
|
+
}
|
|
836
|
+
output.end();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
718
840
|
if (!retryable) {
|
|
719
841
|
if (!committed) {
|
|
720
842
|
if (buffer.length > 0) {
|
|
@@ -732,7 +854,7 @@ export function streamWithLimitsRetry(
|
|
|
732
854
|
return;
|
|
733
855
|
}
|
|
734
856
|
|
|
735
|
-
if (
|
|
857
|
+
if (allowFallback) {
|
|
736
858
|
rememberRateLimit(attempt.model, retryable);
|
|
737
859
|
const next = nextAvailableCandidate(attempt.model);
|
|
738
860
|
if (next) {
|
|
@@ -807,22 +929,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
807
929
|
suppressNextModelSelect = false;
|
|
808
930
|
return;
|
|
809
931
|
}
|
|
932
|
+
if (isInternalSyntheticModel(event.model)) return;
|
|
810
933
|
primaryModel = event.model;
|
|
811
934
|
primaryThinkingLevel = pi.getThinkingLevel();
|
|
812
935
|
});
|
|
813
936
|
|
|
814
937
|
pi.on("before_agent_start", (event, ctx) => {
|
|
815
938
|
sharedCtx = ctx;
|
|
816
|
-
primaryModel
|
|
939
|
+
if (!primaryModel && ctx.model && !isInternalSyntheticModel(ctx.model)) primaryModel = ctx.model;
|
|
817
940
|
primaryThinkingLevel ??= pi.getThinkingLevel();
|
|
818
941
|
loadFallbackSettings(ctx);
|
|
819
942
|
|
|
820
943
|
// Some extensions may register providers after this extension loads. Wrap
|
|
821
944
|
// any APIs that exist by the time an agent starts too.
|
|
822
945
|
for (const model of ctx.modelRegistry.getAll()) {
|
|
823
|
-
registerWrappedApi(pi, model.api);
|
|
946
|
+
if (!isInternalSyntheticModel(model)) registerWrappedApi(pi, model.api);
|
|
824
947
|
}
|
|
825
|
-
if (ctx.model) registerWrappedApi(pi, ctx.model.api);
|
|
948
|
+
if (ctx.model && !isInternalSyntheticModel(ctx.model)) registerWrappedApi(pi, ctx.model.api);
|
|
826
949
|
|
|
827
950
|
// Anthropic subscription/OAuth requests identify as Claude Code, not Pi.
|
|
828
951
|
// Check the session-wide Anthropic OAuth configuration rather than only the
|