@geteigen/cli 0.0.0-stage → 3.0.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/README.md +154 -2
- package/dist/agent.d.ts +32 -0
- package/dist/agent.js +246 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +33 -0
- package/dist/cli/src/agent.d.ts +24 -0
- package/dist/cli/src/agent.js +260 -0
- package/dist/cli/src/agentTools.d.ts +10 -0
- package/dist/cli/src/agentTools.js +70 -0
- package/dist/cli/src/auth.d.ts +5 -0
- package/dist/cli/src/auth.js +33 -0
- package/dist/cli/src/config.d.ts +11 -0
- package/dist/cli/src/config.js +51 -0
- package/dist/cli/src/cursorAuth.d.ts +10 -0
- package/dist/cli/src/cursorAuth.js +92 -0
- package/dist/cli/src/index.d.ts +2 -0
- package/dist/cli/src/index.js +244 -0
- package/dist/cli/src/listModels.d.ts +14 -0
- package/dist/cli/src/listModels.js +196 -0
- package/dist/cli/src/listSessions.d.ts +9 -0
- package/dist/cli/src/listSessions.js +37 -0
- package/dist/cli/src/machineConfig.d.ts +20 -0
- package/dist/cli/src/machineConfig.js +66 -0
- package/dist/cli/src/pickFolder.d.ts +5 -0
- package/dist/cli/src/pickFolder.js +65 -0
- package/dist/cli/src/proc.d.ts +23 -0
- package/dist/cli/src/proc.js +77 -0
- package/dist/cli/src/service.d.ts +17 -0
- package/dist/cli/src/service.js +246 -0
- package/dist/cli/src/workRoots.d.ts +30 -0
- package/dist/cli/src/workRoots.js +221 -0
- package/dist/cli/src/worker.d.ts +8 -0
- package/dist/cli/src/worker.js +51 -0
- package/dist/cli/src/workerCore.d.ts +47 -0
- package/dist/cli/src/workerCore.js +702 -0
- package/dist/cli/src/workspace.d.ts +8 -0
- package/dist/cli/src/workspace.js +269 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +51 -0
- package/dist/cursorAuth.d.ts +10 -0
- package/dist/cursorAuth.js +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +108 -0
- package/dist/listModels.d.ts +6 -0
- package/dist/listModels.js +62 -0
- package/dist/listSessions.d.ts +9 -0
- package/dist/listSessions.js +37 -0
- package/dist/pickFolder.d.ts +5 -0
- package/dist/pickFolder.js +65 -0
- package/dist/shared/actionDrafts.d.ts +83 -0
- package/dist/shared/actionDrafts.js +169 -0
- package/dist/shared/agentArtifacts.d.ts +49 -0
- package/dist/shared/agentArtifacts.js +169 -0
- package/dist/shared/agentLabel.d.ts +18 -0
- package/dist/shared/agentLabel.js +22 -0
- package/dist/shared/agentMentions.d.ts +38 -0
- package/dist/shared/agentMentions.js +110 -0
- package/dist/shared/agentRepos.d.ts +28 -0
- package/dist/shared/agentRepos.js +60 -0
- package/dist/shared/agentRunStatus.d.ts +12 -0
- package/dist/shared/agentRunStatus.js +17 -0
- package/dist/shared/agentUsage.d.ts +24 -0
- package/dist/shared/agentUsage.js +139 -0
- package/dist/shared/approvals.d.ts +44 -0
- package/dist/shared/approvals.js +97 -0
- package/dist/shared/askEigen.d.ts +190 -0
- package/dist/shared/askEigen.js +76 -0
- package/dist/shared/attribution.d.ts +25 -0
- package/dist/shared/attribution.js +63 -0
- package/dist/shared/automations.d.ts +136 -0
- package/dist/shared/automations.js +546 -0
- package/dist/shared/backends/claudeCode.d.ts +42 -0
- package/dist/shared/backends/claudeCode.js +540 -0
- package/dist/shared/backends/codex.d.ts +15 -0
- package/dist/shared/backends/codex.js +287 -0
- package/dist/shared/backends/cursor.d.ts +102 -0
- package/dist/shared/backends/cursor.js +1197 -0
- package/dist/shared/backends/index.d.ts +18 -0
- package/dist/shared/backends/index.js +58 -0
- package/dist/shared/backends/opencode.d.ts +40 -0
- package/dist/shared/backends/opencode.js +397 -0
- package/dist/shared/backends/types.d.ts +171 -0
- package/dist/shared/backends/types.js +19 -0
- package/dist/shared/backends/usage.d.ts +8 -0
- package/dist/shared/backends/usage.js +71 -0
- package/dist/shared/billingPlans.d.ts +60 -0
- package/dist/shared/billingPlans.js +262 -0
- package/dist/shared/brain.d.ts +353 -0
- package/dist/shared/brain.js +487 -0
- package/dist/shared/clarifyingQuestions.d.ts +13 -0
- package/dist/shared/clarifyingQuestions.js +207 -0
- package/dist/shared/claudeModels.d.ts +10 -0
- package/dist/shared/claudeModels.js +52 -0
- package/dist/shared/cloudBackends.d.ts +21 -0
- package/dist/shared/cloudBackends.js +29 -0
- package/dist/shared/codexModels.d.ts +10 -0
- package/dist/shared/codexModels.js +28 -0
- package/dist/shared/contextWindows.d.ts +2 -0
- package/dist/shared/contextWindows.js +30 -0
- package/dist/shared/cursorMcpConfig.d.ts +14 -0
- package/dist/shared/cursorMcpConfig.js +43 -0
- package/dist/shared/cursorProjectMcp.d.ts +10 -0
- package/dist/shared/cursorProjectMcp.js +42 -0
- package/dist/shared/docs.d.ts +59 -0
- package/dist/shared/docs.js +87 -0
- package/dist/shared/events.d.ts +929 -0
- package/dist/shared/events.js +59 -0
- package/dist/shared/integrations.d.ts +266 -0
- package/dist/shared/integrations.js +449 -0
- package/dist/shared/issueTranscript.d.ts +64 -0
- package/dist/shared/issueTranscript.js +387 -0
- package/dist/shared/issues.d.ts +139 -0
- package/dist/shared/issues.js +217 -0
- package/dist/shared/machines.d.ts +115 -0
- package/dist/shared/machines.js +58 -0
- package/dist/shared/markdownMedia.d.ts +13 -0
- package/dist/shared/markdownMedia.js +41 -0
- package/dist/shared/notificationText.d.ts +6 -0
- package/dist/shared/notificationText.js +38 -0
- package/dist/shared/opencodeModels.d.ts +19 -0
- package/dist/shared/opencodeModels.js +62 -0
- package/dist/shared/opencodeSession.d.ts +7 -0
- package/dist/shared/opencodeSession.js +30 -0
- package/dist/shared/orchestration.d.ts +58 -0
- package/dist/shared/orchestration.js +12 -0
- package/dist/shared/orgs.d.ts +47 -0
- package/dist/shared/orgs.js +49 -0
- package/dist/shared/personalOrg.d.ts +5 -0
- package/dist/shared/personalOrg.js +12 -0
- package/dist/shared/plans.d.ts +3 -0
- package/dist/shared/plans.js +19 -0
- package/dist/shared/providerModels.d.ts +40 -0
- package/dist/shared/providerModels.js +503 -0
- package/dist/shared/roomAttention.d.ts +17 -0
- package/dist/shared/roomAttention.js +36 -0
- package/dist/shared/roomContext.d.ts +120 -0
- package/dist/shared/roomContext.js +133 -0
- package/dist/shared/roomPermissions.d.ts +56 -0
- package/dist/shared/roomPermissions.js +133 -0
- package/dist/shared/sessionSeats.d.ts +29 -0
- package/dist/shared/sessionSeats.js +57 -0
- package/dist/shared/slashCommands.d.ts +39 -0
- package/dist/shared/slashCommands.js +166 -0
- package/dist/shared/space.d.ts +33 -0
- package/dist/shared/space.js +54 -0
- package/dist/shared/spaceFeed.d.ts +78 -0
- package/dist/shared/spaceFeed.js +194 -0
- package/dist/shared/stringifyUnknown.d.ts +1 -0
- package/dist/shared/stringifyUnknown.js +1 -0
- package/dist/shared/swarm.d.ts +320 -0
- package/dist/shared/swarm.js +328 -0
- package/dist/shared/swarmVisual.d.ts +79 -0
- package/dist/shared/swarmVisual.js +212 -0
- package/dist/shared/terminalFilter.d.ts +13 -0
- package/dist/shared/terminalFilter.js +46 -0
- package/dist/shared/toolIntegration.d.ts +56 -0
- package/dist/shared/toolIntegration.js +180 -0
- package/dist/shared/typing.d.ts +9 -0
- package/dist/shared/typing.js +37 -0
- package/dist/shared/uploads.d.ts +31 -0
- package/dist/shared/uploads.js +65 -0
- package/dist/shared/voiceApprovals.d.ts +36 -0
- package/dist/shared/voiceApprovals.js +229 -0
- package/dist/shared/workspace.d.ts +47 -0
- package/dist/shared/workspace.js +40 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +266 -0
- package/package.json +51 -4
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { diffFromToolArgs, extractThinkingText, formatToolResultDetail, isEditTool, isQuestionTool, isTodoTool, parseQuestionToolArgs, todoStatusSummary, todosFromToolArgs, TOOL_RESULT_DETAIL_LIMIT, stringifyUnknown, } from "./cursor.js";
|
|
2
|
+
import { claudeToolIntegration } from "../toolIntegration.js";
|
|
3
|
+
import { claudeResultUsage } from "./usage.js";
|
|
4
|
+
function extractText(message) {
|
|
5
|
+
if (!message || typeof message !== "object")
|
|
6
|
+
return "";
|
|
7
|
+
const content = message.content;
|
|
8
|
+
if (typeof content === "string")
|
|
9
|
+
return content;
|
|
10
|
+
if (!Array.isArray(content))
|
|
11
|
+
return "";
|
|
12
|
+
return content
|
|
13
|
+
.map((part) => {
|
|
14
|
+
if (part &&
|
|
15
|
+
typeof part === "object" &&
|
|
16
|
+
part.type === "text" &&
|
|
17
|
+
"text" in part) {
|
|
18
|
+
return String(part.text ?? "");
|
|
19
|
+
}
|
|
20
|
+
return "";
|
|
21
|
+
})
|
|
22
|
+
.join("");
|
|
23
|
+
}
|
|
24
|
+
function asRecord(value) {
|
|
25
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
function blockIndex(event) {
|
|
30
|
+
return typeof event.index === "number" && Number.isFinite(event.index)
|
|
31
|
+
? event.index
|
|
32
|
+
: Number(event.index ?? 0) || 0;
|
|
33
|
+
}
|
|
34
|
+
function tryParseToolArgs(raw) {
|
|
35
|
+
const trimmed = raw.trim();
|
|
36
|
+
if (!trimmed)
|
|
37
|
+
return undefined;
|
|
38
|
+
try {
|
|
39
|
+
return asRecord(JSON.parse(trimmed));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** `message.content` on assistant/user rows, or top-level `content` fallback. */
|
|
46
|
+
function messageContent(ev) {
|
|
47
|
+
const message = asRecord(ev.message);
|
|
48
|
+
if (message && message.content !== undefined)
|
|
49
|
+
return message.content;
|
|
50
|
+
return ev.content;
|
|
51
|
+
}
|
|
52
|
+
function toolPathFromArgs(args) {
|
|
53
|
+
if (!args)
|
|
54
|
+
return undefined;
|
|
55
|
+
for (const k of [
|
|
56
|
+
"file_path",
|
|
57
|
+
"filePath",
|
|
58
|
+
"path",
|
|
59
|
+
"target_file",
|
|
60
|
+
"targetFile",
|
|
61
|
+
]) {
|
|
62
|
+
if (typeof args[k] === "string" && String(args[k]).trim()) {
|
|
63
|
+
return String(args[k]).trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Normalize Claude tool_result `content` (string or content-block array). */
|
|
69
|
+
function extractToolResultContent(content) {
|
|
70
|
+
if (typeof content === "string")
|
|
71
|
+
return content;
|
|
72
|
+
if (!Array.isArray(content)) {
|
|
73
|
+
if (content && typeof content === "object") {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.stringify(content);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
return content
|
|
84
|
+
.map((part) => {
|
|
85
|
+
if (typeof part === "string")
|
|
86
|
+
return part;
|
|
87
|
+
if (!part || typeof part !== "object")
|
|
88
|
+
return "";
|
|
89
|
+
const p = part;
|
|
90
|
+
if (typeof p.text === "string")
|
|
91
|
+
return p.text;
|
|
92
|
+
if (typeof p.content === "string")
|
|
93
|
+
return p.content;
|
|
94
|
+
return "";
|
|
95
|
+
})
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.join("\n");
|
|
98
|
+
}
|
|
99
|
+
function toolDetail(name, args, path) {
|
|
100
|
+
if (!args)
|
|
101
|
+
return name;
|
|
102
|
+
const todos = todosFromToolArgs(args);
|
|
103
|
+
if (isTodoTool(name) || todos.length > 0) {
|
|
104
|
+
return todos.length
|
|
105
|
+
? `${todos.length} todo${todos.length === 1 ? "" : "s"} · ${todoStatusSummary(todos)}`
|
|
106
|
+
: "Updating todos";
|
|
107
|
+
}
|
|
108
|
+
const command = typeof args.command === "string"
|
|
109
|
+
? args.command
|
|
110
|
+
: typeof args.pattern === "string"
|
|
111
|
+
? args.pattern
|
|
112
|
+
: typeof args.glob === "string"
|
|
113
|
+
? args.glob
|
|
114
|
+
: typeof args.glob_pattern === "string"
|
|
115
|
+
? args.glob_pattern
|
|
116
|
+
: undefined;
|
|
117
|
+
if (command)
|
|
118
|
+
return String(command);
|
|
119
|
+
if (path)
|
|
120
|
+
return path;
|
|
121
|
+
try {
|
|
122
|
+
return JSON.stringify(args).slice(0, 120);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return name;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Claude Code CLI headless backend.
|
|
130
|
+
* Spawns: `claude -p --output-format stream-json --verbose ...`
|
|
131
|
+
*
|
|
132
|
+
* Stateful: create a fresh instance per run so pending tool_use ids
|
|
133
|
+
* do not leak across concurrent agents.
|
|
134
|
+
*/
|
|
135
|
+
export class ClaudeCodeBackend {
|
|
136
|
+
kind = "claude-code";
|
|
137
|
+
available = true;
|
|
138
|
+
command = "claude";
|
|
139
|
+
/** tool_use id → metadata, filled on assistant/stream tool_use, consumed on tool_result */
|
|
140
|
+
pendingTools = new Map();
|
|
141
|
+
/** content_block index → accumulating tool_use input JSON */
|
|
142
|
+
streamingBlocks = new Map();
|
|
143
|
+
/**
|
|
144
|
+
* Each assistant message's slice of the run's cumulative text. Messages
|
|
145
|
+
* are joined with a blank line so turns never run together, and a full
|
|
146
|
+
* `assistant` snapshot can correct its own slice without touching others.
|
|
147
|
+
*/
|
|
148
|
+
segments = new Map();
|
|
149
|
+
currentMessageId = null;
|
|
150
|
+
anonymousMessages = 0;
|
|
151
|
+
/** Append (or correct) one message's text in the cumulative buffer. */
|
|
152
|
+
setMessageText(ctx, messageId, text, mode) {
|
|
153
|
+
const buf = ctx.assistantBuf;
|
|
154
|
+
const seg = this.segments.get(messageId);
|
|
155
|
+
if (!seg) {
|
|
156
|
+
if (!text)
|
|
157
|
+
return false;
|
|
158
|
+
if (buf.value && !buf.value.endsWith("\n\n")) {
|
|
159
|
+
buf.value += buf.value.endsWith("\n") ? "\n" : "\n\n";
|
|
160
|
+
}
|
|
161
|
+
this.segments.set(messageId, { start: buf.value.length, text });
|
|
162
|
+
buf.value += text;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
const next = mode === "append" ? seg.text + text : text;
|
|
166
|
+
if (next === seg.text)
|
|
167
|
+
return false;
|
|
168
|
+
const isLast = seg.start + seg.text.length === buf.value.length;
|
|
169
|
+
if (!isLast)
|
|
170
|
+
return false;
|
|
171
|
+
buf.value = buf.value.slice(0, seg.start) + next;
|
|
172
|
+
seg.text = next;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
buildArgs(opts) {
|
|
176
|
+
const args = [
|
|
177
|
+
"-p",
|
|
178
|
+
"--output-format",
|
|
179
|
+
"stream-json",
|
|
180
|
+
"--verbose",
|
|
181
|
+
"--include-partial-messages",
|
|
182
|
+
];
|
|
183
|
+
if (opts.mode === "plan") {
|
|
184
|
+
// Plan mode: explore + propose, no edits until the room exits plan.
|
|
185
|
+
args.push("--permission-mode", "plan");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
args.push("--dangerously-skip-permissions");
|
|
189
|
+
}
|
|
190
|
+
if (opts.modelId && opts.modelId !== "auto") {
|
|
191
|
+
args.push("--model", opts.modelId);
|
|
192
|
+
}
|
|
193
|
+
if (opts.sessionId) {
|
|
194
|
+
args.push("--resume", opts.sessionId);
|
|
195
|
+
}
|
|
196
|
+
if (opts.mcpServers && Object.keys(opts.mcpServers).length) {
|
|
197
|
+
args.push("--mcp-config", JSON.stringify({ mcpServers: opts.mcpServers }));
|
|
198
|
+
}
|
|
199
|
+
// `--mcp-config` is variadic: without `--` the CLI swallows the prompt as
|
|
200
|
+
// another config file path (ENAMETOOLONG). `--` also keeps prompts that
|
|
201
|
+
// start with "-" from being read as flags.
|
|
202
|
+
args.push("--", opts.prompt);
|
|
203
|
+
return args;
|
|
204
|
+
}
|
|
205
|
+
parseLine(json, ctx = {
|
|
206
|
+
assistantBuf: { value: "" },
|
|
207
|
+
gotTerminalEvent: { value: false },
|
|
208
|
+
}) {
|
|
209
|
+
if (!json || typeof json !== "object")
|
|
210
|
+
return [];
|
|
211
|
+
const ev = json;
|
|
212
|
+
const type = ev.type;
|
|
213
|
+
const out = [];
|
|
214
|
+
if (type === "system" && ev.subtype === "init" && ev.session_id) {
|
|
215
|
+
out.push({ kind: "session", sessionId: String(ev.session_id) });
|
|
216
|
+
const names = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
217
|
+
const commands = names(ev.slash_commands);
|
|
218
|
+
if (commands.length && !ctx.toolsOnly) {
|
|
219
|
+
out.push({
|
|
220
|
+
kind: "cli_commands",
|
|
221
|
+
catalog: {
|
|
222
|
+
commands,
|
|
223
|
+
terminalOnly: names(ev.terminal_slash_commands),
|
|
224
|
+
skills: names(ev.skills),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
// Heartbeat while a long tool runs — use it to ensure a card exists even
|
|
231
|
+
// when the original tool_use line was dropped by Blaxel streamLogs.
|
|
232
|
+
if (type === "tool_progress") {
|
|
233
|
+
const callId = (typeof ev.parent_tool_use_id === "string" && ev.parent_tool_use_id) ||
|
|
234
|
+
(typeof ev.tool_use_id === "string" &&
|
|
235
|
+
!String(ev.tool_use_id).includes("-heartbeat-")
|
|
236
|
+
? String(ev.tool_use_id)
|
|
237
|
+
: "") ||
|
|
238
|
+
"";
|
|
239
|
+
const name = typeof ev.tool_name === "string" && ev.tool_name
|
|
240
|
+
? ev.tool_name
|
|
241
|
+
: "tool";
|
|
242
|
+
if (callId)
|
|
243
|
+
this.startTool(callId, name, undefined, out);
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
// Token-level streaming (requires --include-partial-messages).
|
|
247
|
+
// Tool calls start as content_block_start { type: tool_use, input: {} }
|
|
248
|
+
// with args arriving later as input_json_delta — must not return after
|
|
249
|
+
// text_delta only or the room UI never gets tool_start cards.
|
|
250
|
+
if (type === "stream_event") {
|
|
251
|
+
const event = asRecord(ev.event);
|
|
252
|
+
if (!event)
|
|
253
|
+
return out;
|
|
254
|
+
if (event.type === "message_start") {
|
|
255
|
+
const message = asRecord(event.message);
|
|
256
|
+
this.currentMessageId =
|
|
257
|
+
(typeof message?.id === "string" && message.id) ||
|
|
258
|
+
`anon-${(this.anonymousMessages += 1)}`;
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
this.parseContentBlockEvent(event, ctx, out);
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
if (type === "content_block_start" ||
|
|
265
|
+
type === "content_block_delta" ||
|
|
266
|
+
type === "content_block_stop") {
|
|
267
|
+
this.parseContentBlockEvent(ev, ctx, out);
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
if (type === "assistant") {
|
|
271
|
+
const message = ev.message;
|
|
272
|
+
if (!ctx.toolsOnly) {
|
|
273
|
+
const thinking = extractThinkingText(message);
|
|
274
|
+
if (thinking)
|
|
275
|
+
this.emitThinking(ctx, thinking, "replace", true, out);
|
|
276
|
+
const text = extractText(message);
|
|
277
|
+
if (text) {
|
|
278
|
+
const record = asRecord(message);
|
|
279
|
+
const id = (typeof record?.id === "string" && record.id) ||
|
|
280
|
+
this.currentMessageId ||
|
|
281
|
+
`anon-${(this.anonymousMessages += 1)}`;
|
|
282
|
+
// The snapshot is authoritative for its own message: it fills in
|
|
283
|
+
// any deltas the stream missed, then closes the bubble.
|
|
284
|
+
this.setMessageText(ctx, id, text, "replace");
|
|
285
|
+
out.push({ kind: "assistant_final", text: ctx.assistantBuf.value });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const content = messageContent(ev);
|
|
289
|
+
if (Array.isArray(content)) {
|
|
290
|
+
for (const block of content) {
|
|
291
|
+
const b = asRecord(block);
|
|
292
|
+
if (!b || b.type !== "tool_use")
|
|
293
|
+
continue;
|
|
294
|
+
this.startTool(String(b.id ?? ""), String(b.name ?? "tool"), asRecord(b.input), out);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
if (type === "user") {
|
|
300
|
+
const content = messageContent(ev);
|
|
301
|
+
if (!Array.isArray(content))
|
|
302
|
+
return out;
|
|
303
|
+
for (const block of content) {
|
|
304
|
+
if (!block || typeof block !== "object")
|
|
305
|
+
continue;
|
|
306
|
+
const b = block;
|
|
307
|
+
if (b.type !== "tool_result")
|
|
308
|
+
continue;
|
|
309
|
+
const callId = String(b.tool_use_id ?? "");
|
|
310
|
+
if (!callId)
|
|
311
|
+
continue;
|
|
312
|
+
const pending = this.pendingTools.get(callId);
|
|
313
|
+
this.pendingTools.delete(callId);
|
|
314
|
+
const name = pending?.name || "tool";
|
|
315
|
+
const path = pending?.path;
|
|
316
|
+
const resultText = extractToolResultContent(b.content);
|
|
317
|
+
const startDetail = toolDetail(name, pending?.args, path);
|
|
318
|
+
const detail = formatToolResultDetail(name, pending?.args, resultText ? { success: { content: resultText } } : undefined, resultText || startDetail || path || "Done");
|
|
319
|
+
const diffPatch = isEditTool(name) && pending?.args
|
|
320
|
+
? diffFromToolArgs(name, pending.args)
|
|
321
|
+
: undefined;
|
|
322
|
+
const todos = pending?.args ? todosFromToolArgs(pending.args) : [];
|
|
323
|
+
const questions = pending?.args
|
|
324
|
+
? parseQuestionToolArgs(pending.args)
|
|
325
|
+
: [];
|
|
326
|
+
const integration = claudeToolIntegration(name, pending?.args);
|
|
327
|
+
out.push({
|
|
328
|
+
kind: "tool_done",
|
|
329
|
+
callId,
|
|
330
|
+
name: todos.length && !isTodoTool(name)
|
|
331
|
+
? "todo"
|
|
332
|
+
: isQuestionTool(name)
|
|
333
|
+
? "AskUserQuestion"
|
|
334
|
+
: name,
|
|
335
|
+
detail: todos.length
|
|
336
|
+
? `${todos.length} todo${todos.length === 1 ? "" : "s"} · ${todoStatusSummary(todos)}`
|
|
337
|
+
: detail.slice(0, TOOL_RESULT_DETAIL_LIMIT),
|
|
338
|
+
path,
|
|
339
|
+
diffPatch: diffPatch || undefined,
|
|
340
|
+
todos: todos.length ? todos : undefined,
|
|
341
|
+
questions: questions.length ? questions : undefined,
|
|
342
|
+
...(integration ? { integration } : {}),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
if (type === "error") {
|
|
348
|
+
if (ctx.toolsOnly)
|
|
349
|
+
return out;
|
|
350
|
+
ctx.gotTerminalEvent.value = true;
|
|
351
|
+
out.push({
|
|
352
|
+
kind: "error",
|
|
353
|
+
message: stringifyUnknown(ev.error) ||
|
|
354
|
+
stringifyUnknown(ev.message) ||
|
|
355
|
+
stringifyUnknown(ev.result) ||
|
|
356
|
+
ctx.stderr ||
|
|
357
|
+
"Claude Code error",
|
|
358
|
+
});
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
if (type === "result") {
|
|
362
|
+
if (ctx.toolsOnly) {
|
|
363
|
+
if (ev.session_id) {
|
|
364
|
+
out.push({ kind: "session", sessionId: String(ev.session_id) });
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
ctx.gotTerminalEvent.value = true;
|
|
369
|
+
if (ev.session_id) {
|
|
370
|
+
out.push({ kind: "session", sessionId: String(ev.session_id) });
|
|
371
|
+
}
|
|
372
|
+
const subtype = String(ev.subtype ?? "");
|
|
373
|
+
const isError = Boolean(ev.is_error) ||
|
|
374
|
+
subtype.startsWith("error") ||
|
|
375
|
+
subtype === "error_during_execution" ||
|
|
376
|
+
subtype === "error_max_turns";
|
|
377
|
+
if (isError) {
|
|
378
|
+
const msg = stringifyUnknown(ev.result) ||
|
|
379
|
+
stringifyUnknown(ev.errors) ||
|
|
380
|
+
stringifyUnknown(ev.error) ||
|
|
381
|
+
ctx.stderr ||
|
|
382
|
+
`Claude Code error (${subtype || "unknown"})`;
|
|
383
|
+
out.push({ kind: "error", message: msg });
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
out.push({
|
|
387
|
+
kind: "done",
|
|
388
|
+
result: stringifyUnknown(ev.result) ||
|
|
389
|
+
ctx.assistantBuf.value ||
|
|
390
|
+
"",
|
|
391
|
+
usage: claudeResultUsage(ev),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
parseContentBlockEvent(event, ctx, out) {
|
|
398
|
+
const eventType = String(event.type ?? "");
|
|
399
|
+
const index = blockIndex(event);
|
|
400
|
+
if (eventType === "content_block_start") {
|
|
401
|
+
const block = asRecord(event.content_block);
|
|
402
|
+
if (!block)
|
|
403
|
+
return;
|
|
404
|
+
if (block.type === "thinking" || block.type === "reasoning") {
|
|
405
|
+
ctx.thinkingBuf = { value: "" };
|
|
406
|
+
const seed = extractThinkingText(block);
|
|
407
|
+
if (seed && !ctx.toolsOnly)
|
|
408
|
+
this.emitThinking(ctx, seed, "append", false, out);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (block.type !== "tool_use")
|
|
412
|
+
return;
|
|
413
|
+
const callId = String(block.id ?? "");
|
|
414
|
+
if (!callId)
|
|
415
|
+
return;
|
|
416
|
+
const name = String(block.name ?? "tool");
|
|
417
|
+
const args = asRecord(block.input);
|
|
418
|
+
this.streamingBlocks.set(index, {
|
|
419
|
+
callId,
|
|
420
|
+
name,
|
|
421
|
+
inputJson: args && Object.keys(args).length > 0 ? JSON.stringify(args) : "",
|
|
422
|
+
});
|
|
423
|
+
this.startTool(callId, name, args, out);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (eventType === "content_block_delta") {
|
|
427
|
+
const delta = asRecord(event.delta);
|
|
428
|
+
if (!delta)
|
|
429
|
+
return;
|
|
430
|
+
if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
431
|
+
if (ctx.toolsOnly)
|
|
432
|
+
return;
|
|
433
|
+
this.currentMessageId ??= `anon-${(this.anonymousMessages += 1)}`;
|
|
434
|
+
if (this.setMessageText(ctx, this.currentMessageId, delta.text, "append")) {
|
|
435
|
+
out.push({
|
|
436
|
+
kind: "assistant_delta",
|
|
437
|
+
text: ctx.assistantBuf.value,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if ((delta.type === "thinking_delta" || delta.type === "reasoning_delta") &&
|
|
443
|
+
!ctx.toolsOnly) {
|
|
444
|
+
const chunk = typeof delta.thinking === "string"
|
|
445
|
+
? delta.thinking
|
|
446
|
+
: typeof delta.text === "string"
|
|
447
|
+
? delta.text
|
|
448
|
+
: extractThinkingText(delta);
|
|
449
|
+
if (chunk)
|
|
450
|
+
this.emitThinking(ctx, chunk, "append", false, out);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (delta.type === "input_json_delta" &&
|
|
454
|
+
typeof delta.partial_json === "string") {
|
|
455
|
+
const streaming = this.streamingBlocks.get(index);
|
|
456
|
+
if (!streaming)
|
|
457
|
+
return;
|
|
458
|
+
streaming.inputJson += delta.partial_json;
|
|
459
|
+
const args = tryParseToolArgs(streaming.inputJson);
|
|
460
|
+
if (args) {
|
|
461
|
+
this.startTool(streaming.callId, streaming.name, args, out);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (eventType === "content_block_stop") {
|
|
467
|
+
const streaming = this.streamingBlocks.get(index);
|
|
468
|
+
if (!streaming)
|
|
469
|
+
return;
|
|
470
|
+
this.streamingBlocks.delete(index);
|
|
471
|
+
const args = tryParseToolArgs(streaming.inputJson);
|
|
472
|
+
if (args) {
|
|
473
|
+
this.startTool(streaming.callId, streaming.name, args, out);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
emitThinking(ctx, incoming, mode, final, out) {
|
|
478
|
+
if (!incoming)
|
|
479
|
+
return;
|
|
480
|
+
ctx.thinkingBuf ??= { value: "" };
|
|
481
|
+
const next = mode === "replace"
|
|
482
|
+
? incoming
|
|
483
|
+
: ctx.thinkingBuf.value + incoming;
|
|
484
|
+
if (next === ctx.thinkingBuf.value)
|
|
485
|
+
return;
|
|
486
|
+
ctx.thinkingBuf.value = next;
|
|
487
|
+
out.push({
|
|
488
|
+
kind: final ? "thinking_final" : "thinking_delta",
|
|
489
|
+
text: next,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Record a tool_use and emit tool_start once. Re-emits when args go from
|
|
494
|
+
* empty `{}` (content_block_start placeholder) to a real path/command so
|
|
495
|
+
* the existing chat row can pick up a useful title.
|
|
496
|
+
*
|
|
497
|
+
* Nested Task/subagent tools (parent_tool_use_id set) still emit as
|
|
498
|
+
* `tool_start` / later `tool_done` so every Claude Code tool appears as a
|
|
499
|
+
* room tool card.
|
|
500
|
+
*/
|
|
501
|
+
startTool(callId, name, args, out) {
|
|
502
|
+
if (!callId)
|
|
503
|
+
return;
|
|
504
|
+
const path = toolPathFromArgs(args);
|
|
505
|
+
const existing = this.pendingTools.get(callId);
|
|
506
|
+
const hasArgs = Boolean(args && Object.keys(args).length > 0);
|
|
507
|
+
const hadArgs = Boolean(existing?.args && Object.keys(existing.args).length > 0);
|
|
508
|
+
const mergedArgs = hasArgs ? args : existing?.args;
|
|
509
|
+
const mergedPath = path || existing?.path;
|
|
510
|
+
const mergedName = existing?.name || name;
|
|
511
|
+
this.pendingTools.set(callId, {
|
|
512
|
+
name: mergedName,
|
|
513
|
+
path: mergedPath,
|
|
514
|
+
args: mergedArgs,
|
|
515
|
+
});
|
|
516
|
+
const alreadyStarted = Boolean(existing);
|
|
517
|
+
const detailImproved = alreadyStarted && hasArgs && !hadArgs;
|
|
518
|
+
if (alreadyStarted && !detailImproved)
|
|
519
|
+
return;
|
|
520
|
+
const todos = mergedArgs ? todosFromToolArgs(mergedArgs) : [];
|
|
521
|
+
const questions = mergedArgs ? parseQuestionToolArgs(mergedArgs) : [];
|
|
522
|
+
const integration = claudeToolIntegration(mergedName, mergedArgs);
|
|
523
|
+
// Always surface as a first-class tool card (including nested Task tools).
|
|
524
|
+
out.push({
|
|
525
|
+
kind: "tool_start",
|
|
526
|
+
callId,
|
|
527
|
+
name: todos.length && !isTodoTool(mergedName)
|
|
528
|
+
? "todo"
|
|
529
|
+
: isQuestionTool(mergedName)
|
|
530
|
+
? "AskUserQuestion"
|
|
531
|
+
: mergedName,
|
|
532
|
+
detail: toolDetail(mergedName, mergedArgs, mergedPath),
|
|
533
|
+
path: mergedPath,
|
|
534
|
+
todos: todos.length ? todos : undefined,
|
|
535
|
+
questions: questions.length ? questions : undefined,
|
|
536
|
+
...(integration ? { integration } : {}),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
export const claudeCodeBackend = new ClaudeCodeBackend();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BuildArgsOptions, NormalizedAgentEvent, ParseLineContext, WorkerBackend } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Codex CLI headless backend.
|
|
4
|
+
* Spawns: `codex exec --json --dangerously-bypass-approvals-and-sandbox …`
|
|
5
|
+
*
|
|
6
|
+
* Event stream is JSONL (`thread.*`, `item.*`, `turn.*`).
|
|
7
|
+
*/
|
|
8
|
+
export declare class CodexBackend implements WorkerBackend {
|
|
9
|
+
readonly kind: "codex";
|
|
10
|
+
readonly available = true;
|
|
11
|
+
readonly command = "codex";
|
|
12
|
+
buildArgs(opts: BuildArgsOptions): string[];
|
|
13
|
+
parseLine(json: unknown, ctx?: ParseLineContext): NormalizedAgentEvent[];
|
|
14
|
+
}
|
|
15
|
+
export declare const codexBackend: CodexBackend;
|