@posthog/agent 2.0.0 → 2.0.2

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.
Files changed (131) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +221 -219
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
  4. package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
  5. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
  6. package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
  7. package/dist/adapters/claude/permissions/permission-options.js +117 -0
  8. package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
  9. package/dist/adapters/claude/questions/utils.d.ts +132 -0
  10. package/dist/adapters/claude/questions/utils.js +63 -0
  11. package/dist/adapters/claude/questions/utils.js.map +1 -0
  12. package/dist/adapters/claude/tools.d.ts +18 -0
  13. package/dist/adapters/claude/tools.js +95 -0
  14. package/dist/adapters/claude/tools.js.map +1 -0
  15. package/dist/agent-DBQY1BfC.d.ts +123 -0
  16. package/dist/agent.d.ts +5 -0
  17. package/dist/agent.js +3656 -0
  18. package/dist/agent.js.map +1 -0
  19. package/dist/claude-cli/cli.js +3695 -2746
  20. package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
  21. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
  22. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
  23. package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
  24. package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
  25. package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
  26. package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
  27. package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
  28. package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
  29. package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
  30. package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
  31. package/dist/gateway-models.d.ts +24 -0
  32. package/dist/gateway-models.js +93 -0
  33. package/dist/gateway-models.js.map +1 -0
  34. package/dist/index.d.ts +170 -1157
  35. package/dist/index.js +9373 -5135
  36. package/dist/index.js.map +1 -1
  37. package/dist/logger-DDBiMOOD.d.ts +24 -0
  38. package/dist/posthog-api.d.ts +40 -0
  39. package/dist/posthog-api.js +175 -0
  40. package/dist/posthog-api.js.map +1 -0
  41. package/dist/server/agent-server.d.ts +41 -0
  42. package/dist/server/agent-server.js +10503 -0
  43. package/dist/server/agent-server.js.map +1 -0
  44. package/dist/server/bin.d.ts +1 -0
  45. package/dist/server/bin.js +10558 -0
  46. package/dist/server/bin.js.map +1 -0
  47. package/dist/types.d.ts +129 -0
  48. package/dist/types.js +1 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +65 -13
  51. package/src/acp-extensions.ts +98 -16
  52. package/src/adapters/acp-connection.ts +494 -0
  53. package/src/adapters/base-acp-agent.ts +150 -0
  54. package/src/adapters/claude/claude-agent.ts +596 -0
  55. package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
  56. package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
  57. package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
  58. package/src/adapters/claude/hooks.ts +64 -0
  59. package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
  60. package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
  61. package/src/adapters/claude/permissions/permission-options.ts +103 -0
  62. package/src/adapters/claude/plan/utils.ts +56 -0
  63. package/src/adapters/claude/questions/utils.ts +92 -0
  64. package/src/adapters/claude/session/commands.ts +38 -0
  65. package/src/adapters/claude/session/mcp-config.ts +37 -0
  66. package/src/adapters/claude/session/models.ts +12 -0
  67. package/src/adapters/claude/session/options.ts +236 -0
  68. package/src/adapters/claude/tool-meta.ts +143 -0
  69. package/src/adapters/claude/tools.ts +53 -688
  70. package/src/adapters/claude/types.ts +61 -0
  71. package/src/adapters/codex/spawn.ts +130 -0
  72. package/src/agent.ts +96 -587
  73. package/src/execution-mode.ts +43 -0
  74. package/src/gateway-models.ts +135 -0
  75. package/src/index.ts +79 -0
  76. package/src/otel-log-writer.test.ts +105 -0
  77. package/src/otel-log-writer.ts +94 -0
  78. package/src/posthog-api.ts +75 -235
  79. package/src/resume.ts +115 -0
  80. package/src/sagas/apply-snapshot-saga.test.ts +690 -0
  81. package/src/sagas/apply-snapshot-saga.ts +88 -0
  82. package/src/sagas/capture-tree-saga.test.ts +892 -0
  83. package/src/sagas/capture-tree-saga.ts +141 -0
  84. package/src/sagas/resume-saga.test.ts +558 -0
  85. package/src/sagas/resume-saga.ts +332 -0
  86. package/src/sagas/test-fixtures.ts +250 -0
  87. package/src/server/agent-server.test.ts +220 -0
  88. package/src/server/agent-server.ts +748 -0
  89. package/src/server/bin.ts +88 -0
  90. package/src/server/jwt.ts +65 -0
  91. package/src/server/schemas.ts +47 -0
  92. package/src/server/types.ts +13 -0
  93. package/src/server/utils/retry.test.ts +122 -0
  94. package/src/server/utils/retry.ts +61 -0
  95. package/src/server/utils/sse-parser.test.ts +93 -0
  96. package/src/server/utils/sse-parser.ts +46 -0
  97. package/src/session-log-writer.test.ts +140 -0
  98. package/src/session-log-writer.ts +137 -0
  99. package/src/test/assertions.ts +114 -0
  100. package/src/test/controllers/sse-controller.ts +107 -0
  101. package/src/test/fixtures/api.ts +111 -0
  102. package/src/test/fixtures/config.ts +33 -0
  103. package/src/test/fixtures/notifications.ts +92 -0
  104. package/src/test/mocks/claude-sdk.ts +251 -0
  105. package/src/test/mocks/msw-handlers.ts +48 -0
  106. package/src/test/setup.ts +114 -0
  107. package/src/test/wait.ts +41 -0
  108. package/src/tree-tracker.ts +173 -0
  109. package/src/types.ts +54 -137
  110. package/src/utils/acp-content.ts +58 -0
  111. package/src/utils/async-mutex.test.ts +104 -0
  112. package/src/utils/async-mutex.ts +31 -0
  113. package/src/utils/common.ts +15 -0
  114. package/src/utils/gateway.ts +9 -6
  115. package/src/utils/logger.ts +0 -30
  116. package/src/utils/streams.ts +220 -0
  117. package/CLAUDE.md +0 -331
  118. package/src/adapters/claude/claude.ts +0 -1947
  119. package/src/adapters/claude/mcp-server.ts +0 -810
  120. package/src/adapters/claude/utils.ts +0 -267
  121. package/src/adapters/connection.ts +0 -95
  122. package/src/file-manager.ts +0 -273
  123. package/src/git-manager.ts +0 -577
  124. package/src/schemas.ts +0 -241
  125. package/src/session-store.ts +0 -259
  126. package/src/task-manager.ts +0 -163
  127. package/src/todo-manager.ts +0 -180
  128. package/src/tools/registry.ts +0 -134
  129. package/src/tools/types.ts +0 -133
  130. package/src/utils/tapped-stream.ts +0 -60
  131. package/src/worktree-manager.ts +0 -974
@@ -0,0 +1,596 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import {
5
+ type AgentSideConnection,
6
+ type AuthenticateRequest,
7
+ type AvailableCommand,
8
+ type ClientCapabilities,
9
+ type InitializeRequest,
10
+ type InitializeResponse,
11
+ type LoadSessionRequest,
12
+ type LoadSessionResponse,
13
+ type NewSessionRequest,
14
+ type NewSessionResponse,
15
+ type PromptRequest,
16
+ type PromptResponse,
17
+ RequestError,
18
+ type SessionConfigOption,
19
+ type SessionConfigOptionCategory,
20
+ type SessionConfigSelectOption,
21
+ type SetSessionConfigOptionRequest,
22
+ type SetSessionConfigOptionResponse,
23
+ } from "@agentclientprotocol/sdk";
24
+ import {
25
+ type CanUseTool,
26
+ type Options,
27
+ type Query,
28
+ query,
29
+ type SDKMessage,
30
+ type SDKUserMessage,
31
+ } from "@anthropic-ai/claude-agent-sdk";
32
+ import { v7 as uuidv7 } from "uuid";
33
+ import packageJson from "../../../package.json" with { type: "json" };
34
+ import type { SessionContext } from "../../otel-log-writer.js";
35
+ import type { SessionLogWriter } from "../../session-log-writer.js";
36
+ import { unreachable } from "../../utils/common.js";
37
+ import { Logger } from "../../utils/logger.js";
38
+ import { Pushable } from "../../utils/streams.js";
39
+ import { BaseAcpAgent } from "../base-acp-agent.js";
40
+ import { promptToClaude } from "./conversion/acp-to-sdk.js";
41
+ import {
42
+ handleResultMessage,
43
+ handleStreamEvent,
44
+ handleSystemMessage,
45
+ handleUserAssistantMessage,
46
+ } from "./conversion/sdk-to-acp.js";
47
+ import { fetchMcpToolMetadata } from "./mcp/tool-metadata.js";
48
+ import { canUseTool } from "./permissions/permission-handlers.js";
49
+ import { getAvailableSlashCommands } from "./session/commands.js";
50
+ import { parseMcpServers } from "./session/mcp-config.js";
51
+ import { toSdkModelId } from "./session/models.js";
52
+ import {
53
+ buildSessionOptions,
54
+ buildSystemPrompt,
55
+ type ProcessSpawnedInfo,
56
+ } from "./session/options.js";
57
+ import {
58
+ getAvailableModes,
59
+ TWIG_EXECUTION_MODES,
60
+ type TwigExecutionMode,
61
+ } from "./tools.js";
62
+ import type {
63
+ BackgroundTerminal,
64
+ NewSessionMeta,
65
+ Session,
66
+ ToolUseCache,
67
+ } from "./types.js";
68
+
69
+ export interface ClaudeAcpAgentOptions {
70
+ onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
71
+ onProcessExited?: (pid: number) => void;
72
+ }
73
+
74
+ export class ClaudeAcpAgent extends BaseAcpAgent {
75
+ readonly adapterName = "claude";
76
+ declare session: Session;
77
+ toolUseCache: ToolUseCache;
78
+ backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
79
+ clientCapabilities?: ClientCapabilities;
80
+ private logWriter?: SessionLogWriter;
81
+ private processCallbacks?: ClaudeAcpAgentOptions;
82
+ private lastSentConfigOptions?: SessionConfigOption[];
83
+
84
+ constructor(
85
+ client: AgentSideConnection,
86
+ logWriter?: SessionLogWriter,
87
+ processCallbacks?: ClaudeAcpAgentOptions,
88
+ ) {
89
+ super(client);
90
+ this.logWriter = logWriter;
91
+ this.processCallbacks = processCallbacks;
92
+ this.toolUseCache = {};
93
+ this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
94
+ }
95
+
96
+ async initialize(request: InitializeRequest): Promise<InitializeResponse> {
97
+ this.clientCapabilities = request.clientCapabilities;
98
+
99
+ return {
100
+ protocolVersion: 1,
101
+ agentCapabilities: {
102
+ promptCapabilities: {
103
+ image: true,
104
+ embeddedContext: true,
105
+ },
106
+ mcpCapabilities: {
107
+ http: true,
108
+ sse: true,
109
+ },
110
+ loadSession: true,
111
+ _meta: {
112
+ posthog: {
113
+ resumeSession: true,
114
+ },
115
+ },
116
+ },
117
+ agentInfo: {
118
+ name: packageJson.name,
119
+ title: "Claude Code",
120
+ version: packageJson.version,
121
+ },
122
+ authMethods: [
123
+ {
124
+ id: "claude-login",
125
+ name: "Log in with Claude Code",
126
+ description: "Run `claude /login` in the terminal",
127
+ },
128
+ ],
129
+ };
130
+ }
131
+
132
+ async authenticate(_params: AuthenticateRequest): Promise<void> {
133
+ throw new Error("Method not implemented.");
134
+ }
135
+
136
+ async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
137
+ this.checkAuthStatus();
138
+
139
+ const meta = params._meta as NewSessionMeta | undefined;
140
+ const internalSessionId = uuidv7();
141
+ const permissionMode: TwigExecutionMode = "default";
142
+
143
+ const mcpServers = parseMcpServers(params);
144
+ await fetchMcpToolMetadata(mcpServers, this.logger);
145
+
146
+ const options = buildSessionOptions({
147
+ cwd: params.cwd,
148
+ mcpServers,
149
+ permissionMode,
150
+ canUseTool: this.createCanUseTool(internalSessionId),
151
+ logger: this.logger,
152
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
153
+ userProvidedOptions: meta?.claudeCode?.options,
154
+ onModeChange: this.createOnModeChange(internalSessionId),
155
+ onProcessSpawned: this.processCallbacks?.onProcessSpawned,
156
+ onProcessExited: this.processCallbacks?.onProcessExited,
157
+ });
158
+
159
+ const input = new Pushable<SDKUserMessage>();
160
+ const q = query({ prompt: input, options });
161
+
162
+ const session = this.createSession(
163
+ internalSessionId,
164
+ q,
165
+ input,
166
+ permissionMode,
167
+ params.cwd,
168
+ options.abortController as AbortController,
169
+ );
170
+ session.taskRunId = meta?.taskRunId;
171
+ this.registerPersistence(
172
+ internalSessionId,
173
+ meta as Record<string, unknown>,
174
+ );
175
+ const modelOptions = await this.getModelConfigOptions();
176
+ session.modelId = modelOptions.currentModelId;
177
+ await this.trySetModel(q, modelOptions.currentModelId);
178
+
179
+ this.sendAvailableCommandsUpdate(
180
+ internalSessionId,
181
+ await getAvailableSlashCommands(q),
182
+ );
183
+
184
+ return {
185
+ sessionId: internalSessionId,
186
+ configOptions: await this.buildConfigOptions(modelOptions),
187
+ };
188
+ }
189
+
190
+ async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
191
+ return this.resumeSession(params);
192
+ }
193
+
194
+ async resumeSession(
195
+ params: LoadSessionRequest,
196
+ ): Promise<LoadSessionResponse> {
197
+ const { sessionId: internalSessionId } = params;
198
+ if (this.sessionId === internalSessionId) {
199
+ return {};
200
+ }
201
+
202
+ const meta = params._meta as NewSessionMeta | undefined;
203
+ const mcpServers = parseMcpServers(params);
204
+ await fetchMcpToolMetadata(mcpServers, this.logger);
205
+
206
+ const { query: q, session } = await this.initializeQuery({
207
+ internalSessionId,
208
+ cwd: params.cwd,
209
+ permissionMode: "default",
210
+ mcpServers,
211
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
212
+ userProvidedOptions: meta?.claudeCode?.options,
213
+ sessionId: meta?.sessionId,
214
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
215
+ });
216
+
217
+ session.taskRunId = meta?.taskRunId;
218
+ if (meta?.sessionId) {
219
+ session.sessionId = meta.sessionId;
220
+ }
221
+
222
+ this.registerPersistence(
223
+ internalSessionId,
224
+ meta as Record<string, unknown>,
225
+ );
226
+ this.sendAvailableCommandsUpdate(
227
+ internalSessionId,
228
+ await getAvailableSlashCommands(q),
229
+ );
230
+
231
+ return {
232
+ configOptions: await this.buildConfigOptions(),
233
+ };
234
+ }
235
+
236
+ async prompt(params: PromptRequest): Promise<PromptResponse> {
237
+ this.session.cancelled = false;
238
+ this.session.interruptReason = undefined;
239
+
240
+ await this.broadcastUserMessage(params);
241
+ this.session.input.push(promptToClaude(params));
242
+
243
+ return this.processMessages(params.sessionId);
244
+ }
245
+
246
+ async setSessionConfigOption(
247
+ params: SetSessionConfigOptionRequest,
248
+ ): Promise<SetSessionConfigOptionResponse> {
249
+ const configId = params.configId;
250
+ const value = params.value;
251
+
252
+ if (configId === "mode") {
253
+ const modeId = value as TwigExecutionMode;
254
+ if (!TWIG_EXECUTION_MODES.includes(modeId)) {
255
+ throw new Error("Invalid Mode");
256
+ }
257
+ this.session.permissionMode = modeId;
258
+ await this.session.query.setPermissionMode(modeId);
259
+ } else if (configId === "model") {
260
+ await this.setModelWithFallback(this.session.query, value);
261
+ this.session.modelId = value;
262
+ } else {
263
+ throw new Error("Unsupported config option");
264
+ }
265
+
266
+ await this.emitConfigOptionsUpdate();
267
+ return { configOptions: await this.buildConfigOptions() };
268
+ }
269
+
270
+ protected async interruptSession(): Promise<void> {
271
+ await this.session.query.interrupt();
272
+ }
273
+
274
+ async extMethod(
275
+ method: string,
276
+ params: Record<string, unknown>,
277
+ ): Promise<Record<string, unknown>> {
278
+ if (method === "_posthog/session/resume") {
279
+ const result = await this.resumeSession(
280
+ params as unknown as LoadSessionRequest,
281
+ );
282
+ return {
283
+ _meta: {
284
+ configOptions: result.configOptions,
285
+ },
286
+ };
287
+ }
288
+
289
+ throw RequestError.methodNotFound(method);
290
+ }
291
+
292
+ private createSession(
293
+ sessionId: string,
294
+ q: Query,
295
+ input: Pushable<SDKUserMessage>,
296
+ permissionMode: TwigExecutionMode,
297
+ cwd: string,
298
+ abortController: AbortController,
299
+ ): Session {
300
+ const session: Session = {
301
+ query: q,
302
+ input,
303
+ cancelled: false,
304
+ permissionMode,
305
+ cwd,
306
+ notificationHistory: [],
307
+ abortController,
308
+ };
309
+ this.session = session;
310
+ this.sessionId = sessionId;
311
+ return session;
312
+ }
313
+
314
+ private async initializeQuery(config: {
315
+ internalSessionId: string;
316
+ cwd: string;
317
+ permissionMode: TwigExecutionMode;
318
+ mcpServers: ReturnType<typeof parseMcpServers>;
319
+ userProvidedOptions?: Options;
320
+ systemPrompt?: Options["systemPrompt"];
321
+ sessionId?: string;
322
+ additionalDirectories?: string[];
323
+ }): Promise<{
324
+ query: Query;
325
+ input: Pushable<SDKUserMessage>;
326
+ session: Session;
327
+ }> {
328
+ const input = new Pushable<SDKUserMessage>();
329
+
330
+ const options = buildSessionOptions({
331
+ cwd: config.cwd,
332
+ mcpServers: config.mcpServers,
333
+ permissionMode: config.permissionMode,
334
+ canUseTool: this.createCanUseTool(config.internalSessionId),
335
+ logger: this.logger,
336
+ systemPrompt: config.systemPrompt,
337
+ userProvidedOptions: config.userProvidedOptions,
338
+ sessionId: config.sessionId,
339
+ additionalDirectories: config.additionalDirectories,
340
+ onModeChange: this.createOnModeChange(config.internalSessionId),
341
+ onProcessSpawned: this.processCallbacks?.onProcessSpawned,
342
+ onProcessExited: this.processCallbacks?.onProcessExited,
343
+ });
344
+
345
+ const q = query({ prompt: input, options });
346
+ const abortController = options.abortController as AbortController;
347
+
348
+ const session = this.createSession(
349
+ config.internalSessionId,
350
+ q,
351
+ input,
352
+ config.permissionMode,
353
+ config.cwd,
354
+ abortController,
355
+ );
356
+
357
+ return { query: q, input, session };
358
+ }
359
+
360
+ private createCanUseTool(sessionId: string): CanUseTool {
361
+ return async (toolName, toolInput, { suggestions, toolUseID }) =>
362
+ canUseTool({
363
+ session: this.session,
364
+ toolName,
365
+ toolInput: toolInput as Record<string, unknown>,
366
+ toolUseID,
367
+ suggestions,
368
+ client: this.client,
369
+ sessionId,
370
+ fileContentCache: this.fileContentCache,
371
+ logger: this.logger,
372
+ emitConfigOptionsUpdate: () => this.emitConfigOptionsUpdate(sessionId),
373
+ });
374
+ }
375
+
376
+ private createOnModeChange(sessionId: string) {
377
+ return async (newMode: TwigExecutionMode) => {
378
+ if (this.session) {
379
+ this.session.permissionMode = newMode;
380
+ }
381
+ await this.emitConfigOptionsUpdate(sessionId);
382
+ };
383
+ }
384
+
385
+ private async buildConfigOptions(modelOptionsOverride?: {
386
+ currentModelId: string;
387
+ options: SessionConfigSelectOption[];
388
+ }): Promise<SessionConfigOption[]> {
389
+ const options: SessionConfigOption[] = [];
390
+
391
+ const modeOptions = getAvailableModes().map((mode) => ({
392
+ value: mode.id,
393
+ name: mode.name,
394
+ description: mode.description ?? undefined,
395
+ }));
396
+
397
+ options.push({
398
+ id: "mode",
399
+ name: "Approval Preset",
400
+ type: "select",
401
+ currentValue: this.session.permissionMode,
402
+ options: modeOptions,
403
+ category: "mode" as SessionConfigOptionCategory,
404
+ description: "Choose an approval and sandboxing preset for your session",
405
+ });
406
+
407
+ const modelOptions =
408
+ modelOptionsOverride ??
409
+ (await this.getModelConfigOptions(this.session.modelId));
410
+ this.session.modelId = modelOptions.currentModelId;
411
+
412
+ options.push({
413
+ id: "model",
414
+ name: "Model",
415
+ type: "select",
416
+ currentValue: modelOptions.currentModelId,
417
+ options: modelOptions.options,
418
+ category: "model" as SessionConfigOptionCategory,
419
+ description: "Choose which model Claude should use",
420
+ });
421
+
422
+ return options;
423
+ }
424
+
425
+ private async emitConfigOptionsUpdate(sessionId?: string): Promise<void> {
426
+ const configOptions = await this.buildConfigOptions();
427
+ const serialized = JSON.stringify(configOptions);
428
+ if (
429
+ this.lastSentConfigOptions &&
430
+ JSON.stringify(this.lastSentConfigOptions) === serialized
431
+ ) {
432
+ return;
433
+ }
434
+
435
+ this.lastSentConfigOptions = configOptions;
436
+ await this.client.sessionUpdate({
437
+ sessionId: sessionId ?? this.sessionId,
438
+ update: {
439
+ sessionUpdate: "config_option_update",
440
+ configOptions,
441
+ },
442
+ });
443
+ }
444
+
445
+ private checkAuthStatus() {
446
+ const backupExists = fs.existsSync(
447
+ path.resolve(os.homedir(), ".claude.json.backup"),
448
+ );
449
+ const configExists = fs.existsSync(
450
+ path.resolve(os.homedir(), ".claude.json"),
451
+ );
452
+ if (backupExists && !configExists) {
453
+ throw RequestError.authRequired();
454
+ }
455
+ }
456
+
457
+ private async trySetModel(q: Query, modelId: string) {
458
+ try {
459
+ await this.setModelWithFallback(q, modelId);
460
+ } catch (err) {
461
+ this.logger.warn("Failed to set model", { modelId, error: err });
462
+ }
463
+ }
464
+
465
+ private async setModelWithFallback(q: Query, modelId: string): Promise<void> {
466
+ try {
467
+ await q.setModel(modelId);
468
+ return;
469
+ } catch (err) {
470
+ const fallback = toSdkModelId(modelId);
471
+ if (fallback === modelId) {
472
+ throw err;
473
+ }
474
+ await q.setModel(fallback);
475
+ }
476
+ }
477
+
478
+ private registerPersistence(
479
+ sessionId: string,
480
+ meta: Record<string, unknown> | undefined,
481
+ ) {
482
+ const persistence = meta?.persistence as SessionContext | undefined;
483
+ if (persistence && this.logWriter) {
484
+ this.logWriter.register(sessionId, persistence);
485
+ }
486
+ }
487
+
488
+ private sendAvailableCommandsUpdate(
489
+ sessionId: string,
490
+ availableCommands: AvailableCommand[],
491
+ ) {
492
+ setTimeout(() => {
493
+ this.client.sessionUpdate({
494
+ sessionId,
495
+ update: {
496
+ sessionUpdate: "available_commands_update",
497
+ availableCommands,
498
+ },
499
+ });
500
+ }, 0);
501
+ }
502
+
503
+ private async broadcastUserMessage(params: PromptRequest): Promise<void> {
504
+ for (const chunk of params.prompt) {
505
+ const notification = {
506
+ sessionId: params.sessionId,
507
+ update: {
508
+ sessionUpdate: "user_message_chunk" as const,
509
+ content: chunk,
510
+ },
511
+ };
512
+ await this.client.sessionUpdate(notification);
513
+ this.appendNotification(params.sessionId, notification);
514
+ }
515
+ }
516
+
517
+ private async processMessages(sessionId: string): Promise<PromptResponse> {
518
+ const context = {
519
+ session: this.session,
520
+ sessionId,
521
+ client: this.client,
522
+ toolUseCache: this.toolUseCache,
523
+ fileContentCache: this.fileContentCache,
524
+ logger: this.logger,
525
+ };
526
+
527
+ while (true) {
528
+ const { value: message, done } = await this.session.query.next();
529
+
530
+ if (done || !message) {
531
+ return this.handleSessionEnd();
532
+ }
533
+
534
+ const response = await this.handleMessage(message, context);
535
+ if (response) {
536
+ return response;
537
+ }
538
+ }
539
+ }
540
+
541
+ private handleSessionEnd(): PromptResponse {
542
+ if (this.session.cancelled) {
543
+ return {
544
+ stopReason: "cancelled",
545
+ _meta: this.session.interruptReason
546
+ ? { interruptReason: this.session.interruptReason }
547
+ : undefined,
548
+ };
549
+ }
550
+ throw new Error("Session did not end in result");
551
+ }
552
+
553
+ private async handleMessage(
554
+ message: SDKMessage,
555
+ context: Parameters<typeof handleSystemMessage>[1],
556
+ ): Promise<PromptResponse | null> {
557
+ switch (message.type) {
558
+ case "system":
559
+ await handleSystemMessage(message, context);
560
+ return null;
561
+
562
+ case "result": {
563
+ const result = handleResultMessage(message, context);
564
+ if (result.error) throw result.error;
565
+ if (result.shouldStop) {
566
+ return {
567
+ stopReason: result.stopReason as "end_turn" | "max_turn_requests",
568
+ };
569
+ }
570
+ return null;
571
+ }
572
+
573
+ case "stream_event":
574
+ await handleStreamEvent(message, context);
575
+ return null;
576
+
577
+ case "user":
578
+ case "assistant": {
579
+ const result = await handleUserAssistantMessage(message, context);
580
+ if (result.error) throw result.error;
581
+ if (result.shouldStop) {
582
+ return { stopReason: "end_turn" };
583
+ }
584
+ return null;
585
+ }
586
+
587
+ case "tool_progress":
588
+ case "auth_status":
589
+ return null;
590
+
591
+ default:
592
+ unreachable(message, this.logger);
593
+ return null;
594
+ }
595
+ }
596
+ }
@@ -0,0 +1,102 @@
1
+ import type { PromptRequest } from "@agentclientprotocol/sdk";
2
+ import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
3
+ import type { ContentBlockParam } from "@anthropic-ai/sdk/resources";
4
+
5
+ type ImageMimeType = "image/jpeg" | "image/png" | "image/gif" | "image/webp";
6
+
7
+ function sdkText(value: string): ContentBlockParam {
8
+ return { type: "text", text: value };
9
+ }
10
+
11
+ function formatUriAsLink(uri: string): string {
12
+ try {
13
+ if (uri.startsWith("file://")) {
14
+ const filePath = uri.slice(7);
15
+ const name = filePath.split("/").pop() || filePath;
16
+ return `[@${name}](${uri})`;
17
+ }
18
+ if (uri.startsWith("zed://")) {
19
+ const parts = uri.split("/");
20
+ const name = parts[parts.length - 1] || uri;
21
+ return `[@${name}](${uri})`;
22
+ }
23
+ return uri;
24
+ } catch {
25
+ return uri;
26
+ }
27
+ }
28
+
29
+ function transformMcpCommand(text: string): string {
30
+ const mcpMatch = text.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
31
+ if (mcpMatch) {
32
+ const [, server, command, args] = mcpMatch;
33
+ return `/${server}:${command} (MCP)${args || ""}`;
34
+ }
35
+ return text;
36
+ }
37
+
38
+ function processPromptChunk(
39
+ chunk: PromptRequest["prompt"][number],
40
+ content: ContentBlockParam[],
41
+ context: ContentBlockParam[],
42
+ ): void {
43
+ switch (chunk.type) {
44
+ case "text":
45
+ content.push(sdkText(transformMcpCommand(chunk.text)));
46
+ break;
47
+
48
+ case "resource_link":
49
+ content.push(sdkText(formatUriAsLink(chunk.uri)));
50
+ break;
51
+
52
+ case "resource":
53
+ if ("text" in chunk.resource) {
54
+ content.push(sdkText(formatUriAsLink(chunk.resource.uri)));
55
+ context.push(
56
+ sdkText(
57
+ `\n<context ref="${chunk.resource.uri}">\n${chunk.resource.text}\n</context>`,
58
+ ),
59
+ );
60
+ }
61
+ break;
62
+
63
+ case "image":
64
+ if (chunk.data) {
65
+ content.push({
66
+ type: "image",
67
+ source: {
68
+ type: "base64",
69
+ data: chunk.data,
70
+ media_type: chunk.mimeType as ImageMimeType,
71
+ },
72
+ });
73
+ } else if (chunk.uri?.startsWith("http")) {
74
+ content.push({
75
+ type: "image",
76
+ source: { type: "url", url: chunk.uri },
77
+ });
78
+ }
79
+ break;
80
+
81
+ default:
82
+ break;
83
+ }
84
+ }
85
+
86
+ export function promptToClaude(prompt: PromptRequest): SDKUserMessage {
87
+ const content: ContentBlockParam[] = [];
88
+ const context: ContentBlockParam[] = [];
89
+
90
+ for (const chunk of prompt.prompt) {
91
+ processPromptChunk(chunk, content, context);
92
+ }
93
+
94
+ content.push(...context);
95
+
96
+ return {
97
+ type: "user",
98
+ message: { role: "user", content },
99
+ session_id: prompt.sessionId,
100
+ parent_tool_use_id: null,
101
+ };
102
+ }