@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.
Files changed (45) hide show
  1. package/dist/api/openai-responses-shared.d.ts +2 -0
  2. package/dist/api/openai-responses-shared.js +19 -3
  3. package/dist/api/openai-responses.js +30 -9
  4. package/dist/auth/credential-store.d.ts +3 -1
  5. package/dist/auth/credential-store.js +78 -0
  6. package/dist/auth/oauth/kimi-coding.js +6 -4
  7. package/dist/auth/oauth/openai-chatgpt-model-catalog.d.ts +36 -0
  8. package/dist/auth/oauth/openai-chatgpt-model-catalog.js +104 -0
  9. package/dist/auth/oauth/openai-chatgpt.js +76 -0
  10. package/dist/auth/oauth/radius.js +1 -2
  11. package/dist/auth/oauth-refresh.d.ts +2 -0
  12. package/dist/auth/oauth-refresh.js +53 -7
  13. package/dist/auth/pool/failover.d.ts +0 -40
  14. package/dist/auth/pool/failover.js +0 -76
  15. package/dist/auth/pool/slots.js +10 -0
  16. package/dist/auth/resolve.js +3 -0
  17. package/dist/auth/types.d.ts +11 -0
  18. package/dist/models.d.ts +4 -5
  19. package/dist/models.js +36 -1
  20. package/dist/provider-diagnostic.d.ts +2 -0
  21. package/dist/provider-diagnostic.js +6 -1
  22. package/dist/providers/data/.manifest.json +1 -1
  23. package/dist/providers/data/opengateway.json +1 -1
  24. package/dist/providers/data/openrouter.json +1 -1
  25. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  26. package/dist/types.d.ts +6 -0
  27. package/dist/utils/empty-response-errors.d.ts +1 -2
  28. package/dist/utils/empty-response-errors.js +1 -2
  29. package/dist/utils/oauth-refresh-error.d.ts +8 -1
  30. package/dist/utils/oauth-refresh-error.js +3 -1
  31. package/dist/utils/provider-diagnostic-carrier.d.ts +10 -1
  32. package/dist/utils/provider-diagnostic-carrier.js +35 -0
  33. package/dist/utils/provider-diagnostic-sources.d.ts +2 -0
  34. package/dist/utils/provider-diagnostic-sources.js +5 -0
  35. package/dist/utils/provider-diagnostic-vocabulary.d.ts +1 -0
  36. package/dist/utils/provider-diagnostic-vocabulary.js +9 -0
  37. package/dist/utils/provider-retry.js +4 -1
  38. package/dist/utils/retry.d.ts +2 -0
  39. package/dist/utils/retry.js +14 -3
  40. package/dist/utils/siwc-errors.d.ts +2 -0
  41. package/dist/utils/siwc-errors.js +2 -0
  42. package/dist/utils/tool-name-match.js +46 -1
  43. package/package.json +2 -2
  44. package/dist/auth/pool/classify.d.ts +0 -16
  45. 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
@@ -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.
@@ -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);
@@ -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
- /** Remove the stored credential for a provider. */
185
- logout(providerId: string, options?: AuthOperationOptions & {
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-10-31T17:51:49.646Z","structureHash":"57fd6541d19ef32ef3b37aacd18414dee651505144735bf9b5caea77750265f6","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":"bba79f6a44aa3d46ee7f3e1cb88ebe8b2126028c5a7342929a8dd65b4b2cdf81","openrouter.json":"e77968c7c47ab09718a51e61898dc1c5eb2be0bf5c22f80b2db5322ce9895cb7","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":"bfca99a85ad33496c74b76ac9f153b1a246b1ab761cbf3ad99f7cf11aac63b76","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"}}
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"}}