@warlock.js/ai-cli 0.0.0-stage → 5.31.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 +31 -0
- package/LICENSE +21 -0
- package/README.md +69 -2
- package/cjs/index.cjs +4576 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/adapters/aider.d.mts +41 -0
- package/esm/adapters/aider.d.mts.map +1 -0
- package/esm/adapters/aider.mjs +294 -0
- package/esm/adapters/aider.mjs.map +1 -0
- package/esm/adapters/amp.d.mts +33 -0
- package/esm/adapters/amp.d.mts.map +1 -0
- package/esm/adapters/amp.mjs +119 -0
- package/esm/adapters/amp.mjs.map +1 -0
- package/esm/adapters/antigravity.d.mts +37 -0
- package/esm/adapters/antigravity.d.mts.map +1 -0
- package/esm/adapters/antigravity.mjs +235 -0
- package/esm/adapters/antigravity.mjs.map +1 -0
- package/esm/adapters/claude-code.d.mts +30 -0
- package/esm/adapters/claude-code.d.mts.map +1 -0
- package/esm/adapters/claude-code.mjs +121 -0
- package/esm/adapters/claude-code.mjs.map +1 -0
- package/esm/adapters/claude-stream.d.mts +32 -0
- package/esm/adapters/claude-stream.d.mts.map +1 -0
- package/esm/adapters/claude-stream.mjs +140 -0
- package/esm/adapters/claude-stream.mjs.map +1 -0
- package/esm/adapters/codex.d.mts +37 -0
- package/esm/adapters/codex.d.mts.map +1 -0
- package/esm/adapters/codex.mjs +320 -0
- package/esm/adapters/codex.mjs.map +1 -0
- package/esm/adapters/copilot.d.mts +35 -0
- package/esm/adapters/copilot.d.mts.map +1 -0
- package/esm/adapters/copilot.mjs +244 -0
- package/esm/adapters/copilot.mjs.map +1 -0
- package/esm/adapters/cursor.d.mts +35 -0
- package/esm/adapters/cursor.d.mts.map +1 -0
- package/esm/adapters/cursor.mjs +203 -0
- package/esm/adapters/cursor.mjs.map +1 -0
- package/esm/adapters/droid.d.mts +32 -0
- package/esm/adapters/droid.d.mts.map +1 -0
- package/esm/adapters/droid.mjs +183 -0
- package/esm/adapters/droid.mjs.map +1 -0
- package/esm/adapters/grok.d.mts +32 -0
- package/esm/adapters/grok.d.mts.map +1 -0
- package/esm/adapters/grok.mjs +199 -0
- package/esm/adapters/grok.mjs.map +1 -0
- package/esm/adapters/index.d.mts +16 -0
- package/esm/adapters/index.mjs +18 -0
- package/esm/adapters/kimi.d.mts +34 -0
- package/esm/adapters/kimi.d.mts.map +1 -0
- package/esm/adapters/kimi.mjs +162 -0
- package/esm/adapters/kimi.mjs.map +1 -0
- package/esm/adapters/mistral-vibe.d.mts +32 -0
- package/esm/adapters/mistral-vibe.d.mts.map +1 -0
- package/esm/adapters/mistral-vibe.mjs +252 -0
- package/esm/adapters/mistral-vibe.mjs.map +1 -0
- package/esm/adapters/opencode.d.mts +36 -0
- package/esm/adapters/opencode.d.mts.map +1 -0
- package/esm/adapters/opencode.mjs +219 -0
- package/esm/adapters/opencode.mjs.map +1 -0
- package/esm/adapters/qwen-code.d.mts +30 -0
- package/esm/adapters/qwen-code.d.mts.map +1 -0
- package/esm/adapters/qwen-code.mjs +94 -0
- package/esm/adapters/qwen-code.mjs.map +1 -0
- package/esm/adapters/reasonix.d.mts +31 -0
- package/esm/adapters/reasonix.d.mts.map +1 -0
- package/esm/adapters/reasonix.mjs +250 -0
- package/esm/adapters/reasonix.mjs.map +1 -0
- package/esm/adapters/shared.d.mts +78 -0
- package/esm/adapters/shared.d.mts.map +1 -0
- package/esm/adapters/shared.mjs +161 -0
- package/esm/adapters/shared.mjs.map +1 -0
- package/esm/cli-agent.d.mts +40 -0
- package/esm/cli-agent.d.mts.map +1 -0
- package/esm/cli-agent.mjs +207 -0
- package/esm/cli-agent.mjs.map +1 -0
- package/esm/cli.d.mts +53 -0
- package/esm/cli.d.mts.map +1 -0
- package/esm/cli.mjs +61 -0
- package/esm/cli.mjs.map +1 -0
- package/esm/contracts/adapter.contract.d.mts +154 -0
- package/esm/contracts/adapter.contract.d.mts.map +1 -0
- package/esm/contracts/events.type.d.mts +111 -0
- package/esm/contracts/events.type.d.mts.map +1 -0
- package/esm/contracts/index.d.mts +4 -0
- package/esm/contracts/mcp.type.d.mts +29 -0
- package/esm/contracts/mcp.type.d.mts.map +1 -0
- package/esm/contracts/options.type.d.mts +87 -0
- package/esm/contracts/options.type.d.mts.map +1 -0
- package/esm/errors.d.mts +46 -0
- package/esm/errors.d.mts.map +1 -0
- package/esm/errors.mjs +32 -0
- package/esm/errors.mjs.map +1 -0
- package/esm/index.d.mts +31 -0
- package/esm/index.mjs +28 -0
- package/esm/model/cli-model.d.mts +48 -0
- package/esm/model/cli-model.d.mts.map +1 -0
- package/esm/model/cli-model.mjs +133 -0
- package/esm/model/cli-model.mjs.map +1 -0
- package/esm/probe/probe.d.mts +18 -0
- package/esm/probe/probe.d.mts.map +1 -0
- package/esm/probe/probe.mjs +148 -0
- package/esm/probe/probe.mjs.map +1 -0
- package/esm/session/aggregate.mjs +86 -0
- package/esm/session/aggregate.mjs.map +1 -0
- package/esm/session/environment.mjs +71 -0
- package/esm/session/environment.mjs.map +1 -0
- package/esm/session/run-directory.mjs +74 -0
- package/esm/session/run-directory.mjs.map +1 -0
- package/esm/session/session.mjs +188 -0
- package/esm/session/session.mjs.map +1 -0
- package/esm/spawn/invocation.d.mts +35 -0
- package/esm/spawn/invocation.d.mts.map +1 -0
- package/esm/spawn/invocation.mjs +76 -0
- package/esm/spawn/invocation.mjs.map +1 -0
- package/esm/spawn/process-tree.mjs +57 -0
- package/esm/spawn/process-tree.mjs.map +1 -0
- package/esm/spawn/resolve-command.d.mts +73 -0
- package/esm/spawn/resolve-command.d.mts.map +1 -0
- package/esm/spawn/resolve-command.mjs +109 -0
- package/esm/spawn/resolve-command.mjs.map +1 -0
- package/esm/stream/event-log.mjs +52 -0
- package/esm/stream/event-log.mjs.map +1 -0
- package/esm/stream/json-lines.d.mts +33 -0
- package/esm/stream/json-lines.d.mts.map +1 -0
- package/esm/stream/json-lines.mjs +65 -0
- package/esm/stream/json-lines.mjs.map +1 -0
- package/esm/stream/line-splitter.mjs +53 -0
- package/esm/stream/line-splitter.mjs.map +1 -0
- package/esm/utils/toml.d.mts +22 -0
- package/esm/utils/toml.d.mts.map +1 -0
- package/esm/utils/toml.mjs +53 -0
- package/esm/utils/toml.mjs.map +1 -0
- package/llms-full.txt +373 -0
- package/llms.txt +10 -0
- package/package.json +39 -4
- package/skills/README.md +13 -0
- package/skills/index.md +22 -0
- package/skills/run-cli-agents/SKILL.md +186 -0
- package/skills/write-a-cli-adapter/SKILL.md +173 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { asRecord, jsonLinesParser, numberField, recordField, stringField } from "../stream/json-lines.mjs";
|
|
2
|
+
import { argvPrompt, jsonFile, usageOf } from "./shared.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-cli/src/adapters/opencode.ts
|
|
6
|
+
/**
|
|
7
|
+
* OpenCode (`opencode`, npm `opencode-ai`).
|
|
8
|
+
*
|
|
9
|
+
* Verified against the real `opencode --help` / `opencode run --help` of
|
|
10
|
+
* 1.19.3, the CLI source at that tag (`packages/opencode/src/cli/cmd/run.ts`)
|
|
11
|
+
* and the docs (`cli.mdx`, `config.mdx`, `mcp-servers.mdx`,
|
|
12
|
+
* `permissions.mdx`), 2026-10-17.
|
|
13
|
+
*
|
|
14
|
+
* - Headless: `opencode run --format json <message..>`; with no message,
|
|
15
|
+
* piped stdin is the prompt.
|
|
16
|
+
* - Stream: `{"type","timestamp","sessionID","part"|"error"}` lines of six
|
|
17
|
+
* types: `step_start`, `text` (whole text blocks, no deltas), `reasoning`,
|
|
18
|
+
* `tool_use` (emitted once the tool finished — call and result together,
|
|
19
|
+
* `state.status` `completed`/`error`), `step_finish` (per-step `cost` in
|
|
20
|
+
* USD and `tokens{input,output,reasoning,cache{read,write}}`, input net of
|
|
21
|
+
* cache) and `error{error:{name,data:{message}}}`. No init or final record.
|
|
22
|
+
* - Permissions: `standard` keeps OpenCode's own rules (permissive by
|
|
23
|
+
* default; anything that would ask is auto-rejected in `run`); bypass is
|
|
24
|
+
* `--auto`.
|
|
25
|
+
* - MCP: the `mcp` key of a config file named by `OPENCODE_CONFIG`, written
|
|
26
|
+
* to the run directory — works with any home.
|
|
27
|
+
* - Home: OpenCode follows XDG; a private/owned home sets `XDG_CONFIG_HOME`,
|
|
28
|
+
* `XDG_DATA_HOME` (auth lives here), `XDG_STATE_HOME` and `XDG_CACHE_HOME`
|
|
29
|
+
* beneath it. Provider keys use the provider's own variable
|
|
30
|
+
* (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …) — pass them through `env`.
|
|
31
|
+
* - The working directory is passed with `--dir`, because OpenCode prefers a
|
|
32
|
+
* (possibly stale) `PWD` over the process cwd.
|
|
33
|
+
*/
|
|
34
|
+
const opencodeAdapter = {
|
|
35
|
+
name: "opencode",
|
|
36
|
+
title: "OpenCode",
|
|
37
|
+
command: "opencode",
|
|
38
|
+
apiKeyEnv: [],
|
|
39
|
+
homeEnv: [
|
|
40
|
+
"XDG_CONFIG_HOME",
|
|
41
|
+
"XDG_DATA_HOME",
|
|
42
|
+
"XDG_STATE_HOME",
|
|
43
|
+
"XDG_CACHE_HOME"
|
|
44
|
+
],
|
|
45
|
+
transports: ["argv", "stdin"],
|
|
46
|
+
effort: true,
|
|
47
|
+
mcp: true,
|
|
48
|
+
probe: {
|
|
49
|
+
help: [["run", "--help"]],
|
|
50
|
+
version: ["--version"]
|
|
51
|
+
},
|
|
52
|
+
verification: {
|
|
53
|
+
version: "1.19.3",
|
|
54
|
+
verifiedBy: "help",
|
|
55
|
+
sources: [
|
|
56
|
+
"https://opencode.ai/docs/cli/",
|
|
57
|
+
"https://opencode.ai/docs/mcp-servers/",
|
|
58
|
+
"https://opencode.ai/docs/permissions/",
|
|
59
|
+
"https://github.com/anomalyco/opencode/blob/v1.19.3/packages/opencode/src/cli/cmd/run.ts"
|
|
60
|
+
],
|
|
61
|
+
checkedAt: "2026-10-17"
|
|
62
|
+
},
|
|
63
|
+
plan(context) {
|
|
64
|
+
const args = [
|
|
65
|
+
"run",
|
|
66
|
+
"--format",
|
|
67
|
+
"json",
|
|
68
|
+
"--dir",
|
|
69
|
+
context.cwd
|
|
70
|
+
];
|
|
71
|
+
const requires = ["--format", "--dir"];
|
|
72
|
+
const files = [];
|
|
73
|
+
const env = { OPENCODE_DISABLE_AUTOUPDATE: "1" };
|
|
74
|
+
if (context.model !== void 0) {
|
|
75
|
+
args.push("--model", context.model);
|
|
76
|
+
requires.push("--model");
|
|
77
|
+
}
|
|
78
|
+
if (context.effort !== void 0) {
|
|
79
|
+
args.push("--variant", context.effort);
|
|
80
|
+
requires.push("--variant");
|
|
81
|
+
}
|
|
82
|
+
if (context.resume !== void 0) {
|
|
83
|
+
args.push("--session", context.resume);
|
|
84
|
+
requires.push("--session");
|
|
85
|
+
}
|
|
86
|
+
if (context.permission === "bypass") {
|
|
87
|
+
args.push("--auto");
|
|
88
|
+
requires.push("--auto");
|
|
89
|
+
}
|
|
90
|
+
if (context.mcp !== void 0) {
|
|
91
|
+
const file = path.join(context.runDir, "opencode.json");
|
|
92
|
+
files.push({
|
|
93
|
+
path: file,
|
|
94
|
+
content: jsonFile({
|
|
95
|
+
$schema: "https://opencode.ai/config.json",
|
|
96
|
+
mcp: opencodeMcp(context.mcp)
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
env.OPENCODE_CONFIG = file;
|
|
100
|
+
}
|
|
101
|
+
if (context.home !== void 0) {
|
|
102
|
+
env.XDG_CONFIG_HOME = path.join(context.home, "config");
|
|
103
|
+
env.XDG_DATA_HOME = path.join(context.home, "data");
|
|
104
|
+
env.XDG_STATE_HOME = path.join(context.home, "state");
|
|
105
|
+
env.XDG_CACHE_HOME = path.join(context.home, "cache");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
args,
|
|
109
|
+
tail: context.transport === "argv" ? [argvPrompt(context.prompt)] : [],
|
|
110
|
+
stdin: context.transport === "stdin" ? context.prompt : void 0,
|
|
111
|
+
env,
|
|
112
|
+
files,
|
|
113
|
+
requires
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
parser: createOpencodeParser
|
|
117
|
+
};
|
|
118
|
+
function opencodeMcp(servers) {
|
|
119
|
+
const rendered = {};
|
|
120
|
+
for (const [name, server] of Object.entries(servers)) rendered[name] = server.type === "stdio" ? {
|
|
121
|
+
type: "local",
|
|
122
|
+
command: [server.command, ...server.args ?? []],
|
|
123
|
+
...server.env !== void 0 ? { environment: server.env } : {},
|
|
124
|
+
enabled: true
|
|
125
|
+
} : {
|
|
126
|
+
type: "remote",
|
|
127
|
+
url: server.url,
|
|
128
|
+
...server.headers !== void 0 ? { headers: server.headers } : {},
|
|
129
|
+
enabled: true
|
|
130
|
+
};
|
|
131
|
+
return rendered;
|
|
132
|
+
}
|
|
133
|
+
/** Parser for `opencode run --format json`. */
|
|
134
|
+
function createOpencodeParser() {
|
|
135
|
+
let started = false;
|
|
136
|
+
return jsonLinesParser((record) => {
|
|
137
|
+
const events = [];
|
|
138
|
+
const part = recordField(record, "part");
|
|
139
|
+
if (!started) {
|
|
140
|
+
started = true;
|
|
141
|
+
events.push({
|
|
142
|
+
type: "session.started",
|
|
143
|
+
sessionRef: stringField(record, "sessionID"),
|
|
144
|
+
raw: record
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
switch (stringField(record, "type")) {
|
|
148
|
+
case "text": {
|
|
149
|
+
const text = stringField(part, "text") ?? "";
|
|
150
|
+
if (text.length > 0) events.push({
|
|
151
|
+
type: "message",
|
|
152
|
+
text,
|
|
153
|
+
delta: false,
|
|
154
|
+
raw: record
|
|
155
|
+
});
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "tool_use": {
|
|
159
|
+
const state = recordField(part, "state");
|
|
160
|
+
const id = stringField(part, "callID") ?? stringField(part, "id") ?? "";
|
|
161
|
+
const failed = stringField(state, "status") === "error";
|
|
162
|
+
events.push({
|
|
163
|
+
type: "tool.call",
|
|
164
|
+
id,
|
|
165
|
+
name: stringField(part, "tool") ?? "",
|
|
166
|
+
input: state?.input,
|
|
167
|
+
raw: record
|
|
168
|
+
}, {
|
|
169
|
+
type: "tool.result",
|
|
170
|
+
id,
|
|
171
|
+
output: (failed ? stringField(state, "error") : stringField(state, "output")) ?? "",
|
|
172
|
+
isError: failed,
|
|
173
|
+
raw: record
|
|
174
|
+
});
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "step_finish": {
|
|
178
|
+
const tokens = recordField(part, "tokens");
|
|
179
|
+
const cache = recordField(tokens, "cache");
|
|
180
|
+
if (tokens !== void 0) {
|
|
181
|
+
const uncached = numberField(tokens, "input") ?? 0;
|
|
182
|
+
const cacheRead = numberField(cache, "read") ?? 0;
|
|
183
|
+
const cacheWrite = numberField(cache, "write") ?? 0;
|
|
184
|
+
events.push({
|
|
185
|
+
type: "usage",
|
|
186
|
+
cumulative: false,
|
|
187
|
+
usage: usageOf({
|
|
188
|
+
input: uncached + cacheRead + cacheWrite,
|
|
189
|
+
output: numberField(tokens, "output"),
|
|
190
|
+
cachedTokens: cacheRead > 0 ? cacheRead : void 0,
|
|
191
|
+
cacheWriteTokens: cacheWrite > 0 ? cacheWrite : void 0,
|
|
192
|
+
reasoningTokens: numberField(tokens, "reasoning") || void 0,
|
|
193
|
+
costUSD: numberField(part, "cost")
|
|
194
|
+
}),
|
|
195
|
+
raw: record
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "error": {
|
|
201
|
+
const error = asRecord(record.error);
|
|
202
|
+
const name = stringField(error, "name") ?? "Error";
|
|
203
|
+
const message = stringField(recordField(error, "data"), "message") ?? name;
|
|
204
|
+
events.push({
|
|
205
|
+
type: "error",
|
|
206
|
+
message: `${name}: ${message}`,
|
|
207
|
+
fatal: true,
|
|
208
|
+
raw: record
|
|
209
|
+
});
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return events;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
//#endregion
|
|
218
|
+
export { createOpencodeParser, opencodeAdapter };
|
|
219
|
+
//# sourceMappingURL=opencode.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/adapters/opencode.ts"],"sourcesContent":["import path from \"node:path\";\nimport type {\n CliAdapter,\n CliLaunchFile,\n CliMcpServers,\n CliParsedEvent,\n CliStreamParser,\n} from \"../contracts\";\nimport {\n asRecord,\n jsonLinesParser,\n numberField,\n recordField,\n stringField,\n} from \"../stream/json-lines\";\nimport { argvPrompt, jsonFile, usageOf } from \"./shared\";\n\n/**\n * OpenCode (`opencode`, npm `opencode-ai`).\n *\n * Verified against the real `opencode --help` / `opencode run --help` of\n * 1.19.3, the CLI source at that tag (`packages/opencode/src/cli/cmd/run.ts`)\n * and the docs (`cli.mdx`, `config.mdx`, `mcp-servers.mdx`,\n * `permissions.mdx`), 2026-10-17.\n *\n * - Headless: `opencode run --format json <message..>`; with no message,\n * piped stdin is the prompt.\n * - Stream: `{\"type\",\"timestamp\",\"sessionID\",\"part\"|\"error\"}` lines of six\n * types: `step_start`, `text` (whole text blocks, no deltas), `reasoning`,\n * `tool_use` (emitted once the tool finished — call and result together,\n * `state.status` `completed`/`error`), `step_finish` (per-step `cost` in\n * USD and `tokens{input,output,reasoning,cache{read,write}}`, input net of\n * cache) and `error{error:{name,data:{message}}}`. No init or final record.\n * - Permissions: `standard` keeps OpenCode's own rules (permissive by\n * default; anything that would ask is auto-rejected in `run`); bypass is\n * `--auto`.\n * - MCP: the `mcp` key of a config file named by `OPENCODE_CONFIG`, written\n * to the run directory — works with any home.\n * - Home: OpenCode follows XDG; a private/owned home sets `XDG_CONFIG_HOME`,\n * `XDG_DATA_HOME` (auth lives here), `XDG_STATE_HOME` and `XDG_CACHE_HOME`\n * beneath it. Provider keys use the provider's own variable\n * (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …) — pass them through `env`.\n * - The working directory is passed with `--dir`, because OpenCode prefers a\n * (possibly stale) `PWD` over the process cwd.\n */\nexport const opencodeAdapter: CliAdapter = {\n name: \"opencode\",\n title: \"OpenCode\",\n command: \"opencode\",\n apiKeyEnv: [],\n homeEnv: [\"XDG_CONFIG_HOME\", \"XDG_DATA_HOME\", \"XDG_STATE_HOME\", \"XDG_CACHE_HOME\"],\n transports: [\"argv\", \"stdin\"],\n effort: true,\n mcp: true,\n probe: { help: [[\"run\", \"--help\"]], version: [\"--version\"] },\n verification: {\n version: \"1.19.3\",\n verifiedBy: \"help\",\n sources: [\n \"https://opencode.ai/docs/cli/\",\n \"https://opencode.ai/docs/mcp-servers/\",\n \"https://opencode.ai/docs/permissions/\",\n \"https://github.com/anomalyco/opencode/blob/v1.19.3/packages/opencode/src/cli/cmd/run.ts\",\n ],\n checkedAt: \"2026-10-17\",\n },\n plan(context) {\n const args = [\"run\", \"--format\", \"json\", \"--dir\", context.cwd];\n const requires = [\"--format\", \"--dir\"];\n const files: CliLaunchFile[] = [];\n const env: Record<string, string> = { OPENCODE_DISABLE_AUTOUPDATE: \"1\" };\n\n if (context.model !== undefined) {\n args.push(\"--model\", context.model);\n requires.push(\"--model\");\n }\n\n if (context.effort !== undefined) {\n // `--variant` is provider-specific (\"high, max, minimal\"); the level is passed as-is.\n args.push(\"--variant\", context.effort);\n requires.push(\"--variant\");\n }\n\n if (context.resume !== undefined) {\n args.push(\"--session\", context.resume);\n requires.push(\"--session\");\n }\n\n if (context.permission === \"bypass\") {\n args.push(\"--auto\");\n requires.push(\"--auto\");\n }\n\n if (context.mcp !== undefined) {\n const file = path.join(context.runDir, \"opencode.json\");\n\n files.push({\n path: file,\n content: jsonFile({\n $schema: \"https://opencode.ai/config.json\",\n mcp: opencodeMcp(context.mcp),\n }),\n });\n env.OPENCODE_CONFIG = file;\n }\n\n if (context.home !== undefined) {\n env.XDG_CONFIG_HOME = path.join(context.home, \"config\");\n env.XDG_DATA_HOME = path.join(context.home, \"data\");\n env.XDG_STATE_HOME = path.join(context.home, \"state\");\n env.XDG_CACHE_HOME = path.join(context.home, \"cache\");\n }\n\n return {\n args,\n tail: context.transport === \"argv\" ? [argvPrompt(context.prompt)] : [],\n stdin: context.transport === \"stdin\" ? context.prompt : undefined,\n env,\n files,\n requires,\n };\n },\n parser: createOpencodeParser,\n};\n\nfunction opencodeMcp(servers: CliMcpServers): Record<string, unknown> {\n const rendered: Record<string, unknown> = {};\n\n for (const [name, server] of Object.entries(servers)) {\n rendered[name] =\n server.type === \"stdio\"\n ? {\n type: \"local\",\n command: [server.command, ...(server.args ?? [])],\n ...(server.env !== undefined ? { environment: server.env } : {}),\n enabled: true,\n }\n : {\n type: \"remote\",\n url: server.url,\n ...(server.headers !== undefined ? { headers: server.headers } : {}),\n enabled: true,\n };\n }\n\n return rendered;\n}\n\n/** Parser for `opencode run --format json`. */\nexport function createOpencodeParser(): CliStreamParser {\n let started = false;\n\n return jsonLinesParser((record): CliParsedEvent[] => {\n const events: CliParsedEvent[] = [];\n const part = recordField(record, \"part\");\n\n if (!started) {\n started = true;\n events.push({\n type: \"session.started\",\n sessionRef: stringField(record, \"sessionID\"),\n raw: record,\n });\n }\n\n switch (stringField(record, \"type\")) {\n case \"text\": {\n const text = stringField(part, \"text\") ?? \"\";\n\n if (text.length > 0) {\n events.push({ type: \"message\", text, delta: false, raw: record });\n }\n break;\n }\n case \"tool_use\": {\n const state = recordField(part, \"state\");\n const id = stringField(part, \"callID\") ?? stringField(part, \"id\") ?? \"\";\n const failed = stringField(state, \"status\") === \"error\";\n\n events.push(\n {\n type: \"tool.call\",\n id,\n name: stringField(part, \"tool\") ?? \"\",\n input: state?.input,\n raw: record,\n },\n {\n type: \"tool.result\",\n id,\n output: (failed ? stringField(state, \"error\") : stringField(state, \"output\")) ?? \"\",\n isError: failed,\n raw: record,\n },\n );\n break;\n }\n case \"step_finish\": {\n const tokens = recordField(part, \"tokens\");\n const cache = recordField(tokens, \"cache\");\n\n if (tokens !== undefined) {\n const uncached = numberField(tokens, \"input\") ?? 0;\n const cacheRead = numberField(cache, \"read\") ?? 0;\n const cacheWrite = numberField(cache, \"write\") ?? 0;\n\n events.push({\n type: \"usage\",\n cumulative: false,\n usage: usageOf({\n input: uncached + cacheRead + cacheWrite,\n output: numberField(tokens, \"output\"),\n cachedTokens: cacheRead > 0 ? cacheRead : undefined,\n cacheWriteTokens: cacheWrite > 0 ? cacheWrite : undefined,\n reasoningTokens: numberField(tokens, \"reasoning\") || undefined,\n costUSD: numberField(part, \"cost\"),\n }),\n raw: record,\n });\n }\n break;\n }\n case \"error\": {\n const error = asRecord(record.error);\n const name = stringField(error, \"name\") ?? \"Error\";\n const message = stringField(recordField(error, \"data\"), \"message\") ?? name;\n\n events.push({ type: \"error\", message: `${name}: ${message}`, fatal: true, raw: record });\n break;\n }\n }\n\n return events;\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CliAdapter } from "../contracts/adapter.contract.mjs";
|
|
2
|
+
//#region ../../packages/ai-cli/src/adapters/qwen-code.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Qwen Code (`qwen`, npm `@qwen-code/qwen-code`), a Gemini CLI fork.
|
|
5
|
+
*
|
|
6
|
+
* Verified against the real `qwen --help` of 0.25.2 and the docs/source at
|
|
7
|
+
* tag v0.25.2 (`docs/users/features/headless.md`, `nonInteractive/types.ts`),
|
|
8
|
+
* 2026-10-17.
|
|
9
|
+
*
|
|
10
|
+
* - Headless: positional prompt (`-p` is deprecated) with
|
|
11
|
+
* `--output-format stream-json`; piped stdin is used as the prompt when no
|
|
12
|
+
* positional prompt is given.
|
|
13
|
+
* - Stream: the Claude Code `stream-json` shape (`system/init`, `assistant`,
|
|
14
|
+
* `user` tool results, `result`), with `input_tokens` that already include
|
|
15
|
+
* cached tokens and no cost field.
|
|
16
|
+
* - Permissions: headless auto-denies tools that need confirmation (the tool
|
|
17
|
+
* result says so and `permission_denials` lists it); bypass is `--yolo`.
|
|
18
|
+
* - MCP: `--mcp-config <file>` with `{"mcpServers":…}` — written to the run
|
|
19
|
+
* directory, so it works with any home. Remote servers use `httpUrl`
|
|
20
|
+
* (streamable HTTP) or `url` (SSE).
|
|
21
|
+
* - Home: `QWEN_HOME`. Auth depends on the auth type: `OPENAI_API_KEY`
|
|
22
|
+
* (default for `apiKey`), or `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` with
|
|
23
|
+
* `--auth-type` in `args`.
|
|
24
|
+
* - Effort has no flag (it is the `model.reasoningEffort` setting), so
|
|
25
|
+
* `effort` is refused.
|
|
26
|
+
*/
|
|
27
|
+
declare const qwenCodeAdapter: CliAdapter;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { qwenCodeAdapter };
|
|
30
|
+
//# sourceMappingURL=qwen-code.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qwen-code.d.mts","names":[],"sources":["../../../../../../../../packages/ai-cli/src/adapters/qwen-code.ts"],"mappings":";;;;;AA6BA;;;;AA+DC;;;;;;;;;;;;;;;;;cA/DY,eAAA,EAAiB,UA+D7B"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { argvPrompt, jsonFile, mcpServersJson } from "./shared.mjs";
|
|
2
|
+
import { createClaudeStreamParser } from "./claude-stream.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-cli/src/adapters/qwen-code.ts
|
|
6
|
+
/**
|
|
7
|
+
* Qwen Code (`qwen`, npm `@qwen-code/qwen-code`), a Gemini CLI fork.
|
|
8
|
+
*
|
|
9
|
+
* Verified against the real `qwen --help` of 0.25.2 and the docs/source at
|
|
10
|
+
* tag v0.25.2 (`docs/users/features/headless.md`, `nonInteractive/types.ts`),
|
|
11
|
+
* 2026-10-17.
|
|
12
|
+
*
|
|
13
|
+
* - Headless: positional prompt (`-p` is deprecated) with
|
|
14
|
+
* `--output-format stream-json`; piped stdin is used as the prompt when no
|
|
15
|
+
* positional prompt is given.
|
|
16
|
+
* - Stream: the Claude Code `stream-json` shape (`system/init`, `assistant`,
|
|
17
|
+
* `user` tool results, `result`), with `input_tokens` that already include
|
|
18
|
+
* cached tokens and no cost field.
|
|
19
|
+
* - Permissions: headless auto-denies tools that need confirmation (the tool
|
|
20
|
+
* result says so and `permission_denials` lists it); bypass is `--yolo`.
|
|
21
|
+
* - MCP: `--mcp-config <file>` with `{"mcpServers":…}` — written to the run
|
|
22
|
+
* directory, so it works with any home. Remote servers use `httpUrl`
|
|
23
|
+
* (streamable HTTP) or `url` (SSE).
|
|
24
|
+
* - Home: `QWEN_HOME`. Auth depends on the auth type: `OPENAI_API_KEY`
|
|
25
|
+
* (default for `apiKey`), or `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` with
|
|
26
|
+
* `--auth-type` in `args`.
|
|
27
|
+
* - Effort has no flag (it is the `model.reasoningEffort` setting), so
|
|
28
|
+
* `effort` is refused.
|
|
29
|
+
*/
|
|
30
|
+
const qwenCodeAdapter = {
|
|
31
|
+
name: "qwen-code",
|
|
32
|
+
title: "Qwen Code",
|
|
33
|
+
command: "qwen",
|
|
34
|
+
apiKeyEnv: ["OPENAI_API_KEY"],
|
|
35
|
+
homeEnv: ["QWEN_HOME"],
|
|
36
|
+
transports: ["argv", "stdin"],
|
|
37
|
+
effort: false,
|
|
38
|
+
mcp: true,
|
|
39
|
+
probe: {
|
|
40
|
+
help: [["--help"]],
|
|
41
|
+
version: ["--version"]
|
|
42
|
+
},
|
|
43
|
+
verification: {
|
|
44
|
+
version: "0.25.2",
|
|
45
|
+
verifiedBy: "help",
|
|
46
|
+
sources: ["https://qwenlm.github.io/qwen-code-docs/", "https://github.com/QwenLM/qwen-code/blob/v0.25.2/docs/users/features/headless.md"],
|
|
47
|
+
checkedAt: "2026-10-17"
|
|
48
|
+
},
|
|
49
|
+
plan(context) {
|
|
50
|
+
const args = ["--output-format", "stream-json"];
|
|
51
|
+
const requires = ["--output-format", "stream-json"];
|
|
52
|
+
const files = [];
|
|
53
|
+
if (context.model !== void 0) {
|
|
54
|
+
args.push("--model", context.model);
|
|
55
|
+
requires.push("--model");
|
|
56
|
+
}
|
|
57
|
+
if (context.resume !== void 0) {
|
|
58
|
+
args.push("--resume", context.resume);
|
|
59
|
+
requires.push("--resume");
|
|
60
|
+
}
|
|
61
|
+
if (context.permission === "bypass") {
|
|
62
|
+
args.push("--yolo");
|
|
63
|
+
requires.push("--yolo");
|
|
64
|
+
}
|
|
65
|
+
if (context.mcp !== void 0) {
|
|
66
|
+
const file = path.join(context.runDir, "mcp.json");
|
|
67
|
+
files.push({
|
|
68
|
+
path: file,
|
|
69
|
+
content: jsonFile({ mcpServers: mcpServersJson(context.mcp, { httpUrlKey: "httpUrl" }) })
|
|
70
|
+
});
|
|
71
|
+
args.push("--mcp-config", file);
|
|
72
|
+
requires.push("--mcp-config");
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
args,
|
|
76
|
+
tail: context.transport === "argv" ? [argvPrompt(context.prompt)] : [],
|
|
77
|
+
stdin: context.transport === "stdin" ? context.prompt : void 0,
|
|
78
|
+
env: {
|
|
79
|
+
...context.home !== void 0 ? { QWEN_HOME: context.home } : {},
|
|
80
|
+
QWEN_CODE_SUPPRESS_YOLO_WARNING: "1"
|
|
81
|
+
},
|
|
82
|
+
files,
|
|
83
|
+
requires
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
parser: () => createClaudeStreamParser({
|
|
87
|
+
usage: "inclusive",
|
|
88
|
+
vendor: "Qwen Code"
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
export { qwenCodeAdapter };
|
|
94
|
+
//# sourceMappingURL=qwen-code.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qwen-code.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/adapters/qwen-code.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { CliAdapter, CliLaunchFile } from \"../contracts\";\nimport { createClaudeStreamParser } from \"./claude-stream\";\nimport { argvPrompt, jsonFile, mcpServersJson } from \"./shared\";\n\n/**\n * Qwen Code (`qwen`, npm `@qwen-code/qwen-code`), a Gemini CLI fork.\n *\n * Verified against the real `qwen --help` of 0.25.2 and the docs/source at\n * tag v0.25.2 (`docs/users/features/headless.md`, `nonInteractive/types.ts`),\n * 2026-10-17.\n *\n * - Headless: positional prompt (`-p` is deprecated) with\n * `--output-format stream-json`; piped stdin is used as the prompt when no\n * positional prompt is given.\n * - Stream: the Claude Code `stream-json` shape (`system/init`, `assistant`,\n * `user` tool results, `result`), with `input_tokens` that already include\n * cached tokens and no cost field.\n * - Permissions: headless auto-denies tools that need confirmation (the tool\n * result says so and `permission_denials` lists it); bypass is `--yolo`.\n * - MCP: `--mcp-config <file>` with `{\"mcpServers\":…}` — written to the run\n * directory, so it works with any home. Remote servers use `httpUrl`\n * (streamable HTTP) or `url` (SSE).\n * - Home: `QWEN_HOME`. Auth depends on the auth type: `OPENAI_API_KEY`\n * (default for `apiKey`), or `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` with\n * `--auth-type` in `args`.\n * - Effort has no flag (it is the `model.reasoningEffort` setting), so\n * `effort` is refused.\n */\nexport const qwenCodeAdapter: CliAdapter = {\n name: \"qwen-code\",\n title: \"Qwen Code\",\n command: \"qwen\",\n apiKeyEnv: [\"OPENAI_API_KEY\"],\n homeEnv: [\"QWEN_HOME\"],\n transports: [\"argv\", \"stdin\"],\n effort: false,\n mcp: true,\n probe: { help: [[\"--help\"]], version: [\"--version\"] },\n verification: {\n version: \"0.25.2\",\n verifiedBy: \"help\",\n sources: [\n \"https://qwenlm.github.io/qwen-code-docs/\",\n \"https://github.com/QwenLM/qwen-code/blob/v0.25.2/docs/users/features/headless.md\",\n ],\n checkedAt: \"2026-10-17\",\n },\n plan(context) {\n const args = [\"--output-format\", \"stream-json\"];\n const requires = [\"--output-format\", \"stream-json\"];\n const files: CliLaunchFile[] = [];\n\n if (context.model !== undefined) {\n args.push(\"--model\", context.model);\n requires.push(\"--model\");\n }\n\n if (context.resume !== undefined) {\n args.push(\"--resume\", context.resume);\n requires.push(\"--resume\");\n }\n\n if (context.permission === \"bypass\") {\n args.push(\"--yolo\");\n requires.push(\"--yolo\");\n }\n\n if (context.mcp !== undefined) {\n const file = path.join(context.runDir, \"mcp.json\");\n\n files.push({\n path: file,\n content: jsonFile({ mcpServers: mcpServersJson(context.mcp, { httpUrlKey: \"httpUrl\" }) }),\n });\n args.push(\"--mcp-config\", file);\n requires.push(\"--mcp-config\");\n }\n\n return {\n args,\n tail: context.transport === \"argv\" ? [argvPrompt(context.prompt)] : [],\n stdin: context.transport === \"stdin\" ? context.prompt : undefined,\n env: {\n ...(context.home !== undefined ? { QWEN_HOME: context.home } : {}),\n QWEN_CODE_SUPPRESS_YOLO_WARNING: \"1\",\n },\n files,\n requires,\n };\n },\n parser: () => createClaudeStreamParser({ usage: \"inclusive\", vendor: \"Qwen Code\" }),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,kBAA8B;CACzC,MAAM;CACN,OAAO;CACP,SAAS;CACT,WAAW,CAAC,gBAAgB;CAC5B,SAAS,CAAC,WAAW;CACrB,YAAY,CAAC,QAAQ,OAAO;CAC5B,QAAQ;CACR,KAAK;CACL,OAAO;EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC;EAAG,SAAS,CAAC,WAAW;CAAE;CACpD,cAAc;EACZ,SAAS;EACT,YAAY;EACZ,SAAS,CACP,4CACA,kFACF;EACA,WAAW;CACb;CACA,KAAK,SAAS;EACZ,MAAM,OAAO,CAAC,mBAAmB,aAAa;EAC9C,MAAM,WAAW,CAAC,mBAAmB,aAAa;EAClD,MAAM,QAAyB,CAAC;EAEhC,IAAI,QAAQ,UAAU,QAAW;GAC/B,KAAK,KAAK,WAAW,QAAQ,KAAK;GAClC,SAAS,KAAK,SAAS;EACzB;EAEA,IAAI,QAAQ,WAAW,QAAW;GAChC,KAAK,KAAK,YAAY,QAAQ,MAAM;GACpC,SAAS,KAAK,UAAU;EAC1B;EAEA,IAAI,QAAQ,eAAe,UAAU;GACnC,KAAK,KAAK,QAAQ;GAClB,SAAS,KAAK,QAAQ;EACxB;EAEA,IAAI,QAAQ,QAAQ,QAAW;GAC7B,MAAM,OAAO,KAAK,KAAK,QAAQ,QAAQ,UAAU;GAEjD,MAAM,KAAK;IACT,MAAM;IACN,SAAS,SAAS,EAAE,YAAY,eAAe,QAAQ,KAAK,EAAE,YAAY,UAAU,CAAC,EAAE,CAAC;GAC1F,CAAC;GACD,KAAK,KAAK,gBAAgB,IAAI;GAC9B,SAAS,KAAK,cAAc;EAC9B;EAEA,OAAO;GACL;GACA,MAAM,QAAQ,cAAc,SAAS,CAAC,WAAW,QAAQ,MAAM,CAAC,IAAI,CAAC;GACrE,OAAO,QAAQ,cAAc,UAAU,QAAQ,SAAS;GACxD,KAAK;IACH,GAAI,QAAQ,SAAS,SAAY,EAAE,WAAW,QAAQ,KAAK,IAAI,CAAC;IAChE,iCAAiC;GACnC;GACA;GACA;EACF;CACF;CACA,cAAc,yBAAyB;EAAE,OAAO;EAAa,QAAQ;CAAY,CAAC;AACpF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CliAdapter, CliStreamParser } from "../contracts/adapter.contract.mjs";
|
|
2
|
+
//#region ../../packages/ai-cli/src/adapters/reasonix.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reasonix (`reasonix`, npm `reasonix`) — a cache-first coding agent built
|
|
5
|
+
* around DeepSeek (now multi-provider); the npm package launches a prebuilt
|
|
6
|
+
* Go binary.
|
|
7
|
+
*
|
|
8
|
+
* Verified against the real `reasonix run --help` / `--version` of 2.43.0,
|
|
9
|
+
* real stream-json output (captured against a local fake model server) and
|
|
10
|
+
* the docs at that tag (`docs/CLI.md`, `docs/CONFIG_PATHS.md`), 2026-10-17.
|
|
11
|
+
*
|
|
12
|
+
* - Headless: `reasonix run --output-format stream-json <task>`; with no
|
|
13
|
+
* positional task, piped stdin is the prompt.
|
|
14
|
+
* - Stream: every line has `kind` (`text` deltas, `message` full text,
|
|
15
|
+
* `tool_dispatch`/`tool_started`/`tool_result` with a `tool` object whose
|
|
16
|
+
* non-empty `err` marks a failure, `usage` per model call, `turn_done`) plus
|
|
17
|
+
* `sessionId`; the final line is `{"type":"result",…}` with totals.
|
|
18
|
+
* - Permissions: headless "ask" mode refuses writes (listed in
|
|
19
|
+
* `permission_denials`; exit 4 in an untrusted folder); bypass is `--yolo`.
|
|
20
|
+
* - MCP: no flag; `[[plugins]]` in `$REASONIX_HOME/config.toml`.
|
|
21
|
+
* - Auth: provider keys are read only from `$REASONIX_HOME/.env`, never the
|
|
22
|
+
* shell environment, so `apiKey` is written there (0600) as
|
|
23
|
+
* `DEEPSEEK_API_KEY` — this needs a private home. For another provider,
|
|
24
|
+
* put its variable in that file yourself via a caller-owned home.
|
|
25
|
+
*/
|
|
26
|
+
declare const reasonixAdapter: CliAdapter;
|
|
27
|
+
/** Parser for `reasonix run --output-format stream-json`. */
|
|
28
|
+
declare function createReasonixParser(): CliStreamParser;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { createReasonixParser, reasonixAdapter };
|
|
31
|
+
//# sourceMappingURL=reasonix.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasonix.d.mts","names":[],"sources":["../../../../../../../../packages/ai-cli/src/adapters/reasonix.ts"],"mappings":";;;;;AAkDA;;;;AAgFC;AA6BD;;;;AAAuD;;;;;;;;;;;cA7G1C,eAAA,EAAiB,UAgF7B;;iBA6Be,oBAAA,IAAwB,eAAe"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { asRecord, jsonLinesParser, numberField, recordField, stringField } from "../stream/json-lines.mjs";
|
|
2
|
+
import { toToml } from "../utils/toml.mjs";
|
|
3
|
+
import { argvPrompt, dotenvFile, mapEffort, parseArguments, requirePrivateHome, usageOf } from "./shared.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region ../../packages/ai-cli/src/adapters/reasonix.ts
|
|
7
|
+
/**
|
|
8
|
+
* Reasonix (`reasonix`, npm `reasonix`) — a cache-first coding agent built
|
|
9
|
+
* around DeepSeek (now multi-provider); the npm package launches a prebuilt
|
|
10
|
+
* Go binary.
|
|
11
|
+
*
|
|
12
|
+
* Verified against the real `reasonix run --help` / `--version` of 2.43.0,
|
|
13
|
+
* real stream-json output (captured against a local fake model server) and
|
|
14
|
+
* the docs at that tag (`docs/CLI.md`, `docs/CONFIG_PATHS.md`), 2026-10-17.
|
|
15
|
+
*
|
|
16
|
+
* - Headless: `reasonix run --output-format stream-json <task>`; with no
|
|
17
|
+
* positional task, piped stdin is the prompt.
|
|
18
|
+
* - Stream: every line has `kind` (`text` deltas, `message` full text,
|
|
19
|
+
* `tool_dispatch`/`tool_started`/`tool_result` with a `tool` object whose
|
|
20
|
+
* non-empty `err` marks a failure, `usage` per model call, `turn_done`) plus
|
|
21
|
+
* `sessionId`; the final line is `{"type":"result",…}` with totals.
|
|
22
|
+
* - Permissions: headless "ask" mode refuses writes (listed in
|
|
23
|
+
* `permission_denials`; exit 4 in an untrusted folder); bypass is `--yolo`.
|
|
24
|
+
* - MCP: no flag; `[[plugins]]` in `$REASONIX_HOME/config.toml`.
|
|
25
|
+
* - Auth: provider keys are read only from `$REASONIX_HOME/.env`, never the
|
|
26
|
+
* shell environment, so `apiKey` is written there (0600) as
|
|
27
|
+
* `DEEPSEEK_API_KEY` — this needs a private home. For another provider,
|
|
28
|
+
* put its variable in that file yourself via a caller-owned home.
|
|
29
|
+
*/
|
|
30
|
+
const reasonixAdapter = {
|
|
31
|
+
name: "reasonix",
|
|
32
|
+
title: "Reasonix",
|
|
33
|
+
command: "reasonix",
|
|
34
|
+
apiKeyEnv: ["DEEPSEEK_API_KEY"],
|
|
35
|
+
homeEnv: ["REASONIX_HOME"],
|
|
36
|
+
transports: ["argv", "stdin"],
|
|
37
|
+
effort: true,
|
|
38
|
+
mcp: true,
|
|
39
|
+
probe: {
|
|
40
|
+
help: [["run", "--help"]],
|
|
41
|
+
version: ["--version"]
|
|
42
|
+
},
|
|
43
|
+
verification: {
|
|
44
|
+
version: "2.43.0",
|
|
45
|
+
verifiedBy: "help",
|
|
46
|
+
sources: [
|
|
47
|
+
"https://github.com/esengine/DeepSeek-Reasonix/blob/v2.43.0/docs/CLI.md",
|
|
48
|
+
"https://github.com/esengine/DeepSeek-Reasonix/blob/v2.43.0/docs/CONFIG_PATHS.md",
|
|
49
|
+
"https://www.npmjs.com/package/reasonix"
|
|
50
|
+
],
|
|
51
|
+
checkedAt: "2026-10-17"
|
|
52
|
+
},
|
|
53
|
+
plan(context) {
|
|
54
|
+
const args = [
|
|
55
|
+
"run",
|
|
56
|
+
"--output-format",
|
|
57
|
+
"stream-json"
|
|
58
|
+
];
|
|
59
|
+
const requires = ["--output-format", "stream-json"];
|
|
60
|
+
const files = [];
|
|
61
|
+
if (context.model !== void 0) {
|
|
62
|
+
args.push("--model", context.model);
|
|
63
|
+
requires.push("--model");
|
|
64
|
+
}
|
|
65
|
+
if (context.effort !== void 0) {
|
|
66
|
+
args.push("--effort", mapEffort(context.effort, {
|
|
67
|
+
none: "disabled",
|
|
68
|
+
low: "low",
|
|
69
|
+
high: "high",
|
|
70
|
+
max: "max"
|
|
71
|
+
}));
|
|
72
|
+
requires.push("--effort");
|
|
73
|
+
}
|
|
74
|
+
if (context.resume !== void 0) {
|
|
75
|
+
args.push("--resume", context.resume);
|
|
76
|
+
requires.push("--resume");
|
|
77
|
+
}
|
|
78
|
+
if (context.permission === "bypass") {
|
|
79
|
+
args.push("--yolo");
|
|
80
|
+
requires.push("--yolo");
|
|
81
|
+
}
|
|
82
|
+
if (context.mcp !== void 0) {
|
|
83
|
+
const home = requirePrivateHome(context, reasonixAdapter, "MCP servers");
|
|
84
|
+
files.push({
|
|
85
|
+
path: path.join(home, "config.toml"),
|
|
86
|
+
content: toToml({ plugins: reasonixPlugins(context.mcp) })
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (context.apiKey !== void 0) {
|
|
90
|
+
const home = requirePrivateHome(context, reasonixAdapter, "API keys (its .env file)");
|
|
91
|
+
files.push({
|
|
92
|
+
path: path.join(home, ".env"),
|
|
93
|
+
content: dotenvFile({ DEEPSEEK_API_KEY: context.apiKey })
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
args,
|
|
98
|
+
tail: context.transport === "argv" ? [argvPrompt(context.prompt)] : [],
|
|
99
|
+
stdin: context.transport === "stdin" ? context.prompt : void 0,
|
|
100
|
+
env: {
|
|
101
|
+
...context.home !== void 0 ? { REASONIX_HOME: context.home } : {},
|
|
102
|
+
REASONIX_TELEMETRY: "off"
|
|
103
|
+
},
|
|
104
|
+
files,
|
|
105
|
+
requires
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
parser: createReasonixParser
|
|
109
|
+
};
|
|
110
|
+
function reasonixPlugins(servers) {
|
|
111
|
+
return Object.entries(servers).map(([name, server]) => server.type === "stdio" ? {
|
|
112
|
+
name,
|
|
113
|
+
type: "stdio",
|
|
114
|
+
command: server.command,
|
|
115
|
+
args: server.args ?? [],
|
|
116
|
+
env: server.env
|
|
117
|
+
} : {
|
|
118
|
+
name,
|
|
119
|
+
type: server.type,
|
|
120
|
+
url: server.url,
|
|
121
|
+
headers: server.headers
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** Reasonix per-call usage (`promptTokens` includes cache hits). */
|
|
125
|
+
function callUsage(usage) {
|
|
126
|
+
if (usage === void 0) return;
|
|
127
|
+
const cacheHit = numberField(usage, "cacheHitTokens");
|
|
128
|
+
return usageOf({
|
|
129
|
+
input: numberField(usage, "promptTokens"),
|
|
130
|
+
output: numberField(usage, "completionTokens"),
|
|
131
|
+
total: numberField(usage, "totalTokens"),
|
|
132
|
+
cachedTokens: cacheHit !== void 0 && cacheHit > 0 ? cacheHit : void 0,
|
|
133
|
+
reasoningTokens: numberField(usage, "reasoningTokens"),
|
|
134
|
+
costUSD: numberField(usage, "costUsd")
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Parser for `reasonix run --output-format stream-json`. */
|
|
138
|
+
function createReasonixParser() {
|
|
139
|
+
let started = false;
|
|
140
|
+
let sessionRef;
|
|
141
|
+
return {
|
|
142
|
+
...jsonLinesParser((record) => {
|
|
143
|
+
const events = [];
|
|
144
|
+
const recordSession = stringField(record, "sessionId") ?? stringField(record, "session_id");
|
|
145
|
+
if (recordSession !== void 0) sessionRef = recordSession;
|
|
146
|
+
if (!started && recordSession !== void 0) {
|
|
147
|
+
started = true;
|
|
148
|
+
events.push({
|
|
149
|
+
type: "session.started",
|
|
150
|
+
sessionRef: recordSession,
|
|
151
|
+
raw: record
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (stringField(record, "type") === "result") {
|
|
155
|
+
const usage = recordField(record, "usage");
|
|
156
|
+
if (usage !== void 0) events.push({
|
|
157
|
+
type: "usage",
|
|
158
|
+
cumulative: true,
|
|
159
|
+
usage: usageOf({
|
|
160
|
+
input: numberField(usage, "input_tokens"),
|
|
161
|
+
output: numberField(usage, "output_tokens"),
|
|
162
|
+
cachedTokens: numberField(usage, "cache_read_input_tokens") || void 0,
|
|
163
|
+
costUSD: numberField(record, "total_cost_usd")
|
|
164
|
+
}),
|
|
165
|
+
raw: record
|
|
166
|
+
});
|
|
167
|
+
const result = stringField(record, "result") ?? "";
|
|
168
|
+
if (record.is_error === true) events.push({
|
|
169
|
+
type: "error",
|
|
170
|
+
message: result || "Reasonix run failed",
|
|
171
|
+
fatal: true,
|
|
172
|
+
raw: record
|
|
173
|
+
});
|
|
174
|
+
else if (result.length > 0) events.push({
|
|
175
|
+
type: "message",
|
|
176
|
+
text: result,
|
|
177
|
+
delta: false,
|
|
178
|
+
final: true,
|
|
179
|
+
raw: record
|
|
180
|
+
});
|
|
181
|
+
return events;
|
|
182
|
+
}
|
|
183
|
+
const tool = asRecord(record.tool);
|
|
184
|
+
switch (stringField(record, "kind")) {
|
|
185
|
+
case "text": {
|
|
186
|
+
const text = stringField(record, "text") ?? "";
|
|
187
|
+
if (text.length > 0) events.push({
|
|
188
|
+
type: "message",
|
|
189
|
+
text,
|
|
190
|
+
delta: true,
|
|
191
|
+
raw: record
|
|
192
|
+
});
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "message":
|
|
196
|
+
events.push({
|
|
197
|
+
type: "message",
|
|
198
|
+
text: stringField(record, "text") ?? "",
|
|
199
|
+
delta: false,
|
|
200
|
+
raw: record
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
case "tool_dispatch":
|
|
204
|
+
if (tool !== void 0 && tool.partial !== true) events.push({
|
|
205
|
+
type: "tool.call",
|
|
206
|
+
id: stringField(tool, "id") ?? "",
|
|
207
|
+
name: stringField(tool, "name") ?? "",
|
|
208
|
+
input: parseArguments(tool.args),
|
|
209
|
+
raw: record
|
|
210
|
+
});
|
|
211
|
+
break;
|
|
212
|
+
case "tool_result": {
|
|
213
|
+
const error = stringField(tool, "err") ?? "";
|
|
214
|
+
events.push({
|
|
215
|
+
type: "tool.result",
|
|
216
|
+
id: stringField(tool, "id") ?? "",
|
|
217
|
+
output: error.length > 0 ? error : stringField(tool, "output") ?? "",
|
|
218
|
+
isError: error.length > 0,
|
|
219
|
+
raw: record
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "usage": {
|
|
224
|
+
const usage = callUsage(recordField(record, "usage"));
|
|
225
|
+
if (usage !== void 0) events.push({
|
|
226
|
+
type: "usage",
|
|
227
|
+
usage,
|
|
228
|
+
cumulative: false,
|
|
229
|
+
raw: record
|
|
230
|
+
});
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "turn_done":
|
|
234
|
+
if (stringField(record, "status") === "failed") events.push({
|
|
235
|
+
type: "error",
|
|
236
|
+
message: stringField(record, "err") ?? "Reasonix turn failed",
|
|
237
|
+
fatal: true,
|
|
238
|
+
raw: record
|
|
239
|
+
});
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
return events;
|
|
243
|
+
}),
|
|
244
|
+
sessionRef: () => sessionRef
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
export { createReasonixParser, reasonixAdapter };
|
|
250
|
+
//# sourceMappingURL=reasonix.mjs.map
|