agent-shim 8.34.0 → 8.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -17252,7 +17252,7 @@ var require_readable = __commonJS({
17252
17252
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/api/readable.js"(exports2, module2) {
17253
17253
  "use strict";
17254
17254
  var assert2 = require("node:assert");
17255
- var { Readable: Readable2 } = require("node:stream");
17255
+ var { Readable: Readable3 } = require("node:stream");
17256
17256
  var { RequestAbortedError, NotSupportedError, InvalidArgumentError: InvalidArgumentError2, AbortError: AbortError2 } = require_errors();
17257
17257
  var util = require_util2();
17258
17258
  var { ReadableStreamFrom } = require_util2();
@@ -17266,7 +17266,7 @@ var require_readable = __commonJS({
17266
17266
  var kBytesRead = /* @__PURE__ */ Symbol("kBytesRead");
17267
17267
  var noop = () => {
17268
17268
  };
17269
- var BodyReadable = class extends Readable2 {
17269
+ var BodyReadable = class extends Readable3 {
17270
17270
  /**
17271
17271
  * @param {object} opts
17272
17272
  * @param {(this: Readable, size: number) => void} opts.resume
@@ -17655,7 +17655,7 @@ var require_api_request = __commonJS({
17655
17655
  "use strict";
17656
17656
  var assert2 = require("node:assert");
17657
17657
  var { AsyncResource } = require("node:async_hooks");
17658
- var { Readable: Readable2 } = require_readable();
17658
+ var { Readable: Readable3 } = require_readable();
17659
17659
  var { InvalidArgumentError: InvalidArgumentError2, RequestAbortedError } = require_errors();
17660
17660
  var util = require_util2();
17661
17661
  function noop() {
@@ -17736,7 +17736,7 @@ var require_api_request = __commonJS({
17736
17736
  const parsedHeaders = responseHeaders3 === "raw" ? util.parseHeaders(rawHeaders) : headers;
17737
17737
  const contentType = parsedHeaders["content-type"];
17738
17738
  const contentLength = parsedHeaders["content-length"];
17739
- const res = new Readable2({
17739
+ const res = new Readable3({
17740
17740
  resume,
17741
17741
  abort,
17742
17742
  contentType,
@@ -18045,9 +18045,9 @@ var require_api_pipeline = __commonJS({
18045
18045
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/api/api-pipeline.js"(exports2, module2) {
18046
18046
  "use strict";
18047
18047
  var {
18048
- Readable: Readable2,
18048
+ Readable: Readable3,
18049
18049
  Duplex,
18050
- PassThrough
18050
+ PassThrough: PassThrough2
18051
18051
  } = require("node:stream");
18052
18052
  var assert2 = require("node:assert");
18053
18053
  var { AsyncResource } = require("node:async_hooks");
@@ -18061,7 +18061,7 @@ var require_api_pipeline = __commonJS({
18061
18061
  function noop() {
18062
18062
  }
18063
18063
  var kResume = /* @__PURE__ */ Symbol("resume");
18064
- var PipelineRequest = class extends Readable2 {
18064
+ var PipelineRequest = class extends Readable3 {
18065
18065
  constructor() {
18066
18066
  super({ autoDestroy: true });
18067
18067
  this[kResume] = null;
@@ -18078,7 +18078,7 @@ var require_api_pipeline = __commonJS({
18078
18078
  callback(err);
18079
18079
  }
18080
18080
  };
18081
- var PipelineResponse = class extends Readable2 {
18081
+ var PipelineResponse = class extends Readable3 {
18082
18082
  constructor(resume) {
18083
18083
  super({ autoDestroy: true });
18084
18084
  this[kResume] = resume;
@@ -18234,7 +18234,7 @@ var require_api_pipeline = __commonJS({
18234
18234
  this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler);
18235
18235
  return pipelineHandler.ret;
18236
18236
  } catch (err) {
18237
- return new PassThrough().destroy(err);
18237
+ return new PassThrough2().destroy(err);
18238
18238
  }
18239
18239
  }
18240
18240
  module2.exports = pipeline;
@@ -23060,7 +23060,7 @@ var require_cache2 = __commonJS({
23060
23060
  "node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/interceptor/cache.js"(exports2, module2) {
23061
23061
  "use strict";
23062
23062
  var assert2 = require("node:assert");
23063
- var { Readable: Readable2 } = require("node:stream");
23063
+ var { Readable: Readable3 } = require("node:stream");
23064
23064
  var util = require_util2();
23065
23065
  var CacheHandler = require_cache_handler();
23066
23066
  var MemoryCacheStore = require_memory_cache_store();
@@ -23229,7 +23229,7 @@ var require_cache2 = __commonJS({
23229
23229
  return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler));
23230
23230
  }
23231
23231
  function sendCachedValue(handler, opts, result, age, context, isStale2) {
23232
- const stream = util.isStream(result.body) ? result.body : Readable2.from(result.body ?? []);
23232
+ const stream = util.isStream(result.body) ? result.body : Readable3.from(result.body ?? []);
23233
23233
  assert2(!stream.destroyed, "stream should not be destroyed");
23234
23234
  assert2(!stream.readableDidRead, "stream should not be readableDidRead");
23235
23235
  const controller = {
@@ -26437,7 +26437,7 @@ var require_fetch = __commonJS({
26437
26437
  subresourceSet
26438
26438
  } = require_constants4();
26439
26439
  var EE = require("node:events");
26440
- var { Readable: Readable2, pipeline, finished, isErrored, isReadable } = require("node:stream");
26440
+ var { Readable: Readable3, pipeline, finished, isErrored, isReadable } = require("node:stream");
26441
26441
  var { addAbortListener, bufferToLowerCasedHeaderName } = require_util2();
26442
26442
  var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url();
26443
26443
  var { getGlobalDispatcher } = require_global2();
@@ -27380,7 +27380,7 @@ var require_fetch = __commonJS({
27380
27380
  }
27381
27381
  }
27382
27382
  const location = headersList.get("location", true);
27383
- this.body = new Readable2({ read: resume });
27383
+ this.body = new Readable3({ read: resume });
27384
27384
  const willFollow = location && request7.redirect === "follow" && redirectStatusSet.has(status);
27385
27385
  const decoders = [];
27386
27386
  if (request7.method !== "HEAD" && request7.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) {
@@ -57101,13 +57101,23 @@ var CodexProviderConfigSchema = external_exports.strictObject({
57101
57101
  /** Which login the upstream call is made with; see `CODEX_LOGINS`. */
57102
57102
  login: external_exports.enum(CODEX_LOGINS).optional()
57103
57103
  });
57104
+ var ProviderRouteSchema = external_exports.strictObject({
57105
+ /** The condition, the same `when` union a configuration cascade entry takes: the object form or a trilean predicate tree over the request facts the door resolves (`request.model` and kin). */
57106
+ when: WhenSchema,
57107
+ /** The provider a matching request is sent to, by the name a provider file carries. */
57108
+ provider: external_exports.string().min(1),
57109
+ /** The model name to rewrite the request to before it reaches the target, for a provider that expects another form (OpenRouter's `anthropic/claude-...` for instance). A route that rewrites reads the whole body, the one copy the rewrite needs. */
57110
+ model: external_exports.string().min(1).optional()
57111
+ });
57104
57112
  var HttpProviderSchema = external_exports.strictObject({
57105
57113
  $schema: external_exports.string().optional(),
57106
57114
  kind: external_exports.literal("http").optional(),
57107
57115
  displayName: external_exports.string().min(1),
57108
57116
  baseUrl: external_exports.url(),
57109
57117
  credential: ProviderCredentialSchema,
57110
- env: ProviderEnvSchema.optional()
57118
+ env: ProviderEnvSchema.optional(),
57119
+ /** Per-request routing: when present, each request this provider would serve is matched against the list in order, and the first definite match is sent to that entry's provider instead (itself resolved exactly as a directly dialled request would be). Unmatched requests stay on this provider. */
57120
+ routes: external_exports.array(ProviderRouteSchema).optional()
57111
57121
  });
57112
57122
  var CodexProviderSchema = external_exports.strictObject({
57113
57123
  $schema: external_exports.string().optional(),
@@ -60930,7 +60940,17 @@ function resolversOf(context) {
60930
60940
  ...pool?.sevenDay === void 0 ? {} : { "quota.sevenDay.remaining": pool.sevenDay.remaining, "quota.sevenDay.utilization": pool.sevenDay.utilization, "quota.sevenDay.hoursUntilReset": pool.sevenDay.hoursUntilReset },
60931
60941
  ...pool?.burnPerHour === void 0 ? {} : { "quota.burnPerHour": pool.burnPerHour },
60932
60942
  ...pool?.resuming === void 0 ? {} : { "session.resuming": pool.resuming },
60933
- ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo }
60943
+ ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo },
60944
+ ...context.request?.model === void 0 ? {} : { "request.model": context.request.model },
60945
+ ...context.request?.hasImage === void 0 ? {} : { "request.hasImage": context.request.hasImage },
60946
+ ...context.providerQuota === void 0 ? {} : Object.fromEntries(
60947
+ Object.entries(context.providerQuota).flatMap(
60948
+ ([name, windows]) => [
60949
+ ...windows.fiveHour === void 0 ? [] : [["provider." + name + ".fiveHour.remaining", windows.fiveHour.remaining], ["provider." + name + ".fiveHour.utilization", windows.fiveHour.utilization], ["provider." + name + ".fiveHour.hoursUntilReset", windows.fiveHour.hoursUntilReset]],
60950
+ ...windows.sevenDay === void 0 ? [] : [["provider." + name + ".sevenDay.remaining", windows.sevenDay.remaining], ["provider." + name + ".sevenDay.utilization", windows.sevenDay.utilization], ["provider." + name + ".sevenDay.hoursUntilReset", windows.sevenDay.hoursUntilReset]]
60951
+ ]
60952
+ )
60953
+ )
60934
60954
  };
60935
60955
  return {
60936
60956
  resolveValue: (key) => {
@@ -60996,6 +61016,24 @@ function evaluateWhen(when, context) {
60996
61016
  }
60997
61017
  return { status: "definite", passed: failed.length === 0, checked, failed };
60998
61018
  }
61019
+ function referencesFact(when, key) {
61020
+ if (when === void 0 || !("kind" in when)) {
61021
+ return false;
61022
+ }
61023
+ const walk = (node2) => {
61024
+ if (Array.isArray(node2)) {
61025
+ return node2.some(walk);
61026
+ }
61027
+ if (typeof node2 !== "object" || node2 === null) {
61028
+ return false;
61029
+ }
61030
+ if ("kind" in node2 && node2.kind === "reference" && "key" in node2 && node2.key === key) {
61031
+ return true;
61032
+ }
61033
+ return Object.values(node2).some(walk);
61034
+ };
61035
+ return walk(when);
61036
+ }
60999
61037
  function isVacuousWhen(when) {
61000
61038
  return when !== void 0 && !("kind" in when) && Object.keys(when).length === 0;
61001
61039
  }
@@ -63665,7 +63703,7 @@ function registerIdentityCommand(program2, deps) {
63665
63703
  // package.json
63666
63704
  var package_default = {
63667
63705
  name: "agent-shim",
63668
- version: "8.34.0",
63706
+ version: "8.36.0",
63669
63707
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
63670
63708
  license: "MIT",
63671
63709
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -77383,6 +77421,78 @@ function createPassthroughRoute(name, target) {
77383
77421
  }
77384
77422
 
77385
77423
  // src/frontdoor/providerRoute.ts
77424
+ var import_node_stream3 = require("node:stream");
77425
+
77426
+ // src/frontdoor/requestScan.ts
77427
+ var import_node_stream2 = require("node:stream");
77428
+ var BYTES_PER_KIB = 1024;
77429
+ var SCAN_KIB = 64;
77430
+ var REQUEST_SCAN_CAP_BYTES = SCAN_KIB * BYTES_PER_KIB;
77431
+ async function scanRequestHead(request7, wholeBody = false) {
77432
+ const body = request7.body;
77433
+ const head = [];
77434
+ let seen = 0;
77435
+ let text2 = "";
77436
+ let decided = false;
77437
+ let ended = false;
77438
+ let error62;
77439
+ const replay = new import_node_stream2.PassThrough();
77440
+ const modelIn = () => /"model"\s*:\s*"([^"]+)"/.exec(text2)?.[1];
77441
+ const settled = new Promise((resolve) => {
77442
+ const settle2 = () => {
77443
+ const model = modelIn();
77444
+ const hasImage = /"type"\s*:\s*"image"/.test(text2);
77445
+ for (const chunk of head) {
77446
+ replay.write(chunk);
77447
+ }
77448
+ resolve({ ...model === void 0 ? {} : { model }, hasImage, ...wholeBody ? { whole: true, text: text2 } : {}, replayed: { ...request7, body: replay } });
77449
+ };
77450
+ body.on("data", (chunk) => {
77451
+ if (decided) {
77452
+ if (!replay.write(chunk)) {
77453
+ body.pause();
77454
+ replay.once("drain", () => {
77455
+ body.resume();
77456
+ });
77457
+ }
77458
+ return;
77459
+ }
77460
+ head.push(chunk);
77461
+ seen += chunk.length;
77462
+ text2 += chunk.toString("utf8");
77463
+ if (!wholeBody && (modelIn() !== void 0 || seen >= REQUEST_SCAN_CAP_BYTES)) {
77464
+ decided = true;
77465
+ body.pause();
77466
+ replay.once("resume", () => {
77467
+ body.resume();
77468
+ });
77469
+ settle2();
77470
+ }
77471
+ });
77472
+ body.on("end", () => {
77473
+ ended = true;
77474
+ if (!decided) {
77475
+ decided = true;
77476
+ settle2();
77477
+ }
77478
+ replay.end();
77479
+ });
77480
+ body.on("error", (cause) => {
77481
+ error62 = cause;
77482
+ replay.destroy(cause);
77483
+ });
77484
+ body.on("close", () => {
77485
+ if (!decided && !ended && error62 === void 0) {
77486
+ decided = true;
77487
+ settle2();
77488
+ }
77489
+ });
77490
+ });
77491
+ return await settled;
77492
+ }
77493
+
77494
+ // src/frontdoor/providerRoute.ts
77495
+ var MS_PER_HOUR3 = 36e5;
77386
77496
  function createProviderRouteResolver(deps) {
77387
77497
  const isRcSelfHostPath = (path46) => path46 === "/v1/code/sessions" || path46.startsWith("/v1/code/sessions/") || path46 === "/v1/sessions" || path46.startsWith("/v1/sessions/");
77388
77498
  const oauthRoute = createPassthroughRoute("anthropic", { baseUrl: `https://${CONNECT_INTERCEPT_HOST}:${String(HTTPS_PORT)}`, stripPrefix: void 0, headroomUpstream: void 0 });
@@ -77446,7 +77556,83 @@ function createProviderRouteResolver(deps) {
77446
77556
  )
77447
77557
  };
77448
77558
  };
77449
- return async (request7) => await Promise.resolve(resolve(request7));
77559
+ const servingRewritten = (route, rewritten) => ({
77560
+ name: route.name,
77561
+ headroomEligible: route.headroomEligible,
77562
+ headroomUpstream: route.headroomUpstream,
77563
+ // The pipeline serves the route with the request it received; the routing layer's decisions (the rewritten target path, the replayed body and, when the model name was rewritten, the re-framed headers) are substituted here, so the route sees exactly the request the resolution acted on.
77564
+ serve: async (request7, response) => {
77565
+ await route.serve({ ...request7, url: rewritten.url, headers: rewritten.headers, body: rewritten.body }, response);
77566
+ }
77567
+ });
77568
+ return async (request7) => {
77569
+ let current = request7;
77570
+ let replay;
77571
+ const visited = /* @__PURE__ */ new Set();
77572
+ for (; ; ) {
77573
+ const path46 = new URL(current.url, "http://127.0.0.1").pathname;
77574
+ const scoped = parseProviderPath(path46);
77575
+ const providerName = scoped?.provider ?? current.session.provider;
77576
+ if (providerName !== void 0 && visited.has(providerName)) {
77577
+ return { ok: false, status: HTTP_STATUS.internalServerError, message: `provider routing cycle: ${[...visited, providerName].join(" -> ")}` };
77578
+ }
77579
+ if (providerName !== void 0) {
77580
+ visited.add(providerName);
77581
+ }
77582
+ let routes;
77583
+ try {
77584
+ const definition = providerName === void 0 ? void 0 : loadProvider(deps.providersDir, providerName, deps.fs);
77585
+ routes = definition === void 0 || isCodexProvider(definition) ? void 0 : definition.routes;
77586
+ } catch {
77587
+ routes = void 0;
77588
+ }
77589
+ if (routes === void 0 || routes.length === 0) {
77590
+ const resolution = resolve(current);
77591
+ return resolution.ok && replay !== void 0 ? { ok: true, route: servingRewritten(resolution.route, { ...replay, url: current.url }) } : resolution;
77592
+ }
77593
+ const whole = routes.some((entry) => entry.model !== void 0 || referencesFact(entry.when, "request.hasImage"));
77594
+ const snapshot = deps.usageSnapshotOf?.(current.session.identity);
77595
+ const windowFacts = (raw) => {
77596
+ if (raw?.utilization === void 0 || raw.resetsAt === void 0) {
77597
+ return void 0;
77598
+ }
77599
+ return { remaining: Math.max(0, 1 - raw.utilization), utilization: raw.utilization, hoursUntilReset: Math.max(0, (Date.parse(raw.resetsAt) - Date.now()) / MS_PER_HOUR3) };
77600
+ };
77601
+ const providerQuota = Object.fromEntries(
77602
+ routes.flatMap((entry) => {
77603
+ const unified = snapshot?.providers[entry.provider]?.rateLimit?.unified;
77604
+ return [[entry.provider, { fiveHour: windowFacts(unified?.fiveHour), sevenDay: windowFacts(unified?.sevenDay) }]];
77605
+ })
77606
+ );
77607
+ const scan = await scanRequestHead(current, whole);
77608
+ if (scan.replayed === void 0) {
77609
+ const resolution = resolve(current);
77610
+ return resolution.ok && replay !== void 0 ? { ok: true, route: servingRewritten(resolution.route, { ...replay, url: current.url }) } : resolution;
77611
+ }
77612
+ replay = scan.replayed;
77613
+ const context = { nowMs: Date.now(), env: deps.env, providerQuota, ...scan.model === void 0 && scan.hasImage === void 0 ? {} : { request: { ...scan.model === void 0 ? {} : { model: scan.model }, hasImage: scan.hasImage } } };
77614
+ let matched;
77615
+ for (const entry of routes) {
77616
+ const verdict = evaluateWhen(entry.when, context);
77617
+ if (verdict.status === "definite" && verdict.passed) {
77618
+ matched = { provider: entry.provider, model: entry.model };
77619
+ break;
77620
+ }
77621
+ }
77622
+ if (matched === void 0) {
77623
+ const resolution = resolve(current);
77624
+ return resolution.ok ? { ok: true, route: servingRewritten(resolution.route, { ...replay, url: current.url }) } : resolution;
77625
+ }
77626
+ if (matched.model !== void 0 && scan.text !== void 0 && scan.model !== void 0) {
77627
+ const rewritten = scan.text.replace(new RegExp('"model":\\s*"' + scan.model.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + '"'), '"model":"' + matched.model + '"');
77628
+ const headers = Object.fromEntries(Object.entries(replay.headers).filter(([name]) => name.toLowerCase() !== "content-length"));
77629
+ replay = { ...replay, headers, body: import_node_stream3.Readable.from([Buffer.from(rewritten, "utf8")]) };
77630
+ }
77631
+ const rest = scoped === void 0 ? path46 : path46.slice(`${PROVIDER_PATH_PREFIX}${encodeURIComponent(scoped.provider)}`.length);
77632
+ const query = current.url.includes("?") ? current.url.slice(current.url.indexOf("?")) : "";
77633
+ current = { ...current, url: `${PROVIDER_PATH_PREFIX}${encodeURIComponent(matched.provider)}${rest}${query}`, body: replay.body, headers: replay.headers };
77634
+ }
77635
+ };
77450
77636
  }
77451
77637
  function providerCredentialHeaders(credential) {
77452
77638
  return credential.target === "apiKey" ? { "x-api-key": credential.token } : { authorization: `Bearer ${credential.token}` };
@@ -80575,6 +80761,7 @@ function realFrontDoorSupervisorPorts(paths) {
80575
80761
  appendLog(paths, `frontdoor ${String(process.pid)}: ${line}`);
80576
80762
  }
80577
80763
  });
80764
+ const routingSnapshots = /* @__PURE__ */ new Map();
80578
80765
  const rcControlToken = (0, import_node_crypto15.randomUUID)();
80579
80766
  const resolveRoute = rcObservingResolver(
80580
80767
  createProviderRouteResolver({
@@ -80585,6 +80772,7 @@ function realFrontDoorSupervisorPorts(paths) {
80585
80772
  // The same port (cache included) the launcher resolves a launch's provider with, so the credential the door attaches at the route and the one the launcher handed the child are the same resolution of the same block.
80586
80773
  env: process.env,
80587
80774
  credentials: { ...realCredentialPort, cache: realCredentialCacheEnv(paths) },
80775
+ usageSnapshotOf: (identity) => identity === void 0 ? void 0 : routingSnapshots.get(identity) ?? readUsageSnapshot(realFarmFs, paths.usageSnapshotsDir, identity),
80588
80776
  ...rcSelfHostSurface === void 0 ? {} : { rcSelfHostRoute: rcSelfHostSurface.route }
80589
80777
  }),
80590
80778
  rcTracker,
@@ -80599,6 +80787,7 @@ function realFrontDoorSupervisorPorts(paths) {
80599
80787
  readAccount: createAccountReader(realFarmFs, paths.identitiesDir),
80600
80788
  log,
80601
80789
  publish: (snapshot) => {
80790
+ routingSnapshots.set(snapshot.identity, snapshot);
80602
80791
  usagePublisher.publish(snapshot);
80603
80792
  }
80604
80793
  });
@@ -82619,13 +82808,13 @@ function formatLimit(limit) {
82619
82808
  return `last limit: ${limit.kind} (${String(limit.status)}) at ${limit.observedAt}${limit.window === void 0 ? "" : ` on the ${limit.window} window`}${wait2.length === 0 ? "" : `, ${wait2.join(", ")}`}`;
82620
82809
  }
82621
82810
  var MONEY_DECIMALS = 2;
82622
- var MS_PER_HOUR3 = 36e5;
82811
+ var MS_PER_HOUR4 = 36e5;
82623
82812
  var HOURS_PER_DAY4 = 24;
82624
82813
  function formatPeriod(window) {
82625
82814
  if (window.periodMs === void 0) {
82626
82815
  return window.period ?? "unknown period";
82627
82816
  }
82628
- const hours = window.periodMs / MS_PER_HOUR3;
82817
+ const hours = window.periodMs / MS_PER_HOUR4;
82629
82818
  return hours >= HOURS_PER_DAY4 && Number.isInteger(hours / HOURS_PER_DAY4) ? `${String(hours / HOURS_PER_DAY4)}d` : `${String(hours)}h`;
82630
82819
  }
82631
82820
  function formatCount(value2) {
package/dist/index.cjs CHANGED
@@ -589,13 +589,23 @@ var CodexProviderConfigSchema = import_zod.z.strictObject({
589
589
  /** Which login the upstream call is made with; see `CODEX_LOGINS`. */
590
590
  login: import_zod.z.enum(CODEX_LOGINS).optional()
591
591
  });
592
+ var ProviderRouteSchema = import_zod.z.strictObject({
593
+ /** The condition, the same `when` union a configuration cascade entry takes: the object form or a trilean predicate tree over the request facts the door resolves (`request.model` and kin). */
594
+ when: WhenSchema,
595
+ /** The provider a matching request is sent to, by the name a provider file carries. */
596
+ provider: import_zod.z.string().min(1),
597
+ /** The model name to rewrite the request to before it reaches the target, for a provider that expects another form (OpenRouter's `anthropic/claude-...` for instance). A route that rewrites reads the whole body, the one copy the rewrite needs. */
598
+ model: import_zod.z.string().min(1).optional()
599
+ });
592
600
  var HttpProviderSchema = import_zod.z.strictObject({
593
601
  $schema: import_zod.z.string().optional(),
594
602
  kind: import_zod.z.literal("http").optional(),
595
603
  displayName: import_zod.z.string().min(1),
596
604
  baseUrl: import_zod.z.url(),
597
605
  credential: ProviderCredentialSchema,
598
- env: ProviderEnvSchema.optional()
606
+ env: ProviderEnvSchema.optional(),
607
+ /** Per-request routing: when present, each request this provider would serve is matched against the list in order, and the first definite match is sent to that entry's provider instead (itself resolved exactly as a directly dialled request would be). Unmatched requests stay on this provider. */
608
+ routes: import_zod.z.array(ProviderRouteSchema).optional()
599
609
  });
600
610
  var CodexProviderSchema = import_zod.z.strictObject({
601
611
  $schema: import_zod.z.string().optional(),
@@ -1018,7 +1028,17 @@ function resolversOf(context) {
1018
1028
  ...pool?.sevenDay === void 0 ? {} : { "quota.sevenDay.remaining": pool.sevenDay.remaining, "quota.sevenDay.utilization": pool.sevenDay.utilization, "quota.sevenDay.hoursUntilReset": pool.sevenDay.hoursUntilReset },
1019
1029
  ...pool?.burnPerHour === void 0 ? {} : { "quota.burnPerHour": pool.burnPerHour },
1020
1030
  ...pool?.resuming === void 0 ? {} : { "session.resuming": pool.resuming },
1021
- ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo }
1031
+ ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo },
1032
+ ...context.request?.model === void 0 ? {} : { "request.model": context.request.model },
1033
+ ...context.request?.hasImage === void 0 ? {} : { "request.hasImage": context.request.hasImage },
1034
+ ...context.providerQuota === void 0 ? {} : Object.fromEntries(
1035
+ Object.entries(context.providerQuota).flatMap(
1036
+ ([name, windows]) => [
1037
+ ...windows.fiveHour === void 0 ? [] : [["provider." + name + ".fiveHour.remaining", windows.fiveHour.remaining], ["provider." + name + ".fiveHour.utilization", windows.fiveHour.utilization], ["provider." + name + ".fiveHour.hoursUntilReset", windows.fiveHour.hoursUntilReset]],
1038
+ ...windows.sevenDay === void 0 ? [] : [["provider." + name + ".sevenDay.remaining", windows.sevenDay.remaining], ["provider." + name + ".sevenDay.utilization", windows.sevenDay.utilization], ["provider." + name + ".sevenDay.hoursUntilReset", windows.sevenDay.hoursUntilReset]]
1039
+ ]
1040
+ )
1041
+ )
1022
1042
  };
1023
1043
  return {
1024
1044
  resolveValue: (key) => {
@@ -5399,7 +5419,7 @@ var import_undici = require("undici");
5399
5419
  // package.json
5400
5420
  var package_default = {
5401
5421
  name: "agent-shim",
5402
- version: "8.34.0",
5422
+ version: "8.36.0",
5403
5423
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5404
5424
  license: "MIT",
5405
5425
  author: "Joseph Mearman <joseph@mearman.co.uk>",
package/dist/index.mjs CHANGED
@@ -277,13 +277,23 @@ var CodexProviderConfigSchema = z.strictObject({
277
277
  /** Which login the upstream call is made with; see `CODEX_LOGINS`. */
278
278
  login: z.enum(CODEX_LOGINS).optional()
279
279
  });
280
+ var ProviderRouteSchema = z.strictObject({
281
+ /** The condition, the same `when` union a configuration cascade entry takes: the object form or a trilean predicate tree over the request facts the door resolves (`request.model` and kin). */
282
+ when: WhenSchema,
283
+ /** The provider a matching request is sent to, by the name a provider file carries. */
284
+ provider: z.string().min(1),
285
+ /** The model name to rewrite the request to before it reaches the target, for a provider that expects another form (OpenRouter's `anthropic/claude-...` for instance). A route that rewrites reads the whole body, the one copy the rewrite needs. */
286
+ model: z.string().min(1).optional()
287
+ });
280
288
  var HttpProviderSchema = z.strictObject({
281
289
  $schema: z.string().optional(),
282
290
  kind: z.literal("http").optional(),
283
291
  displayName: z.string().min(1),
284
292
  baseUrl: z.url(),
285
293
  credential: ProviderCredentialSchema,
286
- env: ProviderEnvSchema.optional()
294
+ env: ProviderEnvSchema.optional(),
295
+ /** Per-request routing: when present, each request this provider would serve is matched against the list in order, and the first definite match is sent to that entry's provider instead (itself resolved exactly as a directly dialled request would be). Unmatched requests stay on this provider. */
296
+ routes: z.array(ProviderRouteSchema).optional()
287
297
  });
288
298
  var CodexProviderSchema = z.strictObject({
289
299
  $schema: z.string().optional(),
@@ -706,7 +716,17 @@ function resolversOf(context) {
706
716
  ...pool?.sevenDay === void 0 ? {} : { "quota.sevenDay.remaining": pool.sevenDay.remaining, "quota.sevenDay.utilization": pool.sevenDay.utilization, "quota.sevenDay.hoursUntilReset": pool.sevenDay.hoursUntilReset },
707
717
  ...pool?.burnPerHour === void 0 ? {} : { "quota.burnPerHour": pool.burnPerHour },
708
718
  ...pool?.resuming === void 0 ? {} : { "session.resuming": pool.resuming },
709
- ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo }
719
+ ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo },
720
+ ...context.request?.model === void 0 ? {} : { "request.model": context.request.model },
721
+ ...context.request?.hasImage === void 0 ? {} : { "request.hasImage": context.request.hasImage },
722
+ ...context.providerQuota === void 0 ? {} : Object.fromEntries(
723
+ Object.entries(context.providerQuota).flatMap(
724
+ ([name, windows]) => [
725
+ ...windows.fiveHour === void 0 ? [] : [["provider." + name + ".fiveHour.remaining", windows.fiveHour.remaining], ["provider." + name + ".fiveHour.utilization", windows.fiveHour.utilization], ["provider." + name + ".fiveHour.hoursUntilReset", windows.fiveHour.hoursUntilReset]],
726
+ ...windows.sevenDay === void 0 ? [] : [["provider." + name + ".sevenDay.remaining", windows.sevenDay.remaining], ["provider." + name + ".sevenDay.utilization", windows.sevenDay.utilization], ["provider." + name + ".sevenDay.hoursUntilReset", windows.sevenDay.hoursUntilReset]]
727
+ ]
728
+ )
729
+ )
710
730
  };
711
731
  return {
712
732
  resolveValue: (key) => {
@@ -5087,7 +5107,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
5087
5107
  // package.json
5088
5108
  var package_default = {
5089
5109
  name: "agent-shim",
5090
- version: "8.34.0",
5110
+ version: "8.36.0",
5091
5111
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5092
5112
  license: "MIT",
5093
5113
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -4,7 +4,7 @@ import * as zod from 'zod';
4
4
  import { z } from 'zod';
5
5
  import { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';
6
6
  import * as net from 'node:net';
7
- import { Duplex } from 'node:stream';
7
+ import { Duplex, Readable } from 'node:stream';
8
8
  import * as _orpc_client from '@orpc/client';
9
9
  import * as zod_v4_core from 'zod/v4/core';
10
10
  import * as _orpc_server from '@orpc/server';
@@ -446,6 +446,82 @@ declare const ProviderSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
446
446
  }>>;
447
447
  }, z.core.$strict>;
448
448
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
449
+ routes: z.ZodOptional<z.ZodArray<z.ZodObject<{
450
+ when: z.ZodUnion<readonly [z.ZodObject<{
451
+ newerThan: z.ZodOptional<z.ZodString>;
452
+ olderThan: z.ZodOptional<z.ZodString>;
453
+ maxSizeBytes: z.ZodOptional<z.ZodInt>;
454
+ branch: z.ZodOptional<z.ZodString>;
455
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
456
+ }, z.core.$strict>, z.ZodDiscriminatedUnion<[z.ZodObject<{
457
+ kind: z.ZodLiteral<"not">;
458
+ operand: typeof PredicateNodeSchema;
459
+ }, z.core.$strip>, z.ZodObject<{
460
+ kind: z.ZodLiteral<"and">;
461
+ left: typeof PredicateNodeSchema;
462
+ right: typeof PredicateNodeSchema;
463
+ }, z.core.$strip>, z.ZodObject<{
464
+ kind: z.ZodLiteral<"or">;
465
+ left: typeof PredicateNodeSchema;
466
+ right: typeof PredicateNodeSchema;
467
+ }, z.core.$strip>, z.ZodObject<{
468
+ kind: z.ZodLiteral<"allOf">;
469
+ operands: z.ZodArray<typeof PredicateNodeSchema>;
470
+ }, z.core.$strip>, z.ZodObject<{
471
+ kind: z.ZodLiteral<"anyOf">;
472
+ operands: z.ZodArray<typeof PredicateNodeSchema>;
473
+ }, z.core.$strip>, z.ZodObject<{
474
+ kind: z.ZodLiteral<"compare">;
475
+ op: z.ZodEnum<{
476
+ gt: "gt";
477
+ gte: "gte";
478
+ lt: "lt";
479
+ lte: "lte";
480
+ eq: "eq";
481
+ neq: "neq";
482
+ }>;
483
+ left: typeof trilean.ExpressionNodeSchema;
484
+ right: typeof trilean.ExpressionNodeSchema;
485
+ }, z.core.$strip>, z.ZodObject<{
486
+ kind: z.ZodLiteral<"textCompare">;
487
+ op: z.ZodEnum<{
488
+ equals: "equals";
489
+ notEquals: "notEquals";
490
+ matches: "matches";
491
+ notMatches: "notMatches";
492
+ portableMatches: "portableMatches";
493
+ portableNotMatches: "portableNotMatches";
494
+ }>;
495
+ left: typeof trilean.ExpressionNodeSchema;
496
+ right: typeof trilean.ExpressionNodeSchema;
497
+ }, z.core.$strip>, z.ZodObject<{
498
+ kind: z.ZodLiteral<"memberOf">;
499
+ op: z.ZodEnum<{
500
+ in: "in";
501
+ notIn: "notIn";
502
+ }>;
503
+ operand: typeof trilean.ExpressionNodeSchema;
504
+ candidates: z.ZodArray<typeof trilean.ExpressionNodeSchema>;
505
+ }, z.core.$strip>, z.ZodObject<{
506
+ kind: z.ZodLiteral<"exists">;
507
+ operand: typeof trilean.ExpressionNodeSchema;
508
+ }, z.core.$strip>, z.ZodObject<{
509
+ kind: z.ZodLiteral<"some">;
510
+ collection: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
511
+ item: typeof PredicateNodeSchema;
512
+ filter: z.ZodOptional<typeof PredicateNodeSchema>;
513
+ }, z.core.$strip>, z.ZodObject<{
514
+ kind: z.ZodLiteral<"every">;
515
+ collection: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
516
+ item: typeof PredicateNodeSchema;
517
+ filter: z.ZodOptional<typeof PredicateNodeSchema>;
518
+ }, z.core.$strip>, z.ZodObject<{
519
+ kind: z.ZodLiteral<"treeReference">;
520
+ key: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
521
+ }, z.core.$strip>], "kind">]>;
522
+ provider: z.ZodString;
523
+ model: z.ZodOptional<z.ZodString>;
524
+ }, z.core.$strict>>>;
449
525
  }, z.core.$strict>, z.ZodObject<{
450
526
  $schema: z.ZodOptional<z.ZodString>;
451
527
  kind: z.ZodLiteral<"codex">;
@@ -2665,8 +2741,8 @@ interface RoutedRequest {
2665
2741
  readonly url: string;
2666
2742
  /** The request's headers with every internal header already stripped: exactly what a route may forward onwards. */
2667
2743
  readonly headers: IncomingHttpHeaders;
2668
- /** The request's body stream: a route either pipes it onward untouched or reads it whole, never both. */
2669
- readonly body: IncomingMessage;
2744
+ /** The request's body stream: a route either pipes it onward untouched or reads it whole, never both. A readable, not the socket-bound type the pipeline received it as, because the routing layer's scan hands routes a replayed body (the scanned head re-emitted in front of the unread remainder) that is a plain stream. */
2745
+ readonly body: Readable;
2670
2746
  /** Aborted the moment the client goes away; a route aborts its own upstream work with it. */
2671
2747
  readonly signal: AbortSignal;
2672
2748
  readonly session: SessionIdentity;
@@ -5378,12 +5454,12 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
5378
5454
  identity: "identity";
5379
5455
  provider: "provider";
5380
5456
  pool: "pool";
5457
+ keychain: "keychain";
5381
5458
  "ambient-credential": "ambient-credential";
5382
5459
  "binary-discovery": "binary-discovery";
5383
5460
  "claude-shim": "claude-shim";
5384
5461
  "path-resolution": "path-resolution";
5385
5462
  "legacy-name": "legacy-name";
5386
- keychain: "keychain";
5387
5463
  "directory-rules": "directory-rules";
5388
5464
  "categories-local": "categories-local";
5389
5465
  "active-identity": "active-identity";
@@ -5959,12 +6035,12 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
5959
6035
  identity: "identity";
5960
6036
  provider: "provider";
5961
6037
  pool: "pool";
6038
+ keychain: "keychain";
5962
6039
  "ambient-credential": "ambient-credential";
5963
6040
  "binary-discovery": "binary-discovery";
5964
6041
  "claude-shim": "claude-shim";
5965
6042
  "path-resolution": "path-resolution";
5966
6043
  "legacy-name": "legacy-name";
5967
- keychain: "keychain";
5968
6044
  "directory-rules": "directory-rules";
5969
6045
  "categories-local": "categories-local";
5970
6046
  "active-identity": "active-identity";
@@ -6449,12 +6525,12 @@ declare const DoctorFindingSchema: z.ZodObject<{
6449
6525
  identity: "identity";
6450
6526
  provider: "provider";
6451
6527
  pool: "pool";
6528
+ keychain: "keychain";
6452
6529
  "ambient-credential": "ambient-credential";
6453
6530
  "binary-discovery": "binary-discovery";
6454
6531
  "claude-shim": "claude-shim";
6455
6532
  "path-resolution": "path-resolution";
6456
6533
  "legacy-name": "legacy-name";
6457
- keychain: "keychain";
6458
6534
  "directory-rules": "directory-rules";
6459
6535
  "categories-local": "categories-local";
6460
6536
  "active-identity": "active-identity";
@@ -6478,12 +6554,12 @@ declare const DoctorRunOutputSchema: z.ZodObject<{
6478
6554
  identity: "identity";
6479
6555
  provider: "provider";
6480
6556
  pool: "pool";
6557
+ keychain: "keychain";
6481
6558
  "ambient-credential": "ambient-credential";
6482
6559
  "binary-discovery": "binary-discovery";
6483
6560
  "claude-shim": "claude-shim";
6484
6561
  "path-resolution": "path-resolution";
6485
6562
  "legacy-name": "legacy-name";
6486
- keychain: "keychain";
6487
6563
  "directory-rules": "directory-rules";
6488
6564
  "categories-local": "categories-local";
6489
6565
  "active-identity": "active-identity";
@@ -7033,6 +7109,18 @@ interface ConditionContext {
7033
7109
  readonly resuming?: boolean;
7034
7110
  readonly lastPickHoursAgo?: number;
7035
7111
  };
7112
+ /**
7113
+ * The request facts a provider's route condition reads, present only when the condition routes a request the door is resolving: `request.model` (the Messages body's own model field) and `request.hasImage` (an image block anywhere in the scanned head of the body). A request the door could not read that far carries neither, so a condition naming them is indeterminate and the routing falls through, never to a cheaper provider by accident.
7114
+ */
7115
+ readonly request?: {
7116
+ readonly model?: string;
7117
+ readonly hasImage?: boolean;
7118
+ };
7119
+ /** Per-provider quota facts for request routing, keyed by provider name, from the session identity's usage snapshot: a route condition names a target's windows (`provider.<name>.fiveHour.utilization` and kin) to skip one that is exhausted or nearly spent. The windows are the same shape the pool facts read. */
7120
+ readonly providerQuota?: Readonly<Record<string, {
7121
+ readonly fiveHour?: PoolWindowFacts;
7122
+ readonly sevenDay?: PoolWindowFacts;
7123
+ }>>;
7036
7124
  }
7037
7125
  /**
7038
7126
  * The result of evaluating one `when`: definite, with which condition fields were present and which of those did not hold, or indeterminate, when a datum the condition needed is missing. Indeterminate is not false: a missing fact has not failed the condition, it has left it undecided, and the consumer decides what an undecided entry does (the resolver keeps today's behaviour of not passing it, so nothing changes for existing configurations; the pool ranking of #258 instead keeps such a member but ranks it last, naming the missing fact).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.34.0",
3
+ "version": "8.36.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",