@omnicross/subscriptions 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +451 -100
- package/dist/index.d.cts +153 -3
- package/dist/index.d.ts +153 -3
- package/dist/index.js +393 -42
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -27,7 +27,15 @@ export { claudeOAuth, codexOAuth, geminiOAuth } from './oauth.cjs';
|
|
|
27
27
|
* subscriptions layer).
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* The credential surface the subscription block consumes from the host store.
|
|
32
|
+
*
|
|
33
|
+
* The six ORIGINAL methods (active-account getters + refreshers) are REQUIRED;
|
|
34
|
+
* the three by-id methods (subscription-account-scheduling, design D6) are
|
|
35
|
+
* OPTIONAL and feature-detected so every existing lightweight test double that
|
|
36
|
+
* implements only the six keeps compiling and the single-account active path is
|
|
37
|
+
* untouched.
|
|
38
|
+
*/
|
|
31
39
|
interface SubscriptionCredentialStore {
|
|
32
40
|
/** Full decrypted account-tokens config (all subscription providers). */
|
|
33
41
|
getFullConfig(): Promise<AccountTokensConfig>;
|
|
@@ -41,6 +49,124 @@ interface SubscriptionCredentialStore {
|
|
|
41
49
|
refreshGeminiToken(): Promise<boolean>;
|
|
42
50
|
/** Current valid OpenCodeGo static API key; `null` if none. */
|
|
43
51
|
getValidOpenCodeGoApiKey(): Promise<string | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a SPECIFIC account's access token by id (refreshing a near-expiry
|
|
54
|
+
* OAuth token for that account, mirroring the active getter's per-provider
|
|
55
|
+
* refresh policy). `null` when the account is unknown/expired/tokenless.
|
|
56
|
+
*/
|
|
57
|
+
getAccessTokenForAccount?(providerId: SubscriptionProviderId, accountId: string): Promise<string | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Force a refresh of a SPECIFIC account's OAuth token by id; `true` on success.
|
|
60
|
+
* Static-key providers (opencodego) return `false` — they don't refresh.
|
|
61
|
+
*/
|
|
62
|
+
refreshAccountToken?(providerId: SubscriptionProviderId, accountId: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Best-effort record of a selection's time onto the account's `lastUsedAt`
|
|
65
|
+
* (throttled by the selector so the hot path does not rewrite the store every
|
|
66
|
+
* request). Durability only — never affects which credential is valid.
|
|
67
|
+
*/
|
|
68
|
+
touchAccountLastUsed?(providerId: SubscriptionProviderId, accountId: string, iso: string): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* SubscriptionAccountSelector — the pure, in-memory account scheduler
|
|
73
|
+
* (subscription-account-scheduling, design D3).
|
|
74
|
+
*
|
|
75
|
+
* Given a provider's account list (+ an optional session key) it decides WHICH
|
|
76
|
+
* account of the pool serves the outbound request:
|
|
77
|
+
*
|
|
78
|
+
* filter `schedulable !== false`
|
|
79
|
+
* → session-affinity short-circuit (sticky `sessionKey → accountId`, TTL 1h)
|
|
80
|
+
* → sort by `priority` asc → effective `lastUsedAt` asc (LRU) → `createdAt` asc
|
|
81
|
+
* → `[0]`.
|
|
82
|
+
*
|
|
83
|
+
* A faithful port of CRS `sortAccountsByPriority` + `unifiedClaudeScheduler`
|
|
84
|
+
* session mapping. The class holds process-lived state — the affinity map and an
|
|
85
|
+
* in-memory `lastUsedAt` overlay (the authoritative live tie-break value, seeded
|
|
86
|
+
* from the persisted field and advanced to `now` on each selection so repeated
|
|
87
|
+
* selections round-robin fairly even before any durable persist). ONE instance is
|
|
88
|
+
* constructed at bootstrap and SHARED by all three auth strategies (each scopes
|
|
89
|
+
* its calls by `providerId`), mirroring the single long-lived CRS scheduler.
|
|
90
|
+
*
|
|
91
|
+
* ZERO-REGRESSION CARRIER: `select()` returns `null` when the schedulable account
|
|
92
|
+
* count is ≤ 1. Callers treat `null` (and an `isActive` result) as "use the
|
|
93
|
+
* existing active-mirror path", so a single-account provider exercises NO new
|
|
94
|
+
* code and its token read is byte-identical to before this change.
|
|
95
|
+
*
|
|
96
|
+
* @module scheduler/SubscriptionAccountSelector
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/** Sticky session→account affinity TTL (CRS parity: 1 hour). */
|
|
100
|
+
declare const SESSION_AFFINITY_TTL_MS = 3600000;
|
|
101
|
+
/** Min gap between best-effort `lastUsedAt` durable persists, per account. */
|
|
102
|
+
declare const LAST_USED_PERSIST_THROTTLE_MS = 60000;
|
|
103
|
+
/** Precedence assumed for an account with no `priority` (CRS `|| 50`). */
|
|
104
|
+
declare const DEFAULT_ACCOUNT_PRIORITY = 50;
|
|
105
|
+
/** One schedulable candidate — the scheduling projection of an account entry. */
|
|
106
|
+
interface SchedulableAccount {
|
|
107
|
+
id: string;
|
|
108
|
+
/** Default `50` (lower = higher precedence). */
|
|
109
|
+
priority?: number;
|
|
110
|
+
/** ISO — LRU tie-break; absent ⇒ treated as least-recently-used (`0`). */
|
|
111
|
+
lastUsedAt?: string;
|
|
112
|
+
/** ISO — final tie-break; absent ⇒ oldest (`0`). */
|
|
113
|
+
createdAt?: string;
|
|
114
|
+
/** Default `true`; child #2 (account health) sets `false` for an unhealthy
|
|
115
|
+
* account and the selector skips it. */
|
|
116
|
+
schedulable?: boolean;
|
|
117
|
+
}
|
|
118
|
+
interface SelectInput {
|
|
119
|
+
providerId: SubscriptionProviderId;
|
|
120
|
+
accounts: readonly SchedulableAccount[];
|
|
121
|
+
/** The persistent active-account pointer (for the `isActive` discriminant). */
|
|
122
|
+
activeAccountId?: string;
|
|
123
|
+
/** Stable per-conversation key for session affinity; absent ⇒ pure priority/LRU. */
|
|
124
|
+
sessionKey?: string;
|
|
125
|
+
/** Injectable clock for tests (default `Date.now()`). */
|
|
126
|
+
now?: number;
|
|
127
|
+
}
|
|
128
|
+
interface SelectResult {
|
|
129
|
+
accountId: string;
|
|
130
|
+
/** `true` when the chosen account is the active one — the caller then runs the
|
|
131
|
+
* existing active-mirror getter verbatim (no by-id read). */
|
|
132
|
+
isActive: boolean;
|
|
133
|
+
}
|
|
134
|
+
declare class SubscriptionAccountSelector {
|
|
135
|
+
/** `providerId\0accountId → live lastUsedAt ms` (authoritative tie-break). */
|
|
136
|
+
private readonly lastUsedOverlay;
|
|
137
|
+
/** `providerId\0sessionKey → { accountId, expiresAt }`. */
|
|
138
|
+
private readonly affinity;
|
|
139
|
+
/** `providerId\0accountId → last durable-persist ms` (throttle state). */
|
|
140
|
+
private readonly lastPersist;
|
|
141
|
+
/**
|
|
142
|
+
* Choose the account to serve this request, or `null` when there are ≤ 1
|
|
143
|
+
* schedulable accounts (the zero-regression signal — caller uses the active
|
|
144
|
+
* account). Updates the live `lastUsedAt` overlay for the chosen account and,
|
|
145
|
+
* when a `sessionKey` is given, records/extends the affinity mapping.
|
|
146
|
+
*/
|
|
147
|
+
select(input: SelectInput): SelectResult | null;
|
|
148
|
+
/**
|
|
149
|
+
* Drop every session-affinity mapping bound to this account (subscription-account-
|
|
150
|
+
* health, task 4.1). Called when a selected account's by-id token turns out
|
|
151
|
+
* null/invalid or an affinity-bound account becomes unhealthy, so the next
|
|
152
|
+
* selection for those sessions picks a fresh account instead of re-sticking to
|
|
153
|
+
* the bad one. O(affinity entries) — the map is tiny (one entry per live
|
|
154
|
+
* conversation).
|
|
155
|
+
*/
|
|
156
|
+
evictAffinity(providerId: SubscriptionProviderId, accountId: string): void;
|
|
157
|
+
/**
|
|
158
|
+
* Whether a best-effort `lastUsedAt` durable persist is DUE for this account
|
|
159
|
+
* (≥ `LAST_USED_PERSIST_THROTTLE_MS` since the last one). Records the persist
|
|
160
|
+
* time when it returns `true`, so the strategy calls `touchAccountLastUsed`
|
|
161
|
+
* sparingly and the request hot path does not rewrite the store every request.
|
|
162
|
+
*/
|
|
163
|
+
duePersist(providerId: SubscriptionProviderId, accountId: string, now?: number): boolean;
|
|
164
|
+
/** Sort by `priority` asc → effective `lastUsedAt` asc → `createdAt` asc → `[0]`. */
|
|
165
|
+
private pickOrdered;
|
|
166
|
+
/** The live tie-break value: the in-memory overlay when set, else the persisted
|
|
167
|
+
* `lastUsedAt` (0 when absent). */
|
|
168
|
+
private effectiveLastUsed;
|
|
169
|
+
private markUsed;
|
|
44
170
|
}
|
|
45
171
|
|
|
46
172
|
/**
|
|
@@ -57,6 +183,9 @@ interface SubscriptionCredentialStore {
|
|
|
57
183
|
|
|
58
184
|
declare class SubscriptionAccountService {
|
|
59
185
|
private readonly mutex;
|
|
186
|
+
/** ONE account-pool scheduler (subscription-account-scheduling) shared by all
|
|
187
|
+
* four strategies so they share the affinity map + the `lastUsedAt` overlay. */
|
|
188
|
+
private readonly selector;
|
|
60
189
|
private readonly strategies;
|
|
61
190
|
constructor(tokens: SubscriptionCredentialStore);
|
|
62
191
|
/** Returns the strategy bound to a subscription provider, or `null` for unknown ids. */
|
|
@@ -136,7 +265,17 @@ interface DispatcherHooks {
|
|
|
136
265
|
readonly executor: TransformerChainExecutor;
|
|
137
266
|
/** Shared transformer service registry — looks up transformer-by-name. */
|
|
138
267
|
readonly transformerService: TransformerService;
|
|
139
|
-
/**
|
|
268
|
+
/**
|
|
269
|
+
* Fetch + retry helper from the proxy (semaphore, 429/5xx loop). On a non-ok
|
|
270
|
+
* upstream it throws a `ProviderApiError`-shaped error carrying `.status`.
|
|
271
|
+
*
|
|
272
|
+
* ACCOUNT-HEALTH CONTRACT (subscription-account-health): for the daemon-path
|
|
273
|
+
* 429-reset cooldown + 403-ban sniff to function, the thrown error SHOULD also
|
|
274
|
+
* carry the upstream response `headers` (a `Headers` or a plain record) and, on
|
|
275
|
+
* a 403, a bounded `bodyText`/`body` string. The dispatcher reads them
|
|
276
|
+
* STRUCTURALLY (`errHeaders`/`errBodyText`) — absent ⇒ the account-health mark
|
|
277
|
+
* gracefully degrades to a bare-429 (unmarked, lazy re-probe), never an error.
|
|
278
|
+
*/
|
|
140
279
|
fetchWithRetry(url: string, headers: Record<string, string>, body: unknown, model: string): Promise<Response>;
|
|
141
280
|
/** Forward the upstream response to the SDK + tap usage. */
|
|
142
281
|
writeProxyResponse(res: http.ServerResponse, providerResponse: Response, isStream: boolean, reqId?: number): Promise<void>;
|
|
@@ -185,6 +324,17 @@ declare class SubscriptionDispatcher {
|
|
|
185
324
|
*/
|
|
186
325
|
private maybeRetryAfterError;
|
|
187
326
|
private applyHeadersWithRetry;
|
|
327
|
+
/**
|
|
328
|
+
* Mark the served account's health against ONE attempt's outcome
|
|
329
|
+
* (subscription-account-health, task 3.4). No-op when no account was reported
|
|
330
|
+
* (non-pooled / single-account) or on a session-cancel (status 0). On a caught
|
|
331
|
+
* error `err` is passed so the 429-reset / 403-ban drivers are read STRUCTURALLY
|
|
332
|
+
* from the error's upstream `headers` + `bodyText` (the `fetchWithRetry`
|
|
333
|
+
* contract) — so daemon-path 429 cooldown + ban blocking function for
|
|
334
|
+
* multi-account codex/gemini/opencodego pools; absent headers ⇒ a bare-429
|
|
335
|
+
* (unmarked, lazy re-probe). Success (2xx) clears; 401/5xx/thrown → transient.
|
|
336
|
+
*/
|
|
337
|
+
private markHealth;
|
|
188
338
|
/**
|
|
189
339
|
* Resolve the Code Assist project for the gemini subscription profile. Pulls
|
|
190
340
|
* the Bearer the bound `AuthStrategy` would inject (so the strategy stays the
|
|
@@ -199,4 +349,4 @@ declare class SubscriptionDispatcher {
|
|
|
199
349
|
private buildRequestSummary;
|
|
200
350
|
}
|
|
201
351
|
|
|
202
|
-
export { type DispatchRequest, type DispatcherHooks, SubscriptionAccountService, type SubscriptionCredentialStore, SubscriptionDispatcher, SubscriptionProviderRegistry, getSubscriptionAccountService, getSubscriptionProviderRegistry, setSubscriptionAccountService, setSubscriptionProviderRegistry };
|
|
352
|
+
export { DEFAULT_ACCOUNT_PRIORITY, type DispatchRequest, type DispatcherHooks, LAST_USED_PERSIST_THROTTLE_MS, SESSION_AFFINITY_TTL_MS, type SchedulableAccount, type SelectInput, type SelectResult, SubscriptionAccountSelector, SubscriptionAccountService, type SubscriptionCredentialStore, SubscriptionDispatcher, SubscriptionProviderRegistry, getSubscriptionAccountService, getSubscriptionProviderRegistry, setSubscriptionAccountService, setSubscriptionProviderRegistry };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,15 @@ export { claudeOAuth, codexOAuth, geminiOAuth } from './oauth.js';
|
|
|
27
27
|
* subscriptions layer).
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* The credential surface the subscription block consumes from the host store.
|
|
32
|
+
*
|
|
33
|
+
* The six ORIGINAL methods (active-account getters + refreshers) are REQUIRED;
|
|
34
|
+
* the three by-id methods (subscription-account-scheduling, design D6) are
|
|
35
|
+
* OPTIONAL and feature-detected so every existing lightweight test double that
|
|
36
|
+
* implements only the six keeps compiling and the single-account active path is
|
|
37
|
+
* untouched.
|
|
38
|
+
*/
|
|
31
39
|
interface SubscriptionCredentialStore {
|
|
32
40
|
/** Full decrypted account-tokens config (all subscription providers). */
|
|
33
41
|
getFullConfig(): Promise<AccountTokensConfig>;
|
|
@@ -41,6 +49,124 @@ interface SubscriptionCredentialStore {
|
|
|
41
49
|
refreshGeminiToken(): Promise<boolean>;
|
|
42
50
|
/** Current valid OpenCodeGo static API key; `null` if none. */
|
|
43
51
|
getValidOpenCodeGoApiKey(): Promise<string | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a SPECIFIC account's access token by id (refreshing a near-expiry
|
|
54
|
+
* OAuth token for that account, mirroring the active getter's per-provider
|
|
55
|
+
* refresh policy). `null` when the account is unknown/expired/tokenless.
|
|
56
|
+
*/
|
|
57
|
+
getAccessTokenForAccount?(providerId: SubscriptionProviderId, accountId: string): Promise<string | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Force a refresh of a SPECIFIC account's OAuth token by id; `true` on success.
|
|
60
|
+
* Static-key providers (opencodego) return `false` — they don't refresh.
|
|
61
|
+
*/
|
|
62
|
+
refreshAccountToken?(providerId: SubscriptionProviderId, accountId: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Best-effort record of a selection's time onto the account's `lastUsedAt`
|
|
65
|
+
* (throttled by the selector so the hot path does not rewrite the store every
|
|
66
|
+
* request). Durability only — never affects which credential is valid.
|
|
67
|
+
*/
|
|
68
|
+
touchAccountLastUsed?(providerId: SubscriptionProviderId, accountId: string, iso: string): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* SubscriptionAccountSelector — the pure, in-memory account scheduler
|
|
73
|
+
* (subscription-account-scheduling, design D3).
|
|
74
|
+
*
|
|
75
|
+
* Given a provider's account list (+ an optional session key) it decides WHICH
|
|
76
|
+
* account of the pool serves the outbound request:
|
|
77
|
+
*
|
|
78
|
+
* filter `schedulable !== false`
|
|
79
|
+
* → session-affinity short-circuit (sticky `sessionKey → accountId`, TTL 1h)
|
|
80
|
+
* → sort by `priority` asc → effective `lastUsedAt` asc (LRU) → `createdAt` asc
|
|
81
|
+
* → `[0]`.
|
|
82
|
+
*
|
|
83
|
+
* A faithful port of CRS `sortAccountsByPriority` + `unifiedClaudeScheduler`
|
|
84
|
+
* session mapping. The class holds process-lived state — the affinity map and an
|
|
85
|
+
* in-memory `lastUsedAt` overlay (the authoritative live tie-break value, seeded
|
|
86
|
+
* from the persisted field and advanced to `now` on each selection so repeated
|
|
87
|
+
* selections round-robin fairly even before any durable persist). ONE instance is
|
|
88
|
+
* constructed at bootstrap and SHARED by all three auth strategies (each scopes
|
|
89
|
+
* its calls by `providerId`), mirroring the single long-lived CRS scheduler.
|
|
90
|
+
*
|
|
91
|
+
* ZERO-REGRESSION CARRIER: `select()` returns `null` when the schedulable account
|
|
92
|
+
* count is ≤ 1. Callers treat `null` (and an `isActive` result) as "use the
|
|
93
|
+
* existing active-mirror path", so a single-account provider exercises NO new
|
|
94
|
+
* code and its token read is byte-identical to before this change.
|
|
95
|
+
*
|
|
96
|
+
* @module scheduler/SubscriptionAccountSelector
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/** Sticky session→account affinity TTL (CRS parity: 1 hour). */
|
|
100
|
+
declare const SESSION_AFFINITY_TTL_MS = 3600000;
|
|
101
|
+
/** Min gap between best-effort `lastUsedAt` durable persists, per account. */
|
|
102
|
+
declare const LAST_USED_PERSIST_THROTTLE_MS = 60000;
|
|
103
|
+
/** Precedence assumed for an account with no `priority` (CRS `|| 50`). */
|
|
104
|
+
declare const DEFAULT_ACCOUNT_PRIORITY = 50;
|
|
105
|
+
/** One schedulable candidate — the scheduling projection of an account entry. */
|
|
106
|
+
interface SchedulableAccount {
|
|
107
|
+
id: string;
|
|
108
|
+
/** Default `50` (lower = higher precedence). */
|
|
109
|
+
priority?: number;
|
|
110
|
+
/** ISO — LRU tie-break; absent ⇒ treated as least-recently-used (`0`). */
|
|
111
|
+
lastUsedAt?: string;
|
|
112
|
+
/** ISO — final tie-break; absent ⇒ oldest (`0`). */
|
|
113
|
+
createdAt?: string;
|
|
114
|
+
/** Default `true`; child #2 (account health) sets `false` for an unhealthy
|
|
115
|
+
* account and the selector skips it. */
|
|
116
|
+
schedulable?: boolean;
|
|
117
|
+
}
|
|
118
|
+
interface SelectInput {
|
|
119
|
+
providerId: SubscriptionProviderId;
|
|
120
|
+
accounts: readonly SchedulableAccount[];
|
|
121
|
+
/** The persistent active-account pointer (for the `isActive` discriminant). */
|
|
122
|
+
activeAccountId?: string;
|
|
123
|
+
/** Stable per-conversation key for session affinity; absent ⇒ pure priority/LRU. */
|
|
124
|
+
sessionKey?: string;
|
|
125
|
+
/** Injectable clock for tests (default `Date.now()`). */
|
|
126
|
+
now?: number;
|
|
127
|
+
}
|
|
128
|
+
interface SelectResult {
|
|
129
|
+
accountId: string;
|
|
130
|
+
/** `true` when the chosen account is the active one — the caller then runs the
|
|
131
|
+
* existing active-mirror getter verbatim (no by-id read). */
|
|
132
|
+
isActive: boolean;
|
|
133
|
+
}
|
|
134
|
+
declare class SubscriptionAccountSelector {
|
|
135
|
+
/** `providerId\0accountId → live lastUsedAt ms` (authoritative tie-break). */
|
|
136
|
+
private readonly lastUsedOverlay;
|
|
137
|
+
/** `providerId\0sessionKey → { accountId, expiresAt }`. */
|
|
138
|
+
private readonly affinity;
|
|
139
|
+
/** `providerId\0accountId → last durable-persist ms` (throttle state). */
|
|
140
|
+
private readonly lastPersist;
|
|
141
|
+
/**
|
|
142
|
+
* Choose the account to serve this request, or `null` when there are ≤ 1
|
|
143
|
+
* schedulable accounts (the zero-regression signal — caller uses the active
|
|
144
|
+
* account). Updates the live `lastUsedAt` overlay for the chosen account and,
|
|
145
|
+
* when a `sessionKey` is given, records/extends the affinity mapping.
|
|
146
|
+
*/
|
|
147
|
+
select(input: SelectInput): SelectResult | null;
|
|
148
|
+
/**
|
|
149
|
+
* Drop every session-affinity mapping bound to this account (subscription-account-
|
|
150
|
+
* health, task 4.1). Called when a selected account's by-id token turns out
|
|
151
|
+
* null/invalid or an affinity-bound account becomes unhealthy, so the next
|
|
152
|
+
* selection for those sessions picks a fresh account instead of re-sticking to
|
|
153
|
+
* the bad one. O(affinity entries) — the map is tiny (one entry per live
|
|
154
|
+
* conversation).
|
|
155
|
+
*/
|
|
156
|
+
evictAffinity(providerId: SubscriptionProviderId, accountId: string): void;
|
|
157
|
+
/**
|
|
158
|
+
* Whether a best-effort `lastUsedAt` durable persist is DUE for this account
|
|
159
|
+
* (≥ `LAST_USED_PERSIST_THROTTLE_MS` since the last one). Records the persist
|
|
160
|
+
* time when it returns `true`, so the strategy calls `touchAccountLastUsed`
|
|
161
|
+
* sparingly and the request hot path does not rewrite the store every request.
|
|
162
|
+
*/
|
|
163
|
+
duePersist(providerId: SubscriptionProviderId, accountId: string, now?: number): boolean;
|
|
164
|
+
/** Sort by `priority` asc → effective `lastUsedAt` asc → `createdAt` asc → `[0]`. */
|
|
165
|
+
private pickOrdered;
|
|
166
|
+
/** The live tie-break value: the in-memory overlay when set, else the persisted
|
|
167
|
+
* `lastUsedAt` (0 when absent). */
|
|
168
|
+
private effectiveLastUsed;
|
|
169
|
+
private markUsed;
|
|
44
170
|
}
|
|
45
171
|
|
|
46
172
|
/**
|
|
@@ -57,6 +183,9 @@ interface SubscriptionCredentialStore {
|
|
|
57
183
|
|
|
58
184
|
declare class SubscriptionAccountService {
|
|
59
185
|
private readonly mutex;
|
|
186
|
+
/** ONE account-pool scheduler (subscription-account-scheduling) shared by all
|
|
187
|
+
* four strategies so they share the affinity map + the `lastUsedAt` overlay. */
|
|
188
|
+
private readonly selector;
|
|
60
189
|
private readonly strategies;
|
|
61
190
|
constructor(tokens: SubscriptionCredentialStore);
|
|
62
191
|
/** Returns the strategy bound to a subscription provider, or `null` for unknown ids. */
|
|
@@ -136,7 +265,17 @@ interface DispatcherHooks {
|
|
|
136
265
|
readonly executor: TransformerChainExecutor;
|
|
137
266
|
/** Shared transformer service registry — looks up transformer-by-name. */
|
|
138
267
|
readonly transformerService: TransformerService;
|
|
139
|
-
/**
|
|
268
|
+
/**
|
|
269
|
+
* Fetch + retry helper from the proxy (semaphore, 429/5xx loop). On a non-ok
|
|
270
|
+
* upstream it throws a `ProviderApiError`-shaped error carrying `.status`.
|
|
271
|
+
*
|
|
272
|
+
* ACCOUNT-HEALTH CONTRACT (subscription-account-health): for the daemon-path
|
|
273
|
+
* 429-reset cooldown + 403-ban sniff to function, the thrown error SHOULD also
|
|
274
|
+
* carry the upstream response `headers` (a `Headers` or a plain record) and, on
|
|
275
|
+
* a 403, a bounded `bodyText`/`body` string. The dispatcher reads them
|
|
276
|
+
* STRUCTURALLY (`errHeaders`/`errBodyText`) — absent ⇒ the account-health mark
|
|
277
|
+
* gracefully degrades to a bare-429 (unmarked, lazy re-probe), never an error.
|
|
278
|
+
*/
|
|
140
279
|
fetchWithRetry(url: string, headers: Record<string, string>, body: unknown, model: string): Promise<Response>;
|
|
141
280
|
/** Forward the upstream response to the SDK + tap usage. */
|
|
142
281
|
writeProxyResponse(res: http.ServerResponse, providerResponse: Response, isStream: boolean, reqId?: number): Promise<void>;
|
|
@@ -185,6 +324,17 @@ declare class SubscriptionDispatcher {
|
|
|
185
324
|
*/
|
|
186
325
|
private maybeRetryAfterError;
|
|
187
326
|
private applyHeadersWithRetry;
|
|
327
|
+
/**
|
|
328
|
+
* Mark the served account's health against ONE attempt's outcome
|
|
329
|
+
* (subscription-account-health, task 3.4). No-op when no account was reported
|
|
330
|
+
* (non-pooled / single-account) or on a session-cancel (status 0). On a caught
|
|
331
|
+
* error `err` is passed so the 429-reset / 403-ban drivers are read STRUCTURALLY
|
|
332
|
+
* from the error's upstream `headers` + `bodyText` (the `fetchWithRetry`
|
|
333
|
+
* contract) — so daemon-path 429 cooldown + ban blocking function for
|
|
334
|
+
* multi-account codex/gemini/opencodego pools; absent headers ⇒ a bare-429
|
|
335
|
+
* (unmarked, lazy re-probe). Success (2xx) clears; 401/5xx/thrown → transient.
|
|
336
|
+
*/
|
|
337
|
+
private markHealth;
|
|
188
338
|
/**
|
|
189
339
|
* Resolve the Code Assist project for the gemini subscription profile. Pulls
|
|
190
340
|
* the Bearer the bound `AuthStrategy` would inject (so the strategy stays the
|
|
@@ -199,4 +349,4 @@ declare class SubscriptionDispatcher {
|
|
|
199
349
|
private buildRequestSummary;
|
|
200
350
|
}
|
|
201
351
|
|
|
202
|
-
export { type DispatchRequest, type DispatcherHooks, SubscriptionAccountService, type SubscriptionCredentialStore, SubscriptionDispatcher, SubscriptionProviderRegistry, getSubscriptionAccountService, getSubscriptionProviderRegistry, setSubscriptionAccountService, setSubscriptionProviderRegistry };
|
|
352
|
+
export { DEFAULT_ACCOUNT_PRIORITY, type DispatchRequest, type DispatcherHooks, LAST_USED_PERSIST_THROTTLE_MS, SESSION_AFFINITY_TTL_MS, type SchedulableAccount, type SelectInput, type SelectResult, SubscriptionAccountSelector, SubscriptionAccountService, type SubscriptionCredentialStore, SubscriptionDispatcher, SubscriptionProviderRegistry, getSubscriptionAccountService, getSubscriptionProviderRegistry, setSubscriptionAccountService, setSubscriptionProviderRegistry };
|