@mars-sea/dsh-commandcode-provider 0.1.8 → 0.2.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/lib/index.js CHANGED
@@ -35,6 +35,11 @@ const KNOWN_EFFORTS = {
35
35
  "medium",
36
36
  "xhigh"
37
37
  ],
38
+ "MiniMaxAI/MiniMax-M2.5": [
39
+ "low",
40
+ "medium",
41
+ "xhigh"
42
+ ],
38
43
  "claude-fable-5": [
39
44
  "low",
40
45
  "medium",
@@ -42,6 +47,13 @@ const KNOWN_EFFORTS = {
42
47
  "xhigh",
43
48
  "max"
44
49
  ],
50
+ "claude-haiku-4-5-20251001": [
51
+ "low",
52
+ "medium",
53
+ "high",
54
+ "xhigh",
55
+ "max"
56
+ ],
45
57
  "claude-opus-4-7": [
46
58
  "low",
47
59
  "medium",
@@ -148,7 +160,37 @@ const KNOWN_EFFORTS = {
148
160
  "xhigh",
149
161
  "max"
150
162
  ],
163
+ "meta/muse-spark-1.2-contributor": [
164
+ "low",
165
+ "medium",
166
+ "high"
167
+ ],
168
+ "moonshotai/Kimi-K2.5": [
169
+ "low",
170
+ "high",
171
+ "max"
172
+ ],
173
+ "moonshotai/Kimi-K2.6": [
174
+ "low",
175
+ "high",
176
+ "max"
177
+ ],
178
+ "moonshotai/Kimi-K2.7-Code-Highspeed": [
179
+ "low",
180
+ "high",
181
+ "max"
182
+ ],
151
183
  "sakana/fugu-ultra": ["high", "xhigh"],
184
+ "tencent/Hy3": [
185
+ "low",
186
+ "medium",
187
+ "high"
188
+ ],
189
+ "tencent/hy3-paid": [
190
+ "low",
191
+ "medium",
192
+ "high"
193
+ ],
152
194
  "xai/grok-4.5": [
153
195
  "low",
154
196
  "medium",
@@ -160,6 +202,16 @@ const KNOWN_EFFORTS = {
160
202
  "high",
161
203
  "xhigh"
162
204
  ],
205
+ "xiaomi/mimo-v2.5": [
206
+ "low",
207
+ "medium",
208
+ "xhigh"
209
+ ],
210
+ "xiaomi/mimo-v2.5-pro": [
211
+ "low",
212
+ "medium",
213
+ "xhigh"
214
+ ],
163
215
  "zai-org/GLM-5.2": ["high", "max"],
164
216
  "zai-org/GLM-5.3": [
165
217
  "low",
@@ -167,6 +219,213 @@ const KNOWN_EFFORTS = {
167
219
  "max"
168
220
  ]
169
221
  };
222
+ /**
223
+ * Models whose Capabilities include Vision, per the official Command Code
224
+ * model registry (`https://commandcode.ai/docs/reference/cli/models`, generated
225
+ * from the same registry as `cmd --list-models` / the `/model` picker).
226
+ *
227
+ * The Provider API does not expose modality metadata, so this snapshot is the
228
+ * source of truth for image-input gating. Command Code's own CLI falls back to
229
+ * a client-side VISION side-call for text-only models; this adapter does not
230
+ * reproduce that interactive feature, so images sent to a model outside this
231
+ * list are refused loudly (`UNSUPPORTED_CONTENT`) instead of being dropped or
232
+ * sent to a model that cannot read them.
233
+ *
234
+ * Keep in sync with the official registry when new models ship (see the
235
+ * dsh-commandcode-upstream skill).
236
+ */
237
+ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
238
+ "MiniMaxAI/MiniMax-M3",
239
+ "Qwen/Qwen3.6-Plus",
240
+ "Qwen/Qwen3.7-Flash",
241
+ "Qwen/Qwen3.7-Plus",
242
+ "Qwen/Qwen3.8-Max",
243
+ "claude-fable-5",
244
+ "claude-haiku-4-5-20251001",
245
+ "claude-opus-4-7",
246
+ "claude-opus-4-8",
247
+ "claude-opus-5",
248
+ "claude-sonnet-4-6",
249
+ "claude-sonnet-5",
250
+ "google/gemini-3.1-flash-lite",
251
+ "google/gemini-3.5-flash",
252
+ "google/gemini-3.5-flash-lite",
253
+ "google/gemini-3.6-flash",
254
+ "google/gemini-3.7-flash",
255
+ "gpt-5.3-codex",
256
+ "gpt-5.4",
257
+ "gpt-5.4-mini",
258
+ "gpt-5.5",
259
+ "gpt-5.6-luna",
260
+ "gpt-5.6-sol",
261
+ "gpt-5.6-terra",
262
+ "meta/muse-spark-1.1",
263
+ "meta/muse-spark-1.2",
264
+ "meta/muse-spark-1.2-contributor",
265
+ "moonshotai/Kimi-K2.5",
266
+ "moonshotai/Kimi-K2.6",
267
+ "moonshotai/Kimi-K2.7-Code",
268
+ "moonshotai/Kimi-K2.7-Code-Highspeed",
269
+ "moonshotai/Kimi-K3",
270
+ "sakana/fugu-ultra",
271
+ "stepfun/Step-3.7-Flash",
272
+ "thinkingmachines/inkling",
273
+ "thinkingmachines/inkling-small",
274
+ "xai/grok-4.5",
275
+ "xiaomi/mimo-v2.5"
276
+ ]);
277
+ /**
278
+ * Models whose official registry entry lists the `Reasoning` capability but
279
+ * for which the official CLI defines no selectable `reasoning_effort` levels
280
+ * (they are absent from the CLI's model-table efforts; see the authoritative
281
+ * `ZA` table in command-code@1.26.0). The model still thinks — Command Code
282
+ * drives its reasoning depth automatically — so the picker labels them
283
+ * "Supports thinking (auto)" instead of the misleading "Text only", while
284
+ * `KNOWN_EFFORTS` (which mirrors the CLI's effort map exactly) stays the sole
285
+ * source for selectable effort levels.
286
+ *
287
+ * Source: https://commandcode.ai/docs/reference/cli/models (Reasoning flag) and
288
+ * the command-code@1.26.0 bundled model table. Keep in sync via the
289
+ * dsh-commandcode-upstream skill.
290
+ */
291
+ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
292
+ "MiniMaxAI/MiniMax-M3",
293
+ "Qwen/Qwen3.6-Max-Preview",
294
+ "Qwen/Qwen3.6-Plus",
295
+ "Qwen/Qwen3.7-Flash",
296
+ "Qwen/Qwen3.7-Max",
297
+ "Qwen/Qwen3.7-Plus",
298
+ "moonshotai/Kimi-K3",
299
+ "moonshotai/Kimi-K2.7-Code",
300
+ "stepfun/Step-3.5-Flash",
301
+ "stepfun/Step-3.7-Flash",
302
+ "zai-org/GLM-5",
303
+ "zai-org/GLM-5.1",
304
+ "zai-org/GLM-5.2-Fast",
305
+ "nvidia/nemotron-3-ultra-550b-a55b",
306
+ "thinkingmachines/inkling",
307
+ "thinkingmachines/inkling-small",
308
+ "poolside/laguna-s-2.1-free",
309
+ "meta/muse-spark-1.1",
310
+ "meta/muse-spark-1.2"
311
+ ]);
312
+ /**
313
+ * The minimum subscription plan a model is included in, per the official plan
314
+ * pages (`/docs/plans/go`, `/docs/plans/goat`, `/docs/plans/pro`, `/docs/plans/max`
315
+ * and `/docs/resources/pricing-limits`). Each plan's model list is a superset of
316
+ * the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max. Models absent from every
317
+ * plan list (Claude Opus/Fable, Fugu Ultra) are Provider-tier.
318
+ *
319
+ * The Provider API exposes no plan metadata, so this snapshot is the source of
320
+ * truth for the picker's plan annotation — it answers "which plan do I need to
321
+ * actually use this model?" at a glance. Plan labels use the official tier
322
+ * names (`Go`, `GOAT`, `Pro`, `Provider`), with `Max` implying Provider.
323
+ *
324
+ * Keep in sync with the official plan pages when they change (see the
325
+ * dsh-commandcode-upstream skill).
326
+ */
327
+ const KNOWN_PLANS = {
328
+ "MiniMaxAI/MiniMax-M2.5": "go",
329
+ "MiniMaxAI/MiniMax-M2.7": "go",
330
+ "MiniMaxAI/MiniMax-M3": "go",
331
+ "Qwen/Qwen3.6-Max-Preview": "go",
332
+ "Qwen/Qwen3.6-Plus": "go",
333
+ "Qwen/Qwen3.7-Flash": "go",
334
+ "Qwen/Qwen3.7-Max": "go",
335
+ "Qwen/Qwen3.7-Plus": "go",
336
+ "Qwen/Qwen3.8-Max": "go",
337
+ "deepseek/deepseek-v4-flash": "go",
338
+ "deepseek/deepseek-v4-pro": "go",
339
+ "gpt-5.6-luna": "go",
340
+ "meta/muse-spark-1.2-contributor": "go",
341
+ "moonshotai/Kimi-K2.5": "go",
342
+ "moonshotai/Kimi-K2.6": "go",
343
+ "moonshotai/Kimi-K2.7-Code": "go",
344
+ "moonshotai/Kimi-K2.7-Code-Highspeed": "go",
345
+ "moonshotai/Kimi-K3": "go",
346
+ "nvidia/nemotron-3-ultra-550b-a55b": "go",
347
+ "poolside/laguna-s-2.1-free": "go",
348
+ "stepfun/Step-3.5-Flash": "go",
349
+ "stepfun/Step-3.7-Flash": "go",
350
+ "tencent/hy3-paid": "go",
351
+ "thinkingmachines/inkling": "go",
352
+ "thinkingmachines/inkling-small": "go",
353
+ "xai/grok-4.5": "go",
354
+ "xiaomi/mimo-v2.5": "go",
355
+ "xiaomi/mimo-v2.5-pro": "go",
356
+ "zai-org/GLM-5": "go",
357
+ "zai-org/GLM-5.1": "go",
358
+ "zai-org/GLM-5.2": "go",
359
+ "zai-org/GLM-5.2-Fast": "go",
360
+ "zai-org/GLM-5.3": "go",
361
+ "google/gemini-3.7-flash": "goat",
362
+ "meta/muse-spark-1.2": "goat",
363
+ "xai/grok-4.6": "goat",
364
+ "claude-haiku-4-5-20251001": "pro",
365
+ "claude-sonnet-4-6": "pro",
366
+ "claude-sonnet-5": "pro",
367
+ "google/gemini-3.1-flash-lite": "pro",
368
+ "google/gemini-3.5-flash": "pro",
369
+ "google/gemini-3.5-flash-lite": "pro",
370
+ "google/gemini-3.6-flash": "pro",
371
+ "gpt-5.3-codex": "pro",
372
+ "gpt-5.4": "pro",
373
+ "gpt-5.4-mini": "pro",
374
+ "gpt-5.5": "pro",
375
+ "gpt-5.6-sol": "pro",
376
+ "gpt-5.6-terra": "pro",
377
+ "meta/muse-spark-1.1": "pro",
378
+ "claude-fable-5": "provider",
379
+ "claude-opus-4-7": "provider",
380
+ "claude-opus-4-8": "provider",
381
+ "claude-opus-5": "provider",
382
+ "sakana/fugu-ultra": "provider"
383
+ };
384
+ /** Official display labels for each plan tier. */
385
+ const PLAN_LABELS = {
386
+ go: "Go",
387
+ goat: "GOAT",
388
+ pro: "Pro",
389
+ provider: "Provider",
390
+ max: "Max"
391
+ };
392
+ /**
393
+ * Plan-tier sort weights, low to high. Models outside the snapshot (unknown
394
+ * plans) sort after every known tier, keeping known models predictable.
395
+ */
396
+ const PLAN_ORDER = {
397
+ go: 0,
398
+ goat: 1,
399
+ pro: 2,
400
+ provider: 3,
401
+ max: 4
402
+ };
403
+ /**
404
+ * Comparator for the model picker: sort by plan tier (lowest first), then by
405
+ * model name, then by id as a tiebreak. Models with no known plan sort last.
406
+ */
407
+ function compareByPlan(a, b) {
408
+ const pa = PLAN_ORDER[KNOWN_PLANS[a.id] ?? ""] ?? Number.MAX_SAFE_INTEGER;
409
+ const pb = PLAN_ORDER[KNOWN_PLANS[b.id] ?? ""] ?? Number.MAX_SAFE_INTEGER;
410
+ if (pa !== pb) return pa - pb;
411
+ const nameDiff = a.name.localeCompare(b.name);
412
+ if (nameDiff !== 0) return nameDiff;
413
+ return a.id.localeCompare(b.id);
414
+ }
415
+ const KNOWN_DEALS = {
416
+ "deepseek/deepseek-v4-pro": { label: "75% off" },
417
+ "google/gemini-3.7-flash": {
418
+ label: "50% off",
419
+ expiresAt: "2026-12-31T23:59:59Z"
420
+ },
421
+ "MiniMaxAI/MiniMax-M3": { label: "50% off" },
422
+ "xiaomi/mimo-v2.5-pro": { label: "99% off" },
423
+ "xiaomi/mimo-v2.5": { label: "98% off" },
424
+ "poolside/laguna-s-2.1-free": {
425
+ label: "FREE",
426
+ free: true
427
+ }
428
+ };
170
429
  const COMMAND_CODE_CLI_VERSION = "1.26.0";
171
430
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
172
431
  const DEFAULT_GENERATE_MAX_TOKENS = 64e3;
@@ -179,6 +438,58 @@ const MODEL_CACHE_VERSION = 1;
179
438
  function isRecord(value) {
180
439
  return typeof value === "object" && value !== null && !Array.isArray(value);
181
440
  }
441
+ /**
442
+ * Official display label for a model's minimum plan, or undefined for models
443
+ * outside the snapshot (e.g. future catalog additions).
444
+ */
445
+ function planLabel(modelId) {
446
+ const plan = KNOWN_PLANS[modelId];
447
+ return plan === void 0 ? void 0 : PLAN_LABELS[plan];
448
+ }
449
+ /**
450
+ * The active deal label for a model, or undefined when the model has no deal
451
+ * or the deal has expired. Expiry is judged against `now` (defaults to
452
+ * `Date.now()`), so a snapshot that has gone stale stops showing its discount
453
+ * the moment the official end date passes — the user never believes a lapsed
454
+ * deal is still live. Permanent deals (no `expiresAt`) never lapse.
455
+ */
456
+ function dealLabel(modelId, now = Date.now()) {
457
+ const deal = KNOWN_DEALS[modelId];
458
+ if (deal === void 0) return void 0;
459
+ if (deal.expiresAt !== void 0 && now >= Date.parse(deal.expiresAt)) return void 0;
460
+ return deal.label;
461
+ }
462
+ /**
463
+ * Compact human-readable context window, e.g. `1_000_000 -> "1M"`,
464
+ * `256_000 -> "256K"`, `262_144 -> "256K"` (floor to the nearest K).
465
+ * Returns undefined for unknown/absent sizes.
466
+ */
467
+ function formatContext(contextWindow) {
468
+ if (contextWindow === void 0 || !Number.isFinite(contextWindow) || contextWindow <= 0) return;
469
+ if (contextWindow >= 1e6) {
470
+ const m = contextWindow / 1e6;
471
+ const rounded = Math.round(m * 10) / 10;
472
+ return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}M`;
473
+ }
474
+ return `${Math.floor(contextWindow / 1e3)}K`;
475
+ }
476
+ /**
477
+ * Compact one-line summary for the model picker: plan tier, then any active
478
+ * deal (discount or FREE), then `Image` for Vision-capable models, then the
479
+ * context window. Text-only models simply omit the Image marker — "Text only"
480
+ * adds nothing the picker needs to show.
481
+ */
482
+ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
483
+ const parts = [];
484
+ const plan = planLabel(modelId);
485
+ if (plan !== void 0) parts.push(plan);
486
+ const deal = dealLabel(modelId, now);
487
+ if (deal !== void 0) parts.push(deal);
488
+ if (KNOWN_IMAGE_MODELS.has(modelId)) parts.push("Image");
489
+ const ctx = formatContext(contextWindow);
490
+ if (ctx !== void 0) parts.push(ctx);
491
+ return parts.join(" · ");
492
+ }
182
493
  function stringValue(value) {
183
494
  return typeof value === "string" ? value : void 0;
184
495
  }
@@ -286,7 +597,24 @@ function hasImageContent(message) {
286
597
  const check = (blocks) => blocks.some((b) => b.type === "image" || b.type === "tool-result" && check(b.content));
287
598
  return check(message.content);
288
599
  }
289
- function messagesToCC(messages) {
600
+ /**
601
+ * Convert one image reference to the Command Code wire format, as the official
602
+ * CLI does: `{ type: 'image', source: { type: 'base64', media_type, data } }`.
603
+ * Bytes come from the durable attachment service; the media type is the one
604
+ * verified at save time.
605
+ */
606
+ async function imageToCommandCode(ref, readImage) {
607
+ const data = await readImage(ref);
608
+ return {
609
+ type: "image",
610
+ source: {
611
+ type: "base64",
612
+ media_type: ref.mediaType,
613
+ data: Buffer.from(data).toString("base64")
614
+ }
615
+ };
616
+ }
617
+ async function messagesToCC(messages, readImage) {
290
618
  const out = [];
291
619
  const paired = pairedToolCallIds(messages);
292
620
  for (const message of messages) {
@@ -298,7 +626,10 @@ function messagesToCC(messages) {
298
626
  type: "text",
299
627
  text: block.text
300
628
  });
301
- if (block.type === "image") throw new LlmError("Image input is not wired to the attachment service in this adapter yet", "UNSUPPORTED_CONTENT");
629
+ if (block.type === "image") {
630
+ if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
631
+ parts.push(await imageToCommandCode(block.attachment, readImage));
632
+ }
302
633
  }
303
634
  out.push({
304
635
  role: "user",
@@ -350,10 +681,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
350
681
  deps;
351
682
  catalog = [];
352
683
  fetchImpl;
684
+ resolveAttachments;
353
685
  constructor(deps) {
354
686
  super();
355
687
  this.deps = deps;
356
688
  this.fetchImpl = deps.fetchImpl ?? fetch;
689
+ this.resolveAttachments = deps.resolveAttachments;
357
690
  }
358
691
  /**
359
692
  * Command Code is a metered subscription API: 429 (rate limit) and 5xx
@@ -387,21 +720,27 @@ var CommandCodeAdapter = class extends LlmAdapter {
387
720
  return this.catalog;
388
721
  }
389
722
  async listModels(provider) {
390
- return (await this.loadCatalog()).map((model) => ({
391
- provider,
392
- id: model.id,
393
- name: `${model.name} (CC)`,
394
- inputModalities: ["text"]
395
- }));
723
+ return (await this.loadCatalog()).map((model) => {
724
+ const vision = KNOWN_IMAGE_MODELS.has(model.id);
725
+ return {
726
+ provider,
727
+ id: model.id,
728
+ name: `${model.name} (CC)`,
729
+ description: capabilityDescription(model.id, model.contextWindow),
730
+ inputModalities: vision ? ["text", "image"] : ["text"]
731
+ };
732
+ }).sort(compareByPlan);
396
733
  }
397
734
  async resolveModel(provider, model, signal) {
398
735
  const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
399
736
  const efforts = KNOWN_EFFORTS[model];
737
+ const vision = KNOWN_IMAGE_MODELS.has(model);
400
738
  return {
401
739
  provider,
402
740
  id: model,
403
741
  name: entry ? `${entry.name} (CC)` : model,
404
- inputModalities: ["text"],
742
+ description: capabilityDescription(model, entry?.contextWindow),
743
+ inputModalities: vision ? ["text", "image"] : ["text"],
405
744
  ...entry ? {
406
745
  context: { contextWindow: entry.contextWindow },
407
746
  defaultMaxTokens: Math.min(entry.maxTokens, DEFAULT_GENERATE_MAX_TOKENS)
@@ -490,7 +829,14 @@ var CommandCodeAdapter = class extends LlmAdapter {
490
829
  }
491
830
  async *stream(options) {
492
831
  if (options.stop?.length) throw new LlmError("Command Code adapter does not support stop sequences", "UNSUPPORTED_OPTION");
493
- if (options.messages.some(hasImageContent)) throw new LlmError("Image input is not wired to the attachment service in this adapter yet", "UNSUPPORTED_CONTENT");
832
+ const hasImages = options.messages.some(hasImageContent);
833
+ let readImage;
834
+ if (hasImages) {
835
+ if (!KNOWN_IMAGE_MODELS.has(options.model)) throw new LlmError(`Command Code model "${options.model}" does not support image input; switch to a Vision-capable model (see the model registry)`, "UNSUPPORTED_CONTENT");
836
+ const attachments = this.resolveAttachments?.();
837
+ if (attachments === void 0) throw new LlmError("Command Code image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
838
+ readImage = (ref) => attachments.readImage(ref).then((stored) => stored.data);
839
+ }
494
840
  const connection = this.deps.options();
495
841
  const apiKey = await this.deps.resolveApiKey(connection);
496
842
  const modelMax = this.catalog.find((m) => m.id === options.model)?.maxTokens ?? 65536;
@@ -516,7 +862,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
516
862
  skills: null,
517
863
  params: {
518
864
  model: options.model,
519
- messages: messagesToCC(options.messages),
865
+ messages: await messagesToCC(options.messages, readImage),
520
866
  tools: (options.tools ?? []).map((tool) => ({
521
867
  type: "function",
522
868
  name: tool.name,
@@ -947,7 +1293,11 @@ function apply(ctx, config) {
947
1293
  };
948
1294
  const adapter = new CommandCodeAdapter({
949
1295
  options,
950
- resolveApiKey
1296
+ resolveApiKey,
1297
+ resolveAttachments: () => {
1298
+ const attachments = ctx.get("attachments");
1299
+ return attachments === void 0 ? void 0 : attachments;
1300
+ }
951
1301
  });
952
1302
  ctx.llm.registerConfigurableProviders([{
953
1303
  provider: PROVIDER,
@@ -967,6 +1317,6 @@ function apply(ctx, config) {
967
1317
  });
968
1318
  }
969
1319
  //#endregion
970
- export { COMMAND_CODE_CLI_VERSION, CommandCodeAdapter, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, KNOWN_EFFORTS, PROVIDER, apply, applyCommands, commandDefinition, inject, name, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
1320
+ export { COMMAND_CODE_CLI_VERSION, CommandCodeAdapter, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PLANS, KNOWN_THINKING_MODELS, PLAN_LABELS, PLAN_ORDER, PROVIDER, apply, applyCommands, capabilityDescription, commandDefinition, compareByPlan, dealLabel, formatContext, inject, name, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
971
1321
 
972
1322
  //# sourceMappingURL=index.js.map