@jmanuelcorral/openteam 0.17.0 → 0.17.1

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.
@@ -36,13 +36,18 @@
36
36
  {
37
37
  "id": "lemonade",
38
38
  "enabled": false,
39
- "baseURL": "http://localhost:8000/api/v1",
39
+ "baseURL": "http://localhost:13305/api/v1",
40
40
  "defaultModel": {
41
41
  "providerID": "lemonade",
42
42
  "modelID": "Qwen3-Coder-30B-A3B-Instruct-GGUF"
43
43
  },
44
44
  "thinkingModel": "Qwen3-30B-A3B-Thinking-GGUF",
45
- "coderModel": "Qwen3-Coder-30B-A3B-Instruct-GGUF"
45
+ "coderModel": "Qwen3-Coder-30B-A3B-Instruct-GGUF",
46
+ "maxConcurrency": 3,
47
+ "modelConcurrency": {
48
+ "Qwen3-Coder-30B-A3B-Instruct-GGUF": 3,
49
+ "Qwen3-30B-A3B-Thinking-GGUF": 1
50
+ }
46
51
  },
47
52
  {
48
53
  "id": "foundry-local",
package/README.es.md CHANGED
@@ -52,7 +52,7 @@ El router mantiene una garantía de privacidad: con `privacyMode: "forceLocalOnS
52
52
  - Runtimes locales opcionales:
53
53
  - Ollama en `http://localhost:11434/v1`.
54
54
  - LM Studio en `http://localhost:1234/v1`.
55
- - Lemonade Server en `http://localhost:8000/api/v1`.
55
+ - Lemonade Server en `http://localhost:13305/api/v1`.
56
56
  - Foundry Local con puerto dinámico descubierto por CLI en Windows.
57
57
 
58
58
  ## Instalación
@@ -373,6 +373,7 @@ ejecutas desde un subdirectorio verás "provider not configured" y debes hacer
373
373
  | `local.runtimes[].discovery` | `"cli" \| "sdk" \| "manual"` opcional | sin default por item | Discovery de Foundry Local; el registry usa CLI si está configurado y no es `manual`. |
374
374
  | `local.runtimes[].defaultModel` | `ModelRef` | requerido | Modelo por defecto de ese runtime. |
375
375
  | `local.runtimes[].maxConcurrency` | entero positivo opcional | `4` | Límite de generaciones concurrentes de esta instancia (su número real de slots). Se aplica por instancia. |
376
+ | `local.runtimes[].modelConcurrency` | `Record<modelID, entero positivo>` opcional | ninguno | Slots por modelo, porque los slots pertenecen al modelo cargado y no al servidor: un mismo host Lemonade puede servir un modelo con `--parallel 3` y otro con `--parallel 1` a la vez. Cada modelo declarado obtiene su propia cola; el resto comparte el pool de `maxConcurrency` de la instancia. `openteam setup` lo rellena solo cuando el runtime informa de sus slots. |
376
377
  | `budgets.sessionUSD` | `number` positivo opcional | sin límite | Presupuesto frontier por sesión. |
377
378
  | `budgets.monthlyUSD` | `number` positivo opcional | sin límite | Presupuesto frontier mensual. |
378
379
  | `budgets.frontierTokensPerSession` | `number` entero positivo opcional | sin límite | Límite de tokens frontier por sesión. |
@@ -729,7 +730,7 @@ Invoke-RestMethod -Uri "http://localhost:1234/v1/models" -Method Get
729
730
  ### Lemonade Server
730
731
 
731
732
  ```powershell
732
- Invoke-RestMethod -Uri "http://localhost:8000/api/v1/models" -Method Get
733
+ Invoke-RestMethod -Uri "http://localhost:13305/api/v1/models" -Method Get
733
734
  ```
734
735
 
735
736
  ### Foundry Local
@@ -824,7 +825,7 @@ Consulta [docs/compatibility.md](docs/compatibility.md). Resumen verificado el `
824
825
 
825
826
  | Superficie | Versión / contrato |
826
827
  | --- | --- |
827
- | openteam | `0.17.0`; versión actual del paquete |
828
+ | openteam | `0.17.1`; versión actual del paquete |
828
829
  | `@opencode-ai/plugin` | `1.18.19` |
829
830
  | `@opencode-ai/sdk` | `1.18.19` |
830
831
  | Hook de routing | `chat.message`; no `chat.params` para cambiar modelo |
package/README.md CHANGED
@@ -61,7 +61,7 @@ The router keeps a privacy guarantee: with `privacyMode: "forceLocalOnSensitive"
61
61
  - Optional local runtimes:
62
62
  - Ollama at `http://localhost:11434/v1`.
63
63
  - LM Studio at `http://localhost:1234/v1`.
64
- - Lemonade Server at `http://localhost:8000/api/v1`.
64
+ - Lemonade Server at `http://localhost:13305/api/v1`.
65
65
  - Foundry Local with a dynamic port discovered via CLI on Windows.
66
66
 
67
67
  > Local runtimes are **optional**. You can run openteam **frontier-only**, or point it at an Ollama / LM Studio / Lemonade Server running on **another machine on your LAN**.
@@ -325,6 +325,7 @@ configured" and should `cd` to the project root first.
325
325
  | `local.runtimes[].discovery` | `"cli" \| "sdk" \| "manual"` optional | none per item | Foundry Local discovery; the registry uses CLI when configured and not `manual`. |
326
326
  | `local.runtimes[].defaultModel` | `ModelRef` | required | Default model for that runtime. |
327
327
  | `local.runtimes[].maxConcurrency` | positive integer, optional | `4` | Concurrent-generation cap for this instance (its real slot count). Applied per instance. |
328
+ | `local.runtimes[].modelConcurrency` | `Record<modelID, positive integer>`, optional | none | Per-model slot counts, because slots belong to the loaded model rather than to the server: a single Lemonade host can serve one model at `--parallel 3` and another at `--parallel 1` simultaneously. Each declared model gets its own queue; every other model shares the instance-wide `maxConcurrency` pool. Populated automatically by `openteam setup` when the runtime reports its slots. |
328
329
  | `budgets.sessionUSD` | positive number, optional | unlimited | Frontier budget per session. |
329
330
  | `budgets.monthlyUSD` | positive number, optional | unlimited | Monthly frontier budget. |
330
331
  | `budgets.frontierTokensPerSession` | positive integer, optional | unlimited | Frontier token limit per session. |
@@ -622,7 +623,7 @@ Invoke-RestMethod -Uri "http://localhost:1234/v1/models" -Method Get
622
623
  ### Lemonade Server
623
624
 
624
625
  ```powershell
625
- Invoke-RestMethod -Uri "http://localhost:8000/api/v1/models" -Method Get
626
+ Invoke-RestMethod -Uri "http://localhost:13305/api/v1/models" -Method Get
626
627
  ```
627
628
 
628
629
  ### Foundry Local
@@ -713,7 +714,7 @@ See [docs/compatibility.md](docs/compatibility.md). Verified summary as of `2026
713
714
 
714
715
  | Surface | Version / contract |
715
716
  | --- | --- |
716
- | openteam | `0.17.0`; current package version |
717
+ | openteam | `0.17.1`; current package version |
717
718
  | `@opencode-ai/plugin` | `1.18.19` |
718
719
  | `@opencode-ai/sdk` | `1.18.19` |
719
720
  | Routing hook | `chat.message`; not `chat.params` for model changes |
@@ -34,12 +34,12 @@
34
34
  "idempotent": true
35
35
  },
36
36
  "performance": {
37
- "p95Millis": 0.4712040000000002,
37
+ "p95Millis": 0.34230600000000777,
38
38
  "budgetMillis": 50,
39
39
  "samples": 100
40
40
  }
41
41
  },
42
42
  "verdict": "pass",
43
43
  "failedGates": [],
44
- "digest": "c2bae4d5fdfb94e26393cce05e69eacb8829647b091f82d64ce7c6d1a956c891"
44
+ "digest": "3ae52181594e67aee315d4d71b6fd3a58dc34e2b5e74677464b24b57f30e3d8c"
45
45
  }
@@ -20,7 +20,7 @@
20
20
  "rawFindings": 0
21
21
  },
22
22
  "overhead": {
23
- "p95Millis": 0.34261300000000006,
23
+ "p95Millis": 0.26040199999999913,
24
24
  "budgetMillis": 25,
25
25
  "samples": 1000
26
26
  },
@@ -35,5 +35,5 @@
35
35
  },
36
36
  "verdict": "pass",
37
37
  "failedGates": [],
38
- "digest": "2ceb71a2556e57ea51007d88b1e94bf0081ae975eb756b1fad05641bb4515332"
38
+ "digest": "7372b6e7a11519a55b6db9255ea7132ab3d5244c8659bff981368879f13dffbd"
39
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"setupAdapters.d.ts","sourceRoot":"","sources":["../../src/cli/setupAdapters.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EACV,eAAe,EAEf,QAAQ,EAET,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,KAAK,QAAQ,EAEd,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAyBrD,qDAAqD;AACrD,eAAO,MAAM,aAAa,EAAE,QA8E3B,CAAC;AA6BF;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,OAAO,CACzD,sBAAsB,EAAE,CACzB,CAcA;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAuB7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,GACb,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CA2BnE"}
1
+ {"version":3,"file":"setupAdapters.d.ts","sourceRoot":"","sources":["../../src/cli/setupAdapters.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EACV,eAAe,EAEf,QAAQ,EAET,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,KAAK,QAAQ,EAEd,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAyBrD,qDAAqD;AACrD,eAAO,MAAM,aAAa,EAAE,QA8E3B,CAAC;AA6BF;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,OAAO,CACzD,sBAAsB,EAAE,CACzB,CAcA;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CA6B7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,GACb,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CA8BnE"}
package/dist/cli.js CHANGED
@@ -461,6 +461,7 @@ var LocalRuntimeSchema = z2.object({
461
461
  discovery: z2.enum(["cli", "sdk", "manual"]).optional(),
462
462
  defaultModel: ModelRefSchema,
463
463
  maxConcurrency: z2.number().int().positive().optional(),
464
+ modelConcurrency: z2.record(z2.string().min(1), z2.number().int().positive()).optional(),
464
465
  reservedInteractiveSlots: z2.number().int().nonnegative().optional(),
465
466
  thinkingModel: z2.string().min(1).optional(),
466
467
  coderModel: z2.string().min(1).optional()
@@ -682,6 +683,16 @@ function classifyHeuristic(task) {
682
683
  };
683
684
  }
684
685
 
686
+ // src/local/slotPool.ts
687
+ var POOL_SEPARATOR = "::";
688
+ function slotPoolKey(runtime, modelID) {
689
+ if (modelID === undefined) {
690
+ return runtime.id;
691
+ }
692
+ const declared = runtime.modelConcurrency?.[modelID];
693
+ return declared === undefined ? runtime.id : `${runtime.id}${POOL_SEPARATOR}${modelID}`;
694
+ }
695
+
685
696
  // src/capabilities/budget.ts
686
697
  var WARNING_UTILIZATION = 0.8;
687
698
  function projectedSpend(current, estimatedCostUSD) {
@@ -2290,18 +2301,19 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch) {
2290
2301
  if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
2291
2302
  const limiter = deps.runtimeLimiter;
2292
2303
  const priority = priorityForRole(role);
2293
- release = limiter.tryAcquire(runtimeId, priority);
2304
+ const poolKey = slotPoolKey(configuredRuntime(runtimeId, input.config) ?? { id: runtimeId }, decision2.selected.modelID);
2305
+ release = limiter.tryAcquire(poolKey, priority);
2294
2306
  let waitedMs = 0;
2295
2307
  if (release === undefined) {
2296
- const acquisition = await limiter.acquire(runtimeId, priority);
2308
+ const acquisition = await limiter.acquire(poolKey, priority);
2297
2309
  release = acquisition.release;
2298
2310
  waitedMs = acquisition.waitedMs;
2299
2311
  }
2300
2312
  queueObservation = {
2301
2313
  runtimeId,
2302
2314
  waitedMs,
2303
- activeCount: limiter.activeCount(runtimeId),
2304
- queuedCount: limiter.queuedCount(runtimeId)
2315
+ activeCount: limiter.activeCount(poolKey),
2316
+ queuedCount: limiter.queuedCount(poolKey)
2305
2317
  };
2306
2318
  }
2307
2319
  try {
@@ -3102,6 +3114,7 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3102
3114
  "discovery",
3103
3115
  "defaultModel",
3104
3116
  "maxConcurrency",
3117
+ "modelConcurrency",
3105
3118
  "reservedInteractiveSlots",
3106
3119
  "thinkingModel",
3107
3120
  "coderModel"
@@ -10024,24 +10037,141 @@ function ensureFoundryV1BaseURL(endpoint) {
10024
10037
  }
10025
10038
 
10026
10039
  // src/local/lemonade.ts
10027
- var LEMONADE_DEFAULT_BASE_URL = "http://localhost:8000/api/v1";
10040
+ var LEMONADE_DEFAULT_BASE_URL = "http://localhost:13305/api/v1";
10041
+ var NON_TEXT_LABELS = new Set([
10042
+ "image",
10043
+ "tts",
10044
+ "transcription",
10045
+ "embedding",
10046
+ "reranking"
10047
+ ]);
10048
+ var PARALLEL_ARG = /(?:^|\s)(?:--parallel|-np)[\s=]+(\d+)(?!\d)/;
10028
10049
  function createLemonadeAdapter() {
10050
+ const listModels = async (options) => {
10051
+ const baseURL = normalizeBaseURL(options.baseURL ?? LEMONADE_DEFAULT_BASE_URL);
10052
+ const downloaded = await listLemonadeModels(baseURL, options.fetch);
10053
+ const loaded = await readLoadedModels(baseURL, options.fetch);
10054
+ return enrichWithLoadState(downloaded, loaded);
10055
+ };
10029
10056
  return {
10030
10057
  id: "lemonade",
10031
10058
  defaultBaseURL: LEMONADE_DEFAULT_BASE_URL,
10032
- listModels(options) {
10033
- return listOpenAICompatibleModels(normalizeBaseURL(options.baseURL ?? LEMONADE_DEFAULT_BASE_URL), options.fetch);
10034
- },
10035
- probe(options) {
10036
- return probeOpenAICompatibleRuntime({
10037
- id: "lemonade",
10038
- baseURL: options.baseURL ?? LEMONADE_DEFAULT_BASE_URL,
10039
- fetch: options.fetch,
10040
- probedAt: options.probedAt
10041
- });
10059
+ listModels,
10060
+ async probe(options) {
10061
+ const baseURL = normalizeBaseURL(options.baseURL ?? LEMONADE_DEFAULT_BASE_URL);
10062
+ try {
10063
+ return {
10064
+ id: "lemonade",
10065
+ baseURL,
10066
+ reachable: true,
10067
+ models: await listModels({ ...options, baseURL }),
10068
+ probedAt: options.probedAt
10069
+ };
10070
+ } catch (error) {
10071
+ return unavailableSnapshot({
10072
+ id: "lemonade",
10073
+ baseURL,
10074
+ probedAt: options.probedAt,
10075
+ error
10076
+ });
10077
+ }
10042
10078
  }
10043
10079
  };
10044
10080
  }
10081
+ async function listLemonadeModels(baseURL, fetch2) {
10082
+ const response = await fetch2(`${baseURL}/models`, {
10083
+ method: "GET",
10084
+ headers: { accept: "application/json" }
10085
+ });
10086
+ if (!response.ok) {
10087
+ throw new Error(`GET /models failed with HTTP ${response.status}`);
10088
+ }
10089
+ let payload;
10090
+ try {
10091
+ payload = await response.json();
10092
+ } catch (error) {
10093
+ throw new Error(`Malformed JSON from /models: ${errorMessage(error)}`);
10094
+ }
10095
+ const nonText = readNonTextModelIDs(payload);
10096
+ return parseOpenAIModels(payload).filter((model) => !nonText.has(model.modelID));
10097
+ }
10098
+ function readNonTextModelIDs(payload) {
10099
+ const ids = new Set;
10100
+ if (!isObject3(payload) || !Array.isArray(payload.data)) {
10101
+ return ids;
10102
+ }
10103
+ for (const entry of payload.data) {
10104
+ if (!isObject3(entry) || typeof entry.id !== "string") {
10105
+ continue;
10106
+ }
10107
+ if (isNonTextModel(entry)) {
10108
+ ids.add(entry.id);
10109
+ }
10110
+ }
10111
+ return ids;
10112
+ }
10113
+ function isNonTextModel(entry) {
10114
+ if (!Array.isArray(entry.labels)) {
10115
+ return false;
10116
+ }
10117
+ return entry.labels.some((label) => typeof label === "string" && NON_TEXT_LABELS.has(label));
10118
+ }
10119
+ async function readLoadedModels(baseURL, fetch2) {
10120
+ let payload;
10121
+ try {
10122
+ const response = await fetch2(`${baseURL}/health`, {
10123
+ method: "GET",
10124
+ headers: { accept: "application/json" }
10125
+ });
10126
+ if (!response.ok) {
10127
+ return;
10128
+ }
10129
+ payload = await response.json();
10130
+ } catch {
10131
+ return;
10132
+ }
10133
+ if (!isObject3(payload) || !Array.isArray(payload.all_models_loaded)) {
10134
+ return;
10135
+ }
10136
+ const loaded = new Map;
10137
+ for (const entry of payload.all_models_loaded) {
10138
+ if (!isObject3(entry) || typeof entry.model_name !== "string") {
10139
+ continue;
10140
+ }
10141
+ if (entry.loaded === false) {
10142
+ continue;
10143
+ }
10144
+ loaded.set(entry.model_name, { slots: readSlotCount(entry) });
10145
+ }
10146
+ return loaded;
10147
+ }
10148
+ function readSlotCount(entry) {
10149
+ const options = entry.recipe_options;
10150
+ if (!isObject3(options) || typeof options.llamacpp_args !== "string") {
10151
+ return;
10152
+ }
10153
+ const match = PARALLEL_ARG.exec(options.llamacpp_args);
10154
+ if (match === null) {
10155
+ return;
10156
+ }
10157
+ const parsed = Number.parseInt(match[1], 10);
10158
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
10159
+ }
10160
+ function enrichWithLoadState(models, loaded) {
10161
+ if (loaded === undefined) {
10162
+ return [...models];
10163
+ }
10164
+ return models.map((model) => {
10165
+ const hit = loaded.get(model.modelID);
10166
+ if (hit === undefined) {
10167
+ return { ...model, loaded: false };
10168
+ }
10169
+ return hit.slots === undefined ? { ...model, loaded: true } : { ...model, loaded: true, slots: hit.slots };
10170
+ });
10171
+ }
10172
+ function isObject3(value) {
10173
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10174
+ }
10045
10175
 
10046
10176
  // src/local/lmstudio.ts
10047
10177
  var LMSTUDIO_DEFAULT_BASE_URL = "http://localhost:1234/v1";
@@ -10270,6 +10400,9 @@ function createDetect(exec) {
10270
10400
  reachable: snapshot.reachable,
10271
10401
  models: snapshot.models.map((model) => model.modelID)
10272
10402
  };
10403
+ if (snapshot.models.some((model) => model.loaded !== undefined)) {
10404
+ detected.modelInfo = snapshot.models;
10405
+ }
10273
10406
  if (snapshot.error !== undefined) {
10274
10407
  detected.error = snapshot.error;
10275
10408
  }
@@ -10297,6 +10430,9 @@ function createProbeRuntime(exec) {
10297
10430
  reachable: snapshot?.reachable ?? false,
10298
10431
  models: snapshot?.models.map((model) => model.modelID) ?? []
10299
10432
  };
10433
+ if (snapshot?.models.some((model) => model.loaded !== undefined) === true) {
10434
+ detected.modelInfo = snapshot.models;
10435
+ }
10300
10436
  if (snapshot?.error !== undefined) {
10301
10437
  detected.error = snapshot.error;
10302
10438
  }
@@ -11049,6 +11185,14 @@ function runtimeLine(snapshot, runtime) {
11049
11185
  const slots = declared === undefined ? ` · ${DEFAULT_LOCAL_MAX_CONCURRENCY} slot(s) (default)` : ` · ${declared} slot(s) (declared)`;
11050
11186
  return ` ${mark} ${snapshot.id.padEnd(14)} ${snapshot.baseURL || "(no baseURL)"} — ${detail}${slots}`;
11051
11187
  }
11188
+ function runtimeModelSlotsLine(runtime) {
11189
+ const entries = Object.entries(runtime?.modelConcurrency ?? {});
11190
+ if (entries.length === 0) {
11191
+ return;
11192
+ }
11193
+ const detail = entries.map(([modelID, slots]) => `${modelID} ${slots} slot(s)`).join(" · ");
11194
+ return ` ↳ per-model slots: ${detail}`;
11195
+ }
11052
11196
  function isNetworkRuntime(baseURL) {
11053
11197
  if (baseURL.length === 0) {
11054
11198
  return false;
@@ -11304,6 +11448,10 @@ function renderDoctor(input) {
11304
11448
  if (classLine !== undefined) {
11305
11449
  lines.push(classLine);
11306
11450
  }
11451
+ const modelSlots = runtimeModelSlotsLine(runtime);
11452
+ if (modelSlots !== undefined) {
11453
+ lines.push(modelSlots);
11454
+ }
11307
11455
  }
11308
11456
  }
11309
11457
  for (const snapshot of input.snapshots) {
@@ -12039,6 +12187,9 @@ function buildOpenTeamConfig(answers) {
12039
12187
  },
12040
12188
  maxConcurrency: choice.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY
12041
12189
  };
12190
+ if (choice.modelConcurrency !== undefined && Object.keys(choice.modelConcurrency).length > 0) {
12191
+ runtime.modelConcurrency = { ...choice.modelConcurrency };
12192
+ }
12042
12193
  if (choice.thinkingModelID !== undefined && choice.thinkingModelID !== choice.defaultModelID) {
12043
12194
  runtime.thinkingModel = choice.thinkingModelID;
12044
12195
  }
@@ -12235,7 +12386,40 @@ async function chooseFrontier(prompt, profiles) {
12235
12386
  }
12236
12387
  return parseFrontierRef(selected);
12237
12388
  }
12238
- async function chooseRuntimeModel(prompt, models, meta) {
12389
+ function orderModelChoices(models, modelInfo) {
12390
+ const byID = new Map((modelInfo ?? []).map((model) => [model.modelID, model]));
12391
+ const anyLoadState = (modelInfo ?? []).some((model) => model.loaded !== undefined);
12392
+ const decorated = models.map((modelID, index) => {
12393
+ const info = byID.get(modelID);
12394
+ return {
12395
+ modelID,
12396
+ index,
12397
+ loaded: info?.loaded === true,
12398
+ slots: info?.slots
12399
+ };
12400
+ });
12401
+ if (!anyLoadState) {
12402
+ return decorated.map((entry) => ({
12403
+ value: entry.modelID,
12404
+ label: entry.modelID
12405
+ }));
12406
+ }
12407
+ return [...decorated].sort((a, b) => a.loaded === b.loaded ? a.index - b.index : a.loaded ? -1 : 1).map((entry) => ({
12408
+ value: entry.modelID,
12409
+ label: `${entry.modelID} ${modelStateSuffix(entry.loaded, entry.slots)}`
12410
+ }));
12411
+ }
12412
+ function modelStateSuffix(loaded, slots) {
12413
+ if (!loaded) {
12414
+ return "(not loaded)";
12415
+ }
12416
+ return slots === undefined ? "(loaded)" : `(loaded · ${slots} slot${slots === 1 ? "" : "s"})`;
12417
+ }
12418
+ function preferredModelID(models, modelInfo) {
12419
+ const loaded = (modelInfo ?? []).find((model) => model.loaded === true && models.includes(model.modelID));
12420
+ return loaded?.modelID ?? models[0];
12421
+ }
12422
+ async function chooseRuntimeModel(prompt, models, meta, modelInfo) {
12239
12423
  if (models.length === 0) {
12240
12424
  return prompt.text({
12241
12425
  message: `Default model for ${meta.label} (no models detected)`,
@@ -12243,13 +12427,10 @@ async function chooseRuntimeModel(prompt, models, meta) {
12243
12427
  initial: meta.fallbackModelID
12244
12428
  });
12245
12429
  }
12246
- const first = models[0];
12430
+ const first = preferredModelID(models, modelInfo);
12247
12431
  const opts = {
12248
12432
  message: `Default local model for ${meta.label}`,
12249
- choices: models.map((modelID) => ({
12250
- value: modelID,
12251
- label: modelID
12252
- }))
12433
+ choices: orderModelChoices(models, modelInfo)
12253
12434
  };
12254
12435
  if (first !== undefined) {
12255
12436
  opts.initial = first;
@@ -12285,9 +12466,13 @@ function normalizeRemoteBaseURL(raw, defaultBaseURL) {
12285
12466
  }
12286
12467
  return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
12287
12468
  }
12469
+ function connectionOf(info) {
12470
+ const models = info?.models ?? [];
12471
+ return info?.modelInfo === undefined ? { models } : { models, modelInfo: info.modelInfo };
12472
+ }
12288
12473
  async function resolveRuntimeConnection(deps, meta, info) {
12289
12474
  if (meta.defaultBaseURL === undefined) {
12290
- return { models: info?.models ?? [] };
12475
+ return connectionOf(info);
12291
12476
  }
12292
12477
  const location = await deps.prompt.select({
12293
12478
  message: `Where does ${meta.label} run?`,
@@ -12306,23 +12491,22 @@ async function resolveRuntimeConnection(deps, meta, info) {
12306
12491
  });
12307
12492
  if (location === RUNTIME_LOCATION_LOCAL) {
12308
12493
  const baseURL2 = info?.baseURL !== undefined && info.baseURL.length > 0 ? info.baseURL : meta.defaultBaseURL;
12309
- return { baseURL: baseURL2, models: info?.models ?? [] };
12494
+ return { baseURL: baseURL2, ...connectionOf(info) };
12310
12495
  }
12311
12496
  const entered = await deps.prompt.text({
12312
12497
  message: `${meta.label} URL on the network (OpenAI-compatible endpoint)`,
12313
12498
  placeholder: remoteBaseURLExample(meta)
12314
12499
  });
12315
12500
  const baseURL = normalizeRemoteBaseURL(entered, meta.defaultBaseURL);
12316
- let models = [];
12501
+ let probedRuntime;
12317
12502
  if (deps.probeRuntime !== undefined) {
12318
12503
  try {
12319
- const probed = await deps.probeRuntime(meta.id, baseURL);
12320
- models = probed.models;
12504
+ probedRuntime = await deps.probeRuntime(meta.id, baseURL);
12321
12505
  } catch {}
12322
12506
  }
12323
- return { baseURL, models };
12507
+ return { baseURL, ...connectionOf(probedRuntime) };
12324
12508
  }
12325
- async function chooseClassModel(prompt, models, meta, defaultModelID, modelClass) {
12509
+ async function chooseClassModel(prompt, models, meta, defaultModelID, modelClass, modelInfo) {
12326
12510
  const label = modelClass === "thinking" ? `Thinking model for ${meta.label} (design, analysis, review reasoning)` : `Coder model for ${meta.label} (writing and editing code)`;
12327
12511
  if (models.length === 0) {
12328
12512
  const raw = await prompt.text({
@@ -12334,18 +12518,18 @@ async function chooseClassModel(prompt, models, meta, defaultModelID, modelClass
12334
12518
  }
12335
12519
  return prompt.select({
12336
12520
  message: label,
12337
- choices: models.map((modelID) => ({
12338
- value: modelID,
12339
- label: modelID === defaultModelID ? `${modelID} (default)` : modelID
12521
+ choices: orderModelChoices(models, modelInfo).map((choice) => ({
12522
+ value: choice.value,
12523
+ label: choice.value === defaultModelID ? `${choice.label} — default` : choice.label
12340
12524
  })),
12341
12525
  initial: defaultModelID
12342
12526
  });
12343
12527
  }
12344
12528
  async function resolveEnabledRuntime(deps, meta, info) {
12345
12529
  const connection = await resolveRuntimeConnection(deps, meta, info);
12346
- const defaultModelID = await chooseRuntimeModel(deps.prompt, connection.models, meta);
12347
- const thinkingModelID = await chooseClassModel(deps.prompt, connection.models, meta, defaultModelID, "thinking");
12348
- const coderModelID = await chooseClassModel(deps.prompt, connection.models, meta, defaultModelID, "coder");
12530
+ const defaultModelID = await chooseRuntimeModel(deps.prompt, connection.models, meta, connection.modelInfo);
12531
+ const thinkingModelID = await chooseClassModel(deps.prompt, connection.models, meta, defaultModelID, "thinking", connection.modelInfo);
12532
+ const coderModelID = await chooseClassModel(deps.prompt, connection.models, meta, defaultModelID, "coder", connection.modelInfo);
12349
12533
  const choice = {
12350
12534
  id: meta.id,
12351
12535
  enabled: true,
@@ -12357,12 +12541,38 @@ async function resolveEnabledRuntime(deps, meta, info) {
12357
12541
  if (connection.baseURL !== undefined) {
12358
12542
  choice.baseURL = connection.baseURL;
12359
12543
  }
12544
+ const modelConcurrency = reportedModelSlots(connection.modelInfo, [
12545
+ defaultModelID,
12546
+ thinkingModelID,
12547
+ coderModelID
12548
+ ]);
12549
+ if (modelConcurrency !== undefined) {
12550
+ choice.modelConcurrency = modelConcurrency;
12551
+ }
12360
12552
  return choice;
12361
12553
  }
12554
+ function reportedModelSlots(modelInfo, usedModelIDs) {
12555
+ if (modelInfo === undefined) {
12556
+ return;
12557
+ }
12558
+ const used = new Set(usedModelIDs);
12559
+ const slots = {};
12560
+ for (const model of modelInfo) {
12561
+ if (model.slots !== undefined && used.has(model.modelID)) {
12562
+ slots[model.modelID] = model.slots;
12563
+ }
12564
+ }
12565
+ return Object.keys(slots).length > 0 ? slots : undefined;
12566
+ }
12362
12567
  function instanceSuffix(index) {
12363
12568
  return index < 26 ? String.fromCharCode(97 + index) : String(index + 1);
12364
12569
  }
12365
- async function promptSlotCount(prompt, meta) {
12570
+ async function promptSlotCount(prompt, meta, reported) {
12571
+ const detected = reportedSlotSummary(reported);
12572
+ if (detected !== undefined) {
12573
+ prompt.note(`${meta.label} reported its slots per model: ${detected.summary}. ` + "Slots belong to the loaded model, not the server, so openteam queues " + "each model against its own capacity instead of one shared number.", "Detected concurrency");
12574
+ return detected.max;
12575
+ }
12366
12576
  const raw = await prompt.text({
12367
12577
  message: `How many concurrent generation slots does ${meta.label} have? (match its real capacity; leaving one free keeps interactive work responsive)`,
12368
12578
  placeholder: String(DEFAULT_LOCAL_MAX_CONCURRENCY),
@@ -12371,6 +12581,16 @@ async function promptSlotCount(prompt, meta) {
12371
12581
  const parsed = Number.parseInt(raw.trim(), 10);
12372
12582
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_LOCAL_MAX_CONCURRENCY;
12373
12583
  }
12584
+ function reportedSlotSummary(reported) {
12585
+ const entries = Object.entries(reported ?? {});
12586
+ if (entries.length === 0) {
12587
+ return;
12588
+ }
12589
+ return {
12590
+ summary: entries.map(([modelID, slots]) => `${modelID} = ${slots}`).join(", "),
12591
+ max: Math.max(...entries.map(([, slots]) => slots))
12592
+ };
12593
+ }
12374
12594
  async function promptInstanceId(prompt, meta, defaultId, usedIds) {
12375
12595
  for (;; ) {
12376
12596
  const raw = await prompt.text({
@@ -12392,7 +12612,7 @@ async function resolveRuntimeInstances(deps, meta, info, usedIds) {
12392
12612
  });
12393
12613
  if (!fleet) {
12394
12614
  const choice = await resolveEnabledRuntime(deps, meta, info);
12395
- choice.maxConcurrency = await promptSlotCount(deps.prompt, meta);
12615
+ choice.maxConcurrency = await promptSlotCount(deps.prompt, meta, choice.modelConcurrency);
12396
12616
  usedIds.add(choice.id);
12397
12617
  return [choice];
12398
12618
  }
@@ -12403,7 +12623,7 @@ async function resolveRuntimeInstances(deps, meta, info, usedIds) {
12403
12623
  const choice = await resolveEnabledRuntime(deps, meta, info);
12404
12624
  choice.id = id;
12405
12625
  choice.kind = meta.id;
12406
- choice.maxConcurrency = await promptSlotCount(deps.prompt, meta);
12626
+ choice.maxConcurrency = await promptSlotCount(deps.prompt, meta, choice.modelConcurrency);
12407
12627
  usedIds.add(id);
12408
12628
  instances.push(choice);
12409
12629
  const another = await deps.prompt.confirm({
@@ -16239,7 +16459,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
16239
16459
  // package.json
16240
16460
  var package_default = {
16241
16461
  name: "@jmanuelcorral/openteam",
16242
- version: "0.17.0",
16462
+ version: "0.17.1",
16243
16463
  packageManager: "bun@1.3.14",
16244
16464
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
16245
16465
  license: "MIT",
@@ -1,5 +1,5 @@
1
1
  import type { LegacyLayoutFinding } from "../config/legacyPaths";
2
- import { type OpenTeamConfig } from "../config/schema";
2
+ import { type LocalRuntime, type OpenTeamConfig } from "../config/schema";
3
3
  import type { LocalRuntimeSnapshot } from "../local/types";
4
4
  import { type DiagnosticCode } from "../telemetry/diagnostics";
5
5
  import type { GraphViewHealth } from "../telemetry/graphView";
@@ -136,6 +136,17 @@ export type DoctorRosterAudit = {
136
136
  readonly misassignedGuaranteedRoleEntries?: readonly DoctorRosterAuditEntry[];
137
137
  readonly unprofiledRoleEntries?: readonly DoctorRosterAuditEntry[];
138
138
  };
139
+ /**
140
+ * Reports the per-model slot split when a runtime declares one.
141
+ *
142
+ * Without this the `N slot(s)` figure above reads as the whole truth, hiding the
143
+ * case that motivated per-model pools: a host declaring 4 server slots may serve
144
+ * its coder model at 3 and its thinking model at 1, so the roles routed to the
145
+ * thinking model serialise on a single slot. Returns `undefined` when the
146
+ * runtime declares no per-model slots, so nothing is printed for the runtimes
147
+ * that cannot report them. Pure.
148
+ */
149
+ export declare function runtimeModelSlotsLine(runtime: LocalRuntime | undefined): string | undefined;
139
150
  /**
140
151
  * Doctor's view of local-runtime queue-wait telemetry (#144). Presence of this
141
152
  * object means queue telemetry was consulted; `summary === undefined` means it
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}