@omnicross/subscriptions 0.1.3 → 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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Sayo
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sayo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE CHANGED
@@ -1,45 +1,45 @@
1
- @omnicross/subscriptions — THIRD-PARTY NOTICES
2
- ==============================================
3
-
4
- This package contains the subscription-as-provider block (auth strategies,
5
- account service, provider registry, dispatcher, OpenCodeGo routing). Some routing
6
- shapes and configuration-data values were adapted,
7
- clean-room, from the following third-party works. Those repositories are NOT
8
- vendored into this tree; only original TypeScript that mirrors their routing
9
- shape / config-data values lives here (see the in-code provenance comments).
10
-
11
- --------------------------------------------------------------------------------
12
- 1. claude-relay-service (MIT License) — routing shape only
13
- --------------------------------------------------------------------------------
14
-
15
- The Codex (ChatGPT OAuth) upstream routing shape — the
16
- `https://chatgpt.com/backend-api/codex/responses` endpoint dispatch in
17
- `src/SubscriptionProviderRegistry.ts` — mirrors the route shape of
18
- `claude-relay-service` (`src/routes/openaiRoutes.js`). Routing shape only; the
19
- TypeScript is original, and the upstream repository is not vendored.
20
-
21
- https://github.com/Wei-Shaw/claude-relay-service
22
- Licensed under the MIT License.
23
-
24
- --------------------------------------------------------------------------------
25
- 2. oc-go-cc (OpenCodeGo CC) — model-map + routing-shape / config-data
26
- --------------------------------------------------------------------------------
27
-
28
- The OpenCodeGo routing-shape, scenario router, model-map defaults, endpoint
29
- shapes, and Anthropic-shape `x-api-key` fallback in
30
- `src/opencodego/{defaults,endpoints,model-shape,ScenarioRouter}.ts` and
31
- `src/auth/StaticBearerAuthStrategy.ts` are clean-room ports of the model-map and
32
- routing shape from `oc-go-cc` (`internal/router/model_router.go`,
33
- `internal/client/opencode.go`, `configs/config.example.json`). Configuration
34
- data + routing shape only; the TypeScript is original, and the upstream
35
- repository is not vendored.
36
-
37
- --------------------------------------------------------------------------------
38
- 3. js-tiktoken (MIT License)
39
- --------------------------------------------------------------------------------
40
-
41
- `src/opencodego/token-count.ts` lazily imports `js-tiktoken` (cl100k_base
42
- encoding) to estimate input-token counts for OpenCodeGo scenario routing.
43
-
44
- https://github.com/dqbd/tiktoken
45
- Licensed under the MIT License.
1
+ @omnicross/subscriptions — THIRD-PARTY NOTICES
2
+ ==============================================
3
+
4
+ This package contains the subscription-as-provider block (auth strategies,
5
+ account service, provider registry, dispatcher, OpenCodeGo routing). Some routing
6
+ shapes and configuration-data values were adapted,
7
+ clean-room, from the following third-party works. Those repositories are NOT
8
+ vendored into this tree; only original TypeScript that mirrors their routing
9
+ shape / config-data values lives here (see the in-code provenance comments).
10
+
11
+ --------------------------------------------------------------------------------
12
+ 1. claude-relay-service (MIT License) — routing shape only
13
+ --------------------------------------------------------------------------------
14
+
15
+ The Codex (ChatGPT OAuth) upstream routing shape — the
16
+ `https://chatgpt.com/backend-api/codex/responses` endpoint dispatch in
17
+ `src/SubscriptionProviderRegistry.ts` — mirrors the route shape of
18
+ `claude-relay-service` (`src/routes/openaiRoutes.js`). Routing shape only; the
19
+ TypeScript is original, and the upstream repository is not vendored.
20
+
21
+ https://github.com/Wei-Shaw/claude-relay-service
22
+ Licensed under the MIT License.
23
+
24
+ --------------------------------------------------------------------------------
25
+ 2. oc-go-cc (OpenCodeGo CC) — model-map + routing-shape / config-data
26
+ --------------------------------------------------------------------------------
27
+
28
+ The OpenCodeGo routing-shape, scenario router, model-map defaults, endpoint
29
+ shapes, and Anthropic-shape `x-api-key` fallback in
30
+ `src/opencodego/{defaults,endpoints,model-shape,ScenarioRouter}.ts` and
31
+ `src/auth/StaticBearerAuthStrategy.ts` are clean-room ports of the model-map and
32
+ routing shape from `oc-go-cc` (`internal/router/model_router.go`,
33
+ `internal/client/opencode.go`, `configs/config.example.json`). Configuration
34
+ data + routing shape only; the TypeScript is original, and the upstream
35
+ repository is not vendored.
36
+
37
+ --------------------------------------------------------------------------------
38
+ 3. js-tiktoken (MIT License)
39
+ --------------------------------------------------------------------------------
40
+
41
+ `src/opencodego/token-count.ts` lazily imports `js-tiktoken` (cl100k_base
42
+ encoding) to estimate input-token counts for OpenCodeGo scenario routing.
43
+
44
+ https://github.com/dqbd/tiktoken
45
+ Licensed under the MIT License.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- # @omnicross/subscriptions
2
-
3
- omnicross subscription-as-provider support — pass-through / OAuth-bearer / static-bearer auth strategies, the Claude / Codex / Gemini OAuth flows, and the OpenCodeGo scenario dispatcher + fallback chain.
4
-
5
- Part of the [omnicross](https://github.com/Dumoedss/omnicross) monorepo — see the root README for the full overview.
6
-
7
- ```bash
8
- npm install @omnicross/subscriptions
9
- ```
10
-
11
- ## License
12
-
13
- [MIT](LICENSE)
14
-
15
- This package adapts third-party work under its own license — see the `NOTICE` file.
1
+ # @omnicross/subscriptions
2
+
3
+ omnicross subscription-as-provider support — pass-through / OAuth-bearer / static-bearer auth strategies, the Claude / Codex / Gemini OAuth flows, and the OpenCodeGo scenario dispatcher + fallback chain.
4
+
5
+ Part of the [omnicross](https://github.com/Dumoedss/omnicross) monorepo — see the root README for the full overview.
6
+
7
+ ```bash
8
+ npm install @omnicross/subscriptions
9
+ ```
10
+
11
+ ## License
12
+
13
+ [MIT](LICENSE)
14
+
15
+ This package adapts third-party work under its own license — see the `NOTICE` file.
package/dist/index.cjs CHANGED
@@ -194,28 +194,40 @@ async function resolveSelectedToken(selector, tokens, providerId, sessionKey, ac
194
194
  const gated = gateSchedulable(accounts, providerId, health, now, resolvedModel, supportedModelsById);
195
195
  const poolGated = isPoolGated(accounts, health, resolvedModel);
196
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
+ }
197
209
  const targetId = pickByIdTarget(selector, gated, providerId, activeAccountId, sessionKey, now, poolGated);
198
210
  if (targetId !== void 0) {
199
211
  const byId = await tokens.getAccessTokenForAccount(providerId, targetId);
200
212
  if (byId) {
201
213
  maybeTouchLastUsed(selector, tokens, providerId, targetId);
202
- _optionalChain([report, 'optionalCall', _5 => _5(targetId, false, remapFor(targetId))]);
214
+ _optionalChain([report, 'optionalCall', _7 => _7(targetId, false, remapFor(targetId))]);
203
215
  return byId;
204
216
  }
205
217
  selector.evictAffinity(providerId, targetId);
206
- _optionalChain([health, 'optionalAccess', _6 => _6.recordUpstreamOutcome, 'call', _7 => _7(providerId, targetId, { status: 401, now })]);
218
+ _optionalChain([health, 'optionalAccess', _8 => _8.recordUpstreamOutcome, 'call', _9 => _9(providerId, targetId, { status: 401, now })]);
207
219
  const remaining = gated.filter((a) => a.id !== targetId);
208
220
  const retryId = pickByIdTarget(selector, remaining, providerId, activeAccountId, sessionKey, now, poolGated);
209
221
  if (retryId !== void 0) {
210
222
  const retryToken = await tokens.getAccessTokenForAccount(providerId, retryId);
211
223
  if (retryToken) {
212
224
  maybeTouchLastUsed(selector, tokens, providerId, retryId);
213
- _optionalChain([report, 'optionalCall', _8 => _8(retryId, false, remapFor(retryId))]);
225
+ _optionalChain([report, 'optionalCall', _10 => _10(retryId, false, remapFor(retryId))]);
214
226
  return retryToken;
215
227
  }
216
228
  }
217
229
  }
218
- if (activeAccountId) _optionalChain([report, 'optionalCall', _9 => _9(activeAccountId, true, remapFor(activeAccountId))]);
230
+ if (activeAccountId) _optionalChain([report, 'optionalCall', _11 => _11(activeAccountId, true, remapFor(activeAccountId))]);
219
231
  return activeGetter();
220
232
  }
221
233
  return activeGetter();
@@ -264,9 +276,9 @@ var OAuthBearerAuthStrategy = (_class2 = class {
264
276
  this.selector,
265
277
  this.tokens,
266
278
  this.providerId,
267
- _optionalChain([hints, 'optionalAccess', _10 => _10.sessionKey]),
279
+ _optionalChain([hints, 'optionalAccess', _12 => _12.sessionKey]),
268
280
  () => this.resolveAccessToken(),
269
- { health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _11 => _11.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _12 => _12.resolvedModel]) }
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]) }
270
282
  );
271
283
  if (!token) {
272
284
  return;
@@ -288,7 +300,7 @@ var OAuthBearerAuthStrategy = (_class2 = class {
288
300
  async describeStatus() {
289
301
  const config = await this.tokens.getFullConfig();
290
302
  const entry = this.providerId === "codex" ? config.codex : config.gemini;
291
- if (!_optionalChain([entry, 'optionalAccess', _13 => _13.accessToken])) {
303
+ if (!_optionalChain([entry, 'optionalAccess', _16 => _16.accessToken])) {
292
304
  return { providerId: this.providerId, ok: false, reason: "missing-credential" };
293
305
  }
294
306
  if (entry.status === "expired") {
@@ -305,7 +317,7 @@ var OAuthBearerAuthStrategy = (_class2 = class {
305
317
  async resolveAccessToken() {
306
318
  const config = await this.tokens.getFullConfig();
307
319
  const entry = this.providerId === "codex" ? config.codex : config.gemini;
308
- if (!_optionalChain([entry, 'optionalAccess', _14 => _14.accessToken])) return null;
320
+ if (!_optionalChain([entry, 'optionalAccess', _17 => _17.accessToken])) return null;
309
321
  const expiresAtMs = entry.expiresAt ? new Date(entry.expiresAt).getTime() : 0;
310
322
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - REFRESH_LEAD_MS;
311
323
  if (expiringSoon && entry.refreshToken) {
@@ -315,7 +327,7 @@ var OAuthBearerAuthStrategy = (_class2 = class {
315
327
  if (!refreshed) return null;
316
328
  const fresh = await this.tokens.getFullConfig();
317
329
  const freshEntry = this.providerId === "codex" ? fresh.codex : fresh.gemini;
318
- return _nullishCoalesce(_optionalChain([freshEntry, 'optionalAccess', _15 => _15.accessToken]), () => ( null));
330
+ return _nullishCoalesce(_optionalChain([freshEntry, 'optionalAccess', _18 => _18.accessToken]), () => ( null));
319
331
  }
320
332
  if (entry.status === "expired") return null;
321
333
  return entry.accessToken;
@@ -341,9 +353,9 @@ var PassThroughAuthStrategy = (_class3 = class {
341
353
  this.selector,
342
354
  this.tokens,
343
355
  "claude",
344
- _optionalChain([hints, 'optionalAccess', _16 => _16.sessionKey]),
356
+ _optionalChain([hints, 'optionalAccess', _19 => _19.sessionKey]),
345
357
  () => this.tokens.getValidClaudeAccessToken(),
346
- { health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _17 => _17.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _18 => _18.resolvedModel]) }
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]) }
347
359
  );
348
360
  if (!token) return;
349
361
  headers["Authorization"] = `Bearer ${token}`;
@@ -363,7 +375,7 @@ var PassThroughAuthStrategy = (_class3 = class {
363
375
  async describeStatus() {
364
376
  const config = await this.tokens.getFullConfig();
365
377
  const claude = config.claude;
366
- if (!_optionalChain([claude, 'optionalAccess', _19 => _19.accessToken])) {
378
+ if (!_optionalChain([claude, 'optionalAccess', _23 => _23.accessToken])) {
367
379
  return { providerId: "claude", ok: false, reason: "missing-credential" };
368
380
  }
369
381
  if (claude.status === "expired") {
@@ -419,15 +431,15 @@ var StaticBearerAuthStrategy = (_class5 = class {
419
431
  this.selector,
420
432
  this.tokens,
421
433
  "opencodego",
422
- _optionalChain([hints, 'optionalAccess', _20 => _20.sessionKey]),
434
+ _optionalChain([hints, 'optionalAccess', _24 => _24.sessionKey]),
423
435
  () => this.tokens.getValidOpenCodeGoApiKey(),
424
- { health: this.health, reportSelection: _optionalChain([hints, 'optionalAccess', _21 => _21.reportSelection]), resolvedModel: _optionalChain([hints, 'optionalAccess', _22 => _22.resolvedModel]) }
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]) }
425
437
  );
426
438
  if (!key) {
427
439
  return;
428
440
  }
429
441
  headers["Authorization"] = `Bearer ${key}`;
430
- if (_optionalChain([hints, 'optionalAccess', _23 => _23.upstreamUrl, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25(ANTHROPIC_SHAPE_PATH)])) {
442
+ if (_optionalChain([hints, 'optionalAccess', _28 => _28.upstreamUrl, 'optionalAccess', _29 => _29.includes, 'call', _30 => _30(ANTHROPIC_SHAPE_PATH)])) {
431
443
  headers["x-api-key"] = key;
432
444
  }
433
445
  }
@@ -437,7 +449,7 @@ var StaticBearerAuthStrategy = (_class5 = class {
437
449
  async describeStatus() {
438
450
  const config = await this.tokens.getFullConfig();
439
451
  const oc = config.opencodego;
440
- if (!_optionalChain([oc, 'optionalAccess', _26 => _26.apiKey])) {
452
+ if (!_optionalChain([oc, 'optionalAccess', _31 => _31.apiKey])) {
441
453
  return { providerId: "opencodego", ok: false, reason: "missing-credential" };
442
454
  }
443
455
  if (oc.status === "error") {
@@ -781,11 +793,11 @@ function resolveOpenCodeGoShape(entry) {
781
793
  function resolveOpenCodeGoHalf(modelId, config) {
782
794
  if (!config) return "go";
783
795
  for (const entry of Object.values(_nullishCoalesce(config.modelMap, () => ( {})))) {
784
- if (_optionalChain([entry, 'optionalAccess', _27 => _27.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
796
+ if (_optionalChain([entry, 'optionalAccess', _32 => _32.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
785
797
  }
786
798
  for (const list of Object.values(_nullishCoalesce(config.fallbacks, () => ( {})))) {
787
799
  for (const entry of _nullishCoalesce(list, () => ( []))) {
788
- if (_optionalChain([entry, 'optionalAccess', _28 => _28.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
800
+ if (_optionalChain([entry, 'optionalAccess', _33 => _33.modelId]) === modelId) return _nullishCoalesce(entry.provider, () => ( "go"));
789
801
  }
790
802
  }
791
803
  return "go";
@@ -885,7 +897,7 @@ function hasBackgroundPattern(loweredSlices) {
885
897
  return containsAny(loweredSlices, BACKGROUND_KEYWORDS);
886
898
  }
887
899
  function resolveOpenCodeGoScenario(summary, config) {
888
- const longContextThreshold = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _29 => _29.modelMap, 'optionalAccess', _30 => _30.long_context, 'optionalAccess', _31 => _31.contextThreshold]), () => ( DEFAULT_OPENCODEGO_LONG_CONTEXT_THRESHOLD));
900
+ const longContextThreshold = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _34 => _34.modelMap, 'optionalAccess', _35 => _35.long_context, 'optionalAccess', _36 => _36.contextThreshold]), () => ( DEFAULT_OPENCODEGO_LONG_CONTEXT_THRESHOLD));
889
901
  if (summary.estimatedInputTokens >= longContextThreshold) {
890
902
  return "long_context";
891
903
  }
@@ -1015,7 +1027,7 @@ var SubscriptionProviderRegistry = (_class9 = class {
1015
1027
  resolveUpstreamUrl: (model, config) => {
1016
1028
  const oc = config;
1017
1029
  const { half, shape } = resolveOpenCodeGoTarget(model, oc);
1018
- const override = half === "zen" ? _optionalChain([oc, 'optionalAccess', _32 => _32.zenBaseUrl]) : _optionalChain([oc, 'optionalAccess', _33 => _33.baseUrl]);
1030
+ const override = half === "zen" ? _optionalChain([oc, 'optionalAccess', _37 => _37.zenBaseUrl]) : _optionalChain([oc, 'optionalAccess', _38 => _38.baseUrl]);
1019
1031
  return buildOpenCodeGoUrl(half, shape, override);
1020
1032
  },
1021
1033
  // zen seam (Decision 3): vary the provider transformer chain by resolved
@@ -1033,7 +1045,7 @@ var SubscriptionProviderRegistry = (_class9 = class {
1033
1045
  modelTransformerNames: [],
1034
1046
  modelMapper: (sdkModel, summary, config) => {
1035
1047
  const scenario = resolveOpenCodeGoScenario(summary, config);
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));
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));
1037
1049
  if (!entry) {
1038
1050
  return { resolvedModel: sdkModel, scenario };
1039
1051
  }
@@ -1052,7 +1064,7 @@ var SubscriptionProviderRegistry = (_class9 = class {
1052
1064
  // wedge permanently in half-open). When NO circuit is open this returns
1053
1065
  // the same first non-attempted entry as the prior `!attempted` filter.
1054
1066
  nextFallback: (scenario, attempted, config) => {
1055
- const list = _nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess', _38 => _38.fallbacks, 'optionalAccess', _39 => _39[scenario]]), () => ( DEFAULT_OPENCODEGO_FALLBACKS[scenario])), () => ( []));
1067
+ const list = _nullishCoalesce(_nullishCoalesce(_optionalChain([config, 'optionalAccess', _43 => _43.fallbacks, 'optionalAccess', _44 => _44[scenario]]), () => ( DEFAULT_OPENCODEGO_FALLBACKS[scenario])), () => ( []));
1056
1068
  for (const entry of list) {
1057
1069
  if (attempted.includes(entry.modelId)) continue;
1058
1070
  if (this.breaker.allowRequest(entry.modelId)) return entry;
@@ -1153,7 +1165,7 @@ var SubscriptionDispatcher = class {
1153
1165
  scenario = mapped.scenario;
1154
1166
  req.anthropicBody.model = resolvedModel;
1155
1167
  }
1156
- const upstreamUrl = _optionalChain([this, 'access', _40 => _40.profile, 'access', _41 => _41.resolveUpstreamUrl, 'optionalCall', _42 => _42(resolvedModel, ocConfig)]);
1168
+ const upstreamUrl = _optionalChain([this, 'access', _45 => _45.profile, 'access', _46 => _46.resolveUpstreamUrl, 'optionalCall', _47 => _47(resolvedModel, ocConfig)]);
1157
1169
  if (!upstreamUrl) {
1158
1170
  throw new Error(`[SubscriptionDispatcher] profile=${this.profile.providerId} missing resolveUpstreamUrl`);
1159
1171
  }
@@ -1185,7 +1197,7 @@ var SubscriptionDispatcher = class {
1185
1197
  );
1186
1198
  try {
1187
1199
  const upstream = await this.hooks.fetchWithRetry(upstreamUrl, headers, req.anthropicBody, currentModel);
1188
- _optionalChain([this, 'access', _43 => _43.profile, 'access', _44 => _44.recordModelOutcome, 'optionalCall', _45 => _45(currentModel, true)]);
1200
+ _optionalChain([this, 'access', _48 => _48.profile, 'access', _49 => _49.recordModelOutcome, 'optionalCall', _50 => _50(currentModel, true)]);
1189
1201
  this.markHealth(usedAccountId, 200);
1190
1202
  await this.hooks.writeProxyResponse(req.res, upstream, req.isStream, req.reqId);
1191
1203
  return;
@@ -1194,7 +1206,7 @@ var SubscriptionDispatcher = class {
1194
1206
  if (handled.retryOnce) {
1195
1207
  try {
1196
1208
  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)]);
1209
+ _optionalChain([this, 'access', _51 => _51.profile, 'access', _52 => _52.recordModelOutcome, 'optionalCall', _53 => _53(currentModel, true)]);
1198
1210
  this.markHealth(usedAccountId, 200);
1199
1211
  await this.hooks.writeProxyResponse(req.res, upstream, req.isStream, req.reqId);
1200
1212
  return;
@@ -1204,10 +1216,10 @@ var SubscriptionDispatcher = class {
1204
1216
  }
1205
1217
  }
1206
1218
  if (caughtErrorBreakerOutcome(err) === "failure") {
1207
- _optionalChain([this, 'access', _49 => _49.profile, 'access', _50 => _50.recordModelOutcome, 'optionalCall', _51 => _51(currentModel, false)]);
1219
+ _optionalChain([this, 'access', _54 => _54.profile, 'access', _55 => _55.recordModelOutcome, 'optionalCall', _56 => _56(currentModel, false)]);
1208
1220
  }
1209
1221
  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)]);
1222
+ const next = _optionalChain([this, 'access', _57 => _57.profile, 'access', _58 => _58.nextFallback, 'optionalCall', _59 => _59(scenario, attempted, ocConfig)]);
1211
1223
  if (!next || attempted.length >= MAX_FALLBACK_ATTEMPTS_LOCAL) {
1212
1224
  throw err;
1213
1225
  }
@@ -1221,7 +1233,7 @@ var SubscriptionDispatcher = class {
1221
1233
  }
1222
1234
  /** Standard subscription transformer chain — Codex/Gemini/OpenCodeGo OpenAI-shape. */
1223
1235
  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));
1236
+ const providerNames = _nullishCoalesce(_optionalChain([this, 'access', _60 => _60.profile, 'access', _61 => _61.resolveProviderTransformerNames, 'optionalCall', _62 => _62(resolvedModel, ocConfig)]), () => ( this.profile.providerTransformerNames));
1225
1237
  const providerTransformers = this.resolveTransformers(providerNames);
1226
1238
  const modelTransformers = this.resolveTransformers(this.profile.modelTransformerNames);
1227
1239
  const transformerProvider = {
@@ -1262,7 +1274,7 @@ var SubscriptionDispatcher = class {
1262
1274
  );
1263
1275
  try {
1264
1276
  const upstream = await this.hooks.fetchWithRetry(fetchUrl, headers, requestBody, currentModel);
1265
- _optionalChain([this, 'access', _58 => _58.profile, 'access', _59 => _59.recordModelOutcome, 'optionalCall', _60 => _60(currentModel, true)]);
1277
+ _optionalChain([this, 'access', _63 => _63.profile, 'access', _64 => _64.recordModelOutcome, 'optionalCall', _65 => _65(currentModel, true)]);
1266
1278
  this.markHealth(usedAccountId, 200);
1267
1279
  const finalResponse = await this.hooks.executor.executeResponseChain(
1268
1280
  requestBody,
@@ -1278,7 +1290,7 @@ var SubscriptionDispatcher = class {
1278
1290
  if (handled.retryOnce) {
1279
1291
  try {
1280
1292
  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)]);
1293
+ _optionalChain([this, 'access', _66 => _66.profile, 'access', _67 => _67.recordModelOutcome, 'optionalCall', _68 => _68(currentModel, true)]);
1282
1294
  this.markHealth(usedAccountId, 200);
1283
1295
  const finalResponse = await this.hooks.executor.executeResponseChain(
1284
1296
  requestBody,
@@ -1295,10 +1307,10 @@ var SubscriptionDispatcher = class {
1295
1307
  }
1296
1308
  }
1297
1309
  if (caughtErrorBreakerOutcome(err) === "failure") {
1298
- _optionalChain([this, 'access', _64 => _64.profile, 'access', _65 => _65.recordModelOutcome, 'optionalCall', _66 => _66(currentModel, false)]);
1310
+ _optionalChain([this, 'access', _69 => _69.profile, 'access', _70 => _70.recordModelOutcome, 'optionalCall', _71 => _71(currentModel, false)]);
1299
1311
  }
1300
1312
  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)]);
1313
+ const next = _optionalChain([this, 'access', _72 => _72.profile, 'access', _73 => _73.nextFallback, 'optionalCall', _74 => _74(scenario, attempted, ocConfig)]);
1302
1314
  if (!next || attempted.length >= MAX_FALLBACK_ATTEMPTS_LOCAL) {
1303
1315
  throw err;
1304
1316
  }
@@ -1328,7 +1340,7 @@ var SubscriptionDispatcher = class {
1328
1340
  return { firstModel: primaryModel, attempted: [] };
1329
1341
  }
1330
1342
  const skipped = [primaryModel];
1331
- const firstAdmitting = _optionalChain([this, 'access', _70 => _70.profile, 'access', _71 => _71.nextFallback, 'optionalCall', _72 => _72(scenario, skipped, ocConfig)]);
1343
+ const firstAdmitting = _optionalChain([this, 'access', _75 => _75.profile, 'access', _76 => _76.nextFallback, 'optionalCall', _77 => _77(scenario, skipped, ocConfig)]);
1332
1344
  if (firstAdmitting) {
1333
1345
  console.warn(
1334
1346
  `[AgentProxy:subscription] opencodego primary ${primaryModel} circuit open -> first admitting fallback ${firstAdmitting.modelId}`
@@ -1346,7 +1358,7 @@ var SubscriptionDispatcher = class {
1346
1358
  * successfully (caller should retry once); otherwise re-throws.
1347
1359
  */
1348
1360
  async maybeRetryAfterError(err, headers, req, resolvedModel, sessionKey) {
1349
- const status = _nullishCoalesce(_optionalChain([err, 'optionalAccess', _73 => _73.status]), () => ( 0));
1361
+ const status = _nullishCoalesce(_optionalChain([err, 'optionalAccess', _78 => _78.status]), () => ( 0));
1350
1362
  if (status !== 401) {
1351
1363
  return { retryOnce: false, headers };
1352
1364
  }
@@ -1468,11 +1480,11 @@ var SubscriptionDispatcher = class {
1468
1480
  };
1469
1481
  var MAX_FALLBACK_ATTEMPTS_LOCAL = 3;
1470
1482
  function errStatus(err) {
1471
- const status = _optionalChain([err, 'optionalAccess', _74 => _74.status]);
1483
+ const status = _optionalChain([err, 'optionalAccess', _79 => _79.status]);
1472
1484
  return typeof status === "number" ? status : null;
1473
1485
  }
1474
1486
  function errHeaders(err) {
1475
- const h = _optionalChain([err, 'optionalAccess', _75 => _75.headers]);
1487
+ const h = _optionalChain([err, 'optionalAccess', _80 => _80.headers]);
1476
1488
  if (!h) return void 0;
1477
1489
  if (typeof h.get === "function") return h;
1478
1490
  if (typeof h === "object") return h;
@@ -1480,11 +1492,11 @@ function errHeaders(err) {
1480
1492
  }
1481
1493
  function errBodyText(err) {
1482
1494
  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)]);
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)]);
1485
1497
  }
1486
1498
  function caughtErrorBreakerOutcome(err) {
1487
- const status = _optionalChain([err, 'optionalAccess', _80 => _80.status]);
1499
+ const status = _optionalChain([err, 'optionalAccess', _85 => _85.status]);
1488
1500
  if (typeof status !== "number") return "failure";
1489
1501
  if (status === 0) return "neutral";
1490
1502
  if (status >= 500 || status === 429) return "failure";
package/dist/index.js CHANGED
@@ -194,6 +194,18 @@ async function resolveSelectedToken(selector, tokens, providerId, sessionKey, ac
194
194
  const gated = gateSchedulable(accounts, providerId, health, now, resolvedModel, supportedModelsById);
195
195
  const poolGated = isPoolGated(accounts, health, resolvedModel);
196
196
  const remapFor = (id) => remapReportForAccount(supportedModelsById.get(id), resolvedModel);
197
+ const preferredId = ctx?.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
+ report?.(preferredId, preferredId === activeAccountId, remapFor(preferredId));
205
+ return preferredToken;
206
+ }
207
+ }
208
+ }
197
209
  const targetId = pickByIdTarget(selector, gated, providerId, activeAccountId, sessionKey, now, poolGated);
198
210
  if (targetId !== void 0) {
199
211
  const byId = await tokens.getAccessTokenForAccount(providerId, targetId);
@@ -266,7 +278,7 @@ var OAuthBearerAuthStrategy = class {
266
278
  this.providerId,
267
279
  hints?.sessionKey,
268
280
  () => this.resolveAccessToken(),
269
- { health: this.health, reportSelection: hints?.reportSelection, resolvedModel: hints?.resolvedModel }
281
+ { health: this.health, reportSelection: hints?.reportSelection, resolvedModel: hints?.resolvedModel, preferredAccountId: hints?.preferredAccountId }
270
282
  );
271
283
  if (!token) {
272
284
  return;
@@ -343,7 +355,7 @@ var PassThroughAuthStrategy = class {
343
355
  "claude",
344
356
  hints?.sessionKey,
345
357
  () => this.tokens.getValidClaudeAccessToken(),
346
- { health: this.health, reportSelection: hints?.reportSelection, resolvedModel: hints?.resolvedModel }
358
+ { health: this.health, reportSelection: hints?.reportSelection, resolvedModel: hints?.resolvedModel, preferredAccountId: hints?.preferredAccountId }
347
359
  );
348
360
  if (!token) return;
349
361
  headers["Authorization"] = `Bearer ${token}`;
@@ -421,7 +433,7 @@ var StaticBearerAuthStrategy = class {
421
433
  "opencodego",
422
434
  hints?.sessionKey,
423
435
  () => this.tokens.getValidOpenCodeGoApiKey(),
424
- { health: this.health, reportSelection: hints?.reportSelection, resolvedModel: hints?.resolvedModel }
436
+ { health: this.health, reportSelection: hints?.reportSelection, resolvedModel: hints?.resolvedModel, preferredAccountId: hints?.preferredAccountId }
425
437
  );
426
438
  if (!key) {
427
439
  return;
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "@omnicross/subscriptions",
3
- "version": "0.1.3",
4
- "description": "Omnicross subscription-as-provider auth strategies, OAuth flows, and the OpenCodeGo scenario dispatcher.",
5
- "license": "MIT",
6
- "author": "Sayo (https://github.com/Dumoedss)",
7
- "homepage": "https://github.com/Dumoedss/omnicross#readme",
8
- "keywords": [
9
- "omnicross",
10
- "llm",
11
- "oauth",
12
- "subscription",
13
- "claude",
14
- "codex",
15
- "gemini"
16
- ],
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/Dumoedss/omnicross.git",
20
- "directory": "packages/subscriptions"
21
- },
22
- "publishConfig": {
23
- "access": "public"
24
- },
25
- "engines": {
26
- "node": ">=20"
27
- },
28
- "type": "module",
29
- "main": "dist/index.cjs",
30
- "module": "dist/index.js",
31
- "types": "dist/index.d.ts",
32
- "exports": {
33
- ".": {
34
- "types": "./dist/index.d.ts",
35
- "import": "./dist/index.js",
36
- "require": "./dist/index.cjs"
37
- },
38
- "./*": {
39
- "types": "./dist/*.d.ts",
40
- "import": "./dist/*.js",
41
- "require": "./dist/*.cjs"
42
- }
43
- },
44
- "files": [
45
- "dist",
46
- "LICENSE",
47
- "NOTICE",
48
- "README.md"
49
- ],
50
- "scripts": {
51
- "build": "tsup",
52
- "typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
53
- },
54
- "dependencies": {
55
- "@omnicross/contracts": "^0.1.0",
56
- "@omnicross/core": "^0.1.0",
57
- "js-tiktoken": "^1.0.21"
58
- }
59
- }
1
+ {
2
+ "name": "@omnicross/subscriptions",
3
+ "version": "0.1.4",
4
+ "description": "Omnicross subscription-as-provider auth strategies, OAuth flows, and the OpenCodeGo scenario dispatcher.",
5
+ "license": "MIT",
6
+ "author": "Sayo (https://github.com/Dumoedss)",
7
+ "homepage": "https://github.com/Dumoedss/omnicross#readme",
8
+ "keywords": [
9
+ "omnicross",
10
+ "llm",
11
+ "oauth",
12
+ "subscription",
13
+ "claude",
14
+ "codex",
15
+ "gemini"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/Dumoedss/omnicross.git",
20
+ "directory": "packages/subscriptions"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "type": "module",
29
+ "main": "dist/index.cjs",
30
+ "module": "dist/index.js",
31
+ "types": "dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js",
36
+ "require": "./dist/index.cjs"
37
+ },
38
+ "./*": {
39
+ "types": "./dist/*.d.ts",
40
+ "import": "./dist/*.js",
41
+ "require": "./dist/*.cjs"
42
+ }
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "LICENSE",
47
+ "NOTICE",
48
+ "README.md"
49
+ ],
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
53
+ },
54
+ "dependencies": {
55
+ "@omnicross/contracts": "^0.1.0",
56
+ "@omnicross/core": "^0.1.0",
57
+ "js-tiktoken": "^1.0.21"
58
+ }
59
+ }