@omnicross/subscriptions 0.1.2 → 0.1.4
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/LICENSE +21 -21
- package/NOTICE +45 -45
- package/README.md +15 -15
- package/dist/index.cjs +463 -100
- package/dist/index.d.cts +153 -3
- package/dist/index.d.ts +153 -3
- package/dist/index.js +405 -42
- package/package.json +59 -59
package/dist/index.cjs
CHANGED
|
@@ -1,29 +1,293 @@
|
|
|
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 preferredId = _optionalChain([ctx, 'optionalAccess', _5 => _5.preferredAccountId]);
|
|
198
|
+
if (preferredId) {
|
|
199
|
+
const preferred = gated.find((a) => a.id === preferredId);
|
|
200
|
+
if (preferred && preferred.schedulable !== false) {
|
|
201
|
+
const preferredToken = await tokens.getAccessTokenForAccount(providerId, preferredId);
|
|
202
|
+
if (preferredToken) {
|
|
203
|
+
maybeTouchLastUsed(selector, tokens, providerId, preferredId);
|
|
204
|
+
_optionalChain([report, 'optionalCall', _6 => _6(preferredId, preferredId === activeAccountId, remapFor(preferredId))]);
|
|
205
|
+
return preferredToken;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const targetId = pickByIdTarget(selector, gated, providerId, activeAccountId, sessionKey, now, poolGated);
|
|
210
|
+
if (targetId !== void 0) {
|
|
211
|
+
const byId = await tokens.getAccessTokenForAccount(providerId, targetId);
|
|
212
|
+
if (byId) {
|
|
213
|
+
maybeTouchLastUsed(selector, tokens, providerId, targetId);
|
|
214
|
+
_optionalChain([report, 'optionalCall', _7 => _7(targetId, false, remapFor(targetId))]);
|
|
215
|
+
return byId;
|
|
216
|
+
}
|
|
217
|
+
selector.evictAffinity(providerId, targetId);
|
|
218
|
+
_optionalChain([health, 'optionalAccess', _8 => _8.recordUpstreamOutcome, 'call', _9 => _9(providerId, targetId, { status: 401, now })]);
|
|
219
|
+
const remaining = gated.filter((a) => a.id !== targetId);
|
|
220
|
+
const retryId = pickByIdTarget(selector, remaining, providerId, activeAccountId, sessionKey, now, poolGated);
|
|
221
|
+
if (retryId !== void 0) {
|
|
222
|
+
const retryToken = await tokens.getAccessTokenForAccount(providerId, retryId);
|
|
223
|
+
if (retryToken) {
|
|
224
|
+
maybeTouchLastUsed(selector, tokens, providerId, retryId);
|
|
225
|
+
_optionalChain([report, 'optionalCall', _10 => _10(retryId, false, remapFor(retryId))]);
|
|
226
|
+
return retryToken;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (activeAccountId) _optionalChain([report, 'optionalCall', _11 => _11(activeAccountId, true, remapFor(activeAccountId))]);
|
|
231
|
+
return activeGetter();
|
|
232
|
+
}
|
|
233
|
+
return activeGetter();
|
|
234
|
+
}
|
|
235
|
+
function maybeTouchLastUsed(selector, tokens, providerId, accountId) {
|
|
236
|
+
if (!tokens.touchAccountLastUsed) return;
|
|
237
|
+
if (!selector.duePersist(providerId, accountId)) return;
|
|
238
|
+
void tokens.touchAccountLastUsed(providerId, accountId, (/* @__PURE__ */ new Date()).toISOString()).catch(() => {
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function refreshSelectedAccount(selector, tokens, mutex, providerId, sessionKey) {
|
|
242
|
+
if (!sessionKey || !selector || !tokens.refreshAccountToken) return null;
|
|
243
|
+
const config = await tokens.getFullConfig();
|
|
244
|
+
const { accounts, activeAccountId } = readSchedulableAccounts(config, providerId);
|
|
245
|
+
const selection = selector.select({ providerId, accounts, activeAccountId, sessionKey });
|
|
246
|
+
if (!selection || selection.isActive) return null;
|
|
247
|
+
const accountId = selection.accountId;
|
|
248
|
+
return mutex.run(`${providerId}:${accountId}`, async () => {
|
|
249
|
+
try {
|
|
250
|
+
return await _asyncNullishCoalesce(await tokens.refreshAccountToken(providerId, accountId), async () => ( false));
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.warn(`[accountSelection] ${providerId}:${accountId} by-id refresh failed:`, err);
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
7
258
|
// src/auth/OAuthBearerAuthStrategy.ts
|
|
8
259
|
var REFRESH_LEAD_MS = 5 * 6e4;
|
|
9
|
-
var OAuthBearerAuthStrategy = (
|
|
10
|
-
constructor(providerId, tokens, mutex) {;
|
|
260
|
+
var OAuthBearerAuthStrategy = (_class2 = class {
|
|
261
|
+
constructor(providerId, tokens, mutex, selector, health) {;_class2.prototype.__init4.call(this);
|
|
11
262
|
this.tokens = tokens;
|
|
12
263
|
this.mutex = mutex;
|
|
264
|
+
this.selector = selector;
|
|
265
|
+
this.health = health;
|
|
13
266
|
this.providerId = providerId;
|
|
14
267
|
}
|
|
15
268
|
|
|
16
269
|
|
|
17
|
-
__init() {this.kind = "oauth-bearer"}
|
|
18
270
|
|
|
19
|
-
|
|
20
|
-
|
|
271
|
+
|
|
272
|
+
__init4() {this.kind = "oauth-bearer"}
|
|
273
|
+
|
|
274
|
+
async applyHeaders(headers, hints) {
|
|
275
|
+
const token = await resolveSelectedToken(
|
|
276
|
+
this.selector,
|
|
277
|
+
this.tokens,
|
|
278
|
+
this.providerId,
|
|
279
|
+
_optionalChain([hints, 'optionalAccess', _12 => _12.sessionKey]),
|
|
280
|
+
() => this.resolveAccessToken(),
|
|
281
|
+
{ health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _13 => _13.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _14 => _14.resolvedModel]), preferredAccountId: _optionalChain([hints, 'optionalAccess', _15 => _15.preferredAccountId]) }
|
|
282
|
+
);
|
|
21
283
|
if (!token) {
|
|
22
284
|
return;
|
|
23
285
|
}
|
|
24
286
|
headers["Authorization"] = `Bearer ${token}`;
|
|
25
287
|
}
|
|
26
|
-
async onUnauthorized() {
|
|
288
|
+
async onUnauthorized(sessionKey) {
|
|
289
|
+
const byId = await refreshSelectedAccount(this.selector, this.tokens, this.mutex, this.providerId, sessionKey);
|
|
290
|
+
if (byId !== null) return byId;
|
|
27
291
|
return this.mutex.run(`${this.providerId}:refresh`, async () => {
|
|
28
292
|
try {
|
|
29
293
|
return this.providerId === "codex" ? await this.tokens.refreshCodexToken() : await this.tokens.refreshGeminiToken();
|
|
@@ -36,7 +300,7 @@ var OAuthBearerAuthStrategy = (_class = class {
|
|
|
36
300
|
async describeStatus() {
|
|
37
301
|
const config = await this.tokens.getFullConfig();
|
|
38
302
|
const entry = this.providerId === "codex" ? config.codex : config.gemini;
|
|
39
|
-
if (!_optionalChain([entry, 'optionalAccess',
|
|
303
|
+
if (!_optionalChain([entry, 'optionalAccess', _16 => _16.accessToken])) {
|
|
40
304
|
return { providerId: this.providerId, ok: false, reason: "missing-credential" };
|
|
41
305
|
}
|
|
42
306
|
if (entry.status === "expired") {
|
|
@@ -53,7 +317,7 @@ var OAuthBearerAuthStrategy = (_class = class {
|
|
|
53
317
|
async resolveAccessToken() {
|
|
54
318
|
const config = await this.tokens.getFullConfig();
|
|
55
319
|
const entry = this.providerId === "codex" ? config.codex : config.gemini;
|
|
56
|
-
if (!_optionalChain([entry, 'optionalAccess',
|
|
320
|
+
if (!_optionalChain([entry, 'optionalAccess', _17 => _17.accessToken])) return null;
|
|
57
321
|
const expiresAtMs = entry.expiresAt ? new Date(entry.expiresAt).getTime() : 0;
|
|
58
322
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - REFRESH_LEAD_MS;
|
|
59
323
|
if (expiringSoon && entry.refreshToken) {
|
|
@@ -63,29 +327,42 @@ var OAuthBearerAuthStrategy = (_class = class {
|
|
|
63
327
|
if (!refreshed) return null;
|
|
64
328
|
const fresh = await this.tokens.getFullConfig();
|
|
65
329
|
const freshEntry = this.providerId === "codex" ? fresh.codex : fresh.gemini;
|
|
66
|
-
return _nullishCoalesce(_optionalChain([freshEntry, 'optionalAccess',
|
|
330
|
+
return _nullishCoalesce(_optionalChain([freshEntry, 'optionalAccess', _18 => _18.accessToken]), () => ( null));
|
|
67
331
|
}
|
|
68
332
|
if (entry.status === "expired") return null;
|
|
69
333
|
return entry.accessToken;
|
|
70
334
|
}
|
|
71
|
-
},
|
|
335
|
+
}, _class2);
|
|
72
336
|
|
|
73
337
|
// src/auth/PassThroughAuthStrategy.ts
|
|
74
|
-
var PassThroughAuthStrategy = (
|
|
75
|
-
constructor(tokens, mutex) {;
|
|
338
|
+
var PassThroughAuthStrategy = (_class3 = class {
|
|
339
|
+
constructor(tokens, mutex, selector, health) {;_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);
|
|
76
340
|
this.tokens = tokens;
|
|
77
341
|
this.mutex = mutex;
|
|
342
|
+
this.selector = selector;
|
|
343
|
+
this.health = health;
|
|
78
344
|
}
|
|
79
345
|
|
|
80
346
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
__init5() {this.kind = "pass-through"}
|
|
350
|
+
__init6() {this.providerId = "claude"}
|
|
351
|
+
async applyHeaders(headers, hints) {
|
|
352
|
+
const token = await resolveSelectedToken(
|
|
353
|
+
this.selector,
|
|
354
|
+
this.tokens,
|
|
355
|
+
"claude",
|
|
356
|
+
_optionalChain([hints, 'optionalAccess', _19 => _19.sessionKey]),
|
|
357
|
+
() => this.tokens.getValidClaudeAccessToken(),
|
|
358
|
+
{ health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _20 => _20.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _21 => _21.resolvedModel]), preferredAccountId: _optionalChain([hints, 'optionalAccess', _22 => _22.preferredAccountId]) }
|
|
359
|
+
);
|
|
85
360
|
if (!token) return;
|
|
86
361
|
headers["Authorization"] = `Bearer ${token}`;
|
|
87
362
|
}
|
|
88
|
-
async onUnauthorized() {
|
|
363
|
+
async onUnauthorized(sessionKey) {
|
|
364
|
+
const byId = await refreshSelectedAccount(this.selector, this.tokens, this.mutex, "claude", sessionKey);
|
|
365
|
+
if (byId !== null) return byId;
|
|
89
366
|
return this.mutex.run("claude:refresh", async () => {
|
|
90
367
|
try {
|
|
91
368
|
return await this.tokens.refreshClaudeToken();
|
|
@@ -98,7 +375,7 @@ var PassThroughAuthStrategy = (_class2 = class {
|
|
|
98
375
|
async describeStatus() {
|
|
99
376
|
const config = await this.tokens.getFullConfig();
|
|
100
377
|
const claude = config.claude;
|
|
101
|
-
if (!_optionalChain([claude, 'optionalAccess',
|
|
378
|
+
if (!_optionalChain([claude, 'optionalAccess', _23 => _23.accessToken])) {
|
|
102
379
|
return { providerId: "claude", ok: false, reason: "missing-credential" };
|
|
103
380
|
}
|
|
104
381
|
if (claude.status === "expired") {
|
|
@@ -111,11 +388,11 @@ var PassThroughAuthStrategy = (_class2 = class {
|
|
|
111
388
|
}
|
|
112
389
|
return { providerId: "claude", ok: true, expiresAt: claude.expiresAt };
|
|
113
390
|
}
|
|
114
|
-
},
|
|
391
|
+
}, _class3);
|
|
115
392
|
|
|
116
393
|
// src/auth/RefreshMutex.ts
|
|
117
|
-
var RefreshMutex = (
|
|
118
|
-
|
|
394
|
+
var RefreshMutex = (_class4 = class {constructor() { _class4.prototype.__init7.call(this); }
|
|
395
|
+
__init7() {this.inflight = /* @__PURE__ */ new Map()}
|
|
119
396
|
/**
|
|
120
397
|
* Run `task()` exclusively for `key`. If another caller is already running
|
|
121
398
|
* for the same key, this call awaits the existing promise instead of
|
|
@@ -134,34 +411,45 @@ var RefreshMutex = (_class3 = class {constructor() { _class3.prototype.__init4.c
|
|
|
134
411
|
this.inflight.set(key, promise);
|
|
135
412
|
return promise;
|
|
136
413
|
}
|
|
137
|
-
},
|
|
414
|
+
}, _class4);
|
|
138
415
|
|
|
139
416
|
// src/auth/StaticBearerAuthStrategy.ts
|
|
140
417
|
var ANTHROPIC_SHAPE_PATH = "/v1/messages";
|
|
141
|
-
var StaticBearerAuthStrategy = (
|
|
142
|
-
constructor(tokens) {;
|
|
418
|
+
var StaticBearerAuthStrategy = (_class5 = class {
|
|
419
|
+
constructor(tokens, selector, health) {;_class5.prototype.__init8.call(this);_class5.prototype.__init9.call(this);
|
|
143
420
|
this.tokens = tokens;
|
|
421
|
+
this.selector = selector;
|
|
422
|
+
this.health = health;
|
|
144
423
|
}
|
|
145
424
|
|
|
146
|
-
|
|
147
|
-
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
__init8() {this.kind = "static-bearer"}
|
|
428
|
+
__init9() {this.providerId = "opencodego"}
|
|
148
429
|
async applyHeaders(headers, hints) {
|
|
149
|
-
const key = await
|
|
430
|
+
const key = await resolveSelectedToken(
|
|
431
|
+
this.selector,
|
|
432
|
+
this.tokens,
|
|
433
|
+
"opencodego",
|
|
434
|
+
_optionalChain([hints, 'optionalAccess', _24 => _24.sessionKey]),
|
|
435
|
+
() => this.tokens.getValidOpenCodeGoApiKey(),
|
|
436
|
+
{ health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _25 => _25.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _26 => _26.resolvedModel]), preferredAccountId: _optionalChain([hints, 'optionalAccess', _27 => _27.preferredAccountId]) }
|
|
437
|
+
);
|
|
150
438
|
if (!key) {
|
|
151
439
|
return;
|
|
152
440
|
}
|
|
153
441
|
headers["Authorization"] = `Bearer ${key}`;
|
|
154
|
-
if (_optionalChain([hints, 'optionalAccess',
|
|
442
|
+
if (_optionalChain([hints, 'optionalAccess', _28 => _28.upstreamUrl, 'optionalAccess', _29 => _29.includes, 'call', _30 => _30(ANTHROPIC_SHAPE_PATH)])) {
|
|
155
443
|
headers["x-api-key"] = key;
|
|
156
444
|
}
|
|
157
445
|
}
|
|
158
|
-
async onUnauthorized() {
|
|
446
|
+
async onUnauthorized(_sessionKey) {
|
|
159
447
|
return false;
|
|
160
448
|
}
|
|
161
449
|
async describeStatus() {
|
|
162
450
|
const config = await this.tokens.getFullConfig();
|
|
163
451
|
const oc = config.opencodego;
|
|
164
|
-
if (!_optionalChain([oc, 'optionalAccess',
|
|
452
|
+
if (!_optionalChain([oc, 'optionalAccess', _31 => _31.apiKey])) {
|
|
165
453
|
return { providerId: "opencodego", ok: false, reason: "missing-credential" };
|
|
166
454
|
}
|
|
167
455
|
if (oc.status === "error") {
|
|
@@ -169,7 +457,7 @@ var StaticBearerAuthStrategy = (_class4 = class {
|
|
|
169
457
|
}
|
|
170
458
|
return { providerId: "opencodego", ok: true };
|
|
171
459
|
}
|
|
172
|
-
},
|
|
460
|
+
}, _class5);
|
|
173
461
|
|
|
174
462
|
// src/SubscriptionAccountService.ts
|
|
175
463
|
var DISPLAY_NAMES = {
|
|
@@ -178,15 +466,19 @@ var DISPLAY_NAMES = {
|
|
|
178
466
|
gemini: "Gemini (Google OAuth)",
|
|
179
467
|
opencodego: "OpenCodeGo (Bearer key)"
|
|
180
468
|
};
|
|
181
|
-
var SubscriptionAccountService = (
|
|
182
|
-
|
|
469
|
+
var SubscriptionAccountService = (_class6 = class {
|
|
470
|
+
__init10() {this.mutex = new RefreshMutex()}
|
|
471
|
+
/** ONE account-pool scheduler (subscription-account-scheduling) shared by all
|
|
472
|
+
* four strategies so they share the affinity map + the `lastUsedAt` overlay. */
|
|
473
|
+
__init11() {this.selector = new SubscriptionAccountSelector()}
|
|
183
474
|
|
|
184
|
-
constructor(tokens) {;
|
|
475
|
+
constructor(tokens) {;_class6.prototype.__init10.call(this);_class6.prototype.__init11.call(this);
|
|
476
|
+
const health = _SubscriptionAccountHealth.getSharedAccountHealth.call(void 0, );
|
|
185
477
|
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)]
|
|
478
|
+
["claude", new PassThroughAuthStrategy(tokens, this.mutex, this.selector, health)],
|
|
479
|
+
["codex", new OAuthBearerAuthStrategy("codex", tokens, this.mutex, this.selector, health)],
|
|
480
|
+
["gemini", new OAuthBearerAuthStrategy("gemini", tokens, this.mutex, this.selector, health)],
|
|
481
|
+
["opencodego", new StaticBearerAuthStrategy(tokens, this.selector, health)]
|
|
190
482
|
]);
|
|
191
483
|
}
|
|
192
484
|
/** Returns the strategy bound to a subscription provider, or `null` for unknown ids. */
|
|
@@ -214,7 +506,7 @@ var SubscriptionAccountService = (_class5 = class {
|
|
|
214
506
|
}
|
|
215
507
|
return entries;
|
|
216
508
|
}
|
|
217
|
-
},
|
|
509
|
+
}, _class6);
|
|
218
510
|
var _moduleSingleton = null;
|
|
219
511
|
function setSubscriptionAccountService(svc) {
|
|
220
512
|
_moduleSingleton = svc;
|
|
@@ -230,21 +522,21 @@ var _subscriptionRegistryPort = require('@omnicross/core/outbound-api/subscripti
|
|
|
230
522
|
var _GeminiCodeAssistTransformer = require('@omnicross/core/transformer/transformers/GeminiCodeAssistTransformer');
|
|
231
523
|
|
|
232
524
|
// src/opencodego/CircuitBreaker.ts
|
|
233
|
-
var CircuitBreaker = (
|
|
234
|
-
|
|
525
|
+
var CircuitBreaker = (_class7 = class {
|
|
526
|
+
__init12() {this.state = "closed"}
|
|
235
527
|
/** CONSECUTIVE failures while closed (reset by any closed success). */
|
|
236
|
-
|
|
528
|
+
__init13() {this.failureCount = 0}
|
|
237
529
|
/** Successes accumulated in the current half-open probe window. */
|
|
238
|
-
|
|
530
|
+
__init14() {this.successCount = 0}
|
|
239
531
|
/** Test calls admitted in the current half-open window (cap = halfOpenMaxCalls). */
|
|
240
|
-
|
|
532
|
+
__init15() {this.halfOpenCalls = 0}
|
|
241
533
|
/** `now()` at the last recorded failure — drives the open→half-open elapsed check. */
|
|
242
|
-
|
|
534
|
+
__init16() {this.lastFailureTime = 0}
|
|
243
535
|
|
|
244
536
|
|
|
245
537
|
|
|
246
538
|
|
|
247
|
-
constructor(opts = {}) {;
|
|
539
|
+
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
540
|
this.threshold = _nullishCoalesce(opts.threshold, () => ( 3));
|
|
249
541
|
this.openMs = _nullishCoalesce(opts.openMs, () => ( 3e4));
|
|
250
542
|
this.halfOpenMaxCalls = _nullishCoalesce(opts.halfOpenMaxCalls, () => ( 3));
|
|
@@ -327,13 +619,13 @@ var CircuitBreaker = (_class6 = class {
|
|
|
327
619
|
this.state = "open";
|
|
328
620
|
}
|
|
329
621
|
}
|
|
330
|
-
},
|
|
331
|
-
var CircuitBreakerRegistry = (
|
|
332
|
-
constructor(options = {}) {;
|
|
622
|
+
}, _class7);
|
|
623
|
+
var CircuitBreakerRegistry = (_class8 = class {
|
|
624
|
+
constructor(options = {}) {;_class8.prototype.__init17.call(this);
|
|
333
625
|
this.options = options;
|
|
334
626
|
}
|
|
335
627
|
|
|
336
|
-
|
|
628
|
+
__init17() {this.breakers = /* @__PURE__ */ new Map()}
|
|
337
629
|
/** Get (or lazily create) the breaker for a model id. */
|
|
338
630
|
get(modelId) {
|
|
339
631
|
let breaker = this.breakers.get(modelId);
|
|
@@ -355,7 +647,7 @@ var CircuitBreakerRegistry = (_class7 = class {
|
|
|
355
647
|
recordFailure(modelId) {
|
|
356
648
|
this.get(modelId).recordFailure();
|
|
357
649
|
}
|
|
358
|
-
},
|
|
650
|
+
}, _class8);
|
|
359
651
|
|
|
360
652
|
// src/opencodego/defaults.ts
|
|
361
653
|
var DEFAULT_OPENCODEGO_LONG_CONTEXT_THRESHOLD = 8e4;
|
|
@@ -501,11 +793,11 @@ function resolveOpenCodeGoShape(entry) {
|
|
|
501
793
|
function resolveOpenCodeGoHalf(modelId, config) {
|
|
502
794
|
if (!config) return "go";
|
|
503
795
|
for (const entry of Object.values(_nullishCoalesce(config.modelMap, () => ( {})))) {
|
|
504
|
-
if (_optionalChain([entry, 'optionalAccess',
|
|
796
|
+
if (_optionalChain([entry, 'optionalAccess', _32 => _32.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
|
|
505
797
|
}
|
|
506
798
|
for (const list of Object.values(_nullishCoalesce(config.fallbacks, () => ( {})))) {
|
|
507
799
|
for (const entry of _nullishCoalesce(list, () => ( []))) {
|
|
508
|
-
if (_optionalChain([entry, 'optionalAccess',
|
|
800
|
+
if (_optionalChain([entry, 'optionalAccess', _33 => _33.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
|
|
509
801
|
}
|
|
510
802
|
}
|
|
511
803
|
return "go";
|
|
@@ -605,7 +897,7 @@ function hasBackgroundPattern(loweredSlices) {
|
|
|
605
897
|
return containsAny(loweredSlices, BACKGROUND_KEYWORDS);
|
|
606
898
|
}
|
|
607
899
|
function resolveOpenCodeGoScenario(summary, config) {
|
|
608
|
-
const longContextThreshold = _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
900
|
+
const longContextThreshold = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _34 => _34.modelMap, 'optionalAccess', _35 => _35.long_context, 'optionalAccess', _36 => _36.contextThreshold]), () => ( DEFAULT_OPENCODEGO_LONG_CONTEXT_THRESHOLD));
|
|
609
901
|
if (summary.estimatedInputTokens >= longContextThreshold) {
|
|
610
902
|
return "long_context";
|
|
611
903
|
}
|
|
@@ -637,8 +929,8 @@ function resolveOpenCodeGoTarget(modelId, config) {
|
|
|
637
929
|
const shape = resolveOpenCodeGoShape({ provider: half, modelId });
|
|
638
930
|
return { half, shape };
|
|
639
931
|
}
|
|
640
|
-
var SubscriptionProviderRegistry = (
|
|
641
|
-
constructor(accounts, tokens) {;
|
|
932
|
+
var SubscriptionProviderRegistry = (_class9 = class {
|
|
933
|
+
constructor(accounts, tokens) {;_class9.prototype.__init18.call(this);
|
|
642
934
|
this.accounts = accounts;
|
|
643
935
|
this.tokens = tokens;
|
|
644
936
|
const claude = this.accounts.getStrategy("claude");
|
|
@@ -735,7 +1027,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
735
1027
|
resolveUpstreamUrl: (model, config) => {
|
|
736
1028
|
const oc = config;
|
|
737
1029
|
const { half, shape } = resolveOpenCodeGoTarget(model, oc);
|
|
738
|
-
const override = half === "zen" ? _optionalChain([oc, 'optionalAccess',
|
|
1030
|
+
const override = half === "zen" ? _optionalChain([oc, 'optionalAccess', _37 => _37.zenBaseUrl]) : _optionalChain([oc, 'optionalAccess', _38 => _38.baseUrl]);
|
|
739
1031
|
return buildOpenCodeGoUrl(half, shape, override);
|
|
740
1032
|
},
|
|
741
1033
|
// zen seam (Decision 3): vary the provider transformer chain by resolved
|
|
@@ -753,7 +1045,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
753
1045
|
modelTransformerNames: [],
|
|
754
1046
|
modelMapper: (sdkModel, summary, config) => {
|
|
755
1047
|
const scenario = resolveOpenCodeGoScenario(summary, config);
|
|
756
|
-
const entry = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1048
|
+
const entry = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess', _39 => _39.modelMap, 'optionalAccess', _40 => _40[scenario]]), () => ( _optionalChain([config, 'optionalAccess', _41 => _41.modelMap, 'optionalAccess', _42 => _42.default]))), () => ( DEFAULT_OPENCODEGO_MODEL_MAP[scenario])), () => ( DEFAULT_OPENCODEGO_MODEL_MAP.default));
|
|
757
1049
|
if (!entry) {
|
|
758
1050
|
return { resolvedModel: sdkModel, scenario };
|
|
759
1051
|
}
|
|
@@ -772,7 +1064,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
772
1064
|
// wedge permanently in half-open). When NO circuit is open this returns
|
|
773
1065
|
// the same first non-attempted entry as the prior `!attempted` filter.
|
|
774
1066
|
nextFallback: (scenario, attempted, config) => {
|
|
775
|
-
const list = _nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1067
|
+
const list = _nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess', _43 => _43.fallbacks, 'optionalAccess', _44 => _44[scenario]]), () => ( DEFAULT_OPENCODEGO_FALLBACKS[scenario])), () => ( []));
|
|
776
1068
|
for (const entry of list) {
|
|
777
1069
|
if (attempted.includes(entry.modelId)) continue;
|
|
778
1070
|
if (this.breaker.allowRequest(entry.modelId)) return entry;
|
|
@@ -803,7 +1095,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
803
1095
|
* exactly the reference's long-lived `FallbackHandler`. Constructed with the
|
|
804
1096
|
* default reference thresholds (3 / 30s / 3) and the default `Date.now` clock.
|
|
805
1097
|
*/
|
|
806
|
-
|
|
1098
|
+
__init18() {this.breaker = new CircuitBreakerRegistry()}
|
|
807
1099
|
/** Returns the dispatch profile for a known subscription provider, or
|
|
808
1100
|
* `null` for unknown ids (callers must treat null as "fall back to the
|
|
809
1101
|
* legacy LLM provider DB lookup"). */
|
|
@@ -818,7 +1110,7 @@ var SubscriptionProviderRegistry = (_class8 = class {
|
|
|
818
1110
|
const full = await this.tokens.getFullConfig();
|
|
819
1111
|
return full.opencodego;
|
|
820
1112
|
}
|
|
821
|
-
},
|
|
1113
|
+
}, _class9);
|
|
822
1114
|
var _moduleSingleton2 = null;
|
|
823
1115
|
function setSubscriptionProviderRegistry(svc) {
|
|
824
1116
|
_moduleSingleton2 = svc;
|
|
@@ -830,6 +1122,10 @@ function getSubscriptionProviderRegistry() {
|
|
|
830
1122
|
|
|
831
1123
|
// src/SubscriptionDispatcher.ts
|
|
832
1124
|
var _geminicodeassistresolver = require('@omnicross/core/ports/gemini-code-assist-resolver');
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
|
|
833
1129
|
var _matchText = require('@omnicross/core/provider-proxy/matchText');
|
|
834
1130
|
var _serializeError = require('@omnicross/core/serializeError');
|
|
835
1131
|
|
|
@@ -858,6 +1154,7 @@ var SubscriptionDispatcher = class {
|
|
|
858
1154
|
* resolution and probe-detection.
|
|
859
1155
|
*/
|
|
860
1156
|
async dispatch(req) {
|
|
1157
|
+
const sessionKey = _matchText.deriveSubscriptionSessionKey.call(void 0, req.anthropicBody);
|
|
861
1158
|
const ocConfig = this.profile.providerId === "opencodego" ? await this.getOpenCodeGoConfig() : void 0;
|
|
862
1159
|
let scenario = "default";
|
|
863
1160
|
let resolvedModel = req.fallbackModel;
|
|
@@ -868,7 +1165,7 @@ var SubscriptionDispatcher = class {
|
|
|
868
1165
|
scenario = mapped.scenario;
|
|
869
1166
|
req.anthropicBody.model = resolvedModel;
|
|
870
1167
|
}
|
|
871
|
-
const upstreamUrl = _optionalChain([this, 'access',
|
|
1168
|
+
const upstreamUrl = _optionalChain([this, 'access', _45 => _45.profile, 'access', _46 => _46.resolveUpstreamUrl, 'optionalCall', _47 => _47(resolvedModel, ocConfig)]);
|
|
872
1169
|
if (!upstreamUrl) {
|
|
873
1170
|
throw new Error(`[SubscriptionDispatcher] profile=${this.profile.providerId} missing resolveUpstreamUrl`);
|
|
874
1171
|
}
|
|
@@ -876,41 +1173,53 @@ var SubscriptionDispatcher = class {
|
|
|
876
1173
|
provider: resolveOpenCodeGoHalf(resolvedModel, ocConfig),
|
|
877
1174
|
modelId: resolvedModel
|
|
878
1175
|
}) === "anthropic") {
|
|
879
|
-
await this.dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig);
|
|
1176
|
+
await this.dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey);
|
|
880
1177
|
return;
|
|
881
1178
|
}
|
|
882
|
-
await this.dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig);
|
|
1179
|
+
await this.dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey);
|
|
883
1180
|
}
|
|
884
1181
|
/** Bypass path for OpenCodeGo MiniMax models — forwards Anthropic body verbatim. */
|
|
885
|
-
async dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig) {
|
|
1182
|
+
async dispatchAnthropicShapeBypass(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey) {
|
|
886
1183
|
const gate = this.gatePrimaryModel(resolvedModel, scenario, ocConfig);
|
|
887
1184
|
const attempted = gate.attempted;
|
|
888
1185
|
let currentModel = gate.firstModel;
|
|
1186
|
+
let usedAccountId;
|
|
1187
|
+
const reportSelection = (accountId) => {
|
|
1188
|
+
usedAccountId = accountId;
|
|
1189
|
+
};
|
|
889
1190
|
while (attempted.length < MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
890
1191
|
attempted.push(currentModel);
|
|
891
1192
|
req.anthropicBody.model = currentModel;
|
|
892
1193
|
const headers = { "content-type": "application/json" };
|
|
893
|
-
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel });
|
|
1194
|
+
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel, sessionKey, reportSelection });
|
|
894
1195
|
console.info(
|
|
895
1196
|
`[AgentProxy:subscription] REQ#${req.reqId} | opencodego anthropic-shape -> ${upstreamUrl} model=${currentModel} attempt=${attempted.length}`
|
|
896
1197
|
);
|
|
897
1198
|
try {
|
|
898
1199
|
const upstream = await this.hooks.fetchWithRetry(upstreamUrl, headers, req.anthropicBody, currentModel);
|
|
899
|
-
_optionalChain([this, 'access',
|
|
1200
|
+
_optionalChain([this, 'access', _48 => _48.profile, 'access', _49 => _49.recordModelOutcome, 'optionalCall', _50 => _50(currentModel, true)]);
|
|
1201
|
+
this.markHealth(usedAccountId, 200);
|
|
900
1202
|
await this.hooks.writeProxyResponse(req.res, upstream, req.isStream, req.reqId);
|
|
901
1203
|
return;
|
|
902
1204
|
} catch (err) {
|
|
903
|
-
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel);
|
|
1205
|
+
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel, sessionKey);
|
|
904
1206
|
if (handled.retryOnce) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1207
|
+
try {
|
|
1208
|
+
const upstream = await this.hooks.fetchWithRetry(upstreamUrl, handled.headers, req.anthropicBody, currentModel);
|
|
1209
|
+
_optionalChain([this, 'access', _51 => _51.profile, 'access', _52 => _52.recordModelOutcome, 'optionalCall', _53 => _53(currentModel, true)]);
|
|
1210
|
+
this.markHealth(usedAccountId, 200);
|
|
1211
|
+
await this.hooks.writeProxyResponse(req.res, upstream, req.isStream, req.reqId);
|
|
1212
|
+
return;
|
|
1213
|
+
} catch (retryErr) {
|
|
1214
|
+
this.markHealth(usedAccountId, errStatus(retryErr), retryErr);
|
|
1215
|
+
throw retryErr;
|
|
1216
|
+
}
|
|
909
1217
|
}
|
|
910
1218
|
if (caughtErrorBreakerOutcome(err) === "failure") {
|
|
911
|
-
_optionalChain([this, 'access',
|
|
1219
|
+
_optionalChain([this, 'access', _54 => _54.profile, 'access', _55 => _55.recordModelOutcome, 'optionalCall', _56 => _56(currentModel, false)]);
|
|
912
1220
|
}
|
|
913
|
-
|
|
1221
|
+
this.markHealth(usedAccountId, errStatus(err), err);
|
|
1222
|
+
const next = _optionalChain([this, 'access', _57 => _57.profile, 'access', _58 => _58.nextFallback, 'optionalCall', _59 => _59(scenario, attempted, ocConfig)]);
|
|
914
1223
|
if (!next || attempted.length >= MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
915
1224
|
throw err;
|
|
916
1225
|
}
|
|
@@ -923,8 +1232,8 @@ var SubscriptionDispatcher = class {
|
|
|
923
1232
|
}
|
|
924
1233
|
}
|
|
925
1234
|
/** Standard subscription transformer chain — Codex/Gemini/OpenCodeGo OpenAI-shape. */
|
|
926
|
-
async dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig) {
|
|
927
|
-
const providerNames = _nullishCoalesce(_optionalChain([this, 'access',
|
|
1235
|
+
async dispatchTransformerChain(req, upstreamUrl, resolvedModel, scenario, ocConfig, sessionKey) {
|
|
1236
|
+
const providerNames = _nullishCoalesce(_optionalChain([this, 'access', _60 => _60.profile, 'access', _61 => _61.resolveProviderTransformerNames, 'optionalCall', _62 => _62(resolvedModel, ocConfig)]), () => ( this.profile.providerTransformerNames));
|
|
928
1237
|
const providerTransformers = this.resolveTransformers(providerNames);
|
|
929
1238
|
const modelTransformers = this.resolveTransformers(this.profile.modelTransformerNames);
|
|
930
1239
|
const transformerProvider = {
|
|
@@ -940,6 +1249,10 @@ var SubscriptionDispatcher = class {
|
|
|
940
1249
|
const gate = this.gatePrimaryModel(resolvedModel, scenario, ocConfig);
|
|
941
1250
|
const attempted = gate.attempted;
|
|
942
1251
|
let currentModel = gate.firstModel;
|
|
1252
|
+
let usedAccountId;
|
|
1253
|
+
const reportSelection = (accountId) => {
|
|
1254
|
+
usedAccountId = accountId;
|
|
1255
|
+
};
|
|
943
1256
|
while (attempted.length < MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
944
1257
|
attempted.push(currentModel);
|
|
945
1258
|
req.anthropicBody.model = currentModel;
|
|
@@ -954,14 +1267,15 @@ var SubscriptionDispatcher = class {
|
|
|
954
1267
|
...config.headers
|
|
955
1268
|
};
|
|
956
1269
|
stripAuthHeaders(headers);
|
|
957
|
-
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel });
|
|
1270
|
+
await this.applyHeadersWithRetry(headers, { upstreamUrl, resolvedModel: currentModel, sessionKey, reportSelection });
|
|
958
1271
|
const fetchUrl = usesResponsesChain(providerNames) ? upstreamUrl : _nullishCoalesce(resolveConfigUrl(config.url), () => ( upstreamUrl));
|
|
959
1272
|
console.info(
|
|
960
1273
|
`[AgentProxy:subscription] REQ#${req.reqId} | provider=${this.profile.providerId} -> ${fetchUrl} model=${currentModel} attempt=${attempted.length}`
|
|
961
1274
|
);
|
|
962
1275
|
try {
|
|
963
1276
|
const upstream = await this.hooks.fetchWithRetry(fetchUrl, headers, requestBody, currentModel);
|
|
964
|
-
_optionalChain([this, 'access',
|
|
1277
|
+
_optionalChain([this, 'access', _63 => _63.profile, 'access', _64 => _64.recordModelOutcome, 'optionalCall', _65 => _65(currentModel, true)]);
|
|
1278
|
+
this.markHealth(usedAccountId, 200);
|
|
965
1279
|
const finalResponse = await this.hooks.executor.executeResponseChain(
|
|
966
1280
|
requestBody,
|
|
967
1281
|
upstream,
|
|
@@ -972,24 +1286,31 @@ var SubscriptionDispatcher = class {
|
|
|
972
1286
|
await this.hooks.writeProxyResponse(req.res, finalResponse, req.isStream, req.reqId);
|
|
973
1287
|
return;
|
|
974
1288
|
} catch (err) {
|
|
975
|
-
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel);
|
|
1289
|
+
const handled = await this.maybeRetryAfterError(err, headers, req, currentModel, sessionKey);
|
|
976
1290
|
if (handled.retryOnce) {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1291
|
+
try {
|
|
1292
|
+
const upstream = await this.hooks.fetchWithRetry(fetchUrl, handled.headers, requestBody, currentModel);
|
|
1293
|
+
_optionalChain([this, 'access', _66 => _66.profile, 'access', _67 => _67.recordModelOutcome, 'optionalCall', _68 => _68(currentModel, true)]);
|
|
1294
|
+
this.markHealth(usedAccountId, 200);
|
|
1295
|
+
const finalResponse = await this.hooks.executor.executeResponseChain(
|
|
1296
|
+
requestBody,
|
|
1297
|
+
upstream,
|
|
1298
|
+
transformerProvider,
|
|
1299
|
+
{ providerTransformers, modelTransformers },
|
|
1300
|
+
{ endpointTransformer: this.hooks.endpointTransformer }
|
|
1301
|
+
);
|
|
1302
|
+
await this.hooks.writeProxyResponse(req.res, finalResponse, req.isStream, req.reqId);
|
|
1303
|
+
return;
|
|
1304
|
+
} catch (retryErr) {
|
|
1305
|
+
this.markHealth(usedAccountId, errStatus(retryErr), retryErr);
|
|
1306
|
+
throw retryErr;
|
|
1307
|
+
}
|
|
988
1308
|
}
|
|
989
1309
|
if (caughtErrorBreakerOutcome(err) === "failure") {
|
|
990
|
-
_optionalChain([this, 'access',
|
|
1310
|
+
_optionalChain([this, 'access', _69 => _69.profile, 'access', _70 => _70.recordModelOutcome, 'optionalCall', _71 => _71(currentModel, false)]);
|
|
991
1311
|
}
|
|
992
|
-
|
|
1312
|
+
this.markHealth(usedAccountId, errStatus(err), err);
|
|
1313
|
+
const next = _optionalChain([this, 'access', _72 => _72.profile, 'access', _73 => _73.nextFallback, 'optionalCall', _74 => _74(scenario, attempted, ocConfig)]);
|
|
993
1314
|
if (!next || attempted.length >= MAX_FALLBACK_ATTEMPTS_LOCAL) {
|
|
994
1315
|
throw err;
|
|
995
1316
|
}
|
|
@@ -1019,7 +1340,7 @@ var SubscriptionDispatcher = class {
|
|
|
1019
1340
|
return { firstModel: primaryModel, attempted: [] };
|
|
1020
1341
|
}
|
|
1021
1342
|
const skipped = [primaryModel];
|
|
1022
|
-
const firstAdmitting = _optionalChain([this, 'access',
|
|
1343
|
+
const firstAdmitting = _optionalChain([this, 'access', _75 => _75.profile, 'access', _76 => _76.nextFallback, 'optionalCall', _77 => _77(scenario, skipped, ocConfig)]);
|
|
1023
1344
|
if (firstAdmitting) {
|
|
1024
1345
|
console.warn(
|
|
1025
1346
|
`[AgentProxy:subscription] opencodego primary ${primaryModel} circuit open -> first admitting fallback ${firstAdmitting.modelId}`
|
|
@@ -1036,12 +1357,12 @@ var SubscriptionDispatcher = class {
|
|
|
1036
1357
|
* Returns `{ retryOnce: true, headers }` when the strategy refreshed
|
|
1037
1358
|
* successfully (caller should retry once); otherwise re-throws.
|
|
1038
1359
|
*/
|
|
1039
|
-
async maybeRetryAfterError(err, headers, req, resolvedModel) {
|
|
1040
|
-
const status = _nullishCoalesce(_optionalChain([err, 'optionalAccess',
|
|
1360
|
+
async maybeRetryAfterError(err, headers, req, resolvedModel, sessionKey) {
|
|
1361
|
+
const status = _nullishCoalesce(_optionalChain([err, 'optionalAccess', _78 => _78.status]), () => ( 0));
|
|
1041
1362
|
if (status !== 401) {
|
|
1042
1363
|
return { retryOnce: false, headers };
|
|
1043
1364
|
}
|
|
1044
|
-
const refreshed = await this.profile.authStrategy.onUnauthorized();
|
|
1365
|
+
const refreshed = await this.profile.authStrategy.onUnauthorized(sessionKey);
|
|
1045
1366
|
if (!refreshed) {
|
|
1046
1367
|
console.warn(
|
|
1047
1368
|
`[AgentProxy:subscription] REQ#${req.reqId} | 401 not recoverable for provider=${this.profile.providerId}`
|
|
@@ -1050,7 +1371,7 @@ var SubscriptionDispatcher = class {
|
|
|
1050
1371
|
}
|
|
1051
1372
|
const fresh = { ...headers };
|
|
1052
1373
|
stripAuthHeaders(fresh);
|
|
1053
|
-
await this.applyHeadersWithRetry(fresh, { upstreamUrl: "", resolvedModel });
|
|
1374
|
+
await this.applyHeadersWithRetry(fresh, { upstreamUrl: "", resolvedModel, sessionKey });
|
|
1054
1375
|
return { retryOnce: true, headers: fresh };
|
|
1055
1376
|
}
|
|
1056
1377
|
async applyHeadersWithRetry(headers, hints) {
|
|
@@ -1060,6 +1381,28 @@ var SubscriptionDispatcher = class {
|
|
|
1060
1381
|
console.warn("[AgentProxy:subscription] authStrategy.applyHeaders threw:", _serializeError.serializeError.call(void 0, err));
|
|
1061
1382
|
}
|
|
1062
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Mark the served account's health against ONE attempt's outcome
|
|
1386
|
+
* (subscription-account-health, task 3.4). No-op when no account was reported
|
|
1387
|
+
* (non-pooled / single-account) or on a session-cancel (status 0). On a caught
|
|
1388
|
+
* error `err` is passed so the 429-reset / 403-ban drivers are read STRUCTURALLY
|
|
1389
|
+
* from the error's upstream `headers` + `bodyText` (the `fetchWithRetry`
|
|
1390
|
+
* contract) — so daemon-path 429 cooldown + ban blocking function for
|
|
1391
|
+
* multi-account codex/gemini/opencodego pools; absent headers ⇒ a bare-429
|
|
1392
|
+
* (unmarked, lazy re-probe). Success (2xx) clears; 401/5xx/thrown → transient.
|
|
1393
|
+
*/
|
|
1394
|
+
markHealth(accountId, status, err) {
|
|
1395
|
+
if (accountId === void 0 || status === 0) return;
|
|
1396
|
+
const headers = err !== void 0 ? errHeaders(err) : void 0;
|
|
1397
|
+
const reset = headers ? _SubscriptionAccountHealth.resolveResetSeconds.call(void 0, this.profile.providerId, headers) : { resetHeaderSeconds: null, retryAfterSeconds: null };
|
|
1398
|
+
const bodyText = status === 403 && err !== void 0 ? errBodyText(err) : void 0;
|
|
1399
|
+
_SubscriptionAccountHealth.getSharedAccountHealth.call(void 0, ).recordUpstreamOutcome(this.profile.providerId, accountId, {
|
|
1400
|
+
status,
|
|
1401
|
+
resetHeaderSeconds: reset.resetHeaderSeconds,
|
|
1402
|
+
retryAfterSeconds: reset.retryAfterSeconds,
|
|
1403
|
+
bodyText
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1063
1406
|
/**
|
|
1064
1407
|
* Resolve the Code Assist project for the gemini subscription profile. Pulls
|
|
1065
1408
|
* the Bearer the bound `AuthStrategy` would inject (so the strategy stays the
|
|
@@ -1136,8 +1479,24 @@ var SubscriptionDispatcher = class {
|
|
|
1136
1479
|
}
|
|
1137
1480
|
};
|
|
1138
1481
|
var MAX_FALLBACK_ATTEMPTS_LOCAL = 3;
|
|
1482
|
+
function errStatus(err) {
|
|
1483
|
+
const status = _optionalChain([err, 'optionalAccess', _79 => _79.status]);
|
|
1484
|
+
return typeof status === "number" ? status : null;
|
|
1485
|
+
}
|
|
1486
|
+
function errHeaders(err) {
|
|
1487
|
+
const h = _optionalChain([err, 'optionalAccess', _80 => _80.headers]);
|
|
1488
|
+
if (!h) return void 0;
|
|
1489
|
+
if (typeof h.get === "function") return h;
|
|
1490
|
+
if (typeof h === "object") return h;
|
|
1491
|
+
return void 0;
|
|
1492
|
+
}
|
|
1493
|
+
function errBodyText(err) {
|
|
1494
|
+
const e = err;
|
|
1495
|
+
const raw = typeof _optionalChain([e, 'optionalAccess', _81 => _81.bodyText]) === "string" ? e.bodyText : typeof _optionalChain([e, 'optionalAccess', _82 => _82.body]) === "string" ? e.body : void 0;
|
|
1496
|
+
return _optionalChain([raw, 'optionalAccess', _83 => _83.slice, 'call', _84 => _84(0, 2048)]);
|
|
1497
|
+
}
|
|
1139
1498
|
function caughtErrorBreakerOutcome(err) {
|
|
1140
|
-
const status = _optionalChain([err, 'optionalAccess',
|
|
1499
|
+
const status = _optionalChain([err, 'optionalAccess', _85 => _85.status]);
|
|
1141
1500
|
if (typeof status !== "number") return "failure";
|
|
1142
1501
|
if (status === 0) return "neutral";
|
|
1143
1502
|
if (status >= 500 || status === 429) return "failure";
|
|
@@ -1171,4 +1530,8 @@ function stripAuthHeaders(headers) {
|
|
|
1171
1530
|
|
|
1172
1531
|
|
|
1173
1532
|
|
|
1174
|
-
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
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;
|