@mars-sea/dsh-commandcode-provider 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-08-14
8
+
9
+ ### Added
10
+
11
+ - Command Code LLM adapter for DeepSeek Harness, ported from pi-commandcode-provider@0.5.1 (MIT).
12
+ - `commandcode` provider route with a live model catalog (`GET /provider/v1/models`, ~54 models) and per-model reasoning-effort metadata (`KNOWN_EFFORTS`, command-code@1.15.1 snapshot).
13
+ - Models-page card ("Command Code") with an API-key field, backed by the dsh credential seam and the `llm-commandcode` settings namespace.
14
+ - API-key resolution: `config.apiKey` → credential reference `apiKeyEnv` (default `COMMANDCODE_API_KEY`) → launching environment → official CLI auth file (`~/.commandcode/auth.json`). pi/OMP auth files are not scanned.
15
+ - Installable dsh bundle (`dsh.bundle` manifest + `cordis.patch.yml` layer), supporting local-path, GitHub, and npm installs.
16
+ - `StreamChunk` protocol compliance: block assembly, usage-before-finish, tool-call replay for paired calls only, `LlmError` with stable codes, `attributionHeaders()` on all provider HTTP requests, `options.signal` honored.
17
+
18
+ ### Known limitations
19
+
20
+ - Text-only input (image input throws `UNSUPPORTED_CONTENT`); no `stop` sequences; reasoning blocks are not replayed into later turns.
package/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-commandcode-provider contributors
4
+
5
+ This plugin is a port of pi-commandcode-provider (Copyright (c) 2025 patlux,
6
+ MIT), which reverse-engineered the Command Code Provider API. Command Code is
7
+ a product of Command Code, Inc.; this project is not affiliated with or
8
+ endorsed by it, and your use of the Command Code service is governed by
9
+ Command Code's own terms.
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # dsh-commandcode-provider
2
+
3
+ [![CI](https://github.com/Mars-Sea/dsh-commandcode-provider/actions/workflows/ci.yml/badge.svg)](https://github.com/Mars-Sea/dsh-commandcode-provider/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![npm](https://img.shields.io/badge/npm-@mars--sea%2Fdsh--commandcode--provider-blue.svg)](https://www.npmjs.com/package/@mars-sea/dsh-commandcode-provider)
6
+
7
+ Unofficial [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) LLM provider plugin for **Command Code**, ported from [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider) (MIT). It registers a `commandcode` model provider whose requests are translated to Command Code's Provider API (`POST /alpha/generate`, reverse-engineered by the pi plugin, `command-code@1.15.1`).
8
+
9
+ > This is a community integration. You need your own Command Code account and API key or subscription, and Command Code's terms apply. This project is not affiliated with Command Code, Inc.
10
+
11
+ ## What you get
12
+
13
+ - A **plugin bundle** installable into any dsh profile with `dsh plugin add` (npm package with a `dsh.bundle` layer).
14
+ - A **`commandcode` provider route** registered on the `llm` service, selectable in the model picker, with the **live model catalog** fetched from `GET {apiBase}/provider/v1/models` (cached at `~/.commandcode/models-cache.json`).
15
+ - A **Models-page card** ("Command Code") with an API-key field — credentials are stored through the dsh credentials service, same as the DeepSeek card.
16
+ - **API key resolution** in this order: `config.apiKey` → credential reference `apiKeyEnv` (the web Models page writes it, default `COMMANDCODE_API_KEY`) → the launching environment → the official Command Code CLI auth file (`~/.commandcode/auth.json`, written by `command-code login`). pi/OMP auth files are deliberately **not** scanned.
17
+ - **Reasoning-effort support** for the models Command Code's catalog marks as such (e.g. `claude-opus-5`, `gpt-5.5`, `deepseek/deepseek-v4-pro`, …) via `KNOWN_EFFORTS`, matching the pi plugin's snapshot of `command-code@1.15.1`.
18
+
19
+ ## Getting an API key
20
+
21
+ Command Code API keys never expire. The easiest path is the official CLI (Node.js 22+):
22
+
23
+ ```sh
24
+ npm i -g command-code@latest
25
+ cmd login # macOS/Linux; native Windows: cmdc login
26
+ ```
27
+
28
+ `cmd login` opens a browser to authenticate; on success the key is written to `~/.commandcode/auth.json` — this plugin picks it up automatically (last-resort fallback). Alternatively create an API key in the browser ([Command Code Studio](https://commandcode.ai/studio/auth/cli)) and paste it into the Models page card, or `export COMMANDCODE_API_KEY="user_..."`.
29
+
30
+ ## Install
31
+
32
+ ### From GitHub (recommended)
33
+
34
+ ```sh
35
+ dsh plugin --profile web add github:Mars-Sea/dsh-commandcode-provider#<sha>
36
+ ```
37
+
38
+ A git install fetches **sources**, so the package's `prepare` script builds `lib/` after install. pnpm ≥10 blocks that script by default — run the `add`, then copy the **exact package key pnpm prints** into `~/.dsh/profiles/web/pnpm-workspace.yaml`:
39
+
40
+ ```yaml
41
+ allowBuilds:
42
+ 'dsh-commandcode-provider@github:Mars-Sea/dsh-commandcode-provider#<sha>': true
43
+ ```
44
+
45
+ and re-run the `add`. Only allow packages whose source you trust (and pin a commit).
46
+
47
+ ### From npm
48
+
49
+ Published as **`@mars-sea/dsh-commandcode-provider`** (the bare name `dsh-commandcode-provider` is taken on the npm registry by an unrelated package):
50
+
51
+ ```sh
52
+ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider
53
+ ```
54
+
55
+ ### From a local checkout
56
+
57
+ ```sh
58
+ npm install
59
+ npm run build # git-installed/tarball installs do this via `prepare` automatically
60
+ dsh plugin --profile web add /path/to/dsh-commandcode-provider
61
+ ```
62
+
63
+ A local path install links the checkout as-is, so after changing `src/` re-run `npm run build` and restart the app.
64
+
65
+ ### What the install does
66
+
67
+ `dsh plugin add` links the package into the profile, appends `dsh-commandcode-provider` to the profile's `dsh.profile.bundles`, and activates the `cordis.patch.yml` layer, which inserts:
68
+
69
+ ```yaml
70
+ - insert:
71
+ - id: llm-commandcode
72
+ name: dsh-commandcode-provider
73
+ config:
74
+ apiKeyEnv: COMMANDCODE_API_KEY
75
+ ```
76
+
77
+ Verify the composed layer, then (re)start the web app:
78
+
79
+ ```sh
80
+ dsh --profile web --dump-config # shows a "# == dsh-commandcode-provider" layer
81
+ dsh web # or restart your running instance
82
+ ```
83
+
84
+ ## Verify it works
85
+
86
+ After restart, in the web UI: **Settings → Models** shows a **Command Code** card; the model picker lists the live catalog under **commandcode** (54 models at the time of writing). Send a message with a model your plan includes — the default `deepseek/deepseek-v4-flash` works on entry-level plans; open-weight models (DeepSeek/Qwen/Kimi/MiniMax) generally do, while frontier models (Claude/GPT/Gemini/Grok) may require Pro/Max plans or on-demand usage (see FAQ).
87
+
88
+ ## Configure
89
+
90
+ The Command Code card takes your API key (stored in `$DSH_HOME/.credentials.yaml`; the model catalog is browsable without one). Advanced knobs live in the `llm-commandcode` section of `$DSH_HOME/settings.yaml` (overrides the bundle defaults per request, no restart needed):
91
+
92
+ ```yaml
93
+ llm-commandcode:
94
+ apiKeyEnv: COMMANDCODE_API_KEY # credential reference resolved per request
95
+ apiBase: https://api.commandcode.ai
96
+ workingDir: /path/to/project # reported to the API (project slug, config block)
97
+ modelsCachePath: ~/.commandcode/models-cache.json
98
+ ```
99
+
100
+ The composition-entry config (`cordis.patch.yml` / your profile `cordis.patch.yml`) accepts the same keys; a literal `apiKey` there takes precedence over the credential reference.
101
+
102
+ ## Troubleshooting
103
+
104
+ - **`MODEL_NOT_IN_PLAN` (403)** — the selected model is not in your Command Code plan. Pick an open-weight model (e.g. `deepseek/deepseek-v4-flash`) or upgrade. The error names the model and links the official docs.
105
+ - **`MISSING_CREDENTIAL`** — no key anywhere. Store one via the Models page card, export `COMMANDCODE_API_KEY`, set `config.apiKey`, or run `command-code login`. The route stays registered and the catalog stays browsable without a key.
106
+ - **The Models page card shows "not configured" but requests work** — the key came from `~/.commandcode/auth.json` (the `cmd login` fallback), not the dsh credential store. Paste it into the card once to make the card show as configured; both coexist fine.
107
+ - **A reasoning model returns no visible text on short requests** — reasoning models (e.g. `deepseek/deepseek-v4-*`) consume output tokens on reasoning first; a small `maxTokens` can be exhausted before any visible text. This is normal.
108
+ - **`allowBuilds` errors on `dsh plugin add` from git** — copy the exact package key pnpm printed (with the commit hash) into `pnpm-workspace.yaml` and re-run (see [Install](#from-github-recommended)).
109
+
110
+ ## Notes & limitations
111
+
112
+ - **Text-only for now**: image input throws `UNSUPPORTED_CONTENT` (wiring the attachment service to resolve image bytes is future work). The pi plugin's `MODEL_INPUT_MODALITIES` table is intentionally not claimed.
113
+ - **No `stop` sequences**: the wire format has no stop field; requests carrying one throw `UNSUPPORTED_OPTION`.
114
+ - Reasoning blocks are **not replayed** into later turns (matches the official CLI: prior private reasoning must not leak).
115
+ - Only tool calls with a paired tool result are replayed into the conversation.
116
+ - The model catalog endpoint is public; requests to `/alpha/generate` require the key above.
117
+
118
+ ## Development
119
+
120
+ ```sh
121
+ npm install
122
+ npm run typecheck # tsc --noEmit
123
+ npm run build # tsdown -> lib/
124
+ ```
125
+
126
+ ## License
127
+
128
+ MIT — see [LICENSE](./LICENSE). Portions ported from [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider) (MIT).
@@ -0,0 +1,8 @@
1
+ # dsh-commandcode-provider bundle layer.
2
+ # One plugin row: registers the `commandcode` provider route and its
3
+ # Models-page card. All fields are optional — schema defaults apply.
4
+ - insert:
5
+ - id: llm-commandcode
6
+ name: dsh-commandcode-provider
7
+ config:
8
+ apiKeyEnv: COMMANDCODE_API_KEY
package/lib/index.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { GenerateOptions, LlmAdapter, LlmModelInfo, LlmResolvedModelInfo, StreamChunk } from "@deepseek-ai/dsh-llm";
3
+ import { CredentialRef } from "@deepseek-ai/dsh-credentials";
4
+ import { Context } from "@deepseek-ai/cordis";
5
+ //#region src/adapter.d.ts
6
+ declare const KNOWN_EFFORTS: Readonly<Record<string, readonly string[]>>;
7
+ declare const COMMAND_CODE_CLI_VERSION = "1.15.1";
8
+ declare const DEFAULT_API_BASE = "https://api.commandcode.ai";
9
+ declare const DEFAULT_GENERATE_MAX_TOKENS = 64000;
10
+ declare const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
11
+ declare function projectSlugFromPath(pathName: string): string;
12
+ /** Read a usable Command Code credential from the official CLI auth file. */
13
+ declare function resolveAuthFileApiKey(): string | undefined;
14
+ /** Connection facts resolved fresh per request by the plugin entry. */
15
+ interface CommandCodeConnectionOptions {
16
+ /** API base; the Provider API lives under it (`/alpha/generate`, `/provider/v1/models`). */
17
+ apiBase: string;
18
+ /** Working directory reported to the API (project slug, config block). */
19
+ workingDir: string;
20
+ /** Model catalog cache path. */
21
+ modelsCachePath: string;
22
+ }
23
+ /** Everything the adapter needs beyond the request itself. */
24
+ interface CommandCodeAdapterDeps<C extends CommandCodeConnectionOptions = CommandCodeConnectionOptions> {
25
+ /** Resolve the current connection facts (fresh per request, settings-aware). */
26
+ options: () => C;
27
+ /** Resolve a usable API key for the given connection facts, or throw `MISSING_CREDENTIAL`. */
28
+ resolveApiKey: (connection: C) => Promise<string>;
29
+ }
30
+ declare class CommandCodeAdapter<C extends CommandCodeConnectionOptions = CommandCodeConnectionOptions> extends LlmAdapter {
31
+ private readonly deps;
32
+ private catalog;
33
+ constructor(deps: CommandCodeAdapterDeps<C>);
34
+ /** Refresh the catalog (live fetch, cache fallback) and return it. */
35
+ private loadCatalog;
36
+ listModels(provider: string): Promise<readonly LlmModelInfo[]>;
37
+ resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
38
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
39
+ }
40
+ //#endregion
41
+ //#region src/index.d.ts
42
+ declare const name = "llm-commandcode";
43
+ declare const inject: string[];
44
+ /** The single provider route this plugin owns. */
45
+ declare const PROVIDER = "commandcode";
46
+ /** Default models cache path (mirrors the pi plugin's on-disk cache). */
47
+ declare const DEFAULT_MODELS_CACHE_PATH: string;
48
+ /**
49
+ * Plugin config, validated by the same-named schemastery schema and doubling
50
+ * as the `llm-commandcode` settings-section shape. Every field is optional:
51
+ * a missing API key resolves through {@link Config.apiKeyEnv} at each request
52
+ * (the web Models page writes it), with the official Command Code CLI auth
53
+ * file (`~/.commandcode/auth.json`) as the last fallback.
54
+ */
55
+ interface Config {
56
+ /** Credential reference (environment-variable name) resolved per request; defaults to `COMMANDCODE_API_KEY`. */
57
+ apiKeyEnv?: string;
58
+ /** Literal API key override (composition config only); takes precedence over `apiKeyEnv`. */
59
+ apiKey?: string;
60
+ /** API base; defaults to the public Command Code Provider API. */
61
+ apiBase?: string;
62
+ /** Working directory reported to the API; defaults to the process cwd. */
63
+ workingDir?: string;
64
+ /** Model catalog cache path; defaults to `~/.commandcode/models-cache.json`. */
65
+ modelsCachePath?: string;
66
+ }
67
+ declare const Config: z<Config>;
68
+ /** One resolution's complete request facts: connection plus credential reference. */
69
+ interface ResolvedCommandCodeOptions extends CommandCodeConnectionOptions {
70
+ apiKeyEnv: CredentialRef;
71
+ }
72
+ /**
73
+ * The one explicit resolve step from raw config to validated connection
74
+ * facts. Programmatic construction may bypass Schemastery normalization, so
75
+ * every default is re-judged here — for the composition entry at load and for
76
+ * each settings snapshot at its first use.
77
+ */
78
+ declare function resolveAdapterOptions(config: Config): ResolvedCommandCodeOptions;
79
+ declare function apply(ctx: Context, config: Config): void;
80
+ //#endregion
81
+ export { COMMAND_CODE_CLI_VERSION, CommandCodeAdapter, type CommandCodeAdapterDeps, type CommandCodeConnectionOptions, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, KNOWN_EFFORTS, PROVIDER, ResolvedCommandCodeOptions, apply, inject, name, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
82
+ //# sourceMappingURL=index.d.ts.map
package/lib/index.js ADDED
@@ -0,0 +1,731 @@
1
+ import { homedir } from "node:os";
2
+ import { dirname, join } from "node:path";
3
+ import z from "@deepseek-ai/schemastery";
4
+ import { CallId, LlmAdapter, LlmError, ReasoningEffortId, assertUsableApiKey, attributionHeaders } from "@deepseek-ai/dsh-llm";
5
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
6
+ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
7
+ import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
10
+ import { randomUUID } from "node:crypto";
11
+ //#region src/adapter.ts
12
+ /**
13
+ * DeepSeek Harness LLM adapter for the Command Code Provider API.
14
+ *
15
+ * Ported from pi-commandcode-provider@0.5.1 (MIT). This is an unofficial,
16
+ * community-maintained integration; you need your own Command Code account
17
+ * and API key or subscription, and Command Code's terms apply.
18
+ *
19
+ * Wire protocol (reverse-engineered by the pi plugin, command-code@1.15.1):
20
+ * POST {apiBase}/alpha/generate
21
+ * body: { config, memory, taste, skills, params: { model, messages, tools,
22
+ * system, max_tokens, temperature, stream, reasoning_effort? }, threadId }
23
+ * SSE-ish JSONL events: text-delta | reasoning-start/delta/end | tool-call
24
+ * | tool-result | finish | error
25
+ * Model catalog: GET {apiBase}/provider/v1/models -> { object: 'list', data: [...] }
26
+ *
27
+ * The adapter is deliberately free of cordis/schemastery: it receives a
28
+ * per-request options thunk and an API-key resolver from the plugin entry
29
+ * (src/index.ts), so a settings change reaches the very next request.
30
+ */
31
+ const KNOWN_EFFORTS = {
32
+ "Qwen/Qwen3.8-Max": [
33
+ "low",
34
+ "medium",
35
+ "xhigh"
36
+ ],
37
+ "claude-fable-5": [
38
+ "low",
39
+ "medium",
40
+ "high",
41
+ "xhigh",
42
+ "max"
43
+ ],
44
+ "claude-opus-4-7": [
45
+ "low",
46
+ "medium",
47
+ "high",
48
+ "xhigh",
49
+ "max"
50
+ ],
51
+ "claude-opus-4-8": [
52
+ "low",
53
+ "medium",
54
+ "high",
55
+ "xhigh",
56
+ "max"
57
+ ],
58
+ "claude-opus-5": [
59
+ "low",
60
+ "medium",
61
+ "high",
62
+ "xhigh",
63
+ "max"
64
+ ],
65
+ "claude-sonnet-4-6": [
66
+ "low",
67
+ "medium",
68
+ "high",
69
+ "xhigh",
70
+ "max"
71
+ ],
72
+ "claude-sonnet-5": [
73
+ "low",
74
+ "medium",
75
+ "high",
76
+ "xhigh",
77
+ "max"
78
+ ],
79
+ "deepseek/deepseek-v4-flash": ["high", "max"],
80
+ "deepseek/deepseek-v4-pro": ["high", "max"],
81
+ "gpt-5.3-codex": [
82
+ "low",
83
+ "medium",
84
+ "high",
85
+ "xhigh"
86
+ ],
87
+ "gpt-5.4": [
88
+ "low",
89
+ "medium",
90
+ "high",
91
+ "xhigh"
92
+ ],
93
+ "gpt-5.4-mini": [
94
+ "low",
95
+ "medium",
96
+ "high"
97
+ ],
98
+ "gpt-5.5": [
99
+ "low",
100
+ "medium",
101
+ "high",
102
+ "xhigh"
103
+ ],
104
+ "gpt-5.6-luna": [
105
+ "low",
106
+ "medium",
107
+ "high",
108
+ "xhigh",
109
+ "max"
110
+ ],
111
+ "gpt-5.6-sol": [
112
+ "low",
113
+ "medium",
114
+ "high",
115
+ "xhigh",
116
+ "max"
117
+ ],
118
+ "gpt-5.6-terra": [
119
+ "low",
120
+ "medium",
121
+ "high",
122
+ "xhigh",
123
+ "max"
124
+ ],
125
+ "google/gemini-3.1-flash-lite": [
126
+ "low",
127
+ "medium",
128
+ "high"
129
+ ],
130
+ "google/gemini-3.5-flash": [
131
+ "low",
132
+ "medium",
133
+ "high"
134
+ ],
135
+ "google/gemini-3.5-flash-lite": [
136
+ "low",
137
+ "medium",
138
+ "high"
139
+ ],
140
+ "google/gemini-3.6-flash": [
141
+ "low",
142
+ "medium",
143
+ "high"
144
+ ],
145
+ "sakana/fugu-ultra": ["high", "xhigh"],
146
+ "xai/grok-4.5": [
147
+ "low",
148
+ "medium",
149
+ "high"
150
+ ],
151
+ "zai-org/GLM-5.2": ["high", "max"]
152
+ };
153
+ const COMMAND_CODE_CLI_VERSION = "1.15.1";
154
+ const DEFAULT_API_BASE = "https://api.commandcode.ai";
155
+ const DEFAULT_GENERATE_MAX_TOKENS = 64e3;
156
+ const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
157
+ const MODEL_CACHE_VERSION = 1;
158
+ function isRecord(value) {
159
+ return typeof value === "object" && value !== null && !Array.isArray(value);
160
+ }
161
+ function stringValue(value) {
162
+ return typeof value === "string" ? value : void 0;
163
+ }
164
+ function numberValue(value) {
165
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
166
+ }
167
+ function recordOrEmpty(value) {
168
+ if (isRecord(value)) return value;
169
+ if (typeof value === "string") try {
170
+ const parsed = JSON.parse(value);
171
+ if (isRecord(parsed)) return parsed;
172
+ } catch {}
173
+ return {};
174
+ }
175
+ function projectSlugFromPath(pathName) {
176
+ return pathName.toLowerCase().replace(/^[a-z]:/i, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
177
+ }
178
+ function parseStreamEventLine(line) {
179
+ let trimmed = line.trim();
180
+ if (!trimmed || trimmed.startsWith(":") || trimmed.startsWith("event:")) return void 0;
181
+ if (trimmed.startsWith("data:")) trimmed = trimmed.slice(5).trim();
182
+ if (!trimmed || trimmed === "[DONE]") return void 0;
183
+ try {
184
+ return JSON.parse(trimmed);
185
+ } catch {
186
+ return;
187
+ }
188
+ }
189
+ /** Extract the key from the CLI's nested credential records (`command-code`). */
190
+ function apiKeyFromCredentialRecord(value) {
191
+ if (!isRecord(value)) return void 0;
192
+ const type = stringValue(value.type);
193
+ if (type === "api") return stringValue(value.key);
194
+ if (type === "oauth") return stringValue(value.access);
195
+ return stringValue(value.key) ?? stringValue(value.access);
196
+ }
197
+ /** Read a usable Command Code credential from the official CLI auth file. */
198
+ function resolveAuthFileApiKey() {
199
+ const authPath = join(homedir(), ".commandcode", "auth.json");
200
+ try {
201
+ if (!existsSync(authPath)) return void 0;
202
+ const parsed = JSON.parse(readFileSync(authPath, "utf-8"));
203
+ if (!isRecord(parsed)) return void 0;
204
+ const direct = stringValue(parsed.apiKey) ?? stringValue(parsed.commandcode);
205
+ if (direct) return direct;
206
+ return apiKeyFromCredentialRecord(parsed.commandcode) ?? apiKeyFromCredentialRecord(parsed["command-code"]);
207
+ } catch {}
208
+ }
209
+ function parseCatalogResponse(value) {
210
+ if (!isRecord(value) || value.object !== "list" || !Array.isArray(value.data)) throw new LlmError("Unexpected Command Code models response shape", "PROVIDER_PROTOCOL_ERROR");
211
+ const models = [];
212
+ for (const entry of value.data) {
213
+ if (!isRecord(entry)) continue;
214
+ const id = stringValue(entry.id);
215
+ const name = stringValue(entry.name);
216
+ const contextLength = numberValue(entry.context_length);
217
+ if (!id || !name || !contextLength || contextLength <= 0) continue;
218
+ models.push({
219
+ id,
220
+ name,
221
+ contextWindow: contextLength,
222
+ maxTokens: Math.min(contextLength, DEFAULT_MAX_OUTPUT_TOKENS)
223
+ });
224
+ }
225
+ if (models.length === 0) throw new LlmError("Command Code returned an empty model catalog", "PROVIDER_PROTOCOL_ERROR");
226
+ return models;
227
+ }
228
+ async function readModelsCache(cachePath) {
229
+ const parsed = JSON.parse(await readFile(cachePath, "utf-8"));
230
+ if (!isRecord(parsed) || parsed.version !== MODEL_CACHE_VERSION || !Array.isArray(parsed.models)) throw new Error(`Invalid model cache at ${cachePath}`);
231
+ return parsed.models;
232
+ }
233
+ async function writeModelsCache(cachePath, models) {
234
+ await mkdir(dirname(cachePath), { recursive: true });
235
+ const tmp = `${cachePath}.${process.pid}.tmp`;
236
+ try {
237
+ await writeFile(tmp, `${JSON.stringify({
238
+ version: MODEL_CACHE_VERSION,
239
+ models
240
+ }, null, 2)}\n`, {
241
+ encoding: "utf-8",
242
+ mode: 384
243
+ });
244
+ await rename(tmp, cachePath);
245
+ } finally {
246
+ await rm(tmp, { force: true }).catch(() => void 0);
247
+ }
248
+ }
249
+ function pairedToolCallIds(messages) {
250
+ const callIds = /* @__PURE__ */ new Set();
251
+ const resultIds = /* @__PURE__ */ new Set();
252
+ for (const message of messages) for (const block of message.content) {
253
+ if (message.role === "assistant" && block.type === "tool-call") callIds.add(block.id);
254
+ if (block.type === "tool-result") resultIds.add(block.toolCallId);
255
+ }
256
+ return new Set([...callIds].filter((id) => resultIds.has(id)));
257
+ }
258
+ function blockText(block) {
259
+ return block.type === "text" || block.type === "reasoning" ? block.text : "";
260
+ }
261
+ function toolResultText(block) {
262
+ return block.content.map(blockText).filter(Boolean).join("\n");
263
+ }
264
+ function hasImageContent(message) {
265
+ const check = (blocks) => blocks.some((b) => b.type === "image" || b.type === "tool-result" && check(b.content));
266
+ return check(message.content);
267
+ }
268
+ function messagesToCC(messages) {
269
+ const out = [];
270
+ const paired = pairedToolCallIds(messages);
271
+ for (const message of messages) {
272
+ if (message.role === "system") continue;
273
+ if (message.role === "user" && message.source.kind !== "tool") {
274
+ const parts = [];
275
+ for (const block of message.content) {
276
+ if (block.type === "text") parts.push({
277
+ type: "text",
278
+ text: block.text
279
+ });
280
+ if (block.type === "image") throw new LlmError("Image input is not wired to the attachment service in this adapter yet", "UNSUPPORTED_CONTENT");
281
+ }
282
+ out.push({
283
+ role: "user",
284
+ content: parts
285
+ });
286
+ continue;
287
+ }
288
+ if (message.role === "assistant") {
289
+ const parts = [];
290
+ for (const block of message.content) if (block.type === "text") parts.push({
291
+ type: "text",
292
+ text: block.text
293
+ });
294
+ else if (block.type === "tool-call" && paired.has(block.id)) parts.push({
295
+ type: "tool-call",
296
+ toolCallId: block.id,
297
+ toolName: block.name,
298
+ input: recordOrEmpty(block.arguments)
299
+ });
300
+ if (parts.length > 0) out.push({
301
+ role: "assistant",
302
+ content: parts
303
+ });
304
+ continue;
305
+ }
306
+ if (message.role === "user" && message.source.kind === "tool") {
307
+ const block = message.content[0];
308
+ if (!block || block.type !== "tool-result" || !paired.has(block.toolCallId)) continue;
309
+ out.push({
310
+ role: "tool",
311
+ content: [{
312
+ type: "tool-result",
313
+ toolCallId: block.toolCallId,
314
+ toolName: "",
315
+ output: block.isError ? {
316
+ type: "error-text",
317
+ value: toolResultText(block)
318
+ } : {
319
+ type: "text",
320
+ value: toolResultText(block)
321
+ }
322
+ }]
323
+ });
324
+ }
325
+ }
326
+ return out;
327
+ }
328
+ var CommandCodeAdapter = class extends LlmAdapter {
329
+ deps;
330
+ catalog = [];
331
+ constructor(deps) {
332
+ super();
333
+ this.deps = deps;
334
+ }
335
+ /** Refresh the catalog (live fetch, cache fallback) and return it. */
336
+ async loadCatalog(signal) {
337
+ const { apiBase, modelsCachePath } = this.deps.options();
338
+ try {
339
+ const response = await fetch(`${apiBase}/provider/v1/models`, {
340
+ headers: {
341
+ accept: "application/json",
342
+ ...attributionHeaders()
343
+ },
344
+ signal: signal ?? AbortSignal.timeout(1e4)
345
+ });
346
+ if (!response.ok) throw new Error(`models endpoint returned ${response.status}`);
347
+ this.catalog = parseCatalogResponse(await response.json());
348
+ await writeModelsCache(modelsCachePath, this.catalog).catch(() => void 0);
349
+ } catch (error) {
350
+ if (signal?.aborted) throw error;
351
+ this.catalog = await readModelsCache(modelsCachePath).catch(() => this.catalog);
352
+ }
353
+ return this.catalog;
354
+ }
355
+ async listModels(provider) {
356
+ return (await this.loadCatalog()).map((model) => ({
357
+ provider,
358
+ id: model.id,
359
+ name: `${model.name} (CC)`,
360
+ inputModalities: ["text"]
361
+ }));
362
+ }
363
+ async resolveModel(provider, model, signal) {
364
+ const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
365
+ const efforts = KNOWN_EFFORTS[model];
366
+ return {
367
+ provider,
368
+ id: model,
369
+ name: entry ? `${entry.name} (CC)` : model,
370
+ inputModalities: ["text"],
371
+ ...entry ? {
372
+ context: { contextWindow: entry.contextWindow },
373
+ defaultMaxTokens: Math.min(entry.maxTokens, DEFAULT_GENERATE_MAX_TOKENS)
374
+ } : {},
375
+ ...efforts ? { reasoning: { efforts: efforts.map((effort) => ({
376
+ id: ReasoningEffortId(effort),
377
+ name: effort
378
+ })) } } : {}
379
+ };
380
+ }
381
+ async *stream(options) {
382
+ if (options.stop?.length) throw new LlmError("Command Code adapter does not support stop sequences", "UNSUPPORTED_OPTION");
383
+ if (options.messages.some(hasImageContent)) throw new LlmError("Image input is not wired to the attachment service in this adapter yet", "UNSUPPORTED_CONTENT");
384
+ const connection = this.deps.options();
385
+ const apiKey = await this.deps.resolveApiKey(connection);
386
+ const modelMax = this.catalog.find((m) => m.id === options.model)?.maxTokens ?? 65536;
387
+ const maxTokens = Math.min(options.maxTokens ?? modelMax, modelMax, DEFAULT_GENERATE_MAX_TOKENS);
388
+ const effort = options.reasoningEffort;
389
+ const supported = KNOWN_EFFORTS[options.model];
390
+ const reasoningEffort = effort && effort !== "off" && supported?.includes(effort) ? effort : void 0;
391
+ const systemText = [options.system ?? "", ...options.messages.filter((m) => m.role === "system").map((m) => m.content.map(blockText).filter(Boolean).join("\n"))].filter(Boolean).join("\n\n");
392
+ const body = {
393
+ config: {
394
+ workingDir: connection.workingDir,
395
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
396
+ environment: `${process.platform}-${process.arch}, Node.js ${process.version}`,
397
+ structure: [],
398
+ isGitRepo: false,
399
+ currentBranch: "",
400
+ mainBranch: "",
401
+ gitStatus: "",
402
+ recentCommits: []
403
+ },
404
+ memory: null,
405
+ taste: null,
406
+ skills: null,
407
+ params: {
408
+ model: options.model,
409
+ messages: messagesToCC(options.messages),
410
+ tools: (options.tools ?? []).map((tool) => ({
411
+ type: "function",
412
+ name: tool.name,
413
+ description: tool.description,
414
+ input_schema: tool.parameters
415
+ })),
416
+ system: systemText,
417
+ max_tokens: maxTokens,
418
+ temperature: options.temperature ?? .3,
419
+ stream: true,
420
+ ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
421
+ },
422
+ threadId: randomUUID()
423
+ };
424
+ const response = await fetch(`${connection.apiBase}/alpha/generate`, {
425
+ method: "POST",
426
+ headers: {
427
+ "Content-Type": "application/json",
428
+ Authorization: `Bearer ${apiKey}`,
429
+ "x-command-code-version": COMMAND_CODE_CLI_VERSION,
430
+ "x-cli-environment": "production",
431
+ "x-project-slug": projectSlugFromPath(connection.workingDir),
432
+ "x-taste-learning": "true",
433
+ "x-co-flag": "false",
434
+ ...attributionHeaders()
435
+ },
436
+ body: JSON.stringify(body),
437
+ ...options.signal ? { signal: options.signal } : {}
438
+ });
439
+ if (!response.ok) {
440
+ const errText = await response.text().catch(() => "");
441
+ throw new LlmError(`Command Code API error ${response.status}: ${errText.slice(0, 500)}`, response.status === 429 ? "RATE_LIMIT" : "PROVIDER_HTTP_ERROR", { status: response.status });
442
+ }
443
+ if (!response.body) throw new LlmError("Command Code API returned no response body", "PROVIDER_PROTOCOL_ERROR");
444
+ const reader = response.body.getReader();
445
+ const decoder = new TextDecoder();
446
+ let buffer = "";
447
+ let nextIndex = 0;
448
+ let textIndex = -1;
449
+ let textContent = "";
450
+ let reasoningIndex = -1;
451
+ let reasoningContent = "";
452
+ let sawContent = false;
453
+ const closeText = function* () {
454
+ if (textIndex < 0) return;
455
+ yield {
456
+ type: "block-end",
457
+ index: textIndex,
458
+ block: {
459
+ type: "text",
460
+ text: textContent
461
+ }
462
+ };
463
+ textIndex = -1;
464
+ textContent = "";
465
+ };
466
+ const closeReasoning = function* () {
467
+ if (reasoningIndex < 0) return;
468
+ yield {
469
+ type: "block-end",
470
+ index: reasoningIndex,
471
+ block: {
472
+ type: "reasoning",
473
+ text: reasoningContent
474
+ }
475
+ };
476
+ reasoningIndex = -1;
477
+ reasoningContent = "";
478
+ };
479
+ const handleEvent = (event) => {
480
+ const chunks = [];
481
+ if (!isRecord(event)) return chunks;
482
+ switch (event.type) {
483
+ case "text-delta": {
484
+ chunks.push(...closeReasoning());
485
+ if (textIndex < 0) {
486
+ textIndex = nextIndex++;
487
+ chunks.push({
488
+ type: "block-start",
489
+ index: textIndex,
490
+ blockType: "text"
491
+ });
492
+ }
493
+ const delta = stringValue(event.text) ?? "";
494
+ textContent += delta;
495
+ sawContent = true;
496
+ chunks.push({
497
+ type: "text-delta",
498
+ index: textIndex,
499
+ text: delta
500
+ });
501
+ break;
502
+ }
503
+ case "reasoning-delta": {
504
+ chunks.push(...closeText());
505
+ if (reasoningIndex < 0) {
506
+ reasoningIndex = nextIndex++;
507
+ chunks.push({
508
+ type: "block-start",
509
+ index: reasoningIndex,
510
+ blockType: "reasoning"
511
+ });
512
+ }
513
+ const delta = stringValue(event.text) ?? "";
514
+ reasoningContent += delta;
515
+ chunks.push({
516
+ type: "reasoning-delta",
517
+ index: reasoningIndex,
518
+ text: delta
519
+ });
520
+ break;
521
+ }
522
+ case "reasoning-start":
523
+ chunks.push(...closeText());
524
+ break;
525
+ case "reasoning-end":
526
+ chunks.push(...closeReasoning());
527
+ break;
528
+ case "tool-call": {
529
+ chunks.push(...closeText(), ...closeReasoning());
530
+ const id = stringValue(event.toolCallId) ?? randomUUID();
531
+ const name = stringValue(event.toolName) ?? "";
532
+ const args = JSON.stringify(recordOrEmpty(event.input ?? event.args ?? event.arguments));
533
+ const index = nextIndex++;
534
+ sawContent = true;
535
+ chunks.push({
536
+ type: "block-start",
537
+ index,
538
+ blockType: "tool-call"
539
+ }, {
540
+ type: "tool-call-delta",
541
+ index,
542
+ id: CallId(id),
543
+ name,
544
+ argumentsDelta: args
545
+ }, {
546
+ type: "block-end",
547
+ index,
548
+ block: {
549
+ type: "tool-call",
550
+ id: CallId(id),
551
+ name,
552
+ arguments: args
553
+ }
554
+ });
555
+ break;
556
+ }
557
+ case "finish": {
558
+ chunks.push(...closeText(), ...closeReasoning());
559
+ const usage = isRecord(event.totalUsage) ? event.totalUsage : void 0;
560
+ if (usage) {
561
+ const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : void 0;
562
+ const totalInput = numberValue(usage.inputTokens) ?? 0;
563
+ const cacheRead = numberValue(details?.cacheReadTokens) ?? 0;
564
+ const cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0;
565
+ const tokenUsage = {
566
+ inputTokens: numberValue(details?.noCacheTokens) ?? Math.max(0, totalInput - cacheRead - cacheWrite),
567
+ outputTokens: numberValue(usage.outputTokens) ?? 0,
568
+ cacheReadTokens: cacheRead,
569
+ cacheWriteTokens: cacheWrite
570
+ };
571
+ chunks.push({
572
+ type: "usage",
573
+ usage: tokenUsage
574
+ });
575
+ }
576
+ chunks.push({
577
+ type: "finish",
578
+ reason: mapFinishReason(event.finishReason)
579
+ });
580
+ break;
581
+ }
582
+ case "error": {
583
+ const detail = isRecord(event.error) ? stringValue(event.error.message) ?? JSON.stringify(event.error) : stringValue(event.error) ?? stringValue(event.message) ?? "Stream error";
584
+ throw new LlmError(`Command Code stream error: ${detail}`, "PROVIDER_STREAM_ERROR");
585
+ }
586
+ }
587
+ return chunks;
588
+ };
589
+ try {
590
+ let finished = false;
591
+ for (;;) {
592
+ const { done, value } = await reader.read();
593
+ if (done) {
594
+ if (buffer.trim()) for (const chunk of handleEvent(parseStreamEventLine(buffer))) yield chunk;
595
+ break;
596
+ }
597
+ buffer += decoder.decode(value, { stream: true });
598
+ const lines = buffer.split("\n");
599
+ buffer = lines.pop() ?? "";
600
+ for (const line of lines) {
601
+ const chunks = handleEvent(parseStreamEventLine(line));
602
+ for (const chunk of chunks) {
603
+ yield chunk;
604
+ if (chunk.type === "finish") finished = true;
605
+ }
606
+ }
607
+ if (finished) break;
608
+ }
609
+ if (!finished) {
610
+ yield* closeText();
611
+ yield* closeReasoning();
612
+ if (!sawContent) throw new LlmError("Command Code returned an empty response", "EMPTY_RESPONSE");
613
+ yield {
614
+ type: "finish",
615
+ reason: { kind: "stop" }
616
+ };
617
+ }
618
+ } finally {
619
+ await reader.cancel().catch(() => void 0);
620
+ reader.releaseLock();
621
+ }
622
+ }
623
+ };
624
+ function mapFinishReason(reason) {
625
+ if (reason === "tool-calls") return { kind: "tool-calls" };
626
+ if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
627
+ return { kind: "stop" };
628
+ }
629
+ //#endregion
630
+ //#region src/index.ts
631
+ /**
632
+ * dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command
633
+ * Code (unofficial; ported from pi-commandcode-provider@0.5.1).
634
+ *
635
+ * Registers the `commandcode` provider route on `ctx.llm` and declares it in
636
+ * the configurable-provider directory, so the web Models page shows a
637
+ * "Command Code" card with an API-key field and the model picker lists the
638
+ * live Command Code model catalog. Connection facts resolve per request over
639
+ * the optional `llm-commandcode` user-settings section and the credential
640
+ * seam, so a changed key, endpoint, or cache path reaches the next request
641
+ * without a restart.
642
+ *
643
+ * ```yaml
644
+ * - id: llm-commandcode
645
+ * name: dsh-commandcode-provider
646
+ * config:
647
+ * apiKeyEnv: COMMANDCODE_API_KEY
648
+ * ```
649
+ *
650
+ * @module dsh-commandcode-provider
651
+ */
652
+ const name = "llm-commandcode";
653
+ const inject = ["llm"];
654
+ const NS = settingsNamespace("llm-commandcode");
655
+ const DEFAULT_API_KEY_ENV = "COMMANDCODE_API_KEY";
656
+ /** The single provider route this plugin owns. */
657
+ const PROVIDER = "commandcode";
658
+ /** Default models cache path (mirrors the pi plugin's on-disk cache). */
659
+ const DEFAULT_MODELS_CACHE_PATH = join(homedir(), ".commandcode", "models-cache.json");
660
+ const Config = z.object({
661
+ apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
662
+ apiKey: z.string(),
663
+ apiBase: z.string(),
664
+ workingDir: z.string(),
665
+ modelsCachePath: z.string()
666
+ });
667
+ /**
668
+ * The one explicit resolve step from raw config to validated connection
669
+ * facts. Programmatic construction may bypass Schemastery normalization, so
670
+ * every default is re-judged here — for the composition entry at load and for
671
+ * each settings snapshot at its first use.
672
+ */
673
+ function resolveAdapterOptions(config) {
674
+ return {
675
+ apiKeyEnv: credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV),
676
+ apiBase: config.apiBase ?? "https://api.commandcode.ai",
677
+ workingDir: config.workingDir ?? process.cwd(),
678
+ modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH
679
+ };
680
+ }
681
+ function apply(ctx, config) {
682
+ let current = () => config;
683
+ let lastRaw;
684
+ let lastGood;
685
+ const options = () => {
686
+ const raw = current();
687
+ if (raw === lastRaw && lastGood !== void 0) return lastGood;
688
+ const next = resolveAdapterOptions(raw);
689
+ lastRaw = raw;
690
+ lastGood = next;
691
+ return next;
692
+ };
693
+ options();
694
+ const resolveApiKey = async (connection) => {
695
+ const literal = current().apiKey;
696
+ if (literal) return assertUsableApiKey(literal, "llm-commandcode", "config.apiKey");
697
+ const ref = connection.apiKeyEnv;
698
+ const credentials = ctx.get("credentials");
699
+ if (credentials !== void 0) {
700
+ const hit = await credentials.resolve(ref);
701
+ if (hit !== void 0) return assertUsableApiKey(hit.value, "llm-commandcode", ref);
702
+ } else {
703
+ const ambient = launchEnvironmentOf(ctx).get(ref);
704
+ if (ambient !== void 0 && ambient.value.length > 0) return assertUsableApiKey(ambient.value, "llm-commandcode", ref);
705
+ }
706
+ const authFileKey = resolveAuthFileApiKey();
707
+ if (authFileKey) return assertUsableApiKey(authFileKey, "llm-commandcode", "~/.commandcode/auth.json");
708
+ throw new LlmError(`llm-commandcode: no API key for provider route "${PROVIDER}"; store ${ref} through the credentials service (the web Models page writes it), export it in the launching environment, set config.apiKey, or run \`command-code login\` to write ~/.commandcode/auth.json`, "MISSING_CREDENTIAL");
709
+ };
710
+ const adapter = new CommandCodeAdapter({
711
+ options,
712
+ resolveApiKey
713
+ });
714
+ ctx.llm.registerConfigurableProviders([{
715
+ provider: PROVIDER,
716
+ displayName: "Command Code",
717
+ settingsNs: NS,
718
+ settingsPath: []
719
+ }]);
720
+ ctx.llm.registerAdapter([PROVIDER], adapter);
721
+ installSettingsSection(ctx, NS, Config, config, {
722
+ setSource: (source) => {
723
+ current = source;
724
+ },
725
+ onChange: () => {}
726
+ });
727
+ }
728
+ //#endregion
729
+ 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, inject, name, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
730
+
731
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/adapter.ts","../src/index.ts"],"sourcesContent":["/**\n * DeepSeek Harness LLM adapter for the Command Code Provider API.\n *\n * Ported from pi-commandcode-provider@0.5.1 (MIT). This is an unofficial,\n * community-maintained integration; you need your own Command Code account\n * and API key or subscription, and Command Code's terms apply.\n *\n * Wire protocol (reverse-engineered by the pi plugin, command-code@1.15.1):\n * POST {apiBase}/alpha/generate\n * body: { config, memory, taste, skills, params: { model, messages, tools,\n * system, max_tokens, temperature, stream, reasoning_effort? }, threadId }\n * SSE-ish JSONL events: text-delta | reasoning-start/delta/end | tool-call\n * | tool-result | finish | error\n * Model catalog: GET {apiBase}/provider/v1/models -> { object: 'list', data: [...] }\n *\n * The adapter is deliberately free of cordis/schemastery: it receives a\n * per-request options thunk and an API-key resolver from the plugin entry\n * (src/index.ts), so a settings change reaches the very next request.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport { randomUUID } from 'node:crypto'\n\nimport {\n attributionHeaders,\n CallId,\n LlmAdapter,\n LlmError,\n ReasoningEffortId,\n type ContentBlock,\n type FinishReason,\n type GenerateOptions,\n type LlmModelInfo,\n type LlmResolvedModelInfo,\n type Message,\n type StreamChunk,\n type TokenUsage,\n} from '@deepseek-ai/dsh-llm'\n\n// ---------------------------------------------------------------------------\n// Static capability snapshot (from pi plugin, command-code@1.15.1 catalog).\n// The Provider API does not expose reasoning metadata; models omitted here\n// let Command Code choose their reasoning depth, matching the official CLI.\n// ---------------------------------------------------------------------------\n\nexport const KNOWN_EFFORTS: Readonly<Record<string, readonly string[]>> = {\n 'Qwen/Qwen3.8-Max': ['low', 'medium', 'xhigh'],\n 'claude-fable-5': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'claude-opus-4-7': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'claude-opus-4-8': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'claude-opus-5': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'claude-sonnet-4-6': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'claude-sonnet-5': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'deepseek/deepseek-v4-flash': ['high', 'max'],\n 'deepseek/deepseek-v4-pro': ['high', 'max'],\n 'gpt-5.3-codex': ['low', 'medium', 'high', 'xhigh'],\n 'gpt-5.4': ['low', 'medium', 'high', 'xhigh'],\n 'gpt-5.4-mini': ['low', 'medium', 'high'],\n 'gpt-5.5': ['low', 'medium', 'high', 'xhigh'],\n 'gpt-5.6-luna': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'gpt-5.6-sol': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'gpt-5.6-terra': ['low', 'medium', 'high', 'xhigh', 'max'],\n 'google/gemini-3.1-flash-lite': ['low', 'medium', 'high'],\n 'google/gemini-3.5-flash': ['low', 'medium', 'high'],\n 'google/gemini-3.5-flash-lite': ['low', 'medium', 'high'],\n 'google/gemini-3.6-flash': ['low', 'medium', 'high'],\n 'sakana/fugu-ultra': ['high', 'xhigh'],\n 'xai/grok-4.5': ['low', 'medium', 'high'],\n 'zai-org/GLM-5.2': ['high', 'max'],\n}\n\nexport const COMMAND_CODE_CLI_VERSION = '1.15.1'\nexport const DEFAULT_API_BASE = 'https://api.commandcode.ai'\nexport const DEFAULT_GENERATE_MAX_TOKENS = 64_000\nexport const DEFAULT_MAX_OUTPUT_TOKENS = 65_536\nexport const MODELS_TIMEOUT_MS = 10_000\nconst MODEL_CACHE_VERSION = 1\n\n// ---------------------------------------------------------------------------\n// Small helpers (ported from converters.ts / models.ts)\n// ---------------------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined\n}\n\nfunction numberValue(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n}\n\nfunction recordOrEmpty(value: unknown): Record<string, unknown> {\n if (isRecord(value)) return value\n if (typeof value === 'string') {\n try {\n const parsed: unknown = JSON.parse(value)\n if (isRecord(parsed)) return parsed\n } catch {\n // Some providers stream incomplete JSON argument fragments.\n }\n }\n return {}\n}\n\nexport function projectSlugFromPath(pathName: string): string {\n const slug = pathName\n .toLowerCase()\n .replace(/^[a-z]:/i, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n return slug || 'project'\n}\n\nfunction parseStreamEventLine(line: string): unknown | undefined {\n let trimmed = line.trim()\n if (!trimmed || trimmed.startsWith(':') || trimmed.startsWith('event:')) return undefined\n if (trimmed.startsWith('data:')) trimmed = trimmed.slice(5).trim()\n if (!trimmed || trimmed === '[DONE]') return undefined\n try {\n return JSON.parse(trimmed) as unknown\n } catch {\n return undefined\n }\n}\n\n// ---------------------------------------------------------------------------\n// Credential fallback from the official Command Code CLI auth file. Used as\n// the last fallback by the plugin entry, so a user who already logged in with\n// `command-code login` can reuse that credential. Only the official CLI's own\n// file is read — pi/OMP auth files are intentionally not scanned, so their\n// credentials and formats cannot surprise this adapter.\n// ---------------------------------------------------------------------------\n\n/** Extract the key from the CLI's nested credential records (`command-code`). */\nfunction apiKeyFromCredentialRecord(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined\n const type = stringValue(value.type)\n if (type === 'api') return stringValue(value.key)\n if (type === 'oauth') return stringValue(value.access)\n return stringValue(value.key) ?? stringValue(value.access)\n}\n\n/** Read a usable Command Code credential from the official CLI auth file. */\nexport function resolveAuthFileApiKey(): string | undefined {\n const authPath = join(homedir(), '.commandcode', 'auth.json')\n try {\n if (!existsSync(authPath)) return undefined\n const parsed: unknown = JSON.parse(readFileSync(authPath, 'utf-8'))\n if (!isRecord(parsed)) return undefined\n const direct = stringValue(parsed.apiKey) ?? stringValue(parsed.commandcode)\n if (direct) return direct\n const nested =\n apiKeyFromCredentialRecord(parsed.commandcode) ??\n apiKeyFromCredentialRecord(parsed['command-code'])\n return nested\n } catch {\n // Ignore malformed or unreadable auth file.\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// Model catalog discovery with on-disk cache fallback (ported from models.ts)\n// ---------------------------------------------------------------------------\n\ninterface CommandCodeModel {\n id: string\n name: string\n contextWindow: number\n maxTokens: number\n}\n\nfunction parseCatalogResponse(value: unknown): CommandCodeModel[] {\n if (!isRecord(value) || value.object !== 'list' || !Array.isArray(value.data)) {\n throw new LlmError('Unexpected Command Code models response shape', 'PROVIDER_PROTOCOL_ERROR')\n }\n const models: CommandCodeModel[] = []\n for (const entry of value.data) {\n if (!isRecord(entry)) continue\n const id = stringValue(entry.id)\n const name = stringValue(entry.name)\n const contextLength = numberValue(entry.context_length)\n if (!id || !name || !contextLength || contextLength <= 0) continue\n models.push({\n id,\n name,\n contextWindow: contextLength,\n maxTokens: Math.min(contextLength, DEFAULT_MAX_OUTPUT_TOKENS),\n })\n }\n if (models.length === 0) {\n throw new LlmError('Command Code returned an empty model catalog', 'PROVIDER_PROTOCOL_ERROR')\n }\n return models\n}\n\nasync function readModelsCache(cachePath: string): Promise<CommandCodeModel[]> {\n const parsed: unknown = JSON.parse(await readFile(cachePath, 'utf-8'))\n if (!isRecord(parsed) || parsed.version !== MODEL_CACHE_VERSION || !Array.isArray(parsed.models)) {\n throw new Error(`Invalid model cache at ${cachePath}`)\n }\n return parsed.models as CommandCodeModel[]\n}\n\nasync function writeModelsCache(cachePath: string, models: CommandCodeModel[]): Promise<void> {\n await mkdir(dirname(cachePath), { recursive: true })\n const tmp = `${cachePath}.${process.pid}.tmp`\n try {\n await writeFile(tmp, `${JSON.stringify({ version: MODEL_CACHE_VERSION, models }, null, 2)}\\n`, {\n encoding: 'utf-8',\n mode: 0o600,\n })\n await rename(tmp, cachePath)\n } finally {\n await rm(tmp, { force: true }).catch(() => undefined)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Message conversion: harness Message[] -> Command Code wire messages.\n// Reasoning blocks are intentionally NOT replayed (matches the pi plugin and\n// the official CLI: prior private reasoning must not leak into later turns).\n// Only tool calls with a paired tool result are replayed.\n// ---------------------------------------------------------------------------\n\nfunction pairedToolCallIds(messages: readonly Message[]): Set<string> {\n const callIds = new Set<string>()\n const resultIds = new Set<string>()\n for (const message of messages) {\n for (const block of message.content) {\n if (message.role === 'assistant' && block.type === 'tool-call') callIds.add(block.id)\n if (block.type === 'tool-result') resultIds.add(block.toolCallId)\n }\n }\n return new Set([...callIds].filter((id) => resultIds.has(id)))\n}\n\nfunction blockText(block: ContentBlock): string {\n return block.type === 'text' || block.type === 'reasoning' ? block.text : ''\n}\n\nfunction toolResultText(block: Extract<ContentBlock, { type: 'tool-result' }>): string {\n return block.content.map(blockText).filter(Boolean).join('\\n')\n}\n\nfunction hasImageContent(message: Message): boolean {\n const check = (blocks: readonly ContentBlock[]): boolean =>\n blocks.some(\n (b) => b.type === 'image' || (b.type === 'tool-result' && check(b.content)),\n )\n return check(message.content)\n}\n\nfunction messagesToCC(messages: readonly Message[]): unknown[] {\n const out: unknown[] = []\n const paired = pairedToolCallIds(messages)\n\n for (const message of messages) {\n if (message.role === 'system') continue // folded into params.system by the caller\n\n if (message.role === 'user' && message.source.kind !== 'tool') {\n const parts: unknown[] = []\n for (const block of message.content) {\n if (block.type === 'text') parts.push({ type: 'text', text: block.text })\n if (block.type === 'image') {\n // ImageBlock carries an attachment ref owned by the attachment\n // service; resolving it to bytes requires that service. Fail loudly\n // instead of silently dropping (adapter contract).\n throw new LlmError(\n 'Image input is not wired to the attachment service in this adapter yet',\n 'UNSUPPORTED_CONTENT',\n )\n }\n }\n out.push({ role: 'user', content: parts })\n continue\n }\n\n if (message.role === 'assistant') {\n const parts: unknown[] = []\n for (const block of message.content) {\n if (block.type === 'text') {\n parts.push({ type: 'text', text: block.text })\n } else if (block.type === 'tool-call' && paired.has(block.id)) {\n parts.push({\n type: 'tool-call',\n toolCallId: block.id,\n toolName: block.name,\n input: recordOrEmpty(block.arguments),\n })\n }\n // reasoning blocks: skipped by design (see header comment)\n }\n if (parts.length > 0) out.push({ role: 'assistant', content: parts })\n continue\n }\n\n // tool-result message (user role, single tool-result block)\n if (message.role === 'user' && message.source.kind === 'tool') {\n const block = message.content[0]\n if (!block || block.type !== 'tool-result' || !paired.has(block.toolCallId)) continue\n out.push({\n role: 'tool',\n content: [\n {\n type: 'tool-result',\n toolCallId: block.toolCallId,\n toolName: '',\n output: block.isError\n ? { type: 'error-text', value: toolResultText(block) }\n : { type: 'text', value: toolResultText(block) },\n },\n ],\n })\n }\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\n/** Connection facts resolved fresh per request by the plugin entry. */\nexport interface CommandCodeConnectionOptions {\n /** API base; the Provider API lives under it (`/alpha/generate`, `/provider/v1/models`). */\n apiBase: string\n /** Working directory reported to the API (project slug, config block). */\n workingDir: string\n /** Model catalog cache path. */\n modelsCachePath: string\n}\n\n/** Everything the adapter needs beyond the request itself. */\nexport interface CommandCodeAdapterDeps<C extends CommandCodeConnectionOptions = CommandCodeConnectionOptions> {\n /** Resolve the current connection facts (fresh per request, settings-aware). */\n options: () => C\n /** Resolve a usable API key for the given connection facts, or throw `MISSING_CREDENTIAL`. */\n resolveApiKey: (connection: C) => Promise<string>\n}\n\nexport class CommandCodeAdapter<C extends CommandCodeConnectionOptions = CommandCodeConnectionOptions> extends LlmAdapter {\n private catalog: CommandCodeModel[] = []\n\n constructor(private readonly deps: CommandCodeAdapterDeps<C>) {\n super()\n }\n\n /** Refresh the catalog (live fetch, cache fallback) and return it. */\n private async loadCatalog(signal?: AbortSignal): Promise<CommandCodeModel[]> {\n const { apiBase, modelsCachePath } = this.deps.options()\n try {\n const response = await fetch(`${apiBase}/provider/v1/models`, {\n headers: { accept: 'application/json', ...attributionHeaders() },\n signal: signal ?? AbortSignal.timeout(MODELS_TIMEOUT_MS),\n })\n if (!response.ok) {\n throw new Error(`models endpoint returned ${response.status}`)\n }\n this.catalog = parseCatalogResponse(await response.json())\n await writeModelsCache(modelsCachePath, this.catalog).catch(() => undefined)\n } catch (error) {\n if (signal?.aborted) throw error\n // Fall back to the last successful catalog on disk.\n this.catalog = await readModelsCache(modelsCachePath).catch(() => this.catalog)\n }\n return this.catalog\n }\n\n override async listModels(provider: string): Promise<readonly LlmModelInfo[]> {\n const catalog = await this.loadCatalog()\n return catalog.map((model) => ({\n provider,\n id: model.id,\n name: `${model.name} (CC)`,\n inputModalities: ['text' as const],\n }))\n }\n\n override async resolveModel(\n provider: string,\n model: string,\n signal?: AbortSignal,\n ): Promise<LlmResolvedModelInfo> {\n const entry =\n this.catalog.find((m) => m.id === model) ??\n (await this.loadCatalog(signal)).find((m) => m.id === model)\n\n const efforts = KNOWN_EFFORTS[model]\n return {\n provider,\n id: model,\n name: entry ? `${entry.name} (CC)` : model,\n inputModalities: ['text' as const],\n ...(entry\n ? {\n context: { contextWindow: entry.contextWindow },\n defaultMaxTokens: Math.min(entry.maxTokens, DEFAULT_GENERATE_MAX_TOKENS),\n }\n : {}),\n // Omit `reasoning` entirely for models without known effort support:\n // the harness then treats the model as having no selectable efforts.\n ...(efforts\n ? {\n reasoning: {\n efforts: efforts.map((effort) => ({\n id: ReasoningEffortId(effort),\n name: effort,\n })),\n },\n }\n : {}),\n }\n }\n\n async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {\n if (options.stop?.length) {\n // The Command Code wire format has no documented stop field; refuse\n // loudly instead of silently dropping a request field.\n throw new LlmError('Command Code adapter does not support stop sequences', 'UNSUPPORTED_OPTION')\n }\n if (options.messages.some(hasImageContent)) {\n throw new LlmError(\n 'Image input is not wired to the attachment service in this adapter yet',\n 'UNSUPPORTED_CONTENT',\n )\n }\n\n const connection = this.deps.options()\n const apiKey = await this.deps.resolveApiKey(connection)\n const entry = this.catalog.find((m) => m.id === options.model)\n const modelMax = entry?.maxTokens ?? DEFAULT_MAX_OUTPUT_TOKENS\n const maxTokens = Math.min(\n options.maxTokens ?? modelMax,\n modelMax,\n DEFAULT_GENERATE_MAX_TOKENS,\n )\n\n const effort = options.reasoningEffort as string | undefined\n const supported = KNOWN_EFFORTS[options.model]\n const reasoningEffort =\n effort && effort !== 'off' && supported?.includes(effort) ? effort : undefined\n\n const systemText = [\n options.system ?? '',\n ...options.messages\n .filter((m) => m.role === 'system')\n .map((m) => m.content.map(blockText).filter(Boolean).join('\\n')),\n ]\n .filter(Boolean)\n .join('\\n\\n')\n\n const body = {\n config: {\n workingDir: connection.workingDir,\n date: new Date().toISOString().split('T')[0],\n environment: `${process.platform}-${process.arch}, Node.js ${process.version}`,\n structure: [],\n isGitRepo: false,\n currentBranch: '',\n mainBranch: '',\n gitStatus: '',\n recentCommits: [],\n },\n memory: null,\n taste: null,\n skills: null,\n params: {\n model: options.model,\n messages: messagesToCC(options.messages),\n tools: (options.tools ?? []).map((tool) => ({\n type: 'function',\n name: tool.name,\n description: tool.description,\n input_schema: tool.parameters,\n })),\n system: systemText,\n max_tokens: maxTokens,\n temperature: options.temperature ?? 0.3,\n stream: true,\n ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),\n },\n threadId: randomUUID(),\n }\n\n const response = await fetch(`${connection.apiBase}/alpha/generate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n 'x-command-code-version': COMMAND_CODE_CLI_VERSION,\n 'x-cli-environment': 'production',\n 'x-project-slug': projectSlugFromPath(connection.workingDir),\n 'x-taste-learning': 'true',\n 'x-co-flag': 'false',\n ...attributionHeaders(),\n },\n body: JSON.stringify(body),\n ...(options.signal ? { signal: options.signal } : {}),\n })\n\n if (!response.ok) {\n const errText = await response.text().catch(() => '')\n throw new LlmError(\n `Command Code API error ${response.status}: ${errText.slice(0, 500)}`,\n response.status === 429 ? 'RATE_LIMIT' : 'PROVIDER_HTTP_ERROR',\n { status: response.status },\n )\n }\n if (!response.body) {\n throw new LlmError('Command Code API returned no response body', 'PROVIDER_PROTOCOL_ERROR')\n }\n\n // --- SSE/JSONL event stream -> harness StreamChunk protocol ---\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n // Block assembly state: at most one text block and one reasoning block\n // are open at a time (same assumption as the pi plugin).\n let nextIndex = 0\n let textIndex = -1\n let textContent = ''\n let reasoningIndex = -1\n let reasoningContent = ''\n let sawContent = false\n\n const closeText = function* (): Generator<StreamChunk> {\n if (textIndex < 0) return\n yield {\n type: 'block-end',\n index: textIndex,\n block: { type: 'text', text: textContent },\n }\n textIndex = -1\n textContent = ''\n }\n const closeReasoning = function* (): Generator<StreamChunk> {\n if (reasoningIndex < 0) return\n yield {\n type: 'block-end',\n index: reasoningIndex,\n block: { type: 'reasoning', text: reasoningContent },\n }\n reasoningIndex = -1\n reasoningContent = ''\n }\n\n const handleEvent = (event: unknown): StreamChunk[] => {\n const chunks: StreamChunk[] = []\n if (!isRecord(event)) return chunks\n\n switch (event.type) {\n case 'text-delta': {\n chunks.push(...closeReasoning())\n if (textIndex < 0) {\n textIndex = nextIndex++\n chunks.push({ type: 'block-start', index: textIndex, blockType: 'text' })\n }\n const delta = stringValue(event.text) ?? ''\n textContent += delta\n sawContent = true\n chunks.push({ type: 'text-delta', index: textIndex, text: delta })\n break\n }\n case 'reasoning-delta': {\n chunks.push(...closeText())\n if (reasoningIndex < 0) {\n reasoningIndex = nextIndex++\n chunks.push({ type: 'block-start', index: reasoningIndex, blockType: 'reasoning' })\n }\n const delta = stringValue(event.text) ?? ''\n reasoningContent += delta\n chunks.push({ type: 'reasoning-delta', index: reasoningIndex, text: delta })\n break\n }\n case 'reasoning-start':\n chunks.push(...closeText())\n break\n case 'reasoning-end':\n chunks.push(...closeReasoning())\n break\n case 'tool-call': {\n chunks.push(...closeText(), ...closeReasoning())\n const id = stringValue(event.toolCallId) ?? randomUUID()\n const name = stringValue(event.toolName) ?? ''\n const args = JSON.stringify(recordOrEmpty(event.input ?? event.args ?? event.arguments))\n const index = nextIndex++\n sawContent = true\n chunks.push(\n { type: 'block-start', index, blockType: 'tool-call' },\n { type: 'tool-call-delta', index, id: CallId(id), name, argumentsDelta: args },\n {\n type: 'block-end',\n index,\n block: { type: 'tool-call', id: CallId(id), name, arguments: args },\n },\n )\n break\n }\n case 'finish': {\n chunks.push(...closeText(), ...closeReasoning())\n const usage = isRecord(event.totalUsage) ? event.totalUsage : undefined\n if (usage) {\n const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : undefined\n const totalInput = numberValue(usage.inputTokens) ?? 0\n const cacheRead = numberValue(details?.cacheReadTokens) ?? 0\n const cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0\n // Harness TokenUsage counts are disjoint: uncached input only.\n const tokenUsage: TokenUsage = {\n inputTokens:\n numberValue(details?.noCacheTokens) ?? Math.max(0, totalInput - cacheRead - cacheWrite),\n outputTokens: numberValue(usage.outputTokens) ?? 0,\n cacheReadTokens: cacheRead,\n cacheWriteTokens: cacheWrite,\n }\n chunks.push({ type: 'usage', usage: tokenUsage })\n }\n chunks.push({ type: 'finish', reason: mapFinishReason(event.finishReason) })\n break\n }\n case 'error': {\n const detail = isRecord(event.error)\n ? (stringValue(event.error.message) ?? JSON.stringify(event.error))\n : (stringValue(event.error) ?? stringValue(event.message) ?? 'Stream error')\n throw new LlmError(`Command Code stream error: ${detail}`, 'PROVIDER_STREAM_ERROR')\n }\n }\n return chunks\n }\n\n try {\n let finished = false\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n if (buffer.trim()) for (const chunk of handleEvent(parseStreamEventLine(buffer))) yield chunk\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const chunks = handleEvent(parseStreamEventLine(line))\n for (const chunk of chunks) {\n yield chunk\n if (chunk.type === 'finish') finished = true\n }\n }\n if (finished) break\n }\n if (!finished) {\n // Stream ended without a finish event: close open blocks and\n // terminate according to the adapter contract (usage, then finish).\n yield* closeText()\n yield* closeReasoning()\n if (!sawContent) {\n throw new LlmError('Command Code returned an empty response', 'EMPTY_RESPONSE')\n }\n yield { type: 'finish', reason: { kind: 'stop' } }\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n }\n}\n\nfunction mapFinishReason(reason: unknown): FinishReason {\n if (reason === 'tool-calls') return { kind: 'tool-calls' }\n if (\n reason === 'length' ||\n reason === 'max_tokens' ||\n reason === 'max-tokens' ||\n reason === 'max_output_tokens'\n ) {\n return { kind: 'max-tokens' }\n }\n return { kind: 'stop' }\n}\n","/**\n * dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command\n * Code (unofficial; ported from pi-commandcode-provider@0.5.1).\n *\n * Registers the `commandcode` provider route on `ctx.llm` and declares it in\n * the configurable-provider directory, so the web Models page shows a\n * \"Command Code\" card with an API-key field and the model picker lists the\n * live Command Code model catalog. Connection facts resolve per request over\n * the optional `llm-commandcode` user-settings section and the credential\n * seam, so a changed key, endpoint, or cache path reaches the next request\n * without a restart.\n *\n * ```yaml\n * - id: llm-commandcode\n * name: dsh-commandcode-provider\n * config:\n * apiKeyEnv: COMMANDCODE_API_KEY\n * ```\n *\n * @module dsh-commandcode-provider\n */\n\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport z from '@deepseek-ai/schemastery'\nimport { assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm'\nimport { credentialRef, type CredentialRef } from '@deepseek-ai/dsh-credentials'\nimport { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'\nimport { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'\nimport { CommandCodeAdapter, DEFAULT_API_BASE, resolveAuthFileApiKey } from './adapter.ts'\nimport type { CommandCodeConnectionOptions } from './adapter.ts'\n\nexport {\n COMMAND_CODE_CLI_VERSION,\n DEFAULT_API_BASE,\n DEFAULT_GENERATE_MAX_TOKENS,\n DEFAULT_MAX_OUTPUT_TOKENS,\n CommandCodeAdapter,\n KNOWN_EFFORTS,\n projectSlugFromPath,\n resolveAuthFileApiKey,\n} from './adapter.ts'\nexport type { CommandCodeAdapterDeps, CommandCodeConnectionOptions } from './adapter.ts'\n\nexport const name = 'llm-commandcode'\nexport const inject = ['llm']\n\nconst NS = settingsNamespace('llm-commandcode')\nconst DEFAULT_API_KEY_ENV = 'COMMANDCODE_API_KEY'\n\n/** The single provider route this plugin owns. */\nexport const PROVIDER = 'commandcode'\n/** Default models cache path (mirrors the pi plugin's on-disk cache). */\nexport const DEFAULT_MODELS_CACHE_PATH = join(homedir(), '.commandcode', 'models-cache.json')\n\n/**\n * Plugin config, validated by the same-named schemastery schema and doubling\n * as the `llm-commandcode` settings-section shape. Every field is optional:\n * a missing API key resolves through {@link Config.apiKeyEnv} at each request\n * (the web Models page writes it), with the official Command Code CLI auth\n * file (`~/.commandcode/auth.json`) as the last fallback.\n */\nexport interface Config {\n /** Credential reference (environment-variable name) resolved per request; defaults to `COMMANDCODE_API_KEY`. */\n apiKeyEnv?: string\n /** Literal API key override (composition config only); takes precedence over `apiKeyEnv`. */\n apiKey?: string\n /** API base; defaults to the public Command Code Provider API. */\n apiBase?: string\n /** Working directory reported to the API; defaults to the process cwd. */\n workingDir?: string\n /** Model catalog cache path; defaults to `~/.commandcode/models-cache.json`. */\n modelsCachePath?: string\n}\n\nexport const Config: z<Config> = z.object({\n apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV),\n apiKey: z.string(),\n apiBase: z.string(),\n workingDir: z.string(),\n modelsCachePath: z.string(),\n})\n\n/** One resolution's complete request facts: connection plus credential reference. */\nexport interface ResolvedCommandCodeOptions extends CommandCodeConnectionOptions {\n apiKeyEnv: CredentialRef\n}\n\n/**\n * The one explicit resolve step from raw config to validated connection\n * facts. Programmatic construction may bypass Schemastery normalization, so\n * every default is re-judged here — for the composition entry at load and for\n * each settings snapshot at its first use.\n */\nexport function resolveAdapterOptions(config: Config): ResolvedCommandCodeOptions {\n return {\n apiKeyEnv: credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV),\n apiBase: config.apiBase ?? DEFAULT_API_BASE,\n workingDir: config.workingDir ?? process.cwd(),\n modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH,\n }\n}\n\nexport function apply(ctx: Context, config: Config): void {\n let current: () => Config = () => config\n let lastRaw: Config | undefined\n let lastGood: ResolvedCommandCodeOptions | undefined\n const options = (): ResolvedCommandCodeOptions => {\n const raw = current()\n if (raw === lastRaw && lastGood !== undefined) return lastGood\n const next = resolveAdapterOptions(raw)\n lastRaw = raw\n lastGood = next\n return next\n }\n options()\n\n const resolveApiKey = async (connection: ResolvedCommandCodeOptions): Promise<string> => {\n // 1. A literal key in composition config wins outright.\n const literal = current().apiKey\n if (literal) return assertUsableApiKey(literal, 'llm-commandcode', 'config.apiKey')\n // 2. The credential seam (web Models page) or the trusted environment.\n const ref = connection.apiKeyEnv\n const credentials = ctx.get('credentials')\n if (credentials !== undefined) {\n const hit = await credentials.resolve(ref)\n if (hit !== undefined) return assertUsableApiKey(hit.value, 'llm-commandcode', ref)\n } else {\n const ambient = launchEnvironmentOf(ctx).get(ref)\n if (ambient !== undefined && ambient.value.length > 0) {\n return assertUsableApiKey(ambient.value, 'llm-commandcode', ref)\n }\n }\n // 3. Last resort: reuse the official Command Code CLI login (~/.commandcode/auth.json).\n const authFileKey = resolveAuthFileApiKey()\n if (authFileKey) return assertUsableApiKey(authFileKey, 'llm-commandcode', '~/.commandcode/auth.json')\n throw new LlmError(\n `llm-commandcode: no API key for provider route \"${PROVIDER}\"; store ${ref} through the`\n + ' credentials service (the web Models page writes it), export it in the launching'\n + ' environment, set config.apiKey, or run `command-code login` to write'\n + ' ~/.commandcode/auth.json',\n 'MISSING_CREDENTIAL',\n )\n }\n\n const adapter = new CommandCodeAdapter({ options, resolveApiKey })\n // The Models page card: a configurable provider with a settings address.\n // settingsPath [] means the whole `llm-commandcode` section configures it.\n ctx.llm.registerConfigurableProviders([\n { provider: PROVIDER, displayName: 'Command Code', settingsNs: NS, settingsPath: [] },\n ])\n // The live route: this is what makes models requestable under `commandcode`.\n ctx.llm.registerAdapter([PROVIDER], adapter)\n\n installSettingsSection(ctx, NS, Config, config, {\n setSource: (source) => {\n current = source\n },\n // Everything the adapter reads is resolved per request, so a settings\n // change needs no registration-level action.\n onChange: () => {},\n })\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@mars-sea/dsh-commandcode-provider",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial DeepSeek Harness LLM provider plugin for Command Code, ported from pi-commandcode-provider (MIT). Registers the 'commandcode' provider route with a Models-page card and live model catalog.",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./src/*": "./src/*",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "lib",
18
+ "cordis.patch.yml",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsdown",
25
+ "prepare": "tsdown",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dsh": {
29
+ "bundle": {
30
+ "patch": "./cordis.patch.yml"
31
+ }
32
+ },
33
+ "engines": {
34
+ "node": ">=22"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/Mars-Sea/dsh-commandcode-provider/issues"
45
+ },
46
+ "homepage": "https://github.com/Mars-Sea/dsh-commandcode-provider#readme",
47
+ "keywords": [
48
+ "deepseek-harness",
49
+ "dsh",
50
+ "commandcode",
51
+ "command-code",
52
+ "llm",
53
+ "adapter",
54
+ "provider",
55
+ "plugin",
56
+ "bundle"
57
+ ],
58
+ "license": "MIT",
59
+ "peerDependencies": {
60
+ "@deepseek-ai/cordis": "^4.0.1",
61
+ "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
62
+ "@deepseek-ai/dsh-launch-environment": "^0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
65
+ "@deepseek-ai/schemastery": "^3.18.1"
66
+ },
67
+ "devDependencies": {
68
+ "@deepseek-ai/cordis": "^4.0.1",
69
+ "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-launch-environment": "^0.1.0-rc.6",
71
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
72
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
73
+ "@deepseek-ai/schemastery": "^3.18.1",
74
+ "@types/node": "^22.0.0",
75
+ "tsdown": "^0.22.2",
76
+ "typescript": "^6.0.3"
77
+ }
78
+ }