linkshell-cli 0.2.99 → 0.2.101
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/dist/cli/src/index.js +1 -0
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/providers.js +49 -3
- package/dist/cli/src/providers.js.map +1 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +1 -0
- package/dist/cli/src/runtime/acp/acp-client.js +17 -3
- package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +1 -0
- package/dist/cli/src/runtime/acp/agent-session.js +73 -19
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +6 -1
- package/dist/cli/src/runtime/acp/agent-workspace.js +349 -58
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +51 -0
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +318 -0
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -0
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +1 -1
- package/dist/cli/src/runtime/acp/provider-resolver.js +22 -2
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.js +1 -0
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +1885 -265
- package/dist/shared-protocol/src/index.js +36 -10
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +8 -5
- package/src/index.ts +1 -0
- package/src/providers.ts +50 -3
- package/src/runtime/acp/acp-client.ts +18 -3
- package/src/runtime/acp/agent-session.ts +68 -14
- package/src/runtime/acp/agent-workspace.ts +378 -55
- package/src/runtime/acp/claude-sdk-client.ts +372 -0
- package/src/runtime/acp/provider-resolver.ts +24 -3
- package/src/runtime/bridge-session.ts +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { readdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import type { AgentFraming, AgentProtocol } from "./provider-resolver.js";
|
|
5
|
+
|
|
6
|
+
type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
|
|
7
|
+
|
|
8
|
+
interface ClaudeContentBlock {
|
|
9
|
+
type?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
thinking?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
input?: Record<string, unknown>;
|
|
15
|
+
tool_use_id?: string;
|
|
16
|
+
content?: unknown;
|
|
17
|
+
is_error?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ClaudeSdkMessage {
|
|
21
|
+
type?: string;
|
|
22
|
+
subtype?: string;
|
|
23
|
+
message?: Record<string, unknown>;
|
|
24
|
+
session_id?: string;
|
|
25
|
+
uuid?: string;
|
|
26
|
+
result?: string;
|
|
27
|
+
total_cost_usd?: number;
|
|
28
|
+
duration_ms?: number;
|
|
29
|
+
usage?: unknown;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type ClaudeQuery = (input: Record<string, unknown>) => AsyncIterable<ClaudeSdkMessage>;
|
|
34
|
+
|
|
35
|
+
function id(prefix: string): string {
|
|
36
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function projectHash(cwd: string): string {
|
|
40
|
+
return (
|
|
41
|
+
"-" +
|
|
42
|
+
resolve(cwd)
|
|
43
|
+
.replace(/\/$/, "")
|
|
44
|
+
.replace(/\//g, "-")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractToolResultText(content: unknown): string {
|
|
49
|
+
if (typeof content === "string") return content;
|
|
50
|
+
if (Array.isArray(content)) {
|
|
51
|
+
return content
|
|
52
|
+
.map((part) => {
|
|
53
|
+
if (typeof part === "string") return part;
|
|
54
|
+
if (part && typeof part === "object" && "text" in part && typeof (part as { text: unknown }).text === "string") {
|
|
55
|
+
return (part as { text: string }).text;
|
|
56
|
+
}
|
|
57
|
+
return "";
|
|
58
|
+
})
|
|
59
|
+
.join("");
|
|
60
|
+
}
|
|
61
|
+
if (content && typeof content === "object" && "text" in content && typeof (content as { text: unknown }).text === "string") {
|
|
62
|
+
return (content as { text: string }).text;
|
|
63
|
+
}
|
|
64
|
+
return String(content ?? "");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isRealClaudeSessionId(value: string | undefined): value is string {
|
|
68
|
+
return Boolean(value && !value.startsWith("agent-session-"));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function outcomeFromPermissionResponse(value: unknown): "allow" | "deny" {
|
|
72
|
+
const raw = value && typeof value === "object" ? value as Record<string, unknown> : {};
|
|
73
|
+
if (raw.behavior === "allow") return "allow";
|
|
74
|
+
if (raw.behavior === "deny") return "deny";
|
|
75
|
+
const outcome = raw.outcome && typeof raw.outcome === "object"
|
|
76
|
+
? (raw.outcome as Record<string, unknown>).outcome
|
|
77
|
+
: raw.outcome;
|
|
78
|
+
if (outcome === "selected") {
|
|
79
|
+
const optionId = raw.outcome && typeof raw.outcome === "object"
|
|
80
|
+
? String((raw.outcome as Record<string, unknown>).optionId ?? "")
|
|
81
|
+
: "";
|
|
82
|
+
return optionId.toLowerCase().includes("allow") ? "allow" : "deny";
|
|
83
|
+
}
|
|
84
|
+
return outcome === "allow" ? "allow" : "deny";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class ClaudeSdkClient {
|
|
88
|
+
private query: ClaudeQuery | undefined;
|
|
89
|
+
private claudeSessionId: string | undefined;
|
|
90
|
+
private abortController: AbortController | undefined;
|
|
91
|
+
private permissionWaiters = new Map<string, (outcome: "allow" | "deny") => void>();
|
|
92
|
+
|
|
93
|
+
constructor(
|
|
94
|
+
private readonly input: {
|
|
95
|
+
command: string;
|
|
96
|
+
protocol: AgentProtocol;
|
|
97
|
+
framing: AgentFraming;
|
|
98
|
+
cwd: string;
|
|
99
|
+
onNotification: (method: string, params: unknown) => void;
|
|
100
|
+
onRequest: (method: string, params: unknown) => Promise<unknown> | unknown;
|
|
101
|
+
onExit: (message: string) => void;
|
|
102
|
+
},
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
async initialize(): Promise<unknown> {
|
|
106
|
+
try {
|
|
107
|
+
const dynamicImport = new Function("specifier", "return import(specifier)") as (specifier: string) => Promise<unknown>;
|
|
108
|
+
const mod = await dynamicImport("@anthropic-ai/claude-agent-sdk");
|
|
109
|
+
const query = (mod as { query?: unknown }).query;
|
|
110
|
+
if (typeof query !== "function") {
|
|
111
|
+
throw new Error("Claude Agent SDK does not export query()");
|
|
112
|
+
}
|
|
113
|
+
this.query = query as ClaudeQuery;
|
|
114
|
+
return { status: "ok", protocol: "claude-agent-sdk" };
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Claude Agent SDK is not available: ${error instanceof Error ? error.message : String(error)}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async newSession(input: { cwd: string; mcpServers?: unknown }): Promise<unknown> {
|
|
123
|
+
this.claudeSessionId = undefined;
|
|
124
|
+
return { sessionId: undefined, status: "ready", cwd: input.cwd };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async loadSession(input: { sessionId: string; cwd: string; mcpServers?: unknown }): Promise<unknown> {
|
|
128
|
+
this.claudeSessionId = input.sessionId;
|
|
129
|
+
return { sessionId: input.sessionId, status: "loaded", cwd: input.cwd };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async prompt(input: {
|
|
133
|
+
sessionId?: string;
|
|
134
|
+
content: unknown[];
|
|
135
|
+
clientMessageId: string;
|
|
136
|
+
model?: string;
|
|
137
|
+
reasoningEffort?: string;
|
|
138
|
+
permissionMode?: AgentPermissionMode;
|
|
139
|
+
cwd: string;
|
|
140
|
+
}): Promise<unknown> {
|
|
141
|
+
if (!this.query) await this.initialize();
|
|
142
|
+
if (!this.query) throw new Error("Claude Agent SDK is not initialized");
|
|
143
|
+
|
|
144
|
+
this.abortController?.abort();
|
|
145
|
+
const abortController = new AbortController();
|
|
146
|
+
this.abortController = abortController;
|
|
147
|
+
|
|
148
|
+
const prompt = (input.content as Array<{ type?: string; text?: string; data?: string; mimeType?: string }>)
|
|
149
|
+
.map((block) => {
|
|
150
|
+
if (block.type === "image") return `[${block.mimeType ?? "image"} attachment]`;
|
|
151
|
+
return block.text ?? "";
|
|
152
|
+
})
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join("\n");
|
|
155
|
+
|
|
156
|
+
const sdkOptions: Record<string, unknown> = {
|
|
157
|
+
cwd: input.cwd ?? this.input.cwd,
|
|
158
|
+
abortController,
|
|
159
|
+
canUseTool: async (toolName: string, toolInput: unknown) => {
|
|
160
|
+
if (input.permissionMode === "full_access") return { behavior: "allow" };
|
|
161
|
+
if (input.permissionMode === "read_only" && ["Write", "Edit", "MultiEdit", "Bash"].includes(toolName)) {
|
|
162
|
+
return { behavior: "deny", message: "Read-only mode is active." };
|
|
163
|
+
}
|
|
164
|
+
const requestId = id("claude-perm");
|
|
165
|
+
const response = await this.input.onRequest("claude/requestApproval", {
|
|
166
|
+
requestId,
|
|
167
|
+
sessionId: this.claudeSessionId,
|
|
168
|
+
toolCall: {
|
|
169
|
+
toolName,
|
|
170
|
+
input: toolInput,
|
|
171
|
+
},
|
|
172
|
+
context: `Claude wants to use ${toolName}`,
|
|
173
|
+
options: [
|
|
174
|
+
{ id: "deny", label: "拒绝", kind: "deny" },
|
|
175
|
+
{ id: "allow_once", label: "允许一次", kind: "allow" },
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
return { behavior: outcomeFromPermissionResponse(response) === "allow" ? "allow" : "deny" };
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
if (input.model) sdkOptions.model = input.model;
|
|
182
|
+
if (isRealClaudeSessionId(input.sessionId ?? this.claudeSessionId)) {
|
|
183
|
+
sdkOptions.resume = input.sessionId ?? this.claudeSessionId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const toolNames = new Map<string, string>();
|
|
187
|
+
let currentToolId: string | undefined;
|
|
188
|
+
let currentToolName: string | undefined;
|
|
189
|
+
let currentMessageId: string | undefined;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
for await (const message of this.query({ prompt, options: sdkOptions })) {
|
|
193
|
+
if (abortController.signal.aborted) break;
|
|
194
|
+
this.handleSdkMessage(message, {
|
|
195
|
+
cwd: input.cwd ?? this.input.cwd,
|
|
196
|
+
toolNames,
|
|
197
|
+
currentToolId: (value?: string | null) => {
|
|
198
|
+
if (value !== undefined) currentToolId = value ?? undefined;
|
|
199
|
+
return currentToolId;
|
|
200
|
+
},
|
|
201
|
+
currentToolName: (value?: string | null) => {
|
|
202
|
+
if (value !== undefined) currentToolName = value ?? undefined;
|
|
203
|
+
return currentToolName;
|
|
204
|
+
},
|
|
205
|
+
currentMessageId: (value?: string | null) => {
|
|
206
|
+
if (value !== undefined) currentMessageId = value ?? undefined;
|
|
207
|
+
return currentMessageId;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return { sessionId: this.claudeSessionId, status: abortController.signal.aborted ? "cancelled" : "completed" };
|
|
212
|
+
} finally {
|
|
213
|
+
if (this.abortController === abortController) this.abortController = undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
cancel(input: { sessionId?: string; turnId?: string }): void {
|
|
218
|
+
this.abortController?.abort();
|
|
219
|
+
this.abortController = undefined;
|
|
220
|
+
for (const [, resolve] of this.permissionWaiters) resolve("deny");
|
|
221
|
+
this.permissionWaiters.clear();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
respondPermission(input: { sessionId?: string; requestId: string; outcome: "allow" | "deny"; optionId?: string }): void {
|
|
225
|
+
const waiter = this.permissionWaiters.get(input.requestId);
|
|
226
|
+
if (!waiter) return;
|
|
227
|
+
this.permissionWaiters.delete(input.requestId);
|
|
228
|
+
waiter(input.outcome);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async listSessions(): Promise<unknown> {
|
|
232
|
+
const projectDir = join(homedir(), ".claude", "projects", projectHash(this.input.cwd));
|
|
233
|
+
if (!existsSync(projectDir)) return { sessions: [] };
|
|
234
|
+
const sessions: Array<{ id: string; cwd: string; lastModified: number }> = [];
|
|
235
|
+
try {
|
|
236
|
+
for (const entry of readdirSync(projectDir)) {
|
|
237
|
+
if (entry.endsWith(".jsonl")) {
|
|
238
|
+
sessions.push({
|
|
239
|
+
id: entry.replace(".jsonl", ""),
|
|
240
|
+
cwd: this.input.cwd,
|
|
241
|
+
lastModified: 0,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// Ignore unreadable Claude project storage.
|
|
247
|
+
}
|
|
248
|
+
return { sessions };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
stop(): void {
|
|
252
|
+
this.cancel({});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private handleSdkMessage(
|
|
256
|
+
event: ClaudeSdkMessage,
|
|
257
|
+
state: {
|
|
258
|
+
cwd: string;
|
|
259
|
+
toolNames: Map<string, string>;
|
|
260
|
+
currentToolId: (value?: string | null) => string | undefined;
|
|
261
|
+
currentToolName: (value?: string | null) => string | undefined;
|
|
262
|
+
currentMessageId: (value?: string | null) => string | undefined;
|
|
263
|
+
},
|
|
264
|
+
): void {
|
|
265
|
+
switch (event.type) {
|
|
266
|
+
case "system": {
|
|
267
|
+
if (event.subtype === "init" && typeof event.session_id === "string") {
|
|
268
|
+
this.claudeSessionId = event.session_id;
|
|
269
|
+
this.input.onNotification("thread/started", {
|
|
270
|
+
sessionId: event.session_id,
|
|
271
|
+
threadId: event.session_id,
|
|
272
|
+
});
|
|
273
|
+
this.input.onNotification("initialized", {
|
|
274
|
+
sessionId: event.session_id,
|
|
275
|
+
threadId: event.session_id,
|
|
276
|
+
cwd: event.cwd ?? state.cwd,
|
|
277
|
+
model: event.model,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case "assistant": {
|
|
283
|
+
const rawMessage = event.message;
|
|
284
|
+
const content = (Array.isArray(rawMessage?.content) ? rawMessage.content : []) as ClaudeContentBlock[];
|
|
285
|
+
state.currentMessageId(null);
|
|
286
|
+
for (const block of content) {
|
|
287
|
+
if (block.type === "thinking") {
|
|
288
|
+
this.input.onNotification("item/completed", {
|
|
289
|
+
sessionId: this.claudeSessionId,
|
|
290
|
+
item: {
|
|
291
|
+
id: event.uuid ?? id("thinking"),
|
|
292
|
+
type: "thinking",
|
|
293
|
+
text: block.thinking,
|
|
294
|
+
status: "completed",
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
} else if (block.type === "text") {
|
|
298
|
+
const messageId = (typeof rawMessage?.id === "string" ? rawMessage.id : undefined) ?? event.uuid ?? id("msg");
|
|
299
|
+
state.currentMessageId(messageId);
|
|
300
|
+
this.input.onNotification("item/agentMessage/delta", {
|
|
301
|
+
sessionId: this.claudeSessionId,
|
|
302
|
+
itemId: messageId,
|
|
303
|
+
delta: block.text,
|
|
304
|
+
});
|
|
305
|
+
} else if (block.type === "tool_use") {
|
|
306
|
+
const toolId = block.id ?? id("tool");
|
|
307
|
+
const toolName = block.name ?? "tool";
|
|
308
|
+
state.currentToolId(toolId);
|
|
309
|
+
state.currentToolName(toolName);
|
|
310
|
+
state.toolNames.set(toolId, toolName);
|
|
311
|
+
this.input.onNotification("item/started", {
|
|
312
|
+
sessionId: this.claudeSessionId,
|
|
313
|
+
item: {
|
|
314
|
+
id: toolId,
|
|
315
|
+
type: toolName === "Bash" ? "commandExecution" : toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit" ? "fileChange" : "toolCall",
|
|
316
|
+
toolName,
|
|
317
|
+
tool: toolName,
|
|
318
|
+
input: block.input,
|
|
319
|
+
command: block.input?.command as string | undefined,
|
|
320
|
+
cwd: block.input?.cwd as string | undefined ?? state.cwd,
|
|
321
|
+
status: "running",
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const messageId = state.currentMessageId();
|
|
327
|
+
if (messageId) {
|
|
328
|
+
this.input.onNotification("item/completed", {
|
|
329
|
+
sessionId: this.claudeSessionId,
|
|
330
|
+
item: { id: messageId, type: "agentMessage", status: "completed" },
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case "user": {
|
|
336
|
+
const rawMessage = event.message;
|
|
337
|
+
const content = (Array.isArray(rawMessage?.content) ? rawMessage.content : []) as ClaudeContentBlock[];
|
|
338
|
+
for (const block of content) {
|
|
339
|
+
if (block.type !== "tool_result") continue;
|
|
340
|
+
const toolId = block.tool_use_id ?? state.currentToolId();
|
|
341
|
+
const toolName = (toolId ? state.toolNames.get(toolId) : undefined) ?? state.currentToolName();
|
|
342
|
+
const output = extractToolResultText(block.content);
|
|
343
|
+
this.input.onNotification("item/completed", {
|
|
344
|
+
sessionId: this.claudeSessionId,
|
|
345
|
+
item: {
|
|
346
|
+
id: toolId ?? id("tool"),
|
|
347
|
+
type: "toolCall",
|
|
348
|
+
toolName,
|
|
349
|
+
tool: toolName,
|
|
350
|
+
status: block.is_error ? "failed" : "completed",
|
|
351
|
+
output,
|
|
352
|
+
aggregatedOutput: output,
|
|
353
|
+
isError: block.is_error === true,
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case "result": {
|
|
360
|
+
if (typeof event.session_id === "string") this.claudeSessionId = event.session_id;
|
|
361
|
+
this.input.onNotification("turn/completed", {
|
|
362
|
+
sessionId: this.claudeSessionId,
|
|
363
|
+
durationMs: event.duration_ms,
|
|
364
|
+
totalCostUsd: event.total_cost_usd,
|
|
365
|
+
usage: event.usage,
|
|
366
|
+
isError: event.subtype === "error",
|
|
367
|
+
});
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { existsSync } from "node:fs";
|
|
4
5
|
|
|
5
6
|
export type AgentProvider = "codex" | "claude" | "custom";
|
|
6
|
-
export type AgentProtocol = "acp" | "codex-app-server" | "claude-stream-json";
|
|
7
|
+
export type AgentProtocol = "acp" | "codex-app-server" | "claude-agent-sdk" | "claude-stream-json";
|
|
7
8
|
export type AgentFraming = "content-length" | "newline";
|
|
8
9
|
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
9
12
|
export interface AgentCommandConfig {
|
|
10
13
|
command: string;
|
|
11
14
|
provider: AgentProvider;
|
|
@@ -20,11 +23,12 @@ export function resolveAgentCommand(input: {
|
|
|
20
23
|
const explicit = input.command?.trim();
|
|
21
24
|
if (explicit) {
|
|
22
25
|
const isCodexAppServer = /\bcodex\b/.test(explicit) && /\bapp-server\b/.test(explicit);
|
|
26
|
+
const isClaudeCli = input.provider === "claude" && /\bclaude\b/.test(explicit);
|
|
23
27
|
return {
|
|
24
28
|
provider: input.provider,
|
|
25
29
|
command: explicit,
|
|
26
|
-
protocol: isCodexAppServer ? "codex-app-server" : "acp",
|
|
27
|
-
framing: isCodexAppServer ? "newline" : "content-length",
|
|
30
|
+
protocol: isCodexAppServer ? "codex-app-server" : isClaudeCli ? "claude-stream-json" : "acp",
|
|
31
|
+
framing: isCodexAppServer || isClaudeCli ? "newline" : "content-length",
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -38,6 +42,14 @@ export function resolveAgentCommand(input: {
|
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
if (input.provider === "claude") {
|
|
45
|
+
if (process.env.LINKSHELL_CLAUDE_PROVIDER !== "stream-json" && hasPackage("@anthropic-ai/claude-agent-sdk")) {
|
|
46
|
+
return {
|
|
47
|
+
provider: "claude",
|
|
48
|
+
command: "claude-agent-sdk",
|
|
49
|
+
protocol: "claude-agent-sdk",
|
|
50
|
+
framing: "newline",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
41
53
|
return {
|
|
42
54
|
provider: "claude",
|
|
43
55
|
command: "claude --print --output-format stream-json --input-format stream-json --verbose --permission-mode bypassPermissions",
|
|
@@ -50,6 +62,15 @@ export function resolveAgentCommand(input: {
|
|
|
50
62
|
return null;
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
function hasPackage(name: string): boolean {
|
|
66
|
+
try {
|
|
67
|
+
require.resolve(name);
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
function resolveBinary(bin: string): string | null {
|
|
54
75
|
// 1. Try which (PATH lookup)
|
|
55
76
|
try {
|
|
@@ -762,6 +762,7 @@ export class BridgeSession {
|
|
|
762
762
|
case "agent.v2.prompt":
|
|
763
763
|
case "agent.v2.cancel":
|
|
764
764
|
case "agent.v2.permission.respond":
|
|
765
|
+
case "agent.v2.structured_input.respond":
|
|
765
766
|
case "agent.v2.snapshot.request": {
|
|
766
767
|
if (!this.agentWorkspace) {
|
|
767
768
|
this.send(
|