agent-shim 11.75.0 → 11.76.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
@@ -28490,6 +28490,8 @@ var init_schema = __esm({
28490
28490
  $schema: external_exports.string().optional(),
28491
28491
  name: FileNameSchema,
28492
28492
  defaultConfigProfile: StoredNameSchema.optional(),
28493
+ /** The provider this identity exists for: a launch on the identity routes through it unless `--provider`, `--no-provider` or a cascade layer's `launch.provider` says otherwise. Its sessions run on the provider's credential, so the identity needs no stored login. */
28494
+ provider: StoredNameSchema.optional(),
28493
28495
  allowAmbientCredential: external_exports.boolean().default(false),
28494
28496
  credential: CredentialSchema.optional(),
28495
28497
  /** When this identity's five-hour window should be open: the front door opens one at each anchor if none is. Absent, nothing is opened on a schedule. */
@@ -37381,1099 +37383,744 @@ var init_proxyBypass2 = __esm({
37381
37383
  }
37382
37384
  });
37383
37385
 
37384
- // src/usage/account.ts
37385
- function isObject2(value2) {
37386
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
37386
+ // src/providerAliases.ts
37387
+ function parseAliases(file2, raw) {
37388
+ const parsed = ProviderAliasesSchema.safeParse(raw);
37389
+ if (!parsed.success) {
37390
+ throw new ConfigValidationError(file2, parsed.error.issues);
37391
+ }
37392
+ return parsed.data;
37387
37393
  }
37388
- function isIdentityName(name) {
37389
- return IdentitySchema.shape.name.safeParse(name).success;
37394
+ function readProviderAliases(paths) {
37395
+ return readJson(paths.providerAliasesFile, ProviderAliasesSchema) ?? {};
37390
37396
  }
37391
- function claudeJsonPath(identitiesDir, identity) {
37392
- return import_node_path24.default.join(identitiesDir, identity, CLAUDE_JSON);
37397
+ function loadProviderAliases(file2, port) {
37398
+ const raw = port.readConfigFile(file2);
37399
+ return raw === void 0 ? {} : parseAliases(file2, raw);
37393
37400
  }
37394
- function readAccountMetadata(fs31, identitiesDir, identity) {
37395
- if (!isIdentityName(identity)) {
37396
- throw new Error(`"${identity}" is not a valid identity name.`);
37401
+ function writeProviderAliases(paths, aliases) {
37402
+ if (Object.keys(aliases).length === 0) {
37403
+ import_node_fs9.default.rmSync(paths.providerAliasesFile, { force: true });
37404
+ return;
37397
37405
  }
37398
- return readAccountMetadataFile(fs31, claudeJsonPath(identitiesDir, identity));
37406
+ writeJsonAtomic(paths.providerAliasesFile, aliases);
37399
37407
  }
37400
- function readAccountMetadataFile(fs31, file2) {
37401
- const raw = fs31.readFileUtf8(file2);
37402
- if (raw === void 0) {
37403
- return void 0;
37404
- }
37405
- let parsed;
37406
- try {
37407
- parsed = JSON.parse(raw);
37408
- } catch {
37409
- throw new AccountMetadataError(file2, "not valid JSON");
37410
- }
37411
- if (!isObject2(parsed)) {
37412
- throw new AccountMetadataError(file2, "not a JSON object");
37413
- }
37414
- const oauthAccount = parsed.oauthAccount;
37415
- if (!isObject2(oauthAccount)) {
37416
- return void 0;
37408
+ function canonicalProviderName(name, defined, aliases) {
37409
+ if (defined(name)) {
37410
+ return name;
37417
37411
  }
37418
- const picked = {};
37419
- for (const key of Object.keys(AccountMetadataSchema.shape)) {
37420
- const value2 = oauthAccount[key];
37421
- if (value2 !== null && value2 !== void 0) {
37422
- picked[key] = value2;
37412
+ return aliases()[name] ?? name;
37413
+ }
37414
+ function aliasesAfterRename(aliases, from, to2) {
37415
+ const next = {};
37416
+ for (const [alias, target] of Object.entries(aliases)) {
37417
+ if (alias !== to2) {
37418
+ next[alias] = target === from ? to2 : target;
37423
37419
  }
37424
37420
  }
37425
- const account = AccountMetadataSchema.safeParse(picked);
37426
- if (!account.success) {
37427
- throw new AccountMetadataError(file2, account.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; "));
37421
+ next[from] = to2;
37422
+ return next;
37423
+ }
37424
+ function aliasesOf(aliases, name) {
37425
+ return Object.entries(aliases).filter(([, target]) => target === name).map(([alias]) => alias).sort();
37426
+ }
37427
+ function removeProviderAlias(paths, alias) {
37428
+ const aliases = readProviderAliases(paths);
37429
+ if (!(alias in aliases)) {
37430
+ throw new ProviderAliasNotFoundError(alias);
37428
37431
  }
37429
- return account.data;
37432
+ writeProviderAliases(paths, Object.fromEntries(Object.entries(aliases).filter(([name]) => name !== alias)));
37430
37433
  }
37431
- function createAccountReader(fs31, identitiesDir) {
37432
- const cache = /* @__PURE__ */ new Map();
37433
- return (identity) => {
37434
- if (!isIdentityName(identity)) {
37435
- throw new Error(`"${identity}" is not a valid identity name.`);
37434
+ function aliasFindings(aliases, providerNames) {
37435
+ return Object.entries(aliases).map(([alias, target]) => {
37436
+ if (!providerNames.has(target)) {
37437
+ return { level: "fail", alias, message: `${alias} is recorded as a former name of ${target}, which is not a provider. Run \`agent-shim provider unalias ${alias}\`.` };
37436
37438
  }
37437
- const stat = fs31.lstat(claudeJsonPath(identitiesDir, identity));
37438
- const stamp = stat === void 0 ? "absent" : `${String(stat.mtimeMs)}:${String(stat.sizeBytes)}`;
37439
- const cached2 = cache.get(identity);
37440
- if (cached2?.stamp === stamp) {
37441
- return cached2.account;
37439
+ if (providerNames.has(alias)) {
37440
+ return { level: "warn", alias, message: `${alias} is both a provider and a recorded former name of ${target}; the provider wins. Run \`agent-shim provider unalias ${alias}\`.` };
37442
37441
  }
37443
- const account = readAccountMetadata(fs31, identitiesDir, identity);
37444
- cache.set(identity, { stamp, account });
37445
- return account;
37442
+ return { level: "pass", alias, message: `${alias} is a former name of ${target}, still honoured for sessions launched under it. Run \`agent-shim provider unalias ${alias}\` once none is running.` };
37443
+ });
37444
+ }
37445
+ function createProviderCanonicaliser(params) {
37446
+ const reported = /* @__PURE__ */ new Set();
37447
+ return (name) => {
37448
+ const current = canonicalProviderName(name, params.defined, () => {
37449
+ try {
37450
+ return params.aliases();
37451
+ } catch (error63) {
37452
+ params.log(`provider aliases ignored, the aliases file is unreadable: ${error63 instanceof Error ? error63.message : String(error63)}`);
37453
+ return {};
37454
+ }
37455
+ });
37456
+ if (current !== name && !reported.has(name)) {
37457
+ reported.add(name);
37458
+ params.log(`provider ${name} is a former name of ${current}; a session launched under it is still running`);
37459
+ }
37460
+ return current;
37446
37461
  };
37447
37462
  }
37448
- var import_node_path24, CLAUDE_JSON, AccountMetadataError;
37449
- var init_account = __esm({
37450
- "src/usage/account.ts"() {
37463
+ var import_node_fs9, ProviderAliasesSchema, ProviderAliasNotFoundError;
37464
+ var init_providerAliases = __esm({
37465
+ "src/providerAliases.ts"() {
37451
37466
  "use strict";
37452
- import_node_path24 = __toESM(require("node:path"), 1);
37467
+ import_node_fs9 = __toESM(require("node:fs"), 1);
37468
+ init_zod();
37453
37469
  init_cliError();
37454
- init_schema();
37455
- init_schema2();
37456
- CLAUDE_JSON = ".claude.json";
37457
- AccountMetadataError = class extends CliError {
37458
- constructor(file2, detail) {
37459
- super(`${file2}: ${detail}, so its account metadata cannot be read.`);
37460
- this.file = file2;
37461
- this.name = "AccountMetadataError";
37470
+ init_load();
37471
+ init_store();
37472
+ ProviderAliasesSchema = external_exports.record(external_exports.string().min(1), external_exports.string().min(1));
37473
+ ProviderAliasNotFoundError = class extends CliError {
37474
+ constructor(alias) {
37475
+ super(`"${alias}" is not a former provider name. \`agent-shim provider list\` shows the ones recorded.`);
37476
+ this.alias = alias;
37477
+ this.name = "ProviderAliasNotFoundError";
37462
37478
  }
37463
- file;
37479
+ alias;
37464
37480
  };
37465
37481
  }
37466
37482
  });
37467
37483
 
37468
- // package.json
37469
- var package_default;
37470
- var init_package = __esm({
37471
- "package.json"() {
37472
- package_default = {
37473
- name: "agent-shim",
37474
- version: "11.75.0",
37475
- 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.",
37476
- license: "MIT",
37477
- author: "Joseph Mearman <joseph@mearman.co.uk>",
37478
- repository: {
37479
- type: "git",
37480
- url: "git+https://github.com/ExaDev/agent-shim.git"
37481
- },
37482
- homepage: "https://github.com/ExaDev/agent-shim#readme",
37483
- bugs: "https://github.com/ExaDev/agent-shim/issues",
37484
- keywords: [
37485
- "claude-code",
37486
- "claude",
37487
- "cli"
37488
- ],
37489
- packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
37490
- type: "module",
37491
- engines: {
37492
- node: "^22.18 || >=24"
37493
- },
37494
- bin: {
37495
- "agent-shim": "dist/cli.cjs",
37496
- "claude-use": "dist/cli.cjs"
37497
- },
37498
- files: [
37499
- "dist/cli.cjs",
37500
- "dist/index.mjs",
37501
- "dist/index.cjs",
37502
- "dist/types"
37503
- ],
37504
- scripts: {
37505
- prepare: "husky && node scripts/build.mts --bundle-only",
37506
- typecheck: "turbo run _typecheck",
37507
- _typecheck: "tsc --noEmit",
37508
- lint: "turbo run _lint",
37509
- _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
37510
- test: "turbo run _test",
37511
- _test: "vitest run",
37512
- "test:coverage": "turbo run _test:coverage",
37513
- "_test:coverage": "vitest run --coverage",
37514
- knip: "turbo run _knip",
37515
- _knip: "knip --cache",
37516
- build: "turbo run _build",
37517
- _build: "node scripts/build.mts",
37518
- "build:bundle": "node scripts/build.mts --bundle-only",
37519
- prepublishOnly: "pnpm build:bundle",
37520
- schema: "node scripts/gen-schema.mts"
37521
- },
37522
- dependencies: {
37523
- "@anthropic-ai/claude-agent-sdk": "0.3.292",
37524
- "@clack/prompts": "^1.8.0",
37525
- "@modelcontextprotocol/sdk": "1.32.0",
37526
- "@orpc/client": "2.0.0-beta.43",
37527
- "@orpc/openapi": "2.0.0-beta.43",
37528
- "@orpc/server": "2.0.0-beta.43",
37529
- commander: "^15.0.0",
37530
- cosmiconfig: "^10.0.1",
37531
- picomatch: "^4.0.7",
37532
- "string-width": "8.3.0",
37533
- trilean: "1.9.0",
37534
- undici: "7.30.1",
37535
- zod: "^4.6.7"
37536
- },
37537
- devDependencies: {
37538
- "@commitlint/cli": "^21.2.2",
37539
- "@commitlint/config-conventional": "^21.2.2",
37540
- "@exadev/eslint-config": "2.12.1",
37541
- "@semantic-release/changelog": "^7.0.0",
37542
- "@semantic-release/git": "^11.0.1",
37543
- "@semantic-release/npm": "^13.1.5",
37544
- "@types/node": "^26.5.1",
37545
- "@types/picomatch": "^4.0.3",
37546
- "@vitest/coverage-v8": "^4.1.12",
37547
- combinatrix: "2.0.1",
37548
- "conventional-changelog-conventionalcommits": "^10.4.0",
37549
- esbuild: "^0.28.2",
37550
- eslint: "^10.10.0",
37551
- globals: "^17.12.0",
37552
- husky: "^9.1.7",
37553
- jiti: "^2.7.0",
37554
- knip: "^6.35.1",
37555
- "lint-staged": "^17.5.0",
37556
- rollup: "4.64.0",
37557
- "rollup-plugin-dts": "6.5.1",
37558
- "semantic-release": "^25.0.9",
37559
- turbo: "^2.11.5",
37560
- typescript: "^6.0.3",
37561
- "typescript-eslint": "^8.70.0",
37562
- vitest: "^4.1.12",
37563
- yaml: "2.9.1"
37564
- },
37565
- main: "dist/index.cjs",
37566
- module: "dist/index.mjs",
37567
- types: "dist/types/index.d.ts",
37568
- exports: {
37569
- ".": {
37570
- types: "./dist/types/index.d.ts",
37571
- import: "./dist/index.mjs",
37572
- require: "./dist/index.cjs"
37573
- },
37574
- "./package.json": "./package.json"
37575
- }
37576
- };
37484
+ // src/models/identity.ts
37485
+ function withoutContextMarker(model) {
37486
+ return model.toLowerCase().endsWith(CONTEXT_MARKER_1M) ? model.slice(0, -CONTEXT_MARKER_1M.length) : model;
37487
+ }
37488
+ function canonicalText(name) {
37489
+ return name.trim().toLowerCase().replace(NON_IDENTITY_BRACKET_RE, "").replace(/[\s_]+/g, "-").replace(/([a-z])(\d+(?:\.\d+)+)/g, "$1-$2").replace(/-+/g, "-").replace(/^-|-$/g, "");
37490
+ }
37491
+ function splitVariants(tokens) {
37492
+ const variants2 = [];
37493
+ let end = tokens.length;
37494
+ for (; ; ) {
37495
+ const token = tokens[end - 1];
37496
+ if (token === void 0 || !KNOWN_VARIANTS.has(token)) {
37497
+ return { familyTokens: tokens.slice(0, end), variants: variants2 };
37498
+ }
37499
+ variants2.unshift(token);
37500
+ end -= 1;
37577
37501
  }
37578
- });
37579
-
37580
- // src/usage/plan.ts
37581
- function planOf(account) {
37582
- const userTier = account?.userRateLimitTier;
37583
- if (userTier !== void 0 && ZERO_TIER.test(userTier)) {
37584
- return { kind: "pay-per-use", tier: userTier };
37502
+ }
37503
+ function versionOf(tokens) {
37504
+ for (let index = 0; index < tokens.length; index += 1) {
37505
+ const token = tokens[index];
37506
+ const next = tokens[index + 1];
37507
+ if (token === void 0) {
37508
+ return "";
37509
+ }
37510
+ if (/^\d+$/.test(token) && next !== void 0 && /^\d+$/.test(next)) {
37511
+ return `${token}.${next}`;
37512
+ }
37513
+ const dotted = /^\d+(?:\.\d+)+$/.exec(token);
37514
+ if (dotted !== null) {
37515
+ return dotted[0];
37516
+ }
37585
37517
  }
37586
- const tier = account?.organizationRateLimitTier ?? userTier;
37587
- const tierField = tier === void 0 ? {} : { tier };
37588
- if (tier === void 0) {
37589
- return { kind: "subscription", capacity: 1, recognised: false };
37518
+ return "";
37519
+ }
37520
+ function modelIdentity(rawName) {
37521
+ const segments = rawName.trim().toLowerCase().split("/");
37522
+ const stripped = canonicalText(segments[segments.length - 1] ?? "").split(":")[0] ?? "";
37523
+ const withoutDate = stripped.replace(DATE_STAMP_RE, "");
37524
+ const tokens = withoutDate.split("-").filter((token) => token !== "");
37525
+ const { familyTokens, variants: variants2 } = splitVariants(tokens);
37526
+ const version2 = versionOf(familyTokens);
37527
+ const versionless = familyTokens.filter((token, index) => {
37528
+ if (version2 === "") {
37529
+ return true;
37530
+ }
37531
+ const dotted = /^\d+(?:\.\d+)+$/.test(token);
37532
+ const previous = familyTokens[index - 1];
37533
+ const next = familyTokens[index + 1];
37534
+ const insideVersionRun = dotted || /^\d+$/.test(token) && (previous !== void 0 && /^\d+$/.test(previous) || next !== void 0 && /^\d+$/.test(next));
37535
+ return !insideVersionRun;
37536
+ });
37537
+ const family = versionless.join("-");
37538
+ const tokenVendor = familyTokens.find((token) => Object.keys(VENDOR_BY_TOKEN).some((key) => token === key || token.startsWith(key)));
37539
+ const namespace = segments.length >= 2 ? (segments[0] ?? "").split(":").pop() ?? "" : "";
37540
+ const namespaceVendor = VENDOR_BY_NAMESPACE[namespace] ?? "";
37541
+ const tokenVendorName = tokenVendor === void 0 ? "" : VENDOR_BY_TOKEN[Object.keys(VENDOR_BY_TOKEN).find((key) => tokenVendor === key || tokenVendor.startsWith(key)) ?? ""] ?? "";
37542
+ return {
37543
+ vendor: tokenVendorName === "" ? namespaceVendor : tokenVendorName,
37544
+ vendorConflict: tokenVendorName !== "" && namespaceVendor !== "" && tokenVendorName !== namespaceVendor,
37545
+ family,
37546
+ version: version2,
37547
+ variant: variants2.join("-"),
37548
+ familyAndVersion: version2 === "" ? family : `${family}-${version2.replace(/\./g, "-")}`
37549
+ };
37550
+ }
37551
+ function relateModels(left, right) {
37552
+ const a10 = modelIdentity(left);
37553
+ const b = modelIdentity(right);
37554
+ if (a10.familyAndVersion !== b.familyAndVersion) {
37555
+ return sameFamilyOtherVersion(a10, b) ? "similar" : "distinct";
37590
37556
  }
37591
- if (ZERO_TIER.test(tier)) {
37592
- return { kind: "pay-per-use", ...tierField };
37557
+ if (a10.variant !== b.variant || a10.vendorConflict || b.vendorConflict || a10.vendor !== "" && b.vendor !== "" && a10.vendor !== b.vendor) {
37558
+ return "similar";
37593
37559
  }
37594
- const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
37595
- if (multiplier !== void 0) {
37596
- return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
37560
+ return "same";
37561
+ }
37562
+ function sameFamilyOtherVersion(a10, b) {
37563
+ if (a10.family !== b.family) {
37564
+ return false;
37597
37565
  }
37598
- return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
37566
+ if (a10.vendor !== "" && b.vendor !== "" && a10.vendor !== b.vendor) {
37567
+ return false;
37568
+ }
37569
+ return true;
37599
37570
  }
37600
- var PlanClassSchema, TIER_MULTIPLIER, ZERO_TIER, BASE_TIER;
37601
- var init_plan2 = __esm({
37602
- "src/usage/plan.ts"() {
37571
+ var NON_IDENTITY_BRACKET_RE, CONTEXT_MARKER_1M, DATE_STAMP_RE, KNOWN_VARIANTS, VENDOR_BY_NAMESPACE, VENDOR_BY_TOKEN;
37572
+ var init_identity2 = __esm({
37573
+ "src/models/identity.ts"() {
37603
37574
  "use strict";
37604
- init_zod();
37605
- PlanClassSchema = external_exports.union([
37606
- external_exports.strictObject({
37607
- kind: external_exports.literal("subscription"),
37608
- /** The plan's size as a multiple of the smallest subscription tier (Max is 5, Ultra is 20). */
37609
- capacity: external_exports.number().positive(),
37610
- /** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
37611
- recognised: external_exports.boolean(),
37612
- /** The tier string the classification rests on, when the account had one. */
37613
- tier: external_exports.string().optional()
37614
- }),
37615
- external_exports.strictObject({
37616
- kind: external_exports.literal("pay-per-use"),
37617
- tier: external_exports.string().optional()
37618
- })
37619
- ]);
37620
- TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
37621
- ZERO_TIER = /(?:^|_)zero$/;
37622
- BASE_TIER = /(?:^|_)(?:pro|free)$/;
37575
+ NON_IDENTITY_BRACKET_RE = /\[[^\]]*\]/g;
37576
+ CONTEXT_MARKER_1M = "[1m]";
37577
+ DATE_STAMP_RE = /-(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])$/;
37578
+ KNOWN_VARIANTS = /* @__PURE__ */ new Set(["high", "medium", "low", "thinking", "xhigh", "max", "highspeed", "sol", "terra", "luna", "mini", "flash-lite", "turbo"]);
37579
+ VENDOR_BY_NAMESPACE = Object.freeze({
37580
+ anthropic: "anthropic",
37581
+ google: "google",
37582
+ openai: "openai",
37583
+ zai: "zai",
37584
+ "z-ai": "zai",
37585
+ minimax: "minimax",
37586
+ qwen: "qwen",
37587
+ deepseek: "deepseek",
37588
+ moonshot: "moonshot",
37589
+ meta: "meta",
37590
+ mistral: "mistral"
37591
+ });
37592
+ VENDOR_BY_TOKEN = Object.freeze({
37593
+ claude: "anthropic",
37594
+ gemini: "google",
37595
+ antigravity: "google",
37596
+ gpt: "openai",
37597
+ glm: "zai",
37598
+ minimax: "minimax",
37599
+ qwen: "qwen",
37600
+ deepseek: "deepseek",
37601
+ kimi: "moonshot",
37602
+ llama: "meta",
37603
+ mistral: "mistral",
37604
+ mixtral: "mistral"
37605
+ });
37623
37606
  }
37624
37607
  });
37625
37608
 
37626
- // src/usage/scan.ts
37627
- function mergeUsage(current, raw) {
37628
- const parsed = AnthropicUsageSchema.safeParse(raw);
37629
- if (!parsed.success) {
37630
- return current;
37609
+ // src/models/families.ts
37610
+ function modelOf(ref) {
37611
+ const slash = ref.indexOf("/");
37612
+ return slash === -1 ? ref : ref.slice(slash + 1);
37613
+ }
37614
+ function familyKeyOf(rawModel) {
37615
+ const identity = modelIdentity(rawModel);
37616
+ return identity.variant === "" ? identity.familyAndVersion : `${identity.familyAndVersion}${VARIANT_SEPARATOR}${identity.variant}`;
37617
+ }
37618
+ function assertionsOf(models) {
37619
+ return { same: models?.same ?? [], distinct: models?.distinct ?? [] };
37620
+ }
37621
+ function sameModel(left, right, assertions) {
37622
+ if (assertions.distinct.some(([a10, b]) => a10 === left && b === right || a10 === right && b === left)) {
37623
+ return false;
37631
37624
  }
37632
- const usage = parsed.data;
37633
- const merged = {
37634
- ...current,
37635
- ...typeof usage.input_tokens === "number" ? { inputTokens: usage.input_tokens } : {},
37636
- ...typeof usage.output_tokens === "number" ? { outputTokens: usage.output_tokens } : {},
37637
- ...typeof usage.cache_creation_input_tokens === "number" ? { cacheCreationInputTokens: usage.cache_creation_input_tokens } : {},
37638
- ...typeof usage.cache_read_input_tokens === "number" ? { cacheReadInputTokens: usage.cache_read_input_tokens } : {}
37639
- };
37640
- return Object.keys(merged).length === 0 ? current : merged;
37625
+ const leftGroup = assertions.same.find((group) => group.includes(left));
37626
+ const rightGroup = assertions.same.find((group) => group.includes(right));
37627
+ if (leftGroup !== void 0 && leftGroup === rightGroup) {
37628
+ return true;
37629
+ }
37630
+ if (leftGroup !== void 0 && !leftGroup.includes(right) || rightGroup !== void 0 && !rightGroup.includes(left)) {
37631
+ return false;
37632
+ }
37633
+ return relateModels(modelOf(left), modelOf(right)) === "same";
37641
37634
  }
37642
- function errorInfo(raw) {
37643
- const parsed = ErrorObjectSchema.safeParse(raw);
37644
- if (!parsed.success) {
37635
+ function pinnedTo(settings2, ref) {
37636
+ return settings2?.names?.some((name) => name === ref || name === modelOf(ref)) === true;
37637
+ }
37638
+ function buildFamilies(refs, models) {
37639
+ const assertions = assertionsOf(models);
37640
+ const settingsByKey = models?.families ?? {};
37641
+ const groups = [];
37642
+ for (const ref of refs) {
37643
+ const pinnedKey = Object.entries(settingsByKey).find(([, settings2]) => pinnedTo(settings2, ref))?.[0];
37644
+ const joined3 = groups.find((group) => pinnedKey === void 0 ? group.refs.some((first) => sameModel(first, ref, assertions)) : group.key === pinnedKey);
37645
+ if (joined3 !== void 0) {
37646
+ joined3.refs.push(ref);
37647
+ } else {
37648
+ groups.push({ key: pinnedKey ?? familyKeyOf(modelOf(ref)), refs: [ref] });
37649
+ }
37650
+ }
37651
+ return groups.map((group) => ({ key: group.key, refs: group.refs, settings: settingsByKey[group.key] ?? {} }));
37652
+ }
37653
+ function familyNamed(families, member) {
37654
+ if (!member.startsWith(FAMILY_PREFIX)) {
37645
37655
  return void 0;
37646
37656
  }
37647
- const { type: type2, code, message } = parsed.data;
37648
- return {
37649
- ...type2 === void 0 ? {} : { type: type2 },
37650
- ...code === void 0 ? {} : { code: String(code) },
37651
- ...message === void 0 ? {} : { message }
37652
- };
37657
+ const key = member.slice(FAMILY_PREFIX.length);
37658
+ return families.find((family) => family.key === key);
37653
37659
  }
37654
- function isObject3(value2) {
37655
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
37660
+ function familyCapabilities(settings2) {
37661
+ return { vision: settings2.vision ?? true, tools: settings2.tools ?? true, thinking: settings2.thinking ?? true };
37656
37662
  }
37657
- function parseJson2(text5) {
37658
- try {
37659
- const parsed = JSON.parse(text5);
37660
- return parsed;
37661
- } catch {
37662
- return void 0;
37663
+ function familyMode(settings2) {
37664
+ return settings2.mode ?? "ordered";
37665
+ }
37666
+ function sharedFamilies(families) {
37667
+ return families.filter((family) => family.refs.length > 1);
37668
+ }
37669
+ function expandFamilyMembers(members2, families) {
37670
+ const expanded = [];
37671
+ for (const member of members2) {
37672
+ for (const ref of member.startsWith(FAMILY_PREFIX) ? familyNamed(families, member)?.refs ?? [] : [member]) {
37673
+ if (!expanded.includes(ref)) {
37674
+ expanded.push(ref);
37675
+ }
37676
+ }
37663
37677
  }
37678
+ return expanded;
37664
37679
  }
37665
- function createSseScanner() {
37666
- let model;
37667
- let usage;
37668
- let error63;
37669
- let eventName;
37670
- let line = "";
37671
- let skipping = false;
37672
- let data = [];
37673
- const dispatch = () => {
37674
- if (data.length > 0) {
37675
- const payload = parseJson2(data.join("\n"));
37676
- if (isObject3(payload)) {
37677
- const type2 = typeof payload.type === "string" ? payload.type : void 0;
37678
- if (type2 === "message_start" && isObject3(payload.message)) {
37679
- if (typeof payload.message.model === "string") {
37680
- model = payload.message.model;
37681
- }
37682
- usage = mergeUsage(usage, payload.message.usage);
37683
- } else if (type2 === "message_delta") {
37684
- usage = mergeUsage(usage, payload.usage);
37685
- } else if (type2 === "error") {
37686
- error63 = errorInfo(payload.error) ?? error63;
37687
- }
37688
- }
37689
- }
37690
- eventName = void 0;
37691
- data = [];
37692
- };
37693
- const worthKeeping = (partial2) => {
37694
- if (!partial2.startsWith("data:")) {
37695
- return true;
37696
- }
37697
- if (eventName !== void 0) {
37698
- return METADATA_EVENTS.has(eventName);
37699
- }
37700
- const leading = LEADING_TYPE.exec(partial2.slice("data:".length));
37701
- if (leading?.[1] !== void 0) {
37702
- return METADATA_EVENTS.has(leading[1]);
37703
- }
37704
- return void 0;
37705
- };
37706
- const completeLine = (complete) => {
37707
- const text5 = complete.endsWith("\r") ? complete.slice(0, -1) : complete;
37708
- if (text5 === "") {
37709
- dispatch();
37710
- return;
37711
- }
37712
- if (text5.startsWith("event:")) {
37713
- eventName = text5.slice("event:".length).trim();
37714
- return;
37715
- }
37716
- if (text5.startsWith("data:")) {
37717
- const value2 = text5.slice("data:".length);
37718
- data.push(value2.startsWith(" ") ? value2.slice(1) : value2);
37719
- }
37720
- };
37721
- return {
37722
- push: (text5) => {
37723
- let start = 0;
37724
- for (; ; ) {
37725
- const newline = text5.indexOf("\n", start);
37726
- const piece = newline === -1 ? text5.slice(start) : text5.slice(start, newline);
37727
- if (!skipping) {
37728
- line += piece;
37729
- if (worthKeeping(line) === false) {
37730
- line = "";
37731
- skipping = true;
37732
- }
37733
- }
37734
- if (newline === -1) {
37735
- return;
37736
- }
37737
- if (!skipping) {
37738
- completeLine(line);
37739
- }
37740
- line = "";
37741
- skipping = false;
37742
- start = newline + 1;
37743
- }
37744
- },
37745
- result: () => {
37746
- if (!skipping && line !== "") {
37747
- completeLine(line);
37748
- line = "";
37749
- }
37750
- dispatch();
37751
- return { model, usage, error: error63 };
37752
- }
37753
- };
37680
+ function familyOfRef(families, ref) {
37681
+ return families.find((family) => family.refs.includes(ref));
37754
37682
  }
37755
- function createJsonScanner() {
37756
- const captured = /* @__PURE__ */ new Map();
37757
- let depth = 0;
37758
- let inString = false;
37759
- let escaped = false;
37760
- let readingKey = false;
37761
- let expectKey = false;
37762
- let key = "";
37763
- let capturing;
37764
- let capture = "";
37765
- let inValue = false;
37766
- let currentKey = "";
37767
- const endValue = () => {
37768
- if (capturing !== void 0) {
37769
- captured.set(capturing, capture.trim());
37770
- }
37771
- capturing = void 0;
37772
- capture = "";
37773
- inValue = false;
37774
- };
37775
- const step2 = (char) => {
37776
- if (capturing !== void 0 && !(depth === 1 && !inString && (char === "," || char === "}"))) {
37777
- capture += char;
37778
- }
37779
- if (inString) {
37780
- if (escaped) {
37781
- escaped = false;
37782
- } else if (char === "\\") {
37783
- escaped = true;
37784
- } else if (char === '"') {
37785
- inString = false;
37786
- if (readingKey) {
37787
- readingKey = false;
37788
- currentKey = key;
37789
- }
37790
- return;
37791
- }
37792
- if (readingKey) {
37793
- key += char;
37794
- }
37795
- return;
37796
- }
37797
- switch (char) {
37798
- case '"':
37799
- inString = true;
37800
- if (depth === 1 && expectKey) {
37801
- readingKey = true;
37802
- expectKey = false;
37803
- key = "";
37804
- }
37805
- return;
37806
- case "{":
37807
- case "[":
37808
- depth += 1;
37809
- if (depth === 1 && char === "{") {
37810
- expectKey = true;
37811
- }
37812
- return;
37813
- case "}":
37814
- case "]":
37815
- if (depth === 1) {
37816
- endValue();
37817
- }
37818
- depth -= 1;
37819
- return;
37820
- case ":":
37821
- if (depth === 1 && !inValue) {
37822
- inValue = true;
37823
- if (METADATA_KEYS.has(currentKey)) {
37824
- capturing = currentKey;
37825
- capture = "";
37826
- }
37827
- }
37828
- return;
37829
- case ",":
37830
- if (depth === 1) {
37831
- endValue();
37832
- expectKey = true;
37833
- }
37834
- return;
37835
- default:
37836
- return;
37837
- }
37838
- };
37839
- return {
37840
- push: (text5) => {
37841
- for (const char of text5) {
37842
- step2(char);
37843
- }
37844
- },
37845
- result: () => {
37846
- const model = parseJson2(captured.get("model") ?? "");
37847
- const usageRaw = parseJson2(captured.get("usage") ?? "");
37848
- const errorRaw = parseJson2(captured.get("error") ?? "");
37849
- return {
37850
- model: typeof model === "string" ? model : void 0,
37851
- usage: mergeUsage(void 0, usageRaw),
37852
- error: errorInfo(errorRaw)
37853
- };
37854
- }
37855
- };
37683
+ function accountRefsOf(models) {
37684
+ const named = [...(models?.same ?? []).flat(), ...Object.values(models?.families ?? {}).flatMap((settings2) => settings2.names ?? [])];
37685
+ return [...new Set(named.filter((ref) => ref.startsWith("acct:")))];
37856
37686
  }
37857
- var AnthropicUsageSchema, ErrorObjectSchema, METADATA_EVENTS, LEADING_TYPE, METADATA_KEYS;
37858
- var init_scan = __esm({
37859
- "src/usage/scan.ts"() {
37687
+ var FAMILY_PREFIX, VARIANT_SEPARATOR;
37688
+ var init_families = __esm({
37689
+ "src/models/families.ts"() {
37860
37690
  "use strict";
37861
- init_zod();
37862
- AnthropicUsageSchema = external_exports.looseObject({
37863
- input_tokens: external_exports.number().int().nonnegative().nullish(),
37864
- output_tokens: external_exports.number().int().nonnegative().nullish(),
37865
- cache_creation_input_tokens: external_exports.number().int().nonnegative().nullish(),
37866
- cache_read_input_tokens: external_exports.number().int().nonnegative().nullish()
37867
- });
37868
- ErrorObjectSchema = external_exports.looseObject({
37869
- type: external_exports.string().optional(),
37870
- code: external_exports.union([external_exports.string(), external_exports.number()]).optional(),
37871
- message: external_exports.string().optional()
37872
- });
37873
- METADATA_EVENTS = /* @__PURE__ */ new Set(["message_start", "message_delta", "error"]);
37874
- LEADING_TYPE = /^\s*\{\s*"type"\s*:\s*"([^"]*)"/;
37875
- METADATA_KEYS = /* @__PURE__ */ new Set(["model", "usage", "error"]);
37691
+ init_identity2();
37692
+ FAMILY_PREFIX = "family:";
37693
+ VARIANT_SEPARATOR = "+";
37876
37694
  }
37877
37695
  });
37878
37696
 
37879
- // src/usage/middleware.ts
37880
- function scannerFor(headers) {
37881
- const contentType = headerValue(headers, "content-type")?.toLowerCase() ?? "";
37882
- if (contentType.startsWith("text/event-stream")) {
37883
- return createSseScanner();
37884
- }
37885
- if (contentType.startsWith("application/json")) {
37886
- return createJsonScanner();
37887
- }
37888
- return void 0;
37697
+ // src/providersStore.ts
37698
+ function providerJsonPath(paths, name) {
37699
+ return providerFilePath(paths.providersDir, name);
37889
37700
  }
37890
- function headerValue(headers, name) {
37891
- for (const [key, value2] of Object.entries(headers)) {
37892
- if (key.toLowerCase() === name) {
37893
- return value2;
37894
- }
37895
- }
37896
- return void 0;
37701
+ function providerFileDefined(lstat, providersDir, name) {
37702
+ return isStoredName(name) && lstat(providerFilePath(providersDir, name)) !== void 0;
37897
37703
  }
37898
- function decoderFor(encoding) {
37899
- switch (encoding?.trim().toLowerCase()) {
37900
- case void 0:
37901
- case "":
37902
- case "identity":
37903
- return null;
37904
- case "gzip":
37905
- case "x-gzip":
37906
- return zlib.createGunzip();
37907
- case "deflate":
37908
- return zlib.createInflate();
37909
- case "br":
37910
- return zlib.createBrotliDecompress();
37911
- default:
37912
- return void 0;
37913
- }
37704
+ function providerExists(paths, name) {
37705
+ return import_node_fs10.default.existsSync(providerJsonPath(paths, name));
37914
37706
  }
37915
- function destinationOf(path73, sessionProvider) {
37916
- const scoped = parseProviderPath(path73);
37917
- if (scoped !== void 0) {
37918
- return { provider: scoped.provider, endpoint: scoped.rest };
37707
+ function requireProviderExists(paths, name) {
37708
+ const canonical = canonicalProviderName(name, (candidate) => providerExists(paths, candidate), () => readProviderAliases(paths));
37709
+ if (!providerExists(paths, canonical)) {
37710
+ throw new ProviderNotFoundError(name);
37919
37711
  }
37920
- return { provider: sessionProvider ?? ANTHROPIC_PROVIDER, endpoint: path73 };
37921
37712
  }
37922
- function modelChoiceOf(event, servedModel, provider) {
37923
- if (event.modelChoice !== void 0) return event.modelChoice;
37924
- if (event.requestedModel === void 0 || servedModel === void 0 || event.requestedModel === servedModel) return void 0;
37925
- return { requested: event.requestedModel, providers: [provider] };
37713
+ function isRecord4(value2) {
37714
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
37926
37715
  }
37927
- function buildRecord(event, context) {
37928
- const { scan } = context;
37929
- const { provider, endpoint } = destinationOf(event.servedPath, event.session.provider);
37930
- const choice = modelChoiceOf(event, scan?.model, provider);
37931
- const rateLimitHeaders = rateLimitHeadersOf(event.headers);
37932
- const limit = classifyLimit({ status: event.status, headers: rateLimitHeaders ?? {}, error: scan?.error, nowMs: context.endedAt });
37933
- const requestId = headerValue(event.headers, "request-id");
37934
- const { identity, sessionId, projectId } = event.session;
37716
+ function legacyProviderConversion(raw) {
37717
+ if (!isRecord4(raw) || "credential" in raw) {
37718
+ return void 0;
37719
+ }
37720
+ const env = isRecord4(raw.env) ? raw.env : void 0;
37721
+ const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
37722
+ const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
37723
+ if (fields.length === 0) {
37724
+ return void 0;
37725
+ }
37726
+ const sources = [];
37727
+ if (typeof raw.tokenEnv === "string") {
37728
+ sources.push({ env: raw.tokenEnv });
37729
+ }
37730
+ if (Array.isArray(raw.tokenCommand)) {
37731
+ sources.push({ command: raw.tokenCommand });
37732
+ }
37733
+ if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
37734
+ sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
37735
+ }
37736
+ const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
37737
+ const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
37738
+ const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
37935
37739
  return {
37936
- schemaVersion: USAGE_SCHEMA_VERSION,
37937
- at: new Date(event.receivedAt).toISOString(),
37938
- ...identity === void 0 ? {} : { identity },
37939
- provider,
37940
- ...sessionId === void 0 ? {} : { sessionId },
37941
- ...event.clientSessionId === void 0 ? {} : { clientSessionId: event.clientSessionId },
37942
- ...projectId === void 0 ? {} : { project: projectId },
37943
- viaHeadroom: event.viaHeadroom,
37944
- route: event.route,
37945
- method: event.method,
37946
- endpoint,
37947
- ...scan?.model === void 0 ? {} : { model: scan.model },
37948
- ...choice === void 0 ? {} : { requestedModel: choice.requested },
37949
- ...choice !== void 0 && choice.providers.length > 1 ? { candidateProviders: [...choice.providers] } : {},
37950
- status: event.status,
37951
- latencyMs: Math.max(0, Math.round(context.headAt - event.receivedAt)),
37952
- durationMs: Math.max(0, Math.round(context.endedAt - event.receivedAt)),
37953
- outcome: context.outcome,
37954
- ...scan?.usage === void 0 ? {} : { usage: scan.usage },
37955
- ...rateLimitHeaders === void 0 ? {} : { rateLimitHeaders },
37956
- ...limit === void 0 ? {} : { limit },
37957
- ...requestId === void 0 ? {} : { requestId }
37958
- };
37959
- }
37960
- function createUsageMiddleware(deps) {
37961
- return (event) => {
37962
- if (DOOR_ANSWERED_ROUTES.has(event.route)) {
37963
- return void 0;
37964
- }
37965
- const headAt = deps.now();
37966
- const scanner = scannerFor(event.headers);
37967
- const decoder = scanner === void 0 ? null : decoderFor(headerValue(event.headers, "content-encoding"));
37968
- let scanning = scanner !== void 0 && decoder !== void 0;
37969
- const text5 = new TextDecoder();
37970
- const feed = (bytes, final) => {
37971
- if (!scanning || scanner === void 0) {
37972
- return;
37973
- }
37974
- try {
37975
- scanner.push(text5.decode(bytes, { stream: !final }));
37976
- } catch (error63) {
37977
- scanning = false;
37978
- deps.log(`usage: body scan failed for ${event.route}: ${error63 instanceof Error ? error63.message : String(error63)}`);
37979
- }
37980
- };
37981
- const finish = (outcome) => {
37982
- const endedAt = deps.now();
37983
- deps.defer(() => {
37984
- try {
37985
- deps.record(buildRecord(event, { headAt, endedAt, outcome, scan: scanning && scanner !== void 0 ? scanner.result() : void 0 }), event);
37986
- } catch (error63) {
37987
- deps.log(`usage: could not record ${event.method} ${event.path} (${String(event.status)}): ${error63 instanceof Error ? error63.message : String(error63)}`);
37988
- }
37989
- });
37990
- };
37991
- if (decoder === null || decoder === void 0) {
37992
- return {
37993
- chunk: (bytes) => {
37994
- feed(bytes, false);
37995
- },
37996
- end: (outcome) => {
37997
- feed(new Uint8Array(), true);
37998
- finish(outcome);
37999
- }
38000
- };
37740
+ fields,
37741
+ replacement: {
37742
+ ...rest,
37743
+ credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
37744
+ ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
38001
37745
  }
38002
- let ended;
38003
- let settled = false;
38004
- const settle2 = () => {
38005
- if (settled || ended === void 0) {
38006
- return;
38007
- }
38008
- settled = true;
38009
- feed(new Uint8Array(), true);
38010
- finish(ended);
38011
- };
38012
- decoder.on("data", (bytes) => {
38013
- feed(bytes, false);
38014
- });
38015
- decoder.on("error", (error63) => {
38016
- scanning = false;
38017
- deps.log(`usage: could not decompress the body of ${event.route}: ${error63.message}`);
38018
- settle2();
38019
- });
38020
- decoder.on("end", settle2);
38021
- return {
38022
- chunk: (bytes) => {
38023
- if (scanning) {
38024
- decoder.write(bytes);
38025
- }
38026
- },
38027
- end: (outcome) => {
38028
- ended = outcome;
38029
- if (!scanning || outcome === "aborted") {
38030
- decoder.destroy();
38031
- settle2();
38032
- return;
38033
- }
38034
- decoder.end();
38035
- }
38036
- };
38037
37746
  };
38038
37747
  }
38039
- var zlib, ANTHROPIC_PROVIDER, DOOR_ANSWERED_ROUTES;
38040
- var init_middleware = __esm({
38041
- "src/usage/middleware.ts"() {
38042
- "use strict";
38043
- zlib = __toESM(require("node:zlib"), 1);
38044
- init_route();
38045
- init_rateLimit();
38046
- init_scan();
38047
- init_schema2();
38048
- ANTHROPIC_PROVIDER = "anthropic";
38049
- DOOR_ANSWERED_ROUTES = /* @__PURE__ */ new Set(["(unauthorized)", "(unrouted)"]);
37748
+ function parseProviderFile(filePath, raw) {
37749
+ const conversion = legacyProviderConversion(raw);
37750
+ if (conversion !== void 0) {
37751
+ throw new LegacyProviderFileError(filePath, conversion);
38050
37752
  }
38051
- });
38052
-
38053
- // src/usage/quotaFacts.ts
38054
- function windowFacts(window, nowMs) {
38055
- if (window?.utilization === void 0 || window.resetsAt === void 0) {
37753
+ const parsed = parseIgnoringUnrecognisedKeys(ProviderSchema, raw);
37754
+ if (!parsed.success) {
37755
+ throw new ConfigValidationError(filePath, parsed.issues);
37756
+ }
37757
+ noteIgnoredKeys(filePath, parsed.ignored);
37758
+ return parsed.data;
37759
+ }
37760
+ function readProvider(paths, name) {
37761
+ const filePath = providerJsonPath(paths, name);
37762
+ if (!import_node_fs10.default.existsSync(filePath)) {
38056
37763
  return void 0;
38057
37764
  }
38058
- return { remaining: Math.max(0, 1 - window.utilization), utilization: window.utilization, hoursUntilReset: Math.max(0, (Date.parse(window.resetsAt) - nowMs) / MS_PER_HOUR) };
37765
+ const raw = JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
37766
+ return parseProviderFile(filePath, raw);
38059
37767
  }
38060
- function quotaFactsOf(unified, nowMs) {
38061
- const fiveHour = windowFacts(unified?.fiveHour, nowMs);
38062
- const sevenDay = windowFacts(unified?.sevenDay, nowMs);
38063
- const extraUsage = windowFacts(unified === void 0 ? void 0 : soleOverageWindow(unified), nowMs);
38064
- return { ...fiveHour === void 0 ? {} : { fiveHour }, ...sevenDay === void 0 ? {} : { sevenDay }, ...extraUsage === void 0 ? {} : { extraUsage } };
37768
+ function loadProvider(providersDir, name, port) {
37769
+ const filePath = providerFilePath(providersDir, name);
37770
+ const raw = port.readConfigFile(filePath);
37771
+ return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
38065
37772
  }
38066
- var MS_PER_HOUR;
38067
- var init_quotaFacts = __esm({
38068
- "src/usage/quotaFacts.ts"() {
38069
- "use strict";
38070
- init_rateLimit();
38071
- MS_PER_HOUR = 36e5;
38072
- }
38073
- });
38074
-
38075
- // src/usage/pick.ts
38076
- function unifiedOf(state) {
38077
- return state?.rateLimit?.unified;
37773
+ function reportInvalidProvider(name, message) {
37774
+ console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${message}`);
38078
37775
  }
38079
- function limitBlockedUntil(state, nowMs) {
38080
- const limit = state?.lastLimit;
38081
- if (state === void 0 || limit === void 0) {
38082
- return void 0;
38083
- }
38084
- if (state.lastRequestAt !== void 0 && Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
38085
- return void 0;
37776
+ function listProviders(paths, onInvalid = reportInvalidProvider) {
37777
+ const names = listProviderNames(
37778
+ (dir) => import_node_fs10.default.readdirSync(dir),
37779
+ paths.providersDir
37780
+ );
37781
+ const result = [];
37782
+ for (const name of names) {
37783
+ let provider;
37784
+ try {
37785
+ provider = readProvider(paths, name);
37786
+ } catch (error63) {
37787
+ if (!(error63 instanceof ConfigValidationError || error63 instanceof LegacyProviderFileError || error63 instanceof UnsafeNameError)) {
37788
+ throw error63;
37789
+ }
37790
+ onInvalid(name, error63.message);
37791
+ continue;
37792
+ }
37793
+ if (provider !== void 0) {
37794
+ result.push({ name, provider });
37795
+ }
38086
37796
  }
38087
- const until = limit.resetAt === void 0 ? limit.retryAfterSeconds === void 0 ? void 0 : Date.parse(limit.observedAt) + limit.retryAfterSeconds * MS_PER_SECOND5 : Date.parse(limit.resetAt);
38088
- return until !== void 0 && until > nowMs ? until : void 0;
37797
+ return result;
38089
37798
  }
38090
- function windowBlockedUntil(windows) {
38091
- const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
38092
- return resets.length === 0 ? void 0 : Math.max(...resets);
37799
+ function listProviderNames(readdir, providersDir) {
37800
+ let entries;
37801
+ try {
37802
+ entries = readdir(providersDir);
37803
+ } catch {
37804
+ return [];
37805
+ }
37806
+ return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
38093
37807
  }
38094
- function windowBurn(records, which) {
38095
- const points = [];
38096
- for (const record2 of records) {
38097
- const window = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders)?.[which];
38098
- if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
38099
- points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record2.at), utilization: window.utilization } });
38100
- }
37808
+ function describeProviderEndpoint(provider) {
37809
+ if (isCodexProvider(provider)) {
37810
+ return "codex translation daemon";
38101
37811
  }
38102
- const latest = points.at(-1)?.resetsAt;
38103
- const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
38104
- const first = current.at(0);
38105
- const last = current.at(-1);
38106
- if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
38107
- return void 0;
37812
+ if (isAntigravityProvider(provider)) {
37813
+ return (provider.antigravity?.mode ?? ANTIGRAVITY_DEFAULT_MODE) === "local" ? "antigravity local (ACP daemon)" : "antigravity cloud (Gemini API)";
38108
37814
  }
38109
- return (last.utilization - first.utilization) / (last.atMs - first.atMs);
37815
+ return isRouterProvider(provider) ? "the models block" : provider.baseUrl;
38110
37816
  }
38111
- function billedTokensOf(record2) {
38112
- const usage = record2.usage;
38113
- return (usage?.inputTokens ?? 0) + (usage?.cacheCreationInputTokens ?? 0) + (usage?.outputTokens ?? 0);
37817
+ function isEmptyCodexConfig(config2) {
37818
+ return config2 === void 0 || config2.defaultModel === void 0 && config2.effort === void 0 && config2.login === void 0 && (config2.models === void 0 || Object.keys(config2.models).length === 0);
38114
37819
  }
38115
- function utilisationPerBilledToken(records) {
38116
- const points = [];
38117
- for (const record2 of records) {
38118
- const window = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders)?.fiveHour;
38119
- if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
38120
- points.push({ resetsAt: window.resetsAt, utilization: window.utilization, billed: billedTokensOf(record2) });
38121
- }
37820
+ function isEmptyAntigravityConfig(config2) {
37821
+ return config2 === void 0 || config2.mode === void 0 && config2.defaultModel === void 0 && config2.effort === void 0 && config2.degradeDownTiers === void 0 && config2.cloud === void 0 && config2.local === void 0 && (config2.models === void 0 || Object.keys(config2.models).length === 0);
37822
+ }
37823
+ function addProvider(paths, name, input2) {
37824
+ if (providerExists(paths, name)) {
37825
+ throw new ProviderAlreadyExistsError(name);
38122
37826
  }
38123
- const latest = points.at(-1)?.resetsAt;
38124
- const current = points.filter((point) => point.resetsAt === latest);
38125
- const first = current.at(0);
38126
- const last = current.at(-1);
38127
- if (first === void 0 || last === void 0 || last.utilization <= first.utilization) {
38128
- return void 0;
37827
+ requireNewProviderName(name);
37828
+ const kind = input2.kind ?? "http";
37829
+ if (kind === "codex" && input2.baseUrl !== void 0) {
37830
+ throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
38129
37831
  }
38130
- const billed = current.slice(1).reduce((sum2, point) => sum2 + point.billed, 0);
38131
- return billed > 0 ? (last.utilization - first.utilization) / billed : void 0;
38132
- }
38133
- function windowRatio(records) {
38134
- const points = [];
38135
- for (const record2 of records) {
38136
- const unified = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders);
38137
- const five = unified?.fiveHour;
38138
- const seven = unified?.sevenDay;
38139
- if (five?.utilization !== void 0 && five.resetsAt !== void 0 && seven?.utilization !== void 0 && seven.resetsAt !== void 0) {
38140
- points.push({ fiveResetsAt: five.resetsAt, sevenResetsAt: seven.resetsAt, five: five.utilization, seven: seven.utilization });
38141
- }
37832
+ if (kind === "antigravity" && input2.baseUrl !== void 0) {
37833
+ throw new ProviderKindMismatchError("An antigravity provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
38142
37834
  }
38143
- const latest = points.at(-1);
38144
- const current = points.filter((point) => point.fiveResetsAt === latest?.fiveResetsAt && point.sevenResetsAt === latest.sevenResetsAt);
38145
- const first = current.at(0);
38146
- const last = current.at(-1);
38147
- if (first === void 0 || last === void 0 || last.five <= first.five || last.seven <= first.seven) {
38148
- return void 0;
37835
+ if (kind === "router" && input2.baseUrl !== void 0) {
37836
+ throw new ProviderKindMismatchError("A router provider has no base URL: it resolves each request's model name through the models block. Drop --base-url.");
38149
37837
  }
38150
- return (last.seven - first.seven) / (last.five - first.five);
38151
- }
38152
- function percent(fraction) {
38153
- return `${String(Math.round(fraction * PERCENT))}%`;
37838
+ if (kind === "http" && input2.baseUrl === void 0) {
37839
+ throw new ProviderKindMismatchError("An http provider needs --base-url.");
37840
+ }
37841
+ if (kind === "router" && (input2.sources.length > 0 || input2.target !== void 0 || input2.env !== void 0 || input2.pickerModels !== void 0)) {
37842
+ throw new ProviderKindMismatchError("A router provider has no credential, env or picker models of its own: the models block's targets carry their own.");
37843
+ }
37844
+ if (kind !== "http" && kind !== "router" && input2.catalogue !== void 0) {
37845
+ throw new ProviderKindMismatchError("A model catalogue applies only to an http provider.");
37846
+ }
37847
+ if (kind !== "codex" && !isEmptyCodexConfig(input2.codex)) {
37848
+ throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
37849
+ }
37850
+ if (kind !== "antigravity" && !isEmptyAntigravityConfig(input2.antigravity)) {
37851
+ throw new ProviderKindMismatchError("Antigravity settings apply only to an antigravity provider (--kind antigravity).");
37852
+ }
37853
+ const shared = {
37854
+ displayName: input2.displayName,
37855
+ credential: { sources: input2.sources, ...input2.target === void 0 ? {} : { target: input2.target } },
37856
+ ...input2.env === void 0 || Object.keys(input2.env).length === 0 ? {} : { env: input2.env },
37857
+ ...input2.pickerModels === void 0 || input2.pickerModels.length === 0 ? {} : { pickerModels: input2.pickerModels }
37858
+ };
37859
+ const parsed = ProviderSchema.safeParse(
37860
+ kind === "router" ? { kind, displayName: input2.displayName } : kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input2.codex) ? {} : { codex: input2.codex } } : kind === "antigravity" ? { kind, ...shared, ...isEmptyAntigravityConfig(input2.antigravity) ? {} : { antigravity: input2.antigravity } } : { ...shared, baseUrl: input2.baseUrl, ...input2.catalogue === void 0 ? {} : { catalogue: input2.catalogue } }
37861
+ );
37862
+ if (!parsed.success) {
37863
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
37864
+ }
37865
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
37866
+ return parsed.data;
38154
37867
  }
38155
- function describeWindow(label2, window, effective, nowMs) {
38156
- if (effective.reset) {
38157
- return `${label2} window has reset`;
37868
+ function updateProvider(paths, name, input2) {
37869
+ const existing = readProvider(paths, name);
37870
+ if (existing === void 0) {
37871
+ throw new ProviderNotFoundError(name);
38158
37872
  }
38159
- const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
38160
- const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
38161
- return `${label2} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
37873
+ const unset = new Set(input2.unsetEnv);
37874
+ const existingEnv = isRouterProvider(existing) ? {} : existing.env;
37875
+ const env = Object.fromEntries(Object.entries({ ...existingEnv, ...input2.env }).filter(([key]) => !unset.has(key)));
37876
+ const unsetPicker = new Set(input2.unsetPickerModels);
37877
+ const added = new Map((input2.pickerModels ?? []).map((entry) => [entry.model, entry]));
37878
+ const existingPicker = isRouterProvider(existing) ? [] : existing.pickerModels;
37879
+ const pickerModels = [
37880
+ ...(existingPicker ?? []).filter((entry) => !unsetPicker.has(entry.model)).map((entry) => added.get(entry.model) ?? entry),
37881
+ ...[...added.values()].filter((entry) => !(existingPicker ?? []).some((kept) => kept.model === entry.model))
37882
+ ];
37883
+ const router = isRouterProvider(existing);
37884
+ const target = router ? void 0 : input2.target ?? existing.credential.target;
37885
+ const cache = router ? void 0 : input2.cache === void 0 ? existing.credential.cache : input2.cache === false ? void 0 : input2.cache;
37886
+ if (isCodexProvider(existing) && input2.baseUrl !== void 0) {
37887
+ throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
37888
+ }
37889
+ if (isAntigravityProvider(existing) && input2.baseUrl !== void 0) {
37890
+ throw new ProviderKindMismatchError(`Provider "${name}" is an antigravity provider, which has no base URL.`);
37891
+ }
37892
+ if ((isCodexProvider(existing) || isAntigravityProvider(existing)) && input2.catalogue !== void 0 && input2.catalogue !== false) {
37893
+ throw new ProviderKindMismatchError(`Provider "${name}" is not an http provider; a model catalogue applies only to an http provider.`);
37894
+ }
37895
+ if (!isCodexProvider(existing) && !isEmptyCodexConfig(input2.codex)) {
37896
+ throw new ProviderKindMismatchError(`Provider "${name}" is not a codex provider; codex settings apply only to a codex provider.`);
37897
+ }
37898
+ if (!isAntigravityProvider(existing) && !isEmptyAntigravityConfig(input2.antigravity)) {
37899
+ throw new ProviderKindMismatchError(`Provider "${name}" is not an antigravity provider; antigravity settings apply only to an antigravity provider.`);
37900
+ }
37901
+ const shared = {
37902
+ ...input2.displayName === void 0 ? {} : { displayName: input2.displayName },
37903
+ ...router ? {} : {
37904
+ credential: {
37905
+ sources: input2.sources ?? existing.credential.sources,
37906
+ ...target === void 0 ? {} : { target },
37907
+ ...cache === void 0 ? {} : { cache }
37908
+ }
37909
+ },
37910
+ env: Object.keys(env).length === 0 ? void 0 : env,
37911
+ pickerModels: pickerModels.length === 0 ? void 0 : pickerModels
37912
+ };
37913
+ let candidate;
37914
+ if (isCodexProvider(existing)) {
37915
+ const codex = {
37916
+ ...existing.codex,
37917
+ ...input2.codex,
37918
+ ...existing.codex?.models === void 0 && input2.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input2.codex?.models } }
37919
+ };
37920
+ candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
37921
+ } else if (isAntigravityProvider(existing)) {
37922
+ const antigravity = {
37923
+ ...existing.antigravity,
37924
+ ...input2.antigravity,
37925
+ ...existing.antigravity?.models === void 0 && input2.antigravity?.models === void 0 ? {} : { models: { ...existing.antigravity?.models, ...input2.antigravity?.models } }
37926
+ };
37927
+ candidate = { ...existing, ...shared, antigravity: isEmptyAntigravityConfig(antigravity) ? void 0 : antigravity };
37928
+ } else if (router) {
37929
+ candidate = { kind: "router", ...shared };
37930
+ } else {
37931
+ const catalogue = input2.catalogue === void 0 ? existing.catalogue : input2.catalogue === false ? void 0 : input2.catalogue;
37932
+ candidate = { ...existing, ...shared, catalogue, ...input2.baseUrl === void 0 ? {} : { baseUrl: input2.baseUrl } };
37933
+ }
37934
+ const parsed = ProviderSchema.safeParse(candidate);
37935
+ if (!parsed.success) {
37936
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
37937
+ }
37938
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
37939
+ return parsed.data;
38162
37940
  }
38163
- function capacityOf(plan) {
38164
- return plan.kind === "subscription" ? plan.capacity : 1;
37941
+ function removeProvider(paths, name) {
37942
+ if (!providerExists(paths, name)) {
37943
+ throw new ProviderNotFoundError(name);
37944
+ }
37945
+ import_node_fs10.default.rmSync(providerJsonPath(paths, name));
38165
37946
  }
38166
- function assessRefusedNested(member, nested, nowMs) {
38167
- const refusal2 = nested.earliestReturn === void 0 ? { reason: `no member of pool "${nested.pool}" can be picked` } : { reason: `every member of pool "${nested.pool}" is refused; ${nested.earliestReturn.identity} returns at ${new Date(nested.earliestReturn.atMs).toISOString()} (in ${formatAge(nested.earliestReturn.atMs - nowMs)})`, blockedUntilMs: nested.earliestReturn.atMs };
38168
- return {
38169
- candidate: {
38170
- identity: member.identity,
38171
- class: "ineligible",
38172
- ...refusal2.blockedUntilMs === void 0 ? {} : { blockedUntilMs: refusal2.blockedUntilMs },
38173
- plan: planOf(member.account),
38174
- reasons: [refusal2.reason]
37947
+ function cascadeProviderName(cascade, identityProvider) {
37948
+ let name;
37949
+ for (const layer of assembleCascade(cascade).layers) {
37950
+ if (layer.launch?.provider !== void 0) {
37951
+ name = layer.launch.provider;
38175
37952
  }
38176
- };
37953
+ }
37954
+ return name ?? identityProvider;
38177
37955
  }
38178
- function assess(member, nowMs) {
38179
- const nested = member.nested;
38180
- if (nested === void 0) {
38181
- return assessIdentity(member, nowMs);
37956
+ function selectProvider(params) {
37957
+ const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
37958
+ const named = cliName ?? (params.cascade === void 0 ? params.identityProvider : cascadeProviderName(params.cascade, params.identityProvider));
37959
+ if (named === void 0) {
37960
+ return void 0;
38182
37961
  }
38183
- if (nested.kind === "refused") {
38184
- return assessRefusedNested(member, nested, nowMs);
37962
+ const name = canonicalProviderName(named, (candidate) => loadProvider(params.paths.providersDir, candidate, params.port) !== void 0, () => loadProviderAliases(params.paths.providerAliasesFile, params.port));
37963
+ const definition = loadProvider(params.paths.providersDir, name, params.port);
37964
+ if (definition === void 0) {
37965
+ const known = listProviderNames(params.port.readdir, params.paths.providersDir);
37966
+ return {
37967
+ ok: false,
37968
+ status: 1,
37969
+ message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
37970
+ };
38185
37971
  }
38186
- const assessment = assessIdentity(member, nowMs);
38187
- return { ...assessment, candidate: { ...assessment.candidate, reasons: [`picked by pool "${nested.pool}"`, ...nested.reasons] } };
37972
+ return { ok: true, provider: { name, definition } };
38188
37973
  }
38189
- function assessIdentity(member, nowMs) {
38190
- const plan = planOf(member.account);
38191
- const planReasons = plan.kind === "pay-per-use" ? ["billed by use (no plan allowance)"] : plan.recognised ? [`${String(plan.capacity)}x plan`] : [`plan tier ${plan.tier === void 0 ? "not known" : `"${plan.tier}" not recognised`}, counted as 1x`];
38192
- const base = { identity: member.identity, plan };
38193
- if (member.readError !== void 0) {
38194
- return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
38195
- }
38196
- const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
38197
- const unified = unifiedOf(state);
38198
- if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
38199
- return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
37974
+ function resolveProvider(params) {
37975
+ const selection = selectProvider(params);
37976
+ if (selection?.ok !== true) {
37977
+ return selection;
38200
37978
  }
38201
- const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
38202
- const fiveHourWindow = unified.fiveHour;
38203
- const sevenDayWindow = unified.sevenDay;
38204
- const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
38205
- const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
38206
- const windowReasons = [
38207
- ...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
38208
- ...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
38209
- ];
38210
- const overageWindow = soleOverageWindow(unified);
38211
- const overage = overageWindow === void 0 ? void 0 : effectiveWindow(overageWindow, nowMs);
38212
- const overageReasons = overageWindow === void 0 || overage === void 0 ? [] : [describeWindow("extra usage", overageWindow, overage, nowMs)];
38213
- const blockedUntil = Math.max(
38214
- windowBlockedUntil([five, seven, overage].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
38215
- limitBlockedUntil(state, nowMs) ?? 0
38216
- );
38217
- if (blockedUntil > nowMs) {
38218
- if (unified.overageStatus === "allowed") {
38219
- return { candidate: { ...base, class: "pay-per-use", planReturnsAtMs: blockedUntil, reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)}), continues as extra usage`, ...windowReasons, ...planReasons] } };
38220
- }
38221
- return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...unified.fallbackAvailable === true ? ["a fallback is on offer, so requests for some models may still be served"] : [], ...windowReasons, ...overageReasons, ...planReasons] } };
37979
+ const { name, definition } = selection.provider;
37980
+ if (isRouterProvider(definition)) {
37981
+ return { ok: true, provider: { name, definition, credential: { target: "bearer", token: "", source: { env: "AGENT_SHIM_ROUTER" }, warnings: [] } }, warnings: [] };
38222
37982
  }
38223
- if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
38224
- const headroom = overage?.utilization === void 0 ? void 0 : Math.max(0, 1 - overage.utilization);
38225
- return { candidate: { ...base, class: "pay-per-use", ...headroom === void 0 ? {} : { headroom }, reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"], ...overageReasons] } };
37983
+ if (params.credentials === void 0) {
37984
+ return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
38226
37985
  }
38227
- const basis = seven?.utilization !== void 0 ? { window: seven, windowMs: SEVEN_DAY_WINDOW_MS, name: "sevenDay" } : five?.utilization !== void 0 ? { window: five, windowMs: FIVE_HOUR_WINDOW_MS, name: "fiveHour" } : void 0;
38228
- if (basis?.window.utilization === void 0) {
38229
- return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
37986
+ const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
37987
+ if (!resolution.ok) {
37988
+ return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
38230
37989
  }
38231
- const remaining = Math.max(0, 1 - basis.window.utilization);
38232
- const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND5);
38233
- const weeklyScore = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR2);
38234
- const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
38235
- const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND5);
38236
- const ratio = windowRatio(member.records);
38237
- const fiveHourScore = basis.name === "sevenDay" && fiveRemaining !== void 0 && ratio !== void 0 ? Math.min(fiveRemaining * ratio, remaining) * plan.capacity / (fiveUntil / MS_PER_HOUR2) : void 0;
38238
- const fiveHourLeads = fiveHourScore !== void 0 && fiveHourScore > weeklyScore;
38239
- const score = fiveHourLeads ? fiveHourScore : weeklyScore;
38240
- const scoreWindow = fiveHourLeads ? "fiveHour" : basis.name;
38241
- const fiveHourTermReasons = basis.name !== "sevenDay" || fiveRemaining === void 0 ? [] : ratio === void 0 ? ["5h reset not scored: no measured 5h to 7d ratio yet"] : fiveHourLeads ? [`5h term leads: ${percent(fiveRemaining)} of 5h unused is about ${percent(Math.min(fiveRemaining * ratio, remaining))} of the week at the measured ratio, resets in ${formatAge(fiveUntil)}`] : [];
38242
- const burn = windowBurn(member.records, "fiveHour");
38243
- const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
38244
- const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND5);
38245
- const sevenDayBurn = windowBurn(member.records, "sevenDay");
38246
- return {
38247
- candidate: { ...base, class: "scored", score, scoreWindow, reasons: [...fiveHourTermReasons, ...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
38248
- ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
38249
- ...burn === void 0 ? {} : { burn },
38250
- ...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
38251
- ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
38252
- };
38253
- }
38254
- function poolFactsOf(member, nowMs, resuming, sticky) {
38255
- const { fiveHour, sevenDay, extraUsage } = quotaFactsOf(member.snapshot?.providers[ANTHROPIC_PROVIDER]?.rateLimit?.unified, nowMs);
38256
- const burn = windowBurn(member.records, "fiveHour");
38257
- return {
38258
- ...fiveHour === void 0 ? {} : { fiveHour },
38259
- ...sevenDay === void 0 ? {} : { sevenDay },
38260
- ...extraUsage === void 0 ? {} : { extraUsage },
38261
- ...burn === void 0 ? {} : { burnPerHour: burn * MS_PER_HOUR2 },
38262
- resuming,
38263
- ...sticky?.identity === member.identity ? { lastPickHoursAgo: Math.max(0, (nowMs - Date.parse(sticky.at)) / MS_PER_HOUR2) } : {}
38264
- };
37990
+ return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
38265
37991
  }
38266
- function rankPool(input2) {
38267
- const { members: members2, nowMs } = input2;
38268
- const assessments = members2.map((member) => ({ member, ...assess(member, nowMs) }));
38269
- const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
38270
- const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
38271
- const guarded2 = assessments.map((assessment) => {
38272
- const { candidate } = assessment;
38273
- if (candidate.class === "ineligible" || assessment.member.policy === void 0) {
38274
- return assessment;
38275
- }
38276
- const context = { nowMs, env: {}, pool: poolFactsOf(assessment.member, nowMs, input2.resuming, input2.sticky) };
38277
- const verdict = evaluateWhen(assessment.member.policy, context);
38278
- if (verdict.status === "indeterminate") {
38279
- return { ...assessment, candidate: { ...candidate, policyDemoted: true, reasons: [`policy undecided (${verdict.checked.join(", ") === "predicate" ? "predicate" : verdict.checked.join(", ")}): ${verdict.reason}`, ...candidate.reasons] } };
38280
- }
38281
- if (verdict.passed) {
38282
- return assessment;
38283
- }
38284
- return { ...assessment, candidate: { ...candidate, class: "ineligible", reasons: [`skipped by policy (${verdict.checked.join(", ")}: did not hold)`, ...candidate.reasons] } };
38285
- });
38286
- const candidates = guarded2.map((assessment) => {
38287
- const { candidate, fiveHour, burn } = assessment;
38288
- const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
38289
- const { sevenDay, sevenDayBurn } = assessment;
38290
- if (candidate.class !== "scored") {
38291
- return { ...candidate, feasible: true };
38292
- }
38293
- const drySpots = [];
38294
- const dryAfterMs = [];
38295
- if (fiveHour !== void 0 && pace !== void 0) {
38296
- const runsDryInMs = fiveHour.remaining / pace;
38297
- if (runsDryInMs < fiveHour.untilResetMs) {
38298
- dryAfterMs.push(runsDryInMs);
38299
- drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
38300
- }
38301
- }
38302
- if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
38303
- const runsDryInMs = sevenDay.remaining / sevenDayBurn;
38304
- if (runsDryInMs < sevenDay.untilResetMs) {
38305
- dryAfterMs.push(runsDryInMs);
38306
- drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
37992
+ function resolveRoutedProviderCredentials(params) {
37993
+ const { models } = params;
37994
+ if (models === void 0) {
37995
+ return { ok: true, checked: [] };
37996
+ }
37997
+ const virtualNames = new Set(Object.keys(models.virtual ?? {}));
37998
+ const providerNames = /* @__PURE__ */ new Set();
37999
+ const families = () => buildFamilies(params.targets?.() ?? [], models);
38000
+ const collect = (ref) => {
38001
+ if (ref.startsWith(FAMILY_PREFIX)) {
38002
+ for (const target of expandFamilyMembers([ref], families())) {
38003
+ collect(target);
38307
38004
  }
38005
+ return;
38308
38006
  }
38309
- const { sticky: sticky2 } = input2;
38310
- const warmHere = sticky2?.identity === candidate.identity && nowMs - Date.parse(sticky2.at) <= PROMPT_CACHE_TTL_MS;
38311
- if (input2.resuming && input2.resumeContextTokens !== void 0 && fiveHour !== void 0 && !warmHere) {
38312
- const perToken = utilisationPerBilledToken(assessment.member.records);
38313
- const committed = input2.committedContextTokens?.[candidate.identity] ?? 0;
38314
- const cost = perToken === void 0 ? void 0 : (input2.resumeContextTokens + committed) * perToken;
38315
- if (cost !== void 0 && cost > fiveHour.remaining) {
38316
- dryAfterMs.push(0);
38317
- const alongside = committed === 0 ? "" : ` (with ${String(committed)} tokens of other conversations already moving here)`;
38318
- drySpots.push(`resuming a ${String(input2.resumeContextTokens)}-token conversation here${alongside} would cost about ${percent(cost)} of the 5h window, which has ${percent(fiveHour.remaining)} left`);
38319
- }
38007
+ if (ref.startsWith("acct:") || virtualNames.has(ref)) {
38008
+ return;
38320
38009
  }
38321
- if (drySpots.length === 0) {
38322
- return { ...candidate, feasible: true };
38010
+ const [provider] = ref.split("/", 1);
38011
+ if (provider !== void 0 && provider !== "") {
38012
+ providerNames.add(provider);
38323
38013
  }
38324
- return { ...candidate, feasible: false, runsDryAtMs: nowMs + Math.min(...dryAfterMs), reasons: [...drySpots, ...candidate.reasons] };
38325
- });
38326
- const ordered = input2.preference === "listed" ? [...candidates] : [...candidates].sort(
38327
- (left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(left.policyDemoted ?? false) - Number(right.policyDemoted ?? false) || Number(right.feasible) - Number(left.feasible) || (right.headroom ?? 1) - (left.headroom ?? 1) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
38328
- );
38329
- const sticky = input2.sticky;
38330
- const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
38331
- const stickyCandidate = ordered[stickyIndex];
38332
- const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
38333
- if (sticky !== void 0 && keepSticky) {
38334
- const ageMs = nowMs - Date.parse(sticky.at);
38335
- const warm = ageMs <= PROMPT_CACHE_TTL_MS;
38336
- if (input2.resuming || warm) {
38337
- const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
38338
- ordered.splice(stickyIndex, 1);
38339
- ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
38014
+ };
38015
+ for (const model of Object.values(models.virtual ?? {})) {
38016
+ for (const member of model.members) {
38017
+ collect(member);
38340
38018
  }
38341
38019
  }
38342
- const movedOff = sticky !== void 0 && input2.resuming && !keepSticky ? { identity: sticky.identity, reason: stickyCandidate?.reasons[0] ?? "no longer among the pool's members" } : void 0;
38343
- const pick2 = ordered.find((candidate) => candidate.class !== "ineligible");
38344
- const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
38345
- const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
38346
- const holdForReturn = pick2?.runsDryAtMs === void 0 ? void 0 : returns.filter((entry) => entry.atMs < (pick2.runsDryAtMs ?? 0)).reduce((soonest, entry) => soonest === void 0 || entry.atMs < soonest.atMs ? entry : soonest, void 0);
38347
- return {
38348
- candidates: ordered,
38349
- ...pick2 === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick: pick2 },
38350
- ...movedOff === void 0 || pick2 === void 0 ? {} : { movedOff },
38351
- ...pick2?.planReturnsAtMs === void 0 ? {} : { planReturn: { identity: pick2.identity, atMs: pick2.planReturnsAtMs } },
38352
- ...holdForReturn === void 0 ? {} : { holdForReturn }
38353
- };
38354
- }
38355
- var MS_PER_SECOND5, MS_PER_HOUR2, HOURS_PER_DAY2, FIVE_HOURS, PROMPT_CACHE_TTL_HOURS, PERCENT, SEVEN_DAYS, FIVE_HOUR_WINDOW_MS, SEVEN_DAY_WINDOW_MS, PROMPT_CACHE_TTL_MS, CLASS_ORDER;
38356
- var init_pick = __esm({
38357
- "src/usage/pick.ts"() {
38358
- "use strict";
38359
- init_conditions();
38360
- init_plan2();
38361
- init_middleware();
38362
- init_preflight();
38363
- init_quotaFacts();
38364
- init_rateLimit();
38365
- MS_PER_SECOND5 = 1e3;
38366
- MS_PER_HOUR2 = 36e5;
38367
- HOURS_PER_DAY2 = 24;
38368
- FIVE_HOURS = 5;
38369
- PROMPT_CACHE_TTL_HOURS = 1;
38370
- PERCENT = 100;
38371
- SEVEN_DAYS = 7;
38372
- FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR2;
38373
- SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR2;
38374
- PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR2;
38375
- CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
38020
+ for (const ref of Object.values(models.tiers ?? {})) {
38021
+ collect(ref);
38376
38022
  }
38377
- });
38378
-
38379
- // src/usage/read.ts
38380
- function segmentDay(epochMs) {
38381
- return new Date(epochMs).toISOString().slice(0, "YYYY-MM-DD".length);
38382
- }
38383
- function segmentName(day, pid) {
38384
- return `${day}.${String(pid)}.jsonl`;
38385
- }
38386
- function listLogSegments(fs31, logDir) {
38387
- return fs31.readdir(logDir).flatMap((name) => {
38388
- const match = SEGMENT_RE.exec(name);
38389
- const day = match?.[1];
38390
- if (day === void 0) {
38391
- return [];
38392
- }
38393
- return [{ name, day, endsAt: Date.parse(`${day}T00:00:00.000Z`) + DAY_MS }];
38394
- }).sort((left, right) => left.day.localeCompare(right.day) || left.name.localeCompare(right.name));
38395
- }
38396
- function readUsageLog(fs31, logDir, options = {}) {
38397
- const { sinceMs } = options;
38398
- const records = [];
38399
- let invalidLines = 0;
38400
- for (const segment of listLogSegments(fs31, logDir)) {
38401
- if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
38023
+ if (providerNames.size === 0) {
38024
+ return { ok: true, checked: [] };
38025
+ }
38026
+ if (params.credentials === void 0) {
38027
+ return { ok: false, status: 1, message: `agent-shim: the models block routes to providers ${[...providerNames].sort().join(", ")}, which need their credentials resolved, but this launcher has no credential port wired` };
38028
+ }
38029
+ const failures = [];
38030
+ let credentialFailure = false;
38031
+ for (const name of [...providerNames].sort()) {
38032
+ const definition = loadProvider(params.paths.providersDir, name, params.port);
38033
+ if (definition === void 0) {
38034
+ failures.push(`no provider named "${name}"`);
38402
38035
  continue;
38403
38036
  }
38404
- const read = readSegment(fs31, logDir, segment);
38405
- if (read === void 0) {
38037
+ if (isRouterProvider(definition)) {
38406
38038
  continue;
38407
38039
  }
38408
- invalidLines += read.invalidLines;
38409
- for (const record2 of read.records) {
38410
- if (sinceMs === void 0 || Date.parse(record2.at) >= sinceMs) {
38411
- records.push(record2);
38040
+ const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
38041
+ if (!resolution.ok) {
38042
+ failures.push(resolution.message.replace(/^agent-shim: /, ""));
38043
+ credentialFailure = true;
38044
+ }
38045
+ }
38046
+ if (failures.length > 0) {
38047
+ return { ok: false, status: credentialFailure ? CREDENTIAL_UNAVAILABLE_EXIT : 1, message: `agent-shim: this launch routes model names through the models block, but: ${failures.join("; ")}` };
38048
+ }
38049
+ return { ok: true, checked: [...providerNames].sort() };
38050
+ }
38051
+ var import_node_fs10, ProviderNotFoundError, ProviderAlreadyExistsError, LEGACY_PROVIDER_FIELDS, LEGACY_ENV_CREDENTIAL_KEYS, LEGACY_LITERAL_PLACEHOLDER, LegacyProviderFileError, ProviderKindMismatchError;
38052
+ var init_providersStore = __esm({
38053
+ "src/providersStore.ts"() {
38054
+ "use strict";
38055
+ import_node_fs10 = __toESM(require("node:fs"), 1);
38056
+ init_load();
38057
+ init_store();
38058
+ init_unrecognisedKeys();
38059
+ init_schema();
38060
+ init_cliError();
38061
+ init_providerAliases();
38062
+ init_credential();
38063
+ init_walk();
38064
+ init_families();
38065
+ init_names();
38066
+ ProviderNotFoundError = class extends CliError {
38067
+ constructor(name) {
38068
+ super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
38069
+ this.name = name;
38070
+ this.name = "ProviderNotFoundError";
38071
+ }
38072
+ name;
38073
+ };
38074
+ ProviderAlreadyExistsError = class extends CliError {
38075
+ constructor(providerName) {
38076
+ super(`A provider named "${providerName}" already exists.`);
38077
+ this.providerName = providerName;
38078
+ this.name = "ProviderAlreadyExistsError";
38079
+ }
38080
+ providerName;
38081
+ };
38082
+ LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
38083
+ LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
38084
+ LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
38085
+ LegacyProviderFileError = class extends CliError {
38086
+ constructor(filePath, conversion) {
38087
+ super(
38088
+ `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
38089
+ ` + JSON.stringify(conversion.replacement, null, 2)
38090
+ );
38091
+ this.filePath = filePath;
38092
+ this.conversion = conversion;
38093
+ this.name = "LegacyProviderFileError";
38412
38094
  }
38413
- }
38095
+ filePath;
38096
+ conversion;
38097
+ };
38098
+ ProviderKindMismatchError = class extends UsageError {
38099
+ constructor(message) {
38100
+ super(message);
38101
+ this.name = "ProviderKindMismatchError";
38102
+ }
38103
+ };
38414
38104
  }
38415
- records.sort((left, right) => left.at.localeCompare(right.at));
38416
- return { records, invalidLines };
38105
+ });
38106
+
38107
+ // src/usage/account.ts
38108
+ function isObject2(value2) {
38109
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
38417
38110
  }
38418
- function readSegment(fs31, logDir, segment) {
38419
- const contents = fs31.readFileUtf8(import_node_path26.default.join(logDir, segment.name));
38420
- if (contents === void 0) {
38421
- return void 0;
38422
- }
38423
- const records = [];
38424
- let invalidLines = 0;
38425
- const lines = contents.split("\n");
38426
- lines.pop();
38427
- for (const line of lines) {
38428
- if (line === "") {
38429
- continue;
38430
- }
38431
- let raw;
38432
- try {
38433
- raw = JSON.parse(line);
38434
- } catch {
38435
- invalidLines += 1;
38436
- continue;
38437
- }
38438
- const parsed = UsageRecordSchema.safeParse(raw);
38439
- if (!parsed.success) {
38440
- invalidLines += 1;
38441
- continue;
38442
- }
38443
- records.push(parsed.data);
38444
- }
38445
- return { records, invalidLines };
38111
+ function isIdentityName(name) {
38112
+ return IdentitySchema.shape.name.safeParse(name).success;
38446
38113
  }
38447
- function readRecentUsageRecords(fs31, logDir, count2) {
38448
- const byDay = /* @__PURE__ */ new Map();
38449
- for (const segment of listLogSegments(fs31, logDir)) {
38450
- byDay.set(segment.day, [...byDay.get(segment.day) ?? [], segment]);
38451
- }
38452
- const days = [...byDay.values()];
38453
- let records = [];
38454
- for (let index = days.length - 1; index >= 0; index -= 1) {
38455
- const dayRecords = [];
38456
- for (const segment of days[index] ?? []) {
38457
- dayRecords.push(...readSegment(fs31, logDir, segment)?.records ?? []);
38458
- }
38459
- records = [...dayRecords, ...records];
38460
- records.sort((left, right) => left.at.localeCompare(right.at));
38461
- const olderDay = days[index - 1]?.[0];
38462
- const newestHeld = records[records.length - count2];
38463
- if (olderDay === void 0 || newestHeld !== void 0 && Date.parse(newestHeld.at) >= olderDay.endsAt) {
38464
- break;
38465
- }
38466
- }
38467
- return records.slice(-count2);
38114
+ function claudeJsonPath(identitiesDir, identity) {
38115
+ return import_node_path24.default.join(identitiesDir, identity, CLAUDE_JSON);
38468
38116
  }
38469
- function snapshotPath(snapshotsDir, identity) {
38117
+ function readAccountMetadata(fs31, identitiesDir, identity) {
38470
38118
  if (!isIdentityName(identity)) {
38471
38119
  throw new Error(`"${identity}" is not a valid identity name.`);
38472
38120
  }
38473
- return import_node_path26.default.join(snapshotsDir, `${identity}.json`);
38121
+ return readAccountMetadataFile(fs31, claudeJsonPath(identitiesDir, identity));
38474
38122
  }
38475
- function readUsageSnapshot(fs31, snapshotsDir, identity) {
38476
- const file2 = snapshotPath(snapshotsDir, identity);
38123
+ function readAccountMetadataFile(fs31, file2) {
38477
38124
  const raw = fs31.readFileUtf8(file2);
38478
38125
  if (raw === void 0) {
38479
38126
  return void 0;
@@ -38482,1225 +38129,1586 @@ function readUsageSnapshot(fs31, snapshotsDir, identity) {
38482
38129
  try {
38483
38130
  parsed = JSON.parse(raw);
38484
38131
  } catch {
38485
- throw new UsageSnapshotError(file2, "not valid JSON");
38132
+ throw new AccountMetadataError(file2, "not valid JSON");
38486
38133
  }
38487
- const snapshot = UsageSnapshotSchema.safeParse(parsed);
38488
- if (!snapshot.success) {
38489
- throw new UsageSnapshotError(file2, snapshot.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; "));
38134
+ if (!isObject2(parsed)) {
38135
+ throw new AccountMetadataError(file2, "not a JSON object");
38490
38136
  }
38491
- return snapshot.data;
38492
- }
38493
- function listUsageSnapshots(fs31, snapshotsDir) {
38494
- return fs31.readdir(snapshotsDir).flatMap((name) => {
38495
- if (!name.endsWith(".json")) {
38496
- return [];
38497
- }
38498
- const identity = name.slice(0, -".json".length);
38499
- if (!isIdentityName(identity)) {
38500
- return [];
38501
- }
38502
- const snapshot = readUsageSnapshot(fs31, snapshotsDir, identity);
38503
- return snapshot === void 0 ? [] : [snapshot];
38504
- }).sort((left, right) => left.identity.localeCompare(right.identity));
38505
- }
38506
- function summariseUsage(records) {
38507
- const groups = /* @__PURE__ */ new Map();
38508
- for (const record2 of records) {
38509
- const key = `${record2.identity ?? ""}\0${record2.provider}`;
38510
- const group = groups.get(key);
38511
- if (group === void 0) {
38512
- groups.set(key, [record2]);
38513
- } else {
38514
- group.push(record2);
38137
+ const oauthAccount = parsed.oauthAccount;
38138
+ if (!isObject2(oauthAccount)) {
38139
+ return void 0;
38140
+ }
38141
+ const picked = {};
38142
+ for (const key of Object.keys(AccountMetadataSchema.shape)) {
38143
+ const value2 = oauthAccount[key];
38144
+ if (value2 !== null && value2 !== void 0) {
38145
+ picked[key] = value2;
38515
38146
  }
38516
38147
  }
38517
- const summaries = [...groups.values()].flatMap((group) => {
38518
- const [first] = group;
38519
- const last = group.at(-1);
38520
- if (first === void 0 || last === void 0) {
38521
- return [];
38148
+ const account = AccountMetadataSchema.safeParse(picked);
38149
+ if (!account.success) {
38150
+ throw new AccountMetadataError(file2, account.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; "));
38151
+ }
38152
+ return account.data;
38153
+ }
38154
+ function createAccountReader(fs31, identitiesDir) {
38155
+ const cache = /* @__PURE__ */ new Map();
38156
+ return (identity) => {
38157
+ if (!isIdentityName(identity)) {
38158
+ throw new Error(`"${identity}" is not a valid identity name.`);
38522
38159
  }
38523
- const modelCounts = /* @__PURE__ */ new Map();
38524
- for (const record2 of group) {
38525
- if (record2.model !== void 0) {
38526
- modelCounts.set(record2.model, (modelCounts.get(record2.model) ?? 0) + 1);
38527
- }
38160
+ const stat = fs31.lstat(claudeJsonPath(identitiesDir, identity));
38161
+ const stamp = stat === void 0 ? "absent" : `${String(stat.mtimeMs)}:${String(stat.sizeBytes)}`;
38162
+ const cached2 = cache.get(identity);
38163
+ if (cached2?.stamp === stamp) {
38164
+ return cached2.account;
38528
38165
  }
38529
- const sum2 = (field) => group.reduce((total, record2) => total + (record2.usage?.[field] ?? 0), 0);
38530
- return [
38531
- {
38532
- identity: first.identity,
38533
- provider: first.provider,
38534
- requests: group.length,
38535
- failed: group.filter((record2) => record2.status >= FIRST_ERROR_STATUS).length,
38536
- limits: {
38537
- "rate-limited": group.filter((record2) => record2.limit?.kind === "rate-limited").length,
38538
- "quota-exhausted": group.filter((record2) => record2.limit?.kind === "quota-exhausted").length
38539
- },
38540
- tokens: {
38541
- inputTokens: sum2("inputTokens"),
38542
- outputTokens: sum2("outputTokens"),
38543
- cacheCreationInputTokens: sum2("cacheCreationInputTokens"),
38544
- cacheReadInputTokens: sum2("cacheReadInputTokens")
38545
- },
38546
- firstAt: first.at,
38547
- lastAt: last.at,
38548
- models: [...modelCounts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([model]) => model)
38549
- }
38550
- ];
38551
- });
38552
- return summaries.sort((left, right) => (left.identity ?? "").localeCompare(right.identity ?? "") || left.provider.localeCompare(right.provider));
38166
+ const account = readAccountMetadata(fs31, identitiesDir, identity);
38167
+ cache.set(identity, { stamp, account });
38168
+ return account;
38169
+ };
38553
38170
  }
38554
- var import_node_path26, SEGMENT_RE, DAY_MS, UsageSnapshotError, FIRST_ERROR_STATUS;
38555
- var init_read = __esm({
38556
- "src/usage/read.ts"() {
38171
+ var import_node_path24, CLAUDE_JSON, AccountMetadataError;
38172
+ var init_account = __esm({
38173
+ "src/usage/account.ts"() {
38557
38174
  "use strict";
38558
- import_node_path26 = __toESM(require("node:path"), 1);
38559
- init_account();
38175
+ import_node_path24 = __toESM(require("node:path"), 1);
38176
+ init_cliError();
38177
+ init_schema();
38560
38178
  init_schema2();
38561
- SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
38562
- DAY_MS = 864e5;
38563
- UsageSnapshotError = class extends Error {
38179
+ CLAUDE_JSON = ".claude.json";
38180
+ AccountMetadataError = class extends CliError {
38564
38181
  constructor(file2, detail) {
38565
- super(`${file2} is not a usage snapshot this agent-shim can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
38182
+ super(`${file2}: ${detail}, so its account metadata cannot be read.`);
38566
38183
  this.file = file2;
38567
- this.name = "UsageSnapshotError";
38184
+ this.name = "AccountMetadataError";
38568
38185
  }
38569
38186
  file;
38570
38187
  };
38571
- FIRST_ERROR_STATUS = 400;
38572
38188
  }
38573
38189
  });
38574
38190
 
38575
- // src/usage/store.ts
38576
- function pruneUsageLog(fs31, logDir, nowMs) {
38577
- const cutoff = nowMs - USAGE_RETENTION_MS;
38578
- for (const segment of listLogSegments(fs31, logDir)) {
38579
- if (segment.endsAt <= cutoff) {
38580
- fs31.removeRecursive(import_node_path27.default.join(logDir, segment.name));
38581
- }
38582
- }
38583
- }
38584
- function later(left, right) {
38585
- return left.localeCompare(right) >= 0 ? left : right;
38586
- }
38587
- function observedAt(record2) {
38588
- return new Date(Date.parse(record2.at) + record2.latencyMs).toISOString();
38589
- }
38590
- function foldProviderState(current, record2) {
38591
- const seen = observedAt(record2);
38592
- const newest = current?.lastRequestAt === void 0 || record2.at.localeCompare(current.lastRequestAt) >= 0;
38593
- const rateLimit = record2.rateLimitHeaders === void 0 || current?.rateLimit !== void 0 && current.rateLimit.observedAt.localeCompare(seen) > 0 ? current?.rateLimit : (() => {
38594
- const unified = parseUnifiedRateLimit(record2.rateLimitHeaders);
38595
- return { observedAt: seen, headers: record2.rateLimitHeaders, ...unified === void 0 ? {} : { unified } };
38596
- })();
38597
- const lastLimit = record2.limit === void 0 || current?.lastLimit !== void 0 && current.lastLimit.observedAt.localeCompare(seen) > 0 ? current?.lastLimit : { ...record2.limit, observedAt: seen, status: record2.status };
38598
- const lastModel = newest ? record2.model ?? current?.lastModel : current.lastModel ?? record2.model;
38599
- return {
38600
- lastRequestAt: current?.lastRequestAt === void 0 ? record2.at : later(current.lastRequestAt, record2.at),
38601
- ...newest ? { lastStatus: record2.status } : current.lastStatus === void 0 ? {} : { lastStatus: current.lastStatus },
38602
- ...lastModel === void 0 ? {} : { lastModel },
38603
- ...rateLimit === void 0 ? {} : { rateLimit },
38604
- ...lastLimit === void 0 ? {} : { lastLimit },
38605
- ...current?.quota === void 0 ? {} : { quota: current.quota }
38606
- };
38607
- }
38608
- function createUsageStore(deps) {
38609
- const { fs: fs31, paths } = deps;
38610
- let prunedDay;
38611
- const rewriteSnapshot = (identity, fold) => {
38612
- if (!isIdentityName(identity)) {
38613
- deps.log(`usage: no snapshot for identity "${identity}", which is not a valid identity name`);
38614
- return false;
38615
- }
38616
- let current;
38617
- try {
38618
- current = readUsageSnapshot(fs31, paths.usageSnapshotsDir, identity);
38619
- } catch (error63) {
38620
- if (!(error63 instanceof UsageSnapshotError)) {
38621
- throw error63;
38622
- }
38623
- deps.log(`usage: replacing an unreadable snapshot: ${error63.message}`);
38624
- }
38625
- const providers = fold(current);
38626
- if (providers === void 0) {
38627
- return false;
38628
- }
38629
- let account;
38630
- try {
38631
- account = deps.readAccount(identity);
38632
- } catch (error63) {
38633
- if (!(error63 instanceof AccountMetadataError)) {
38634
- throw error63;
38635
- }
38636
- deps.log(`usage: snapshot for ${identity} written without account metadata: ${error63.message}`);
38637
- }
38638
- const snapshot = UsageSnapshotSchema.parse({
38639
- schemaVersion: USAGE_SCHEMA_VERSION,
38640
- identity,
38641
- updatedAt: new Date(deps.now()).toISOString(),
38642
- ...account === void 0 ? {} : { account },
38643
- providers
38644
- });
38645
- fs31.mkdirPrivate(paths.usageSnapshotsDir);
38646
- fs31.writeFilePrivate(snapshotPath(paths.usageSnapshotsDir, identity), `${JSON.stringify(snapshot, null, 2)}
38647
- `);
38648
- deps.publish?.(snapshot);
38649
- return true;
38650
- };
38651
- return {
38652
- record: (input2) => {
38653
- const record2 = UsageRecordSchema.parse(input2);
38654
- const nowMs = deps.now();
38655
- fs31.mkdirPrivate(paths.usageDir);
38656
- fs31.mkdirPrivate(paths.usageLogDir);
38657
- const day = segmentDay(nowMs);
38658
- if (prunedDay !== day) {
38659
- pruneUsageLog(fs31, paths.usageLogDir, nowMs);
38660
- prunedDay = day;
38661
- }
38662
- fs31.appendFilePrivate(import_node_path27.default.join(paths.usageLogDir, segmentName(day, deps.pid)), `${JSON.stringify(record2)}
38663
- `);
38664
- deps.onRecord?.(record2);
38665
- if (record2.identity !== void 0) {
38666
- rewriteSnapshot(record2.identity, (current) => ({ ...current?.providers, [record2.provider]: foldProviderState(current?.providers[record2.provider], record2) }));
38667
- }
38668
- },
38669
- recordQuota: (identity, provider, quota) => rewriteSnapshot(identity, (current) => {
38670
- const state = current?.providers[provider];
38671
- if (current === void 0 || state === void 0) {
38672
- return void 0;
38191
+ // package.json
38192
+ var package_default;
38193
+ var init_package = __esm({
38194
+ "package.json"() {
38195
+ package_default = {
38196
+ name: "agent-shim",
38197
+ version: "11.76.0",
38198
+ 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.",
38199
+ license: "MIT",
38200
+ author: "Joseph Mearman <joseph@mearman.co.uk>",
38201
+ repository: {
38202
+ type: "git",
38203
+ url: "git+https://github.com/ExaDev/agent-shim.git"
38204
+ },
38205
+ homepage: "https://github.com/ExaDev/agent-shim#readme",
38206
+ bugs: "https://github.com/ExaDev/agent-shim/issues",
38207
+ keywords: [
38208
+ "claude-code",
38209
+ "claude",
38210
+ "cli"
38211
+ ],
38212
+ packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
38213
+ type: "module",
38214
+ engines: {
38215
+ node: "^22.18 || >=24"
38216
+ },
38217
+ bin: {
38218
+ "agent-shim": "dist/cli.cjs",
38219
+ "claude-use": "dist/cli.cjs"
38220
+ },
38221
+ files: [
38222
+ "dist/cli.cjs",
38223
+ "dist/index.mjs",
38224
+ "dist/index.cjs",
38225
+ "dist/types"
38226
+ ],
38227
+ scripts: {
38228
+ prepare: "husky && node scripts/build.mts --bundle-only",
38229
+ typecheck: "turbo run _typecheck",
38230
+ _typecheck: "tsc --noEmit",
38231
+ lint: "turbo run _lint",
38232
+ _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
38233
+ test: "turbo run _test",
38234
+ _test: "vitest run",
38235
+ "test:coverage": "turbo run _test:coverage",
38236
+ "_test:coverage": "vitest run --coverage",
38237
+ knip: "turbo run _knip",
38238
+ _knip: "knip --cache",
38239
+ build: "turbo run _build",
38240
+ _build: "node scripts/build.mts",
38241
+ "build:bundle": "node scripts/build.mts --bundle-only",
38242
+ prepublishOnly: "pnpm build:bundle",
38243
+ schema: "node scripts/gen-schema.mts"
38244
+ },
38245
+ dependencies: {
38246
+ "@anthropic-ai/claude-agent-sdk": "0.3.292",
38247
+ "@clack/prompts": "^1.8.0",
38248
+ "@modelcontextprotocol/sdk": "1.32.0",
38249
+ "@orpc/client": "2.0.0-beta.43",
38250
+ "@orpc/openapi": "2.0.0-beta.43",
38251
+ "@orpc/server": "2.0.0-beta.43",
38252
+ commander: "^15.0.0",
38253
+ cosmiconfig: "^10.0.1",
38254
+ picomatch: "^4.0.7",
38255
+ "string-width": "8.3.0",
38256
+ trilean: "1.9.0",
38257
+ undici: "7.30.1",
38258
+ zod: "^4.6.7"
38259
+ },
38260
+ devDependencies: {
38261
+ "@commitlint/cli": "^21.2.2",
38262
+ "@commitlint/config-conventional": "^21.2.2",
38263
+ "@exadev/eslint-config": "2.12.1",
38264
+ "@semantic-release/changelog": "^7.0.0",
38265
+ "@semantic-release/git": "^11.0.1",
38266
+ "@semantic-release/npm": "^13.1.5",
38267
+ "@types/node": "^26.5.1",
38268
+ "@types/picomatch": "^4.0.3",
38269
+ "@vitest/coverage-v8": "^4.1.12",
38270
+ combinatrix: "2.0.1",
38271
+ "conventional-changelog-conventionalcommits": "^10.4.0",
38272
+ esbuild: "^0.28.2",
38273
+ eslint: "^10.10.0",
38274
+ globals: "^17.12.0",
38275
+ husky: "^9.1.7",
38276
+ jiti: "^2.7.0",
38277
+ knip: "^6.35.1",
38278
+ "lint-staged": "^17.5.0",
38279
+ rollup: "4.64.0",
38280
+ "rollup-plugin-dts": "6.5.1",
38281
+ "semantic-release": "^25.0.9",
38282
+ turbo: "^2.11.5",
38283
+ typescript: "^6.0.3",
38284
+ "typescript-eslint": "^8.70.0",
38285
+ vitest: "^4.1.12",
38286
+ yaml: "2.9.1"
38287
+ },
38288
+ main: "dist/index.cjs",
38289
+ module: "dist/index.mjs",
38290
+ types: "dist/types/index.d.ts",
38291
+ exports: {
38292
+ ".": {
38293
+ types: "./dist/types/index.d.ts",
38294
+ import: "./dist/index.mjs",
38295
+ require: "./dist/index.cjs"
38296
+ },
38297
+ "./package.json": "./package.json"
38673
38298
  }
38674
- const keep = state.quota !== void 0 && state.quota.observedAt.localeCompare(quota.observedAt) > 0;
38675
- return { ...current.providers, [provider]: { ...state, quota: keep ? state.quota : quota } };
38676
- }),
38677
- recordRateLimit: (identity, provider, rateLimit) => rewriteSnapshot(identity, (current) => {
38678
- const state = current?.providers[provider] ?? {};
38679
- const keep = state.rateLimit !== void 0 && state.rateLimit.observedAt.localeCompare(rateLimit.observedAt) > 0;
38680
- const earlier = state.rateLimit;
38681
- const merged = rateLimit.unified === void 0 ? { ...earlier ?? rateLimit, planWindowsUnavailableAt: rateLimit.observedAt } : { ...rateLimit, unified: { ...earlier?.unified === void 0 ? {} : overageFactsOf(earlier.unified), ...rateLimit.unified } };
38682
- return { ...current?.providers, [provider]: { ...state, rateLimit: keep ? state.rateLimit : merged } };
38683
- })
38684
- };
38685
- }
38686
- var import_node_path27, DAY_MS2, DAYS_IN_WEEK2, USAGE_RETENTION_MS;
38687
- var init_store2 = __esm({
38688
- "src/usage/store.ts"() {
38689
- "use strict";
38690
- import_node_path27 = __toESM(require("node:path"), 1);
38691
- init_account();
38692
- init_rateLimit();
38693
- init_read();
38694
- init_schema2();
38695
- DAY_MS2 = 864e5;
38696
- DAYS_IN_WEEK2 = 7;
38697
- USAGE_RETENTION_MS = DAYS_IN_WEEK2 * DAY_MS2;
38299
+ };
38698
38300
  }
38699
38301
  });
38700
38302
 
38701
- // src/usage/poolPick.ts
38702
- function errorMessage(error63) {
38703
- return error63 instanceof Error ? error63.message : String(error63);
38704
- }
38705
- function loadPoolMembers(fs31, paths, identities, nowMs) {
38706
- const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
38707
- const log = readUsageLog(fs31, paths.usageLogDir, { sinceMs: windowStartMs });
38708
- return identities.map((identity) => {
38709
- const records = log.records.filter((record2) => record2.identity === identity && record2.provider === ANTHROPIC_PROVIDER && Date.parse(record2.at) >= windowStartMs);
38710
- try {
38711
- const snapshot = readUsageSnapshot(fs31, paths.usageSnapshotsDir, identity);
38712
- const account = readAccountMetadata(fs31, paths.identitiesDir, identity) ?? snapshot?.account;
38713
- return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
38714
- } catch (error63) {
38715
- if (error63 instanceof UsageSnapshotError || error63 instanceof AccountMetadataError) {
38716
- return { identity, records, readError: errorMessage(error63) };
38717
- }
38718
- throw error63;
38719
- }
38720
- });
38721
- }
38722
- function rankPoolGraph(input2) {
38723
- return rankNamedPool(input2, input2.poolName, [], input2.sticky);
38724
- }
38725
- function rankNamedPool(input2, poolName, stack, sticky) {
38726
- const pool = input2.pools[poolName];
38727
- if (pool === void 0) {
38728
- throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
38303
+ // src/usage/plan.ts
38304
+ function planOf(account) {
38305
+ const userTier = account?.userRateLimitTier;
38306
+ if (userTier !== void 0 && ZERO_TIER.test(userTier)) {
38307
+ return { kind: "pay-per-use", tier: userTier };
38729
38308
  }
38730
- const childStack = [...stack, poolName];
38731
- const direct = pool.identities.filter((entry) => poolNameOf(poolMemberIdentity(entry)) === void 0);
38732
- const missing = direct.filter((entry) => !input2.identityExists(poolMemberIdentity(entry))).map((entry) => ({ pool: poolName, identity: poolMemberIdentity(entry) }));
38733
- const loaded = new Map(loadPoolMembers(input2.fs, input2.paths, direct.filter((entry) => input2.identityExists(poolMemberIdentity(entry))).map(poolMemberIdentity), input2.nowMs).map((member) => [member.identity, member]));
38734
- const members2 = [];
38735
- for (const entry of pool.identities) {
38736
- const nestedName = poolNameOf(poolMemberIdentity(entry));
38737
- if (nestedName === void 0) {
38738
- const member = loaded.get(poolMemberIdentity(entry));
38739
- if (member !== void 0) {
38740
- members2.push(typeof entry === "string" ? member : { ...member, policy: entry.when });
38741
- }
38742
- continue;
38743
- }
38744
- assertNestable(input2.pools, poolName, poolMemberIdentity(entry), nestedName, childStack);
38745
- const nested = nestedMember(input2, nestedName, childStack, sticky);
38746
- members2.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
38747
- missing.push(...nested.missing);
38309
+ const tier = account?.organizationRateLimitTier ?? userTier;
38310
+ const tierField = tier === void 0 ? {} : { tier };
38311
+ if (tier === void 0) {
38312
+ return { kind: "subscription", capacity: 1, recognised: false };
38748
38313
  }
38749
- const ranking = rankPool({
38750
- members: members2,
38751
- nowMs: input2.nowMs,
38752
- resuming: input2.resuming,
38753
- ...input2.resumeContextTokens === void 0 ? {} : { resumeContextTokens: input2.resumeContextTokens },
38754
- ...input2.committedContextTokens === void 0 ? {} : { committedContextTokens: input2.committedContextTokens },
38755
- ...sticky === void 0 ? {} : { sticky },
38756
- ...pool.preference === void 0 ? {} : { preference: pool.preference }
38757
- });
38758
- return { ranking, missing };
38314
+ if (ZERO_TIER.test(tier)) {
38315
+ return { kind: "pay-per-use", ...tierField };
38316
+ }
38317
+ const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
38318
+ if (multiplier !== void 0) {
38319
+ return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
38320
+ }
38321
+ return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
38759
38322
  }
38760
- function assertNestable(pools, poolName, member, nestedName, childStack) {
38761
- if (pools[nestedName] === void 0) {
38762
- throw new PoolGraphError(`Pool "${poolName}" member "${member}" names a pool that is not defined.`);
38323
+ var PlanClassSchema, TIER_MULTIPLIER, ZERO_TIER, BASE_TIER;
38324
+ var init_plan2 = __esm({
38325
+ "src/usage/plan.ts"() {
38326
+ "use strict";
38327
+ init_zod();
38328
+ PlanClassSchema = external_exports.union([
38329
+ external_exports.strictObject({
38330
+ kind: external_exports.literal("subscription"),
38331
+ /** The plan's size as a multiple of the smallest subscription tier (Max is 5, Ultra is 20). */
38332
+ capacity: external_exports.number().positive(),
38333
+ /** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
38334
+ recognised: external_exports.boolean(),
38335
+ /** The tier string the classification rests on, when the account had one. */
38336
+ tier: external_exports.string().optional()
38337
+ }),
38338
+ external_exports.strictObject({
38339
+ kind: external_exports.literal("pay-per-use"),
38340
+ tier: external_exports.string().optional()
38341
+ })
38342
+ ]);
38343
+ TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
38344
+ ZERO_TIER = /(?:^|_)zero$/;
38345
+ BASE_TIER = /(?:^|_)(?:pro|free)$/;
38763
38346
  }
38764
- if (childStack.includes(nestedName)) {
38765
- throw new PoolGraphError(`Pool "${poolName}" member "${member}" closes a cycle: ${[...childStack.slice(childStack.indexOf(nestedName)), nestedName].join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
38347
+ });
38348
+
38349
+ // src/usage/scan.ts
38350
+ function mergeUsage(current, raw) {
38351
+ const parsed = AnthropicUsageSchema.safeParse(raw);
38352
+ if (!parsed.success) {
38353
+ return current;
38766
38354
  }
38355
+ const usage = parsed.data;
38356
+ const merged = {
38357
+ ...current,
38358
+ ...typeof usage.input_tokens === "number" ? { inputTokens: usage.input_tokens } : {},
38359
+ ...typeof usage.output_tokens === "number" ? { outputTokens: usage.output_tokens } : {},
38360
+ ...typeof usage.cache_creation_input_tokens === "number" ? { cacheCreationInputTokens: usage.cache_creation_input_tokens } : {},
38361
+ ...typeof usage.cache_read_input_tokens === "number" ? { cacheReadInputTokens: usage.cache_read_input_tokens } : {}
38362
+ };
38363
+ return Object.keys(merged).length === 0 ? current : merged;
38767
38364
  }
38768
- function nestedMember(input2, nestedName, childStack, sticky) {
38769
- const nested = rankNamedPool(input2, nestedName, childStack, sticky);
38770
- const pick2 = nested.ranking.pick;
38771
- if (pick2 !== void 0) {
38772
- const [member] = loadPoolMembers(input2.fs, input2.paths, [pick2.identity], input2.nowMs);
38773
- return { member: { ...member ?? { identity: pick2.identity, records: [] }, nested: { kind: "pick", pool: nestedName, reasons: pick2.reasons } }, missing: nested.missing };
38365
+ function errorInfo(raw) {
38366
+ const parsed = ErrorObjectSchema.safeParse(raw);
38367
+ if (!parsed.success) {
38368
+ return void 0;
38774
38369
  }
38775
- const earliestReturn = nested.ranking.earliestReturn;
38370
+ const { type: type2, code, message } = parsed.data;
38776
38371
  return {
38777
- member: {
38778
- identity: earliestReturn?.identity ?? `pool:${nestedName}`,
38779
- records: [],
38780
- nested: { kind: "refused", pool: nestedName, ...earliestReturn === void 0 ? {} : { earliestReturn } }
38781
- },
38782
- missing: nested.missing
38372
+ ...type2 === void 0 ? {} : { type: type2 },
38373
+ ...code === void 0 ? {} : { code: String(code) },
38374
+ ...message === void 0 ? {} : { message }
38783
38375
  };
38784
38376
  }
38785
- function readPicksFile(fs31, file2) {
38786
- const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
38787
- const raw = fs31.readFileUtf8(file2);
38788
- if (raw === void 0) {
38789
- return { file: empty };
38790
- }
38791
- let parsed;
38377
+ function isObject3(value2) {
38378
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
38379
+ }
38380
+ function parseJson2(text5) {
38792
38381
  try {
38793
- parsed = JSON.parse(raw);
38382
+ const parsed = JSON.parse(text5);
38383
+ return parsed;
38794
38384
  } catch {
38795
- return { file: empty, problem: `${file2} is not valid JSON` };
38385
+ return void 0;
38796
38386
  }
38797
- const result = PicksFileSchema.safeParse(parsed);
38798
- return result.success ? { file: result.data } : { file: empty, problem: `${file2} is not a picks file this agent-shim can read` };
38799
38387
  }
38800
- function readStickyPick(fs31, picksFile, directory) {
38801
- const read = readPicksFile(fs31, picksFile);
38802
- const entry = read.file.picks[directory];
38803
- return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
38388
+ function createSseScanner() {
38389
+ let model;
38390
+ let usage;
38391
+ let error63;
38392
+ let eventName;
38393
+ let line = "";
38394
+ let skipping = false;
38395
+ let data = [];
38396
+ const dispatch = () => {
38397
+ if (data.length > 0) {
38398
+ const payload = parseJson2(data.join("\n"));
38399
+ if (isObject3(payload)) {
38400
+ const type2 = typeof payload.type === "string" ? payload.type : void 0;
38401
+ if (type2 === "message_start" && isObject3(payload.message)) {
38402
+ if (typeof payload.message.model === "string") {
38403
+ model = payload.message.model;
38404
+ }
38405
+ usage = mergeUsage(usage, payload.message.usage);
38406
+ } else if (type2 === "message_delta") {
38407
+ usage = mergeUsage(usage, payload.usage);
38408
+ } else if (type2 === "error") {
38409
+ error63 = errorInfo(payload.error) ?? error63;
38410
+ }
38411
+ }
38412
+ }
38413
+ eventName = void 0;
38414
+ data = [];
38415
+ };
38416
+ const worthKeeping = (partial2) => {
38417
+ if (!partial2.startsWith("data:")) {
38418
+ return true;
38419
+ }
38420
+ if (eventName !== void 0) {
38421
+ return METADATA_EVENTS.has(eventName);
38422
+ }
38423
+ const leading = LEADING_TYPE.exec(partial2.slice("data:".length));
38424
+ if (leading?.[1] !== void 0) {
38425
+ return METADATA_EVENTS.has(leading[1]);
38426
+ }
38427
+ return void 0;
38428
+ };
38429
+ const completeLine = (complete) => {
38430
+ const text5 = complete.endsWith("\r") ? complete.slice(0, -1) : complete;
38431
+ if (text5 === "") {
38432
+ dispatch();
38433
+ return;
38434
+ }
38435
+ if (text5.startsWith("event:")) {
38436
+ eventName = text5.slice("event:".length).trim();
38437
+ return;
38438
+ }
38439
+ if (text5.startsWith("data:")) {
38440
+ const value2 = text5.slice("data:".length);
38441
+ data.push(value2.startsWith(" ") ? value2.slice(1) : value2);
38442
+ }
38443
+ };
38444
+ return {
38445
+ push: (text5) => {
38446
+ let start = 0;
38447
+ for (; ; ) {
38448
+ const newline = text5.indexOf("\n", start);
38449
+ const piece = newline === -1 ? text5.slice(start) : text5.slice(start, newline);
38450
+ if (!skipping) {
38451
+ line += piece;
38452
+ if (worthKeeping(line) === false) {
38453
+ line = "";
38454
+ skipping = true;
38455
+ }
38456
+ }
38457
+ if (newline === -1) {
38458
+ return;
38459
+ }
38460
+ if (!skipping) {
38461
+ completeLine(line);
38462
+ }
38463
+ line = "";
38464
+ skipping = false;
38465
+ start = newline + 1;
38466
+ }
38467
+ },
38468
+ result: () => {
38469
+ if (!skipping && line !== "") {
38470
+ completeLine(line);
38471
+ line = "";
38472
+ }
38473
+ dispatch();
38474
+ return { model, usage, error: error63 };
38475
+ }
38476
+ };
38804
38477
  }
38805
- function recordStickyPick(fs31, picksFile, directory, identity, nowMs) {
38806
- const current = readPicksFile(fs31, picksFile).file.picks;
38807
- const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
38808
- const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
38809
- fs31.mkdirPrivate(import_node_path28.default.dirname(picksFile));
38810
- fs31.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
38811
- `);
38478
+ function createJsonScanner() {
38479
+ const captured = /* @__PURE__ */ new Map();
38480
+ let depth = 0;
38481
+ let inString = false;
38482
+ let escaped = false;
38483
+ let readingKey = false;
38484
+ let expectKey = false;
38485
+ let key = "";
38486
+ let capturing;
38487
+ let capture = "";
38488
+ let inValue = false;
38489
+ let currentKey = "";
38490
+ const endValue = () => {
38491
+ if (capturing !== void 0) {
38492
+ captured.set(capturing, capture.trim());
38493
+ }
38494
+ capturing = void 0;
38495
+ capture = "";
38496
+ inValue = false;
38497
+ };
38498
+ const step2 = (char) => {
38499
+ if (capturing !== void 0 && !(depth === 1 && !inString && (char === "," || char === "}"))) {
38500
+ capture += char;
38501
+ }
38502
+ if (inString) {
38503
+ if (escaped) {
38504
+ escaped = false;
38505
+ } else if (char === "\\") {
38506
+ escaped = true;
38507
+ } else if (char === '"') {
38508
+ inString = false;
38509
+ if (readingKey) {
38510
+ readingKey = false;
38511
+ currentKey = key;
38512
+ }
38513
+ return;
38514
+ }
38515
+ if (readingKey) {
38516
+ key += char;
38517
+ }
38518
+ return;
38519
+ }
38520
+ switch (char) {
38521
+ case '"':
38522
+ inString = true;
38523
+ if (depth === 1 && expectKey) {
38524
+ readingKey = true;
38525
+ expectKey = false;
38526
+ key = "";
38527
+ }
38528
+ return;
38529
+ case "{":
38530
+ case "[":
38531
+ depth += 1;
38532
+ if (depth === 1 && char === "{") {
38533
+ expectKey = true;
38534
+ }
38535
+ return;
38536
+ case "}":
38537
+ case "]":
38538
+ if (depth === 1) {
38539
+ endValue();
38540
+ }
38541
+ depth -= 1;
38542
+ return;
38543
+ case ":":
38544
+ if (depth === 1 && !inValue) {
38545
+ inValue = true;
38546
+ if (METADATA_KEYS.has(currentKey)) {
38547
+ capturing = currentKey;
38548
+ capture = "";
38549
+ }
38550
+ }
38551
+ return;
38552
+ case ",":
38553
+ if (depth === 1) {
38554
+ endValue();
38555
+ expectKey = true;
38556
+ }
38557
+ return;
38558
+ default:
38559
+ return;
38560
+ }
38561
+ };
38562
+ return {
38563
+ push: (text5) => {
38564
+ for (const char of text5) {
38565
+ step2(char);
38566
+ }
38567
+ },
38568
+ result: () => {
38569
+ const model = parseJson2(captured.get("model") ?? "");
38570
+ const usageRaw = parseJson2(captured.get("usage") ?? "");
38571
+ const errorRaw = parseJson2(captured.get("error") ?? "");
38572
+ return {
38573
+ model: typeof model === "string" ? model : void 0,
38574
+ usage: mergeUsage(void 0, usageRaw),
38575
+ error: errorInfo(errorRaw)
38576
+ };
38577
+ }
38578
+ };
38812
38579
  }
38813
- var import_node_path28, PoolGraphError, SCHEMA_VERSION, PickEntrySchema, PicksFileSchema;
38814
- var init_poolPick = __esm({
38815
- "src/usage/poolPick.ts"() {
38580
+ var AnthropicUsageSchema, ErrorObjectSchema, METADATA_EVENTS, LEADING_TYPE, METADATA_KEYS;
38581
+ var init_scan = __esm({
38582
+ "src/usage/scan.ts"() {
38816
38583
  "use strict";
38817
- import_node_path28 = __toESM(require("node:path"), 1);
38818
38584
  init_zod();
38819
- init_cliError();
38820
- init_identity();
38821
- init_schema();
38822
- init_account();
38823
- init_pick();
38824
- init_read();
38825
- init_middleware();
38826
- init_store2();
38827
- PoolGraphError = class extends CliError {
38828
- constructor(message) {
38829
- super(message);
38830
- this.name = "PoolGraphError";
38831
- }
38832
- };
38833
- SCHEMA_VERSION = 1;
38834
- PickEntrySchema = external_exports.strictObject({ identity: external_exports.string().min(1), at: external_exports.iso.datetime() });
38835
- PicksFileSchema = external_exports.strictObject({
38836
- schemaVersion: external_exports.literal(SCHEMA_VERSION),
38837
- picks: external_exports.record(external_exports.string().min(1), PickEntrySchema)
38585
+ AnthropicUsageSchema = external_exports.looseObject({
38586
+ input_tokens: external_exports.number().int().nonnegative().nullish(),
38587
+ output_tokens: external_exports.number().int().nonnegative().nullish(),
38588
+ cache_creation_input_tokens: external_exports.number().int().nonnegative().nullish(),
38589
+ cache_read_input_tokens: external_exports.number().int().nonnegative().nullish()
38590
+ });
38591
+ ErrorObjectSchema = external_exports.looseObject({
38592
+ type: external_exports.string().optional(),
38593
+ code: external_exports.union([external_exports.string(), external_exports.number()]).optional(),
38594
+ message: external_exports.string().optional()
38838
38595
  });
38596
+ METADATA_EVENTS = /* @__PURE__ */ new Set(["message_start", "message_delta", "error"]);
38597
+ LEADING_TYPE = /^\s*\{\s*"type"\s*:\s*"([^"]*)"/;
38598
+ METADATA_KEYS = /* @__PURE__ */ new Set(["model", "usage", "error"]);
38839
38599
  }
38840
38600
  });
38841
38601
 
38842
- // src/realCredentialCache.ts
38843
- function cacheFileNameFor(owner) {
38844
- return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
38602
+ // src/usage/middleware.ts
38603
+ function scannerFor(headers) {
38604
+ const contentType = headerValue(headers, "content-type")?.toLowerCase() ?? "";
38605
+ if (contentType.startsWith("text/event-stream")) {
38606
+ return createSseScanner();
38607
+ }
38608
+ if (contentType.startsWith("application/json")) {
38609
+ return createJsonScanner();
38610
+ }
38611
+ return void 0;
38845
38612
  }
38846
- function parseEntry(raw) {
38847
- let json2;
38848
- try {
38849
- json2 = JSON.parse(raw);
38850
- } catch {
38851
- return void 0;
38613
+ function headerValue(headers, name) {
38614
+ for (const [key, value2] of Object.entries(headers)) {
38615
+ if (key.toLowerCase() === name) {
38616
+ return value2;
38617
+ }
38852
38618
  }
38853
- const parsed = CachedCredentialSchema.safeParse(json2);
38854
- return parsed.success ? parsed.data : void 0;
38619
+ return void 0;
38855
38620
  }
38856
- function requireKeychain() {
38857
- if (process.platform !== "darwin") {
38858
- throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
38621
+ function decoderFor(encoding) {
38622
+ switch (encoding?.trim().toLowerCase()) {
38623
+ case void 0:
38624
+ case "":
38625
+ case "identity":
38626
+ return null;
38627
+ case "gzip":
38628
+ case "x-gzip":
38629
+ return zlib.createGunzip();
38630
+ case "deflate":
38631
+ return zlib.createInflate();
38632
+ case "br":
38633
+ return zlib.createBrotliDecompress();
38634
+ default:
38635
+ return void 0;
38859
38636
  }
38860
38637
  }
38861
- function security(args, stdin2) {
38862
- const result = spawnSyncBlocking("a keychain call", "security", args, { encoding: "utf8", ...stdin2 === void 0 ? {} : { input: stdin2 }, stdio: ["pipe", "pipe", "ignore"] });
38863
- return { status: result.status, stdout: result.stdout };
38638
+ function destinationOf(path73, sessionProvider) {
38639
+ const scoped = parseProviderPath(path73);
38640
+ if (scoped !== void 0) {
38641
+ return { provider: scoped.provider, endpoint: scoped.rest };
38642
+ }
38643
+ return { provider: sessionProvider ?? ANTHROPIC_PROVIDER, endpoint: path73 };
38864
38644
  }
38865
- async function securityAsync(args, stdin2) {
38866
- return await new Promise((resolve3) => {
38867
- const child = (0, import_node_child_process4.spawn)("security", args, { stdio: ["pipe", "pipe", "ignore"] });
38868
- let stdout2 = "";
38869
- let settled = false;
38870
- const settle2 = (status) => {
38871
- if (!settled) {
38872
- settled = true;
38873
- resolve3({ status, stdout: stdout2 });
38874
- }
38875
- };
38876
- child.stdout.setEncoding("utf8");
38877
- child.stdout.on("data", (chunk2) => {
38878
- stdout2 += chunk2;
38879
- });
38880
- child.stdin.on("error", () => {
38881
- });
38882
- child.stdin.end(stdin2);
38883
- child.once("error", () => {
38884
- settle2(null);
38885
- });
38886
- child.once("close", settle2);
38887
- });
38645
+ function modelChoiceOf(event, servedModel, provider) {
38646
+ if (event.modelChoice !== void 0) return event.modelChoice;
38647
+ if (event.requestedModel === void 0 || servedModel === void 0 || event.requestedModel === servedModel) return void 0;
38648
+ return { requested: event.requestedModel, providers: [provider] };
38888
38649
  }
38889
- function createRealCredentialCache(root) {
38890
- const dir = import_node_path29.default.join(root, "credential-cache");
38891
- const filePath = (owner) => import_node_path29.default.join(dir, cacheFileNameFor(owner));
38650
+ function buildRecord(event, context) {
38651
+ const { scan } = context;
38652
+ const { provider, endpoint } = destinationOf(event.servedPath, event.session.provider);
38653
+ const choice = modelChoiceOf(event, scan?.model, provider);
38654
+ const rateLimitHeaders = rateLimitHeadersOf(event.headers);
38655
+ const limit = classifyLimit({ status: event.status, headers: rateLimitHeaders ?? {}, error: scan?.error, nowMs: context.endedAt });
38656
+ const requestId = headerValue(event.headers, "request-id");
38657
+ const { identity, sessionId, projectId } = event.session;
38892
38658
  return {
38893
- read(store, owner) {
38894
- if (store === "file") {
38895
- try {
38896
- return parseEntry(import_node_fs9.default.readFileSync(filePath(owner), "utf8"));
38897
- } catch {
38898
- return void 0;
38899
- }
38900
- }
38901
- requireKeychain();
38902
- const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
38903
- return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
38904
- },
38905
- write(store, owner, entry) {
38906
- const serialised = JSON.stringify(entry);
38907
- if (store === "file") {
38908
- import_node_fs9.default.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
38909
- import_node_fs9.default.chmodSync(dir, CACHE_DIR_MODE);
38910
- const target = filePath(owner);
38911
- const temp = `${target}.${String(process.pid)}.tmp`;
38912
- import_node_fs9.default.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
38913
- import_node_fs9.default.renameSync(temp, target);
38659
+ schemaVersion: USAGE_SCHEMA_VERSION,
38660
+ at: new Date(event.receivedAt).toISOString(),
38661
+ ...identity === void 0 ? {} : { identity },
38662
+ provider,
38663
+ ...sessionId === void 0 ? {} : { sessionId },
38664
+ ...event.clientSessionId === void 0 ? {} : { clientSessionId: event.clientSessionId },
38665
+ ...projectId === void 0 ? {} : { project: projectId },
38666
+ viaHeadroom: event.viaHeadroom,
38667
+ route: event.route,
38668
+ method: event.method,
38669
+ endpoint,
38670
+ ...scan?.model === void 0 ? {} : { model: scan.model },
38671
+ ...choice === void 0 ? {} : { requestedModel: choice.requested },
38672
+ ...choice !== void 0 && choice.providers.length > 1 ? { candidateProviders: [...choice.providers] } : {},
38673
+ status: event.status,
38674
+ latencyMs: Math.max(0, Math.round(context.headAt - event.receivedAt)),
38675
+ durationMs: Math.max(0, Math.round(context.endedAt - event.receivedAt)),
38676
+ outcome: context.outcome,
38677
+ ...scan?.usage === void 0 ? {} : { usage: scan.usage },
38678
+ ...rateLimitHeaders === void 0 ? {} : { rateLimitHeaders },
38679
+ ...limit === void 0 ? {} : { limit },
38680
+ ...requestId === void 0 ? {} : { requestId }
38681
+ };
38682
+ }
38683
+ function createUsageMiddleware(deps) {
38684
+ return (event) => {
38685
+ if (DOOR_ANSWERED_ROUTES.has(event.route)) {
38686
+ return void 0;
38687
+ }
38688
+ const headAt = deps.now();
38689
+ const scanner = scannerFor(event.headers);
38690
+ const decoder = scanner === void 0 ? null : decoderFor(headerValue(event.headers, "content-encoding"));
38691
+ let scanning = scanner !== void 0 && decoder !== void 0;
38692
+ const text5 = new TextDecoder();
38693
+ const feed = (bytes, final) => {
38694
+ if (!scanning || scanner === void 0) {
38914
38695
  return;
38915
38696
  }
38916
- requireKeychain();
38917
- const encoded = Buffer.from(serialised, "utf8").toString("base64");
38918
- const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
38919
- `);
38920
- if (result.status !== 0) {
38921
- throw new Error(`could not write the keychain credential cache for ${owner}`);
38922
- }
38923
- },
38924
- remove(store, owner) {
38925
- if (store === "file") {
38926
- import_node_fs9.default.rmSync(filePath(owner), { force: true });
38927
- return;
38697
+ try {
38698
+ scanner.push(text5.decode(bytes, { stream: !final }));
38699
+ } catch (error63) {
38700
+ scanning = false;
38701
+ deps.log(`usage: body scan failed for ${event.route}: ${error63 instanceof Error ? error63.message : String(error63)}`);
38928
38702
  }
38929
- requireKeychain();
38930
- security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
38931
- }
38932
- };
38933
- }
38934
- function realCredentialCacheEnv(paths) {
38935
- return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
38936
- }
38937
- function createAsyncRealCredentialCache(root) {
38938
- const dir = import_node_path29.default.join(root, "credential-cache");
38939
- const filePath = (owner) => import_node_path29.default.join(dir, cacheFileNameFor(owner));
38940
- return {
38941
- async read(store, owner) {
38942
- if (store === "file") {
38703
+ };
38704
+ const finish = (outcome) => {
38705
+ const endedAt = deps.now();
38706
+ deps.defer(() => {
38943
38707
  try {
38944
- return parseEntry(await import_node_fs9.default.promises.readFile(filePath(owner), "utf8"));
38945
- } catch {
38946
- return void 0;
38708
+ deps.record(buildRecord(event, { headAt, endedAt, outcome, scan: scanning && scanner !== void 0 ? scanner.result() : void 0 }), event);
38709
+ } catch (error63) {
38710
+ deps.log(`usage: could not record ${event.method} ${event.path} (${String(event.status)}): ${error63 instanceof Error ? error63.message : String(error63)}`);
38947
38711
  }
38948
- }
38949
- requireKeychain();
38950
- const result = await securityAsync(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
38951
- return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
38952
- },
38953
- async write(store, owner, entry) {
38954
- const serialised = JSON.stringify(entry);
38955
- if (store === "file") {
38956
- await import_node_fs9.default.promises.mkdir(dir, { recursive: true, mode: CACHE_DIR_MODE });
38957
- await import_node_fs9.default.promises.chmod(dir, CACHE_DIR_MODE);
38958
- const target = filePath(owner);
38959
- const temp = `${target}.${String(process.pid)}.tmp`;
38960
- await import_node_fs9.default.promises.writeFile(temp, serialised, { mode: CACHE_FILE_MODE });
38961
- await import_node_fs9.default.promises.rename(temp, target);
38712
+ });
38713
+ };
38714
+ if (decoder === null || decoder === void 0) {
38715
+ return {
38716
+ chunk: (bytes) => {
38717
+ feed(bytes, false);
38718
+ },
38719
+ end: (outcome) => {
38720
+ feed(new Uint8Array(), true);
38721
+ finish(outcome);
38722
+ }
38723
+ };
38724
+ }
38725
+ let ended;
38726
+ let settled = false;
38727
+ const settle2 = () => {
38728
+ if (settled || ended === void 0) {
38962
38729
  return;
38963
38730
  }
38964
- requireKeychain();
38965
- const encoded = Buffer.from(serialised, "utf8").toString("base64");
38966
- const result = await securityAsync(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
38967
- `);
38968
- if (result.status !== 0) {
38969
- throw new Error(`could not write the keychain credential cache for ${owner}`);
38731
+ settled = true;
38732
+ feed(new Uint8Array(), true);
38733
+ finish(ended);
38734
+ };
38735
+ decoder.on("data", (bytes) => {
38736
+ feed(bytes, false);
38737
+ });
38738
+ decoder.on("error", (error63) => {
38739
+ scanning = false;
38740
+ deps.log(`usage: could not decompress the body of ${event.route}: ${error63.message}`);
38741
+ settle2();
38742
+ });
38743
+ decoder.on("end", settle2);
38744
+ return {
38745
+ chunk: (bytes) => {
38746
+ if (scanning) {
38747
+ decoder.write(bytes);
38748
+ }
38749
+ },
38750
+ end: (outcome) => {
38751
+ ended = outcome;
38752
+ if (!scanning || outcome === "aborted") {
38753
+ decoder.destroy();
38754
+ settle2();
38755
+ return;
38756
+ }
38757
+ decoder.end();
38970
38758
  }
38971
- }
38759
+ };
38972
38760
  };
38973
38761
  }
38974
- function realAsyncCredentialCacheEnv(paths) {
38975
- return { port: createAsyncRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
38976
- }
38977
- var import_node_child_process4, import_node_fs9, import_node_path29, KEYCHAIN_CACHE_SERVICE, CACHE_DIR_MODE, CACHE_FILE_MODE;
38978
- var init_realCredentialCache = __esm({
38979
- "src/realCredentialCache.ts"() {
38762
+ var zlib, ANTHROPIC_PROVIDER, DOOR_ANSWERED_ROUTES;
38763
+ var init_middleware = __esm({
38764
+ "src/usage/middleware.ts"() {
38980
38765
  "use strict";
38981
- import_node_child_process4 = require("node:child_process");
38982
- import_node_fs9 = __toESM(require("node:fs"), 1);
38983
- import_node_path29 = __toESM(require("node:path"), 1);
38984
- init_blockingSpawn();
38985
- init_credentialCache();
38986
- KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
38987
- CACHE_DIR_MODE = 448;
38988
- CACHE_FILE_MODE = 384;
38766
+ zlib = __toESM(require("node:zlib"), 1);
38767
+ init_route();
38768
+ init_rateLimit();
38769
+ init_scan();
38770
+ init_schema2();
38771
+ ANTHROPIC_PROVIDER = "anthropic";
38772
+ DOOR_ANSWERED_ROUTES = /* @__PURE__ */ new Set(["(unauthorized)", "(unrouted)"]);
38989
38773
  }
38990
38774
  });
38991
38775
 
38992
- // src/providerAliases.ts
38993
- function parseAliases(file2, raw) {
38994
- const parsed = ProviderAliasesSchema.safeParse(raw);
38995
- if (!parsed.success) {
38996
- throw new ConfigValidationError(file2, parsed.error.issues);
38776
+ // src/usage/quotaFacts.ts
38777
+ function windowFacts(window, nowMs) {
38778
+ if (window?.utilization === void 0 || window.resetsAt === void 0) {
38779
+ return void 0;
38997
38780
  }
38998
- return parsed.data;
38999
- }
39000
- function readProviderAliases(paths) {
39001
- return readJson(paths.providerAliasesFile, ProviderAliasesSchema) ?? {};
38781
+ return { remaining: Math.max(0, 1 - window.utilization), utilization: window.utilization, hoursUntilReset: Math.max(0, (Date.parse(window.resetsAt) - nowMs) / MS_PER_HOUR) };
39002
38782
  }
39003
- function loadProviderAliases(file2, port) {
39004
- const raw = port.readConfigFile(file2);
39005
- return raw === void 0 ? {} : parseAliases(file2, raw);
38783
+ function quotaFactsOf(unified, nowMs) {
38784
+ const fiveHour = windowFacts(unified?.fiveHour, nowMs);
38785
+ const sevenDay = windowFacts(unified?.sevenDay, nowMs);
38786
+ const extraUsage = windowFacts(unified === void 0 ? void 0 : soleOverageWindow(unified), nowMs);
38787
+ return { ...fiveHour === void 0 ? {} : { fiveHour }, ...sevenDay === void 0 ? {} : { sevenDay }, ...extraUsage === void 0 ? {} : { extraUsage } };
39006
38788
  }
39007
- function writeProviderAliases(paths, aliases) {
39008
- if (Object.keys(aliases).length === 0) {
39009
- import_node_fs10.default.rmSync(paths.providerAliasesFile, { force: true });
39010
- return;
38789
+ var MS_PER_HOUR;
38790
+ var init_quotaFacts = __esm({
38791
+ "src/usage/quotaFacts.ts"() {
38792
+ "use strict";
38793
+ init_rateLimit();
38794
+ MS_PER_HOUR = 36e5;
39011
38795
  }
39012
- writeJsonAtomic(paths.providerAliasesFile, aliases);
38796
+ });
38797
+
38798
+ // src/usage/pick.ts
38799
+ function unifiedOf(state) {
38800
+ return state?.rateLimit?.unified;
39013
38801
  }
39014
- function canonicalProviderName(name, defined, aliases) {
39015
- if (defined(name)) {
39016
- return name;
38802
+ function limitBlockedUntil(state, nowMs) {
38803
+ const limit = state?.lastLimit;
38804
+ if (state === void 0 || limit === void 0) {
38805
+ return void 0;
39017
38806
  }
39018
- return aliases()[name] ?? name;
39019
- }
39020
- function aliasesAfterRename(aliases, from, to2) {
39021
- const next = {};
39022
- for (const [alias, target] of Object.entries(aliases)) {
39023
- if (alias !== to2) {
39024
- next[alias] = target === from ? to2 : target;
39025
- }
38807
+ if (state.lastRequestAt !== void 0 && Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
38808
+ return void 0;
39026
38809
  }
39027
- next[from] = to2;
39028
- return next;
38810
+ const until = limit.resetAt === void 0 ? limit.retryAfterSeconds === void 0 ? void 0 : Date.parse(limit.observedAt) + limit.retryAfterSeconds * MS_PER_SECOND5 : Date.parse(limit.resetAt);
38811
+ return until !== void 0 && until > nowMs ? until : void 0;
39029
38812
  }
39030
- function aliasesOf(aliases, name) {
39031
- return Object.entries(aliases).filter(([, target]) => target === name).map(([alias]) => alias).sort();
38813
+ function windowBlockedUntil(windows) {
38814
+ const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
38815
+ return resets.length === 0 ? void 0 : Math.max(...resets);
39032
38816
  }
39033
- function removeProviderAlias(paths, alias) {
39034
- const aliases = readProviderAliases(paths);
39035
- if (!(alias in aliases)) {
39036
- throw new ProviderAliasNotFoundError(alias);
38817
+ function windowBurn(records, which) {
38818
+ const points = [];
38819
+ for (const record2 of records) {
38820
+ const window = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders)?.[which];
38821
+ if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
38822
+ points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record2.at), utilization: window.utilization } });
38823
+ }
39037
38824
  }
39038
- writeProviderAliases(paths, Object.fromEntries(Object.entries(aliases).filter(([name]) => name !== alias)));
38825
+ const latest = points.at(-1)?.resetsAt;
38826
+ const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
38827
+ const first = current.at(0);
38828
+ const last = current.at(-1);
38829
+ if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
38830
+ return void 0;
38831
+ }
38832
+ return (last.utilization - first.utilization) / (last.atMs - first.atMs);
39039
38833
  }
39040
- function aliasFindings(aliases, providerNames) {
39041
- return Object.entries(aliases).map(([alias, target]) => {
39042
- if (!providerNames.has(target)) {
39043
- return { level: "fail", alias, message: `${alias} is recorded as a former name of ${target}, which is not a provider. Run \`agent-shim provider unalias ${alias}\`.` };
39044
- }
39045
- if (providerNames.has(alias)) {
39046
- return { level: "warn", alias, message: `${alias} is both a provider and a recorded former name of ${target}; the provider wins. Run \`agent-shim provider unalias ${alias}\`.` };
39047
- }
39048
- return { level: "pass", alias, message: `${alias} is a former name of ${target}, still honoured for sessions launched under it. Run \`agent-shim provider unalias ${alias}\` once none is running.` };
39049
- });
38834
+ function billedTokensOf(record2) {
38835
+ const usage = record2.usage;
38836
+ return (usage?.inputTokens ?? 0) + (usage?.cacheCreationInputTokens ?? 0) + (usage?.outputTokens ?? 0);
39050
38837
  }
39051
- function createProviderCanonicaliser(params) {
39052
- const reported = /* @__PURE__ */ new Set();
39053
- return (name) => {
39054
- const current = canonicalProviderName(name, params.defined, () => {
39055
- try {
39056
- return params.aliases();
39057
- } catch (error63) {
39058
- params.log(`provider aliases ignored, the aliases file is unreadable: ${error63 instanceof Error ? error63.message : String(error63)}`);
39059
- return {};
39060
- }
39061
- });
39062
- if (current !== name && !reported.has(name)) {
39063
- reported.add(name);
39064
- params.log(`provider ${name} is a former name of ${current}; a session launched under it is still running`);
38838
+ function utilisationPerBilledToken(records) {
38839
+ const points = [];
38840
+ for (const record2 of records) {
38841
+ const window = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders)?.fiveHour;
38842
+ if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
38843
+ points.push({ resetsAt: window.resetsAt, utilization: window.utilization, billed: billedTokensOf(record2) });
39065
38844
  }
39066
- return current;
39067
- };
39068
- }
39069
- var import_node_fs10, ProviderAliasesSchema, ProviderAliasNotFoundError;
39070
- var init_providerAliases = __esm({
39071
- "src/providerAliases.ts"() {
39072
- "use strict";
39073
- import_node_fs10 = __toESM(require("node:fs"), 1);
39074
- init_zod();
39075
- init_cliError();
39076
- init_load();
39077
- init_store();
39078
- ProviderAliasesSchema = external_exports.record(external_exports.string().min(1), external_exports.string().min(1));
39079
- ProviderAliasNotFoundError = class extends CliError {
39080
- constructor(alias) {
39081
- super(`"${alias}" is not a former provider name. \`agent-shim provider list\` shows the ones recorded.`);
39082
- this.alias = alias;
39083
- this.name = "ProviderAliasNotFoundError";
39084
- }
39085
- alias;
39086
- };
39087
38845
  }
39088
- });
39089
-
39090
- // src/models/identity.ts
39091
- function withoutContextMarker(model) {
39092
- return model.toLowerCase().endsWith(CONTEXT_MARKER_1M) ? model.slice(0, -CONTEXT_MARKER_1M.length) : model;
39093
- }
39094
- function canonicalText(name) {
39095
- return name.trim().toLowerCase().replace(NON_IDENTITY_BRACKET_RE, "").replace(/[\s_]+/g, "-").replace(/([a-z])(\d+(?:\.\d+)+)/g, "$1-$2").replace(/-+/g, "-").replace(/^-|-$/g, "");
38846
+ const latest = points.at(-1)?.resetsAt;
38847
+ const current = points.filter((point) => point.resetsAt === latest);
38848
+ const first = current.at(0);
38849
+ const last = current.at(-1);
38850
+ if (first === void 0 || last === void 0 || last.utilization <= first.utilization) {
38851
+ return void 0;
38852
+ }
38853
+ const billed = current.slice(1).reduce((sum2, point) => sum2 + point.billed, 0);
38854
+ return billed > 0 ? (last.utilization - first.utilization) / billed : void 0;
39096
38855
  }
39097
- function splitVariants(tokens) {
39098
- const variants2 = [];
39099
- let end = tokens.length;
39100
- for (; ; ) {
39101
- const token = tokens[end - 1];
39102
- if (token === void 0 || !KNOWN_VARIANTS.has(token)) {
39103
- return { familyTokens: tokens.slice(0, end), variants: variants2 };
38856
+ function windowRatio(records) {
38857
+ const points = [];
38858
+ for (const record2 of records) {
38859
+ const unified = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders);
38860
+ const five = unified?.fiveHour;
38861
+ const seven = unified?.sevenDay;
38862
+ if (five?.utilization !== void 0 && five.resetsAt !== void 0 && seven?.utilization !== void 0 && seven.resetsAt !== void 0) {
38863
+ points.push({ fiveResetsAt: five.resetsAt, sevenResetsAt: seven.resetsAt, five: five.utilization, seven: seven.utilization });
39104
38864
  }
39105
- variants2.unshift(token);
39106
- end -= 1;
39107
38865
  }
38866
+ const latest = points.at(-1);
38867
+ const current = points.filter((point) => point.fiveResetsAt === latest?.fiveResetsAt && point.sevenResetsAt === latest.sevenResetsAt);
38868
+ const first = current.at(0);
38869
+ const last = current.at(-1);
38870
+ if (first === void 0 || last === void 0 || last.five <= first.five || last.seven <= first.seven) {
38871
+ return void 0;
38872
+ }
38873
+ return (last.seven - first.seven) / (last.five - first.five);
39108
38874
  }
39109
- function versionOf(tokens) {
39110
- for (let index = 0; index < tokens.length; index += 1) {
39111
- const token = tokens[index];
39112
- const next = tokens[index + 1];
39113
- if (token === void 0) {
39114
- return "";
39115
- }
39116
- if (/^\d+$/.test(token) && next !== void 0 && /^\d+$/.test(next)) {
39117
- return `${token}.${next}`;
39118
- }
39119
- const dotted = /^\d+(?:\.\d+)+$/.exec(token);
39120
- if (dotted !== null) {
39121
- return dotted[0];
39122
- }
38875
+ function percent(fraction) {
38876
+ return `${String(Math.round(fraction * PERCENT))}%`;
38877
+ }
38878
+ function describeWindow(label2, window, effective, nowMs) {
38879
+ if (effective.reset) {
38880
+ return `${label2} window has reset`;
39123
38881
  }
39124
- return "";
38882
+ const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
38883
+ const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
38884
+ return `${label2} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
39125
38885
  }
39126
- function modelIdentity(rawName) {
39127
- const segments = rawName.trim().toLowerCase().split("/");
39128
- const stripped = canonicalText(segments[segments.length - 1] ?? "").split(":")[0] ?? "";
39129
- const withoutDate = stripped.replace(DATE_STAMP_RE, "");
39130
- const tokens = withoutDate.split("-").filter((token) => token !== "");
39131
- const { familyTokens, variants: variants2 } = splitVariants(tokens);
39132
- const version2 = versionOf(familyTokens);
39133
- const versionless = familyTokens.filter((token, index) => {
39134
- if (version2 === "") {
39135
- return true;
39136
- }
39137
- const dotted = /^\d+(?:\.\d+)+$/.test(token);
39138
- const previous = familyTokens[index - 1];
39139
- const next = familyTokens[index + 1];
39140
- const insideVersionRun = dotted || /^\d+$/.test(token) && (previous !== void 0 && /^\d+$/.test(previous) || next !== void 0 && /^\d+$/.test(next));
39141
- return !insideVersionRun;
39142
- });
39143
- const family = versionless.join("-");
39144
- const tokenVendor = familyTokens.find((token) => Object.keys(VENDOR_BY_TOKEN).some((key) => token === key || token.startsWith(key)));
39145
- const namespace = segments.length >= 2 ? (segments[0] ?? "").split(":").pop() ?? "" : "";
39146
- const namespaceVendor = VENDOR_BY_NAMESPACE[namespace] ?? "";
39147
- const tokenVendorName = tokenVendor === void 0 ? "" : VENDOR_BY_TOKEN[Object.keys(VENDOR_BY_TOKEN).find((key) => tokenVendor === key || tokenVendor.startsWith(key)) ?? ""] ?? "";
38886
+ function capacityOf(plan) {
38887
+ return plan.kind === "subscription" ? plan.capacity : 1;
38888
+ }
38889
+ function assessRefusedNested(member, nested, nowMs) {
38890
+ const refusal2 = nested.earliestReturn === void 0 ? { reason: `no member of pool "${nested.pool}" can be picked` } : { reason: `every member of pool "${nested.pool}" is refused; ${nested.earliestReturn.identity} returns at ${new Date(nested.earliestReturn.atMs).toISOString()} (in ${formatAge(nested.earliestReturn.atMs - nowMs)})`, blockedUntilMs: nested.earliestReturn.atMs };
39148
38891
  return {
39149
- vendor: tokenVendorName === "" ? namespaceVendor : tokenVendorName,
39150
- vendorConflict: tokenVendorName !== "" && namespaceVendor !== "" && tokenVendorName !== namespaceVendor,
39151
- family,
39152
- version: version2,
39153
- variant: variants2.join("-"),
39154
- familyAndVersion: version2 === "" ? family : `${family}-${version2.replace(/\./g, "-")}`
38892
+ candidate: {
38893
+ identity: member.identity,
38894
+ class: "ineligible",
38895
+ ...refusal2.blockedUntilMs === void 0 ? {} : { blockedUntilMs: refusal2.blockedUntilMs },
38896
+ plan: planOf(member.account),
38897
+ reasons: [refusal2.reason]
38898
+ }
39155
38899
  };
39156
38900
  }
39157
- function relateModels(left, right) {
39158
- const a10 = modelIdentity(left);
39159
- const b = modelIdentity(right);
39160
- if (a10.familyAndVersion !== b.familyAndVersion) {
39161
- return sameFamilyOtherVersion(a10, b) ? "similar" : "distinct";
38901
+ function assess(member, nowMs) {
38902
+ const nested = member.nested;
38903
+ if (nested === void 0) {
38904
+ return assessIdentity(member, nowMs);
39162
38905
  }
39163
- if (a10.variant !== b.variant || a10.vendorConflict || b.vendorConflict || a10.vendor !== "" && b.vendor !== "" && a10.vendor !== b.vendor) {
39164
- return "similar";
38906
+ if (nested.kind === "refused") {
38907
+ return assessRefusedNested(member, nested, nowMs);
39165
38908
  }
39166
- return "same";
38909
+ const assessment = assessIdentity(member, nowMs);
38910
+ return { ...assessment, candidate: { ...assessment.candidate, reasons: [`picked by pool "${nested.pool}"`, ...nested.reasons] } };
39167
38911
  }
39168
- function sameFamilyOtherVersion(a10, b) {
39169
- if (a10.family !== b.family) {
39170
- return false;
38912
+ function assessIdentity(member, nowMs) {
38913
+ const plan = planOf(member.account);
38914
+ const planReasons = plan.kind === "pay-per-use" ? ["billed by use (no plan allowance)"] : plan.recognised ? [`${String(plan.capacity)}x plan`] : [`plan tier ${plan.tier === void 0 ? "not known" : `"${plan.tier}" not recognised`}, counted as 1x`];
38915
+ const base = { identity: member.identity, plan };
38916
+ if (member.readError !== void 0) {
38917
+ return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
39171
38918
  }
39172
- if (a10.vendor !== "" && b.vendor !== "" && a10.vendor !== b.vendor) {
39173
- return false;
38919
+ const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
38920
+ const unified = unifiedOf(state);
38921
+ if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
38922
+ return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
39174
38923
  }
39175
- return true;
38924
+ const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
38925
+ const fiveHourWindow = unified.fiveHour;
38926
+ const sevenDayWindow = unified.sevenDay;
38927
+ const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
38928
+ const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
38929
+ const windowReasons = [
38930
+ ...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
38931
+ ...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
38932
+ ];
38933
+ const overageWindow = soleOverageWindow(unified);
38934
+ const overage = overageWindow === void 0 ? void 0 : effectiveWindow(overageWindow, nowMs);
38935
+ const overageReasons = overageWindow === void 0 || overage === void 0 ? [] : [describeWindow("extra usage", overageWindow, overage, nowMs)];
38936
+ const blockedUntil = Math.max(
38937
+ windowBlockedUntil([five, seven, overage].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
38938
+ limitBlockedUntil(state, nowMs) ?? 0
38939
+ );
38940
+ if (blockedUntil > nowMs) {
38941
+ if (unified.overageStatus === "allowed") {
38942
+ return { candidate: { ...base, class: "pay-per-use", planReturnsAtMs: blockedUntil, reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)}), continues as extra usage`, ...windowReasons, ...planReasons] } };
38943
+ }
38944
+ return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...unified.fallbackAvailable === true ? ["a fallback is on offer, so requests for some models may still be served"] : [], ...windowReasons, ...overageReasons, ...planReasons] } };
38945
+ }
38946
+ if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
38947
+ const headroom = overage?.utilization === void 0 ? void 0 : Math.max(0, 1 - overage.utilization);
38948
+ return { candidate: { ...base, class: "pay-per-use", ...headroom === void 0 ? {} : { headroom }, reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"], ...overageReasons] } };
38949
+ }
38950
+ const basis = seven?.utilization !== void 0 ? { window: seven, windowMs: SEVEN_DAY_WINDOW_MS, name: "sevenDay" } : five?.utilization !== void 0 ? { window: five, windowMs: FIVE_HOUR_WINDOW_MS, name: "fiveHour" } : void 0;
38951
+ if (basis?.window.utilization === void 0) {
38952
+ return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
38953
+ }
38954
+ const remaining = Math.max(0, 1 - basis.window.utilization);
38955
+ const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND5);
38956
+ const weeklyScore = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR2);
38957
+ const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
38958
+ const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND5);
38959
+ const ratio = windowRatio(member.records);
38960
+ const fiveHourScore = basis.name === "sevenDay" && fiveRemaining !== void 0 && ratio !== void 0 ? Math.min(fiveRemaining * ratio, remaining) * plan.capacity / (fiveUntil / MS_PER_HOUR2) : void 0;
38961
+ const fiveHourLeads = fiveHourScore !== void 0 && fiveHourScore > weeklyScore;
38962
+ const score = fiveHourLeads ? fiveHourScore : weeklyScore;
38963
+ const scoreWindow = fiveHourLeads ? "fiveHour" : basis.name;
38964
+ const fiveHourTermReasons = basis.name !== "sevenDay" || fiveRemaining === void 0 ? [] : ratio === void 0 ? ["5h reset not scored: no measured 5h to 7d ratio yet"] : fiveHourLeads ? [`5h term leads: ${percent(fiveRemaining)} of 5h unused is about ${percent(Math.min(fiveRemaining * ratio, remaining))} of the week at the measured ratio, resets in ${formatAge(fiveUntil)}`] : [];
38965
+ const burn = windowBurn(member.records, "fiveHour");
38966
+ const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
38967
+ const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND5);
38968
+ const sevenDayBurn = windowBurn(member.records, "sevenDay");
38969
+ return {
38970
+ candidate: { ...base, class: "scored", score, scoreWindow, reasons: [...fiveHourTermReasons, ...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
38971
+ ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
38972
+ ...burn === void 0 ? {} : { burn },
38973
+ ...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
38974
+ ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
38975
+ };
39176
38976
  }
39177
- var NON_IDENTITY_BRACKET_RE, CONTEXT_MARKER_1M, DATE_STAMP_RE, KNOWN_VARIANTS, VENDOR_BY_NAMESPACE, VENDOR_BY_TOKEN;
39178
- var init_identity2 = __esm({
39179
- "src/models/identity.ts"() {
39180
- "use strict";
39181
- NON_IDENTITY_BRACKET_RE = /\[[^\]]*\]/g;
39182
- CONTEXT_MARKER_1M = "[1m]";
39183
- DATE_STAMP_RE = /-(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])$/;
39184
- KNOWN_VARIANTS = /* @__PURE__ */ new Set(["high", "medium", "low", "thinking", "xhigh", "max", "highspeed", "sol", "terra", "luna", "mini", "flash-lite", "turbo"]);
39185
- VENDOR_BY_NAMESPACE = Object.freeze({
39186
- anthropic: "anthropic",
39187
- google: "google",
39188
- openai: "openai",
39189
- zai: "zai",
39190
- "z-ai": "zai",
39191
- minimax: "minimax",
39192
- qwen: "qwen",
39193
- deepseek: "deepseek",
39194
- moonshot: "moonshot",
39195
- meta: "meta",
39196
- mistral: "mistral"
39197
- });
39198
- VENDOR_BY_TOKEN = Object.freeze({
39199
- claude: "anthropic",
39200
- gemini: "google",
39201
- antigravity: "google",
39202
- gpt: "openai",
39203
- glm: "zai",
39204
- minimax: "minimax",
39205
- qwen: "qwen",
39206
- deepseek: "deepseek",
39207
- kimi: "moonshot",
39208
- llama: "meta",
39209
- mistral: "mistral",
39210
- mixtral: "mistral"
39211
- });
38977
+ function poolFactsOf(member, nowMs, resuming, sticky) {
38978
+ const { fiveHour, sevenDay, extraUsage } = quotaFactsOf(member.snapshot?.providers[ANTHROPIC_PROVIDER]?.rateLimit?.unified, nowMs);
38979
+ const burn = windowBurn(member.records, "fiveHour");
38980
+ return {
38981
+ ...fiveHour === void 0 ? {} : { fiveHour },
38982
+ ...sevenDay === void 0 ? {} : { sevenDay },
38983
+ ...extraUsage === void 0 ? {} : { extraUsage },
38984
+ ...burn === void 0 ? {} : { burnPerHour: burn * MS_PER_HOUR2 },
38985
+ resuming,
38986
+ ...sticky?.identity === member.identity ? { lastPickHoursAgo: Math.max(0, (nowMs - Date.parse(sticky.at)) / MS_PER_HOUR2) } : {}
38987
+ };
38988
+ }
38989
+ function rankPool(input2) {
38990
+ const { members: members2, nowMs } = input2;
38991
+ const assessments = members2.map((member) => ({ member, ...assess(member, nowMs) }));
38992
+ const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
38993
+ const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
38994
+ const guarded2 = assessments.map((assessment) => {
38995
+ const { candidate } = assessment;
38996
+ if (candidate.class === "ineligible" || assessment.member.policy === void 0) {
38997
+ return assessment;
38998
+ }
38999
+ const context = { nowMs, env: {}, pool: poolFactsOf(assessment.member, nowMs, input2.resuming, input2.sticky) };
39000
+ const verdict = evaluateWhen(assessment.member.policy, context);
39001
+ if (verdict.status === "indeterminate") {
39002
+ return { ...assessment, candidate: { ...candidate, policyDemoted: true, reasons: [`policy undecided (${verdict.checked.join(", ") === "predicate" ? "predicate" : verdict.checked.join(", ")}): ${verdict.reason}`, ...candidate.reasons] } };
39003
+ }
39004
+ if (verdict.passed) {
39005
+ return assessment;
39006
+ }
39007
+ return { ...assessment, candidate: { ...candidate, class: "ineligible", reasons: [`skipped by policy (${verdict.checked.join(", ")}: did not hold)`, ...candidate.reasons] } };
39008
+ });
39009
+ const candidates = guarded2.map((assessment) => {
39010
+ const { candidate, fiveHour, burn } = assessment;
39011
+ const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
39012
+ const { sevenDay, sevenDayBurn } = assessment;
39013
+ if (candidate.class !== "scored") {
39014
+ return { ...candidate, feasible: true };
39015
+ }
39016
+ const drySpots = [];
39017
+ const dryAfterMs = [];
39018
+ if (fiveHour !== void 0 && pace !== void 0) {
39019
+ const runsDryInMs = fiveHour.remaining / pace;
39020
+ if (runsDryInMs < fiveHour.untilResetMs) {
39021
+ dryAfterMs.push(runsDryInMs);
39022
+ drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
39023
+ }
39024
+ }
39025
+ if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
39026
+ const runsDryInMs = sevenDay.remaining / sevenDayBurn;
39027
+ if (runsDryInMs < sevenDay.untilResetMs) {
39028
+ dryAfterMs.push(runsDryInMs);
39029
+ drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
39030
+ }
39031
+ }
39032
+ const { sticky: sticky2 } = input2;
39033
+ const warmHere = sticky2?.identity === candidate.identity && nowMs - Date.parse(sticky2.at) <= PROMPT_CACHE_TTL_MS;
39034
+ if (input2.resuming && input2.resumeContextTokens !== void 0 && fiveHour !== void 0 && !warmHere) {
39035
+ const perToken = utilisationPerBilledToken(assessment.member.records);
39036
+ const committed = input2.committedContextTokens?.[candidate.identity] ?? 0;
39037
+ const cost = perToken === void 0 ? void 0 : (input2.resumeContextTokens + committed) * perToken;
39038
+ if (cost !== void 0 && cost > fiveHour.remaining) {
39039
+ dryAfterMs.push(0);
39040
+ const alongside = committed === 0 ? "" : ` (with ${String(committed)} tokens of other conversations already moving here)`;
39041
+ drySpots.push(`resuming a ${String(input2.resumeContextTokens)}-token conversation here${alongside} would cost about ${percent(cost)} of the 5h window, which has ${percent(fiveHour.remaining)} left`);
39042
+ }
39043
+ }
39044
+ if (drySpots.length === 0) {
39045
+ return { ...candidate, feasible: true };
39046
+ }
39047
+ return { ...candidate, feasible: false, runsDryAtMs: nowMs + Math.min(...dryAfterMs), reasons: [...drySpots, ...candidate.reasons] };
39048
+ });
39049
+ const ordered = input2.preference === "listed" ? [...candidates] : [...candidates].sort(
39050
+ (left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(left.policyDemoted ?? false) - Number(right.policyDemoted ?? false) || Number(right.feasible) - Number(left.feasible) || (right.headroom ?? 1) - (left.headroom ?? 1) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
39051
+ );
39052
+ const sticky = input2.sticky;
39053
+ const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
39054
+ const stickyCandidate = ordered[stickyIndex];
39055
+ const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
39056
+ if (sticky !== void 0 && keepSticky) {
39057
+ const ageMs = nowMs - Date.parse(sticky.at);
39058
+ const warm = ageMs <= PROMPT_CACHE_TTL_MS;
39059
+ if (input2.resuming || warm) {
39060
+ const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
39061
+ ordered.splice(stickyIndex, 1);
39062
+ ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
39063
+ }
39064
+ }
39065
+ const movedOff = sticky !== void 0 && input2.resuming && !keepSticky ? { identity: sticky.identity, reason: stickyCandidate?.reasons[0] ?? "no longer among the pool's members" } : void 0;
39066
+ const pick2 = ordered.find((candidate) => candidate.class !== "ineligible");
39067
+ const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
39068
+ const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
39069
+ const holdForReturn = pick2?.runsDryAtMs === void 0 ? void 0 : returns.filter((entry) => entry.atMs < (pick2.runsDryAtMs ?? 0)).reduce((soonest, entry) => soonest === void 0 || entry.atMs < soonest.atMs ? entry : soonest, void 0);
39070
+ return {
39071
+ candidates: ordered,
39072
+ ...pick2 === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick: pick2 },
39073
+ ...movedOff === void 0 || pick2 === void 0 ? {} : { movedOff },
39074
+ ...pick2?.planReturnsAtMs === void 0 ? {} : { planReturn: { identity: pick2.identity, atMs: pick2.planReturnsAtMs } },
39075
+ ...holdForReturn === void 0 ? {} : { holdForReturn }
39076
+ };
39077
+ }
39078
+ var MS_PER_SECOND5, MS_PER_HOUR2, HOURS_PER_DAY2, FIVE_HOURS, PROMPT_CACHE_TTL_HOURS, PERCENT, SEVEN_DAYS, FIVE_HOUR_WINDOW_MS, SEVEN_DAY_WINDOW_MS, PROMPT_CACHE_TTL_MS, CLASS_ORDER;
39079
+ var init_pick = __esm({
39080
+ "src/usage/pick.ts"() {
39081
+ "use strict";
39082
+ init_conditions();
39083
+ init_plan2();
39084
+ init_middleware();
39085
+ init_preflight();
39086
+ init_quotaFacts();
39087
+ init_rateLimit();
39088
+ MS_PER_SECOND5 = 1e3;
39089
+ MS_PER_HOUR2 = 36e5;
39090
+ HOURS_PER_DAY2 = 24;
39091
+ FIVE_HOURS = 5;
39092
+ PROMPT_CACHE_TTL_HOURS = 1;
39093
+ PERCENT = 100;
39094
+ SEVEN_DAYS = 7;
39095
+ FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR2;
39096
+ SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR2;
39097
+ PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR2;
39098
+ CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
39212
39099
  }
39213
39100
  });
39214
39101
 
39215
- // src/models/families.ts
39216
- function modelOf(ref) {
39217
- const slash = ref.indexOf("/");
39218
- return slash === -1 ? ref : ref.slice(slash + 1);
39219
- }
39220
- function familyKeyOf(rawModel) {
39221
- const identity = modelIdentity(rawModel);
39222
- return identity.variant === "" ? identity.familyAndVersion : `${identity.familyAndVersion}${VARIANT_SEPARATOR}${identity.variant}`;
39223
- }
39224
- function assertionsOf(models) {
39225
- return { same: models?.same ?? [], distinct: models?.distinct ?? [] };
39102
+ // src/usage/read.ts
39103
+ function segmentDay(epochMs) {
39104
+ return new Date(epochMs).toISOString().slice(0, "YYYY-MM-DD".length);
39226
39105
  }
39227
- function sameModel(left, right, assertions) {
39228
- if (assertions.distinct.some(([a10, b]) => a10 === left && b === right || a10 === right && b === left)) {
39229
- return false;
39230
- }
39231
- const leftGroup = assertions.same.find((group) => group.includes(left));
39232
- const rightGroup = assertions.same.find((group) => group.includes(right));
39233
- if (leftGroup !== void 0 && leftGroup === rightGroup) {
39234
- return true;
39235
- }
39236
- if (leftGroup !== void 0 && !leftGroup.includes(right) || rightGroup !== void 0 && !rightGroup.includes(left)) {
39237
- return false;
39238
- }
39239
- return relateModels(modelOf(left), modelOf(right)) === "same";
39106
+ function segmentName(day, pid) {
39107
+ return `${day}.${String(pid)}.jsonl`;
39240
39108
  }
39241
- function pinnedTo(settings2, ref) {
39242
- return settings2?.names?.some((name) => name === ref || name === modelOf(ref)) === true;
39109
+ function listLogSegments(fs31, logDir) {
39110
+ return fs31.readdir(logDir).flatMap((name) => {
39111
+ const match = SEGMENT_RE.exec(name);
39112
+ const day = match?.[1];
39113
+ if (day === void 0) {
39114
+ return [];
39115
+ }
39116
+ return [{ name, day, endsAt: Date.parse(`${day}T00:00:00.000Z`) + DAY_MS }];
39117
+ }).sort((left, right) => left.day.localeCompare(right.day) || left.name.localeCompare(right.name));
39243
39118
  }
39244
- function buildFamilies(refs, models) {
39245
- const assertions = assertionsOf(models);
39246
- const settingsByKey = models?.families ?? {};
39247
- const groups = [];
39248
- for (const ref of refs) {
39249
- const pinnedKey = Object.entries(settingsByKey).find(([, settings2]) => pinnedTo(settings2, ref))?.[0];
39250
- const joined3 = groups.find((group) => pinnedKey === void 0 ? group.refs.some((first) => sameModel(first, ref, assertions)) : group.key === pinnedKey);
39251
- if (joined3 !== void 0) {
39252
- joined3.refs.push(ref);
39253
- } else {
39254
- groups.push({ key: pinnedKey ?? familyKeyOf(modelOf(ref)), refs: [ref] });
39119
+ function readUsageLog(fs31, logDir, options = {}) {
39120
+ const { sinceMs } = options;
39121
+ const records = [];
39122
+ let invalidLines = 0;
39123
+ for (const segment of listLogSegments(fs31, logDir)) {
39124
+ if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
39125
+ continue;
39126
+ }
39127
+ const read = readSegment(fs31, logDir, segment);
39128
+ if (read === void 0) {
39129
+ continue;
39130
+ }
39131
+ invalidLines += read.invalidLines;
39132
+ for (const record2 of read.records) {
39133
+ if (sinceMs === void 0 || Date.parse(record2.at) >= sinceMs) {
39134
+ records.push(record2);
39135
+ }
39255
39136
  }
39256
39137
  }
39257
- return groups.map((group) => ({ key: group.key, refs: group.refs, settings: settingsByKey[group.key] ?? {} }));
39138
+ records.sort((left, right) => left.at.localeCompare(right.at));
39139
+ return { records, invalidLines };
39258
39140
  }
39259
- function familyNamed(families, member) {
39260
- if (!member.startsWith(FAMILY_PREFIX)) {
39141
+ function readSegment(fs31, logDir, segment) {
39142
+ const contents = fs31.readFileUtf8(import_node_path26.default.join(logDir, segment.name));
39143
+ if (contents === void 0) {
39261
39144
  return void 0;
39262
39145
  }
39263
- const key = member.slice(FAMILY_PREFIX.length);
39264
- return families.find((family) => family.key === key);
39265
- }
39266
- function familyCapabilities(settings2) {
39267
- return { vision: settings2.vision ?? true, tools: settings2.tools ?? true, thinking: settings2.thinking ?? true };
39268
- }
39269
- function familyMode(settings2) {
39270
- return settings2.mode ?? "ordered";
39271
- }
39272
- function sharedFamilies(families) {
39273
- return families.filter((family) => family.refs.length > 1);
39274
- }
39275
- function expandFamilyMembers(members2, families) {
39276
- const expanded = [];
39277
- for (const member of members2) {
39278
- for (const ref of member.startsWith(FAMILY_PREFIX) ? familyNamed(families, member)?.refs ?? [] : [member]) {
39279
- if (!expanded.includes(ref)) {
39280
- expanded.push(ref);
39281
- }
39146
+ const records = [];
39147
+ let invalidLines = 0;
39148
+ const lines = contents.split("\n");
39149
+ lines.pop();
39150
+ for (const line of lines) {
39151
+ if (line === "") {
39152
+ continue;
39153
+ }
39154
+ let raw;
39155
+ try {
39156
+ raw = JSON.parse(line);
39157
+ } catch {
39158
+ invalidLines += 1;
39159
+ continue;
39160
+ }
39161
+ const parsed = UsageRecordSchema.safeParse(raw);
39162
+ if (!parsed.success) {
39163
+ invalidLines += 1;
39164
+ continue;
39282
39165
  }
39166
+ records.push(parsed.data);
39283
39167
  }
39284
- return expanded;
39285
- }
39286
- function familyOfRef(families, ref) {
39287
- return families.find((family) => family.refs.includes(ref));
39288
- }
39289
- function accountRefsOf(models) {
39290
- const named = [...(models?.same ?? []).flat(), ...Object.values(models?.families ?? {}).flatMap((settings2) => settings2.names ?? [])];
39291
- return [...new Set(named.filter((ref) => ref.startsWith("acct:")))];
39168
+ return { records, invalidLines };
39292
39169
  }
39293
- var FAMILY_PREFIX, VARIANT_SEPARATOR;
39294
- var init_families = __esm({
39295
- "src/models/families.ts"() {
39296
- "use strict";
39297
- init_identity2();
39298
- FAMILY_PREFIX = "family:";
39299
- VARIANT_SEPARATOR = "+";
39170
+ function readRecentUsageRecords(fs31, logDir, count2) {
39171
+ const byDay = /* @__PURE__ */ new Map();
39172
+ for (const segment of listLogSegments(fs31, logDir)) {
39173
+ byDay.set(segment.day, [...byDay.get(segment.day) ?? [], segment]);
39300
39174
  }
39301
- });
39302
-
39303
- // src/providersStore.ts
39304
- function providerJsonPath(paths, name) {
39305
- return providerFilePath(paths.providersDir, name);
39306
- }
39307
- function providerFileDefined(lstat, providersDir, name) {
39308
- return isStoredName(name) && lstat(providerFilePath(providersDir, name)) !== void 0;
39309
- }
39310
- function providerExists(paths, name) {
39311
- return import_node_fs11.default.existsSync(providerJsonPath(paths, name));
39312
- }
39313
- function isRecord4(value2) {
39314
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
39175
+ const days = [...byDay.values()];
39176
+ let records = [];
39177
+ for (let index = days.length - 1; index >= 0; index -= 1) {
39178
+ const dayRecords = [];
39179
+ for (const segment of days[index] ?? []) {
39180
+ dayRecords.push(...readSegment(fs31, logDir, segment)?.records ?? []);
39181
+ }
39182
+ records = [...dayRecords, ...records];
39183
+ records.sort((left, right) => left.at.localeCompare(right.at));
39184
+ const olderDay = days[index - 1]?.[0];
39185
+ const newestHeld = records[records.length - count2];
39186
+ if (olderDay === void 0 || newestHeld !== void 0 && Date.parse(newestHeld.at) >= olderDay.endsAt) {
39187
+ break;
39188
+ }
39189
+ }
39190
+ return records.slice(-count2);
39315
39191
  }
39316
- function legacyProviderConversion(raw) {
39317
- if (!isRecord4(raw) || "credential" in raw) {
39318
- return void 0;
39192
+ function snapshotPath(snapshotsDir, identity) {
39193
+ if (!isIdentityName(identity)) {
39194
+ throw new Error(`"${identity}" is not a valid identity name.`);
39319
39195
  }
39320
- const env = isRecord4(raw.env) ? raw.env : void 0;
39321
- const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
39322
- const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
39323
- if (fields.length === 0) {
39196
+ return import_node_path26.default.join(snapshotsDir, `${identity}.json`);
39197
+ }
39198
+ function readUsageSnapshot(fs31, snapshotsDir, identity) {
39199
+ const file2 = snapshotPath(snapshotsDir, identity);
39200
+ const raw = fs31.readFileUtf8(file2);
39201
+ if (raw === void 0) {
39324
39202
  return void 0;
39325
39203
  }
39326
- const sources = [];
39327
- if (typeof raw.tokenEnv === "string") {
39328
- sources.push({ env: raw.tokenEnv });
39204
+ let parsed;
39205
+ try {
39206
+ parsed = JSON.parse(raw);
39207
+ } catch {
39208
+ throw new UsageSnapshotError(file2, "not valid JSON");
39329
39209
  }
39330
- if (Array.isArray(raw.tokenCommand)) {
39331
- sources.push({ command: raw.tokenCommand });
39210
+ const snapshot = UsageSnapshotSchema.safeParse(parsed);
39211
+ if (!snapshot.success) {
39212
+ throw new UsageSnapshotError(file2, snapshot.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; "));
39332
39213
  }
39333
- if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
39334
- sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
39214
+ return snapshot.data;
39215
+ }
39216
+ function listUsageSnapshots(fs31, snapshotsDir) {
39217
+ return fs31.readdir(snapshotsDir).flatMap((name) => {
39218
+ if (!name.endsWith(".json")) {
39219
+ return [];
39220
+ }
39221
+ const identity = name.slice(0, -".json".length);
39222
+ if (!isIdentityName(identity)) {
39223
+ return [];
39224
+ }
39225
+ const snapshot = readUsageSnapshot(fs31, snapshotsDir, identity);
39226
+ return snapshot === void 0 ? [] : [snapshot];
39227
+ }).sort((left, right) => left.identity.localeCompare(right.identity));
39228
+ }
39229
+ function summariseUsage(records) {
39230
+ const groups = /* @__PURE__ */ new Map();
39231
+ for (const record2 of records) {
39232
+ const key = `${record2.identity ?? ""}\0${record2.provider}`;
39233
+ const group = groups.get(key);
39234
+ if (group === void 0) {
39235
+ groups.set(key, [record2]);
39236
+ } else {
39237
+ group.push(record2);
39238
+ }
39335
39239
  }
39336
- const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
39337
- const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
39338
- const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
39339
- return {
39340
- fields,
39341
- replacement: {
39342
- ...rest,
39343
- credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
39344
- ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
39240
+ const summaries = [...groups.values()].flatMap((group) => {
39241
+ const [first] = group;
39242
+ const last = group.at(-1);
39243
+ if (first === void 0 || last === void 0) {
39244
+ return [];
39345
39245
  }
39346
- };
39246
+ const modelCounts = /* @__PURE__ */ new Map();
39247
+ for (const record2 of group) {
39248
+ if (record2.model !== void 0) {
39249
+ modelCounts.set(record2.model, (modelCounts.get(record2.model) ?? 0) + 1);
39250
+ }
39251
+ }
39252
+ const sum2 = (field) => group.reduce((total, record2) => total + (record2.usage?.[field] ?? 0), 0);
39253
+ return [
39254
+ {
39255
+ identity: first.identity,
39256
+ provider: first.provider,
39257
+ requests: group.length,
39258
+ failed: group.filter((record2) => record2.status >= FIRST_ERROR_STATUS).length,
39259
+ limits: {
39260
+ "rate-limited": group.filter((record2) => record2.limit?.kind === "rate-limited").length,
39261
+ "quota-exhausted": group.filter((record2) => record2.limit?.kind === "quota-exhausted").length
39262
+ },
39263
+ tokens: {
39264
+ inputTokens: sum2("inputTokens"),
39265
+ outputTokens: sum2("outputTokens"),
39266
+ cacheCreationInputTokens: sum2("cacheCreationInputTokens"),
39267
+ cacheReadInputTokens: sum2("cacheReadInputTokens")
39268
+ },
39269
+ firstAt: first.at,
39270
+ lastAt: last.at,
39271
+ models: [...modelCounts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([model]) => model)
39272
+ }
39273
+ ];
39274
+ });
39275
+ return summaries.sort((left, right) => (left.identity ?? "").localeCompare(right.identity ?? "") || left.provider.localeCompare(right.provider));
39347
39276
  }
39348
- function parseProviderFile(filePath, raw) {
39349
- const conversion = legacyProviderConversion(raw);
39350
- if (conversion !== void 0) {
39351
- throw new LegacyProviderFileError(filePath, conversion);
39277
+ var import_node_path26, SEGMENT_RE, DAY_MS, UsageSnapshotError, FIRST_ERROR_STATUS;
39278
+ var init_read = __esm({
39279
+ "src/usage/read.ts"() {
39280
+ "use strict";
39281
+ import_node_path26 = __toESM(require("node:path"), 1);
39282
+ init_account();
39283
+ init_schema2();
39284
+ SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
39285
+ DAY_MS = 864e5;
39286
+ UsageSnapshotError = class extends Error {
39287
+ constructor(file2, detail) {
39288
+ super(`${file2} is not a usage snapshot this agent-shim can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
39289
+ this.file = file2;
39290
+ this.name = "UsageSnapshotError";
39291
+ }
39292
+ file;
39293
+ };
39294
+ FIRST_ERROR_STATUS = 400;
39352
39295
  }
39353
- const parsed = parseIgnoringUnrecognisedKeys(ProviderSchema, raw);
39354
- if (!parsed.success) {
39355
- throw new ConfigValidationError(filePath, parsed.issues);
39296
+ });
39297
+
39298
+ // src/usage/store.ts
39299
+ function pruneUsageLog(fs31, logDir, nowMs) {
39300
+ const cutoff = nowMs - USAGE_RETENTION_MS;
39301
+ for (const segment of listLogSegments(fs31, logDir)) {
39302
+ if (segment.endsAt <= cutoff) {
39303
+ fs31.removeRecursive(import_node_path27.default.join(logDir, segment.name));
39304
+ }
39356
39305
  }
39357
- noteIgnoredKeys(filePath, parsed.ignored);
39358
- return parsed.data;
39359
39306
  }
39360
- function readProvider(paths, name) {
39361
- const filePath = providerJsonPath(paths, name);
39362
- if (!import_node_fs11.default.existsSync(filePath)) {
39363
- return void 0;
39364
- }
39365
- const raw = JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf8"));
39366
- return parseProviderFile(filePath, raw);
39307
+ function later(left, right) {
39308
+ return left.localeCompare(right) >= 0 ? left : right;
39367
39309
  }
39368
- function loadProvider(providersDir, name, port) {
39369
- const filePath = providerFilePath(providersDir, name);
39370
- const raw = port.readConfigFile(filePath);
39371
- return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
39310
+ function observedAt(record2) {
39311
+ return new Date(Date.parse(record2.at) + record2.latencyMs).toISOString();
39372
39312
  }
39373
- function reportInvalidProvider(name, message) {
39374
- console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${message}`);
39313
+ function foldProviderState(current, record2) {
39314
+ const seen = observedAt(record2);
39315
+ const newest = current?.lastRequestAt === void 0 || record2.at.localeCompare(current.lastRequestAt) >= 0;
39316
+ const rateLimit = record2.rateLimitHeaders === void 0 || current?.rateLimit !== void 0 && current.rateLimit.observedAt.localeCompare(seen) > 0 ? current?.rateLimit : (() => {
39317
+ const unified = parseUnifiedRateLimit(record2.rateLimitHeaders);
39318
+ return { observedAt: seen, headers: record2.rateLimitHeaders, ...unified === void 0 ? {} : { unified } };
39319
+ })();
39320
+ const lastLimit = record2.limit === void 0 || current?.lastLimit !== void 0 && current.lastLimit.observedAt.localeCompare(seen) > 0 ? current?.lastLimit : { ...record2.limit, observedAt: seen, status: record2.status };
39321
+ const lastModel = newest ? record2.model ?? current?.lastModel : current.lastModel ?? record2.model;
39322
+ return {
39323
+ lastRequestAt: current?.lastRequestAt === void 0 ? record2.at : later(current.lastRequestAt, record2.at),
39324
+ ...newest ? { lastStatus: record2.status } : current.lastStatus === void 0 ? {} : { lastStatus: current.lastStatus },
39325
+ ...lastModel === void 0 ? {} : { lastModel },
39326
+ ...rateLimit === void 0 ? {} : { rateLimit },
39327
+ ...lastLimit === void 0 ? {} : { lastLimit },
39328
+ ...current?.quota === void 0 ? {} : { quota: current.quota }
39329
+ };
39375
39330
  }
39376
- function listProviders(paths, onInvalid = reportInvalidProvider) {
39377
- const names = listProviderNames(
39378
- (dir) => import_node_fs11.default.readdirSync(dir),
39379
- paths.providersDir
39380
- );
39381
- const result = [];
39382
- for (const name of names) {
39383
- let provider;
39331
+ function createUsageStore(deps) {
39332
+ const { fs: fs31, paths } = deps;
39333
+ let prunedDay;
39334
+ const rewriteSnapshot = (identity, fold) => {
39335
+ if (!isIdentityName(identity)) {
39336
+ deps.log(`usage: no snapshot for identity "${identity}", which is not a valid identity name`);
39337
+ return false;
39338
+ }
39339
+ let current;
39384
39340
  try {
39385
- provider = readProvider(paths, name);
39341
+ current = readUsageSnapshot(fs31, paths.usageSnapshotsDir, identity);
39386
39342
  } catch (error63) {
39387
- if (!(error63 instanceof ConfigValidationError || error63 instanceof LegacyProviderFileError || error63 instanceof UnsafeNameError)) {
39343
+ if (!(error63 instanceof UsageSnapshotError)) {
39388
39344
  throw error63;
39389
39345
  }
39390
- onInvalid(name, error63.message);
39391
- continue;
39346
+ deps.log(`usage: replacing an unreadable snapshot: ${error63.message}`);
39392
39347
  }
39393
- if (provider !== void 0) {
39394
- result.push({ name, provider });
39348
+ const providers = fold(current);
39349
+ if (providers === void 0) {
39350
+ return false;
39395
39351
  }
39396
- }
39397
- return result;
39398
- }
39399
- function listProviderNames(readdir, providersDir) {
39400
- let entries;
39401
- try {
39402
- entries = readdir(providersDir);
39403
- } catch {
39404
- return [];
39405
- }
39406
- return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
39407
- }
39408
- function describeProviderEndpoint(provider) {
39409
- if (isCodexProvider(provider)) {
39410
- return "codex translation daemon";
39411
- }
39412
- if (isAntigravityProvider(provider)) {
39413
- return (provider.antigravity?.mode ?? ANTIGRAVITY_DEFAULT_MODE) === "local" ? "antigravity local (ACP daemon)" : "antigravity cloud (Gemini API)";
39414
- }
39415
- return isRouterProvider(provider) ? "the models block" : provider.baseUrl;
39416
- }
39417
- function isEmptyCodexConfig(config2) {
39418
- return config2 === void 0 || config2.defaultModel === void 0 && config2.effort === void 0 && config2.login === void 0 && (config2.models === void 0 || Object.keys(config2.models).length === 0);
39419
- }
39420
- function isEmptyAntigravityConfig(config2) {
39421
- return config2 === void 0 || config2.mode === void 0 && config2.defaultModel === void 0 && config2.effort === void 0 && config2.degradeDownTiers === void 0 && config2.cloud === void 0 && config2.local === void 0 && (config2.models === void 0 || Object.keys(config2.models).length === 0);
39422
- }
39423
- function addProvider(paths, name, input2) {
39424
- if (providerExists(paths, name)) {
39425
- throw new ProviderAlreadyExistsError(name);
39426
- }
39427
- requireNewProviderName(name);
39428
- const kind = input2.kind ?? "http";
39429
- if (kind === "codex" && input2.baseUrl !== void 0) {
39430
- throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
39431
- }
39432
- if (kind === "antigravity" && input2.baseUrl !== void 0) {
39433
- throw new ProviderKindMismatchError("An antigravity provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
39434
- }
39435
- if (kind === "router" && input2.baseUrl !== void 0) {
39436
- throw new ProviderKindMismatchError("A router provider has no base URL: it resolves each request's model name through the models block. Drop --base-url.");
39437
- }
39438
- if (kind === "http" && input2.baseUrl === void 0) {
39439
- throw new ProviderKindMismatchError("An http provider needs --base-url.");
39440
- }
39441
- if (kind === "router" && (input2.sources.length > 0 || input2.target !== void 0 || input2.env !== void 0 || input2.pickerModels !== void 0)) {
39442
- throw new ProviderKindMismatchError("A router provider has no credential, env or picker models of its own: the models block's targets carry their own.");
39443
- }
39444
- if (kind !== "http" && kind !== "router" && input2.catalogue !== void 0) {
39445
- throw new ProviderKindMismatchError("A model catalogue applies only to an http provider.");
39446
- }
39447
- if (kind !== "codex" && !isEmptyCodexConfig(input2.codex)) {
39448
- throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
39449
- }
39450
- if (kind !== "antigravity" && !isEmptyAntigravityConfig(input2.antigravity)) {
39451
- throw new ProviderKindMismatchError("Antigravity settings apply only to an antigravity provider (--kind antigravity).");
39452
- }
39453
- const shared = {
39454
- displayName: input2.displayName,
39455
- credential: { sources: input2.sources, ...input2.target === void 0 ? {} : { target: input2.target } },
39456
- ...input2.env === void 0 || Object.keys(input2.env).length === 0 ? {} : { env: input2.env },
39457
- ...input2.pickerModels === void 0 || input2.pickerModels.length === 0 ? {} : { pickerModels: input2.pickerModels }
39352
+ let account;
39353
+ try {
39354
+ account = deps.readAccount(identity);
39355
+ } catch (error63) {
39356
+ if (!(error63 instanceof AccountMetadataError)) {
39357
+ throw error63;
39358
+ }
39359
+ deps.log(`usage: snapshot for ${identity} written without account metadata: ${error63.message}`);
39360
+ }
39361
+ const snapshot = UsageSnapshotSchema.parse({
39362
+ schemaVersion: USAGE_SCHEMA_VERSION,
39363
+ identity,
39364
+ updatedAt: new Date(deps.now()).toISOString(),
39365
+ ...account === void 0 ? {} : { account },
39366
+ providers
39367
+ });
39368
+ fs31.mkdirPrivate(paths.usageSnapshotsDir);
39369
+ fs31.writeFilePrivate(snapshotPath(paths.usageSnapshotsDir, identity), `${JSON.stringify(snapshot, null, 2)}
39370
+ `);
39371
+ deps.publish?.(snapshot);
39372
+ return true;
39373
+ };
39374
+ return {
39375
+ record: (input2) => {
39376
+ const record2 = UsageRecordSchema.parse(input2);
39377
+ const nowMs = deps.now();
39378
+ fs31.mkdirPrivate(paths.usageDir);
39379
+ fs31.mkdirPrivate(paths.usageLogDir);
39380
+ const day = segmentDay(nowMs);
39381
+ if (prunedDay !== day) {
39382
+ pruneUsageLog(fs31, paths.usageLogDir, nowMs);
39383
+ prunedDay = day;
39384
+ }
39385
+ fs31.appendFilePrivate(import_node_path27.default.join(paths.usageLogDir, segmentName(day, deps.pid)), `${JSON.stringify(record2)}
39386
+ `);
39387
+ deps.onRecord?.(record2);
39388
+ if (record2.identity !== void 0) {
39389
+ rewriteSnapshot(record2.identity, (current) => ({ ...current?.providers, [record2.provider]: foldProviderState(current?.providers[record2.provider], record2) }));
39390
+ }
39391
+ },
39392
+ recordQuota: (identity, provider, quota) => rewriteSnapshot(identity, (current) => {
39393
+ const state = current?.providers[provider];
39394
+ if (current === void 0 || state === void 0) {
39395
+ return void 0;
39396
+ }
39397
+ const keep = state.quota !== void 0 && state.quota.observedAt.localeCompare(quota.observedAt) > 0;
39398
+ return { ...current.providers, [provider]: { ...state, quota: keep ? state.quota : quota } };
39399
+ }),
39400
+ recordRateLimit: (identity, provider, rateLimit) => rewriteSnapshot(identity, (current) => {
39401
+ const state = current?.providers[provider] ?? {};
39402
+ const keep = state.rateLimit !== void 0 && state.rateLimit.observedAt.localeCompare(rateLimit.observedAt) > 0;
39403
+ const earlier = state.rateLimit;
39404
+ const merged = rateLimit.unified === void 0 ? { ...earlier ?? rateLimit, planWindowsUnavailableAt: rateLimit.observedAt } : { ...rateLimit, unified: { ...earlier?.unified === void 0 ? {} : overageFactsOf(earlier.unified), ...rateLimit.unified } };
39405
+ return { ...current?.providers, [provider]: { ...state, rateLimit: keep ? state.rateLimit : merged } };
39406
+ })
39458
39407
  };
39459
- const parsed = ProviderSchema.safeParse(
39460
- kind === "router" ? { kind, displayName: input2.displayName } : kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input2.codex) ? {} : { codex: input2.codex } } : kind === "antigravity" ? { kind, ...shared, ...isEmptyAntigravityConfig(input2.antigravity) ? {} : { antigravity: input2.antigravity } } : { ...shared, baseUrl: input2.baseUrl, ...input2.catalogue === void 0 ? {} : { catalogue: input2.catalogue } }
39461
- );
39462
- if (!parsed.success) {
39463
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
39464
- }
39465
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
39466
- return parsed.data;
39467
39408
  }
39468
- function updateProvider(paths, name, input2) {
39469
- const existing = readProvider(paths, name);
39470
- if (existing === void 0) {
39471
- throw new ProviderNotFoundError(name);
39409
+ var import_node_path27, DAY_MS2, DAYS_IN_WEEK2, USAGE_RETENTION_MS;
39410
+ var init_store2 = __esm({
39411
+ "src/usage/store.ts"() {
39412
+ "use strict";
39413
+ import_node_path27 = __toESM(require("node:path"), 1);
39414
+ init_account();
39415
+ init_rateLimit();
39416
+ init_read();
39417
+ init_schema2();
39418
+ DAY_MS2 = 864e5;
39419
+ DAYS_IN_WEEK2 = 7;
39420
+ USAGE_RETENTION_MS = DAYS_IN_WEEK2 * DAY_MS2;
39472
39421
  }
39473
- const unset = new Set(input2.unsetEnv);
39474
- const existingEnv = isRouterProvider(existing) ? {} : existing.env;
39475
- const env = Object.fromEntries(Object.entries({ ...existingEnv, ...input2.env }).filter(([key]) => !unset.has(key)));
39476
- const unsetPicker = new Set(input2.unsetPickerModels);
39477
- const added = new Map((input2.pickerModels ?? []).map((entry) => [entry.model, entry]));
39478
- const existingPicker = isRouterProvider(existing) ? [] : existing.pickerModels;
39479
- const pickerModels = [
39480
- ...(existingPicker ?? []).filter((entry) => !unsetPicker.has(entry.model)).map((entry) => added.get(entry.model) ?? entry),
39481
- ...[...added.values()].filter((entry) => !(existingPicker ?? []).some((kept) => kept.model === entry.model))
39482
- ];
39483
- const router = isRouterProvider(existing);
39484
- const target = router ? void 0 : input2.target ?? existing.credential.target;
39485
- const cache = router ? void 0 : input2.cache === void 0 ? existing.credential.cache : input2.cache === false ? void 0 : input2.cache;
39486
- if (isCodexProvider(existing) && input2.baseUrl !== void 0) {
39487
- throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
39422
+ });
39423
+
39424
+ // src/usage/poolPick.ts
39425
+ function errorMessage(error63) {
39426
+ return error63 instanceof Error ? error63.message : String(error63);
39427
+ }
39428
+ function loadPoolMembers(fs31, paths, identities, nowMs) {
39429
+ const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
39430
+ const log = readUsageLog(fs31, paths.usageLogDir, { sinceMs: windowStartMs });
39431
+ return identities.map((identity) => {
39432
+ const records = log.records.filter((record2) => record2.identity === identity && record2.provider === ANTHROPIC_PROVIDER && Date.parse(record2.at) >= windowStartMs);
39433
+ try {
39434
+ const snapshot = readUsageSnapshot(fs31, paths.usageSnapshotsDir, identity);
39435
+ const account = readAccountMetadata(fs31, paths.identitiesDir, identity) ?? snapshot?.account;
39436
+ return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
39437
+ } catch (error63) {
39438
+ if (error63 instanceof UsageSnapshotError || error63 instanceof AccountMetadataError) {
39439
+ return { identity, records, readError: errorMessage(error63) };
39440
+ }
39441
+ throw error63;
39442
+ }
39443
+ });
39444
+ }
39445
+ function rankPoolGraph(input2) {
39446
+ return rankNamedPool(input2, input2.poolName, [], input2.sticky);
39447
+ }
39448
+ function rankNamedPool(input2, poolName, stack, sticky) {
39449
+ const pool = input2.pools[poolName];
39450
+ if (pool === void 0) {
39451
+ throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
39488
39452
  }
39489
- if (isAntigravityProvider(existing) && input2.baseUrl !== void 0) {
39490
- throw new ProviderKindMismatchError(`Provider "${name}" is an antigravity provider, which has no base URL.`);
39453
+ const childStack = [...stack, poolName];
39454
+ const direct = pool.identities.filter((entry) => poolNameOf(poolMemberIdentity(entry)) === void 0);
39455
+ const missing = direct.filter((entry) => !input2.identityExists(poolMemberIdentity(entry))).map((entry) => ({ pool: poolName, identity: poolMemberIdentity(entry) }));
39456
+ const loaded = new Map(loadPoolMembers(input2.fs, input2.paths, direct.filter((entry) => input2.identityExists(poolMemberIdentity(entry))).map(poolMemberIdentity), input2.nowMs).map((member) => [member.identity, member]));
39457
+ const members2 = [];
39458
+ for (const entry of pool.identities) {
39459
+ const nestedName = poolNameOf(poolMemberIdentity(entry));
39460
+ if (nestedName === void 0) {
39461
+ const member = loaded.get(poolMemberIdentity(entry));
39462
+ if (member !== void 0) {
39463
+ members2.push(typeof entry === "string" ? member : { ...member, policy: entry.when });
39464
+ }
39465
+ continue;
39466
+ }
39467
+ assertNestable(input2.pools, poolName, poolMemberIdentity(entry), nestedName, childStack);
39468
+ const nested = nestedMember(input2, nestedName, childStack, sticky);
39469
+ members2.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
39470
+ missing.push(...nested.missing);
39491
39471
  }
39492
- if ((isCodexProvider(existing) || isAntigravityProvider(existing)) && input2.catalogue !== void 0 && input2.catalogue !== false) {
39493
- throw new ProviderKindMismatchError(`Provider "${name}" is not an http provider; a model catalogue applies only to an http provider.`);
39472
+ const ranking = rankPool({
39473
+ members: members2,
39474
+ nowMs: input2.nowMs,
39475
+ resuming: input2.resuming,
39476
+ ...input2.resumeContextTokens === void 0 ? {} : { resumeContextTokens: input2.resumeContextTokens },
39477
+ ...input2.committedContextTokens === void 0 ? {} : { committedContextTokens: input2.committedContextTokens },
39478
+ ...sticky === void 0 ? {} : { sticky },
39479
+ ...pool.preference === void 0 ? {} : { preference: pool.preference }
39480
+ });
39481
+ return { ranking, missing };
39482
+ }
39483
+ function assertNestable(pools, poolName, member, nestedName, childStack) {
39484
+ if (pools[nestedName] === void 0) {
39485
+ throw new PoolGraphError(`Pool "${poolName}" member "${member}" names a pool that is not defined.`);
39494
39486
  }
39495
- if (!isCodexProvider(existing) && !isEmptyCodexConfig(input2.codex)) {
39496
- throw new ProviderKindMismatchError(`Provider "${name}" is not a codex provider; codex settings apply only to a codex provider.`);
39487
+ if (childStack.includes(nestedName)) {
39488
+ throw new PoolGraphError(`Pool "${poolName}" member "${member}" closes a cycle: ${[...childStack.slice(childStack.indexOf(nestedName)), nestedName].join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
39497
39489
  }
39498
- if (!isAntigravityProvider(existing) && !isEmptyAntigravityConfig(input2.antigravity)) {
39499
- throw new ProviderKindMismatchError(`Provider "${name}" is not an antigravity provider; antigravity settings apply only to an antigravity provider.`);
39490
+ }
39491
+ function nestedMember(input2, nestedName, childStack, sticky) {
39492
+ const nested = rankNamedPool(input2, nestedName, childStack, sticky);
39493
+ const pick2 = nested.ranking.pick;
39494
+ if (pick2 !== void 0) {
39495
+ const [member] = loadPoolMembers(input2.fs, input2.paths, [pick2.identity], input2.nowMs);
39496
+ return { member: { ...member ?? { identity: pick2.identity, records: [] }, nested: { kind: "pick", pool: nestedName, reasons: pick2.reasons } }, missing: nested.missing };
39500
39497
  }
39501
- const shared = {
39502
- ...input2.displayName === void 0 ? {} : { displayName: input2.displayName },
39503
- ...router ? {} : {
39504
- credential: {
39505
- sources: input2.sources ?? existing.credential.sources,
39506
- ...target === void 0 ? {} : { target },
39507
- ...cache === void 0 ? {} : { cache }
39508
- }
39498
+ const earliestReturn = nested.ranking.earliestReturn;
39499
+ return {
39500
+ member: {
39501
+ identity: earliestReturn?.identity ?? `pool:${nestedName}`,
39502
+ records: [],
39503
+ nested: { kind: "refused", pool: nestedName, ...earliestReturn === void 0 ? {} : { earliestReturn } }
39509
39504
  },
39510
- env: Object.keys(env).length === 0 ? void 0 : env,
39511
- pickerModels: pickerModels.length === 0 ? void 0 : pickerModels
39505
+ missing: nested.missing
39512
39506
  };
39513
- let candidate;
39514
- if (isCodexProvider(existing)) {
39515
- const codex = {
39516
- ...existing.codex,
39517
- ...input2.codex,
39518
- ...existing.codex?.models === void 0 && input2.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input2.codex?.models } }
39519
- };
39520
- candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
39521
- } else if (isAntigravityProvider(existing)) {
39522
- const antigravity = {
39523
- ...existing.antigravity,
39524
- ...input2.antigravity,
39525
- ...existing.antigravity?.models === void 0 && input2.antigravity?.models === void 0 ? {} : { models: { ...existing.antigravity?.models, ...input2.antigravity?.models } }
39526
- };
39527
- candidate = { ...existing, ...shared, antigravity: isEmptyAntigravityConfig(antigravity) ? void 0 : antigravity };
39528
- } else if (router) {
39529
- candidate = { kind: "router", ...shared };
39530
- } else {
39531
- const catalogue = input2.catalogue === void 0 ? existing.catalogue : input2.catalogue === false ? void 0 : input2.catalogue;
39532
- candidate = { ...existing, ...shared, catalogue, ...input2.baseUrl === void 0 ? {} : { baseUrl: input2.baseUrl } };
39507
+ }
39508
+ function readPicksFile(fs31, file2) {
39509
+ const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
39510
+ const raw = fs31.readFileUtf8(file2);
39511
+ if (raw === void 0) {
39512
+ return { file: empty };
39533
39513
  }
39534
- const parsed = ProviderSchema.safeParse(candidate);
39535
- if (!parsed.success) {
39536
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
39514
+ let parsed;
39515
+ try {
39516
+ parsed = JSON.parse(raw);
39517
+ } catch {
39518
+ return { file: empty, problem: `${file2} is not valid JSON` };
39537
39519
  }
39538
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
39539
- return parsed.data;
39520
+ const result = PicksFileSchema.safeParse(parsed);
39521
+ return result.success ? { file: result.data } : { file: empty, problem: `${file2} is not a picks file this agent-shim can read` };
39540
39522
  }
39541
- function removeProvider(paths, name) {
39542
- if (!providerExists(paths, name)) {
39543
- throw new ProviderNotFoundError(name);
39544
- }
39545
- import_node_fs11.default.rmSync(providerJsonPath(paths, name));
39523
+ function readStickyPick(fs31, picksFile, directory) {
39524
+ const read = readPicksFile(fs31, picksFile);
39525
+ const entry = read.file.picks[directory];
39526
+ return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
39546
39527
  }
39547
- function cascadeProviderName(cascade) {
39548
- let name;
39549
- for (const layer of assembleCascade(cascade).layers) {
39550
- if (layer.launch?.provider !== void 0) {
39551
- name = layer.launch.provider;
39552
- }
39553
- }
39554
- return name;
39528
+ function recordStickyPick(fs31, picksFile, directory, identity, nowMs) {
39529
+ const current = readPicksFile(fs31, picksFile).file.picks;
39530
+ const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
39531
+ const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
39532
+ fs31.mkdirPrivate(import_node_path28.default.dirname(picksFile));
39533
+ fs31.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
39534
+ `);
39555
39535
  }
39556
- function selectProvider(params) {
39557
- const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
39558
- const named = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
39559
- if (named === void 0) {
39560
- return void 0;
39561
- }
39562
- const name = canonicalProviderName(named, (candidate) => loadProvider(params.paths.providersDir, candidate, params.port) !== void 0, () => loadProviderAliases(params.paths.providerAliasesFile, params.port));
39563
- const definition = loadProvider(params.paths.providersDir, name, params.port);
39564
- if (definition === void 0) {
39565
- const known = listProviderNames(params.port.readdir, params.paths.providersDir);
39566
- return {
39567
- ok: false,
39568
- status: 1,
39569
- message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
39536
+ var import_node_path28, PoolGraphError, SCHEMA_VERSION, PickEntrySchema, PicksFileSchema;
39537
+ var init_poolPick = __esm({
39538
+ "src/usage/poolPick.ts"() {
39539
+ "use strict";
39540
+ import_node_path28 = __toESM(require("node:path"), 1);
39541
+ init_zod();
39542
+ init_cliError();
39543
+ init_identity();
39544
+ init_schema();
39545
+ init_account();
39546
+ init_pick();
39547
+ init_read();
39548
+ init_middleware();
39549
+ init_store2();
39550
+ PoolGraphError = class extends CliError {
39551
+ constructor(message) {
39552
+ super(message);
39553
+ this.name = "PoolGraphError";
39554
+ }
39570
39555
  };
39556
+ SCHEMA_VERSION = 1;
39557
+ PickEntrySchema = external_exports.strictObject({ identity: external_exports.string().min(1), at: external_exports.iso.datetime() });
39558
+ PicksFileSchema = external_exports.strictObject({
39559
+ schemaVersion: external_exports.literal(SCHEMA_VERSION),
39560
+ picks: external_exports.record(external_exports.string().min(1), PickEntrySchema)
39561
+ });
39571
39562
  }
39572
- return { ok: true, provider: { name, definition } };
39563
+ });
39564
+
39565
+ // src/realCredentialCache.ts
39566
+ function cacheFileNameFor(owner) {
39567
+ return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
39573
39568
  }
39574
- function resolveProvider(params) {
39575
- const selection = selectProvider(params);
39576
- if (selection?.ok !== true) {
39577
- return selection;
39578
- }
39579
- const { name, definition } = selection.provider;
39580
- if (isRouterProvider(definition)) {
39581
- return { ok: true, provider: { name, definition, credential: { target: "bearer", token: "", source: { env: "AGENT_SHIM_ROUTER" }, warnings: [] } }, warnings: [] };
39582
- }
39583
- if (params.credentials === void 0) {
39584
- return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
39585
- }
39586
- const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
39587
- if (!resolution.ok) {
39588
- return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
39569
+ function parseEntry(raw) {
39570
+ let json2;
39571
+ try {
39572
+ json2 = JSON.parse(raw);
39573
+ } catch {
39574
+ return void 0;
39589
39575
  }
39590
- return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
39576
+ const parsed = CachedCredentialSchema.safeParse(json2);
39577
+ return parsed.success ? parsed.data : void 0;
39591
39578
  }
39592
- function resolveRoutedProviderCredentials(params) {
39593
- const { models } = params;
39594
- if (models === void 0) {
39595
- return { ok: true, checked: [] };
39579
+ function requireKeychain() {
39580
+ if (process.platform !== "darwin") {
39581
+ throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
39596
39582
  }
39597
- const virtualNames = new Set(Object.keys(models.virtual ?? {}));
39598
- const providerNames = /* @__PURE__ */ new Set();
39599
- const families = () => buildFamilies(params.targets?.() ?? [], models);
39600
- const collect = (ref) => {
39601
- if (ref.startsWith(FAMILY_PREFIX)) {
39602
- for (const target of expandFamilyMembers([ref], families())) {
39603
- collect(target);
39583
+ }
39584
+ function security(args, stdin2) {
39585
+ const result = spawnSyncBlocking("a keychain call", "security", args, { encoding: "utf8", ...stdin2 === void 0 ? {} : { input: stdin2 }, stdio: ["pipe", "pipe", "ignore"] });
39586
+ return { status: result.status, stdout: result.stdout };
39587
+ }
39588
+ async function securityAsync(args, stdin2) {
39589
+ return await new Promise((resolve3) => {
39590
+ const child = (0, import_node_child_process4.spawn)("security", args, { stdio: ["pipe", "pipe", "ignore"] });
39591
+ let stdout2 = "";
39592
+ let settled = false;
39593
+ const settle2 = (status) => {
39594
+ if (!settled) {
39595
+ settled = true;
39596
+ resolve3({ status, stdout: stdout2 });
39604
39597
  }
39605
- return;
39606
- }
39607
- if (ref.startsWith("acct:") || virtualNames.has(ref)) {
39608
- return;
39609
- }
39610
- const [provider] = ref.split("/", 1);
39611
- if (provider !== void 0 && provider !== "") {
39612
- providerNames.add(provider);
39598
+ };
39599
+ child.stdout.setEncoding("utf8");
39600
+ child.stdout.on("data", (chunk2) => {
39601
+ stdout2 += chunk2;
39602
+ });
39603
+ child.stdin.on("error", () => {
39604
+ });
39605
+ child.stdin.end(stdin2);
39606
+ child.once("error", () => {
39607
+ settle2(null);
39608
+ });
39609
+ child.once("close", settle2);
39610
+ });
39611
+ }
39612
+ function createRealCredentialCache(root) {
39613
+ const dir = import_node_path29.default.join(root, "credential-cache");
39614
+ const filePath = (owner) => import_node_path29.default.join(dir, cacheFileNameFor(owner));
39615
+ return {
39616
+ read(store, owner) {
39617
+ if (store === "file") {
39618
+ try {
39619
+ return parseEntry(import_node_fs11.default.readFileSync(filePath(owner), "utf8"));
39620
+ } catch {
39621
+ return void 0;
39622
+ }
39623
+ }
39624
+ requireKeychain();
39625
+ const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
39626
+ return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
39627
+ },
39628
+ write(store, owner, entry) {
39629
+ const serialised = JSON.stringify(entry);
39630
+ if (store === "file") {
39631
+ import_node_fs11.default.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
39632
+ import_node_fs11.default.chmodSync(dir, CACHE_DIR_MODE);
39633
+ const target = filePath(owner);
39634
+ const temp = `${target}.${String(process.pid)}.tmp`;
39635
+ import_node_fs11.default.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
39636
+ import_node_fs11.default.renameSync(temp, target);
39637
+ return;
39638
+ }
39639
+ requireKeychain();
39640
+ const encoded = Buffer.from(serialised, "utf8").toString("base64");
39641
+ const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
39642
+ `);
39643
+ if (result.status !== 0) {
39644
+ throw new Error(`could not write the keychain credential cache for ${owner}`);
39645
+ }
39646
+ },
39647
+ remove(store, owner) {
39648
+ if (store === "file") {
39649
+ import_node_fs11.default.rmSync(filePath(owner), { force: true });
39650
+ return;
39651
+ }
39652
+ requireKeychain();
39653
+ security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
39613
39654
  }
39614
39655
  };
39615
- for (const model of Object.values(models.virtual ?? {})) {
39616
- for (const member of model.members) {
39617
- collect(member);
39618
- }
39619
- }
39620
- for (const ref of Object.values(models.tiers ?? {})) {
39621
- collect(ref);
39622
- }
39623
- if (providerNames.size === 0) {
39624
- return { ok: true, checked: [] };
39625
- }
39626
- if (params.credentials === void 0) {
39627
- return { ok: false, status: 1, message: `agent-shim: the models block routes to providers ${[...providerNames].sort().join(", ")}, which need their credentials resolved, but this launcher has no credential port wired` };
39628
- }
39629
- const failures = [];
39630
- let credentialFailure = false;
39631
- for (const name of [...providerNames].sort()) {
39632
- const definition = loadProvider(params.paths.providersDir, name, params.port);
39633
- if (definition === void 0) {
39634
- failures.push(`no provider named "${name}"`);
39635
- continue;
39636
- }
39637
- if (isRouterProvider(definition)) {
39638
- continue;
39639
- }
39640
- const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
39641
- if (!resolution.ok) {
39642
- failures.push(resolution.message.replace(/^agent-shim: /, ""));
39643
- credentialFailure = true;
39644
- }
39645
- }
39646
- if (failures.length > 0) {
39647
- return { ok: false, status: credentialFailure ? CREDENTIAL_UNAVAILABLE_EXIT : 1, message: `agent-shim: this launch routes model names through the models block, but: ${failures.join("; ")}` };
39648
- }
39649
- return { ok: true, checked: [...providerNames].sort() };
39650
39656
  }
39651
- var import_node_fs11, ProviderNotFoundError, ProviderAlreadyExistsError, LEGACY_PROVIDER_FIELDS, LEGACY_ENV_CREDENTIAL_KEYS, LEGACY_LITERAL_PLACEHOLDER, LegacyProviderFileError, ProviderKindMismatchError;
39652
- var init_providersStore = __esm({
39653
- "src/providersStore.ts"() {
39654
- "use strict";
39655
- import_node_fs11 = __toESM(require("node:fs"), 1);
39656
- init_load();
39657
- init_store();
39658
- init_unrecognisedKeys();
39659
- init_schema();
39660
- init_cliError();
39661
- init_providerAliases();
39662
- init_credential();
39663
- init_walk();
39664
- init_families();
39665
- init_names();
39666
- ProviderNotFoundError = class extends CliError {
39667
- constructor(name) {
39668
- super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
39669
- this.name = name;
39670
- this.name = "ProviderNotFoundError";
39671
- }
39672
- name;
39673
- };
39674
- ProviderAlreadyExistsError = class extends CliError {
39675
- constructor(providerName) {
39676
- super(`A provider named "${providerName}" already exists.`);
39677
- this.providerName = providerName;
39678
- this.name = "ProviderAlreadyExistsError";
39657
+ function realCredentialCacheEnv(paths) {
39658
+ return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
39659
+ }
39660
+ function createAsyncRealCredentialCache(root) {
39661
+ const dir = import_node_path29.default.join(root, "credential-cache");
39662
+ const filePath = (owner) => import_node_path29.default.join(dir, cacheFileNameFor(owner));
39663
+ return {
39664
+ async read(store, owner) {
39665
+ if (store === "file") {
39666
+ try {
39667
+ return parseEntry(await import_node_fs11.default.promises.readFile(filePath(owner), "utf8"));
39668
+ } catch {
39669
+ return void 0;
39670
+ }
39679
39671
  }
39680
- providerName;
39681
- };
39682
- LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
39683
- LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
39684
- LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
39685
- LegacyProviderFileError = class extends CliError {
39686
- constructor(filePath, conversion) {
39687
- super(
39688
- `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
39689
- ` + JSON.stringify(conversion.replacement, null, 2)
39690
- );
39691
- this.filePath = filePath;
39692
- this.conversion = conversion;
39693
- this.name = "LegacyProviderFileError";
39672
+ requireKeychain();
39673
+ const result = await securityAsync(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
39674
+ return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
39675
+ },
39676
+ async write(store, owner, entry) {
39677
+ const serialised = JSON.stringify(entry);
39678
+ if (store === "file") {
39679
+ await import_node_fs11.default.promises.mkdir(dir, { recursive: true, mode: CACHE_DIR_MODE });
39680
+ await import_node_fs11.default.promises.chmod(dir, CACHE_DIR_MODE);
39681
+ const target = filePath(owner);
39682
+ const temp = `${target}.${String(process.pid)}.tmp`;
39683
+ await import_node_fs11.default.promises.writeFile(temp, serialised, { mode: CACHE_FILE_MODE });
39684
+ await import_node_fs11.default.promises.rename(temp, target);
39685
+ return;
39694
39686
  }
39695
- filePath;
39696
- conversion;
39697
- };
39698
- ProviderKindMismatchError = class extends UsageError {
39699
- constructor(message) {
39700
- super(message);
39701
- this.name = "ProviderKindMismatchError";
39687
+ requireKeychain();
39688
+ const encoded = Buffer.from(serialised, "utf8").toString("base64");
39689
+ const result = await securityAsync(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
39690
+ `);
39691
+ if (result.status !== 0) {
39692
+ throw new Error(`could not write the keychain credential cache for ${owner}`);
39702
39693
  }
39703
- };
39694
+ }
39695
+ };
39696
+ }
39697
+ function realAsyncCredentialCacheEnv(paths) {
39698
+ return { port: createAsyncRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
39699
+ }
39700
+ var import_node_child_process4, import_node_fs11, import_node_path29, KEYCHAIN_CACHE_SERVICE, CACHE_DIR_MODE, CACHE_FILE_MODE;
39701
+ var init_realCredentialCache = __esm({
39702
+ "src/realCredentialCache.ts"() {
39703
+ "use strict";
39704
+ import_node_child_process4 = require("node:child_process");
39705
+ import_node_fs11 = __toESM(require("node:fs"), 1);
39706
+ import_node_path29 = __toESM(require("node:path"), 1);
39707
+ init_blockingSpawn();
39708
+ init_credentialCache();
39709
+ KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
39710
+ CACHE_DIR_MODE = 448;
39711
+ CACHE_FILE_MODE = 384;
39704
39712
  }
39705
39713
  });
39706
39714
 
@@ -39715,6 +39723,9 @@ function decideSignIn(facts) {
39715
39723
  if (facts.hasCredentialBlock) {
39716
39724
  return { state: "token", detail: "supplies its own credential through a credential block, so a stored login is not what a launch uses" };
39717
39725
  }
39726
+ if (facts.provider !== void 0) {
39727
+ return { state: "token", detail: "runs provider sessions with the provider's credential, so a stored login is not what a launch uses" };
39728
+ }
39718
39729
  if (facts.keychainItem === true) {
39719
39730
  return { state: "signed-in", detail: "a login is stored in the macOS Keychain" };
39720
39731
  }
@@ -39727,9 +39738,10 @@ function credentialsFilePath(farmRoot) {
39727
39738
  return import_node_path30.default.join(farmRoot, CREDENTIALS_FILE);
39728
39739
  }
39729
39740
  function identitySignIn(fs31, probe, identity) {
39730
- const probing = !identity.hasCredentialBlock;
39741
+ const probing = !identity.hasCredentialBlock && identity.provider === void 0;
39731
39742
  return decideSignIn({
39732
39743
  hasCredentialBlock: identity.hasCredentialBlock,
39744
+ provider: identity.provider,
39733
39745
  keychainItem: probing && probe.platform === "darwin" && probe.run !== void 0 ? keychainHasItem(probe.run, identity.farmRoot) : void 0,
39734
39746
  credentialsFile: probing && fs31.lstat(credentialsFilePath(identity.farmRoot)) !== void 0,
39735
39747
  platform: probe.platform
@@ -69869,7 +69881,8 @@ function decideLaunch(params, readOnly) {
69869
69881
  paths,
69870
69882
  port: fs31,
69871
69883
  ...parsedArgv.provider === void 0 || parsedArgv.provider === false ? {} : { cliProvider: parsedArgv.provider },
69872
- ...farmContext === void 0 ? {} : { cascade: farmContext.cascade }
69884
+ ...farmContext === void 0 ? {} : { cascade: farmContext.cascade },
69885
+ ...loadedIdentity?.config.provider === void 0 ? {} : { identityProvider: loadedIdentity.config.provider }
69873
69886
  };
69874
69887
  let selectedProvider;
69875
69888
  let resolvedProvider;
@@ -70339,7 +70352,7 @@ function realPrepareLaunchParams(paths, options) {
70339
70352
  frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
70340
70353
  credentials: { ...realCredentialPort, cache: realCredentialCacheEnv(paths) },
70341
70354
  hold: realHoldPort,
70342
- hasStoredLogin: (identity) => identitySignIn(realFarmFs, { platform: process.platform, run: options.run ?? realRunPort }, { farmRoot: import_node_path44.default.join(paths.identitiesDir, identity), hasCredentialBlock: false }).state !== "signed-out",
70355
+ hasStoredLogin: (identity) => identitySignIn(realFarmFs, { platform: process.platform, run: options.run ?? realRunPort }, { farmRoot: import_node_path44.default.join(paths.identitiesDir, identity), hasCredentialBlock: false, provider: void 0 }).state !== "signed-out",
70343
70356
  ...farm.directoryIdentity === void 0 ? {} : { directoryPinnedIdentity: farm.directoryIdentity },
70344
70357
  ...farm.directoryConfigProfile === void 0 ? {} : { directoryRuleConfigProfile: farm.directoryConfigProfile },
70345
70358
  ...farm.globalDefaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: farm.globalDefaultConfigProfile },
@@ -116380,6 +116393,12 @@ function setDefaultConfigProfile(paths, identityName, profileName) {
116380
116393
  defaultConfigProfile: profileName
116381
116394
  });
116382
116395
  }
116396
+ function setIdentityProvider(paths, identityName, providerName) {
116397
+ if (!identityExists(paths, identityName)) {
116398
+ throw new IdentityNotFoundError(identityName);
116399
+ }
116400
+ return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { provider: providerName });
116401
+ }
116383
116402
  function setAllowAmbientCredential(paths, identityName, allow) {
116384
116403
  if (!identityExists(paths, identityName)) {
116385
116404
  throw new IdentityNotFoundError(identityName);
@@ -117524,6 +117543,7 @@ function registerProfileCommand(program2, deps) {
117524
117543
  }
117525
117544
 
117526
117545
  // src/identityManager.ts
117546
+ init_providersStore();
117527
117547
  init_identity();
117528
117548
  init_realPorts();
117529
117549
 
@@ -117662,6 +117682,7 @@ function toIdentityView(paths, { entry, read }, sharesLoginWith) {
117662
117682
  directory: import_node_path25.default.join(paths.identitiesDir, entry.name),
117663
117683
  ...entry.problem === void 0 ? {
117664
117684
  ...entry.identity.defaultConfigProfile === void 0 ? {} : { defaultConfigProfile: entry.identity.defaultConfigProfile },
117685
+ ...entry.identity.provider === void 0 ? {} : { provider: entry.identity.provider },
117665
117686
  allowAmbientCredential: entry.identity.allowAmbientCredential,
117666
117687
  ...entry.identity.credential === void 0 ? {} : { credential: summariseCredential(entry.identity.credential) },
117667
117688
  ...entry.identity.anchors === void 0 ? {} : { anchors: entry.identity.anchors }
@@ -117688,9 +117709,10 @@ function formatIdentityLine(entry, view) {
117688
117709
  return `${marker}${entry.name} [unreadable: ${entry.problem}]${account}${accountProblem}`;
117689
117710
  }
117690
117711
  const defaultProfile = entry.identity.defaultConfigProfile === void 0 ? "" : ` (default profile: ${entry.identity.defaultConfigProfile})`;
117712
+ const provider = entry.identity.provider === void 0 ? "" : ` (provider: ${entry.identity.provider})`;
117691
117713
  const ambient = entry.identity.allowAmbientCredential ? " [allows ambient credential]" : "";
117692
117714
  const credential = entry.identity.credential === void 0 ? "" : ` [credential ${describeCredential(entry.identity.credential)}]`;
117693
- return `${marker}${entry.name}${defaultProfile}${ambient}${credential}${account}${accountProblem}`;
117715
+ return `${marker}${entry.name}${defaultProfile}${provider}${ambient}${credential}${account}${accountProblem}`;
117694
117716
  }
117695
117717
  function registerIdentityCommand(program2, deps) {
117696
117718
  const { paths } = deps;
@@ -117763,13 +117785,14 @@ function registerIdentityCommand(program2, deps) {
117763
117785
  return;
117764
117786
  }
117765
117787
  console.log(`Default configuration profile: ${view.defaultConfigProfile ?? "(none)"}`);
117788
+ console.log(`Provider: ${view.provider ?? "(none)"}`);
117766
117789
  console.log(`Allows ambient credential: ${view.allowAmbientCredential === true ? "yes" : "no"}`);
117767
117790
  console.log(`Credential: ${entry.identity?.credential === void 0 ? "(none; uses its stored login)" : describeCredential(entry.identity.credential)}`);
117768
117791
  console.log(`Anchors: ${view.anchors === void 0 ? "(none)" : view.anchors.map(formatAnchor).join(" ")}`);
117769
117792
  }),
117770
117793
  ["agent-shim identity show work", "agent-shim identity show work --json"]
117771
117794
  );
117772
- const identitySet = identity.command("set <name>").description("Update an identity's settings.").option("--json", "Print the result as JSON.").option("--default-profile <profile>", "Configuration profile this identity uses when nothing more specific selects one.").option("--no-default-profile", "Clear this identity's default configuration profile.").option("--allow-ambient-credential", "Allow this identity to launch even with an ambient credential env var set.").option("--no-allow-ambient-credential", "Disallow ambient credential env vars for this identity (the default).").option(
117795
+ const identitySet = identity.command("set <name>").description("Update an identity's settings.").option("--json", "Print the result as JSON.").option("--default-profile <profile>", "Configuration profile this identity uses when nothing more specific selects one.").option("--no-default-profile", "Clear this identity's default configuration profile.").option("--provider <provider>", "Provider this identity exists for: a launch on it routes through the provider unless a flag, directory rule or profile selects another, and doctor expects no stored login.").option("--no-provider", "Clear this identity's provider.").option("--allow-ambient-credential", "Allow this identity to launch even with an ambient credential env var set.").option("--no-allow-ambient-credential", "Disallow ambient credential env vars for this identity (the default).").option(
117773
117796
  "--credential <source>",
117774
117797
  `Authenticate this identity from a token instead of its stored login, replacing any existing sources (repeatable, in the order tried): ${CREDENTIAL_SOURCE_SYNTAX}.`,
117775
117798
  collectCredentialSource
@@ -117789,12 +117812,15 @@ function registerIdentityCommand(program2, deps) {
117789
117812
  const { json: json2, ...options } = allOptions;
117790
117813
  if (Object.entries(allOptions).every(([key, value2]) => key === "json" || value2 === void 0)) {
117791
117814
  throw new UsageError(
117792
- "Nothing to change: pass --default-profile, --no-default-profile, --allow-ambient-credential, --no-allow-ambient-credential, --credential, --no-credential, --credential-target, --anchor, --no-anchor or a --credential-cache option."
117815
+ "Nothing to change: pass --default-profile, --no-default-profile, --provider, --no-provider, --allow-ambient-credential, --no-allow-ambient-credential, --credential, --no-credential, --credential-target, --anchor, --no-anchor or a --credential-cache option."
117793
117816
  );
117794
117817
  }
117795
117818
  if (!identityExists(paths, name)) {
117796
117819
  throw new IdentityNotFoundError(name);
117797
117820
  }
117821
+ if (typeof options.provider === "string") {
117822
+ requireProviderExists(paths, options.provider);
117823
+ }
117798
117824
  const messages = [];
117799
117825
  if (options.defaultProfile !== void 0) {
117800
117826
  const profileName = options.defaultProfile === false ? void 0 : options.defaultProfile;
@@ -117806,6 +117832,11 @@ function registerIdentityCommand(program2, deps) {
117806
117832
  profileName === void 0 ? `Identity "${name}" no longer has a default configuration profile.` : `Identity "${name}" now defaults to configuration profile "${profileName}".`
117807
117833
  );
117808
117834
  }
117835
+ if (options.provider !== void 0) {
117836
+ const providerName = options.provider === false ? void 0 : options.provider;
117837
+ setIdentityProvider(paths, name, providerName);
117838
+ messages.push(providerName === void 0 ? `Identity "${name}" no longer has a provider.` : `Identity "${name}" now runs on provider "${providerName}" unless something more specific selects one.`);
117839
+ }
117809
117840
  if (options.allowAmbientCredential !== void 0) {
117810
117841
  setAllowAmbientCredential(paths, name, options.allowAmbientCredential);
117811
117842
  messages.push(`Identity "${name}" ${options.allowAmbientCredential ? "now allows" : "no longer allows"} an ambient credential.`);
@@ -117836,6 +117867,7 @@ function registerIdentityCommand(program2, deps) {
117836
117867
  }),
117837
117868
  [
117838
117869
  "agent-shim identity set work --default-profile client-acme",
117870
+ "agent-shim identity set minimax --provider minimax",
117839
117871
  "agent-shim identity set work --allow-ambient-credential",
117840
117872
  "agent-shim identity set work --credential-target oauthToken --credential op:op://vault/claude-work/token",
117841
117873
  "agent-shim identity set work --no-credential",
@@ -118352,7 +118384,7 @@ function collectCheckReport(params) {
118352
118384
  read,
118353
118385
  ...configProfileDecision.name === void 0 ? {} : { baseConfigProfile: configProfileDecision.name }
118354
118386
  }).input;
118355
- const providerName = cascadeProviderName(cascade);
118387
+ const providerName = cascadeProviderName(cascade, identityConfig?.provider);
118356
118388
  const provider = providerName === void 0 ? void 0 : loadCheckProvider(paths, providerName);
118357
118389
  const farmRoot = identityDecision.name === void 0 ? void 0 : import_node_path31.default.join(paths.identitiesDir, identityDecision.name);
118358
118390
  const settingsFiles = {
@@ -123579,6 +123611,7 @@ function runDoctor(params) {
123579
123611
  }
123580
123612
  }
123581
123613
  const validProfileNames = new Set(profileSources.keys());
123614
+ const validProviderNames = /* @__PURE__ */ new Set([...params.providers.map((entry) => entry.name), ...Object.keys(params.providerAliases ?? {})]);
123582
123615
  const validIdentityNames = /* @__PURE__ */ new Set();
123583
123616
  for (const entry of params.identities) {
123584
123617
  const validated2 = validate3(IdentitySchema, entry, "identity", entry.name);
@@ -123588,35 +123621,47 @@ function runDoctor(params) {
123588
123621
  }
123589
123622
  validIdentityNames.add(entry.name);
123590
123623
  const defaultProfile = validated2.data.defaultConfigProfile;
123624
+ let consistent = true;
123591
123625
  if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
123626
+ consistent = false;
123592
123627
  push(
123593
123628
  "identity",
123594
123629
  "fail",
123595
123630
  `Identity "${entry.name}" names configuration profile "${defaultProfile}" as its default, but no such profile exists.`,
123596
123631
  entry.name
123597
123632
  );
123598
- } else {
123599
- const credential = validated2.data.credential;
123600
- const cache = credential === void 0 ? { text: "", unreadable: false } : cacheClause(params.credentialCache, `identity ${entry.name}`, credential);
123601
- push(
123602
- "identity",
123603
- cache.unreadable ? "fail" : "pass",
123604
- `${entry.name} is valid and authenticates with ${credential === void 0 ? "its stored login" : `credential ${describeCredential(credential)}`}${cache.text}.`,
123605
- entry.name
123606
- );
123607
- const signIn = decideSignIn({
123608
- hasCredentialBlock: credential !== void 0,
123609
- keychainItem: params.platform === "darwin" && params.run !== void 0 ? keychainHasItem(params.run, entry.farmRoot) : void 0,
123610
- credentialsFile: entry.credentialsFilePresent,
123611
- platform: params.platform
123612
- });
123613
- push(
123614
- "sign-in",
123615
- signIn.state === "signed-out" ? "warn" : "pass",
123616
- signIn.state === "signed-out" ? `${entry.name} is signed out (${signIn.detail}). Run \`agent-shim run @${entry.name}\` and sign in.` : `${entry.name} ${signIn.state === "token" ? "uses a credential block" : "is signed in"} (${signIn.detail}).`,
123617
- entry.name
123618
- );
123619
123633
  }
123634
+ const identityProvider = validated2.data.provider;
123635
+ if (identityProvider !== void 0 && !validProviderNames.has(identityProvider)) {
123636
+ consistent = false;
123637
+ push("identity", "fail", `Identity "${entry.name}" names provider "${identityProvider}" as its provider, but no such provider exists.`, entry.name);
123638
+ }
123639
+ if (!consistent) {
123640
+ continue;
123641
+ }
123642
+ const credential = validated2.data.credential;
123643
+ const cache = credential === void 0 ? { text: "", unreadable: false } : cacheClause(params.credentialCache, `identity ${entry.name}`, credential);
123644
+ const authenticatesWith = credential !== void 0 ? `credential ${describeCredential(credential)}` : identityProvider !== void 0 ? `the credential of provider "${identityProvider}"` : "its stored login";
123645
+ push(
123646
+ "identity",
123647
+ cache.unreadable ? "fail" : "pass",
123648
+ `${entry.name} is valid and authenticates with ${authenticatesWith}${cache.text}.`,
123649
+ entry.name
123650
+ );
123651
+ const standing = credential !== void 0 ? "uses a credential block" : identityProvider !== void 0 ? `exists for provider "${identityProvider}"` : "is signed in";
123652
+ const signIn = decideSignIn({
123653
+ hasCredentialBlock: credential !== void 0,
123654
+ provider: identityProvider,
123655
+ keychainItem: params.platform === "darwin" && params.run !== void 0 ? keychainHasItem(params.run, entry.farmRoot) : void 0,
123656
+ credentialsFile: entry.credentialsFilePresent,
123657
+ platform: params.platform
123658
+ });
123659
+ push(
123660
+ "sign-in",
123661
+ signIn.state === "signed-out" ? "warn" : "pass",
123662
+ signIn.state === "signed-out" ? `${entry.name} is signed out (${signIn.detail}). Run \`agent-shim run @${entry.name}\` and sign in.` : `${entry.name} ${standing} (${signIn.detail}).`,
123663
+ entry.name
123664
+ );
123620
123665
  }
123621
123666
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
123622
123667
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
@@ -133043,6 +133088,7 @@ var storedMutationOutput = (kind, action, value2) => external_exports.strictObje
133043
133088
  var IdentityValueSchema = external_exports.strictObject({
133044
133089
  name: external_exports.string(),
133045
133090
  defaultConfigProfile: external_exports.string().optional(),
133091
+ provider: external_exports.string().optional(),
133046
133092
  allowAmbientCredential: external_exports.boolean(),
133047
133093
  credential: CredentialSummarySchema2.optional(),
133048
133094
  anchors: external_exports.array(AnchorSchema).optional()
@@ -133062,6 +133108,8 @@ var ConfigIdentityAddOutputSchema = storedMutationOutput("identity", "created",
133062
133108
  var ConfigIdentitySetInputSchema = external_exports.strictObject({
133063
133109
  name: external_exports.string().min(1),
133064
133110
  defaultConfigProfile: external_exports.union([external_exports.string().min(1), external_exports.literal(false)]).optional(),
133111
+ /** The provider the identity exists for; `false` clears it. */
133112
+ provider: external_exports.union([external_exports.string().min(1), external_exports.literal(false)]).optional(),
133065
133113
  allowAmbientCredential: external_exports.boolean().optional(),
133066
133114
  credential: external_exports.union([external_exports.literal(false), CredentialWriteSchema(CREDENTIAL_TARGETS)]).optional(),
133067
133115
  /** `false` removes the anchors; a list replaces them. */
@@ -133206,6 +133254,7 @@ function identityValue(name, paths) {
133206
133254
  return {
133207
133255
  name: identity.name,
133208
133256
  ...identity.defaultConfigProfile === void 0 ? {} : { defaultConfigProfile: identity.defaultConfigProfile },
133257
+ ...identity.provider === void 0 ? {} : { provider: identity.provider },
133209
133258
  allowAmbientCredential: identity.allowAmbientCredential,
133210
133259
  ...identity.credential === void 0 ? {} : { credential: summariseCredential(identity.credential) },
133211
133260
  ...identity.anchors === void 0 ? {} : { anchors: identity.anchors }
@@ -133238,18 +133287,24 @@ function createConfigApiRouter(deps) {
133238
133287
  return { action: "created", kind: "identity", name: input2.name, value: identityValue(input2.name, paths) };
133239
133288
  })
133240
133289
  ),
133241
- set: route("/rest/config/identity/set", "Update an identity's default profile, ambient-credential setting, credential or anchors").input(ConfigIdentitySetInputSchema).output(ConfigIdentitySetOutputSchema).handler(
133290
+ set: route("/rest/config/identity/set", "Update an identity's default profile, provider, ambient-credential setting, credential or anchors").input(ConfigIdentitySetInputSchema).output(ConfigIdentitySetOutputSchema).handler(
133242
133291
  ({ input: input2 }) => guarded(() => {
133243
- requireChange(input2.defaultConfigProfile !== void 0 || input2.allowAmbientCredential !== void 0 || input2.credential !== void 0 || input2.anchors !== void 0, "defaultConfigProfile, allowAmbientCredential, credential or anchors");
133292
+ requireChange(input2.defaultConfigProfile !== void 0 || input2.provider !== void 0 || input2.allowAmbientCredential !== void 0 || input2.credential !== void 0 || input2.anchors !== void 0, "defaultConfigProfile, provider, allowAmbientCredential, credential or anchors");
133244
133293
  if (!identityExists(paths, input2.name)) {
133245
133294
  throw new IdentityNotFoundError(input2.name);
133246
133295
  }
133247
133296
  if (typeof input2.defaultConfigProfile === "string") {
133248
133297
  requireProfileExists(paths, input2.defaultConfigProfile);
133249
133298
  }
133299
+ if (typeof input2.provider === "string") {
133300
+ requireProviderExists(paths, input2.provider);
133301
+ }
133250
133302
  if (input2.defaultConfigProfile !== void 0) {
133251
133303
  setDefaultConfigProfile(paths, input2.name, input2.defaultConfigProfile === false ? void 0 : input2.defaultConfigProfile);
133252
133304
  }
133305
+ if (input2.provider !== void 0) {
133306
+ setIdentityProvider(paths, input2.name, input2.provider === false ? void 0 : input2.provider);
133307
+ }
133253
133308
  if (input2.allowAmbientCredential !== void 0) {
133254
133309
  setAllowAmbientCredential(paths, input2.name, input2.allowAmbientCredential);
133255
133310
  }
@@ -156467,6 +156522,12 @@ function rewriteReferences(paths, from, to2) {
156467
156522
  rewritten.push(`profile ${name}`);
156468
156523
  }
156469
156524
  }
156525
+ for (const entry of listIdentities(paths)) {
156526
+ if (entry.identity?.provider === from) {
156527
+ setIdentityProvider(paths, entry.name, to2);
156528
+ rewritten.push(`identity ${entry.name}`);
156529
+ }
156530
+ }
156470
156531
  const rules = readDirectoryRules(paths);
156471
156532
  const renamedRules = rules.rules.map((rule) => {
156472
156533
  const launch = renamedLaunch(rule.launch, from, to2);
@@ -157426,12 +157487,15 @@ function collectAccounts(fs31, paths, identity, probe) {
157426
157487
  requireIdentity(paths, identity);
157427
157488
  }
157428
157489
  const names = identity === void 0 ? listIdentities(paths).map((entry) => entry.name) : [identity];
157429
- return names.map((name) => ({
157430
- identity: name,
157431
- account: readAccountMetadata(fs31, paths.identitiesDir, name),
157432
- signIn: identitySignIn(fs31, probe, { farmRoot: import_node_path70.default.join(paths.identitiesDir, name), hasCredentialBlock: readIdentity(paths, name)?.credential !== void 0 }),
157433
- usage: readUsageSnapshot(fs31, paths.usageSnapshotsDir, name)
157434
- }));
157490
+ return names.map((name) => {
157491
+ const stored2 = readIdentity(paths, name);
157492
+ return {
157493
+ identity: name,
157494
+ account: readAccountMetadata(fs31, paths.identitiesDir, name),
157495
+ signIn: identitySignIn(fs31, probe, { farmRoot: import_node_path70.default.join(paths.identitiesDir, name), hasCredentialBlock: stored2?.credential !== void 0, provider: stored2?.provider }),
157496
+ usage: readUsageSnapshot(fs31, paths.usageSnapshotsDir, name)
157497
+ };
157498
+ });
157435
157499
  }
157436
157500
  function formatAccounts(views) {
157437
157501
  if (views.length === 0) {