@pruddiman/dispatch 1.5.0-beta.9d77ada → 1.5.0-beta.9ed214f

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.js CHANGED
@@ -1197,6 +1197,32 @@ import {
1197
1197
  createOpencode,
1198
1198
  createOpencodeClient
1199
1199
  } from "@opencode-ai/sdk";
1200
+ async function listModels(opts) {
1201
+ let client;
1202
+ let stopServer;
1203
+ if (opts?.url) {
1204
+ client = createOpencodeClient({ baseUrl: opts.url });
1205
+ } else {
1206
+ if (opts?.cwd) {
1207
+ log.debug(`listModels: requested cwd "${opts.cwd}" \u2014 OpenCode SDK does not support spawn-level cwd`);
1208
+ }
1209
+ try {
1210
+ const oc = await createOpencode({ port: 0 });
1211
+ client = oc.client;
1212
+ stopServer = () => oc.server.close();
1213
+ } catch (err) {
1214
+ log.debug(`listModels: failed to start OpenCode server: ${log.formatErrorChain(err)}`);
1215
+ throw err;
1216
+ }
1217
+ }
1218
+ try {
1219
+ const { data } = await client.config.providers();
1220
+ if (!data) return [];
1221
+ return data.providers.filter((p) => p.source === "env" || p.source === "config" || p.source === "custom").flatMap((p) => Object.keys(p.models).map((modelId) => `${p.id}/${modelId}`)).sort();
1222
+ } finally {
1223
+ stopServer?.();
1224
+ }
1225
+ }
1200
1226
  async function boot(opts) {
1201
1227
  let client;
1202
1228
  let stopServer;
@@ -1398,6 +1424,21 @@ var init_opencode = __esm({
1398
1424
  async function loadCopilotSdk() {
1399
1425
  return import("@github/copilot-sdk");
1400
1426
  }
1427
+ async function listModels2(opts) {
1428
+ const { CopilotClient } = await loadCopilotSdk();
1429
+ const client = new CopilotClient({
1430
+ ...opts?.url ? { cliUrl: opts.url } : {}
1431
+ });
1432
+ try {
1433
+ await client.start();
1434
+ const models = await client.listModels();
1435
+ return models.map((m) => m.id).sort();
1436
+ } finally {
1437
+ await client.stop().catch((err) => {
1438
+ log.debug(`Failed to stop Copilot list-models client: ${log.formatErrorChain(err)}`);
1439
+ });
1440
+ }
1441
+ }
1401
1442
  async function boot2(opts) {
1402
1443
  log.debug(opts?.url ? `Connecting to Copilot CLI at ${opts.url}` : "Starting Copilot CLI...");
1403
1444
  const { CopilotClient, approveAll } = await loadCopilotSdk();
@@ -1538,6 +1579,30 @@ var init_copilot = __esm({
1538
1579
  // src/providers/claude.ts
1539
1580
  import { randomUUID } from "crypto";
1540
1581
  import { query, unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";
1582
+ async function listModels3(opts) {
1583
+ try {
1584
+ const queryOpts = {
1585
+ model: opts?.model ?? "claude-sonnet-4",
1586
+ permissionMode: "bypassPermissions",
1587
+ allowDangerouslySkipPermissions: true
1588
+ };
1589
+ const q = query({ prompt: "", options: queryOpts });
1590
+ try {
1591
+ const models = await q.supportedModels();
1592
+ return models.map((m) => m.value).sort();
1593
+ } finally {
1594
+ q.close();
1595
+ }
1596
+ } catch (err) {
1597
+ log.debug(`Failed to list models dynamically: ${log.formatErrorChain(err)}`);
1598
+ return [
1599
+ "claude-haiku-3-5",
1600
+ "claude-opus-4-6",
1601
+ "claude-sonnet-4",
1602
+ "claude-sonnet-4-5"
1603
+ ];
1604
+ }
1605
+ }
1541
1606
  async function boot3(opts) {
1542
1607
  const model = opts?.model ?? "claude-sonnet-4";
1543
1608
  const cwd = opts?.cwd;
@@ -1645,6 +1710,13 @@ import { randomUUID as randomUUID2 } from "crypto";
1645
1710
  async function loadCodexSdk() {
1646
1711
  return import("@openai/codex-sdk");
1647
1712
  }
1713
+ async function listModels4(_opts) {
1714
+ return [
1715
+ "codex-mini-latest",
1716
+ "o3-mini",
1717
+ "o4-mini"
1718
+ ];
1719
+ }
1648
1720
  async function boot4(opts) {
1649
1721
  const model = opts?.model ?? "o4-mini";
1650
1722
  log.debug(`Booting Codex provider with model ${model}`);
@@ -1793,7 +1865,7 @@ async function checkCodexAuth() {
1793
1865
  return { status: "authenticated" };
1794
1866
  }
1795
1867
  try {
1796
- await exec3("codex", ["auth", "status"], { timeout: AUTH_PROBE_TIMEOUT_MS });
1868
+ await exec3("codex", ["login", "status"], { timeout: AUTH_PROBE_TIMEOUT_MS });
1797
1869
  return { status: "authenticated" };
1798
1870
  } catch {
1799
1871
  return {
@@ -1812,7 +1884,7 @@ async function checkOpencodeAuth() {
1812
1884
  };
1813
1885
  }
1814
1886
  try {
1815
- await exec3("opencode", ["auth", "status"], { timeout: AUTH_PROBE_TIMEOUT_MS });
1887
+ await exec3("opencode", ["auth", "list"], { timeout: AUTH_PROBE_TIMEOUT_MS });
1816
1888
  return { status: "authenticated" };
1817
1889
  } catch {
1818
1890
  return {
@@ -1905,7 +1977,16 @@ async function bootProvider(name, opts) {
1905
1977
  }
1906
1978
  return bootFn(opts);
1907
1979
  }
1908
- var PROVIDERS;
1980
+ async function listProviderModels(name, opts) {
1981
+ const fn = LIST_MODELS[name];
1982
+ if (!fn) {
1983
+ throw new Error(
1984
+ `Unknown provider "${name}". Available: ${PROVIDER_NAMES.join(", ")}`
1985
+ );
1986
+ }
1987
+ return fn(opts);
1988
+ }
1989
+ var PROVIDERS, LIST_MODELS;
1909
1990
  var init_providers = __esm({
1910
1991
  "src/providers/index.ts"() {
1911
1992
  "use strict";
@@ -1922,6 +2003,12 @@ var init_providers = __esm({
1922
2003
  claude: boot3,
1923
2004
  codex: boot4
1924
2005
  };
2006
+ LIST_MODELS = {
2007
+ opencode: listModels,
2008
+ copilot: listModels2,
2009
+ claude: listModels3,
2010
+ codex: listModels4
2011
+ };
1925
2012
  }
1926
2013
  });
1927
2014
 
@@ -2426,6 +2513,63 @@ async function runInteractiveConfigWizard(configDir) {
2426
2513
  `${enabledProviders.length} provider(s) enabled. Dispatch will automatically route tasks to the best available provider.`
2427
2514
  );
2428
2515
  console.log();
2516
+ const providerModels = existing.providerModels ? { ...existing.providerModels } : {};
2517
+ const configureModels = await confirm({
2518
+ message: `Configure model selection for ${enabledProviders.length} provider(s)?`,
2519
+ default: false
2520
+ });
2521
+ if (configureModels) {
2522
+ for (const name of enabledProviders) {
2523
+ const meta = PROVIDER_REGISTRY[name];
2524
+ const existingOverride = providerModels[name];
2525
+ console.log();
2526
+ log.info(`${meta.displayName} models:`);
2527
+ let models;
2528
+ try {
2529
+ log.dim(` Fetching available models...`);
2530
+ models = await Promise.race([
2531
+ listProviderModels(name),
2532
+ new Promise(
2533
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), MODEL_LIST_TIMEOUT_MS)
2534
+ )
2535
+ ]);
2536
+ } catch {
2537
+ log.warn(` Could not fetch model list \u2014 showing defaults only`);
2538
+ models = [meta.defaultStrongModel, meta.defaultFastModel];
2539
+ }
2540
+ const modelSet = new Set(models);
2541
+ modelSet.add(meta.defaultStrongModel);
2542
+ modelSet.add(meta.defaultFastModel);
2543
+ const allModels = [...modelSet].sort();
2544
+ const strongDefault = existingOverride?.strong ?? meta.defaultStrongModel;
2545
+ const strongChoice = await select({
2546
+ message: "Strong model (executor, spec):",
2547
+ choices: [
2548
+ { name: `(default) ${meta.defaultStrongModel}`, value: void 0 },
2549
+ ...allModels.map((m) => ({ name: m, value: m }))
2550
+ ],
2551
+ default: strongDefault === meta.defaultStrongModel ? void 0 : strongDefault
2552
+ });
2553
+ const fastDefault = existingOverride?.fast ?? meta.defaultFastModel;
2554
+ const fastChoice = await select({
2555
+ message: "Fast model (planner, commit):",
2556
+ choices: [
2557
+ { name: `(default) ${meta.defaultFastModel}`, value: void 0 },
2558
+ ...allModels.map((m) => ({ name: m, value: m }))
2559
+ ],
2560
+ default: fastDefault === meta.defaultFastModel ? void 0 : fastDefault
2561
+ });
2562
+ const entry = {};
2563
+ if (strongChoice !== void 0) entry.strong = strongChoice;
2564
+ if (fastChoice !== void 0) entry.fast = fastChoice;
2565
+ if (entry.strong !== void 0 || entry.fast !== void 0) {
2566
+ providerModels[name] = entry;
2567
+ } else {
2568
+ delete providerModels[name];
2569
+ }
2570
+ }
2571
+ }
2572
+ console.log();
2429
2573
  const detectedSource = await detectDatasource(process.cwd());
2430
2574
  const datasourceDefault = existing.source ?? "auto";
2431
2575
  if (detectedSource) {
@@ -2504,6 +2648,11 @@ async function runInteractiveConfigWizard(configDir) {
2504
2648
  enabledProviders,
2505
2649
  source
2506
2650
  };
2651
+ if (Object.keys(providerModels).length > 0) {
2652
+ newConfig.providerModels = providerModels;
2653
+ } else {
2654
+ delete newConfig.providerModels;
2655
+ }
2507
2656
  if (org !== void 0) newConfig.org = org;
2508
2657
  if (project !== void 0) newConfig.project = project;
2509
2658
  if (workItemType !== void 0) newConfig.workItemType = workItemType;
@@ -2515,6 +2664,13 @@ async function runInteractiveConfigWizard(configDir) {
2515
2664
  if (value !== void 0) {
2516
2665
  if (key === "enabledProviders" && Array.isArray(value)) {
2517
2666
  console.log(` ${key} = ${value.join(", ")}`);
2667
+ } else if (key === "providerModels" && typeof value === "object") {
2668
+ for (const [provider, models] of Object.entries(value)) {
2669
+ const parts = [];
2670
+ if (models.strong) parts.push(`strong=${models.strong}`);
2671
+ if (models.fast) parts.push(`fast=${models.fast}`);
2672
+ if (parts.length) console.log(` ${provider} models = ${parts.join(", ")}`);
2673
+ }
2518
2674
  } else {
2519
2675
  console.log(` ${key} = ${value}`);
2520
2676
  }
@@ -2535,6 +2691,7 @@ async function runInteractiveConfigWizard(configDir) {
2535
2691
  log.dim("Configuration not saved.");
2536
2692
  }
2537
2693
  }
2694
+ var MODEL_LIST_TIMEOUT_MS;
2538
2695
  var init_config_prompts = __esm({
2539
2696
  "src/config-prompts.tsx"() {
2540
2697
  "use strict";
@@ -2543,8 +2700,10 @@ var init_config_prompts = __esm({
2543
2700
  init_config();
2544
2701
  init_datasources();
2545
2702
  init_auth();
2703
+ init_providers();
2546
2704
  init_registry();
2547
2705
  init_auth_setup();
2706
+ MODEL_LIST_TIMEOUT_MS = 8e3;
2548
2707
  }
2549
2708
  });
2550
2709
 
@@ -2594,6 +2753,23 @@ function migrateConfig(raw) {
2594
2753
  (p) => PROVIDER_NAMES.includes(p)
2595
2754
  );
2596
2755
  }
2756
+ if (raw.providerModels !== void 0 && typeof raw.providerModels === "object" && raw.providerModels !== null) {
2757
+ const validated = {};
2758
+ for (const [providerName, modelCfg] of Object.entries(raw.providerModels)) {
2759
+ if (!PROVIDER_NAMES.includes(providerName)) continue;
2760
+ if (typeof modelCfg !== "object" || modelCfg === null) continue;
2761
+ const cfg = modelCfg;
2762
+ const entry = {};
2763
+ if (typeof cfg.strong === "string") entry.strong = cfg.strong;
2764
+ if (typeof cfg.fast === "string") entry.fast = cfg.fast;
2765
+ if (entry.strong !== void 0 || entry.fast !== void 0) {
2766
+ validated[providerName] = entry;
2767
+ }
2768
+ }
2769
+ if (Object.keys(validated).length > 0) {
2770
+ config.providerModels = validated;
2771
+ }
2772
+ }
2597
2773
  if (raw.source !== void 0) config.source = raw.source;
2598
2774
  if (raw.planTimeout !== void 0) config.planTimeout = raw.planTimeout;
2599
2775
  if (raw.specTimeout !== void 0) config.specTimeout = raw.specTimeout;
@@ -2617,6 +2793,7 @@ async function saveConfig(config, configDir) {
2617
2793
  function validateConfigValue(key, value) {
2618
2794
  switch (key) {
2619
2795
  case "enabledProviders":
2796
+ case "providerModels":
2620
2797
  return null;
2621
2798
  case "source":
2622
2799
  if (!DATASOURCE_NAMES.includes(value)) {
@@ -2702,7 +2879,7 @@ var init_config = __esm({
2702
2879
  specKillTimeout: { min: 1, max: 120 },
2703
2880
  concurrency: { min: 1, max: 64 }
2704
2881
  };
2705
- CONFIG_KEYS = ["enabledProviders", "source", "planTimeout", "specTimeout", "specWarnTimeout", "specKillTimeout", "concurrency", "org", "project", "workItemType", "iteration", "area", "username"];
2882
+ CONFIG_KEYS = ["enabledProviders", "providerModels", "source", "planTimeout", "specTimeout", "specWarnTimeout", "specKillTimeout", "concurrency", "org", "project", "workItemType", "iteration", "area", "username"];
2706
2883
  }
2707
2884
  });
2708
2885
 
@@ -3387,6 +3564,7 @@ var init_cli_config = __esm({
3387
3564
  init_datasources();
3388
3565
  CONFIG_TO_CLI = {
3389
3566
  enabledProviders: "enabledProviders",
3567
+ providerModels: "providerModels",
3390
3568
  source: "issueSource",
3391
3569
  planTimeout: "planTimeout",
3392
3570
  specTimeout: "specTimeout",
@@ -3404,14 +3582,19 @@ var init_cli_config = __esm({
3404
3582
  });
3405
3583
 
3406
3584
  // src/providers/router.ts
3407
- function routeSkill(role, authenticatedProviders, forceProvider) {
3585
+ function resolveModel(meta, isFast, overrides) {
3586
+ const override = overrides?.[meta.name];
3587
+ if (isFast) return override?.fast ?? meta.defaultFastModel;
3588
+ return override?.strong ?? meta.defaultStrongModel;
3589
+ }
3590
+ function routeSkill(role, authenticatedProviders, forceProvider, modelOverrides) {
3408
3591
  if (forceProvider) {
3409
3592
  const meta = PROVIDER_REGISTRY[forceProvider];
3410
3593
  const isFast2 = FAST_ROLES.has(role);
3411
3594
  return [
3412
3595
  {
3413
3596
  provider: forceProvider,
3414
- model: isFast2 ? meta.defaultFastModel : meta.defaultStrongModel,
3597
+ model: resolveModel(meta, isFast2, modelOverrides),
3415
3598
  priority: 0
3416
3599
  }
3417
3600
  ];
@@ -3427,7 +3610,7 @@ function routeSkill(role, authenticatedProviders, forceProvider) {
3427
3610
  return [
3428
3611
  {
3429
3612
  provider: meta.name,
3430
- model: isFast2 ? meta.defaultFastModel : meta.defaultStrongModel,
3613
+ model: resolveModel(meta, isFast2, modelOverrides),
3431
3614
  priority: 0
3432
3615
  }
3433
3616
  ];
@@ -3446,15 +3629,15 @@ function routeSkill(role, authenticatedProviders, forceProvider) {
3446
3629
  });
3447
3630
  return scored.map(({ meta }, i) => ({
3448
3631
  provider: meta.name,
3449
- model: isFast ? meta.defaultFastModel : meta.defaultStrongModel,
3632
+ model: resolveModel(meta, isFast, modelOverrides),
3450
3633
  priority: i
3451
3634
  }));
3452
3635
  }
3453
- function routeAllSkills(authenticatedProviders, forceProvider) {
3636
+ function routeAllSkills(authenticatedProviders, forceProvider, modelOverrides) {
3454
3637
  return {
3455
- planner: routeSkill("planner", authenticatedProviders, forceProvider),
3456
- executor: routeSkill("executor", authenticatedProviders, forceProvider),
3457
- commit: routeSkill("commit", authenticatedProviders, forceProvider)
3638
+ planner: routeSkill("planner", authenticatedProviders, forceProvider, modelOverrides),
3639
+ executor: routeSkill("executor", authenticatedProviders, forceProvider, modelOverrides),
3640
+ commit: routeSkill("commit", authenticatedProviders, forceProvider, modelOverrides)
3458
3641
  };
3459
3642
  }
3460
3643
  var FAST_ROLES;
@@ -5296,6 +5479,7 @@ async function runSpecPipeline(opts) {
5296
5479
  issues,
5297
5480
  provider: forceProvider,
5298
5481
  enabledProviders,
5482
+ providerModels,
5299
5483
  serverUrl,
5300
5484
  cwd: specCwd,
5301
5485
  outputDir = join8(specCwd, ".dispatch", "specs"),
@@ -5351,7 +5535,7 @@ async function runSpecPipeline(opts) {
5351
5535
  if (available.length === 0) {
5352
5536
  throw new Error("No authenticated providers available. Run 'dispatch config' to set up providers.");
5353
5537
  }
5354
- const specRoute = routeSkill("spec", available, forceProvider);
5538
+ const specRoute = routeSkill("spec", available, forceProvider, providerModels);
5355
5539
  const resolvedProvider = specRoute[0].provider;
5356
5540
  const resolvedModel = specRoute[0].model;
5357
5541
  const { instance } = await bootPipeline(resolvedProvider, serverUrl, specCwd, resolvedModel, source);
@@ -6200,6 +6384,7 @@ async function runDispatchPipeline(opts, cwd) {
6200
6384
  feature,
6201
6385
  provider,
6202
6386
  enabledProviders,
6387
+ providerModels,
6203
6388
  source,
6204
6389
  org,
6205
6390
  project,
@@ -6217,7 +6402,7 @@ async function runDispatchPipeline(opts, cwd) {
6217
6402
  if (available.length === 0) {
6218
6403
  throw new Error("No authenticated providers available. Run 'dispatch config' to set up providers.");
6219
6404
  }
6220
- const agentRoutes = routeAllSkills(available, provider);
6405
+ const agentRoutes = routeAllSkills(available, provider, providerModels);
6221
6406
  function createPool(entries, bootCwd) {
6222
6407
  return new ProviderPool({ entries, bootOpts: { url: serverUrl, cwd: bootCwd } });
6223
6408
  }
@@ -7090,6 +7275,7 @@ async function boot5(opts) {
7090
7275
  issueSource: m.issueSource,
7091
7276
  provider: m.provider,
7092
7277
  enabledProviders: m.enabledProviders,
7278
+ providerModels: m.providerModels,
7093
7279
  serverUrl: m.serverUrl,
7094
7280
  cwd: m.cwd,
7095
7281
  outputDir: m.outputDir,
@@ -7136,6 +7322,7 @@ async function boot5(opts) {
7136
7322
  issueSource: m.issueSource,
7137
7323
  provider: m.provider,
7138
7324
  enabledProviders: m.enabledProviders,
7325
+ providerModels: m.providerModels,
7139
7326
  serverUrl: m.serverUrl,
7140
7327
  cwd: m.cwd,
7141
7328
  outputDir: m.outputDir,
@@ -7161,6 +7348,7 @@ async function boot5(opts) {
7161
7348
  noWorktree: m.noWorktree,
7162
7349
  provider: m.provider,
7163
7350
  enabledProviders: m.enabledProviders,
7351
+ providerModels: m.providerModels,
7164
7352
  serverUrl: m.serverUrl,
7165
7353
  source: m.issueSource,
7166
7354
  org: m.org,
@@ -7728,6 +7916,7 @@ function registerSpecTools(server, cwd) {
7728
7916
  opts: {
7729
7917
  issues: resolvedIssues,
7730
7918
  enabledProviders: config.enabledProviders,
7919
+ providerModels: config.providerModels,
7731
7920
  issueSource: config.source,
7732
7921
  org: config.org,
7733
7922
  project: config.project,
@@ -7928,6 +8117,7 @@ function registerDispatchTools(server, cwd) {
7928
8117
  dryRun: false,
7929
8118
  provider: args.provider,
7930
8119
  enabledProviders: config.enabledProviders,
8120
+ providerModels: config.providerModels,
7931
8121
  source: config.source,
7932
8122
  org: config.org,
7933
8123
  project: config.project,
@@ -7966,6 +8156,7 @@ function registerDispatchTools(server, cwd) {
7966
8156
  issueIds: args.issueIds,
7967
8157
  dryRun: true,
7968
8158
  enabledProviders: config.enabledProviders,
8159
+ providerModels: config.providerModels,
7969
8160
  source: config.source,
7970
8161
  org: config.org,
7971
8162
  project: config.project,
@@ -8225,6 +8416,7 @@ function registerRecoveryTools(server, cwd) {
8225
8416
  issueIds,
8226
8417
  dryRun: false,
8227
8418
  enabledProviders: config.enabledProviders,
8419
+ providerModels: config.providerModels,
8228
8420
  source: config.source,
8229
8421
  org: config.org,
8230
8422
  project: config.project,
@@ -8286,6 +8478,7 @@ function registerRecoveryTools(server, cwd) {
8286
8478
  issueIds,
8287
8479
  dryRun: false,
8288
8480
  enabledProviders: config.enabledProviders,
8481
+ providerModels: config.providerModels,
8289
8482
  source: config.source,
8290
8483
  org: config.org,
8291
8484
  project: config.project,
@@ -8928,7 +9121,7 @@ async function main() {
8928
9121
  process.exit(0);
8929
9122
  }
8930
9123
  if (args.version) {
8931
- console.log(`dispatch v${"1.5.0-beta.9d77ada"}`);
9124
+ console.log(`dispatch v${"1.5.0-beta.9ed214f"}`);
8932
9125
  process.exit(0);
8933
9126
  }
8934
9127
  const orchestrator = await boot5({ cwd: args.cwd });