@omnicross/subscriptions 0.1.5 → 0.1.7
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/NOTICE +4 -33
- package/dist/{chunk-TPW5Q25Y.cjs → chunk-PWV5NBO5.cjs} +47 -16
- package/dist/{chunk-IXGHVMZB.js → chunk-UETFQ5LB.js} +47 -16
- package/dist/index.cjs +323 -154
- package/dist/index.d.cts +12 -12
- package/dist/index.d.ts +12 -12
- package/dist/index.js +278 -109
- package/dist/oauth.cjs +2 -2
- package/dist/oauth.d.cts +1 -5
- package/dist/oauth.d.ts +1 -5
- package/dist/oauth.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
claude_exports,
|
|
3
3
|
codex_exports,
|
|
4
4
|
gemini_exports
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UETFQ5LB.js";
|
|
6
6
|
|
|
7
7
|
// src/scheduler/SubscriptionAccountSelector.ts
|
|
8
8
|
var SESSION_AFFINITY_TTL_MS = 36e5;
|
|
@@ -108,6 +108,15 @@ var SubscriptionAccountSelector = class {
|
|
|
108
108
|
// src/SubscriptionAccountService.ts
|
|
109
109
|
import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
110
110
|
|
|
111
|
+
// src/scheduler/accountSelection.ts
|
|
112
|
+
import {
|
|
113
|
+
AccountAllowanceExhaustedError,
|
|
114
|
+
getSharedAccountAllowanceScheduling
|
|
115
|
+
} from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
116
|
+
import {
|
|
117
|
+
BoundAccountSelectionError
|
|
118
|
+
} from "@omnicross/core/pipeline/BoundAccountSelectionError";
|
|
119
|
+
|
|
111
120
|
// src/scheduler/accountModelMap.ts
|
|
112
121
|
function canonicalModelId(value) {
|
|
113
122
|
const idx = value.indexOf(",");
|
|
@@ -154,19 +163,104 @@ function gateSchedulable(accounts, providerId, health, now, resolvedModel, suppo
|
|
|
154
163
|
return accounts.map((a) => {
|
|
155
164
|
const healthOk = health ? health.isSchedulable(providerId, a.id, now) : true;
|
|
156
165
|
const modelOk = resolvedModel ? accountSupportsModel(supportedModelsById.get(a.id), resolvedModel) : true;
|
|
157
|
-
return { ...a, schedulable: healthOk && modelOk };
|
|
166
|
+
return { ...a, schedulable: a.schedulable !== false && healthOk && modelOk };
|
|
158
167
|
});
|
|
159
168
|
}
|
|
160
169
|
function isPoolGated(accounts, health, resolvedModel) {
|
|
161
170
|
return (health !== void 0 || resolvedModel !== void 0) && accounts.length >= 2;
|
|
162
171
|
}
|
|
172
|
+
function hasUsableToken(token) {
|
|
173
|
+
return typeof token === "string" && token.trim() !== "";
|
|
174
|
+
}
|
|
175
|
+
async function resolveStrictPreferredToken(selector, tokens, providerId, preferredId, activeGetter, ctx) {
|
|
176
|
+
let config;
|
|
177
|
+
try {
|
|
178
|
+
config = await tokens.getFullConfig();
|
|
179
|
+
} catch {
|
|
180
|
+
throw new BoundAccountSelectionError(providerId, "unavailable");
|
|
181
|
+
}
|
|
182
|
+
let accounts;
|
|
183
|
+
let activeAccountId;
|
|
184
|
+
let supportedModelsById;
|
|
185
|
+
try {
|
|
186
|
+
({ accounts, activeAccountId, supportedModelsById } = readSchedulableAccounts(config, providerId));
|
|
187
|
+
} catch {
|
|
188
|
+
throw new BoundAccountSelectionError(providerId, "unavailable");
|
|
189
|
+
}
|
|
190
|
+
const preferred = accounts.find((account) => account.id === preferredId);
|
|
191
|
+
if (!preferred) {
|
|
192
|
+
throw new BoundAccountSelectionError(providerId, "not-found");
|
|
193
|
+
}
|
|
194
|
+
if (preferred.schedulable === false) {
|
|
195
|
+
throw new BoundAccountSelectionError(providerId, "disabled");
|
|
196
|
+
}
|
|
197
|
+
if (ctx.health && !ctx.health.isSchedulable(providerId, preferredId, ctx.now)) {
|
|
198
|
+
throw new BoundAccountSelectionError(providerId, "unhealthy");
|
|
199
|
+
}
|
|
200
|
+
if (ctx.resolvedModel && !accountSupportsModel(supportedModelsById.get(preferredId), ctx.resolvedModel)) {
|
|
201
|
+
throw new BoundAccountSelectionError(providerId, "model-incompatible");
|
|
202
|
+
}
|
|
203
|
+
const allowance = getSharedAccountAllowanceScheduling().evaluate(
|
|
204
|
+
providerId,
|
|
205
|
+
preferredId,
|
|
206
|
+
preferred.priority ?? DEFAULT_ACCOUNT_PRIORITY,
|
|
207
|
+
ctx.now
|
|
208
|
+
);
|
|
209
|
+
if (allowance.action === "pause") {
|
|
210
|
+
throw new BoundAccountSelectionError(providerId, "allowance-paused", allowance.resumeAt);
|
|
211
|
+
}
|
|
212
|
+
let token = null;
|
|
213
|
+
try {
|
|
214
|
+
token = tokens.getAccessTokenForAccount ? await tokens.getAccessTokenForAccount(providerId, preferredId) : preferredId === activeAccountId ? await activeGetter() : null;
|
|
215
|
+
} catch {
|
|
216
|
+
throw new BoundAccountSelectionError(providerId, "unavailable");
|
|
217
|
+
}
|
|
218
|
+
if (!hasUsableToken(token)) {
|
|
219
|
+
throw new BoundAccountSelectionError(providerId, "empty-token");
|
|
220
|
+
}
|
|
221
|
+
const remapped = remapReportForAccount(supportedModelsById.get(preferredId), ctx.resolvedModel);
|
|
222
|
+
if (selector) maybeTouchLastUsed(selector, tokens, providerId, preferredId);
|
|
223
|
+
ctx.reportSelection?.(preferredId, preferredId === activeAccountId, remapped);
|
|
224
|
+
return token;
|
|
225
|
+
}
|
|
226
|
+
function gateByAllowance(accounts, providerId, now) {
|
|
227
|
+
const scheduling = getSharedAccountAllowanceScheduling();
|
|
228
|
+
const evaluatedAt = now ?? Date.now();
|
|
229
|
+
const eligibleBeforePolicy = accounts.filter((account) => account.schedulable !== false);
|
|
230
|
+
if (eligibleBeforePolicy.length === 0) return accounts;
|
|
231
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
232
|
+
const gated = accounts.map((account) => {
|
|
233
|
+
if (account.schedulable === false) return account;
|
|
234
|
+
const decision = scheduling.evaluate(
|
|
235
|
+
providerId,
|
|
236
|
+
account.id,
|
|
237
|
+
account.priority ?? DEFAULT_ACCOUNT_PRIORITY,
|
|
238
|
+
evaluatedAt
|
|
239
|
+
);
|
|
240
|
+
decisions.set(account.id, decision);
|
|
241
|
+
return {
|
|
242
|
+
...account,
|
|
243
|
+
priority: decision.effectivePriority,
|
|
244
|
+
schedulable: decision.schedulable
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
if (gated.some((account) => account.schedulable !== false)) return gated;
|
|
248
|
+
const paused = eligibleBeforePolicy.map((account) => decisions.get(account.id)).filter((decision) => decision?.action === "pause");
|
|
249
|
+
if (paused.length !== eligibleBeforePolicy.length) return gated;
|
|
250
|
+
const resumeAt = paused.map((decision) => decision?.resumeAt).filter((value) => !!value).sort()[0];
|
|
251
|
+
throw new AccountAllowanceExhaustedError(providerId, resumeAt);
|
|
252
|
+
}
|
|
163
253
|
function readSchedulableAccounts(config, providerId) {
|
|
164
254
|
const raw = config[ACCOUNTS_KEY[providerId]] ?? [];
|
|
165
255
|
const accounts = raw.map((a) => ({
|
|
166
256
|
id: a.id,
|
|
257
|
+
group: typeof a.group === "string" && a.group.trim() !== "" ? a.group.trim() : providerId,
|
|
167
258
|
priority: a.priority,
|
|
168
259
|
lastUsedAt: a.lastUsedAt,
|
|
169
|
-
createdAt: a.createdAt
|
|
260
|
+
createdAt: a.createdAt,
|
|
261
|
+
// Persisted opt-out is absolute, including a one-account pool. Legacy rows
|
|
262
|
+
// omit the field and therefore remain enabled.
|
|
263
|
+
schedulable: a.enabled !== false
|
|
170
264
|
}));
|
|
171
265
|
const supportedModelsById = new Map(
|
|
172
266
|
raw.map((a) => [a.id, a.supportedModels])
|
|
@@ -188,13 +282,53 @@ async function resolveSelectedToken(selector, tokens, providerId, sessionKey, ac
|
|
|
188
282
|
const report = ctx?.reportSelection;
|
|
189
283
|
const now = ctx?.now;
|
|
190
284
|
const resolvedModel = ctx?.resolvedModel;
|
|
285
|
+
const preferredId = typeof ctx?.preferredAccountId === "string" && ctx.preferredAccountId.trim() !== "" ? ctx.preferredAccountId.trim() : void 0;
|
|
286
|
+
const preferredGroup = typeof ctx?.preferredAccountGroup === "string" && ctx.preferredAccountGroup.trim() !== "" ? ctx.preferredAccountGroup.trim() : void 0;
|
|
287
|
+
if (preferredId && ctx && ctx.boundAccountFallbackPolicy !== "pool") {
|
|
288
|
+
return resolveStrictPreferredToken(selector, tokens, providerId, preferredId, activeGetter, ctx);
|
|
289
|
+
}
|
|
191
290
|
if (selector && tokens.getAccessTokenForAccount) {
|
|
192
291
|
const config = await tokens.getFullConfig();
|
|
193
292
|
const { accounts, activeAccountId, supportedModelsById } = readSchedulableAccounts(config, providerId);
|
|
194
|
-
const
|
|
195
|
-
|
|
293
|
+
const groupAccounts = preferredGroup ? accounts.filter((account) => account.group === preferredGroup) : accounts;
|
|
294
|
+
if (preferredGroup && groupAccounts.length === 0 && ctx?.boundAccountFallbackPolicy !== "pool") {
|
|
295
|
+
throw new BoundAccountSelectionError(providerId, "not-found");
|
|
296
|
+
}
|
|
297
|
+
let candidates = groupAccounts.length > 0 ? groupAccounts : accounts;
|
|
298
|
+
let healthAndModelGated = gateSchedulable(
|
|
299
|
+
candidates,
|
|
300
|
+
providerId,
|
|
301
|
+
health,
|
|
302
|
+
now,
|
|
303
|
+
resolvedModel,
|
|
304
|
+
supportedModelsById
|
|
305
|
+
);
|
|
306
|
+
let gated = gateByAllowance(healthAndModelGated, providerId, now);
|
|
307
|
+
let groupHasUsableCredential = gated.some((account) => account.schedulable !== false);
|
|
308
|
+
if (groupHasUsableCredential && preferredGroup && ctx?.boundAccountFallbackPolicy === "pool" && candidates !== accounts) {
|
|
309
|
+
groupHasUsableCredential = false;
|
|
310
|
+
for (const account of gated) {
|
|
311
|
+
if (account.schedulable === false) continue;
|
|
312
|
+
if (await tokens.getAccessTokenForAccount(providerId, account.id)) {
|
|
313
|
+
groupHasUsableCredential = true;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (preferredGroup && ctx?.boundAccountFallbackPolicy === "pool" && candidates !== accounts && !groupHasUsableCredential) {
|
|
319
|
+
candidates = accounts;
|
|
320
|
+
healthAndModelGated = gateSchedulable(
|
|
321
|
+
candidates,
|
|
322
|
+
providerId,
|
|
323
|
+
health,
|
|
324
|
+
now,
|
|
325
|
+
resolvedModel,
|
|
326
|
+
supportedModelsById
|
|
327
|
+
);
|
|
328
|
+
gated = gateByAllowance(healthAndModelGated, providerId, now);
|
|
329
|
+
}
|
|
330
|
+
const poolGated = isPoolGated(candidates, health, resolvedModel) || candidates.some((account) => account.schedulable === false) || preferredGroup !== void 0;
|
|
196
331
|
const remapFor = (id) => remapReportForAccount(supportedModelsById.get(id), resolvedModel);
|
|
197
|
-
const preferredId = ctx?.preferredAccountId;
|
|
198
332
|
if (preferredId) {
|
|
199
333
|
const preferred = gated.find((a) => a.id === preferredId);
|
|
200
334
|
if (preferred && preferred.schedulable !== false) {
|
|
@@ -227,7 +361,28 @@ async function resolveSelectedToken(selector, tokens, providerId, sessionKey, ac
|
|
|
227
361
|
}
|
|
228
362
|
}
|
|
229
363
|
}
|
|
230
|
-
if (activeAccountId)
|
|
364
|
+
if (activeAccountId) {
|
|
365
|
+
if (preferredGroup && ctx?.boundAccountFallbackPolicy !== "pool" && !candidates.some((account) => account.id === activeAccountId)) {
|
|
366
|
+
throw new BoundAccountSelectionError(providerId, "unavailable");
|
|
367
|
+
}
|
|
368
|
+
const persistedActive = accounts.find((account) => account.id === activeAccountId);
|
|
369
|
+
if (persistedActive?.schedulable === false) return null;
|
|
370
|
+
const activeAllowance = getSharedAccountAllowanceScheduling().evaluate(
|
|
371
|
+
providerId,
|
|
372
|
+
activeAccountId,
|
|
373
|
+
persistedActive?.priority ?? DEFAULT_ACCOUNT_PRIORITY,
|
|
374
|
+
now
|
|
375
|
+
);
|
|
376
|
+
if (activeAllowance.action === "pause") {
|
|
377
|
+
throw new AccountAllowanceExhaustedError(providerId, activeAllowance.resumeAt);
|
|
378
|
+
}
|
|
379
|
+
const token = await activeGetter();
|
|
380
|
+
if (hasUsableToken(token)) {
|
|
381
|
+
maybeTouchLastUsed(selector, tokens, providerId, activeAccountId);
|
|
382
|
+
report?.(activeAccountId, true, remapFor(activeAccountId));
|
|
383
|
+
}
|
|
384
|
+
return token;
|
|
385
|
+
}
|
|
231
386
|
return activeGetter();
|
|
232
387
|
}
|
|
233
388
|
return activeGetter();
|
|
@@ -278,7 +433,14 @@ var OAuthBearerAuthStrategy = class {
|
|
|
278
433
|
this.providerId,
|
|
279
434
|
hints?.sessionKey,
|
|
280
435
|
() => this.resolveAccessToken(),
|
|
281
|
-
{
|
|
436
|
+
{
|
|
437
|
+
health: this.health,
|
|
438
|
+
reportSelection: hints?.reportSelection,
|
|
439
|
+
resolvedModel: hints?.resolvedModel,
|
|
440
|
+
preferredAccountId: hints?.preferredAccountId,
|
|
441
|
+
preferredAccountGroup: hints?.preferredAccountGroup,
|
|
442
|
+
boundAccountFallbackPolicy: hints?.boundAccountFallbackPolicy
|
|
443
|
+
}
|
|
282
444
|
);
|
|
283
445
|
if (!token) {
|
|
284
446
|
return;
|
|
@@ -355,7 +517,14 @@ var PassThroughAuthStrategy = class {
|
|
|
355
517
|
"claude",
|
|
356
518
|
hints?.sessionKey,
|
|
357
519
|
() => this.tokens.getValidClaudeAccessToken(),
|
|
358
|
-
{
|
|
520
|
+
{
|
|
521
|
+
health: this.health,
|
|
522
|
+
reportSelection: hints?.reportSelection,
|
|
523
|
+
resolvedModel: hints?.resolvedModel,
|
|
524
|
+
preferredAccountId: hints?.preferredAccountId,
|
|
525
|
+
preferredAccountGroup: hints?.preferredAccountGroup,
|
|
526
|
+
boundAccountFallbackPolicy: hints?.boundAccountFallbackPolicy
|
|
527
|
+
}
|
|
359
528
|
);
|
|
360
529
|
if (!token) return;
|
|
361
530
|
headers["Authorization"] = `Bearer ${token}`;
|
|
@@ -433,7 +602,14 @@ var StaticBearerAuthStrategy = class {
|
|
|
433
602
|
"opencodego",
|
|
434
603
|
hints?.sessionKey,
|
|
435
604
|
() => this.tokens.getValidOpenCodeGoApiKey(),
|
|
436
|
-
{
|
|
605
|
+
{
|
|
606
|
+
health: this.health,
|
|
607
|
+
reportSelection: hints?.reportSelection,
|
|
608
|
+
resolvedModel: hints?.resolvedModel,
|
|
609
|
+
preferredAccountId: hints?.preferredAccountId,
|
|
610
|
+
preferredAccountGroup: hints?.preferredAccountGroup,
|
|
611
|
+
boundAccountFallbackPolicy: hints?.boundAccountFallbackPolicy
|
|
612
|
+
}
|
|
437
613
|
);
|
|
438
614
|
if (!key) {
|
|
439
615
|
return;
|
|
@@ -523,31 +699,29 @@ import { buildCodeAssistUrl } from "@omnicross/core/transformer/transformers/Gem
|
|
|
523
699
|
|
|
524
700
|
// src/opencodego/CircuitBreaker.ts
|
|
525
701
|
var CircuitBreaker = class {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
lastFailureTime = 0;
|
|
535
|
-
threshold;
|
|
536
|
-
openMs;
|
|
537
|
-
halfOpenMaxCalls;
|
|
702
|
+
snapshot = {
|
|
703
|
+
mode: "closed",
|
|
704
|
+
consecutiveFailures: 0,
|
|
705
|
+
openedAt: 0,
|
|
706
|
+
probeAdmissions: 0,
|
|
707
|
+
probeSuccesses: 0
|
|
708
|
+
};
|
|
709
|
+
limits;
|
|
538
710
|
now;
|
|
539
711
|
constructor(opts = {}) {
|
|
540
|
-
this.
|
|
541
|
-
|
|
542
|
-
|
|
712
|
+
this.limits = {
|
|
713
|
+
threshold: opts.threshold ?? 3,
|
|
714
|
+
openMs: opts.openMs ?? 3e4,
|
|
715
|
+
halfOpenMaxCalls: opts.halfOpenMaxCalls ?? 3
|
|
716
|
+
};
|
|
543
717
|
this.now = opts.now ?? Date.now;
|
|
544
718
|
}
|
|
545
719
|
/** Current state (diagnostics / tests). */
|
|
546
720
|
getState() {
|
|
547
|
-
return this.
|
|
721
|
+
return this.snapshot.mode;
|
|
548
722
|
}
|
|
549
723
|
/**
|
|
550
|
-
* Admission gate
|
|
724
|
+
* Admission gate. Returns whether a
|
|
551
725
|
* request to this model is allowed RIGHT NOW. Side-effecting BY DESIGN:
|
|
552
726
|
* - `closed` → always admit.
|
|
553
727
|
* - `open` → if `now() - lastFailureTime > openMs`, FLIP to `half-open`,
|
|
@@ -558,65 +732,82 @@ var CircuitBreaker = class {
|
|
|
558
732
|
* recorded outcome resolves the state).
|
|
559
733
|
*/
|
|
560
734
|
allowRequest() {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
case "half-open":
|
|
573
|
-
if (this.halfOpenCalls < this.halfOpenMaxCalls) {
|
|
574
|
-
this.halfOpenCalls += 1;
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
return false;
|
|
578
|
-
default:
|
|
579
|
-
return true;
|
|
735
|
+
if (this.snapshot.mode === "closed") return true;
|
|
736
|
+
if (this.snapshot.mode === "open") {
|
|
737
|
+
const elapsed = this.now() - this.snapshot.openedAt;
|
|
738
|
+
if (elapsed <= this.limits.openMs) return false;
|
|
739
|
+
this.snapshot = {
|
|
740
|
+
...this.snapshot,
|
|
741
|
+
mode: "half-open",
|
|
742
|
+
probeAdmissions: 1,
|
|
743
|
+
probeSuccesses: 0
|
|
744
|
+
};
|
|
745
|
+
return true;
|
|
580
746
|
}
|
|
747
|
+
if (this.snapshot.probeAdmissions >= this.limits.halfOpenMaxCalls) return false;
|
|
748
|
+
this.snapshot = {
|
|
749
|
+
...this.snapshot,
|
|
750
|
+
probeAdmissions: this.snapshot.probeAdmissions + 1
|
|
751
|
+
};
|
|
752
|
+
return true;
|
|
581
753
|
}
|
|
582
754
|
/**
|
|
583
|
-
* Record a successful attempt
|
|
755
|
+
* Record a successful attempt.
|
|
584
756
|
* - `half-open` → increment `successCount`; at `halfOpenMaxCalls` successes,
|
|
585
757
|
* CLOSE the circuit and reset all counters.
|
|
586
758
|
* - `closed` → reset the consecutive `failureCount` (a single good call
|
|
587
759
|
* clears the streak).
|
|
588
760
|
*/
|
|
589
761
|
recordSuccess() {
|
|
590
|
-
if (this.
|
|
591
|
-
|
|
592
|
-
if (this.
|
|
593
|
-
this.
|
|
594
|
-
this.failureCount = 0;
|
|
595
|
-
this.successCount = 0;
|
|
596
|
-
this.halfOpenCalls = 0;
|
|
762
|
+
if (this.snapshot.mode === "open") return;
|
|
763
|
+
if (this.snapshot.mode === "closed") {
|
|
764
|
+
if (this.snapshot.consecutiveFailures !== 0) {
|
|
765
|
+
this.snapshot = { ...this.snapshot, consecutiveFailures: 0 };
|
|
597
766
|
}
|
|
598
767
|
return;
|
|
599
768
|
}
|
|
600
|
-
this.
|
|
769
|
+
const probeSuccesses = this.snapshot.probeSuccesses + 1;
|
|
770
|
+
if (probeSuccesses >= this.limits.halfOpenMaxCalls) {
|
|
771
|
+
this.snapshot = {
|
|
772
|
+
mode: "closed",
|
|
773
|
+
consecutiveFailures: 0,
|
|
774
|
+
openedAt: 0,
|
|
775
|
+
probeAdmissions: 0,
|
|
776
|
+
probeSuccesses: 0
|
|
777
|
+
};
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
this.snapshot = { ...this.snapshot, probeSuccesses };
|
|
601
781
|
}
|
|
602
782
|
/**
|
|
603
|
-
* Record a failed attempt
|
|
783
|
+
* Record a failed attempt.
|
|
604
784
|
* - `half-open` → immediately RE-OPEN (one probe failure is enough); stamp
|
|
605
785
|
* `lastFailureTime`, reset `successCount`.
|
|
606
786
|
* - `closed` → increment the consecutive `failureCount`; at `threshold`,
|
|
607
787
|
* OPEN the circuit. Always stamp `lastFailureTime`.
|
|
608
788
|
*/
|
|
609
789
|
recordFailure() {
|
|
610
|
-
|
|
611
|
-
if (this.
|
|
612
|
-
this.
|
|
613
|
-
|
|
614
|
-
|
|
790
|
+
const openedAt = this.now();
|
|
791
|
+
if (this.snapshot.mode === "half-open") {
|
|
792
|
+
this.snapshot = {
|
|
793
|
+
...this.snapshot,
|
|
794
|
+
mode: "open",
|
|
795
|
+
openedAt,
|
|
796
|
+
probeAdmissions: 0,
|
|
797
|
+
probeSuccesses: 0
|
|
798
|
+
};
|
|
615
799
|
return;
|
|
616
800
|
}
|
|
617
|
-
this.
|
|
618
|
-
|
|
619
|
-
this.
|
|
801
|
+
const consecutiveFailures = this.snapshot.consecutiveFailures + 1;
|
|
802
|
+
this.snapshot = {
|
|
803
|
+
...this.snapshot,
|
|
804
|
+
consecutiveFailures,
|
|
805
|
+
openedAt,
|
|
806
|
+
mode: consecutiveFailures >= this.limits.threshold ? "open" : this.snapshot.mode
|
|
807
|
+
};
|
|
808
|
+
if (this.snapshot.mode === "open") {
|
|
809
|
+
this.snapshot.probeAdmissions = 0;
|
|
810
|
+
this.snapshot.probeSuccesses = 0;
|
|
620
811
|
}
|
|
621
812
|
}
|
|
622
813
|
};
|
|
@@ -665,8 +856,7 @@ var DEFAULT_OPENCODEGO_MODEL_MAP = {
|
|
|
665
856
|
modelId: "glm-5"
|
|
666
857
|
},
|
|
667
858
|
complex: {
|
|
668
|
-
//
|
|
669
|
-
// drifted to `mimo-v2-pro` (audit D4).
|
|
859
|
+
// Complex tasks use the higher-capability GLM variant by default.
|
|
670
860
|
modelId: "glm-5.1"
|
|
671
861
|
},
|
|
672
862
|
fast: {
|
|
@@ -805,67 +995,44 @@ function resolveOpenCodeGoHalf(modelId, config) {
|
|
|
805
995
|
|
|
806
996
|
// src/opencodego/ScenarioRouter.ts
|
|
807
997
|
var COMPLEX_KEYWORDS = [
|
|
808
|
-
// Architectural
|
|
809
|
-
"architect",
|
|
810
998
|
"architecture",
|
|
811
999
|
"refactor",
|
|
812
1000
|
"redesign",
|
|
813
|
-
"complex",
|
|
814
|
-
"difficult",
|
|
815
|
-
"challenging",
|
|
816
1001
|
"optimize",
|
|
817
1002
|
"performance",
|
|
818
|
-
"efficiency",
|
|
819
|
-
"design pattern",
|
|
820
|
-
"best practice",
|
|
821
|
-
// Tool-related keywords indicate complex operations
|
|
822
|
-
"execute",
|
|
823
|
-
"run command",
|
|
824
|
-
"bash",
|
|
825
|
-
"shell",
|
|
826
1003
|
"implement",
|
|
827
1004
|
"build",
|
|
828
|
-
"create",
|
|
829
|
-
"add feature",
|
|
830
|
-
"write to",
|
|
831
1005
|
"edit file",
|
|
832
|
-
"
|
|
1006
|
+
"debug",
|
|
1007
|
+
"migrate",
|
|
1008
|
+
"benchmark"
|
|
833
1009
|
];
|
|
834
1010
|
var THINKING_KEYWORDS = [
|
|
835
1011
|
"think",
|
|
836
|
-
"thinking",
|
|
837
1012
|
"plan",
|
|
838
1013
|
"reason",
|
|
839
|
-
"reasoning",
|
|
840
1014
|
"analyze",
|
|
841
|
-
"
|
|
842
|
-
"
|
|
1015
|
+
"step by step",
|
|
1016
|
+
"evaluate",
|
|
1017
|
+
"compare tradeoffs"
|
|
843
1018
|
];
|
|
844
1019
|
var ANT_THINKING_MARKER = "antThinking";
|
|
845
1020
|
var TOOL_BLOCKERS = [
|
|
846
1021
|
"tool",
|
|
847
1022
|
"function",
|
|
848
|
-
"
|
|
849
|
-
"run command",
|
|
1023
|
+
"command",
|
|
850
1024
|
"write",
|
|
851
1025
|
"edit",
|
|
852
|
-
"create",
|
|
853
1026
|
"delete",
|
|
854
|
-
"remove",
|
|
855
1027
|
"implement",
|
|
856
1028
|
"build",
|
|
857
|
-
"add",
|
|
858
1029
|
"modify"
|
|
859
1030
|
];
|
|
860
1031
|
var BACKGROUND_KEYWORDS = [
|
|
861
1032
|
"list directory",
|
|
862
|
-
"ls -",
|
|
863
|
-
"dir",
|
|
864
1033
|
"show file",
|
|
865
|
-
"
|
|
866
|
-
"cat file",
|
|
1034
|
+
"read file",
|
|
867
1035
|
"what is",
|
|
868
|
-
"what's",
|
|
869
1036
|
"tell me about",
|
|
870
1037
|
"check status",
|
|
871
1038
|
"show status"
|
|
@@ -921,7 +1088,7 @@ function opencodegoTransformerNamesForShape(shape) {
|
|
|
921
1088
|
return ["gemini"];
|
|
922
1089
|
case "chat":
|
|
923
1090
|
default:
|
|
924
|
-
return ["
|
|
1091
|
+
return ["openai"];
|
|
925
1092
|
}
|
|
926
1093
|
}
|
|
927
1094
|
function resolveOpenCodeGoTarget(modelId, config) {
|
|
@@ -976,7 +1143,7 @@ var SubscriptionProviderRegistry = class {
|
|
|
976
1143
|
authStrategy: codex,
|
|
977
1144
|
mode: "transformer",
|
|
978
1145
|
// ChatGPT internal endpoint — accepts the OpenAI Responses API
|
|
979
|
-
// format
|
|
1146
|
+
// format and uses the Codex OAuth access token below.
|
|
980
1147
|
// The Codex OAuth access token grants access here; the public
|
|
981
1148
|
// `api.openai.com/v1/responses` endpoint would reject the same token.
|
|
982
1149
|
resolveUpstreamUrl: () => "https://chatgpt.com/backend-api/codex/responses",
|
|
@@ -1022,8 +1189,8 @@ var SubscriptionProviderRegistry = class {
|
|
|
1022
1189
|
// `ocConfig`; the core `/v1/messages` plan builder passes the opaque
|
|
1023
1190
|
// `route.subscriptionConfig`). With NO zen config every resolved model
|
|
1024
1191
|
// is go-half → byte-identical to the prior resolver.
|
|
1025
|
-
// `// UNVERIFIED (no live zen key)`: the
|
|
1026
|
-
//
|
|
1192
|
+
// `// UNVERIFIED (no live zen key)`: the ZEN endpoint hosts and paths
|
|
1193
|
+
// are covered by in-process tests only.
|
|
1027
1194
|
resolveUpstreamUrl: (model, config) => {
|
|
1028
1195
|
const oc = config;
|
|
1029
1196
|
const { half, shape } = resolveOpenCodeGoTarget(model, oc);
|
|
@@ -1031,7 +1198,7 @@ var SubscriptionProviderRegistry = class {
|
|
|
1031
1198
|
return buildOpenCodeGoUrl(half, shape, override);
|
|
1032
1199
|
},
|
|
1033
1200
|
// zen seam (Decision 3): vary the provider transformer chain by resolved
|
|
1034
|
-
// shape (anthropic⇒[] verbatim, chat⇒
|
|
1201
|
+
// shape (anthropic⇒[] verbatim, chat⇒openai, responses⇒openai-response,
|
|
1035
1202
|
// gemini⇒gemini). OPTIONAL on the profile type — only opencodego sets it;
|
|
1036
1203
|
// claude/codex/gemini omit it and fall back to `providerTransformerNames`,
|
|
1037
1204
|
// keeping their routing byte-identical. The static `providerTransformerNames`
|
|
@@ -1041,7 +1208,7 @@ var SubscriptionProviderRegistry = class {
|
|
|
1041
1208
|
const { shape } = resolveOpenCodeGoTarget(model, config);
|
|
1042
1209
|
return opencodegoTransformerNamesForShape(shape);
|
|
1043
1210
|
},
|
|
1044
|
-
providerTransformerNames: ["
|
|
1211
|
+
providerTransformerNames: ["openai"],
|
|
1045
1212
|
modelTransformerNames: [],
|
|
1046
1213
|
modelMapper: (sdkModel, summary, config) => {
|
|
1047
1214
|
const scenario = resolveOpenCodeGoScenario(summary, config);
|
|
@@ -1055,9 +1222,8 @@ var SubscriptionProviderRegistry = class {
|
|
|
1055
1222
|
// circuit is open. `breaker.allowRequest(modelId)` is the admission
|
|
1056
1223
|
// gate — calling it has the side effect of flipping an `open` model to
|
|
1057
1224
|
// `half-open` once its 30s window elapses AND counting a half-open admit
|
|
1058
|
-
// slot. It MUST therefore be consulted
|
|
1059
|
-
// on the candidate about to be attempted
|
|
1060
|
-
// (`fallback.go` calls `AllowRequest` once, on the model it returns).
|
|
1225
|
+
// slot. It MUST therefore be consulted exactly once per returned model,
|
|
1226
|
+
// on the candidate about to be attempted.
|
|
1061
1227
|
// An early-returning scan (NOT `Array.filter`, which would `allowRequest`
|
|
1062
1228
|
// every candidate and burn the admit slots of half-open models AFTER the
|
|
1063
1229
|
// chosen one — those are never attempted, never recorded, so they would
|
|
@@ -1091,9 +1257,9 @@ var SubscriptionProviderRegistry = class {
|
|
|
1091
1257
|
* process singleton, built here and captured by the opencodego profile's
|
|
1092
1258
|
* `nextFallback` (consult) + `recordModelOutcome` (record) closures. Because
|
|
1093
1259
|
* the `SubscriptionProviderRegistry` is itself a process singleton (via
|
|
1094
|
-
* `setSubscriptionProviderRegistry`), breaker state persists across requests
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1260
|
+
* `setSubscriptionProviderRegistry`), breaker state persists across requests.
|
|
1261
|
+
* Constructed with the production thresholds (3 / 30s / 3) and the default
|
|
1262
|
+
* `Date.now` clock.
|
|
1097
1263
|
*/
|
|
1098
1264
|
breaker = new CircuitBreakerRegistry();
|
|
1099
1265
|
/** Returns the dispatch profile for a known subscription provider, or
|
|
@@ -1121,6 +1287,8 @@ function getSubscriptionProviderRegistry() {
|
|
|
1121
1287
|
}
|
|
1122
1288
|
|
|
1123
1289
|
// src/SubscriptionDispatcher.ts
|
|
1290
|
+
import { isAccountAllowanceExhaustedError } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
1291
|
+
import { isBoundAccountSelectionError } from "@omnicross/core/pipeline/BoundAccountSelectionError";
|
|
1124
1292
|
import { getGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
|
|
1125
1293
|
import {
|
|
1126
1294
|
getSharedAccountHealth as getSharedAccountHealth2,
|
|
@@ -1378,6 +1546,7 @@ var SubscriptionDispatcher = class {
|
|
|
1378
1546
|
try {
|
|
1379
1547
|
await this.profile.authStrategy.applyHeaders(headers, hints);
|
|
1380
1548
|
} catch (err) {
|
|
1549
|
+
if (isAccountAllowanceExhaustedError(err) || isBoundAccountSelectionError(err)) throw err;
|
|
1381
1550
|
console.warn("[AgentProxy:subscription] authStrategy.applyHeaders threw:", serializeError(err));
|
|
1382
1551
|
}
|
|
1383
1552
|
}
|
package/dist/oauth.cjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkPWV5NBO5cjs = require('./chunk-PWV5NBO5.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
exports.claudeOAuth =
|
|
10
|
+
exports.claudeOAuth = _chunkPWV5NBO5cjs.claude_exports; exports.codexOAuth = _chunkPWV5NBO5cjs.codex_exports; exports.geminiOAuth = _chunkPWV5NBO5cjs.gemini_exports;
|
package/dist/oauth.d.cts
CHANGED
|
@@ -10,7 +10,7 @@ import { TokenExchangeRequest, OAuthParams } from '@omnicross/contracts/account-
|
|
|
10
10
|
* through an INJECTED `FetchLike` port — NO `electron`, NO `net`, NO host path
|
|
11
11
|
* (a desktop host can inject an electron-net adapter; the daemon injects global
|
|
12
12
|
* `fetch`). Claude `code=true`, `state` carried in exchange, setup-token has no
|
|
13
|
-
* refresh_token.
|
|
13
|
+
* refresh_token.
|
|
14
14
|
*
|
|
15
15
|
* @module @omnicross/subscriptions/oauth/flows/claude
|
|
16
16
|
*/
|
|
@@ -60,8 +60,6 @@ declare namespace claude {
|
|
|
60
60
|
* the exchange body, refresh carries `scope=openid profile email`, and the refresh
|
|
61
61
|
* defaults `expiresIn` to 3600 + returns an `idToken`. Network goes through the
|
|
62
62
|
* injected `FetchLike`.
|
|
63
|
-
* Reference: claude-relay-service `src/services/openaiAccountService.js`.
|
|
64
|
-
*
|
|
65
63
|
* @module @omnicross/subscriptions/oauth/flows/codex
|
|
66
64
|
*/
|
|
67
65
|
|
|
@@ -97,8 +95,6 @@ declare namespace codex {
|
|
|
97
95
|
* `refreshGeminiToken`). Network goes through the injected `FetchLike`.
|
|
98
96
|
* NOTE: `exchangeCodeForTokens` keeps a POSITIONAL signature
|
|
99
97
|
* `(authorizationCode, codeVerifier)`.
|
|
100
|
-
* Reference: claude-relay-service `src/services/geminiAccountService.js`.
|
|
101
|
-
*
|
|
102
98
|
* @module @omnicross/subscriptions/oauth/flows/gemini
|
|
103
99
|
*/
|
|
104
100
|
|
package/dist/oauth.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { TokenExchangeRequest, OAuthParams } from '@omnicross/contracts/account-
|
|
|
10
10
|
* through an INJECTED `FetchLike` port — NO `electron`, NO `net`, NO host path
|
|
11
11
|
* (a desktop host can inject an electron-net adapter; the daemon injects global
|
|
12
12
|
* `fetch`). Claude `code=true`, `state` carried in exchange, setup-token has no
|
|
13
|
-
* refresh_token.
|
|
13
|
+
* refresh_token.
|
|
14
14
|
*
|
|
15
15
|
* @module @omnicross/subscriptions/oauth/flows/claude
|
|
16
16
|
*/
|
|
@@ -60,8 +60,6 @@ declare namespace claude {
|
|
|
60
60
|
* the exchange body, refresh carries `scope=openid profile email`, and the refresh
|
|
61
61
|
* defaults `expiresIn` to 3600 + returns an `idToken`. Network goes through the
|
|
62
62
|
* injected `FetchLike`.
|
|
63
|
-
* Reference: claude-relay-service `src/services/openaiAccountService.js`.
|
|
64
|
-
*
|
|
65
63
|
* @module @omnicross/subscriptions/oauth/flows/codex
|
|
66
64
|
*/
|
|
67
65
|
|
|
@@ -97,8 +95,6 @@ declare namespace codex {
|
|
|
97
95
|
* `refreshGeminiToken`). Network goes through the injected `FetchLike`.
|
|
98
96
|
* NOTE: `exchangeCodeForTokens` keeps a POSITIONAL signature
|
|
99
97
|
* `(authorizationCode, codeVerifier)`.
|
|
100
|
-
* Reference: claude-relay-service `src/services/geminiAccountService.js`.
|
|
101
|
-
*
|
|
102
98
|
* @module @omnicross/subscriptions/oauth/flows/gemini
|
|
103
99
|
*/
|
|
104
100
|
|