@code-yeongyu/senpi-ai 2026.10.31-2 → 2026.11.2-2
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/dist/api/openai-responses-shared.d.ts +2 -0
- package/dist/api/openai-responses-shared.js +19 -3
- package/dist/api/openai-responses.js +30 -9
- package/dist/auth/credential-store.d.ts +3 -1
- package/dist/auth/credential-store.js +78 -0
- package/dist/auth/oauth/kimi-coding.js +6 -4
- package/dist/auth/oauth/openai-chatgpt-model-catalog.d.ts +36 -0
- package/dist/auth/oauth/openai-chatgpt-model-catalog.js +104 -0
- package/dist/auth/oauth/openai-chatgpt.js +76 -0
- package/dist/auth/oauth/radius.js +1 -2
- package/dist/auth/oauth-refresh.d.ts +2 -0
- package/dist/auth/oauth-refresh.js +53 -7
- package/dist/auth/pool/failover.d.ts +0 -40
- package/dist/auth/pool/failover.js +0 -76
- package/dist/auth/pool/slots.js +10 -0
- package/dist/auth/resolve.js +3 -0
- package/dist/auth/types.d.ts +11 -0
- package/dist/models.d.ts +4 -5
- package/dist/models.js +36 -1
- package/dist/provider-diagnostic.d.ts +2 -0
- package/dist/provider-diagnostic.js +6 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/opengateway.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/empty-response-errors.d.ts +1 -2
- package/dist/utils/empty-response-errors.js +1 -2
- package/dist/utils/oauth-refresh-error.d.ts +8 -1
- package/dist/utils/oauth-refresh-error.js +3 -1
- package/dist/utils/provider-diagnostic-carrier.d.ts +10 -1
- package/dist/utils/provider-diagnostic-carrier.js +35 -0
- package/dist/utils/provider-diagnostic-sources.d.ts +2 -0
- package/dist/utils/provider-diagnostic-sources.js +5 -0
- package/dist/utils/provider-diagnostic-vocabulary.d.ts +1 -0
- package/dist/utils/provider-diagnostic-vocabulary.js +9 -0
- package/dist/utils/provider-retry.js +4 -1
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +14 -3
- package/dist/utils/siwc-errors.d.ts +2 -0
- package/dist/utils/siwc-errors.js +2 -0
- package/dist/utils/tool-name-match.js +46 -1
- package/package.json +2 -2
- package/dist/auth/pool/classify.d.ts +0 -16
- package/dist/auth/pool/classify.js +0 -75
|
@@ -1,45 +1,5 @@
|
|
|
1
1
|
import { TURN_RETRY_SUPPRESSION_PREFIX } from "../../utils/provider-failure-description.ts";
|
|
2
|
-
import { type PoolFailureClassification } from "./classify.ts";
|
|
3
|
-
import type { SelectableSlot } from "./select.ts";
|
|
4
2
|
export { TURN_RETRY_SUPPRESSION_PREFIX };
|
|
5
3
|
export declare const DEFAULT_SLOT_BLOCK_MS = 60000;
|
|
6
4
|
export declare const MAX_SLOT_BLOCK_MS: number;
|
|
7
|
-
export type PoolFailoverEvent<TSlot extends SelectableSlot> = {
|
|
8
|
-
slot: TSlot;
|
|
9
|
-
nextSlot?: TSlot;
|
|
10
|
-
classification: PoolFailureClassification;
|
|
11
|
-
attempt: number;
|
|
12
|
-
committedOutput: boolean;
|
|
13
|
-
};
|
|
14
|
-
export type PoolFailoverOptions<TEvent, TSlot extends SelectableSlot> = {
|
|
15
|
-
slots: readonly TSlot[];
|
|
16
|
-
select: (slots: readonly TSlot[]) => TSlot;
|
|
17
|
-
runAttempt: (slot: TSlot) => AsyncIterable<TEvent> | Promise<AsyncIterable<TEvent>>;
|
|
18
|
-
classify?: (error: unknown) => PoolFailureClassification;
|
|
19
|
-
/**
|
|
20
|
-
* Rotation is transparent only before committed output. Absent, every yielded
|
|
21
|
-
* event counts as committed (default-DENY), so silent mid-stream rotation
|
|
22
|
-
* requires an explicit opt-in from a caller that knows which events are
|
|
23
|
-
* bookkeeping rather than user-visible output.
|
|
24
|
-
*/
|
|
25
|
-
isCommittedOutput?: (event: TEvent) => boolean;
|
|
26
|
-
persistBlock?: (slot: TSlot) => void | Promise<void>;
|
|
27
|
-
onRotate?: (event: PoolFailoverEvent<TSlot>) => void | Promise<void>;
|
|
28
|
-
now?: () => number;
|
|
29
|
-
baseBlockMs?: number;
|
|
30
|
-
};
|
|
31
|
-
export declare class PoolFailoverError extends Error {
|
|
32
|
-
readonly classification: PoolFailureClassification;
|
|
33
|
-
readonly original: unknown;
|
|
34
|
-
readonly suppressTurnRetry: boolean;
|
|
35
|
-
constructor(classification: PoolFailureClassification, original: unknown, suppressTurnRetry: boolean);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Runs at most one attempt per slot. A rotation is transparent only while no
|
|
39
|
-
* committed output has reached the caller; afterwards the classified error is
|
|
40
|
-
* thrown with the turn-retry suppression marker so the session layer never
|
|
41
|
-
* replays a partially delivered turn. Non-rotate classes throw immediately and
|
|
42
|
-
* compose with the retry/model-fallback machinery above this engine.
|
|
43
|
-
*/
|
|
44
|
-
export declare function runSlotFailover<TEvent, TSlot extends SelectableSlot>(options: PoolFailoverOptions<TEvent, TSlot>): AsyncGenerator<TEvent>;
|
|
45
5
|
//# sourceMappingURL=failover.d.ts.map
|
|
@@ -1,81 +1,5 @@
|
|
|
1
1
|
import { TURN_RETRY_SUPPRESSION_PREFIX } from "../../utils/provider-failure-description.js";
|
|
2
|
-
import { classifyPoolFailure } from "./classify.js";
|
|
3
2
|
export { TURN_RETRY_SUPPRESSION_PREFIX };
|
|
4
3
|
export const DEFAULT_SLOT_BLOCK_MS = 60_000;
|
|
5
4
|
export const MAX_SLOT_BLOCK_MS = 48 * 60 * 60 * 1_000;
|
|
6
|
-
export class PoolFailoverError extends Error {
|
|
7
|
-
constructor(classification, original, suppressTurnRetry) {
|
|
8
|
-
const detail = original instanceof Error ? original.message : String(original);
|
|
9
|
-
super(`${suppressTurnRetry ? TURN_RETRY_SUPPRESSION_PREFIX : ""}${detail}`);
|
|
10
|
-
this.name = "PoolFailoverError";
|
|
11
|
-
this.classification = classification;
|
|
12
|
-
this.original = original;
|
|
13
|
-
this.suppressTurnRetry = suppressTurnRetry;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function blockedSlot(slot, classification, now, attempt, baseBlockMs) {
|
|
17
|
-
if (classification.blockReason === "auth_error") {
|
|
18
|
-
// An auth block has no expiry: only a re-login rewrites the slot.
|
|
19
|
-
const next = { ...slot, blockReason: "auth_error" };
|
|
20
|
-
delete next.blockedUntil;
|
|
21
|
-
return next;
|
|
22
|
-
}
|
|
23
|
-
const fallback = Math.min(MAX_SLOT_BLOCK_MS, baseBlockMs * 2 ** attempt);
|
|
24
|
-
const duration = Math.min(MAX_SLOT_BLOCK_MS, classification.retryAfterMs ?? fallback);
|
|
25
|
-
return { ...slot, blockedUntil: now + duration, blockReason: "rate_limit" };
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Runs at most one attempt per slot. A rotation is transparent only while no
|
|
29
|
-
* committed output has reached the caller; afterwards the classified error is
|
|
30
|
-
* thrown with the turn-retry suppression marker so the session layer never
|
|
31
|
-
* replays a partially delivered turn. Non-rotate classes throw immediately and
|
|
32
|
-
* compose with the retry/model-fallback machinery above this engine.
|
|
33
|
-
*/
|
|
34
|
-
export async function* runSlotFailover(options) {
|
|
35
|
-
const now = options.now ?? Date.now;
|
|
36
|
-
const classify = options.classify ?? classifyPoolFailure;
|
|
37
|
-
const committed = options.isCommittedOutput ?? (() => true);
|
|
38
|
-
const baseBlockMs = options.baseBlockMs ?? DEFAULT_SLOT_BLOCK_MS;
|
|
39
|
-
let slots = [...options.slots];
|
|
40
|
-
let lastError;
|
|
41
|
-
for (let attempt = 0; attempt < options.slots.length; attempt++) {
|
|
42
|
-
const slot = options.select(slots);
|
|
43
|
-
let committedOutput = false;
|
|
44
|
-
try {
|
|
45
|
-
const attemptStream = await options.runAttempt(slot);
|
|
46
|
-
for await (const event of attemptStream) {
|
|
47
|
-
committedOutput ||= committed(event);
|
|
48
|
-
yield event;
|
|
49
|
-
}
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
const classification = classify(error);
|
|
54
|
-
const failure = new PoolFailoverError(classification, error, committedOutput);
|
|
55
|
-
lastError = failure;
|
|
56
|
-
if (classification.action !== "rotate")
|
|
57
|
-
throw failure;
|
|
58
|
-
const blocked = blockedSlot(slot, classification, now(), attempt, baseBlockMs);
|
|
59
|
-
slots = slots.map((candidate) => (candidate.name === blocked.name ? blocked : candidate));
|
|
60
|
-
await options.persistBlock?.(blocked);
|
|
61
|
-
const rotation = {
|
|
62
|
-
slot: blocked,
|
|
63
|
-
classification,
|
|
64
|
-
attempt: attempt + 1,
|
|
65
|
-
committedOutput,
|
|
66
|
-
};
|
|
67
|
-
try {
|
|
68
|
-
if (!committedOutput && attempt + 1 < slots.length) {
|
|
69
|
-
rotation.nextSlot = options.select(slots);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
await options.onRotate?.(rotation);
|
|
74
|
-
}
|
|
75
|
-
if (committedOutput)
|
|
76
|
-
throw failure;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
throw lastError ?? new Error("Credential pool failover exhausted without an attempt");
|
|
80
|
-
}
|
|
81
5
|
//# sourceMappingURL=failover.js.map
|
package/dist/auth/pool/slots.js
CHANGED
|
@@ -359,6 +359,16 @@ export function appendLoginSlot(stored, flat, onAllocated, options) {
|
|
|
359
359
|
return flat;
|
|
360
360
|
}
|
|
361
361
|
const storedAccounts = Array.isArray(current.accounts) ? current.accounts : undefined;
|
|
362
|
+
if (!provided && "signedOut" in current && current.signedOut === true) {
|
|
363
|
+
// Sign-out keeps only the registration for a later sign-in (senpi#5003 W7). That sign-in takes over
|
|
364
|
+
// the flat projection; as a sibling slot it would leave the provider resolving to the signed-out copy.
|
|
365
|
+
const name = storedAccounts ? nextLoginSlotName(current) : DEFAULT_SLOT_NAME;
|
|
366
|
+
const base = storedAccounts ? upsertSlot(current, slotFromFlatCredentialNamed(flat, name)) : current;
|
|
367
|
+
const next = { ...base, ...flat, ...(storedAccounts ? { accounts: base.accounts } : {}) };
|
|
368
|
+
Reflect.deleteProperty(next, "signedOut");
|
|
369
|
+
onAllocated?.(name, "generated");
|
|
370
|
+
return next;
|
|
371
|
+
}
|
|
362
372
|
if (provided) {
|
|
363
373
|
// A pool merges onto the stored pool; a flat current keeps the whole-write
|
|
364
374
|
// shape because the provider's accounts already carry this login.
|
package/dist/auth/resolve.js
CHANGED
|
@@ -97,6 +97,9 @@ export function oauthRefreshModelsError(error, providerId) {
|
|
|
97
97
|
* exchange outside the store lock, and compare-and-swaps the rotated slot.
|
|
98
98
|
*/
|
|
99
99
|
async function resolveStoredOAuth(credentials, providerId, oauth, stored, authContext, requestEnv, signal, minOAuthValidityMs, slotName, rejectedAccess) {
|
|
100
|
+
// A retained registration is not an expired session to refresh.
|
|
101
|
+
if (stored.signedOut === true || (!stored.access && !stored.refresh))
|
|
102
|
+
return undefined;
|
|
100
103
|
const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);
|
|
101
104
|
const expiresSoon = (credential) => Date.now() + minimumValidityMs >= credential.expires;
|
|
102
105
|
const isStale = (credential) => expiresSoon(credential) || (rejectedAccess !== undefined && credential.access === rejectedAccess);
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -39,6 +39,15 @@ export interface CredentialInfo {
|
|
|
39
39
|
export interface AuthOperationOptions {
|
|
40
40
|
signal?: AbortSignal;
|
|
41
41
|
}
|
|
42
|
+
/** Secret-free outcome; callers can direct an unconfirmed user to ChatGPT Settings. */
|
|
43
|
+
export type OAuthRevocationResult = {
|
|
44
|
+
status: "revoked" | "remote_revocation_unconfirmed";
|
|
45
|
+
};
|
|
46
|
+
export interface LogoutOptions extends AuthOperationOptions {
|
|
47
|
+
slotId?: string;
|
|
48
|
+
/** Injectable backoff scheduler; production defaults to an abortable timer. */
|
|
49
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
50
|
+
}
|
|
42
51
|
/**
|
|
43
52
|
* App-owned credential storage, keyed by `Provider.id`, one entry per
|
|
44
53
|
* provider; an entry may pool sibling slots under `accounts` while its flat
|
|
@@ -267,6 +276,8 @@ export interface OAuthAuth {
|
|
|
267
276
|
* (invalid_grant etc.). `Models` runs this under the store lock.
|
|
268
277
|
*/
|
|
269
278
|
refresh(credential: OAuthCredential, signal: AbortSignal): Promise<OAuthCredential>;
|
|
279
|
+
/** End the renewable session before the caller clears local tokens. */
|
|
280
|
+
revoke?(credential: OAuthCredential, options?: LogoutOptions): Promise<OAuthRevocationResult>;
|
|
270
281
|
/**
|
|
271
282
|
* Optional side-effect-free availability check, mirroring `ApiKeyAuth.check`.
|
|
272
283
|
* Supply this when a stored OAuth credential does not by itself imply the
|
package/dist/models.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type AuthResolutionOverrides } from "./auth/resolve.ts";
|
|
2
|
-
import type { AuthCheck, AuthContext, AuthInteraction, AuthOperationOptions, AuthResult, AuthType, Credential, CredentialStore, LoginOptions, ProviderAuth } from "./auth/types.ts";
|
|
2
|
+
import type { AuthCheck, AuthContext, AuthInteraction, AuthOperationOptions, AuthResult, AuthType, Credential, CredentialStore, LoginOptions, LogoutOptions, OAuthRevocationResult, ProviderAuth } from "./auth/types.ts";
|
|
3
3
|
import { type ModelsStore, type ModelsStoreEntry } from "./models-store.ts";
|
|
4
4
|
import type { AnyModel, Api, ApiStreamOptions, AssistantImages, AssistantMessage, AssistantMessageEventStream, ClassifierApi, ClassifierContext, ClassifierModel, ClassifierOptions, ClassifierResult, Context, DeferredCancelOptions, DeferredFetchOptions, DeferredHandle, ImageApi, ImageModel, ImagesContext, ImagesOptions, Model, ModelThinkingLevel, ModelType, ModelTypeMap, ProviderClassifier, ProviderHeaders, ProviderImages, ProviderStreams, SimpleStreamOptions, ThinkingLevelMap, TranscriptContext, Usage } from "./types.ts";
|
|
5
5
|
import type { RetryPolicyProfile } from "./utils/retry-profile/types.ts";
|
|
6
6
|
export { ModelsError, type ModelsErrorCode, PROVIDER_NOT_CONFIGURED_PREFIX, providerNotConfiguredMessage, } from "./auth/resolve.ts";
|
|
7
7
|
export { getModelType, isModelType } from "./utils/model-operations.ts";
|
|
8
|
+
export type { LogoutOptions, OAuthRevocationResult } from "./auth/types.ts";
|
|
8
9
|
export interface ModelsPublication {
|
|
9
10
|
/** Provider-selected persisted catalog. Omit to leave storage unchanged; null deletes it. */
|
|
10
11
|
persist?: ModelsStoreEntry | null;
|
|
@@ -181,10 +182,8 @@ export interface Models {
|
|
|
181
182
|
getAuth(model: AnyModel, overrides?: AuthResolutionOverrides): Promise<AuthResult | undefined>;
|
|
182
183
|
/** Run a provider-owned login flow and persist its returned credential. */
|
|
183
184
|
login(providerId: string, type: AuthType, interaction: AuthInteraction, options?: LoginOptions): Promise<Credential>;
|
|
184
|
-
/**
|
|
185
|
-
logout(providerId: string, options?:
|
|
186
|
-
slotId?: string;
|
|
187
|
-
}): Promise<void>;
|
|
185
|
+
/** Revoke supported OAuth sessions and retain registrations; otherwise remove the credential. */
|
|
186
|
+
logout(providerId: string, options?: LogoutOptions): Promise<OAuthRevocationResult | void>;
|
|
188
187
|
stream<TApi extends Api>(model: Model<TApi>, context: Context, options?: ModelsApiStreamOptions<TApi>): AssistantMessageEventStream;
|
|
189
188
|
complete<TApi extends Api>(model: Model<TApi>, context: Context, options?: ModelsApiStreamOptions<TApi>): Promise<AssistantMessage>;
|
|
190
189
|
streamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream;
|
package/dist/models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lazyStream } from "./api/lazy.js";
|
|
2
2
|
import { defaultProviderAuthContext as defaultAuthContext } from "./auth/context.js";
|
|
3
|
-
import { InMemoryCredentialStore } from "./auth/credential-store.js";
|
|
3
|
+
import { InMemoryCredentialStore, revokeStoredOAuth } from "./auth/credential-store.js";
|
|
4
4
|
import { appendLoginSlot, assertLoginKeyPresent, removeSlot, withoutBlankKey } from "./auth/pool/slots.js";
|
|
5
5
|
import { resolveRefreshCredential } from "./auth/refresh-credential.js";
|
|
6
6
|
import { ModelsError, providerNotConfiguredMessage, resolveProviderAuth, } from "./auth/resolve.js";
|
|
@@ -42,6 +42,8 @@ class ModelsImpl {
|
|
|
42
42
|
this.refreshGenerations = new Map();
|
|
43
43
|
this.refreshControllers = new Map();
|
|
44
44
|
this.publicationChains = new Map();
|
|
45
|
+
this.logoutControllers = new Map();
|
|
46
|
+
this.signingOut = new Map();
|
|
45
47
|
this.credentials = options?.credentials ?? new InMemoryCredentialStore();
|
|
46
48
|
this.modelsStore = options?.modelsStore ?? new InMemoryModelsStore();
|
|
47
49
|
this.authContext = options?.authContext ?? defaultAuthContext();
|
|
@@ -338,6 +340,8 @@ class ModelsImpl {
|
|
|
338
340
|
async getAuth(providerOrModel, overrides) {
|
|
339
341
|
const signal = operationSignal(overrides?.signal);
|
|
340
342
|
const providerId = typeof providerOrModel === "string" ? providerOrModel : providerOrModel.provider;
|
|
343
|
+
if (this.signingOut.has(providerId))
|
|
344
|
+
return undefined;
|
|
341
345
|
const provider = this.providers.get(providerId);
|
|
342
346
|
if (!provider)
|
|
343
347
|
return undefined;
|
|
@@ -414,7 +418,15 @@ class ModelsImpl {
|
|
|
414
418
|
async logout(providerId, options) {
|
|
415
419
|
const signal = operationSignal(options?.signal);
|
|
416
420
|
signal.throwIfAborted();
|
|
421
|
+
const oauth = this.providers.get(providerId)?.auth.oauth;
|
|
417
422
|
try {
|
|
423
|
+
if (oauth?.revoke) {
|
|
424
|
+
this.signingOut.set(providerId, (this.signingOut.get(providerId) ?? 0) + 1);
|
|
425
|
+
this.logoutControllers.get(providerId)?.abort(new Error("Account signed out"));
|
|
426
|
+
const result = await revokeStoredOAuth(this.credentials, providerId, oauth, options);
|
|
427
|
+
if (result)
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
418
430
|
if (options?.slotId !== undefined) {
|
|
419
431
|
const current = await this.credentials.read(providerId, { signal });
|
|
420
432
|
const next = removeSlot(current, options.slotId);
|
|
@@ -431,6 +443,17 @@ class ModelsImpl {
|
|
|
431
443
|
signal.throwIfAborted();
|
|
432
444
|
throw new ModelsError("auth", `Credential store delete failed for ${providerId}`, { cause: error });
|
|
433
445
|
}
|
|
446
|
+
finally {
|
|
447
|
+
if (oauth?.revoke) {
|
|
448
|
+
const remaining = (this.signingOut.get(providerId) ?? 1) - 1;
|
|
449
|
+
if (remaining > 0)
|
|
450
|
+
this.signingOut.set(providerId, remaining);
|
|
451
|
+
else {
|
|
452
|
+
this.signingOut.delete(providerId);
|
|
453
|
+
this.logoutControllers.delete(providerId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
434
457
|
}
|
|
435
458
|
requireProvider(model) {
|
|
436
459
|
const provider = this.providers.get(model.provider);
|
|
@@ -445,6 +468,17 @@ class ModelsImpl {
|
|
|
445
468
|
}
|
|
446
469
|
async applyAuth(model, options) {
|
|
447
470
|
this.requireProvider(model);
|
|
471
|
+
if (this.providers.get(model.provider)?.auth.oauth?.revoke) {
|
|
472
|
+
let controller = this.logoutControllers.get(model.provider);
|
|
473
|
+
if (!controller) {
|
|
474
|
+
controller = new AbortController();
|
|
475
|
+
this.logoutControllers.set(model.provider, controller);
|
|
476
|
+
}
|
|
477
|
+
options = {
|
|
478
|
+
...options,
|
|
479
|
+
signal: AbortSignal.any([controller.signal, ...(options?.signal ? [options.signal] : [])]),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
448
482
|
const resolution = await this.getAuth(model, {
|
|
449
483
|
apiKey: options?.apiKey,
|
|
450
484
|
env: options?.env,
|
|
@@ -454,6 +488,7 @@ class ModelsImpl {
|
|
|
454
488
|
throw new ModelsError("auth", providerNotConfiguredMessage(model.provider));
|
|
455
489
|
}
|
|
456
490
|
const auth = resolution.auth;
|
|
491
|
+
options?.signal?.throwIfAborted();
|
|
457
492
|
// Explicit request options win per-field; the Models-only transform runs last.
|
|
458
493
|
const apiKey = options?.apiKey ?? auth.apiKey;
|
|
459
494
|
let headers = mergeHeaders(auth.headers, options?.headers);
|
|
@@ -16,6 +16,8 @@ export interface ProviderDiagnostic {
|
|
|
16
16
|
/** Provider token from the closed allowlist, never raw provider text. */
|
|
17
17
|
code?: string;
|
|
18
18
|
evidence: ProviderDiagnosticEvidence;
|
|
19
|
+
/** Adapter-selected SIWC route; keeps direct-admission policy separate from API-key routes. */
|
|
20
|
+
route?: "siwc";
|
|
19
21
|
/** When a `quota` or `rate_limit` failure resets (epoch ms), when the provider named it. */
|
|
20
22
|
resetsAt?: number;
|
|
21
23
|
}
|
|
@@ -18,6 +18,10 @@ export function sanitizeProviderDiagnostic(value) {
|
|
|
18
18
|
const httpStatus = readField(value, "httpStatus");
|
|
19
19
|
const code = readField(value, "code");
|
|
20
20
|
const resetsAt = readField(value, "resetsAt");
|
|
21
|
+
const route = readField(value, "route");
|
|
22
|
+
if (route !== undefined && route !== "siwc")
|
|
23
|
+
return undefined;
|
|
24
|
+
const routed = route === "siwc" ? { route } : {};
|
|
21
25
|
if (!isProviderDiagnosticCategory(category))
|
|
22
26
|
return undefined;
|
|
23
27
|
if (httpStatus !== undefined && !isProviderDiagnosticStatus(httpStatus))
|
|
@@ -30,7 +34,7 @@ export function sanitizeProviderDiagnostic(value) {
|
|
|
30
34
|
return undefined;
|
|
31
35
|
if (category !== categoryForProviderStatus(httpStatus))
|
|
32
36
|
return undefined;
|
|
33
|
-
return buildProviderDiagnostic({ category, httpStatus, evidence, ...reset });
|
|
37
|
+
return buildProviderDiagnostic({ category, httpStatus, evidence, ...reset, ...routed });
|
|
34
38
|
}
|
|
35
39
|
if (evidence === "structured_transport") {
|
|
36
40
|
if (httpStatus !== undefined || typeof code !== "string")
|
|
@@ -56,6 +60,7 @@ export function sanitizeProviderDiagnostic(value) {
|
|
|
56
60
|
code,
|
|
57
61
|
evidence,
|
|
58
62
|
...reset,
|
|
63
|
+
...routed,
|
|
59
64
|
});
|
|
60
65
|
}
|
|
61
66
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":6,"generatedAt":"2026-
|
|
1
|
+
{"schemaVersion":6,"generatedAt":"2026-11-02T10:16:26.903Z","structureHash":"f0f3f20609cd9e60584b70d80d9c3220a803729c89f4deb1ae207b2688be24a2","files":{"alibaba-token-plan.json":"9f4d42cc765b2a875bcdcaa392bfdc1323c88968309baf9db9f611a26c595c73","amazon-bedrock.json":"a7e20838b095788cb9d5acbde49664b7f2868fad9b10fb4bbbd66635085bca2d","ant-ling.json":"628feab3ef6c7d80ce96f75999804e1d9f05fec3851e5302e30fff3b69db90f6","anthropic.json":"111384e9f5845e0247bd34edf2400ef7720fcbdac140b944cf8878192d8bd6ab","azure-openai-responses.json":"fd64c6a2a99fa87d768ed2f75f5c7813876df1dbe2f0d1776f7d9bd7ce2e61e4","bai.json":"7e6861aaf27d8af34e1771c6092602194fcec0a0046e6adfab9c3ee54d32b99f","baseten.json":"5bd3c98c61769388b3724368ef964d16b7a58e41a6edcf4ebceb9d4ac0a21d3b","cerebras.json":"2eb969b2956844f9a641d3de52c19f1791293c58f607282fb3b0ebfe85ff66f8","chatgpt-subscription.json":"98320f1d51709d26ca047b3d6e3191cfe2f192f1b27897cbb492f47b0e5a5030","cloudflare-ai-gateway.json":"ac281b8cc18eb84ba61f215eff3234d14732cd66239617fb59d6812b13d6473f","cloudflare-workers-ai.json":"1b9ad4360bde817f0ea358431020308f1052840e2278a57a93629f1790072d2e","deepseek.json":"10a296fb3e898f7715c80890c8af0af4f5fd58f22dbcd6612fdc5d762157ccdc","fireworks.json":"56c557eb97573028e871c211cb17e1d60665eb12d67a712615db4bb255bc9253","github-copilot.json":"474fa92eb88ef242208ca93709ebe6d7f21754917d0cad6210bd1edf2b99a87e","google-vertex.json":"8f986b4aca8c685b0f3481bd252bda21e4858bf9015ad37fc5bcc44b7f01d076","google.json":"d25613a7377e81d785d571be514d67b35436511ce6946247dc66f8674e2a5035","groq.json":"087df37206e9eed8b10f1e558e4c496289bef60074b7e8afba4f20fb102b4ec2","huggingface.json":"209cdcf205961ef2ff203486da614232a86a069ebe0e98266189a14a235fac8f","meta.json":"563f5ec07973f52ab8c3eb26ef91a36ad8a79e950b37174935666cdcb624bf35","minimax-cn.json":"70df757e95e9ea2275bf451956b58a746020835f6cbcbd1a644d7fad5b9941d3","minimax.json":"747038b05f4fc24e41405e8bc810f893d7d5121e214e35e6f812f775769a3806","mistral.json":"aafecc66686645b1baa8c90e81c02856115f09999ff507f9bee004d2e1c77a28","moonshotai-cn.json":"a953eace729ef96c91195d477c63c776a1c40aa36085b61236ca1be0fe5b9337","moonshotai.json":"0435f9dc4e532c0764b6d33d8dfa83ce5fccdbcab54224c5ae74ec2d965290f5","nvidia.json":"0e45bcd4d5a897f6f6785c0e257a9fb03691cef3ff87d9254da5daa5075468da","openai.json":"d61200cdd22d931dfff8181a5ed8d636c89fb34637dcec80f07ec24f8c2ef5c6","opencode-go.json":"2a5e0424854edfc0ef9a49033e80a8eb32c01a98c56ba2879b36a9502baa4027","opencode.json":"0e08d3fc23284852968106c4e0c17e85adedf729db145b8c7871e9bd999845b1","opengateway.json":"30d8a056c65d066a1fded229cd298423574452b11b28c28565ec4710bbbf8613","openrouter.json":"1d576e444dc36c0d95c19c44b3fc13e1e6ff2af6dc55a14c371d79599befcfe9","qwen-token-plan-cn.json":"751b18bd443fceb16659ea33013fed90fbf3bbdb4e0ffd7607c93cee2b4cf40d","qwen-token-plan-individual.json":"35ffb0ba1a1ce3f68fc73262dd5e07fbcb5bf5911ad1e6e17bc0621e2894676a","qwen-token-plan.json":"e59b0a45e238578f80d0eea937a093b9cec7cb93751ae20a9e88b16c09eb5cdd","radius.json":"5241be199f873fd7474b1ccb1fc911af223903af681909029fefe9fb8b49f83c","together.json":"a68b8386a2f28f9e1570d296223fdbdc0ebcca8a322ade5b999212c0e60b772c","typesafe.json":"83409437dbf4c353ac413c0db4e7cf29902fc35e0e0197bdb4eeda2d7f3b07cd","venice.json":"6614b0b42eaeae73be35239c630414df1268aef624de540781d0fd45a4962c72","vercel-ai-gateway.json":"e3bb26317be06ee0636a7a13e10b3addc18c7589ffb1e7c7235277224c836c09","xai.json":"cf3f475fe5a13625f858171900ba3456c4d95ebb0bac5060cc25e29bb31d3457","xiaomi-token-plan-ams.json":"7fa339ba5675460b1ff4e5bca9e8ad0aa60b0277d75a989b1c196aa6ca2af34d","xiaomi-token-plan-cn.json":"ef7917c6ea0d3edc655c8a52082ee5f1330658aa6f10199d8f164eb4605ef0a0","xiaomi-token-plan-sgp.json":"addda93f6b70ba879db97e3cfd2e4f2efc7cda47d7daa78c23c5b0e51162de5c","xiaomi.json":"5a42caf49d3e4d7946d60386e9899c2146bf8e3cbaff4c576ee4f49b398cc11c","zai-coding-cn.json":"2b6f85e04b5c568e320b1bde25b1c1614af9342e4c1ba0e1d5bb248aaf50cfe9","zai.json":"e9cc408e1756de8abc70148d63d89d11a63c65b7ef06ad0661cd1328e4209ba3"}}
|