@mars-sea/dsh-commandcode-provider 0.1.6 → 0.1.9
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/CHANGELOG.md +33 -0
- package/README.md +57 -17
- package/README.zh-CN.md +56 -17
- package/cordis.patch.yml +8 -1
- package/lib/client.js +57 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +39 -1
- package/lib/index.js +150 -20
- package/lib/index.js.map +1 -1
- package/package.json +19 -6
package/lib/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
-
import {
|
|
4
|
+
import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
|
|
5
|
+
import { CallId, LlmAdapter, LlmError, ReasoningEffortId, assertUsableApiKey, attributionHeaders, errorChain, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
6
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
6
7
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
7
8
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
@@ -166,10 +167,69 @@ const KNOWN_EFFORTS = {
|
|
|
166
167
|
"max"
|
|
167
168
|
]
|
|
168
169
|
};
|
|
170
|
+
/**
|
|
171
|
+
* Models whose Capabilities include Vision, per the official Command Code
|
|
172
|
+
* model registry (`https://commandcode.ai/docs/reference/cli/models`, generated
|
|
173
|
+
* from the same registry as `cmd --list-models` / the `/model` picker).
|
|
174
|
+
*
|
|
175
|
+
* The Provider API does not expose modality metadata, so this snapshot is the
|
|
176
|
+
* source of truth for image-input gating. Command Code's own CLI falls back to
|
|
177
|
+
* a client-side VISION side-call for text-only models; this adapter does not
|
|
178
|
+
* reproduce that interactive feature, so images sent to a model outside this
|
|
179
|
+
* list are refused loudly (`UNSUPPORTED_CONTENT`) instead of being dropped or
|
|
180
|
+
* sent to a model that cannot read them.
|
|
181
|
+
*
|
|
182
|
+
* Keep in sync with the official registry when new models ship (see the
|
|
183
|
+
* dsh-commandcode-upstream skill).
|
|
184
|
+
*/
|
|
185
|
+
const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
186
|
+
"MiniMaxAI/MiniMax-M3",
|
|
187
|
+
"Qwen/Qwen3.6-Plus",
|
|
188
|
+
"Qwen/Qwen3.7-Flash",
|
|
189
|
+
"Qwen/Qwen3.7-Plus",
|
|
190
|
+
"Qwen/Qwen3.8-Max",
|
|
191
|
+
"claude-fable-5",
|
|
192
|
+
"claude-haiku-4-5-20251001",
|
|
193
|
+
"claude-opus-4-7",
|
|
194
|
+
"claude-opus-4-8",
|
|
195
|
+
"claude-opus-5",
|
|
196
|
+
"claude-sonnet-4-6",
|
|
197
|
+
"claude-sonnet-5",
|
|
198
|
+
"google/gemini-3.1-flash-lite",
|
|
199
|
+
"google/gemini-3.5-flash",
|
|
200
|
+
"google/gemini-3.5-flash-lite",
|
|
201
|
+
"google/gemini-3.6-flash",
|
|
202
|
+
"google/gemini-3.7-flash",
|
|
203
|
+
"gpt-5.3-codex",
|
|
204
|
+
"gpt-5.4",
|
|
205
|
+
"gpt-5.4-mini",
|
|
206
|
+
"gpt-5.5",
|
|
207
|
+
"gpt-5.6-luna",
|
|
208
|
+
"gpt-5.6-sol",
|
|
209
|
+
"gpt-5.6-terra",
|
|
210
|
+
"meta/muse-spark-1.1",
|
|
211
|
+
"meta/muse-spark-1.2",
|
|
212
|
+
"meta/muse-spark-1.2-contributor",
|
|
213
|
+
"moonshotai/Kimi-K2.5",
|
|
214
|
+
"moonshotai/Kimi-K2.6",
|
|
215
|
+
"moonshotai/Kimi-K2.7-Code",
|
|
216
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed",
|
|
217
|
+
"moonshotai/Kimi-K3",
|
|
218
|
+
"sakana/fugu-ultra",
|
|
219
|
+
"stepfun/Step-3.7-Flash",
|
|
220
|
+
"thinkingmachines/inkling",
|
|
221
|
+
"thinkingmachines/inkling-small",
|
|
222
|
+
"xai/grok-4.5",
|
|
223
|
+
"xiaomi/mimo-v2.5"
|
|
224
|
+
]);
|
|
169
225
|
const COMMAND_CODE_CLI_VERSION = "1.26.0";
|
|
170
226
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
171
227
|
const DEFAULT_GENERATE_MAX_TOKENS = 64e3;
|
|
172
228
|
const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
|
|
229
|
+
/** Head-of-request timeout: how long to wait for the first response byte. */
|
|
230
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
231
|
+
/** Stream idle timeout: a generation that stalls this long is a dead connection. */
|
|
232
|
+
const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 12e4;
|
|
173
233
|
const MODEL_CACHE_VERSION = 1;
|
|
174
234
|
function isRecord(value) {
|
|
175
235
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -281,7 +341,24 @@ function hasImageContent(message) {
|
|
|
281
341
|
const check = (blocks) => blocks.some((b) => b.type === "image" || b.type === "tool-result" && check(b.content));
|
|
282
342
|
return check(message.content);
|
|
283
343
|
}
|
|
284
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Convert one image reference to the Command Code wire format, as the official
|
|
346
|
+
* CLI does: `{ type: 'image', source: { type: 'base64', media_type, data } }`.
|
|
347
|
+
* Bytes come from the durable attachment service; the media type is the one
|
|
348
|
+
* verified at save time.
|
|
349
|
+
*/
|
|
350
|
+
async function imageToCommandCode(ref, readImage) {
|
|
351
|
+
const data = await readImage(ref);
|
|
352
|
+
return {
|
|
353
|
+
type: "image",
|
|
354
|
+
source: {
|
|
355
|
+
type: "base64",
|
|
356
|
+
media_type: ref.mediaType,
|
|
357
|
+
data: Buffer.from(data).toString("base64")
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async function messagesToCC(messages, readImage) {
|
|
285
362
|
const out = [];
|
|
286
363
|
const paired = pairedToolCallIds(messages);
|
|
287
364
|
for (const message of messages) {
|
|
@@ -293,7 +370,10 @@ function messagesToCC(messages) {
|
|
|
293
370
|
type: "text",
|
|
294
371
|
text: block.text
|
|
295
372
|
});
|
|
296
|
-
if (block.type === "image")
|
|
373
|
+
if (block.type === "image") {
|
|
374
|
+
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
375
|
+
parts.push(await imageToCommandCode(block.attachment, readImage));
|
|
376
|
+
}
|
|
297
377
|
}
|
|
298
378
|
out.push({
|
|
299
379
|
role: "user",
|
|
@@ -345,10 +425,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
345
425
|
deps;
|
|
346
426
|
catalog = [];
|
|
347
427
|
fetchImpl;
|
|
428
|
+
resolveAttachments;
|
|
348
429
|
constructor(deps) {
|
|
349
430
|
super();
|
|
350
431
|
this.deps = deps;
|
|
351
432
|
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
433
|
+
this.resolveAttachments = deps.resolveAttachments;
|
|
352
434
|
}
|
|
353
435
|
/**
|
|
354
436
|
* Command Code is a metered subscription API: 429 (rate limit) and 5xx
|
|
@@ -382,21 +464,27 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
382
464
|
return this.catalog;
|
|
383
465
|
}
|
|
384
466
|
async listModels(provider) {
|
|
385
|
-
return (await this.loadCatalog()).map((model) =>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
467
|
+
return (await this.loadCatalog()).map((model) => {
|
|
468
|
+
const vision = KNOWN_IMAGE_MODELS.has(model.id);
|
|
469
|
+
return {
|
|
470
|
+
provider,
|
|
471
|
+
id: model.id,
|
|
472
|
+
name: `${model.name} (CC)`,
|
|
473
|
+
description: vision ? "Supports image input" : "Text only",
|
|
474
|
+
inputModalities: vision ? ["text", "image"] : ["text"]
|
|
475
|
+
};
|
|
476
|
+
});
|
|
391
477
|
}
|
|
392
478
|
async resolveModel(provider, model, signal) {
|
|
393
479
|
const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
|
|
394
480
|
const efforts = KNOWN_EFFORTS[model];
|
|
481
|
+
const vision = KNOWN_IMAGE_MODELS.has(model);
|
|
395
482
|
return {
|
|
396
483
|
provider,
|
|
397
484
|
id: model,
|
|
398
485
|
name: entry ? `${entry.name} (CC)` : model,
|
|
399
|
-
|
|
486
|
+
description: vision ? "Supports image input" : "Text only",
|
|
487
|
+
inputModalities: vision ? ["text", "image"] : ["text"],
|
|
400
488
|
...entry ? {
|
|
401
489
|
context: { contextWindow: entry.contextWindow },
|
|
402
490
|
defaultMaxTokens: Math.min(entry.maxTokens, DEFAULT_GENERATE_MAX_TOKENS)
|
|
@@ -485,7 +573,14 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
485
573
|
}
|
|
486
574
|
async *stream(options) {
|
|
487
575
|
if (options.stop?.length) throw new LlmError("Command Code adapter does not support stop sequences", "UNSUPPORTED_OPTION");
|
|
488
|
-
|
|
576
|
+
const hasImages = options.messages.some(hasImageContent);
|
|
577
|
+
let readImage;
|
|
578
|
+
if (hasImages) {
|
|
579
|
+
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");
|
|
580
|
+
const attachments = this.resolveAttachments?.();
|
|
581
|
+
if (attachments === void 0) throw new LlmError("Command Code image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
582
|
+
readImage = (ref) => attachments.readImage(ref).then((stored) => stored.data);
|
|
583
|
+
}
|
|
489
584
|
const connection = this.deps.options();
|
|
490
585
|
const apiKey = await this.deps.resolveApiKey(connection);
|
|
491
586
|
const modelMax = this.catalog.find((m) => m.id === options.model)?.maxTokens ?? 65536;
|
|
@@ -511,7 +606,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
511
606
|
skills: null,
|
|
512
607
|
params: {
|
|
513
608
|
model: options.model,
|
|
514
|
-
messages: messagesToCC(options.messages),
|
|
609
|
+
messages: await messagesToCC(options.messages, readImage),
|
|
515
610
|
tools: (options.tools ?? []).map((tool) => ({
|
|
516
611
|
type: "function",
|
|
517
612
|
name: tool.name,
|
|
@@ -541,11 +636,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
541
636
|
...attributionHeaders()
|
|
542
637
|
},
|
|
543
638
|
body: JSON.stringify(body),
|
|
544
|
-
|
|
639
|
+
signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(connection.requestTimeoutMs)]) : AbortSignal.timeout(connection.requestTimeoutMs)
|
|
545
640
|
});
|
|
546
641
|
} catch (error) {
|
|
547
642
|
if (options.signal?.aborted) throw error;
|
|
548
|
-
throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate
|
|
643
|
+
if (error instanceof DOMException && error.name === "TimeoutError") throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate did not respond within ${connection.requestTimeoutMs}ms: ${errorChain(error)}`, "TIMEOUT", { cause: error });
|
|
644
|
+
throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate failed: ${errorChain(error)}`, "TRANSPORT", { cause: error });
|
|
549
645
|
}
|
|
550
646
|
if (!response.ok) {
|
|
551
647
|
const errText = await response.text().catch(() => "");
|
|
@@ -562,6 +658,21 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
562
658
|
const reader = response.body.getReader();
|
|
563
659
|
const decoder = new TextDecoder();
|
|
564
660
|
let buffer = "";
|
|
661
|
+
let idleTimer;
|
|
662
|
+
let idleFired = false;
|
|
663
|
+
const armIdle = () => {
|
|
664
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
665
|
+
idleTimer = setTimeout(() => {
|
|
666
|
+
idleFired = true;
|
|
667
|
+
reader.cancel().catch(() => void 0);
|
|
668
|
+
}, connection.streamIdleTimeoutMs);
|
|
669
|
+
};
|
|
670
|
+
const clearIdle = () => {
|
|
671
|
+
if (idleTimer !== void 0) {
|
|
672
|
+
clearTimeout(idleTimer);
|
|
673
|
+
idleTimer = void 0;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
565
676
|
let nextIndex = 0;
|
|
566
677
|
let textIndex = -1;
|
|
567
678
|
let textContent = "";
|
|
@@ -708,14 +819,18 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
708
819
|
let finished = false;
|
|
709
820
|
for (;;) {
|
|
710
821
|
let read;
|
|
822
|
+
armIdle();
|
|
711
823
|
try {
|
|
712
824
|
read = await reader.read();
|
|
713
825
|
} catch (error) {
|
|
714
826
|
if (options.signal?.aborted) throw error;
|
|
715
|
-
throw new LlmError(`Command Code API stream from ${connection.apiBase} failed while reading`, "TRANSPORT", { cause: error });
|
|
827
|
+
throw new LlmError(`Command Code API stream from ${connection.apiBase} failed while reading: ${errorChain(error)}`, "TRANSPORT", { cause: error });
|
|
828
|
+
} finally {
|
|
829
|
+
clearIdle();
|
|
716
830
|
}
|
|
717
831
|
const { done, value } = read;
|
|
718
832
|
if (done) {
|
|
833
|
+
if (idleFired) throw new LlmError(`Command Code API stream from ${connection.apiBase} was idle for ${connection.streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, "TIMEOUT");
|
|
719
834
|
if (buffer.trim()) for (const chunk of handleEvent(parseStreamEventLine(buffer))) yield chunk;
|
|
720
835
|
break;
|
|
721
836
|
}
|
|
@@ -741,6 +856,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
741
856
|
};
|
|
742
857
|
}
|
|
743
858
|
} finally {
|
|
859
|
+
clearIdle();
|
|
744
860
|
await reader.cancel().catch(() => void 0);
|
|
745
861
|
reader.releaseLock();
|
|
746
862
|
}
|
|
@@ -844,11 +960,17 @@ function applyCommands(ctx, deps) {
|
|
|
844
960
|
*
|
|
845
961
|
* ```yaml
|
|
846
962
|
* - id: llm-commandcode
|
|
847
|
-
* name: dsh-commandcode-provider
|
|
963
|
+
* name: "@mars-sea/dsh-commandcode-provider"
|
|
848
964
|
* config:
|
|
849
965
|
* apiKeyEnv: COMMANDCODE_API_KEY
|
|
850
966
|
* ```
|
|
851
967
|
*
|
|
968
|
+
* The `name` is the full package specifier as installed in the profile's
|
|
969
|
+
* node_modules: the loader imports it as a module, and pnpm links packages by
|
|
970
|
+
* their true (scoped) name — a bare `dsh-commandcode-provider` fails to
|
|
971
|
+
* resolve (ERR_MODULE_NOT_FOUND) and crashes the app on boot. The value must
|
|
972
|
+
* be quoted in YAML: an unquoted scalar starting with `@` fails to parse.
|
|
973
|
+
*
|
|
852
974
|
* @module dsh-commandcode-provider
|
|
853
975
|
*/
|
|
854
976
|
const name = "llm-commandcode";
|
|
@@ -864,7 +986,9 @@ const Config = z.object({
|
|
|
864
986
|
apiKey: z.string(),
|
|
865
987
|
apiBase: z.string(),
|
|
866
988
|
workingDir: z.string(),
|
|
867
|
-
modelsCachePath: z.string()
|
|
989
|
+
modelsCachePath: z.string(),
|
|
990
|
+
requestTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
|
|
991
|
+
streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS)
|
|
868
992
|
});
|
|
869
993
|
/**
|
|
870
994
|
* The one explicit resolve step from raw config to validated connection
|
|
@@ -877,7 +1001,9 @@ function resolveAdapterOptions(config) {
|
|
|
877
1001
|
apiKeyEnv: credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV),
|
|
878
1002
|
apiBase: config.apiBase ?? "https://api.commandcode.ai",
|
|
879
1003
|
workingDir: config.workingDir ?? process.cwd(),
|
|
880
|
-
modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH
|
|
1004
|
+
modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH,
|
|
1005
|
+
requestTimeoutMs: config.requestTimeoutMs ?? 6e4,
|
|
1006
|
+
streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 12e4
|
|
881
1007
|
};
|
|
882
1008
|
}
|
|
883
1009
|
function apply(ctx, config) {
|
|
@@ -911,7 +1037,11 @@ function apply(ctx, config) {
|
|
|
911
1037
|
};
|
|
912
1038
|
const adapter = new CommandCodeAdapter({
|
|
913
1039
|
options,
|
|
914
|
-
resolveApiKey
|
|
1040
|
+
resolveApiKey,
|
|
1041
|
+
resolveAttachments: () => {
|
|
1042
|
+
const attachments = ctx.get("attachments");
|
|
1043
|
+
return attachments === void 0 ? void 0 : attachments;
|
|
1044
|
+
}
|
|
915
1045
|
});
|
|
916
1046
|
ctx.llm.registerConfigurableProviders([{
|
|
917
1047
|
provider: PROVIDER,
|
|
@@ -931,6 +1061,6 @@ function apply(ctx, config) {
|
|
|
931
1061
|
});
|
|
932
1062
|
}
|
|
933
1063
|
//#endregion
|
|
934
|
-
export { COMMAND_CODE_CLI_VERSION, CommandCodeAdapter, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, KNOWN_EFFORTS, PROVIDER, apply, applyCommands, commandDefinition, inject, name, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
|
|
1064
|
+
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, KNOWN_IMAGE_MODELS, PROVIDER, apply, applyCommands, commandDefinition, inject, name, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
|
|
935
1065
|
|
|
936
1066
|
//# sourceMappingURL=index.js.map
|