claude-use 11.32.0 → 11.34.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/index.cjs CHANGED
@@ -231,6 +231,9 @@ function isHttpProvider(provider) {
231
231
  function isCodexProvider(provider) {
232
232
  return provider.kind === "codex";
233
233
  }
234
+ function isRouterProvider(provider) {
235
+ return provider.kind === "router";
236
+ }
234
237
  function isAntigravityProvider(provider) {
235
238
  return provider.kind === "antigravity";
236
239
  }
@@ -245,7 +248,7 @@ function isTimeZone(name) {
245
248
  return false;
246
249
  }
247
250
  }
248
- var import_node_path2, import_zod, import_trilean, CATEGORY_NAMES, OVERRIDABLE_CATEGORIES, CategoryMapSchema, DURATION_RE, DurationSchema, WhenConditionObjectSchema, WhenSchema, EntryValueSchema, ENTRY_KEY_RE, EntriesSchema, LaunchSchema, CREDENTIAL_TARGETS, PROVIDER_CREDENTIAL_TARGETS, CREDENTIAL_TARGET_VARS, ArgvSchema, CommandSourceOptions, EnvCredentialSourceSchema, FileCredentialSourceSchema, CommandCredentialSourceSchema, OpCredentialSourceSchema, KeychainCredentialSourceSchema, LiteralCredentialSourceSchema, CredentialSourceSchema, RemoteCredentialSourceSchema, CREDENTIAL_CACHE_STORES, CredentialCacheSchema, CredentialSchema, ProviderCredentialSchema, RESERVED_PROVIDER_ENV_KEYS, ProviderEnvSchema, PROVIDER_KINDS, ANTIGRAVITY_MODES, CLAUDE_TIERS, ANTIGRAVITY_EFFORTS, ANTIGRAVITY_DEFAULT_MODE, ANTIGRAVITY_DEFAULT_MODEL, ANTIGRAVITY_DEFAULT_TIER_MODELS, ANTIGRAVITY_DEFAULT_EFFORT, AntigravityModelTargetSchema, AntigravityProviderConfigSchema, CODEX_EFFORTS, CODEX_LOGINS, CODEX_DEFAULT_LOGIN, CODEX_DEFAULT_MODEL, CODEX_DEFAULT_TIER_MODELS, CODEX_DEFAULT_EFFORT, CodexProviderConfigSchema, ProviderRouteSchema, PickerModelSchema, PickerModelsSchema, ProviderCatalogueSchema, HttpProviderSchema, CodexProviderSchema, AntigravityProviderSchema, ProviderSchema, ConfigProfileSchema, POOL_SELECTOR_PREFIX, POOL_NAME_PATTERN, IDENTITY_NAME_PATTERN, PoolNameSchema, SELECTOR_RE, IdentitySelectorSchema, PoolMemberSchema, POOL_PREFERENCES, PoolPreferenceSchema, WHOLE_PERCENT_MAX, CompactAtUtilisationSchema, PoolSchema, WEEKDAYS, AnchorSchema, JOB_WINDOWS, JobTriggerSchema, JobTargetSchema, JobSchema, DirectoryRuleSchema, DirectoryRulesSchema, PortableConfigSchema, HEADROOM_MODES, HEADROOM_CCR_MODES, HEADROOM_ROLLOUT_CHANNELS, INTERCEPT_CHANNELS, READ_MATURATION_CHANNELS, HeadroomGlobalConfigObjectSchema, HeadroomGlobalConfigSchema, HEADROOM_DEFAULT_SOURCE, HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES, FrontDoorGlobalConfigSchema, UPDATE_MODES, UpdateGlobalConfigSchema, GlobalConfigSchema, IdentitySchema, CategoryClassificationSchema, CategoryClassificationOverlaySchema, SHIPPED_CATEGORY_DEFAULTS;
251
+ var import_node_path2, import_zod, import_trilean, CATEGORY_NAMES, OVERRIDABLE_CATEGORIES, CategoryMapSchema, DURATION_RE, DurationSchema, WhenConditionObjectSchema, WhenSchema, EntryValueSchema, ENTRY_KEY_RE, EntriesSchema, LaunchSchema, CREDENTIAL_TARGETS, PROVIDER_CREDENTIAL_TARGETS, CREDENTIAL_TARGET_VARS, ArgvSchema, CommandSourceOptions, EnvCredentialSourceSchema, FileCredentialSourceSchema, CommandCredentialSourceSchema, OpCredentialSourceSchema, KeychainCredentialSourceSchema, LiteralCredentialSourceSchema, CredentialSourceSchema, RemoteCredentialSourceSchema, CREDENTIAL_CACHE_STORES, CredentialCacheSchema, CredentialSchema, ProviderCredentialSchema, RESERVED_PROVIDER_ENV_KEYS, ProviderEnvSchema, PROVIDER_KINDS, ANTIGRAVITY_MODES, CLAUDE_TIERS, ANTIGRAVITY_EFFORTS, ANTIGRAVITY_DEFAULT_MODE, ANTIGRAVITY_DEFAULT_MODEL, ANTIGRAVITY_DEFAULT_TIER_MODELS, ANTIGRAVITY_DEFAULT_EFFORT, AntigravityModelTargetSchema, AntigravityProviderConfigSchema, CODEX_EFFORTS, CODEX_LOGINS, CODEX_DEFAULT_LOGIN, CODEX_DEFAULT_MODEL, CODEX_DEFAULT_TIER_MODELS, CODEX_DEFAULT_EFFORT, CodexProviderConfigSchema, ProviderRouteSchema, PickerModelSchema, PickerModelsSchema, ProviderCatalogueSchema, HttpProviderSchema, CodexProviderSchema, RouterProviderSchema, AntigravityProviderSchema, ProviderSchema, SELECTION_MODES, VirtualModelSchema, ModelPricesSchema, ModelsConfigSchema, ModelsLayerSchema, ConfigProfileSchema, POOL_SELECTOR_PREFIX, POOL_NAME_PATTERN, IDENTITY_NAME_PATTERN, PoolNameSchema, SELECTOR_RE, IdentitySelectorSchema, PoolMemberSchema, POOL_PREFERENCES, PoolPreferenceSchema, WHOLE_PERCENT_MAX, CompactAtUtilisationSchema, PoolSchema, WEEKDAYS, AnchorSchema, JOB_WINDOWS, JobTriggerSchema, JobTargetSchema, JobSchema, DirectoryRuleSchema, DirectoryRulesSchema, PortableConfigSchema, HEADROOM_MODES, HEADROOM_CCR_MODES, HEADROOM_ROLLOUT_CHANNELS, INTERCEPT_CHANNELS, READ_MATURATION_CHANNELS, HeadroomGlobalConfigObjectSchema, HeadroomGlobalConfigSchema, HEADROOM_DEFAULT_SOURCE, HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES, FrontDoorGlobalConfigSchema, UPDATE_MODES, UpdateGlobalConfigSchema, GlobalConfigSchema, IdentitySchema, CategoryClassificationSchema, CategoryClassificationOverlaySchema, SHIPPED_CATEGORY_DEFAULTS;
249
252
  var init_schema = __esm({
250
253
  "src/config/schema.ts"() {
251
254
  "use strict";
@@ -290,6 +293,8 @@ var init_schema = __esm({
290
293
  delay: import_zod.z.string().regex(DURATION_RE, "must be a duration such as 5s or 500ms").optional(),
291
294
  /** Whether the launcher waits for a key press after its own output before it starts claude. Takes the place of `delay` when both are set. Applies only when a terminal is watching. */
292
295
  pause: import_zod.z.boolean().optional(),
296
+ /** Whether a provider session routes provider-qualified model names (`z/glm-5.3`, `auto`) through the `models` block to the provider that serves them, instead of sending every request to the launch provider. */
297
+ routeByName: import_zod.z.boolean().optional(),
293
298
  /** Whether the launcher's report, for a launch that picks from a pool, lists every member the pick ranked with why each stands where it does, and not only the member it picked. */
294
299
  explain: import_zod.z.boolean().optional()
295
300
  });
@@ -350,7 +355,7 @@ var init_schema = __esm({
350
355
  ProviderEnvSchema = import_zod.z.record(import_zod.z.string().min(1), import_zod.z.string()).refine((env) => !Object.keys(env).some((key) => RESERVED_PROVIDER_ENV_KEYS.includes(key)), {
351
356
  message: `env may not set ${RESERVED_PROVIDER_ENV_KEYS.join(", ")}: the credential block's target sets one and agent-shim removes the others`
352
357
  });
353
- PROVIDER_KINDS = ["http", "codex", "antigravity"];
358
+ PROVIDER_KINDS = ["http", "codex", "antigravity", "router"];
354
359
  ANTIGRAVITY_MODES = ["cloud", "local"];
355
360
  CLAUDE_TIERS = ["fable", "opus", "sonnet", "haiku"];
356
361
  ANTIGRAVITY_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"];
@@ -463,6 +468,11 @@ var init_schema = __esm({
463
468
  pickerModels: PickerModelsSchema.optional(),
464
469
  codex: CodexProviderConfigSchema.optional()
465
470
  });
471
+ RouterProviderSchema = import_zod.z.strictObject({
472
+ $schema: import_zod.z.string().optional(),
473
+ kind: import_zod.z.literal("router"),
474
+ displayName: import_zod.z.string().min(1)
475
+ });
466
476
  AntigravityProviderSchema = import_zod.z.strictObject({
467
477
  $schema: import_zod.z.string().optional(),
468
478
  kind: import_zod.z.literal("antigravity"),
@@ -473,14 +483,55 @@ var init_schema = __esm({
473
483
  pickerModels: PickerModelsSchema.optional(),
474
484
  antigravity: AntigravityProviderConfigSchema.optional()
475
485
  });
476
- ProviderSchema = import_zod.z.discriminatedUnion("kind", [HttpProviderSchema, CodexProviderSchema, AntigravityProviderSchema]);
486
+ ProviderSchema = import_zod.z.discriminatedUnion("kind", [HttpProviderSchema, CodexProviderSchema, AntigravityProviderSchema, RouterProviderSchema]);
487
+ SELECTION_MODES = ["ordered", "most-allowance", "soonest-reset", "spread", "adaptive", "lowest-latency", "highest-success", "cheapest"];
488
+ VirtualModelSchema = import_zod.z.strictObject({
489
+ /** How the virtual model chooses its member for a request. */
490
+ mode: import_zod.z.enum(SELECTION_MODES),
491
+ /** The members to choose among, in preference order where the mode reads order. Each names a target (`z/glm-5.3`), an account model (`acct:work/opus`), another virtual model, or a family (`family:<canonical>`). */
492
+ members: import_zod.z.array(import_zod.z.string().min(1)).min(1)
493
+ });
494
+ ModelPricesSchema = import_zod.z.strictObject({
495
+ input: import_zod.z.number().positive(),
496
+ output: import_zod.z.number().positive()
497
+ });
498
+ ModelsConfigSchema = import_zod.z.strictObject({
499
+ /** Virtual models, by name. */
500
+ virtual: import_zod.z.record(import_zod.z.string().min(1), VirtualModelSchema).optional(),
501
+ /** Which model ref each Claude tier (`fable`, `opus`, `sonnet`, `haiku`) points at: a concrete target, an account model, a virtual model, or a family. */
502
+ tiers: import_zod.z.strictObject({
503
+ fable: import_zod.z.string().min(1).optional(),
504
+ opus: import_zod.z.string().min(1).optional(),
505
+ sonnet: import_zod.z.string().min(1).optional(),
506
+ haiku: import_zod.z.string().min(1).optional()
507
+ }).optional(),
508
+ /** Prices per model ref, for the `cheapest` mode. */
509
+ prices: import_zod.z.record(import_zod.z.string().min(1), ModelPricesSchema).optional(),
510
+ /** Model refs hidden from the picker and from routing, whatever else names them. */
511
+ hide: import_zod.z.array(import_zod.z.string().min(1)).optional(),
512
+ /** When set, the only model refs visible; everything else is hidden. */
513
+ show: import_zod.z.array(import_zod.z.string().min(1)).optional()
514
+ });
515
+ ModelsLayerSchema = import_zod.z.strictObject({
516
+ virtual: import_zod.z.record(import_zod.z.string().min(1), import_zod.z.union([VirtualModelSchema, import_zod.z.null()])).optional(),
517
+ tiers: import_zod.z.strictObject({
518
+ fable: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]).optional(),
519
+ opus: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]).optional(),
520
+ sonnet: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]).optional(),
521
+ haiku: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]).optional()
522
+ }).optional(),
523
+ prices: import_zod.z.record(import_zod.z.string().min(1), import_zod.z.union([ModelPricesSchema, import_zod.z.null()])).optional(),
524
+ hide: import_zod.z.array(import_zod.z.string().min(1)).optional(),
525
+ show: import_zod.z.array(import_zod.z.string().min(1)).optional()
526
+ });
477
527
  ConfigProfileSchema = import_zod.z.strictObject({
478
528
  $schema: import_zod.z.string().optional(),
479
529
  description: import_zod.z.string().optional(),
480
530
  extends: import_zod.z.array(import_zod.z.string().min(1)).optional(),
481
531
  categories: CategoryMapSchema.optional(),
482
532
  entries: EntriesSchema.optional(),
483
- launch: LaunchSchema.optional()
533
+ launch: LaunchSchema.optional(),
534
+ models: ModelsLayerSchema.optional()
484
535
  });
485
536
  POOL_SELECTOR_PREFIX = "pool:";
486
537
  POOL_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._-]*";
@@ -590,6 +641,7 @@ var init_schema = __esm({
590
641
  frontdoor: FrontDoorGlobalConfigSchema.optional(),
591
642
  update: UpdateGlobalConfigSchema.optional(),
592
643
  pools: import_zod.z.record(PoolNameSchema, PoolSchema).optional(),
644
+ models: ModelsConfigSchema.optional(),
593
645
  jobs: import_zod.z.record(PoolNameSchema, JobSchema).optional()
594
646
  });
595
647
  IdentitySchema = import_zod.z.strictObject({
@@ -1366,6 +1418,7 @@ function flattenLayers(layers, options) {
1366
1418
  const rules = /* @__PURE__ */ new Map();
1367
1419
  const launch = {};
1368
1420
  const diagnostics = [];
1421
+ let models;
1369
1422
  for (const layer of layers) {
1370
1423
  if (layer.categories !== void 0) {
1371
1424
  for (const [name, value] of Object.entries(layer.categories)) {
@@ -1404,6 +1457,9 @@ function flattenLayers(layers, options) {
1404
1457
  launch.explain = layer.launch.explain;
1405
1458
  }
1406
1459
  }
1460
+ if (layer.models !== void 0) {
1461
+ models = mergeModels(models, layer.models);
1462
+ }
1407
1463
  if (layer.entries === void 0) {
1408
1464
  continue;
1409
1465
  }
@@ -1466,7 +1522,7 @@ function flattenLayers(layers, options) {
1466
1522
  rules.set(pattern, rule);
1467
1523
  }
1468
1524
  }
1469
- return { categories, rules, launch, diagnostics };
1525
+ return { categories, rules, launch, ...models === void 0 ? {} : { models }, diagnostics };
1470
1526
  }
1471
1527
  function orderedKeys(layer) {
1472
1528
  const present = new Set(Object.keys(layer.entries ?? {}));
@@ -1491,6 +1547,67 @@ function matchingRules(flattened, relPath) {
1491
1547
  }
1492
1548
  return matches.sort((a, b) => compareSpecificity(b, a));
1493
1549
  }
1550
+ function mergeModels(current, layer) {
1551
+ const pieces = { ...current ?? {} };
1552
+ if (layer.virtual !== void 0) {
1553
+ const virtual = {};
1554
+ for (const [name, definition] of Object.entries(current?.virtual ?? {})) {
1555
+ if (layer.virtual[name] === void 0) {
1556
+ virtual[name] = definition;
1557
+ }
1558
+ }
1559
+ for (const [name, definition] of Object.entries(layer.virtual)) {
1560
+ if (definition !== null) {
1561
+ virtual[name] = definition;
1562
+ }
1563
+ }
1564
+ pieces.virtual = virtual;
1565
+ }
1566
+ if (layer.tiers !== void 0) {
1567
+ const tiers = {};
1568
+ for (const tier of ["fable", "opus", "sonnet", "haiku"]) {
1569
+ const ref = layer.tiers[tier];
1570
+ const inherited = current?.tiers?.[tier];
1571
+ if (ref !== null && ref !== void 0) {
1572
+ tiers[tier] = ref;
1573
+ } else if (ref === void 0 && inherited !== void 0) {
1574
+ tiers[tier] = inherited;
1575
+ }
1576
+ }
1577
+ pieces.tiers = tiers;
1578
+ }
1579
+ if (layer.prices !== void 0) {
1580
+ const priced = {};
1581
+ for (const [ref, prices] of Object.entries(current?.prices ?? {})) {
1582
+ if (layer.prices[ref] === void 0) {
1583
+ priced[ref] = prices;
1584
+ }
1585
+ }
1586
+ for (const [ref, prices] of Object.entries(layer.prices)) {
1587
+ if (prices !== null) {
1588
+ priced[ref] = prices;
1589
+ }
1590
+ }
1591
+ pieces.prices = priced;
1592
+ }
1593
+ if (layer.hide !== void 0) {
1594
+ pieces.hide = layer.hide;
1595
+ }
1596
+ if (layer.show !== void 0) {
1597
+ pieces.show = layer.show;
1598
+ }
1599
+ const merged = modelsOf(pieces);
1600
+ return Object.keys(merged).length === 0 ? void 0 : merged;
1601
+ }
1602
+ function modelsOf(pieces) {
1603
+ return {
1604
+ ...pieces.virtual === void 0 || Object.keys(pieces.virtual).length === 0 ? {} : { virtual: pieces.virtual },
1605
+ ...pieces.tiers === void 0 || Object.keys(pieces.tiers).length === 0 ? {} : { tiers: pieces.tiers },
1606
+ ...pieces.prices === void 0 || Object.keys(pieces.prices).length === 0 ? {} : { prices: pieces.prices },
1607
+ ...pieces.hide === void 0 || pieces.hide.length === 0 ? {} : { hide: [...pieces.hide] },
1608
+ ...pieces.show === void 0 || pieces.show.length === 0 ? {} : { show: [...pieces.show] }
1609
+ };
1610
+ }
1494
1611
  var init_flatten = __esm({
1495
1612
  "src/resolve/flatten.ts"() {
1496
1613
  "use strict";
@@ -1866,7 +1983,8 @@ function profileLayers(rootName, load, startId) {
1866
1983
  ...source.profile.categories === void 0 ? {} : { categories: source.profile.categories },
1867
1984
  ...source.profile.entries === void 0 ? {} : { entries: source.profile.entries },
1868
1985
  ...source.entryOrder === void 0 ? {} : { entryOrder: source.entryOrder },
1869
- ...source.profile.launch === void 0 ? {} : { launch: source.profile.launch }
1986
+ ...source.profile.launch === void 0 ? {} : { launch: source.profile.launch },
1987
+ ...source.profile.models === void 0 ? {} : { models: source.profile.models }
1870
1988
  });
1871
1989
  nextId += 1;
1872
1990
  }
@@ -1954,7 +2072,8 @@ function assembleCascade(input) {
1954
2072
  ...source.config.categories === void 0 ? {} : { categories: source.config.categories },
1955
2073
  ...source.config.entries === void 0 ? {} : { entries: source.config.entries },
1956
2074
  ...source.entryOrder === void 0 ? {} : { entryOrder: source.entryOrder },
1957
- ...source.config.launch === void 0 ? {} : { launch: source.config.launch }
2075
+ ...source.config.launch === void 0 ? {} : { launch: source.config.launch },
2076
+ ...source.config.models === void 0 ? {} : { models: source.config.models }
1958
2077
  });
1959
2078
  nextId += 1;
1960
2079
  }
@@ -2983,7 +3102,7 @@ var init_package = __esm({
2983
3102
  "package.json"() {
2984
3103
  package_default = {
2985
3104
  name: "claude-use",
2986
- version: "11.32.0",
3105
+ version: "11.34.0",
2987
3106
  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.",
2988
3107
  license: "MIT",
2989
3108
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -6684,7 +6803,7 @@ function describeProviderEndpoint(provider) {
6684
6803
  if (isAntigravityProvider(provider)) {
6685
6804
  return (provider.antigravity?.mode ?? ANTIGRAVITY_DEFAULT_MODE) === "local" ? "antigravity local (ACP daemon)" : "antigravity cloud (Gemini API)";
6686
6805
  }
6687
- return provider.baseUrl;
6806
+ return isRouterProvider(provider) ? "the models block" : provider.baseUrl;
6688
6807
  }
6689
6808
  function isEmptyCodexConfig(config) {
6690
6809
  return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && config.login === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
@@ -6706,10 +6825,16 @@ function addProvider(paths, name, input) {
6706
6825
  if (kind === "antigravity" && input.baseUrl !== void 0) {
6707
6826
  throw new ProviderKindMismatchError("An antigravity provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
6708
6827
  }
6828
+ if (kind === "router" && input.baseUrl !== void 0) {
6829
+ throw new ProviderKindMismatchError("A router provider has no base URL: it resolves each request's model name through the models block. Drop --base-url.");
6830
+ }
6709
6831
  if (kind === "http" && input.baseUrl === void 0) {
6710
6832
  throw new ProviderKindMismatchError("An http provider needs --base-url.");
6711
6833
  }
6712
- if (kind !== "http" && input.catalogue !== void 0) {
6834
+ if (kind === "router" && (input.sources.length > 0 || input.target !== void 0 || input.env !== void 0 || input.pickerModels !== void 0)) {
6835
+ throw new ProviderKindMismatchError("A router provider has no credential, env or picker models of its own: the models block's targets carry their own.");
6836
+ }
6837
+ if (kind !== "http" && kind !== "router" && input.catalogue !== void 0) {
6713
6838
  throw new ProviderKindMismatchError("A model catalogue applies only to an http provider.");
6714
6839
  }
6715
6840
  if (kind !== "codex" && !isEmptyCodexConfig(input.codex)) {
@@ -6725,7 +6850,7 @@ function addProvider(paths, name, input) {
6725
6850
  ...input.pickerModels === void 0 || input.pickerModels.length === 0 ? {} : { pickerModels: input.pickerModels }
6726
6851
  };
6727
6852
  const parsed = ProviderSchema.safeParse(
6728
- kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : kind === "antigravity" ? { kind, ...shared, ...isEmptyAntigravityConfig(input.antigravity) ? {} : { antigravity: input.antigravity } } : { ...shared, baseUrl: input.baseUrl, ...input.catalogue === void 0 ? {} : { catalogue: input.catalogue } }
6853
+ kind === "router" ? { kind, displayName: input.displayName } : kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : kind === "antigravity" ? { kind, ...shared, ...isEmptyAntigravityConfig(input.antigravity) ? {} : { antigravity: input.antigravity } } : { ...shared, baseUrl: input.baseUrl, ...input.catalogue === void 0 ? {} : { catalogue: input.catalogue } }
6729
6854
  );
6730
6855
  if (!parsed.success) {
6731
6856
  throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
@@ -6739,15 +6864,18 @@ function updateProvider(paths, name, input) {
6739
6864
  throw new ProviderNotFoundError(name);
6740
6865
  }
6741
6866
  const unset = new Set(input.unsetEnv);
6742
- const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
6867
+ const existingEnv = isRouterProvider(existing) ? {} : existing.env;
6868
+ const env = Object.fromEntries(Object.entries({ ...existingEnv, ...input.env }).filter(([key]) => !unset.has(key)));
6743
6869
  const unsetPicker = new Set(input.unsetPickerModels);
6744
6870
  const added = new Map((input.pickerModels ?? []).map((entry) => [entry.model, entry]));
6871
+ const existingPicker = isRouterProvider(existing) ? [] : existing.pickerModels;
6745
6872
  const pickerModels = [
6746
- ...(existing.pickerModels ?? []).filter((entry) => !unsetPicker.has(entry.model)).map((entry) => added.get(entry.model) ?? entry),
6747
- ...[...added.values()].filter((entry) => !(existing.pickerModels ?? []).some((kept) => kept.model === entry.model))
6873
+ ...(existingPicker ?? []).filter((entry) => !unsetPicker.has(entry.model)).map((entry) => added.get(entry.model) ?? entry),
6874
+ ...[...added.values()].filter((entry) => !(existingPicker ?? []).some((kept) => kept.model === entry.model))
6748
6875
  ];
6749
- const target = input.target ?? existing.credential.target;
6750
- const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
6876
+ const router = isRouterProvider(existing);
6877
+ const target = router ? void 0 : input.target ?? existing.credential.target;
6878
+ const cache = router ? void 0 : input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
6751
6879
  if (isCodexProvider(existing) && input.baseUrl !== void 0) {
6752
6880
  throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
6753
6881
  }
@@ -6765,10 +6893,12 @@ function updateProvider(paths, name, input) {
6765
6893
  }
6766
6894
  const shared = {
6767
6895
  ...input.displayName === void 0 ? {} : { displayName: input.displayName },
6768
- credential: {
6769
- sources: input.sources ?? existing.credential.sources,
6770
- ...target === void 0 ? {} : { target },
6771
- ...cache === void 0 ? {} : { cache }
6896
+ ...router ? {} : {
6897
+ credential: {
6898
+ sources: input.sources ?? existing.credential.sources,
6899
+ ...target === void 0 ? {} : { target },
6900
+ ...cache === void 0 ? {} : { cache }
6901
+ }
6772
6902
  },
6773
6903
  env: Object.keys(env).length === 0 ? void 0 : env,
6774
6904
  pickerModels: pickerModels.length === 0 ? void 0 : pickerModels
@@ -6788,6 +6918,8 @@ function updateProvider(paths, name, input) {
6788
6918
  ...existing.antigravity?.models === void 0 && input.antigravity?.models === void 0 ? {} : { models: { ...existing.antigravity?.models, ...input.antigravity?.models } }
6789
6919
  };
6790
6920
  candidate = { ...existing, ...shared, antigravity: isEmptyAntigravityConfig(antigravity) ? void 0 : antigravity };
6921
+ } else if (router) {
6922
+ candidate = { kind: "router", ...shared };
6791
6923
  } else {
6792
6924
  const catalogue = input.catalogue === void 0 ? existing.catalogue : input.catalogue === false ? void 0 : input.catalogue;
6793
6925
  candidate = { ...existing, ...shared, catalogue, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
@@ -6837,6 +6969,9 @@ function resolveProvider(params) {
6837
6969
  return selection;
6838
6970
  }
6839
6971
  const { name, definition } = selection.provider;
6972
+ if (isRouterProvider(definition)) {
6973
+ return { ok: true, provider: { name, definition, credential: { target: "bearer", token: "", source: { env: "AGENT_SHIM_ROUTER" }, warnings: [] } }, warnings: [] };
6974
+ }
6840
6975
  if (params.credentials === void 0) {
6841
6976
  return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
6842
6977
  }
@@ -7814,7 +7949,7 @@ function buildEnv(params) {
7814
7949
  env.CLAUDE_CONFIG_DIR = import_node_path36.default.join(params.identitiesDir, params.resolvedIdentityName);
7815
7950
  }
7816
7951
  if (params.provider !== void 0) {
7817
- const providerEnv = params.provider.definition.env ?? {};
7952
+ const providerEnv = params.provider.definition.kind !== void 0 && params.provider.definition.kind !== "http" ? {} : params.provider.definition.env ?? {};
7818
7953
  providerCustomHeaders = providerEnv.ANTHROPIC_CUSTOM_HEADERS;
7819
7954
  env.ANTHROPIC_BASE_URL = void 0;
7820
7955
  env.AGENT_SHIM_PROVIDER = params.provider.definition.displayName;
@@ -8477,8 +8612,8 @@ function catalogueRows(provider, models) {
8477
8612
  return filterCatalogue(models, provider.catalogue).map((model) => ({ model: model.id, label: labelOf(model.id), description: describedAs(model.id, model.name === void 0 ? "Listed by the provider" : `Listed by the provider as ${model.name}`), behavesAs: TIER_BEHAVES_AS[tier] })).sort((left, right) => left.model.localeCompare(right.model));
8478
8613
  }
8479
8614
  function providerModelRows(provider, catalogue = []) {
8480
- const derived = provider.kind === "codex" ? codexRows(provider) : provider.kind === "antigravity" ? antigravityRows(provider) : httpRows(provider);
8481
- const declared = (provider.pickerModels ?? []).map(declaredRow);
8615
+ const derived = provider.kind === "codex" ? codexRows(provider) : provider.kind === "antigravity" ? antigravityRows(provider) : provider.kind === "router" ? [] : httpRows(provider);
8616
+ const declared = provider.kind === "router" ? [] : (provider.pickerModels ?? []).map(declaredRow);
8482
8617
  const declaredModels = new Set(declared.map((row) => row.model));
8483
8618
  const named = [...derived.filter((row) => !declaredModels.has(row.model)), ...declared];
8484
8619
  const taken = new Set(named.map((row) => labelOf(row.model)));
@@ -9096,7 +9231,7 @@ function resolveLaunch(params) {
9096
9231
  });
9097
9232
  const credentialNames = decided.identityCredentialBlock === void 0 ? [] : Object.keys(credentialVariables(decided.identityCredentialBlock.target ?? "bearer", ""));
9098
9233
  const environment = [.../* @__PURE__ */ new Set([...Object.keys(built).filter((name) => built[name] !== env[name]), ...credentialNames])].sort();
9099
- const credential = decided.provider !== void 0 ? { subject: "provider", name: decided.provider.name, summary: summariseCredential(decided.provider.definition.credential) } : decided.identityCredentialBlock !== void 0 && decided.farmIdentity !== void 0 ? { subject: "identity", name: decided.farmIdentity, summary: summariseCredential(decided.identityCredentialBlock) } : void 0;
9234
+ const credential = decided.provider !== void 0 ? isRouterProvider(decided.provider.definition) ? void 0 : { subject: "provider", name: decided.provider.name, summary: summariseCredential(decided.provider.definition.credential) } : decided.identityCredentialBlock !== void 0 && decided.farmIdentity !== void 0 ? { subject: "identity", name: decided.farmIdentity, summary: summariseCredential(decided.identityCredentialBlock) } : void 0;
9100
9235
  return {
9101
9236
  decision: decided.decision,
9102
9237
  flags: decided.flags,
@@ -14327,11 +14462,12 @@ function cachedState(cacheEnv, owner, block) {
14327
14462
  function buildCredentialReport(provider, identity, identityName, cacheEnv) {
14328
14463
  const identityCredential = identity?.credential === void 0 ? void 0 : summariseCredential(identity.credential);
14329
14464
  const identityCached = identity?.credential === void 0 || identityName === void 0 ? void 0 : cachedState(cacheEnv, `identity ${identityName}`, identity.credential);
14330
- const providerCached = provider?.definition === void 0 ? void 0 : cachedState(cacheEnv, `provider ${provider.name}`, provider.definition.credential);
14465
+ const providerCredential = provider?.definition !== void 0 && !isRouterProvider(provider.definition) ? provider.definition.credential : void 0;
14466
+ const providerCached = providerCredential === void 0 || provider === void 0 ? void 0 : cachedState(cacheEnv, `provider ${provider.name}`, providerCredential);
14331
14467
  return {
14332
14468
  applies: provider !== void 0 ? "provider" : identityCredential !== void 0 ? "identity" : "stored-login",
14333
14469
  ...provider === void 0 ? {} : {
14334
- provider: provider.problem === void 0 ? { name: provider.name, credential: summariseCredential(provider.definition.credential), ...providerCached === void 0 ? {} : { cached: providerCached } } : { name: provider.name, problem: provider.problem }
14470
+ provider: provider.problem === void 0 ? { name: provider.name, ...providerCredential === void 0 ? {} : { credential: summariseCredential(providerCredential), ...providerCached === void 0 ? {} : { cached: providerCached } } } : { name: provider.name, problem: provider.problem }
14335
14471
  },
14336
14472
  ...identityCredential === void 0 ? {} : { identity: identityCredential },
14337
14473
  ...identityCached === void 0 ? {} : { identityCached }
@@ -14426,7 +14562,7 @@ function formatCheckReport(report) {
14426
14562
  const { credential } = report;
14427
14563
  if (credential.provider !== void 0) {
14428
14564
  lines.push(
14429
- ` Provider ${credential.provider.name}: ${credential.provider.credential === void 0 ? `unusable: ${credential.provider.problem}` : formatCredentialSummary(credential.provider.credential)}${credential.provider.cached === void 0 ? "" : `; ${formatCachedCredentialState(credential.provider.cached)}`}`
14565
+ ` Provider ${credential.provider.name}: ${credential.provider.credential === void 0 ? `unusable: ${credential.provider.problem ?? ""}` : formatCredentialSummary(credential.provider.credential)}${credential.provider.cached === void 0 ? "" : `; ${formatCachedCredentialState(credential.provider.cached)}`}`
14430
14566
  );
14431
14567
  }
14432
14568
  if (credential.identity !== void 0) {
@@ -14712,7 +14848,7 @@ var CachedCredentialStateSchema = import_zod17.z.union([
14712
14848
  import_zod17.z.strictObject({ store: import_zod17.z.enum(CREDENTIAL_CACHE_STORES), status: import_zod17.z.enum(["fresh", "expired"]), ageMs: import_zod17.z.number(), expiresInMs: import_zod17.z.number().optional() })
14713
14849
  ]);
14714
14850
  var CheckCredentialProviderSchema = import_zod17.z.union([
14715
- import_zod17.z.strictObject({ name: import_zod17.z.string(), credential: CredentialSummarySchema, cached: CachedCredentialStateSchema.optional() }),
14851
+ import_zod17.z.strictObject({ name: import_zod17.z.string(), credential: CredentialSummarySchema.optional(), cached: CachedCredentialStateSchema.optional() }),
14716
14852
  import_zod17.z.strictObject({ name: import_zod17.z.string(), problem: import_zod17.z.string() })
14717
14853
  ]);
14718
14854
  var CheckCredentialSchema = import_zod17.z.strictObject({
@@ -15085,6 +15221,107 @@ init_credentialCache();
15085
15221
  init_realCredentialCache();
15086
15222
  init_source();
15087
15223
 
15224
+ // src/modelsStore.ts
15225
+ init_store();
15226
+ init_schema();
15227
+ var VirtualModelNotFoundError = class extends Error {
15228
+ constructor(name) {
15229
+ super(`No virtual model named "${name}".`);
15230
+ this.name = "VirtualModelNotFoundError";
15231
+ }
15232
+ };
15233
+ function memberKindOf(ref, models) {
15234
+ if (ref.startsWith("acct:")) {
15235
+ return "account";
15236
+ }
15237
+ if (ref.startsWith("family:")) {
15238
+ return "family";
15239
+ }
15240
+ return models?.virtual !== void 0 && ref in models.virtual ? "virtual" : "provider";
15241
+ }
15242
+ function expandMembers(name, models) {
15243
+ const virtual = models?.virtual ?? {};
15244
+ const expanded = [];
15245
+ const walk = (current, chain) => {
15246
+ const nested = virtual[current];
15247
+ if (nested !== void 0) {
15248
+ if (chain.includes(current)) {
15249
+ throw new Error(`virtual model cycle: ${[...chain, current].join(" -> ")}`);
15250
+ }
15251
+ for (const member of nested.members) {
15252
+ walk(member, [...chain, current]);
15253
+ }
15254
+ return;
15255
+ }
15256
+ if (!expanded.includes(current)) {
15257
+ expanded.push(current);
15258
+ }
15259
+ };
15260
+ const top = virtual[name];
15261
+ if (top === void 0) {
15262
+ throw new VirtualModelNotFoundError(name);
15263
+ }
15264
+ for (const member of top.members) {
15265
+ walk(member, [name]);
15266
+ }
15267
+ return expanded;
15268
+ }
15269
+
15270
+ // src/models/doctor.ts
15271
+ function modelsFindings(input) {
15272
+ const findings = [];
15273
+ const { models } = input;
15274
+ const providers = new Set(input.providers);
15275
+ const identities = new Set(input.identities);
15276
+ const virtualNames = new Set(Object.keys(models.virtual ?? {}));
15277
+ for (const [name, model] of Object.entries(models.virtual ?? {})) {
15278
+ for (const member of model.members) {
15279
+ const kind = memberKindOf(member, models);
15280
+ if (kind === "provider" && !member.includes("/")) {
15281
+ findings.push({ severity: "fail", message: `Virtual model "${name}" names member "${member}", which is neither a defined virtual model nor a provider model (a provider model is <provider>/<model>).` });
15282
+ } else if (kind === "provider" && !providers.has(member.split("/")[0] ?? "")) {
15283
+ findings.push({ severity: "fail", message: `Virtual model "${name}" names member "${member}", but no provider "${member.split("/")[0] ?? ""}" is defined.` });
15284
+ }
15285
+ if (kind === "virtual" && !virtualNames.has(member)) {
15286
+ findings.push({ severity: "fail", message: `Virtual model "${name}" names member "${member}", but no virtual model "${member}" is defined.` });
15287
+ }
15288
+ if (kind === "account") {
15289
+ const identity = member.slice("acct:".length).split("/")[0] ?? "";
15290
+ if (!identities.has(identity)) {
15291
+ findings.push({ severity: "fail", message: `Virtual model "${name}" names member "${member}", but no identity "${identity}" is defined.` });
15292
+ }
15293
+ }
15294
+ }
15295
+ try {
15296
+ expandMembers(name, models);
15297
+ } catch (error) {
15298
+ findings.push({ severity: "fail", message: `Virtual model "${name}" cannot be expanded: ${error instanceof Error ? error.message : String(error)}` });
15299
+ }
15300
+ if (model.mode === "cheapest") {
15301
+ const expanded = expandMembers(name, models);
15302
+ const unpriced = expanded.filter((member) => models.prices?.[member] === void 0 && memberKindOf(member, models) !== "virtual");
15303
+ if (unpriced.length > 0) {
15304
+ findings.push({ severity: "warn", message: `Virtual model "${name}" chooses by price, but no prices are set for: ${unpriced.join(", ")}.` });
15305
+ }
15306
+ }
15307
+ }
15308
+ for (const [tier, ref] of Object.entries(models.tiers ?? {})) {
15309
+ const kind = memberKindOf(ref, models);
15310
+ const known = kind === "virtual" ? virtualNames.has(ref) : kind === "provider" ? providers.has(ref.split("/")[0] ?? "") : true;
15311
+ if (!known) {
15312
+ findings.push({ severity: "fail", message: `Tier ${tier} points at "${ref}", which names nothing defined.` });
15313
+ }
15314
+ }
15315
+ const hidden = new Set(models.hide ?? []);
15316
+ const shown = new Set(models.show ?? []);
15317
+ for (const ref of hidden) {
15318
+ if (shown.has(ref)) {
15319
+ findings.push({ severity: "fail", message: `Model ref "${ref}" is both hidden and in the show-only list.` });
15320
+ }
15321
+ }
15322
+ return findings;
15323
+ }
15324
+
15088
15325
  // src/headroom/allowlistDrift.ts
15089
15326
  init_earlierRelease();
15090
15327
  init_state2();
@@ -15271,7 +15508,8 @@ var DOCTOR_SECTIONS = [
15271
15508
  "categories-local",
15272
15509
  "active-identity",
15273
15510
  "headroom",
15274
- "frontdoor"
15511
+ "frontdoor",
15512
+ "models"
15275
15513
  ];
15276
15514
  function refinePathShadow(status, ownExecutablePath, realpath) {
15277
15515
  if (status.status !== "shadowed") {
@@ -15394,9 +15632,11 @@ function pushProvider(push, entry, cacheEnv, signIn) {
15394
15632
  if (validated3.ignored.length > 0) {
15395
15633
  push("provider", "warn", describeIgnoredKeys(entry.path, validated3.ignored), entry.name);
15396
15634
  }
15397
- const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated3.data.credential);
15398
- push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated3.data)}, credential ${describeCredential(validated3.data.credential)})${cache.text}.`, entry.name);
15399
- if (validated3.data.env?.ENABLE_TOOL_SEARCH === void 0) {
15635
+ const router = isRouterProvider(validated3.data);
15636
+ const noCache = { unreadable: false, text: "" };
15637
+ const cache = router ? noCache : cacheClause(cacheEnv, `provider ${entry.name}`, validated3.data.credential);
15638
+ push("provider", cache.unreadable ? "fail" : "pass", router ? `${entry.name} is valid (${describeProviderEndpoint(validated3.data)})` : `${entry.name} is valid (${describeProviderEndpoint(validated3.data)}, credential ${describeCredential(validated3.data.credential)})${cache.text}.`, entry.name);
15639
+ if (!router && validated3.data.env?.ENABLE_TOOL_SEARCH === void 0) {
15400
15640
  push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider keeps Claude Code's own API as its base URL, so it defers MCP tool loading and sends tool_reference payloads the upstream backend must understand. Set ENABLE_TOOL_SEARCH=false in the provider's env block to load tools upfront instead, when the backend does not support them.`, entry.name);
15401
15641
  }
15402
15642
  if (signIn !== void 0 && isCodexProvider(validated3.data) && validated3.data.codex?.login === "chatgpt-sign-in") {
@@ -15423,6 +15663,9 @@ function pushLegacyName(push, params) {
15423
15663
  );
15424
15664
  }
15425
15665
  }
15666
+ function parsedModelsOf(paths) {
15667
+ return readGlobalConfig(paths)?.models;
15668
+ }
15426
15669
  function runDoctor(params) {
15427
15670
  const findings = [];
15428
15671
  const push = (section, severity, message, subject) => {
@@ -15624,6 +15867,17 @@ function runDoctor(params) {
15624
15867
  }
15625
15868
  }
15626
15869
  }
15870
+ {
15871
+ const models = params.models;
15872
+ if (models === void 0) {
15873
+ push("models", "pass", "No models block configured.");
15874
+ } else {
15875
+ for (const finding of modelsFindings({ models, providers: params.providers.map((entry) => entry.name), identities: params.identities.map((entry) => entry.name) })) {
15876
+ push("models", finding.severity, finding.message);
15877
+ }
15878
+ push("models", "pass", "The models block is consistent.");
15879
+ }
15880
+ }
15627
15881
  if (params.categoriesLocal.raw === void 0) {
15628
15882
  push("categories-local", "pass", "No categories.local.json configured.");
15629
15883
  } else {
@@ -15718,6 +15972,7 @@ function runDoctor(params) {
15718
15972
  return { findings, ok: !findings.some((finding) => finding.severity === "fail") };
15719
15973
  }
15720
15974
  var SECTION_TITLES = {
15975
+ models: "Virtual models",
15721
15976
  "ambient-credential": "Ambient-credential exposure",
15722
15977
  "binary-discovery": "Claude Code binary discovery",
15723
15978
  "claude-shim": "`claude` command shim",
@@ -15850,6 +16105,7 @@ function collectDoctorReport(params) {
15850
16105
  providers,
15851
16106
  directoryRules: { path: paths.directoryRulesFile, raw: realFsPort.readFileUtf8(paths.directoryRulesFile) },
15852
16107
  globalConfig: { path: paths.globalConfigFile, raw: realFsPort.readFileUtf8(paths.globalConfigFile) },
16108
+ models: parsedModelsOf(paths),
15853
16109
  categoriesLocal: { path: paths.categoriesLocalFile, raw: realFsPort.readFileUtf8(paths.categoriesLocalFile) },
15854
16110
  activeIdentity: { path: paths.activeIdentityFile, raw: realFsPort.readFileUtf8(paths.activeIdentityFile) },
15855
16111
  binaryDiscovery,
@@ -16178,7 +16434,7 @@ var ProviderValueSchema = import_zod24.z.strictObject({
16178
16434
  displayName: import_zod24.z.string(),
16179
16435
  baseUrl: import_zod24.z.string().optional(),
16180
16436
  codex: CodexProviderConfigSchema.optional(),
16181
- credential: CredentialSummarySchema2,
16437
+ credential: CredentialSummarySchema2.optional(),
16182
16438
  env: import_zod24.z.record(import_zod24.z.string(), import_zod24.z.string()).optional()
16183
16439
  });
16184
16440
  var ConfigNameInputSchema = import_zod24.z.strictObject({ name: import_zod24.z.string().min(1) });
@@ -16328,9 +16584,8 @@ function providerValue(name, provider) {
16328
16584
  name,
16329
16585
  kind: provider.kind ?? "http",
16330
16586
  displayName: provider.displayName,
16331
- ...isCodexProvider(provider) ? provider.codex === void 0 ? {} : { codex: provider.codex } : isAntigravityProvider(provider) ? provider.antigravity === void 0 ? {} : { antigravity: provider.antigravity } : { baseUrl: provider.baseUrl },
16332
- credential: summariseCredential(provider.credential),
16333
- ...provider.env === void 0 ? {} : { env: provider.env }
16587
+ ...isCodexProvider(provider) ? provider.codex === void 0 ? {} : { codex: provider.codex } : isAntigravityProvider(provider) ? provider.antigravity === void 0 ? {} : { antigravity: provider.antigravity } : isRouterProvider(provider) ? {} : { baseUrl: provider.baseUrl },
16588
+ ...isRouterProvider(provider) ? {} : { credential: summariseCredential(provider.credential), ...provider.env === void 0 ? {} : { env: provider.env } }
16334
16589
  };
16335
16590
  }
16336
16591
  function profileValue(name, paths) {
@@ -16512,7 +16767,7 @@ function createConfigApiRouter(deps) {
16512
16767
  if (input.baseUrl !== void 0 && isHttpProvider(existing) && input.baseUrl !== existing.baseUrl && credential?.sources === void 0) {
16513
16768
  throw new import_client2.ORPCError("BAD_REQUEST", { message: `Changing the base URL of provider "${input.name}" requires a credential block in the same call: the stored credential is not sent to a different endpoint.` });
16514
16769
  }
16515
- const cache = credential?.cache === void 0 ? void 0 : mergeCredentialCache(credential.cache === false ? { enabled: false } : { ...credential.cache, enabled: true }, existing.credential.cache);
16770
+ const cache = credential?.cache === void 0 || isRouterProvider(existing) ? void 0 : mergeCredentialCache(credential.cache === false ? { enabled: false } : { ...credential.cache, enabled: true }, isRouterProvider(existing) ? void 0 : existing.credential.cache);
16516
16771
  const updated = updateProvider(paths, input.name, {
16517
16772
  ...input.displayName === void 0 ? {} : { displayName: input.displayName },
16518
16773
  ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl },