@centerforagenticai/pi-multi-account 0.1.2 → 0.1.5
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 +1 -1
- package/packages/pi-anthropic-oauth/src/stream.ts +8 -0
- package/src/anthropic-adaptive-stream.ts +81 -15
- package/src/anthropic-alias-stream.ts +9 -1
- package/src/codex-adapter.ts +109 -11
- package/src/config.ts +151 -0
- package/src/error-classification.ts +13 -1
- package/src/host-final-stop-message.ts +136 -0
- package/src/index.ts +41 -4
- package/src/logical-provider.ts +157 -2
- package/src/model-fallback-policy.ts +384 -0
- package/src/recovery-engine.ts +354 -68
- package/src/recovery-plan.ts +7 -1
- package/src/recovery-send-evidence.ts +29 -0
- package/src/refusal-advice.ts +139 -0
- package/src/routing.ts +17 -1
- package/src/shared-usage.ts +100 -176
- package/src/usage-fetch.ts +52 -32
- package/src/usage.ts +29 -26
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isContextOverflow,
|
|
3
|
+
isRetryableAssistantError,
|
|
4
|
+
type AssistantMessage,
|
|
5
|
+
} from "@earendil-works/pi-ai";
|
|
6
|
+
import { sanitizeDiagnosticText } from "./diagnostics.js";
|
|
7
|
+
|
|
8
|
+
/** Fixed fallbacks for a structured provider stop; none matches a host re-dispatch pattern. */
|
|
9
|
+
export const REFUSAL_FALLBACK_MESSAGE = "The model refused to complete the request";
|
|
10
|
+
export const UNKNOWN_STOP_FALLBACK_MESSAGE =
|
|
11
|
+
"Provider stopped with an unrecognized stop reason";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Appended when a reworded unknown stop reason omits words a host predicate acts
|
|
15
|
+
* on, so the published reason does not read as complete. Neither pinned
|
|
16
|
+
* predicate matches it, and the marked message is probed again before use.
|
|
17
|
+
*/
|
|
18
|
+
export const PARTLY_WITHHELD_MARKER = " (partly withheld)";
|
|
19
|
+
|
|
20
|
+
/** Upper bound on the normalized stop reason named in a reworded message. */
|
|
21
|
+
const MAX_NAMED_REASON_LENGTH = 64;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether the pinned host would re-dispatch an error terminal carrying `text`.
|
|
25
|
+
*
|
|
26
|
+
* The host re-dispatches when either retry predicate matches the prose:
|
|
27
|
+
* `AgentSession._isRetryableError` runs pi-ai `isRetryableAssistantError`, and
|
|
28
|
+
* `_checkCompaction` runs `isContextOverflow` before compacting and retrying once
|
|
29
|
+
* (pi-coding-agent dist/core/agent-session.js, both from `@earendil-works/pi-ai`).
|
|
30
|
+
* An unreadable predicate result counts as a match, so the caller keeps looking
|
|
31
|
+
* for a safer form.
|
|
32
|
+
*/
|
|
33
|
+
export function hostWouldRedispatch(message: AssistantMessage, text: string): boolean {
|
|
34
|
+
try {
|
|
35
|
+
const probe = { ...message, errorMessage: text };
|
|
36
|
+
return isRetryableAssistantError(probe) || isContextOverflow(probe, 0);
|
|
37
|
+
} catch {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function namedReasonMessage(reason: string): string {
|
|
43
|
+
return `Provider stopped (reason: ${reason})`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Normalizes a bounded, sanitized raw stop reason into plain words: every run of
|
|
48
|
+
* non-alphanumeric characters (underscores included) becomes one space, and the
|
|
49
|
+
* result is capped at a word boundary where possible.
|
|
50
|
+
*/
|
|
51
|
+
function normalizedReasonWords(rawStopReason: unknown): string[] {
|
|
52
|
+
if (typeof rawStopReason !== "string") return [];
|
|
53
|
+
const words = sanitizeDiagnosticText(rawStopReason)
|
|
54
|
+
.replace(/[^A-Za-z0-9]+/g, " ")
|
|
55
|
+
.trim()
|
|
56
|
+
.split(" ")
|
|
57
|
+
.filter((word) => word.length > 0);
|
|
58
|
+
const kept: string[] = [];
|
|
59
|
+
let length = 0;
|
|
60
|
+
for (const word of words) {
|
|
61
|
+
const next = length === 0 ? word.length : length + 1 + word.length;
|
|
62
|
+
if (next > MAX_NAMED_REASON_LENGTH) {
|
|
63
|
+
if (kept.length === 0) kept.push(word.slice(0, MAX_NAMED_REASON_LENGTH));
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
kept.push(word);
|
|
67
|
+
length = next;
|
|
68
|
+
}
|
|
69
|
+
return kept;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Names an unknown stop reason in a message neither host predicate acts on.
|
|
74
|
+
*
|
|
75
|
+
* The first form keeps every normalized word. When that still matches a host
|
|
76
|
+
* predicate (a reason containing "overloaded" or "timeout", say), the second
|
|
77
|
+
* form admits words in order and drops each one whose addition would make the
|
|
78
|
+
* message match. When any word was dropped the result ends with
|
|
79
|
+
* `PARTLY_WITHHELD_MARKER`; that marked text is probed too, and an unsafe or
|
|
80
|
+
* empty result yields `undefined`.
|
|
81
|
+
*/
|
|
82
|
+
function unknownStopNamingReason(
|
|
83
|
+
message: AssistantMessage,
|
|
84
|
+
rawStopReason: unknown,
|
|
85
|
+
): string | undefined {
|
|
86
|
+
const words = normalizedReasonWords(rawStopReason);
|
|
87
|
+
if (words.length === 0) return undefined;
|
|
88
|
+
const reworded = namedReasonMessage(words.join(" "));
|
|
89
|
+
if (!hostWouldRedispatch(message, reworded)) return reworded;
|
|
90
|
+
const kept: string[] = [];
|
|
91
|
+
for (const word of words) {
|
|
92
|
+
const tentative = namedReasonMessage([...kept, word].join(" "));
|
|
93
|
+
if (!hostWouldRedispatch(message, tentative)) kept.push(word);
|
|
94
|
+
}
|
|
95
|
+
if (kept.length === 0) return undefined;
|
|
96
|
+
const named = namedReasonMessage(kept.join(" "));
|
|
97
|
+
if (kept.length === words.length) return named;
|
|
98
|
+
const marked = `${named}${PARTLY_WITHHELD_MARKER}`;
|
|
99
|
+
return hostWouldRedispatch(message, marked) ? undefined : marked;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The public error text for a structured refusal or unknown provider stop.
|
|
104
|
+
*
|
|
105
|
+
* The pinned host re-dispatches an error terminal from its prose alone (see
|
|
106
|
+
* `hostWouldRedispatch`). A refusal explanation or stop reason is
|
|
107
|
+
* provider-authored, so text such as "overloaded", "prompt is too long", or
|
|
108
|
+
* "model_context_window_exceeded" would make the host send the stopped request
|
|
109
|
+
* again. The structured code alone selects this path; the provider text is kept
|
|
110
|
+
* only when neither host predicate would act on it. An unknown stop is then
|
|
111
|
+
* reworded so it still names its reason, and only when no reworded form is safe
|
|
112
|
+
* is a fixed fallback published. A refusal whose explanation is unsafe publishes
|
|
113
|
+
* its fixed fallback directly. A message without the structured code, or that is
|
|
114
|
+
* not an error terminal, yields `undefined` and must be published unchanged.
|
|
115
|
+
*/
|
|
116
|
+
export function hostFinalStopMessage(
|
|
117
|
+
message: AssistantMessage,
|
|
118
|
+
): { readonly errorMessage: string } | undefined {
|
|
119
|
+
const code = (message as { code?: unknown }).code;
|
|
120
|
+
if (message.stopReason !== "error") return undefined;
|
|
121
|
+
if (code !== "refusal" && code !== "unknown_stop") return undefined;
|
|
122
|
+
const candidate = message.errorMessage;
|
|
123
|
+
if (
|
|
124
|
+
typeof candidate === "string" &&
|
|
125
|
+
candidate.length > 0 &&
|
|
126
|
+
!hostWouldRedispatch(message, candidate)
|
|
127
|
+
) {
|
|
128
|
+
return { errorMessage: candidate };
|
|
129
|
+
}
|
|
130
|
+
if (code === "refusal") return { errorMessage: REFUSAL_FALLBACK_MESSAGE };
|
|
131
|
+
return {
|
|
132
|
+
errorMessage:
|
|
133
|
+
unknownStopNamingReason(message, message.rawStopReason) ??
|
|
134
|
+
UNKNOWN_STOP_FALLBACK_MESSAGE,
|
|
135
|
+
};
|
|
136
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
import { DiagnosticStore } from "./diagnostic-store.js";
|
|
81
81
|
import { DeclarationNoticeMarker } from "./declaration-notice-marker.js";
|
|
82
82
|
import { DiagnosticLog } from "./diagnostics.js";
|
|
83
|
+
import { createRefusalAdvisor } from "./refusal-advice.js";
|
|
83
84
|
import {
|
|
84
85
|
classifyProviderId,
|
|
85
86
|
createPublicAuthStorageAdapter,
|
|
@@ -3890,6 +3891,18 @@ export const createMultiAccountExtension =
|
|
|
3890
3891
|
modelSupport,
|
|
3891
3892
|
alreadyCooled: classified.alreadyCooled === true,
|
|
3892
3893
|
});
|
|
3894
|
+
if (decision.status === "retained") {
|
|
3895
|
+
// A structured refusal or unknown stop keeps the account: no switch,
|
|
3896
|
+
// no park, no OpenRouter, and no continuation. The same context would
|
|
3897
|
+
// stop the same way on any account.
|
|
3898
|
+
diagnostics.record(
|
|
3899
|
+
"info",
|
|
3900
|
+
"routing.retained",
|
|
3901
|
+
"The provider stopped the turn with a refusal or unknown stop reason; the account was kept and no follow-up was scheduled.",
|
|
3902
|
+
{ providerId, kind: decision.classification.kind ?? "unknown" },
|
|
3903
|
+
);
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3893
3906
|
if (decision.status === "paused") {
|
|
3894
3907
|
// OpenRouter is an explicitly enabled, metered FINAL rung. The helper
|
|
3895
3908
|
// re-checks every managed family so family-chain policy cannot bypass
|
|
@@ -4147,6 +4160,9 @@ export const createMultiAccountExtension =
|
|
|
4147
4160
|
const lastFailure = new Map<string, ProviderFailureSignal>();
|
|
4148
4161
|
const handledFailures = new WeakSet<object>();
|
|
4149
4162
|
const observedMessages = new WeakSet<object>();
|
|
4163
|
+
// Operator advice for a structured refusal. A delegate-owned in-process
|
|
4164
|
+
// session shares the foreground UI, so only the foreground advises.
|
|
4165
|
+
const refusalAdvisor = createRefusalAdvisor({ foreground: !delegateOwnedSession });
|
|
4150
4166
|
const recordUsage = (
|
|
4151
4167
|
observation: () => UsageObservation | undefined,
|
|
4152
4168
|
): void => {
|
|
@@ -4873,7 +4889,7 @@ export const createMultiAccountExtension =
|
|
|
4873
4889
|
config,
|
|
4874
4890
|
nowMs,
|
|
4875
4891
|
});
|
|
4876
|
-
if (reactive.status
|
|
4892
|
+
if (reactive.status !== "selected") return;
|
|
4877
4893
|
reactiveCandidate = reactive;
|
|
4878
4894
|
destinationProviderId = reactive.destination.providerId;
|
|
4879
4895
|
}
|
|
@@ -5141,6 +5157,17 @@ export const createMultiAccountExtension =
|
|
|
5141
5157
|
acceptedLogicalAssociation,
|
|
5142
5158
|
);
|
|
5143
5159
|
}
|
|
5160
|
+
// Advice only: names the physical route that refused, never routes.
|
|
5161
|
+
refusalAdvisor.advise(
|
|
5162
|
+
originalMessage,
|
|
5163
|
+
messageContext,
|
|
5164
|
+
acceptedLogicalAssociation && logicalAssociation !== undefined
|
|
5165
|
+
? {
|
|
5166
|
+
providerId: logicalAssociation.route.providerId,
|
|
5167
|
+
modelId: logicalAssociation.dispatchedModelId,
|
|
5168
|
+
}
|
|
5169
|
+
: {},
|
|
5170
|
+
);
|
|
5144
5171
|
|
|
5145
5172
|
try {
|
|
5146
5173
|
const validOriginalMessage = originalMessage as AssistantMessage;
|
|
@@ -5228,6 +5255,8 @@ export const createMultiAccountExtension =
|
|
|
5228
5255
|
) {
|
|
5229
5256
|
return;
|
|
5230
5257
|
}
|
|
5258
|
+
// Advice only: a managed account's structured refusal never routes.
|
|
5259
|
+
refusalAdvisor.advise(event.message, messageContext, identity);
|
|
5231
5260
|
const subscriptionFamily = isRoutingEligibleAccountFamily(messageSlot)
|
|
5232
5261
|
? messageSlot.family
|
|
5233
5262
|
: undefined;
|
|
@@ -5299,9 +5328,17 @@ export const createMultiAccountExtension =
|
|
|
5299
5328
|
turnRouteOrigin = origin;
|
|
5300
5329
|
handledFailures.add(event.message);
|
|
5301
5330
|
const responseFailure = lastFailure.get(providerId) ?? {};
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5331
|
+
// A structured provider stop is copied only from this two-value allowlist,
|
|
5332
|
+
// never spread, and it wins over any code parsed from provider-authored
|
|
5333
|
+
// errorMessage prose: refusal text must not read as a rate limit.
|
|
5334
|
+
const rawStopCode = (event.message as { code?: unknown }).code;
|
|
5335
|
+
const stopCode =
|
|
5336
|
+
rawStopCode === "refusal" || rawStopCode === "unknown_stop"
|
|
5337
|
+
? rawStopCode
|
|
5338
|
+
: undefined;
|
|
5339
|
+
const messageCode =
|
|
5340
|
+
stopCode ??
|
|
5341
|
+
providerErrorCodeFromMessage(event.message.errorMessage);
|
|
5305
5342
|
const failure: ProviderFailureSignal = {
|
|
5306
5343
|
...responseFailure,
|
|
5307
5344
|
...(messageCode === undefined ? {} : { code: messageCode }),
|
package/src/logical-provider.ts
CHANGED
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
import { logicalAccountEligible, recordFailureCooldown } from "./routing.js";
|
|
17
17
|
import type { ManagedAccount } from "./routing.js";
|
|
18
18
|
import {
|
|
19
|
+
isContextOverflow,
|
|
19
20
|
isRetryableAssistantError,
|
|
20
21
|
type AssistantMessage,
|
|
21
22
|
type ProviderResponse,
|
|
22
23
|
type SimpleStreamOptions,
|
|
23
24
|
} from "@earendil-works/pi-ai";
|
|
24
25
|
import { RuntimeState, type LogicalRoutePin } from "./runtime-state.js";
|
|
26
|
+
import { hostFinalStopMessage } from "./host-final-stop-message.js";
|
|
25
27
|
import { DEFAULT_CONFIG } from "./config.js";
|
|
26
28
|
import type {
|
|
27
29
|
AllowedFamily,
|
|
@@ -47,6 +49,16 @@ import type { ProviderType, Vendor } from "./vendor.js";
|
|
|
47
49
|
|
|
48
50
|
export { LOGICAL_PROVIDER_ID } from "./models-declaration.js";
|
|
49
51
|
import { LOGICAL_PROVIDER_ID } from "./models-declaration.js";
|
|
52
|
+
import { forceCodexSseOptions } from "./codex-adapter.js";
|
|
53
|
+
|
|
54
|
+
/** Fixed diagnostic label for a caller-supplied transport; never echoes the raw value. */
|
|
55
|
+
function codexTransportLabel(
|
|
56
|
+
transport: unknown,
|
|
57
|
+
): "websocket" | "websocket-cached" | "auto" | "other" {
|
|
58
|
+
return transport === "websocket" || transport === "websocket-cached" || transport === "auto"
|
|
59
|
+
? transport
|
|
60
|
+
: "other";
|
|
61
|
+
}
|
|
50
62
|
|
|
51
63
|
/** One physical account the logical provider may dispatch to. */
|
|
52
64
|
export interface LogicalPhysicalAccount {
|
|
@@ -342,6 +354,36 @@ const EXHAUSTION_LENGTH_ALLOWANCE_MULTIPLIER = 8;
|
|
|
342
354
|
const EXHAUSTION_LENGTH_MAX_CONTEXT_FRACTION = 0.8;
|
|
343
355
|
const EXHAUSTION_LENGTH_ERROR_MESSAGE = "provider returned error (usage-limit)";
|
|
344
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Fixed public text for a setup-shaped context overflow. The pinned host's
|
|
359
|
+
* `isContextOverflow` matches it (so the host compacts and retries once) and
|
|
360
|
+
* `isRetryableAssistantError` does not (so the host does not fail over).
|
|
361
|
+
*/
|
|
362
|
+
export const SETUP_CONTEXT_OVERFLOW_MESSAGE = "context_length_exceeded (provider_error)";
|
|
363
|
+
|
|
364
|
+
type SetupFailureDisposition = "context-overflow" | "retryable" | "host-final";
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* How the pinned host treats the raw setup text. The host checks the two
|
|
368
|
+
* predicates separately: `_handlePostAgentRun` compacts and retries once on
|
|
369
|
+
* `isContextOverflow`, while `_isRetryableError` excludes overflow and fails
|
|
370
|
+
* over on `isRetryableAssistantError`. An unreadable predicate result counts
|
|
371
|
+
* as host-final.
|
|
372
|
+
*/
|
|
373
|
+
function setupFailureDisposition(
|
|
374
|
+
message: AssistantMessage,
|
|
375
|
+
raw: unknown,
|
|
376
|
+
): SetupFailureDisposition {
|
|
377
|
+
if (typeof raw !== "string" || raw.length === 0) return "host-final";
|
|
378
|
+
try {
|
|
379
|
+
const probe = { ...message, errorMessage: raw };
|
|
380
|
+
if (isContextOverflow(probe, 0)) return "context-overflow";
|
|
381
|
+
return isRetryableAssistantError(probe) ? "retryable" : "host-final";
|
|
382
|
+
} catch {
|
|
383
|
+
return "host-final";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
345
387
|
function finiteNonNegative(value: unknown): number | undefined {
|
|
346
388
|
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
347
389
|
? value
|
|
@@ -531,6 +573,54 @@ function projectFailureSignal(
|
|
|
531
573
|
};
|
|
532
574
|
}
|
|
533
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Whether a physical terminal is the host's setup-error shape: the first event
|
|
578
|
+
* of the stream is an `error` with no content, all-zero usage, no diagnostics,
|
|
579
|
+
* no structured stop code, and no structured failure evidence.
|
|
580
|
+
*
|
|
581
|
+
* That is what pi-ai `lazyStream` (`createSetupErrorMessage`) publishes when a
|
|
582
|
+
* provider stream throws or rejects before it starts, so its `errorMessage` is
|
|
583
|
+
* raw exception text, not provider-authored failure prose. The production cause
|
|
584
|
+
* of the observed setup `TypeError` is not known (see UPSTREAM.md). The same
|
|
585
|
+
* shape also carries transient pre-start failures ("fetch failed", a 503 before
|
|
586
|
+
* `start`), so the caller decides retryability from the text, never publishes
|
|
587
|
+
* it. A real provider failure that carries a recognized code or status keeps
|
|
588
|
+
* its own text and routing.
|
|
589
|
+
*/
|
|
590
|
+
function isUnclassifiedSetupFailure(
|
|
591
|
+
message: AssistantMessage,
|
|
592
|
+
failure: ProviderFailureSignal,
|
|
593
|
+
): boolean {
|
|
594
|
+
try {
|
|
595
|
+
if (message.stopReason !== "error") return false;
|
|
596
|
+
if (!Array.isArray(message.content) || message.content.length !== 0) return false;
|
|
597
|
+
const diagnostics = (message as { diagnostics?: unknown }).diagnostics;
|
|
598
|
+
if (diagnostics !== undefined && !(Array.isArray(diagnostics) && diagnostics.length === 0)) {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
if ((message as { code?: unknown }).code !== undefined) return false;
|
|
602
|
+
const usage = projectTerminalUsage(message);
|
|
603
|
+
if (
|
|
604
|
+
usage === undefined ||
|
|
605
|
+
usage.input !== 0 ||
|
|
606
|
+
usage.output !== 0 ||
|
|
607
|
+
usage.cacheRead !== 0 ||
|
|
608
|
+
usage.cacheWrite !== 0 ||
|
|
609
|
+
usage.totalTokens !== 0 ||
|
|
610
|
+
usage.cost.total !== 0
|
|
611
|
+
) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return (
|
|
615
|
+
failure.code === undefined &&
|
|
616
|
+
failure.httpStatus === undefined &&
|
|
617
|
+
failure.transportKind === undefined
|
|
618
|
+
);
|
|
619
|
+
} catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
534
624
|
function safeProjectFailureSignal(
|
|
535
625
|
error: unknown,
|
|
536
626
|
modelId: string,
|
|
@@ -666,6 +756,7 @@ export function createLogicalProvider(
|
|
|
666
756
|
deps: LogicalProviderDeps,
|
|
667
757
|
): LogicalProvider {
|
|
668
758
|
const coordinator = createHostRetryCoordinator(deps);
|
|
759
|
+
let codexTransportNoticeSent = false;
|
|
669
760
|
|
|
670
761
|
const diagnose = (message: string): void => {
|
|
671
762
|
deps.onDiagnostic?.(message);
|
|
@@ -936,6 +1027,7 @@ export function createLogicalProvider(
|
|
|
936
1027
|
|
|
937
1028
|
const projectMessage = (message: AssistantMessage, modelId: string): AssistantMessage => ({
|
|
938
1029
|
...message, api: LOGICAL_PROVIDER_ID, provider: LOGICAL_PROVIDER_ID, model: modelId,
|
|
1030
|
+
...hostFinalStopMessage(message),
|
|
939
1031
|
});
|
|
940
1032
|
|
|
941
1033
|
const projectEvent = (event: unknown, modelId: string): unknown => {
|
|
@@ -951,6 +1043,18 @@ export function createLogicalProvider(
|
|
|
951
1043
|
return { ...candidate, [key]: projectMessage(value as AssistantMessage, modelId) };
|
|
952
1044
|
};
|
|
953
1045
|
|
|
1046
|
+
const withPublicErrorMessage = (event: unknown, errorMessage: string): unknown => {
|
|
1047
|
+
if (typeof event !== "object" || event === null) return event;
|
|
1048
|
+
const candidate = event as Record<string, unknown>;
|
|
1049
|
+
if (candidate.type !== "error" || typeof candidate.error !== "object" || candidate.error === null) {
|
|
1050
|
+
return event;
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
...candidate,
|
|
1054
|
+
error: { ...(candidate.error as AssistantMessage), errorMessage },
|
|
1055
|
+
};
|
|
1056
|
+
};
|
|
1057
|
+
|
|
954
1058
|
const watchStream = (
|
|
955
1059
|
stream: AsyncIterable<unknown>,
|
|
956
1060
|
model: unknown,
|
|
@@ -962,6 +1066,7 @@ export function createLogicalProvider(
|
|
|
962
1066
|
): AsyncIterable<unknown> => ({
|
|
963
1067
|
async *[Symbol.asyncIterator]() {
|
|
964
1068
|
let sawTerminal = false;
|
|
1069
|
+
let sawEvent = false;
|
|
965
1070
|
let failureReceipt: HostRetryCooldownReceipt | undefined;
|
|
966
1071
|
const recordFailureOnce = (error: unknown): HostRetryCooldownReceipt => {
|
|
967
1072
|
failureReceipt ??= coordinator.recordFailure({
|
|
@@ -1003,7 +1108,10 @@ export function createLogicalProvider(
|
|
|
1003
1108
|
? (event as { type?: unknown }).type
|
|
1004
1109
|
: undefined;
|
|
1005
1110
|
if (eventType === "done" || eventType === "error") sawTerminal = true;
|
|
1111
|
+
const firstEvent = !sawEvent;
|
|
1112
|
+
sawEvent = true;
|
|
1006
1113
|
const terminal = terminalAttribution(event);
|
|
1114
|
+
let setupFailureMessage: string | undefined;
|
|
1007
1115
|
if (terminal !== undefined) {
|
|
1008
1116
|
const { message, outcome } = terminal;
|
|
1009
1117
|
if (outcome === "finish") {
|
|
@@ -1035,10 +1143,41 @@ export function createLogicalProvider(
|
|
|
1035
1143
|
} else {
|
|
1036
1144
|
const failure = safeProjectFailureSignal(message, dispatchedModelId);
|
|
1037
1145
|
attributeFailure(message, failure, recordFailureOnce(message));
|
|
1146
|
+
if (firstEvent && isUnclassifiedSetupFailure(message, failure)) {
|
|
1147
|
+
// The physical account is cooled above exactly like any other
|
|
1148
|
+
// failure. Only the published text changes: the raw text is
|
|
1149
|
+
// replaced by the bounded classified message the
|
|
1150
|
+
// rejected-dispatch path uses, so it is never published. The
|
|
1151
|
+
// setup shape is shared by transient pre-start failures ("fetch
|
|
1152
|
+
// failed", a 503 before `start`), pre-start context overflows
|
|
1153
|
+
// (a Codex 400 or Anthropic 413), and deterministic setup throws.
|
|
1154
|
+
// The raw text decides the form: an overflow publishes a fixed
|
|
1155
|
+
// overflow message so the host compacts instead of failing
|
|
1156
|
+
// over; a host-retryable text keeps the retryable form and
|
|
1157
|
+
// fails over; anything else is host-final (`provider_error`),
|
|
1158
|
+
// so a deterministic fault is not repeated on the next account.
|
|
1159
|
+
const disposition = setupFailureDisposition(message, message.errorMessage);
|
|
1160
|
+
setupFailureMessage =
|
|
1161
|
+
disposition === "context-overflow"
|
|
1162
|
+
? SETUP_CONTEXT_OVERFLOW_MESSAGE
|
|
1163
|
+
: classifiedErrorMessage(failure, disposition === "retryable");
|
|
1164
|
+
try {
|
|
1165
|
+
deps.onDiagnostic?.(
|
|
1166
|
+
`logical dispatch for ${account.providerId} failed during stream setup; ` +
|
|
1167
|
+
"published a classified failure instead of the raw setup error",
|
|
1168
|
+
);
|
|
1169
|
+
} catch {
|
|
1170
|
+
// A diagnostic sink failure cannot replace a provider result.
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1038
1173
|
}
|
|
1039
1174
|
await attempt.waitForTerminal();
|
|
1040
1175
|
}
|
|
1041
|
-
const
|
|
1176
|
+
const projectedEvent = projectEvent(event, requestedModelId);
|
|
1177
|
+
const publicEvent =
|
|
1178
|
+
setupFailureMessage === undefined
|
|
1179
|
+
? projectedEvent
|
|
1180
|
+
: withPublicErrorMessage(projectedEvent, setupFailureMessage);
|
|
1042
1181
|
if (terminal !== undefined && publicEvent !== event) {
|
|
1043
1182
|
const publicTerminal = terminalAttribution(publicEvent);
|
|
1044
1183
|
if (publicTerminal !== undefined) {
|
|
@@ -1127,8 +1266,24 @@ export function createLogicalProvider(
|
|
|
1127
1266
|
await originalOnResponse(response, responseModel);
|
|
1128
1267
|
}
|
|
1129
1268
|
};
|
|
1269
|
+
let routedOptions = options;
|
|
1270
|
+
if (account.family === "openai-codex") {
|
|
1271
|
+
// Temporary Pi 0.99 WebSocket containment; see CODEX_FORCED_TRANSPORT.
|
|
1272
|
+
const forced = forceCodexSseOptions(options);
|
|
1273
|
+
routedOptions = forced.options;
|
|
1274
|
+
if (forced.overridden && options?.transport !== undefined && !codexTransportNoticeSent) {
|
|
1275
|
+
codexTransportNoticeSent = true;
|
|
1276
|
+
try {
|
|
1277
|
+
deps.onDiagnostic?.(
|
|
1278
|
+
`Codex transport "${codexTransportLabel(options.transport)}" overridden to "sse" on routed calls (Pi 0.99 WebSocket containment).`,
|
|
1279
|
+
);
|
|
1280
|
+
} catch {
|
|
1281
|
+
// A diagnostic sink failure cannot replace a provider result.
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1130
1285
|
const attributedOptions: SimpleStreamOptions = {
|
|
1131
|
-
...
|
|
1286
|
+
...routedOptions,
|
|
1132
1287
|
onPayload: wrappedOnPayload,
|
|
1133
1288
|
onResponse: wrappedOnResponse,
|
|
1134
1289
|
};
|