@oh-my-pi/pi-ai 18.2.11 → 18.3.0

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 (114) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/THIRD-PARTY-NOTICES.txt +1 -1
  3. package/dist/types/auth/abort.d.ts +6 -0
  4. package/dist/types/auth/affinity.d.ts +68 -0
  5. package/dist/types/auth/blocks.d.ts +115 -0
  6. package/dist/types/auth/cascade.d.ts +147 -0
  7. package/dist/types/auth/health.d.ts +66 -0
  8. package/dist/types/auth/oauth.d.ts +93 -0
  9. package/dist/types/auth/policy.d.ts +20 -0
  10. package/dist/types/auth/pool.d.ts +206 -0
  11. package/dist/types/auth/rank.d.ts +53 -0
  12. package/dist/types/auth/refresh.d.ts +59 -0
  13. package/dist/types/auth/resets.d.ts +28 -0
  14. package/dist/types/auth/rotation.d.ts +66 -0
  15. package/dist/types/auth/select.d.ts +86 -0
  16. package/dist/types/auth/sqlite-credential-store.d.ts +11 -10
  17. package/dist/types/auth/store.d.ts +195 -0
  18. package/dist/types/auth/types.d.ts +1132 -0
  19. package/dist/types/auth/usage-cache.d.ts +73 -0
  20. package/dist/types/auth/usage-report.d.ts +32 -0
  21. package/dist/types/auth/usage.d.ts +107 -0
  22. package/dist/types/auth-broker/discover.d.ts +14 -1
  23. package/dist/types/auth-broker/refresher.d.ts +1 -1
  24. package/dist/types/auth-broker/remote-store.d.ts +8 -11
  25. package/dist/types/auth-broker/types.d.ts +1 -1
  26. package/dist/types/auth-gateway/dispatch.d.ts +1 -1
  27. package/dist/types/auth-gateway/types.d.ts +2 -0
  28. package/dist/types/auth-retry.d.ts +21 -21
  29. package/dist/types/auth-storage.d.ts +32 -1332
  30. package/dist/types/index.d.ts +2 -0
  31. package/dist/types/provider-session-state.d.ts +2 -2
  32. package/dist/types/providers/anthropic-client.d.ts +6 -0
  33. package/dist/types/providers/anthropic-compaction.d.ts +1 -1
  34. package/dist/types/providers/anthropic-user-profiles.d.ts +88 -0
  35. package/dist/types/providers/anthropic-wire.d.ts +20 -14
  36. package/dist/types/providers/anthropic.d.ts +3 -2
  37. package/dist/types/providers/apple-foundation-models.d.ts +37 -0
  38. package/dist/types/providers/openai-codex/access-programs.d.ts +13 -0
  39. package/dist/types/providers/openai-codex/request-transformer.d.ts +4 -0
  40. package/dist/types/providers/register-builtins.d.ts +2 -0
  41. package/dist/types/providers/transform-messages.d.ts +1 -1
  42. package/dist/types/registry/oauth/types.d.ts +1 -1
  43. package/dist/types/registry/types.d.ts +1 -1
  44. package/dist/types/types.d.ts +62 -23
  45. package/dist/types/usage/claude-reset.d.ts +3 -1
  46. package/dist/types/usage/registry.d.ts +10 -0
  47. package/dist/types/usage.d.ts +19 -3
  48. package/dist/types/utils/schema/foundation-models.d.ts +15 -0
  49. package/dist/types/utils/schema/index.d.ts +1 -0
  50. package/package.json +10 -7
  51. package/src/api-registry.ts +1 -0
  52. package/src/auth/abort.ts +17 -0
  53. package/src/auth/affinity.ts +267 -0
  54. package/src/auth/blocks.ts +572 -0
  55. package/src/auth/cascade.ts +491 -0
  56. package/src/auth/health.ts +432 -0
  57. package/src/auth/oauth.ts +352 -0
  58. package/src/auth/policy.ts +141 -0
  59. package/src/auth/pool.ts +784 -0
  60. package/src/auth/rank.ts +146 -0
  61. package/src/auth/refresh.ts +575 -0
  62. package/src/auth/resets.ts +252 -0
  63. package/src/auth/rotation.ts +496 -0
  64. package/src/auth/select.ts +1058 -0
  65. package/src/auth/sqlite-credential-store.ts +22 -20
  66. package/src/auth/store.ts +242 -0
  67. package/src/auth/types.ts +1230 -0
  68. package/src/auth/usage-cache.ts +332 -0
  69. package/src/auth/usage-report.ts +269 -0
  70. package/src/auth/usage.ts +816 -0
  71. package/src/auth-broker/discover.ts +170 -22
  72. package/src/auth-broker/refresher.ts +7 -6
  73. package/src/auth-broker/remote-store.ts +12 -39
  74. package/src/auth-broker/server.ts +25 -25
  75. package/src/auth-broker/types.ts +1 -1
  76. package/src/auth-gateway/dispatch.ts +18 -14
  77. package/src/auth-gateway/http.ts +2 -1
  78. package/src/auth-gateway/routes/video.ts +1 -1
  79. package/src/auth-gateway/server.ts +3 -2
  80. package/src/auth-gateway/types.ts +2 -0
  81. package/src/auth-retry.ts +47 -30
  82. package/src/auth-storage.ts +172 -7612
  83. package/src/index.ts +2 -0
  84. package/src/provider-session-state.ts +2 -2
  85. package/src/providers/anthropic-client.ts +31 -9
  86. package/src/providers/anthropic-compaction.ts +28 -11
  87. package/src/providers/anthropic-identity.ts +1 -1
  88. package/src/providers/anthropic-messages-server.ts +2 -0
  89. package/src/providers/anthropic-user-profiles.ts +195 -0
  90. package/src/providers/anthropic-wire.ts +18 -12
  91. package/src/providers/anthropic.ts +508 -505
  92. package/src/providers/apple-foundation-models.ts +406 -0
  93. package/src/providers/cursor.ts +4 -3
  94. package/src/providers/google-gemini-cli.ts +2 -2
  95. package/src/providers/openai-codex/access-programs.ts +70 -0
  96. package/src/providers/openai-codex/request-transformer.ts +2 -0
  97. package/src/providers/openai-codex-responses.ts +60 -1
  98. package/src/providers/pi-native-server.ts +3 -2
  99. package/src/providers/register-builtins.ts +6 -0
  100. package/src/providers/transform-messages.ts +9 -0
  101. package/src/registry/oauth/types.ts +1 -1
  102. package/src/registry/types.ts +1 -1
  103. package/src/stream.ts +47 -9
  104. package/src/types.ts +66 -20
  105. package/src/usage/claude-reset.ts +20 -1
  106. package/src/usage/claude.ts +14 -0
  107. package/src/usage/google-antigravity.ts +1 -0
  108. package/src/usage/openai-codex.ts +140 -5
  109. package/src/usage/opencode-go.ts +4 -0
  110. package/src/usage/registry.ts +72 -0
  111. package/src/usage/zai.ts +1 -0
  112. package/src/usage.ts +20 -4
  113. package/src/utils/schema/foundation-models.ts +255 -0
  114. package/src/utils/schema/index.ts +1 -0
@@ -0,0 +1,206 @@
1
+ import type { BlockStoreHealth } from "./blocks.js";
2
+ import type { AccountPolicies } from "./policy.js";
3
+ import type { AuthCredentialStore } from "./store.js";
4
+ import type { AuthCredential, AuthCredentialEntry, AuthCredentialSnapshot, AuthCredentialSnapshotEntry, AuthStorageData, CredentialDisabledEvent, CredentialsApi, DisabledCredentialSummary, OAuthCredential, StoredAuthCredential } from "./types.js";
5
+ import type { UsageCredential } from "../usage.js";
6
+ /** One stored credential row as cached in memory. */
7
+ export type StoredCredential = {
8
+ id: number;
9
+ credential: AuthCredential;
10
+ };
11
+ /** Credential equality used for snapshot change detection. */
12
+ export declare function authCredentialEquals(left: AuthCredential, right: AuthCredential): boolean;
13
+ /** Dependencies for credential validation, corruption handling, and assignment reset. */
14
+ export interface CredentialPoolOptions {
15
+ policies: AccountPolicies;
16
+ blockHealth: BlockStoreHealth;
17
+ /** Called whenever a provider's credential set changed locally. */
18
+ onReset: (provider: string) => void;
19
+ }
20
+ /** In-memory credential snapshot over an AuthCredentialStore: CRUD, change detection, events. */
21
+ export declare class CredentialPool implements CredentialsApi {
22
+ #private;
23
+ constructor(store: AuthCredentialStore, options: CredentialPoolOptions);
24
+ get closed(): boolean;
25
+ get generation(): number;
26
+ providers(): IterableIterator<string>;
27
+ /** Reset session affinity and round-robin state after a local credential change. */
28
+ reset(provider: string): void;
29
+ /** Re-list one provider and adopt its persisted rows in memory. */
30
+ reloadProvider(provider: string): StoredAuthCredential[];
31
+ /** Persist a login-entered API key. */
32
+ storeLoginApiKey(provider: string, key: string): Promise<void>;
33
+ /**
34
+ * Close the underlying credential store.
35
+ *
36
+ * After calling this, the instance must not be reused.
37
+ */
38
+ close(): void;
39
+ /**
40
+ * Reload state after another process commits to the backing store, then
41
+ * notify snapshot consumers even when only credential blocks changed.
42
+ */
43
+ poll(): Promise<boolean>;
44
+ /**
45
+ * Adopt credentials another process committed before selecting or rotating.
46
+ *
47
+ * The store is shared across every omp process, but the pool is an
48
+ * in-process cache refreshed only by this process's own writes. Without
49
+ * this a long-running session ranks a stale pool for its whole lifetime:
50
+ * `omp auth` in another terminal is invisible, rotation reports no usable
51
+ * sibling while a freshly added account sits unblocked in SQLite, and the
52
+ * turn degrades to the fallback chain. The auth-broker path already polls;
53
+ * direct-store sessions had no equivalent.
54
+ *
55
+ * A poll is two cheap reads (`PRAGMA data_version` plus the auth revision)
56
+ * and re-lists credentials only when another connection committed, so it
57
+ * runs on every resolution rather than on a timer that would make recovery
58
+ * depend on wall-clock spacing. It sits on the paths that read the pool —
59
+ * OAuth selection, and the two public usage-limit entry points — and is
60
+ * idempotent, so a rotation reached through `markUsageLimitReached` costs
61
+ * one extra `data_version` read and no second reload.
62
+ */
63
+ adoptExternalChanges(): Promise<void>;
64
+ onGeneration(listener: (generation: number) => void): () => void;
65
+ bump(reason: string): void;
66
+ /**
67
+ * Subscribe to {@link CredentialDisabledEvent}s. Multiple subscribers are supported and
68
+ * each fires for every disable event; subscribers are invoked in registration order with
69
+ * exceptions and async rejections isolated per-listener so a misbehaving subscriber
70
+ * cannot break the disable path or starve the rest of the chain.
71
+ *
72
+ * If `credential_disabled` events were emitted while no listener was subscribed, they are
73
+ * replayed (in insertion order) to the listener that triggers the empty→non-empty
74
+ * transition. The drain is one-shot — listeners that subscribe after that no longer see
75
+ * past events.
76
+ *
77
+ * Returns an unsubscribe function. The function is idempotent: calling it more than once
78
+ * is a no-op. After every subscriber has unsubscribed, subsequent disable events buffer
79
+ * again until the next subscribe.
80
+ *
81
+ * @param listener Callback invoked with each disable event. May be sync or async.
82
+ * @returns A function that removes this listener from the subscriber set.
83
+ */
84
+ onDisabled(listener: (event: CredentialDisabledEvent) => void | Promise<void>): () => void;
85
+ /**
86
+ * Reload credentials from storage.
87
+ */
88
+ reload(): Promise<void>;
89
+ /**
90
+ * Gets cached credentials for a provider.
91
+ * @param provider - Provider name (e.g., "anthropic", "openai")
92
+ * @returns Array of stored credentials, empty if none exist
93
+ */
94
+ entries(provider: string): StoredCredential[];
95
+ /**
96
+ * Updates in-memory credential cache for a provider.
97
+ * Removes the provider entry entirely if credentials array is empty.
98
+ * @param provider - Provider name (e.g., "anthropic", "openai")
99
+ * @param credentials - Array of stored credentials to cache
100
+ */
101
+ replace(provider: string, credentials: StoredCredential[]): void;
102
+ noteBearer(provider: string, bearer: string, credentialId: number | undefined): void;
103
+ idForBearer(provider: string, bearer: string): number | undefined;
104
+ dedupe(provider: string, credentials: AuthCredential[]): AuthCredential[];
105
+ pruneDuplicates(provider: string, entries: StoredCredential[]): Promise<StoredCredential[]>;
106
+ /** Returns all credentials for a provider as an array. */
107
+ credentials(provider: string): AuthCredential[];
108
+ /**
109
+ * Persist a refreshed credential by id only while the row still matches this
110
+ * process's snapshot. A peer rotation wins the CAS and is reloaded instead of
111
+ * being overwritten after this process releases its refresh lease.
112
+ *
113
+ * Returns the row's current index, or -1 when it was disabled or removed.
114
+ */
115
+ replaceById(provider: string, id: number, credential: AuthCredential): number;
116
+ /**
117
+ * CAS-disable the row with `id`, but only if its persisted credential still
118
+ * matches `expected` — i.e. no peer/login rotated it while we refreshed.
119
+ * Addresses the row by id (re-resolved here, then matched on `data` in the
120
+ * store) so a concurrent reorder can't tear down the wrong credential.
121
+ */
122
+ disableIfMatches(provider: string, id: number, expected: AuthCredential, disabledCause: string): boolean;
123
+ emitDisabled(event: CredentialDisabledEvent): void;
124
+ /**
125
+ * Get credential for a provider (first entry if multiple).
126
+ */
127
+ get(provider: string): AuthCredential | undefined;
128
+ /**
129
+ * Set credential for a provider.
130
+ */
131
+ set(provider: string, credential: AuthCredentialEntry): Promise<void>;
132
+ /**
133
+ * List stored credential rows, optionally filtered by provider.
134
+ */
135
+ list(provider?: string): StoredAuthCredential[];
136
+ upsertOAuth(provider: string, credential: OAuthCredential): Promise<void>;
137
+ /**
138
+ * Remove credential for a provider.
139
+ */
140
+ remove(provider: string): Promise<void>;
141
+ /**
142
+ * Remove one stored credential for a provider.
143
+ */
144
+ removeById(provider: string, credentialId: number): Promise<boolean>;
145
+ /**
146
+ * Check if credentials exist for a provider in storage.
147
+ */
148
+ has(provider: string): boolean;
149
+ /**
150
+ * Check if OAuth credentials are configured for a provider.
151
+ */
152
+ hasOAuth(provider: string): boolean;
153
+ /**
154
+ * Get OAuth credentials for a provider.
155
+ */
156
+ getOAuth(provider: string): OAuthCredential | undefined;
157
+ /**
158
+ * Get all credentials.
159
+ */
160
+ all(): AuthStorageData;
161
+ /**
162
+ * Build a redacted snapshot of all loaded credentials for the auth-broker
163
+ * wire. OAuth refresh tokens are replaced with {@link REMOTE_REFRESH_SENTINEL}
164
+ * so clients never see the actual refresh token.
165
+ *
166
+ * Callers must {@link CredentialPool.reload} first when serving a stale snapshot
167
+ * (the broker server's HTTP handler does this).
168
+ */
169
+ snapshot(): AuthCredentialSnapshot;
170
+ /**
171
+ * Disabled credential tombstones for display surfaces (`omp usage`,
172
+ * broker `GET /v1/credentials/disabled`). Empty when the backing store
173
+ * keeps no tombstones or the remote broker predates the endpoint.
174
+ */
175
+ listDisabled(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;
176
+ /**
177
+ * Force the backing store to revalidate its credential snapshot, then
178
+ * reload. Remote broker stores re-fetch the snapshot; local stores are
179
+ * always current, so only the reload runs. Callers that pair live
180
+ * per-credential data with stored identities (`omp usage`) use this so a
181
+ * disk-cached snapshot cannot misattribute fresh reports.
182
+ */
183
+ revalidate(): Promise<void>;
184
+ /**
185
+ * Disable the credential with the given id and emit a
186
+ * {@link CredentialDisabledEvent}. Used by the auth-broker server to honour
187
+ * `POST /v1/credential/:id/disable`. Returns `false` when no such row exists.
188
+ */
189
+ disable(id: number, disabledCause: string): Promise<boolean>;
190
+ /**
191
+ * Upsert a credential into the underlying store, refresh the in-memory
192
+ * snapshot, and return the redacted snapshot entries for the provider.
193
+ *
194
+ * Used by the auth-broker server to honour `POST /v1/credential`. The
195
+ * persistence layer (`SqliteAuthCredentialStore.upsertAuthCredential`)
196
+ * does identity-key matching, so re-uploading the same email/account replaces
197
+ * the existing row instead of inserting a duplicate.
198
+ */
199
+ upsert(provider: string, credential: AuthCredential): Promise<AuthCredentialSnapshotEntry[]>;
200
+ /**
201
+ * Find the stored credential id matching a {@link UsageCredential} so the
202
+ * refresh override can address the row. Mirrors the matching logic in
203
+ * `UsageService.persistRefreshedCredential`.
204
+ */
205
+ findIdForUsageCredential(provider: string, previous: UsageCredential): number | undefined;
206
+ }
@@ -0,0 +1,53 @@
1
+ import type { PlanGate, UsageReport } from "../usage.js";
2
+ import type { AuthCredential, ApiKeyCredential, OAuthCredential } from "./types.js";
3
+ /** Selected stored credential and its pool index. */
4
+ export type CredentialSelection<T extends AuthCredential> = {
5
+ credential: T;
6
+ index: number;
7
+ };
8
+ /** Selected OAuth credential for routing. */
9
+ export type OAuthSelection = CredentialSelection<OAuthCredential>;
10
+ /** Selected API-key credential for routing. */
11
+ export type ApiKeySelection = CredentialSelection<ApiKeyCredential>;
12
+ /** Selected credential with its observed usage. */
13
+ export type UsageCandidate<T extends AuthCredential> = {
14
+ selection: CredentialSelection<T>;
15
+ usage: UsageReport | null;
16
+ usageChecked: boolean;
17
+ /** Present after policy-aware ranking; used to decide whether a warm automatic pin may be evicted. */
18
+ inReserve?: boolean;
19
+ /** True only when reserve ranking had a usable remaining-fraction measurement. */
20
+ reserveMeasured?: boolean;
21
+ };
22
+ /** OAuth credential eligible for usage ranking. */
23
+ export type OAuthCandidate = UsageCandidate<OAuthCredential>;
24
+ /** API-key credential eligible for usage ranking. */
25
+ export type ApiKeyCandidate = UsageCandidate<ApiKeyCredential>;
26
+ /** Ranked candidate with a possible block deadline. */
27
+ export type UsageRankingResult<T extends AuthCredential> = UsageCandidate<T> & {
28
+ blockedUntil: number | undefined;
29
+ };
30
+ /** Candidate with all account-policy and usage ranking metrics. */
31
+ export type UsageRankedCandidate<T extends AuthCredential> = UsageCandidate<T> & {
32
+ blocked: boolean;
33
+ blockedUntil?: number;
34
+ inReserve: boolean;
35
+ reserveMeasured?: boolean;
36
+ accountPriority: number;
37
+ hasPriorityBoost: boolean;
38
+ usageMeasured: boolean;
39
+ planPriority: number;
40
+ secondaryUsed: number;
41
+ secondaryRequiredDrain: number;
42
+ primaryUsed: number;
43
+ primaryRequiredDrain: number;
44
+ orderPos: number;
45
+ };
46
+ /** OAuth candidate with complete ranking metrics. */
47
+ export type RankedOAuthCandidate = UsageRankedCandidate<OAuthCredential>;
48
+ /** API-key candidate with complete ranking metrics. */
49
+ export type RankedApiKeyCandidate = UsageRankedCandidate<ApiKeyCredential>;
50
+ /** Rank accounts by model-plan eligibility when a plan gate applies. */
51
+ export declare function planPriority(gate: PlanGate | undefined, report: UsageReport | null): number;
52
+ /** Sort ranked candidates by blocks, plan, reserve, boost, hot window, usage and drain. */
53
+ export declare function orderUsageRankedCandidates<T extends AuthCredential>(candidates: UsageRankedCandidate<T>[], planGated: boolean): UsageCandidate<T>[];
@@ -0,0 +1,59 @@
1
+ import type { OAuthCredentials } from "../registry/oauth/types.js";
2
+ import type { Provider } from "../types.js";
3
+ import { type CredentialPool } from "./pool.js";
4
+ import type { AccountPolicies } from "./policy.js";
5
+ import { type AuthCredentialStore } from "./store.js";
6
+ import { type AuthCredentialSnapshotEntry, type AuthStorageOptions, type OAuthCredential, type StoredOAuthRefreshOptions, type StoredOAuthRefreshResult } from "./types.js";
7
+ /**
8
+ * Refresh OAuth access tokens this many ms before their stated expiry. The
9
+ * skew exists so callers downstream of {@link OAuthRefresher} (stream providers,
10
+ * usage probes, web_search) never observe a credential that is expired or
11
+ * about to expire mid-request — there's a single rotation point and everyone
12
+ * downstream trusts the token they receive.
13
+ *
14
+ * Set to 60s: comfortably absorbs request RTT + a clock-skew window without
15
+ * triggering a refresh on every request. Provider token endpoints typically
16
+ * mint access tokens with 30-60min lifetimes, so refreshing 60s early changes
17
+ * the rotation cadence by <4%.
18
+ */
19
+ export declare const OAUTH_REFRESH_SKEW_MS = 60000;
20
+ /** Merge provider refresh bytes with the stored OAuth row, preserving subtype metadata for every refresh path. */
21
+ export declare function mergeRefreshedCredential<T extends OAuthCredential>(current: T, refreshed: OAuthCredentials): T;
22
+ /** Dependencies for lease-guarded OAuth refresh. */
23
+ export interface OAuthRefresherDeps {
24
+ store: AuthCredentialStore;
25
+ pool: CredentialPool;
26
+ policies: AccountPolicies;
27
+ override?: AuthStorageOptions["refreshOAuthCredential"];
28
+ }
29
+ /** Single-flighted, lease-guarded OAuth refresh with compare-and-set persistence. */
30
+ export declare class OAuthRefresher {
31
+ #private;
32
+ constructor(deps: OAuthRefresherDeps);
33
+ /**
34
+ * Refresh one stored OAuth credential under durable row ownership.
35
+ */
36
+ refreshStored<T extends OAuthCredential = OAuthCredential>(provider: string, options: StoredOAuthRefreshOptions<T>): Promise<StoredOAuthRefreshResult<T>>;
37
+ /**
38
+ * Handles a definitively-dead OAuth grant discovered during refresh (`invalid_grant`,
39
+ * `revoked`, …): checks for a peer rotation that raced the failure, then CAS-disables
40
+ * the row and emits `credential_disabled`. Shared by the eager preflight refresh
41
+ * and the final-candidate refresh so both actually disable the credential — not
42
+ * just temporarily block it — on a definitive failure.
43
+ *
44
+ * Returns `"disabled"` once the row is torn down, `"peer-rotated"` when a concurrent
45
+ * process refreshed the same row first (the persisted refresh token no longer matches
46
+ * what we attempted — the caller should reload and retry with the new credential), or
47
+ * `"cas-lost"` when the disable itself lost a race and the caller should reload before
48
+ * continuing.
49
+ */
50
+ disableDefinitiveFailure(provider: string, credentialId: number | undefined, attemptedCredential: OAuthCredential, index: number, errorMsg: string): Promise<"disabled" | "peer-rotated" | "cas-lost">;
51
+ refresh(provider: Provider, credential: OAuthCredential, credentialId: number | undefined, signal?: AbortSignal): Promise<OAuthCredentials>;
52
+ /**
53
+ * Refresh the OAuth credential with the given id through a per-credential
54
+ * single-flight. Concurrent callers for the same row await the same upstream
55
+ * refresh attempt, which is required for providers that rotate refresh tokens
56
+ * on every successful refresh.
57
+ */
58
+ refreshById(id: number, signal?: AbortSignal): Promise<AuthCredentialSnapshotEntry>;
59
+ }
@@ -0,0 +1,28 @@
1
+ import type { CredentialBlocks } from "./blocks.js";
2
+ import type { OAuthAccounts } from "./oauth.js";
3
+ import type { CredentialPool } from "./pool.js";
4
+ import type { AuthCredentialStore } from "./store.js";
5
+ import type { ListResetCreditsOptions, RedeemResetCreditOptions, ResetCreditAccountStatus, ResetCreditRedeemOutcome, ResetsApi } from "./types.js";
6
+ import type { UsageService } from "./usage.js";
7
+ import type { UsageCache } from "./usage-cache.js";
8
+ /** Dependencies for listing and redeeming stored-account reset credits. */
9
+ export interface ResetCreditsDeps {
10
+ store: AuthCredentialStore;
11
+ pool: CredentialPool;
12
+ oauth: OAuthAccounts;
13
+ usage: UsageService;
14
+ usageCache: UsageCache;
15
+ blocks: CredentialBlocks;
16
+ }
17
+ /** Saved rate-limit resets (Codex / Claude): list offers and redeem one per account. */
18
+ export declare class ResetCredits implements ResetsApi {
19
+ #private;
20
+ constructor(deps: ResetCreditsDeps);
21
+ /** List live saved-reset balances and eligibility for one provider's stored OAuth accounts. */
22
+ list(options?: ListResetCreditsOptions): Promise<ResetCreditAccountStatus[]>;
23
+ /**
24
+ * Redeem a stored account's saved reset after checking its live offer.
25
+ * Business refusals return a code; transport errors may throw without losing Claude's request ID.
26
+ */
27
+ redeem(options: RedeemResetCreditOptions): Promise<ResetCreditRedeemOutcome>;
28
+ }
@@ -0,0 +1,66 @@
1
+ import type { CredentialRankingContext, CredentialRankingStrategy } from "../usage.js";
2
+ import type { RankingStrategyResolver } from "../usage/registry.js";
3
+ import type { CredentialBlocks } from "./blocks.js";
4
+ import type { KeyOverrides } from "./cascade.js";
5
+ import type { SessionAffinity } from "./affinity.js";
6
+ import type { CredentialPool } from "./pool.js";
7
+ import type { AuthCredentialStore } from "./store.js";
8
+ import type { InvalidateCredentialMatchingOptions, LimitsApi, MarkUsageLimitOptions, RotateCredentialOptions, UsageLimitMarkResult } from "./types.js";
9
+ import type { UsageService } from "./usage.js";
10
+ /** Routing scope and strategy for one failed credential. */
11
+ export type CredentialBlockRouting = {
12
+ providerKey: string;
13
+ strategy: CredentialRankingStrategy | undefined;
14
+ rankingContext: CredentialRankingContext;
15
+ blockScope: string | undefined;
16
+ siblingBlockScopes: readonly string[];
17
+ };
18
+ /** Dependencies for rate-limit marking and credential rotation. */
19
+ export interface RateLimitsDeps {
20
+ store: AuthCredentialStore;
21
+ pool: CredentialPool;
22
+ overrides: KeyOverrides;
23
+ blocks: CredentialBlocks;
24
+ affinity: SessionAffinity;
25
+ usage: UsageService;
26
+ strategies: RankingStrategyResolver;
27
+ }
28
+ /** Usage-limit marking, credential rotation after failures, and bearer-matched invalidation. */
29
+ export declare class RateLimits implements LimitsApi {
30
+ #private;
31
+ constructor(deps: RateLimitsDeps);
32
+ /**
33
+ * Marks the current session's credential as temporarily blocked due to usage limits.
34
+ * Uses usage reports to determine accurate reset time when available.
35
+ * Returns whether a sibling credential is available now; when none is, also
36
+ * reports the earliest time a blocked sibling becomes available again so
37
+ * callers can wait for the sibling instead of the provider's full window.
38
+ */
39
+ markReached(provider: string, sessionId: string | undefined, options?: MarkUsageLimitOptions): Promise<UsageLimitMarkResult>;
40
+ invalidateMatching(provider: string, apiKey: string, options?: InvalidateCredentialMatchingOptions): Promise<boolean>;
41
+ /**
42
+ * Rotate away from the credential that failed after a retryable auth error —
43
+ * step (c) of the auth-retry policy. Prefer the failed stored row id supplied
44
+ * in `options.credentialId`, then the failed bearer supplied in
45
+ * `options.apiKey`, so overlapping requests cannot redirect rotation through
46
+ * stale session stickiness. Fall back to the session-sticky credential only
47
+ * when neither explicit target is available. For hard-auth errors, an explicit
48
+ * target that no longer matches storage returns `false` without mutation.
49
+ * Delayed usage-limit and account-policy errors may instead recover the durable
50
+ * OAuth row from the bearer fingerprint recorded when the request resolved.
51
+ *
52
+ * - usage-limit / account-rate-limit error → {@link RateLimits.markReached}
53
+ * (temporary block via its own backoff — default plus server usage-report
54
+ * reset; sticky left intact so the next resolve re-ranks around the block).
55
+ * - exact model-entitlement denial (Codex ChatGPT account or Cursor plan) →
56
+ * temporarily block only that requested model, then rotate.
57
+ * - other account-scoped policy denial → temporarily block that account
58
+ * without marking its credential suspect, then rotate through siblings.
59
+ * - otherwise (hard 401 / auth failure) → mark the credential suspect (or
60
+ * reload when no broker hook is wired) and block it, then drop matching
61
+ * sticky state.
62
+ *
63
+ * Returns whether another usable credential of the same type remains.
64
+ */
65
+ rotate(provider: string, sessionId: string | undefined, options?: RotateCredentialOptions): Promise<boolean>;
66
+ }
@@ -0,0 +1,86 @@
1
+ import type { Provider } from "../types.js";
2
+ import type { CredentialRankingContext, CredentialRankingStrategy, PlanGate, UsageReport } from "../usage.js";
3
+ import type { RankingStrategyResolver } from "../usage/registry.js";
4
+ import type { SessionAffinity } from "./affinity.js";
5
+ import { type CredentialBlocks } from "./blocks.js";
6
+ import type { AccountPolicies } from "./policy.js";
7
+ import { type CredentialPool } from "./pool.js";
8
+ import { type ApiKeySelection, type OAuthSelection } from "./rank.js";
9
+ import { type OAuthRefresher } from "./refresh.js";
10
+ import type { AuthCredentialStore } from "./store.js";
11
+ import type { ApiKeyCredential, AuthApiKeyOptions, AuthCredential, OAuthCredential } from "./types.js";
12
+ import type { UsageService } from "./usage.js";
13
+ /** Temporary block after a transient OAuth refresh failure. */
14
+ export declare const OAUTH_REFRESH_FAILURE_BACKOFF_MS: number;
15
+ /** OAuth bearer and credential chosen for a request, with its durable row id when available. */
16
+ export type OAuthResolutionResult = {
17
+ apiKey: string;
18
+ credential: OAuthCredential;
19
+ credentialId?: number;
20
+ };
21
+ /** Options for CredentialSelector.tryOAuth when evaluating one OAuth credential. */
22
+ export type TryOAuthOptions = {
23
+ checkUsage: boolean;
24
+ allowBlocked: boolean;
25
+ prefetchedUsage?: UsageReport | null;
26
+ usagePrechecked?: boolean;
27
+ planGate?: PlanGate;
28
+ enforcePlanRequirement?: boolean;
29
+ strategy?: CredentialRankingStrategy;
30
+ rankingContext?: CredentialRankingContext;
31
+ blockScope?: string;
32
+ blockScopes?: readonly string[];
33
+ /** When false, a definitive failure of THIS credential returns undefined instead of falling back to the ranked/round-robin selector (target-only resolution). */
34
+ allowFallback?: boolean;
35
+ };
36
+ /** Services consulted by CredentialSelector for policy, usage, blocks, refresh, and session affinity. */
37
+ export interface CredentialSelectorDeps {
38
+ store: AuthCredentialStore;
39
+ pool: CredentialPool;
40
+ policies: AccountPolicies;
41
+ blocks: CredentialBlocks;
42
+ affinity: SessionAffinity;
43
+ usage: UsageService;
44
+ refresher: OAuthRefresher;
45
+ strategies: RankingStrategyResolver;
46
+ }
47
+ /** Picks which stored credential serves a request: ordering, usage ranking, OAuth refresh ladder. */
48
+ export declare class CredentialSelector {
49
+ #private;
50
+ constructor(deps: CredentialSelectorDeps);
51
+ /** Restart round-robin assignments after a provider's credential set changes. */
52
+ resetRoundRobin(provider: string): void;
53
+ /**
54
+ * Selects a credential of the specified type for a provider.
55
+ * Returns both the credential and its index in the original array (for updates/removal).
56
+ * Uses deterministic hashing for session stickiness and skips blocked credentials when possible.
57
+ */
58
+ selectByType<T extends AuthCredential["type"]>(provider: string, type: T, sessionId?: string, filter?: (credential: AuthCredential) => boolean): {
59
+ credential: Extract<AuthCredential, {
60
+ type: T;
61
+ }>;
62
+ index: number;
63
+ } | undefined;
64
+ selectApiKey(provider: string, sessionId: string | undefined, options: AuthApiKeyOptions | undefined, filter?: (credential: ApiKeyCredential) => boolean): Promise<ApiKeySelection | undefined>;
65
+ /**
66
+ * Resolves an OAuth credential, trying credentials in priority order.
67
+ *
68
+ * Resolution ladder — a request in hand always beats "no API key":
69
+ * 1. strict: unblocked credentials only, usage limits respected, plan
70
+ * filter enforced (when any account is confirmed eligible);
71
+ * 2. plan-fitting last resort: same plan filter, but blocked/exhausted
72
+ * accounts are allowed (blocked candidates rank earliest-unblocking
73
+ * first) so the caller gets real usage-limit semantics from the wire
74
+ * instead of a missing key;
75
+ * 3. unfiltered last resort: the plan filter matched nothing usable —
76
+ * skip it and try every account once; the server is the final arbiter
77
+ * of model access.
78
+ *
79
+ * Returns both the API key bytes for outbound requests AND the refreshed
80
+ * {@link OAuthCredential} so callers needing identity metadata (account id,
81
+ * project id, etc.) do not have to dereference the snapshot themselves.
82
+ */
83
+ resolveOAuth(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<OAuthResolutionResult | undefined>;
84
+ /** Attempts to use a single OAuth credential, checking usage and refreshing token. */
85
+ tryOAuth(provider: Provider, selection: OAuthSelection, providerKey: string, sessionId: string | undefined, options: AuthApiKeyOptions | undefined, usageOptions: TryOAuthOptions): Promise<OAuthResolutionResult | undefined>;
86
+ }
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * SQLite-backed credential persistence for AuthStorage.
3
3
  *
4
- * The public AuthCredentialStore interface remains in ../auth-storage so local
5
- * and remote stores share the same contract.
4
+ * The AuthCredentialStore contract lives in ./store so local and remote stores
5
+ * share the same interface.
6
6
  */
7
7
  import type { Database } from "bun:sqlite";
8
8
  import { isSqliteBusyError, isSqliteCorruptionError } from "@oh-my-pi/pi-utils";
9
- import type { AuthCredential, AuthCredentialStore, CredentialRefreshLeaseFence, DisabledCredentialSummary, StoredAuthCredential, StoredCredentialBlock } from "../auth-storage.js";
9
+ import type { AuthCredentialStore, CredentialRefreshLeaseFence } from "./store.js";
10
+ import type { AuthCredential, DisabledCredentialSummary, StoredAuthCredential, StoredCredentialBlock } from "./types.js";
10
11
  import type { OAuthCredentials } from "../registry/oauth/types.js";
11
12
  import type { ClientUsageReport, ClientUsageSummary, UsageHistoryEntry, UsageHistoryQuery } from "../usage.js";
12
13
  export declare const USAGE_REPORT_TTL_MS: number;
@@ -33,11 +34,11 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
33
34
  static open(dbPath?: string): Promise<SqliteAuthCredentialStore>;
34
35
  listAuthCredentials(provider?: string): StoredAuthCredential[];
35
36
  listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]>;
36
- replaceAuthCredentialsForProvider(provider: string, credentials: AuthCredential[]): StoredAuthCredential[];
37
- upsertAuthCredentialForProvider(provider: string, credential: AuthCredential): StoredAuthCredential[];
37
+ replaceAuthCredentials(provider: string, credentials: AuthCredential[]): Promise<StoredAuthCredential[]>;
38
+ upsertAuthCredential(provider: string, credential: AuthCredential): Promise<StoredAuthCredential[]>;
38
39
  updateAuthCredential(id: number, credential: AuthCredential): void;
39
40
  tryUpdateAuthCredentialIfMatches(id: number, expectedData: string, credential: AuthCredential, lease?: CredentialRefreshLeaseFence): boolean;
40
- deleteAuthCredential(id: number, disabledCause: string): void;
41
+ deleteAuthCredential(id: number, disabledCause: string): Promise<boolean>;
41
42
  /**
42
43
  * CAS-style disable: only soft-deletes the row when its `data` column still
43
44
  * matches `expectedData` and the row has not already been disabled. Used by
@@ -45,7 +46,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
45
46
  * row between our pre-check and the disable.
46
47
  */
47
48
  tryDisableAuthCredentialIfMatches(id: number, expectedData: string, disabledCause: string, lease?: CredentialRefreshLeaseFence): boolean;
48
- deleteAuthCredentialsForProvider(provider: string, disabledCause: string): void;
49
+ deleteAuthCredentials(provider: string, disabledCause: string): Promise<void>;
49
50
  getCache(key: string, options?: {
50
51
  includeExpired?: boolean;
51
52
  }): string | null;
@@ -72,7 +73,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
72
73
  * Save OAuth credentials for a provider.
73
74
  * Preserves unrelated identities and replaces only the matching credential.
74
75
  */
75
- saveOAuth(provider: string, credentials: OAuthCredentials): void;
76
+ saveOAuth(provider: string, credentials: OAuthCredentials): Promise<void>;
76
77
  /**
77
78
  * Get OAuth credentials for a provider.
78
79
  */
@@ -80,7 +81,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
80
81
  /**
81
82
  * Save API key for a provider (replaces existing).
82
83
  */
83
- saveApiKey(provider: string, apiKey: string): void;
84
+ saveApiKey(provider: string, apiKey: string): Promise<void>;
84
85
  /**
85
86
  * Get API key for a provider.
86
87
  */
@@ -92,7 +93,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
92
93
  /**
93
94
  * Delete all credentials for a provider.
94
95
  */
95
- deleteProvider(provider: string): void;
96
+ deleteProvider(provider: string): Promise<void>;
96
97
  /**
97
98
  * SQLite increments `data_version` when another connection commits. Own
98
99
  * writes leave it unchanged and already notify AuthStorage directly.