@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.cjs
CHANGED
|
@@ -1,29 +1,281 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9;
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
var _chunkTPW5Q25Ycjs = require('./chunk-TPW5Q25Y.cjs');
|
|
6
6
|
|
|
7
|
+
// src/scheduler/SubscriptionAccountSelector.ts
|
|
8
|
+
var SESSION_AFFINITY_TTL_MS = 36e5;
|
|
9
|
+
var LAST_USED_PERSIST_THROTTLE_MS = 6e4;
|
|
10
|
+
var DEFAULT_ACCOUNT_PRIORITY = 50;
|
|
11
|
+
function scopedKey(providerId, tail) {
|
|
12
|
+
return `${providerId}\0${tail}`;
|
|
13
|
+
}
|
|
14
|
+
function parseIso(iso) {
|
|
15
|
+
if (!iso) return 0;
|
|
16
|
+
const ms = Date.parse(iso);
|
|
17
|
+
return Number.isNaN(ms) ? 0 : ms;
|
|
18
|
+
}
|
|
19
|
+
var SubscriptionAccountSelector = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this); }
|
|
20
|
+
/** `providerId\0accountId → live lastUsedAt ms` (authoritative tie-break). */
|
|
21
|
+
__init() {this.lastUsedOverlay = /* @__PURE__ */ new Map()}
|
|
22
|
+
/** `providerId\0sessionKey → { accountId, expiresAt }`. */
|
|
23
|
+
__init2() {this.affinity = /* @__PURE__ */ new Map()}
|
|
24
|
+
/** `providerId\0accountId → last durable-persist ms` (throttle state). */
|
|
25
|
+
__init3() {this.lastPersist = /* @__PURE__ */ new Map()}
|
|
26
|
+
/**
|
|
27
|
+
* Choose the account to serve this request, or `null` when there are ≤ 1
|
|
28
|
+
* schedulable accounts (the zero-regression signal — caller uses the active
|
|
29
|
+
* account). Updates the live `lastUsedAt` overlay for the chosen account and,
|
|
30
|
+
* when a `sessionKey` is given, records/extends the affinity mapping.
|
|
31
|
+
*/
|
|
32
|
+
select(input) {
|
|
33
|
+
const now = _nullishCoalesce(input.now, () => ( Date.now()));
|
|
34
|
+
const schedulable = input.accounts.filter((a) => a.schedulable !== false);
|
|
35
|
+
if (schedulable.length <= 1) return null;
|
|
36
|
+
if (input.sessionKey) {
|
|
37
|
+
const aKey = scopedKey(input.providerId, input.sessionKey);
|
|
38
|
+
const hit = this.affinity.get(aKey);
|
|
39
|
+
if (hit && hit.expiresAt > now && schedulable.some((a) => a.id === hit.accountId)) {
|
|
40
|
+
hit.expiresAt = now + SESSION_AFFINITY_TTL_MS;
|
|
41
|
+
this.markUsed(input.providerId, hit.accountId, now);
|
|
42
|
+
return { accountId: hit.accountId, isActive: hit.accountId === input.activeAccountId };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const chosen = this.pickOrdered(input.providerId, schedulable);
|
|
46
|
+
this.markUsed(input.providerId, chosen.id, now);
|
|
47
|
+
if (input.sessionKey) {
|
|
48
|
+
this.affinity.set(scopedKey(input.providerId, input.sessionKey), {
|
|
49
|
+
accountId: chosen.id,
|
|
50
|
+
expiresAt: now + SESSION_AFFINITY_TTL_MS
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { accountId: chosen.id, isActive: chosen.id === input.activeAccountId };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Drop every session-affinity mapping bound to this account (subscription-account-
|
|
57
|
+
* health, task 4.1). Called when a selected account's by-id token turns out
|
|
58
|
+
* null/invalid or an affinity-bound account becomes unhealthy, so the next
|
|
59
|
+
* selection for those sessions picks a fresh account instead of re-sticking to
|
|
60
|
+
* the bad one. O(affinity entries) — the map is tiny (one entry per live
|
|
61
|
+
* conversation).
|
|
62
|
+
*/
|
|
63
|
+
evictAffinity(providerId, accountId) {
|
|
64
|
+
const prefix = `${providerId} `;
|
|
65
|
+
for (const [key, entry] of this.affinity) {
|
|
66
|
+
if (entry.accountId === accountId && key.startsWith(prefix)) {
|
|
67
|
+
this.affinity.delete(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a best-effort `lastUsedAt` durable persist is DUE for this account
|
|
73
|
+
* (≥ `LAST_USED_PERSIST_THROTTLE_MS` since the last one). Records the persist
|
|
74
|
+
* time when it returns `true`, so the strategy calls `touchAccountLastUsed`
|
|
75
|
+
* sparingly and the request hot path does not rewrite the store every request.
|
|
76
|
+
*/
|
|
77
|
+
duePersist(providerId, accountId, now = Date.now()) {
|
|
78
|
+
const key = scopedKey(providerId, accountId);
|
|
79
|
+
const last = _nullishCoalesce(this.lastPersist.get(key), () => ( 0));
|
|
80
|
+
if (now - last < LAST_USED_PERSIST_THROTTLE_MS) return false;
|
|
81
|
+
this.lastPersist.set(key, now);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/** Sort by `priority` asc → effective `lastUsedAt` asc → `createdAt` asc → `[0]`. */
|
|
85
|
+
pickOrdered(providerId, accounts) {
|
|
86
|
+
const ranked = accounts.map((account) => ({
|
|
87
|
+
account,
|
|
88
|
+
priority: _nullishCoalesce(account.priority, () => ( DEFAULT_ACCOUNT_PRIORITY)),
|
|
89
|
+
lastUsed: this.effectiveLastUsed(providerId, account),
|
|
90
|
+
created: parseIso(account.createdAt)
|
|
91
|
+
}));
|
|
92
|
+
ranked.sort(
|
|
93
|
+
(x, y) => x.priority - y.priority || x.lastUsed - y.lastUsed || x.created - y.created
|
|
94
|
+
);
|
|
95
|
+
return ranked[0].account;
|
|
96
|
+
}
|
|
97
|
+
/** The live tie-break value: the in-memory overlay when set, else the persisted
|
|
98
|
+
* `lastUsedAt` (0 when absent). */
|
|
99
|
+
effectiveLastUsed(providerId, account) {
|
|
100
|
+
const overlay = this.lastUsedOverlay.get(scopedKey(providerId, account.id));
|
|
101
|
+
return _nullishCoalesce(overlay, () => ( parseIso(account.lastUsedAt)));
|
|
102
|
+
}
|
|
103
|
+
markUsed(providerId, accountId, now) {
|
|
104
|
+
this.lastUsedOverlay.set(scopedKey(providerId, accountId), now);
|
|
105
|
+
}
|
|
106
|
+
}, _class);
|
|
107
|
+
|
|
108
|
+
// src/SubscriptionAccountService.ts
|
|
109
|
+
var _SubscriptionAccountHealth = require('@omnicross/core/pipeline/SubscriptionAccountHealth');
|
|
110
|
+
|
|
111
|
+
// src/scheduler/accountModelMap.ts
|
|
112
|
+
function canonicalModelId(value) {
|
|
113
|
+
const idx = value.indexOf(",");
|
|
114
|
+
const bare = idx >= 0 ? value.slice(idx + 1) : value;
|
|
115
|
+
return bare.trim().toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
function accountSupportsModel(supportedModels, model) {
|
|
118
|
+
if (!supportedModels) return true;
|
|
119
|
+
const target = canonicalModelId(model);
|
|
120
|
+
if (Array.isArray(supportedModels)) {
|
|
121
|
+
return supportedModels.some((m) => canonicalModelId(m) === target);
|
|
122
|
+
}
|
|
123
|
+
return Object.keys(supportedModels).some((k) => canonicalModelId(k) === target);
|
|
124
|
+
}
|
|
125
|
+
function remapForAccount(supportedModels, model) {
|
|
126
|
+
if (!supportedModels || Array.isArray(supportedModels)) return model;
|
|
127
|
+
const target = canonicalModelId(model);
|
|
128
|
+
for (const [key, actual] of Object.entries(supportedModels)) {
|
|
129
|
+
if (canonicalModelId(key) === target) return actual;
|
|
130
|
+
}
|
|
131
|
+
return model;
|
|
132
|
+
}
|
|
133
|
+
function remapReportForAccount(supportedModels, model) {
|
|
134
|
+
if (!model) return void 0;
|
|
135
|
+
const remapped = remapForAccount(supportedModels, model);
|
|
136
|
+
return remapped === model ? void 0 : remapped;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/scheduler/accountSelection.ts
|
|
140
|
+
var ACCOUNTS_KEY = {
|
|
141
|
+
claude: "claudeAccounts",
|
|
142
|
+
codex: "codexAccounts",
|
|
143
|
+
gemini: "geminiAccounts",
|
|
144
|
+
opencodego: "opencodegoAccounts"
|
|
145
|
+
};
|
|
146
|
+
var ACTIVE_KEY = {
|
|
147
|
+
claude: "activeClaudeAccountId",
|
|
148
|
+
codex: "activeCodexAccountId",
|
|
149
|
+
gemini: "activeGeminiAccountId",
|
|
150
|
+
opencodego: "activeOpencodegoAccountId"
|
|
151
|
+
};
|
|
152
|
+
function gateSchedulable(accounts, providerId, health, now, resolvedModel, supportedModelsById) {
|
|
153
|
+
if (!health && !resolvedModel || accounts.length < 2) return accounts;
|
|
154
|
+
return accounts.map((a) => {
|
|
155
|
+
const healthOk = health ? health.isSchedulable(providerId, a.id, now) : true;
|
|
156
|
+
const modelOk = resolvedModel ? accountSupportsModel(supportedModelsById.get(a.id), resolvedModel) : true;
|
|
157
|
+
return { ...a, schedulable: healthOk && modelOk };
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function isPoolGated(accounts, health, resolvedModel) {
|
|
161
|
+
return (health !== void 0 || resolvedModel !== void 0) && accounts.length >= 2;
|
|
162
|
+
}
|
|
163
|
+
function readSchedulableAccounts(config, providerId) {
|
|
164
|
+
const raw = _nullishCoalesce(config[ACCOUNTS_KEY[providerId]], () => ( []));
|
|
165
|
+
const accounts = raw.map((a) => ({
|
|
166
|
+
id: a.id,
|
|
167
|
+
priority: a.priority,
|
|
168
|
+
lastUsedAt: a.lastUsedAt,
|
|
169
|
+
createdAt: a.createdAt
|
|
170
|
+
}));
|
|
171
|
+
const supportedModelsById = new Map(
|
|
172
|
+
raw.map((a) => [a.id, a.supportedModels])
|
|
173
|
+
);
|
|
174
|
+
const activeAccountId = config[ACTIVE_KEY[providerId]];
|
|
175
|
+
return { accounts, activeAccountId, supportedModelsById };
|
|
176
|
+
}
|
|
177
|
+
function pickByIdTarget(selector, gated, providerId, activeAccountId, sessionKey, now, healthGated) {
|
|
178
|
+
const selection = selector.select({ providerId, accounts: gated, activeAccountId, sessionKey, now });
|
|
179
|
+
if (selection && !selection.isActive) return selection.accountId;
|
|
180
|
+
if (selection === null && healthGated) {
|
|
181
|
+
const schedulable = gated.filter((a) => a.schedulable !== false);
|
|
182
|
+
if (schedulable.length === 1 && schedulable[0].id !== activeAccountId) return schedulable[0].id;
|
|
183
|
+
}
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
async function resolveSelectedToken(selector, tokens, providerId, sessionKey, activeGetter, ctx) {
|
|
187
|
+
const health = _optionalChain([ctx, 'optionalAccess', _ => _.health]);
|
|
188
|
+
const report = _optionalChain([ctx, 'optionalAccess', _2 => _2.reportSelection]);
|
|
189
|
+
const now = _optionalChain([ctx, 'optionalAccess', _3 => _3.now]);
|
|
190
|
+
const resolvedModel = _optionalChain([ctx, 'optionalAccess', _4 => _4.resolvedModel]);
|
|
191
|
+
if (selector && tokens.getAccessTokenForAccount) {
|
|
192
|
+
const config = await tokens.getFullConfig();
|
|
193
|
+
const { accounts, activeAccountId, supportedModelsById } = readSchedulableAccounts(config, providerId);
|
|
194
|
+
const gated = gateSchedulable(accounts, providerId, health, now, resolvedModel, supportedModelsById);
|
|
195
|
+
const poolGated = isPoolGated(accounts, health, resolvedModel);
|
|
196
|
+
const remapFor = (id) => remapReportForAccount(supportedModelsById.get(id), resolvedModel);
|
|
197
|
+
const targetId = pickByIdTarget(selector, gated, providerId, activeAccountId, sessionKey, now, poolGated);
|
|
198
|
+
if (targetId !== void 0) {
|
|
199
|
+
const byId = await tokens.getAccessTokenForAccount(providerId, targetId);
|
|
200
|
+
if (byId) {
|
|
201
|
+
maybeTouchLastUsed(selector, tokens, providerId, targetId);
|
|
202
|
+
_optionalChain([report, 'optionalCall', _5 => _5(targetId, false, remapFor(targetId))]);
|
|
203
|
+
return byId;
|
|
204
|
+
}
|
|
205
|
+
selector.evictAffinity(providerId, targetId);
|
|
206
|
+
_optionalChain([health, 'optionalAccess', _6 => _6.recordUpstreamOutcome, 'call', _7 => _7(providerId, targetId, { status: 401, now })]);
|
|
207
|
+
const remaining = gated.filter((a) => a.id !== targetId);
|
|
208
|
+
const retryId = pickByIdTarget(selector, remaining, providerId, activeAccountId, sessionKey, now, poolGated);
|
|
209
|
+
if (retryId !== void 0) {
|
|
210
|
+
const retryToken = await tokens.getAccessTokenForAccount(providerId, retryId);
|
|
211
|
+
if (retryToken) {
|
|
212
|
+
maybeTouchLastUsed(selector, tokens, providerId, retryId);
|
|
213
|
+
_optionalChain([report, 'optionalCall', _8 => _8(retryId, false, remapFor(retryId))]);
|
|
214
|
+
return retryToken;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (activeAccountId) _optionalChain([report, 'optionalCall', _9 => _9(activeAccountId, true, remapFor(activeAccountId))]);
|
|
219
|
+
return activeGetter();
|
|
220
|
+
}
|
|
221
|
+
return activeGetter();
|
|
222
|
+
}
|
|
223
|
+
function maybeTouchLastUsed(selector, tokens, providerId, accountId) {
|
|
224
|
+
if (!tokens.touchAccountLastUsed) return;
|
|
225
|
+
if (!selector.duePersist(providerId, accountId)) return;
|
|
226
|
+
void tokens.touchAccountLastUsed(providerId, accountId, (/* @__PURE__ */ new Date()).toISOString()).catch(() => {
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function refreshSelectedAccount(selector, tokens, mutex, providerId, sessionKey) {
|
|
230
|
+
if (!sessionKey || !selector || !tokens.refreshAccountToken) return null;
|
|
231
|
+
const config = await tokens.getFullConfig();
|
|
232
|
+
const { accounts, activeAccountId } = readSchedulableAccounts(config, providerId);
|
|
233
|
+
const selection = selector.select({ providerId, accounts, activeAccountId, sessionKey });
|
|
234
|
+
if (!selection || selection.isActive) return null;
|
|
235
|
+
const accountId = selection.accountId;
|
|
236
|
+
return mutex.run(`${providerId}:${accountId}`, async () => {
|
|
237
|
+
try {
|
|
238
|
+
return await _asyncNullishCoalesce(await tokens.refreshAccountToken(providerId, accountId), async () => ( false));
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.warn(`[accountSelection] ${providerId}:${accountId} by-id refresh failed:`, err);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
7
246
|
// src/auth/OAuthBearerAuthStrategy.ts
|
|
8
247
|
var REFRESH_LEAD_MS = 5 * 6e4;
|
|
9
|
-
var OAuthBearerAuthStrategy = (
|
|
10
|
-
constructor(providerId, tokens, mutex) {;
|
|
248
|
+
var OAuthBearerAuthStrategy = (_class2 = class {
|
|
249
|
+
constructor(providerId, tokens, mutex, selector, health) {;_class2.prototype.__init4.call(this);
|
|
11
250
|
this.tokens = tokens;
|
|
12
251
|
this.mutex = mutex;
|
|
252
|
+
this.selector = selector;
|
|
253
|
+
this.health = health;
|
|
13
254
|
this.providerId = providerId;
|
|
14
255
|
}
|
|
15
256
|
|
|
16
257
|
|
|
17
|
-
__init() {this.kind = "oauth-bearer"}
|
|
18
258
|
|
|
19
|
-
|
|
20
|
-
|
|
259
|
+
|
|
260
|
+
__init4() {this.kind = "oauth-bearer"}
|
|
261
|
+
|
|
262
|
+
async applyHeaders(headers, hints) {
|
|
263
|
+
const token = await resolveSelectedToken(
|
|
264
|
+
this.selector,
|
|
265
|
+
this.tokens,
|
|
266
|
+
this.providerId,
|
|
267
|
+
_optionalChain([hints, 'optionalAccess', _10 => _10.sessionKey]),
|
|
268
|
+
() => this.resolveAccessToken(),
|
|
269
|
+
{ health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _11 => _11.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _12 => _12.resolvedModel]) }
|
|
270
|
+
);
|
|
21
271
|
if (!token) {
|
|
22
272
|
return;
|
|
23
273
|
}
|
|
24
274
|
headers["Authorization"] = `Bearer ${token}`;
|
|
25
275
|
}
|
|
26
|
-
async onUnauthorized() {
|
|
276
|
+
async onUnauthorized(sessionKey) {
|
|
277
|
+
const byId = await refreshSelectedAccount(this.selector, this.tokens, this.mutex, this.providerId, sessionKey);
|
|
278
|
+
if (byId !== null) return byId;
|
|
27
279
|
return this.mutex.run(`${this.providerId}:refresh`, async () => {
|
|
28
280
|
try {
|
|
29
281
|
return this.providerId === "codex" ? await this.tokens.refreshCodexToken() : await this.tokens.refreshGeminiToken();
|
|
@@ -36,7 +288,7 @@ var OAuthBearerAuthStrategy = (_class = class {
|
|
|
36
288
|
async describeStatus() {
|
|
37
289
|
const config = await this.tokens.getFullConfig();
|
|
38
290
|
const entry = this.providerId === "codex" ? config.codex : config.gemini;
|
|
39
|
-
if (!_optionalChain([entry, 'optionalAccess',
|
|
291
|
+
if (!_optionalChain([entry, 'optionalAccess', _13 => _13.accessToken])) {
|
|
40
292
|
return { providerId: this.providerId, ok: false, reason: "missing-credential" };
|
|
41
293
|
}
|
|
42
294
|
if (entry.status === "expired") {
|
|
@@ -53,7 +305,7 @@ var OAuthBearerAuthStrategy = (_class = class {
|
|
|
53
305
|
async resolveAccessToken() {
|
|
54
306
|
const config = await this.tokens.getFullConfig();
|
|
55
307
|
const entry = this.providerId === "codex" ? config.codex : config.gemini;
|
|
56
|
-
if (!_optionalChain([entry, 'optionalAccess',
|
|
308
|
+
if (!_optionalChain([entry, 'optionalAccess', _14 => _14.accessToken])) return null;
|
|
57
309
|
const expiresAtMs = entry.expiresAt ? new Date(entry.expiresAt).getTime() : 0;
|
|
58
310
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - REFRESH_LEAD_MS;
|
|
59
311
|
if (expiringSoon && entry.refreshToken) {
|
|
@@ -63,29 +315,42 @@ var OAuthBearerAuthStrategy = (_class = class {
|
|
|
63
315
|
if (!refreshed) return null;
|
|
64
316
|
const fresh = await this.tokens.getFullConfig();
|
|
65
317
|
const freshEntry = this.providerId === "codex" ? fresh.codex : fresh.gemini;
|
|
66
|
-
return _nullishCoalesce(_optionalChain([freshEntry, 'optionalAccess',
|
|
318
|
+
return _nullishCoalesce(_optionalChain([freshEntry, 'optionalAccess', _15 => _15.accessToken]), () => ( null));
|
|
67
319
|
}
|
|
68
320
|
if (entry.status === "expired") return null;
|
|
69
321
|
return entry.accessToken;
|
|
70
322
|
}
|
|
71
|
-
},
|
|
323
|
+
}, _class2);
|
|
72
324
|
|
|
73
325
|
// src/auth/PassThroughAuthStrategy.ts
|
|
74
|
-
var PassThroughAuthStrategy = (
|
|
75
|
-
constructor(tokens, mutex) {;
|
|
326
|
+
var PassThroughAuthStrategy = (_class3 = class {
|
|
327
|
+
constructor(tokens, mutex, selector, health) {;_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);
|
|
76
328
|
this.tokens = tokens;
|
|
77
329
|
this.mutex = mutex;
|
|
330
|
+
this.selector = selector;
|
|
331
|
+
this.health = health;
|
|
78
332
|
}
|
|
79
333
|
|
|
80
334
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
__init5() {this.kind = "pass-through"}
|
|
338
|
+
__init6() {this.providerId = "claude"}
|
|
339
|
+
async applyHeaders(headers, hints) {
|
|
340
|
+
const token = await resolveSelectedToken(
|
|
341
|
+
this.selector,
|
|
342
|
+
this.tokens,
|
|
343
|
+
"claude",
|
|
344
|
+
_optionalChain([hints, 'optionalAccess', _16 => _16.sessionKey]),
|
|
345
|
+
() => this.tokens.getValidClaudeAccessToken(),
|
|
346
|
+
{ health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _17 => _17.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _18 => _18.resolvedModel]) }
|
|
347
|
+
);
|
|
85
348
|
if (!token) return;
|
|
86
349
|
headers["Authorization"] = `Bearer ${token}`;
|
|
87
350
|
}
|
|
88
|
-
async onUnauthorized() {
|
|
351
|
+
async onUnauthorized(sessionKey) {
|
|
352
|
+
const byId = await refreshSelectedAccount(this.selector, this.tokens, this.mutex, "claude", sessionKey);
|
|
353
|
+
if (byId !== null) return byId;
|
|
89
354
|
return this.mutex.run("claude:refresh", async () => {
|
|
90
355
|
try {
|
|
91
356
|
return await this.tokens.refreshClaudeToken();
|
|
@@ -98,7 +363,7 @@ var PassThroughAuthStrategy = (_class2 = class {
|
|
|
98
363
|
async describeStatus() {
|
|
99
364
|
const config = await this.tokens.getFullConfig();
|
|
100
365
|
const claude = config.claude;
|
|
101
|
-
if (!_optionalChain([claude, 'optionalAccess',
|
|
366
|
+
if (!_optionalChain([claude, 'optionalAccess', _19 => _19.accessToken])) {
|
|
102
367
|
return { providerId: "claude", ok: false, reason: "missing-credential" };
|
|
103
368
|
}
|
|
104
369
|
if (claude.status === "expired") {
|
|
@@ -111,11 +376,11 @@ var PassThroughAuthStrategy = (_class2 = class {
|
|
|
111
376
|
}
|
|
112
377
|
return { providerId: "claude", ok: true, expiresAt: claude.expiresAt };
|
|
113
378
|
}
|
|
114
|
-
},
|
|
379
|
+
}, _class3);
|
|
115
380
|
|
|
116
381
|
// src/auth/RefreshMutex.ts
|
|
117
|
-
var RefreshMutex = (
|
|
118
|
-
|
|
382
|
+
var RefreshMutex = (_class4 = class {constructor() { _class4.prototype.__init7.call(this); }
|
|
383
|
+
__init7() {this.inflight = /* @__PURE__ */ new Map()}
|
|
119
384
|
/**
|
|
120
385
|
* Run `task()` exclusively for `key`. If another caller is already running
|
|
121
386
|
* for the same key, this call awaits the existing promise instead of
|
|
@@ -134,34 +399,45 @@ var RefreshMutex = (_class3 = class {constructor() { _class3.prototype.__init4.c
|
|
|
134
399
|
this.inflight.set(key, promise);
|
|
135
400
|
return promise;
|
|
136
401
|
}
|
|
137
|
-
},
|
|
402
|
+
}, _class4);
|
|
138
403
|
|
|
139
404
|
// src/auth/StaticBearerAuthStrategy.ts
|
|
140
405
|
var ANTHROPIC_SHAPE_PATH = "/v1/messages";
|
|
141
|
-
var StaticBearerAuthStrategy = (
|
|
142
|
-
constructor(tokens) {;
|
|
406
|
+
var StaticBearerAuthStrategy = (_class5 = class {
|
|
407
|
+
constructor(tokens, selector, health) {;_class5.prototype.__init8.call(this);_class5.prototype.__init9.call(this);
|
|
143
408
|
this.tokens = tokens;
|
|
409
|
+
this.selector = selector;
|
|
410
|
+
this.health = health;
|
|
144
411
|
}
|
|
145
412
|
|
|
146
|
-
|
|
147
|
-
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
__init8() {this.kind = "static-bearer"}
|
|
416
|
+
__init9() {this.providerId = "opencodego"}
|
|
148
417
|
async applyHeaders(headers, hints) {
|
|
149
|
-
const key = await
|
|
418
|
+
const key = await resolveSelectedToken(
|
|
419
|
+
this.selector,
|
|
420
|
+
this.tokens,
|
|
421
|
+
"opencodego",
|
|
422
|
+
_optionalChain([hints, 'optionalAccess', _20 => _20.sessionKey]),
|
|
423
|
+
() => this.tokens.getValidOpenCodeGoApiKey(),
|
|
424
|
+
{ health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _21 => _21.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _22 => _22.resolvedModel]) }
|
|
425
|
+
);
|
|
150
426
|
if (!key) {
|
|
151
427
|
return;
|
|
152
428
|
}
|
|
153
429
|
headers["Authorization"] = `Bearer ${key}`;
|
|
154
|
-
if (_optionalChain([hints, 'optionalAccess',
|
|
430
|
+
if (_optionalChain([hints, 'optionalAccess', _23 => _23.upstreamUrl, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25(ANTHROPIC_SHAPE_PATH)])) {
|
|
155
431
|
headers["x-api-key"] = key;
|
|
156
432
|
}
|
|
157
433
|
}
|
|
158
|
-
async onUnauthorized() {
|
|
434
|
+
async onUnauthorized(_sessionKey) {
|
|
159
435
|
return false;
|
|
160
436
|
}
|
|
161
437
|
async describeStatus() {
|
|
162
438
|
const config = await this.tokens.getFullConfig();
|
|
163
439
|
const oc = config.opencodego;
|
|
164
|
-
if (!_optionalChain([oc, 'optionalAccess',
|
|
440
|
+
if (!_optionalChain([oc, 'optionalAccess', _26 => _26.apiKey])) {
|
|
165
441
|
return { providerId: "opencodego", ok: false, reason: "missing-credential" };
|
|
166
442
|
}
|
|
167
443
|
if (oc.status === "error") {
|
|
@@ -169,7 +445,7 @@ var StaticBearerAuthStrategy = (_class4 = class {
|
|
|
169
445
|
}
|
|
170
446
|
return { providerId: "opencodego", ok: true };
|
|
171
447
|
}
|
|
172
|
-
},
|
|
448
|
+
}, _class5);
|
|
173
449
|
|
|
174
450
|
// src/SubscriptionAccountService.ts
|
|
175
451
|
var DISPLAY_NAMES = {
|
|
@@ -178,15 +454,19 @@ var DISPLAY_NAMES = {
|
|
|
178
454
|
gemini: "Gemini (Google OAuth)",
|
|
179
455
|
opencodego: "OpenCodeGo (Bearer key)"
|
|
180
456
|
};
|
|
181
|
-
var SubscriptionAccountService = (
|
|
182
|
-
|
|
457
|
+
var SubscriptionAccountService = (_class6 = class {
|
|
458
|
+
__init10() {this.mutex = new RefreshMutex()}
|
|
459
|
+
/** ONE account-pool scheduler (subscription-account-scheduling) shared by all
|
|
460
|
+
* four strategies so they share the affinity map + the `lastUsedAt` overlay. */
|
|
461
|
+
__init11() {this.selector = new SubscriptionAccountSelector()}
|
|
183
462
|
|
|
184
|
-
constructor(tokens) {;
|
|
463
|
+
constructor(tokens) {;_class6.prototype.__init10.call(this);_class6.prototype.__init11.call(this);
|
|
464
|
+
const health = _SubscriptionAccountHealth.getSharedAccountHealth.call(void 0, );
|
|
185
465
|
this.strategies = /* @__PURE__ */ new Map([
|
|
186
|
-
["claude", new PassThroughAuthStrategy(tokens, this.mutex)],
|
|
187
|
-
["codex", new OAuthBearerAuthStrategy("codex", tokens, this.mutex)],
|
|
188
|
-
["gemini", new OAuthBearerAuthStrategy("gemini", tokens, this.mutex)],
|
|
189
|
-
["opencodego", new StaticBearerAuthStrategy(tokens)]
|
|
466
|
+
["claude", new PassThroughAuthStrategy(tokens, this.mutex, this.selector, health)],
|
|
467
|
+
["codex", new OAuthBearerAuthStrategy("codex", tokens, this.mutex, this.selector, health)],
|
|
468
|
+
["gemini", new OAuthBearerAuthStrategy("gemini", tokens, this.mutex, this.selector, health)],
|
|
469
|
+
["opencodego", new StaticBearerAuthStrategy(tokens, this.selector, health)]
|
|
190
470
|
]);
|
|
191
471
|
}
|
|
192
472
|
/** Returns the strategy bound to a subscription provider, or `null` for unknown ids. */
|
|
@@ -214,7 +494,7 @@ var SubscriptionAccountService = (_class5 = class {
|
|
|
214
494
|
}
|
|
215
495
|
return entries;
|
|
216
496
|
}
|
|
217
|
-
},
|
|
497
|
+
}, _class6);
|
|
218
498
|
var _moduleSingleton = null;
|
|
219
499
|
function setSubscriptionAccountService(svc) {
|
|
220
500
|
_moduleSingleton = svc;
|
|
@@ -230,21 +510,21 @@ var _subscriptionRegistryPort = require('@omnicross/core/outbound-api/subscripti
|
|
|
230
510
|
var _GeminiCodeAssistTransformer = require('@omnicross/core/transformer/transformers/GeminiCodeAssistTransformer');
|
|
231
511
|
|
|
232
512
|
// src/opencodego/CircuitBreaker.ts
|
|
233
|
-
var CircuitBreaker = (
|
|
234
|
-
|
|
513
|
+
var CircuitBreaker = (_class7 = class {
|
|
514
|
+
__init12() {this.state = "closed"}
|
|
235
515
|
/** CONSECUTIVE failures while closed (reset by any closed success). */
|
|
236
|
-
|
|
516
|
+
__init13() {this.failureCount = 0}
|
|
237
517
|
/** Successes accumulated in the current half-open probe window. */
|
|
238
|
-
|
|
518
|
+
__init14() {this.successCount = 0}
|
|
239
519
|
/** Test calls admitted in the current half-open window (cap = halfOpenMaxCalls). */
|
|
240
|
-
|
|
520
|
+
__init15() {this.halfOpenCalls = 0}
|
|
241
521
|
/** `now()` at the last recorded failure — drives the open→half-open elapsed check. */
|
|
242
|
-
|
|
522
|
+
__init16() {this.lastFailureTime = 0}
|
|
243
523
|
|
|
244
524
|
|
|
245
525
|
|
|
246
526
|
|
|
247
|
-
constructor(opts = {}) {;
|
|
527
|
+
constructor(opts = {}) {;_class7.prototype.__init12.call(this);_class7.prototype.__init13.call(this);_class7.prototype.__init14.call(this);_class7.prototype.__init15.call(this);_class7.prototype.__init16.call(this);
|
|
248
528
|
this.threshold = _nullishCoalesce(opts.threshold, () => ( 3));
|
|
249
529
|
this.openMs = _nullishCoalesce(opts.openMs, () => ( 3e4));
|
|
250
530
|
this.halfOpenMaxCalls = _nullishCoalesce(opts.halfOpenMaxCalls, () => ( 3));
|
|
@@ -327,13 +607,13 @@ var CircuitBreaker = (_class6 = class {
|
|
|
327
607
|
this.state = "open";
|
|
328
608
|
}
|
|
329
609
|
}
|
|
330
|
-
},
|
|
331
|
-
var CircuitBreakerRegistry = (
|
|
332
|
-
constructor(options = {}) {;
|
|
610
|
+
}, _class7);
|
|
611
|
+
var CircuitBreakerRegistry = (_class8 = class {
|
|
612
|
+
constructor(options = {}) {;_class8.prototype.__init17.call(this);
|
|
333
613
|
this.options = options;
|
|
334
614
|
}
|
|
335
615
|
|
|
336
|
-
|
|
616
|
+
__init17() {this.breakers = /* @__PURE__ */ new Map()}
|
|
337
617
|
/** Get (or lazily create) the breaker for a model id. */
|
|
338
618
|
get(modelId) {
|
|
339
619
|
let breaker = this.breakers.get(modelId);
|
|
@@ -355,7 +635,7 @@ var CircuitBreakerRegistry = (_class7 = class {
|
|
|
355
635
|
recordFailure(modelId) {
|
|
356
636
|
this.get(modelId).recordFailure();
|
|
357
637
|
}
|
|
358
|
-
},
|
|
638
|
+
}, _class8);
|
|
359
639
|
|
|
360
640
|
// src/opencodego/defaults.ts
|
|
361
641
|
var DEFAULT_OPENCODEGO_LONG_CONTEXT_THRESHOLD = 8e4;
|
|
@@ -501,11 +781,11 @@ function resolveOpenCodeGoShape(entry) {
|
|
|
501
781
|
function resolveOpenCodeGoHalf(modelId, config) {
|
|
502
782
|
if (!config) return "go";
|
|
503
783
|
for (const entry of Object.values(_nullishCoalesce(config.modelMap, () => ( {})))) {
|
|
504
|
-
if (_optionalChain([entry, 'optionalAccess',
|
|
784
|
+
if (_optionalChain([entry, 'optionalAccess', _27 => _27.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
|
|
505
785
|
}
|
|
506
786
|
for (const list of Object.values(_nullishCoalesce(config.fallbacks, () => ( {})))) {
|
|
507
787
|
for (const entry of _nullishCoalesce(list, () => ( []))) {
|
|
508
|
-
if (_optionalChain([entry, 'optionalAccess',
|
|
788
|
+
if (_optionalChain([entry, 'optionalAccess', _28 => _28.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
|
|
509
789
|
}
|
|
510
790
|
}
|
|
511
791
|
return "go";
|
|
@@ -605,7 +885,7 @@ function hasBackgroundPattern(loweredSlices) {
|
|
|
605
885
|
return containsAny(loweredSlices, BACKGROUND_KEYWORDS);
|
|
606
886
|
}
|
|
607
887
|
function resolveOpenCodeGoScenario(summary, config) {
|
|
608
|
-
const longContextThreshold = _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
888
|
+
const longContextThreshold = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _29 => _29.modelMap, 'optionalAccess', _30 => _30.long_context, 'optionalAccess', _31 => _31.contextThreshold]), () => ( DEFAULT_OPENCODEGO_LONG_CONTEXT_THRESHOLD));
|
|
609
889
|
if (summary.estimatedInputTokens >= longContextThreshold) {
|
|
610
890
|
return "long_context";
|
|
611
891
|
}
|
|
@@ -637,8 +917,8 @@ function resolveOpenCodeGoTarget(modelId, config) {
|
|
|
637
917
|
const shape = resolveOpenCodeGoShape({ provider: half, modelId });
|
|
638
918
|
return { half, shape };
|
|
639
919
|
}
|
|
640
|
-
var SubscriptionProviderRegistry = (
|
|
641
|
-
constructor(accounts, tokens) {;
|
|
920
|
+
var SubscriptionProviderRegistry = (_class9 = class {
|
|
921
|
+
constructor(accounts, tokens) {;_class9.prototype.__init18.call(this);
|
|
642
922
|
this.accounts = accounts;
|
|
643
923
|
this.tokens = tokens;
|
|
644
924
|
const claude = this.accounts.getStrategy("claude");
|
|
@@ -735,7 +1015,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
735
1015
|
resolveUpstreamUrl: (model, config) => {
|
|
736
1016
|
const oc = config;
|
|
737
1017
|
const { half, shape } = resolveOpenCodeGoTarget(model, oc);
|
|
738
|
-
const override = half === "zen" ? _optionalChain([oc, 'optionalAccess',
|
|
1018
|
+
const override = half === "zen" ? _optionalChain([oc, 'optionalAccess', _32 => _32.zenBaseUrl]) : _optionalChain([oc, 'optionalAccess', _33 => _33.baseUrl]);
|
|
739
1019
|
return buildOpenCodeGoUrl(half, shape, override);
|
|
740
1020
|
},
|
|
741
1021
|
// zen seam (Decision 3): vary the provider transformer chain by resolved
|
|
@@ -753,7 +1033,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
753
1033
|
modelTransformerNames: [],
|
|
754
1034
|
modelMapper: (sdkModel, summary, config) => {
|
|
755
1035
|
const scenario = resolveOpenCodeGoScenario(summary, config);
|
|
756
|
-
const entry = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1036
|
+
const entry = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess', _34 => _34.modelMap, 'optionalAccess', _35 => _35[scenario]]), () => ( _optionalChain([config, 'optionalAccess', _36 => _36.modelMap, 'optionalAccess', _37 => _37.default]))), () => ( DEFAULT_OPENCODEGO_MODEL_MAP[scenario])), () => ( DEFAULT_OPENCODEGO_MODEL_MAP.default));
|
|
757
1037
|
if (!entry) {
|
|
758
1038
|
return { resolvedModel: sdkModel, scenario };
|
|
759
1039
|
}
|
|
@@ -772,7 +1052,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
772
1052
|
// wedge permanently in half-open). When NO circuit is open this returns
|
|
773
1053
|
// the same first non-attempted entry as the prior `!attempted` filter.
|
|
774
1054
|
nextFallback: (scenario, attempted, config) => {
|
|
775
|
-
const list = _nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1055
|
+
const list = _nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess', _38 => _38.fallbacks, 'optionalAccess', _39 => _39[scenario]]), () => ( DEFAULT_OPENCODEGO_FALLBACKS[scenario])), () => ( []));
|
|
776
1056
|
for (const entry of list) {
|
|
777
1057
|
if (attempted.includes(entry.modelId)) continue;
|
|
778
1058
|
if (this.breaker.allowRequest(entry.modelId)) return entry;
|
|
@@ -803,7 +1083,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
803
1083
|
* exactly the reference's long-lived `FallbackHandler`. Constructed with the
|
|
804
1084
|
* default reference thresholds (3 / 30s / 3) and the default `Date.now` clock.
|
|
805
1085
|
*/
|
|
806
|
-
|
|
1086
|
+
__init18() {this.breaker = new CircuitBreakerRegistry()}
|
|
807
1087
|
/** Returns the dispatch profile for a known subscription provider, or
|
|
808
1088
|
* `null` for unknown ids (callers must treat null as "fall back to the
|
|
809
1089
|
* legacy LLM provider DB lookup"). */
|
|
@@ -818,7 +1098,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
818
1098
|
const full = await this.tokens.getFullConfig();
|
|
819
1099
|
return full.opencodego;
|
|
820
1100
|
}
|
|
821
|
-
},
|
|
1101
|
+
}, _class9);
|
|
822
1102
|
var _moduleSingleton2 = null;
|
|
823
1103
|
function setSubscriptionProviderRegistry(svc) {
|
|
824
1104
|
_moduleSingleton2 = svc;
|
|
@@ -830,6 +1110,10 @@ function getSubscriptionProviderRegistry() {
|
|
|
830
1110
|
|
|
831
1111
|
// src/SubscriptionDispatcher.ts
|
|
832
1112
|
var _geminicodeassistresolver = require('@omnicross/core/ports/gemini-code-assist-resolver');
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
|
|
833
1117
|
var _matchText = require('@omnicross/core/provider-proxy/matchText');
|
|
834
1118
|
var _serializeError = require('@omnicross/core/serializeError');
|
|
835
1119
|
|
|
@@ -858,6 +1142,7 @@ var SubscriptionDispatcher = class {
|
|
|
858
1142
|
* resolution and probe-detection.
|
|
859
1143
|
*/
|
|
860
1144
|
async dispatch(req) {
|
|
1145
|
+
const sessionKey = _matchText.deriveSubscriptionSessionKey.call(void 0, req.anthropicBody);
|
|
861
1146
|
const ocConfig = this.profile.providerId === "opencodego" ? await this.getOpenCodeGoConfig() : void 0;
|
|
862
1147
|
let scenario = "default";
|
|
863
1148
|
let resolvedModel = req.fallbackModel;
|
|
@@ -868,7 +1153,7 @@ var SubscriptionDispatcher = class {
|
|
|
868
1153
|
scenario = mapped.scenario;
|
|
869
1154
|
req.anthropicBody.model = resolvedModel;
|
|
870
1155
|
}
|
|
871
|
-
const upstreamUrl = _optionalChain([this, 'access',
|
|
1156
|
+
const upstreamUrl = _optionalChain([this, 'access', _40 => _40.profile, 'access', _41 => _41.resolveUpstreamUrl, 'optionalCall', _42 => _42(resolvedModel, ocConfig)]);
|
|
872
1157
|
if (!upstreamUrl) {
|
|
873
1158
|
throw new Error(`[SubscriptionDispatcher] profile=${this.profile.providerId} missing resolveUpstreamUrl`);
|
|
874
1159
|
}
|
|
@@ -876,41 +1161,53 @@ var SubscriptionDispatcher = class {
|
|
|
876
1161
|
provider: resolveOpenCodeGoHalf(resolvedModel, ocConfig),
|
|
877
1162
|
modelId: resolvedModel
|
|
878
1163
|
}) === "anthropic") {
|
|
879
|
-
await this.dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig);
|
|
1164
|
+
await this.dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey);
|
|
880
1165
|
return;
|
|
881
1166
|
}
|
|
882
|
-
await this.dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig);
|
|
1167
|
+
await this.dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey);
|
|
883
1168
|
}
|
|
884
1169
|
/** Bypass path for OpenCodeGo MiniMax models — forwards Anthropic body verbatim. */
|
|
885
|
-
async dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig) {
|
|
1170
|
+
async dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey) {
|
|
886
1171
|
const gate = this.gatePrimaryModel(resolvedModel, scenario, ocConfig);
|
|
887
1172
|
const attempted = gate.attempted;
|
|
888
1173
|
let currentModel = gate.firstModel;
|
|
1174
|
+
let usedAccountId;
|
|
1175
|
+
const reportSelection = (accountId) => {
|
|
1176
|
+
usedAccountId = accountId;
|
|
1177
|
+
};
|
|
889
1178
|
while (attempted.length < MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
890
1179
|
attempted.push(currentModel);
|
|
891
1180
|
req.anthropicBody.model = currentModel;
|
|
892
1181
|
const headers = { "content-type": "application/json" };
|
|
893
|
-
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel });
|
|
1182
|
+
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel, sessionKey, reportSelection });
|
|
894
1183
|
console.info(
|
|
895
1184
|
`[AgentProxy:subscription] REQ#${req.reqId} | opencodego anthropic-shape -> ${upstreamUrl} model=${currentModel} attempt=${attempted.length}`
|
|
896
1185
|
);
|
|
897
1186
|
try {
|
|
898
1187
|
const upstream = await this.hooks.fetchWithRetry(upstreamUrl, headers, req.anthropicBody, currentModel);
|
|
899
|
-
_optionalChain([this, 'access',
|
|
1188
|
+
_optionalChain([this, 'access', _43 => _43.profile, 'access', _44 => _44.recordModelOutcome, 'optionalCall', _45 => _45(currentModel, true)]);
|
|
1189
|
+
this.markHealth(usedAccountId, 200);
|
|
900
1190
|
await this.hooks.writeProxyResponse(req.res, upstream, req.isStream, req.reqId);
|
|
901
1191
|
return;
|
|
902
1192
|
} catch (err) {
|
|
903
|
-
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel);
|
|
1193
|
+
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel, sessionKey);
|
|
904
1194
|
if (handled.retryOnce) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1195
|
+
try {
|
|
1196
|
+
const upstream = await this.hooks.fetchWithRetry(upstreamUrl, handled.headers, req.anthropicBody, currentModel);
|
|
1197
|
+
_optionalChain([this, 'access', _46 => _46.profile, 'access', _47 => _47.recordModelOutcome, 'optionalCall', _48 => _48(currentModel, true)]);
|
|
1198
|
+
this.markHealth(usedAccountId, 200);
|
|
1199
|
+
await this.hooks.writeProxyResponse(req.res, upstream, req.isStream, req.reqId);
|
|
1200
|
+
return;
|
|
1201
|
+
} catch (retryErr) {
|
|
1202
|
+
this.markHealth(usedAccountId, errStatus(retryErr), retryErr);
|
|
1203
|
+
throw retryErr;
|
|
1204
|
+
}
|
|
909
1205
|
}
|
|
910
1206
|
if (caughtErrorBreakerOutcome(err) === "failure") {
|
|
911
|
-
_optionalChain([this, 'access',
|
|
1207
|
+
_optionalChain([this, 'access', _49 => _49.profile, 'access', _50 => _50.recordModelOutcome, 'optionalCall', _51 => _51(currentModel, false)]);
|
|
912
1208
|
}
|
|
913
|
-
|
|
1209
|
+
this.markHealth(usedAccountId, errStatus(err), err);
|
|
1210
|
+
const next = _optionalChain([this, 'access', _52 => _52.profile, 'access', _53 => _53.nextFallback, 'optionalCall', _54 => _54(scenario, attempted, ocConfig)]);
|
|
914
1211
|
if (!next || attempted.length >= MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
915
1212
|
throw err;
|
|
916
1213
|
}
|
|
@@ -923,8 +1220,8 @@ var SubscriptionDispatcher = class {
|
|
|
923
1220
|
}
|
|
924
1221
|
}
|
|
925
1222
|
/** Standard subscription transformer chain — Codex/Gemini/OpenCodeGo OpenAI-shape. */
|
|
926
|
-
async dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig) {
|
|
927
|
-
const providerNames = _nullishCoalesce(_optionalChain([this, 'access',
|
|
1223
|
+
async dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey) {
|
|
1224
|
+
const providerNames = _nullishCoalesce(_optionalChain([this, 'access', _55 => _55.profile, 'access', _56 => _56.resolveProviderTransformerNames, 'optionalCall', _57 => _57(resolvedModel, ocConfig)]), () => ( this.profile.providerTransformerNames));
|
|
928
1225
|
const providerTransformers = this.resolveTransformers(providerNames);
|
|
929
1226
|
const modelTransformers = this.resolveTransformers(this.profile.modelTransformerNames);
|
|
930
1227
|
const transformerProvider = {
|
|
@@ -940,6 +1237,10 @@ var SubscriptionDispatcher = class {
|
|
|
940
1237
|
const gate = this.gatePrimaryModel(resolvedModel, scenario, ocConfig);
|
|
941
1238
|
const attempted = gate.attempted;
|
|
942
1239
|
let currentModel = gate.firstModel;
|
|
1240
|
+
let usedAccountId;
|
|
1241
|
+
const reportSelection = (accountId) => {
|
|
1242
|
+
usedAccountId = accountId;
|
|
1243
|
+
};
|
|
943
1244
|
while (attempted.length < MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
944
1245
|
attempted.push(currentModel);
|
|
945
1246
|
req.anthropicBody.model = currentModel;
|
|
@@ -954,14 +1255,15 @@ var SubscriptionDispatcher = class {
|
|
|
954
1255
|
...config.headers
|
|
955
1256
|
};
|
|
956
1257
|
stripAuthHeaders(headers);
|
|
957
|
-
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel });
|
|
1258
|
+
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel, sessionKey, reportSelection });
|
|
958
1259
|
const fetchUrl = usesResponsesChain(providerNames) ? upstreamUrl : _nullishCoalesce(resolveConfigUrl(config.url), () => ( upstreamUrl));
|
|
959
1260
|
console.info(
|
|
960
1261
|
`[AgentProxy:subscription] REQ#${req.reqId} | provider=${this.profile.providerId} -> ${fetchUrl} model=${currentModel} attempt=${attempted.length}`
|
|
961
1262
|
);
|
|
962
1263
|
try {
|
|
963
1264
|
const upstream = await this.hooks.fetchWithRetry(fetchUrl, headers, requestBody, currentModel);
|
|
964
|
-
_optionalChain([this, 'access',
|
|
1265
|
+
_optionalChain([this, 'access', _58 => _58.profile, 'access', _59 => _59.recordModelOutcome, 'optionalCall', _60 => _60(currentModel, true)]);
|
|
1266
|
+
this.markHealth(usedAccountId, 200);
|
|
965
1267
|
const finalResponse = await this.hooks.executor.executeResponseChain(
|
|
966
1268
|
requestBody,
|
|
967
1269
|
upstream,
|
|
@@ -972,24 +1274,31 @@ var SubscriptionDispatcher = class {
|
|
|
972
1274
|
await this.hooks.writeProxyResponse(req.res, finalResponse, req.isStream, req.reqId);
|
|
973
1275
|
return;
|
|
974
1276
|
} catch (err) {
|
|
975
|
-
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel);
|
|
1277
|
+
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel, sessionKey);
|
|
976
1278
|
if (handled.retryOnce) {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1279
|
+
try {
|
|
1280
|
+
const upstream = await this.hooks.fetchWithRetry(fetchUrl, handled.headers, requestBody, currentModel);
|
|
1281
|
+
_optionalChain([this, 'access', _61 => _61.profile, 'access', _62 => _62.recordModelOutcome, 'optionalCall', _63 => _63(currentModel, true)]);
|
|
1282
|
+
this.markHealth(usedAccountId, 200);
|
|
1283
|
+
const finalResponse = await this.hooks.executor.executeResponseChain(
|
|
1284
|
+
requestBody,
|
|
1285
|
+
upstream,
|
|
1286
|
+
transformerProvider,
|
|
1287
|
+
{ providerTransformers, modelTransformers },
|
|
1288
|
+
{ endpointTransformer: this.hooks.endpointTransformer }
|
|
1289
|
+
);
|
|
1290
|
+
await this.hooks.writeProxyResponse(req.res, finalResponse, req.isStream, req.reqId);
|
|
1291
|
+
return;
|
|
1292
|
+
} catch (retryErr) {
|
|
1293
|
+
this.markHealth(usedAccountId, errStatus(retryErr), retryErr);
|
|
1294
|
+
throw retryErr;
|
|
1295
|
+
}
|
|
988
1296
|
}
|
|
989
1297
|
if (caughtErrorBreakerOutcome(err) === "failure") {
|
|
990
|
-
_optionalChain([this, 'access',
|
|
1298
|
+
_optionalChain([this, 'access', _64 => _64.profile, 'access', _65 => _65.recordModelOutcome, 'optionalCall', _66 => _66(currentModel, false)]);
|
|
991
1299
|
}
|
|
992
|
-
|
|
1300
|
+
this.markHealth(usedAccountId, errStatus(err), err);
|
|
1301
|
+
const next = _optionalChain([this, 'access', _67 => _67.profile, 'access', _68 => _68.nextFallback, 'optionalCall', _69 => _69(scenario, attempted, ocConfig)]);
|
|
993
1302
|
if (!next || attempted.length >= MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
994
1303
|
throw err;
|
|
995
1304
|
}
|
|
@@ -1019,7 +1328,7 @@ var SubscriptionDispatcher = class {
|
|
|
1019
1328
|
return { firstModel: primaryModel, attempted: [] };
|
|
1020
1329
|
}
|
|
1021
1330
|
const skipped = [primaryModel];
|
|
1022
|
-
const firstAdmitting = _optionalChain([this, 'access',
|
|
1331
|
+
const firstAdmitting = _optionalChain([this, 'access', _70 => _70.profile, 'access', _71 => _71.nextFallback, 'optionalCall', _72 => _72(scenario, skipped, ocConfig)]);
|
|
1023
1332
|
if (firstAdmitting) {
|
|
1024
1333
|
console.warn(
|
|
1025
1334
|
`[AgentProxy:subscription] opencodego primary ${primaryModel} circuit open -> first admitting fallback ${firstAdmitting.modelId}`
|
|
@@ -1036,12 +1345,12 @@ var SubscriptionDispatcher = class {
|
|
|
1036
1345
|
* Returns `{ retryOnce: true, headers }` when the strategy refreshed
|
|
1037
1346
|
* successfully (caller should retry once); otherwise re-throws.
|
|
1038
1347
|
*/
|
|
1039
|
-
async maybeRetryAfterError(err, headers, req, resolvedModel) {
|
|
1040
|
-
const status = _nullishCoalesce(_optionalChain([err, 'optionalAccess',
|
|
1348
|
+
async maybeRetryAfterError(err, headers, req, resolvedModel, sessionKey) {
|
|
1349
|
+
const status = _nullishCoalesce(_optionalChain([err, 'optionalAccess', _73 => _73.status]), () => ( 0));
|
|
1041
1350
|
if (status !== 401) {
|
|
1042
1351
|
return { retryOnce: false, headers };
|
|
1043
1352
|
}
|
|
1044
|
-
const refreshed = await this.profile.authStrategy.onUnauthorized();
|
|
1353
|
+
const refreshed = await this.profile.authStrategy.onUnauthorized(sessionKey);
|
|
1045
1354
|
if (!refreshed) {
|
|
1046
1355
|
console.warn(
|
|
1047
1356
|
`[AgentProxy:subscription] REQ#${req.reqId} | 401 not recoverable for provider=${this.profile.providerId}`
|
|
@@ -1050,7 +1359,7 @@ var SubscriptionDispatcher = class {
|
|
|
1050
1359
|
}
|
|
1051
1360
|
const fresh = { ...headers };
|
|
1052
1361
|
stripAuthHeaders(fresh);
|
|
1053
|
-
await this.applyHeadersWithRetry(fresh, { upstreamUrl: "", resolvedModel });
|
|
1362
|
+
await this.applyHeadersWithRetry(fresh, { upstreamUrl: "", resolvedModel, sessionKey });
|
|
1054
1363
|
return { retryOnce: true, headers: fresh };
|
|
1055
1364
|
}
|
|
1056
1365
|
async applyHeadersWithRetry(headers, hints) {
|
|
@@ -1060,6 +1369,28 @@ var SubscriptionDispatcher = class {
|
|
|
1060
1369
|
console.warn("[AgentProxy:subscription] authStrategy.applyHeaders threw:", _serializeError.serializeError.call(void 0, err));
|
|
1061
1370
|
}
|
|
1062
1371
|
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Mark the served account's health against ONE attempt's outcome
|
|
1374
|
+
* (subscription-account-health, task 3.4). No-op when no account was reported
|
|
1375
|
+
* (non-pooled / single-account) or on a session-cancel (status 0). On a caught
|
|
1376
|
+
* error `err` is passed so the 429-reset / 403-ban drivers are read STRUCTURALLY
|
|
1377
|
+
* from the error's upstream `headers` + `bodyText` (the `fetchWithRetry`
|
|
1378
|
+
* contract) — so daemon-path 429 cooldown + ban blocking function for
|
|
1379
|
+
* multi-account codex/gemini/opencodego pools; absent headers ⇒ a bare-429
|
|
1380
|
+
* (unmarked, lazy re-probe). Success (2xx) clears; 401/5xx/thrown → transient.
|
|
1381
|
+
*/
|
|
1382
|
+
markHealth(accountId, status, err) {
|
|
1383
|
+
if (accountId === void 0 || status === 0) return;
|
|
1384
|
+
const headers = err !== void 0 ? errHeaders(err) : void 0;
|
|
1385
|
+
const reset = headers ? _SubscriptionAccountHealth.resolveResetSeconds.call(void 0, this.profile.providerId, headers) : { resetHeaderSeconds: null, retryAfterSeconds: null };
|
|
1386
|
+
const bodyText = status === 403 && err !== void 0 ? errBodyText(err) : void 0;
|
|
1387
|
+
_SubscriptionAccountHealth.getSharedAccountHealth.call(void 0, ).recordUpstreamOutcome(this.profile.providerId, accountId, {
|
|
1388
|
+
status,
|
|
1389
|
+
resetHeaderSeconds: reset.resetHeaderSeconds,
|
|
1390
|
+
retryAfterSeconds: reset.retryAfterSeconds,
|
|
1391
|
+
bodyText
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1063
1394
|
/**
|
|
1064
1395
|
* Resolve the Code Assist project for the gemini subscription profile. Pulls
|
|
1065
1396
|
* the Bearer the bound `AuthStrategy` would inject (so the strategy stays the
|
|
@@ -1136,8 +1467,24 @@ var SubscriptionDispatcher = class {
|
|
|
1136
1467
|
}
|
|
1137
1468
|
};
|
|
1138
1469
|
var MAX_FALLBACK_ATTEMPTS_LOCAL = 3;
|
|
1470
|
+
function errStatus(err) {
|
|
1471
|
+
const status = _optionalChain([err, 'optionalAccess', _74 => _74.status]);
|
|
1472
|
+
return typeof status === "number" ? status : null;
|
|
1473
|
+
}
|
|
1474
|
+
function errHeaders(err) {
|
|
1475
|
+
const h = _optionalChain([err, 'optionalAccess', _75 => _75.headers]);
|
|
1476
|
+
if (!h) return void 0;
|
|
1477
|
+
if (typeof h.get === "function") return h;
|
|
1478
|
+
if (typeof h === "object") return h;
|
|
1479
|
+
return void 0;
|
|
1480
|
+
}
|
|
1481
|
+
function errBodyText(err) {
|
|
1482
|
+
const e = err;
|
|
1483
|
+
const raw = typeof _optionalChain([e, 'optionalAccess', _76 => _76.bodyText]) === "string" ? e.bodyText : typeof _optionalChain([e, 'optionalAccess', _77 => _77.body]) === "string" ? e.body : void 0;
|
|
1484
|
+
return _optionalChain([raw, 'optionalAccess', _78 => _78.slice, 'call', _79 => _79(0, 2048)]);
|
|
1485
|
+
}
|
|
1139
1486
|
function caughtErrorBreakerOutcome(err) {
|
|
1140
|
-
const status = _optionalChain([err, 'optionalAccess',
|
|
1487
|
+
const status = _optionalChain([err, 'optionalAccess', _80 => _80.status]);
|
|
1141
1488
|
if (typeof status !== "number") return "failure";
|
|
1142
1489
|
if (status === 0) return "neutral";
|
|
1143
1490
|
if (status >= 500 || status === 429) return "failure";
|
|
@@ -1171,4 +1518,8 @@ function stripAuthHeaders(headers) {
|
|
|
1171
1518
|
|
|
1172
1519
|
|
|
1173
1520
|
|
|
1174
|
-
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
exports.DEFAULT_ACCOUNT_PRIORITY = DEFAULT_ACCOUNT_PRIORITY; exports.LAST_USED_PERSIST_THROTTLE_MS = LAST_USED_PERSIST_THROTTLE_MS; exports.SESSION_AFFINITY_TTL_MS = SESSION_AFFINITY_TTL_MS; exports.SubscriptionAccountSelector = SubscriptionAccountSelector; exports.SubscriptionAccountService = SubscriptionAccountService; exports.SubscriptionDispatcher = SubscriptionDispatcher; exports.SubscriptionProviderRegistry = SubscriptionProviderRegistry; exports.claudeOAuth = _chunkTPW5Q25Ycjs.claude_exports; exports.codexOAuth = _chunkTPW5Q25Ycjs.codex_exports; exports.geminiOAuth = _chunkTPW5Q25Ycjs.gemini_exports; exports.getSubscriptionAccountService = getSubscriptionAccountService; exports.getSubscriptionProviderRegistry = getSubscriptionProviderRegistry; exports.setSubscriptionAccountService = setSubscriptionAccountService; exports.setSubscriptionProviderRegistry = setSubscriptionProviderRegistry;
|