@posthog/agent 1.30.0 → 2.0.1

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 (144) 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 +172 -1203
  35. package/dist/index.js +3704 -6826
  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 +4451 -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 +4507 -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 +66 -14
  51. package/src/acp-extensions.ts +93 -61
  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 -611
  70. package/src/adapters/claude/types.ts +61 -0
  71. package/src/adapters/codex/spawn.ts +130 -0
  72. package/src/agent.ts +97 -734
  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 +51 -154
  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/dist/templates/plan-template.md +0 -41
  119. package/src/adapters/claude/claude.ts +0 -1543
  120. package/src/adapters/claude/mcp-server.ts +0 -810
  121. package/src/adapters/claude/utils.ts +0 -267
  122. package/src/agents/execution.ts +0 -37
  123. package/src/agents/planning.ts +0 -60
  124. package/src/agents/research.ts +0 -160
  125. package/src/file-manager.ts +0 -306
  126. package/src/git-manager.ts +0 -577
  127. package/src/prompt-builder.ts +0 -499
  128. package/src/schemas.ts +0 -241
  129. package/src/session-store.ts +0 -259
  130. package/src/task-manager.ts +0 -163
  131. package/src/template-manager.ts +0 -236
  132. package/src/templates/plan-template.md +0 -41
  133. package/src/todo-manager.ts +0 -180
  134. package/src/tools/registry.ts +0 -129
  135. package/src/tools/types.ts +0 -127
  136. package/src/utils/tapped-stream.ts +0 -60
  137. package/src/workflow/config.ts +0 -53
  138. package/src/workflow/steps/build.ts +0 -135
  139. package/src/workflow/steps/finalize.ts +0 -241
  140. package/src/workflow/steps/plan.ts +0 -167
  141. package/src/workflow/steps/research.ts +0 -223
  142. package/src/workflow/types.ts +0 -62
  143. package/src/workflow/utils.ts +0 -53
  144. package/src/worktree-manager.ts +0 -928
@@ -0,0 +1,571 @@
1
+ import type {
2
+ AgentSideConnection,
3
+ Role,
4
+ SessionNotification,
5
+ } from "@agentclientprotocol/sdk";
6
+ import { RequestError } from "@agentclientprotocol/sdk";
7
+ import type {
8
+ SDKPartialAssistantMessage,
9
+ SDKUserMessage,
10
+ } from "@anthropic-ai/claude-agent-sdk";
11
+ import type { ContentBlockParam } from "@anthropic-ai/sdk/resources";
12
+ import type {
13
+ BetaContentBlock,
14
+ BetaRawContentBlockDelta,
15
+ } from "@anthropic-ai/sdk/resources/beta.mjs";
16
+ import { image, text } from "../../../utils/acp-content.js";
17
+ import { unreachable } from "../../../utils/common.js";
18
+ import type { Logger } from "../../../utils/logger.js";
19
+ import { registerHookCallback } from "../hooks.js";
20
+ import type { Session, ToolUpdateMeta, ToolUseCache } from "../types.js";
21
+ import {
22
+ type ClaudePlanEntry,
23
+ planEntries,
24
+ toolInfoFromToolUse,
25
+ toolUpdateFromToolResult,
26
+ } from "./tool-use-to-acp.js";
27
+
28
+ type AnthropicContentChunk =
29
+ | ContentBlockParam
30
+ | BetaContentBlock
31
+ | BetaRawContentBlockDelta;
32
+
33
+ type AnthropicMessageContent = string | Array<{ type: string; text?: string }>;
34
+
35
+ interface AnthropicMessageWithContent {
36
+ type: Role;
37
+ message: {
38
+ content: AnthropicMessageContent;
39
+ role?: Role;
40
+ model?: string;
41
+ };
42
+ }
43
+
44
+ type ChunkHandlerContext = {
45
+ sessionId: string;
46
+ toolUseCache: ToolUseCache;
47
+ fileContentCache: { [key: string]: string };
48
+ client: AgentSideConnection;
49
+ logger: Logger;
50
+ };
51
+
52
+ export interface MessageHandlerContext {
53
+ session: Session;
54
+ sessionId: string;
55
+ client: AgentSideConnection;
56
+ toolUseCache: ToolUseCache;
57
+ fileContentCache: { [key: string]: string };
58
+ logger: Logger;
59
+ }
60
+
61
+ function messageUpdateType(role: Role) {
62
+ return role === "assistant" ? "agent_message_chunk" : "user_message_chunk";
63
+ }
64
+
65
+ function toolMeta(toolName: string, toolResponse?: unknown): ToolUpdateMeta {
66
+ return toolResponse
67
+ ? { claudeCode: { toolName, toolResponse } }
68
+ : { claudeCode: { toolName } };
69
+ }
70
+
71
+ function handleTextChunk(
72
+ chunk: { text: string },
73
+ role: Role,
74
+ ): SessionNotification["update"] {
75
+ return {
76
+ sessionUpdate: messageUpdateType(role),
77
+ content: text(chunk.text),
78
+ };
79
+ }
80
+
81
+ function handleImageChunk(
82
+ chunk: {
83
+ source: { type: string; data?: string; media_type?: string; url?: string };
84
+ },
85
+ role: Role,
86
+ ): SessionNotification["update"] {
87
+ return {
88
+ sessionUpdate: messageUpdateType(role),
89
+ content: image(
90
+ chunk.source.type === "base64" ? (chunk.source.data ?? "") : "",
91
+ chunk.source.type === "base64" ? (chunk.source.media_type ?? "") : "",
92
+ chunk.source.type === "url" ? chunk.source.url : undefined,
93
+ ),
94
+ };
95
+ }
96
+
97
+ function handleThinkingChunk(chunk: {
98
+ thinking: string;
99
+ }): SessionNotification["update"] {
100
+ return {
101
+ sessionUpdate: "agent_thought_chunk",
102
+ content: text(chunk.thinking),
103
+ };
104
+ }
105
+
106
+ function handleToolUseChunk(
107
+ chunk: ToolUseCache[string],
108
+ ctx: ChunkHandlerContext,
109
+ ): SessionNotification["update"] | null {
110
+ ctx.toolUseCache[chunk.id] = chunk;
111
+
112
+ if (chunk.name === "TodoWrite") {
113
+ const input = chunk.input as { todos?: unknown[] };
114
+ if (Array.isArray(input.todos)) {
115
+ return {
116
+ sessionUpdate: "plan",
117
+ entries: planEntries(chunk.input as { todos: ClaudePlanEntry[] }),
118
+ };
119
+ }
120
+ return null;
121
+ }
122
+
123
+ registerHookCallback(chunk.id, {
124
+ onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
125
+ const toolUse = ctx.toolUseCache[toolUseId];
126
+ if (toolUse) {
127
+ await ctx.client.sessionUpdate({
128
+ sessionId: ctx.sessionId,
129
+ update: {
130
+ _meta: toolMeta(toolUse.name, toolResponse),
131
+ toolCallId: toolUseId,
132
+ sessionUpdate: "tool_call_update",
133
+ },
134
+ });
135
+ } else {
136
+ ctx.logger.error(
137
+ `Got a tool response for tool use that wasn't tracked: ${toolUseId}`,
138
+ );
139
+ }
140
+ },
141
+ });
142
+
143
+ let rawInput: Record<string, unknown> | undefined;
144
+ try {
145
+ rawInput = JSON.parse(JSON.stringify(chunk.input));
146
+ } catch {
147
+ // ignore
148
+ }
149
+
150
+ return {
151
+ _meta: toolMeta(chunk.name),
152
+ toolCallId: chunk.id,
153
+ sessionUpdate: "tool_call",
154
+ rawInput,
155
+ status: "pending",
156
+ ...toolInfoFromToolUse(chunk, ctx.fileContentCache, ctx.logger),
157
+ };
158
+ }
159
+
160
+ function handleToolResultChunk(
161
+ chunk: AnthropicContentChunk & { tool_use_id: string; is_error?: boolean },
162
+ ctx: ChunkHandlerContext,
163
+ ): SessionNotification["update"] | null {
164
+ const toolUse = ctx.toolUseCache[chunk.tool_use_id];
165
+ if (!toolUse) {
166
+ ctx.logger.error(
167
+ `Got a tool result for tool use that wasn't tracked: ${chunk.tool_use_id}`,
168
+ );
169
+ return null;
170
+ }
171
+
172
+ if (toolUse.name === "TodoWrite") {
173
+ return null;
174
+ }
175
+
176
+ return {
177
+ _meta: toolMeta(toolUse.name),
178
+ toolCallId: chunk.tool_use_id,
179
+ sessionUpdate: "tool_call_update",
180
+ status: chunk.is_error ? "failed" : "completed",
181
+ ...toolUpdateFromToolResult(
182
+ chunk as Parameters<typeof toolUpdateFromToolResult>[0],
183
+ toolUse,
184
+ ),
185
+ };
186
+ }
187
+
188
+ function processContentChunk(
189
+ chunk: AnthropicContentChunk,
190
+ role: Role,
191
+ ctx: ChunkHandlerContext,
192
+ ): SessionNotification["update"] | null {
193
+ switch (chunk.type) {
194
+ case "text":
195
+ case "text_delta":
196
+ return handleTextChunk(chunk, role);
197
+
198
+ case "image":
199
+ return handleImageChunk(chunk, role);
200
+
201
+ case "thinking":
202
+ case "thinking_delta":
203
+ return handleThinkingChunk(chunk);
204
+
205
+ case "tool_use":
206
+ case "server_tool_use":
207
+ case "mcp_tool_use":
208
+ return handleToolUseChunk(chunk as ToolUseCache[string], ctx);
209
+
210
+ case "tool_result":
211
+ case "tool_search_tool_result":
212
+ case "web_fetch_tool_result":
213
+ case "web_search_tool_result":
214
+ case "code_execution_tool_result":
215
+ case "bash_code_execution_tool_result":
216
+ case "text_editor_code_execution_tool_result":
217
+ case "mcp_tool_result":
218
+ return handleToolResultChunk(
219
+ chunk as AnthropicContentChunk & {
220
+ tool_use_id: string;
221
+ is_error?: boolean;
222
+ },
223
+ ctx,
224
+ );
225
+
226
+ case "document":
227
+ case "search_result":
228
+ case "redacted_thinking":
229
+ case "input_json_delta":
230
+ case "citations_delta":
231
+ case "signature_delta":
232
+ case "container_upload":
233
+ return null;
234
+
235
+ default:
236
+ unreachable(chunk, ctx.logger);
237
+ return null;
238
+ }
239
+ }
240
+
241
+ function toAcpNotifications(
242
+ content:
243
+ | string
244
+ | ContentBlockParam[]
245
+ | BetaContentBlock[]
246
+ | BetaRawContentBlockDelta[],
247
+ role: Role,
248
+ sessionId: string,
249
+ toolUseCache: ToolUseCache,
250
+ fileContentCache: { [key: string]: string },
251
+ client: AgentSideConnection,
252
+ logger: Logger,
253
+ ): SessionNotification[] {
254
+ if (typeof content === "string") {
255
+ return [
256
+ {
257
+ sessionId,
258
+ update: {
259
+ sessionUpdate: messageUpdateType(role),
260
+ content: text(content),
261
+ },
262
+ },
263
+ ];
264
+ }
265
+
266
+ const ctx: ChunkHandlerContext = {
267
+ sessionId,
268
+ toolUseCache,
269
+ fileContentCache,
270
+ client,
271
+ logger,
272
+ };
273
+ const output: SessionNotification[] = [];
274
+
275
+ for (const chunk of content) {
276
+ const update = processContentChunk(chunk, role, ctx);
277
+ if (update) {
278
+ output.push({ sessionId, update });
279
+ }
280
+ }
281
+
282
+ return output;
283
+ }
284
+
285
+ function streamEventToAcpNotifications(
286
+ message: SDKPartialAssistantMessage,
287
+ sessionId: string,
288
+ toolUseCache: ToolUseCache,
289
+ fileContentCache: { [key: string]: string },
290
+ client: AgentSideConnection,
291
+ logger: Logger,
292
+ ): SessionNotification[] {
293
+ const event = message.event;
294
+ switch (event.type) {
295
+ case "content_block_start":
296
+ return toAcpNotifications(
297
+ [event.content_block],
298
+ "assistant",
299
+ sessionId,
300
+ toolUseCache,
301
+ fileContentCache,
302
+ client,
303
+ logger,
304
+ );
305
+ case "content_block_delta":
306
+ return toAcpNotifications(
307
+ [event.delta],
308
+ "assistant",
309
+ sessionId,
310
+ toolUseCache,
311
+ fileContentCache,
312
+ client,
313
+ logger,
314
+ );
315
+ case "message_start":
316
+ case "message_delta":
317
+ case "message_stop":
318
+ case "content_block_stop":
319
+ return [];
320
+
321
+ default:
322
+ unreachable(event, logger);
323
+ return [];
324
+ }
325
+ }
326
+
327
+ export async function handleSystemMessage(
328
+ message: any,
329
+ context: MessageHandlerContext,
330
+ ): Promise<void> {
331
+ const { session, sessionId, client, logger } = context;
332
+
333
+ switch (message.subtype) {
334
+ case "init":
335
+ if (message.session_id && session && !session.sessionId) {
336
+ session.sessionId = message.session_id;
337
+ if (session.taskRunId) {
338
+ await client.extNotification("_posthog/sdk_session", {
339
+ taskRunId: session.taskRunId,
340
+ sessionId: message.session_id,
341
+ adapter: "claude",
342
+ });
343
+ }
344
+ }
345
+ break;
346
+ case "compact_boundary":
347
+ await client.extNotification("_posthog/compact_boundary", {
348
+ sessionId,
349
+ trigger: message.compact_metadata.trigger,
350
+ preTokens: message.compact_metadata.pre_tokens,
351
+ });
352
+ break;
353
+ case "hook_response":
354
+ logger.info("Hook response received", {
355
+ hookName: message.hook_name,
356
+ hookEvent: message.hook_event,
357
+ });
358
+ break;
359
+ case "status":
360
+ if (message.status === "compacting") {
361
+ logger.info("Session compacting started", { sessionId });
362
+ await client.extNotification("_posthog/status", {
363
+ sessionId,
364
+ status: "compacting",
365
+ });
366
+ }
367
+ break;
368
+ case "task_notification": {
369
+ logger.info("Task notification received", {
370
+ sessionId,
371
+ taskId: message.task_id,
372
+ status: message.status,
373
+ summary: message.summary,
374
+ });
375
+ await client.extNotification("_posthog/task_notification", {
376
+ sessionId,
377
+ taskId: message.task_id,
378
+ status: message.status,
379
+ summary: message.summary,
380
+ outputFile: message.output_file,
381
+ });
382
+ break;
383
+ }
384
+ default:
385
+ break;
386
+ }
387
+ }
388
+
389
+ export function handleResultMessage(
390
+ message: any,
391
+ context: MessageHandlerContext,
392
+ ): { shouldStop: boolean; stopReason?: string; error?: Error } {
393
+ const { session } = context;
394
+
395
+ if (session.cancelled) {
396
+ return {
397
+ shouldStop: true,
398
+ stopReason: "cancelled",
399
+ };
400
+ }
401
+
402
+ switch (message.subtype) {
403
+ case "success": {
404
+ if (message.result.includes("Please run /login")) {
405
+ return {
406
+ shouldStop: true,
407
+ error: RequestError.authRequired(),
408
+ };
409
+ }
410
+ if (message.is_error) {
411
+ return {
412
+ shouldStop: true,
413
+ error: RequestError.internalError(undefined, message.result),
414
+ };
415
+ }
416
+ return { shouldStop: true, stopReason: "end_turn" };
417
+ }
418
+ case "error_during_execution":
419
+ if (message.is_error) {
420
+ return {
421
+ shouldStop: true,
422
+ error: RequestError.internalError(
423
+ undefined,
424
+ message.errors.join(", ") || message.subtype,
425
+ ),
426
+ };
427
+ }
428
+ return { shouldStop: true, stopReason: "end_turn" };
429
+ case "error_max_budget_usd":
430
+ case "error_max_turns":
431
+ case "error_max_structured_output_retries":
432
+ if (message.is_error) {
433
+ return {
434
+ shouldStop: true,
435
+ error: RequestError.internalError(
436
+ undefined,
437
+ message.errors.join(", ") || message.subtype,
438
+ ),
439
+ };
440
+ }
441
+ return { shouldStop: true, stopReason: "max_turn_requests" };
442
+ default:
443
+ return { shouldStop: false };
444
+ }
445
+ }
446
+
447
+ export async function handleStreamEvent(
448
+ message: SDKPartialAssistantMessage,
449
+ context: MessageHandlerContext,
450
+ ): Promise<void> {
451
+ const { sessionId, client, toolUseCache, fileContentCache, logger } = context;
452
+
453
+ for (const notification of streamEventToAcpNotifications(
454
+ message,
455
+ sessionId,
456
+ toolUseCache,
457
+ fileContentCache,
458
+ client,
459
+ logger,
460
+ )) {
461
+ await client.sessionUpdate(notification);
462
+ context.session.notificationHistory.push(notification);
463
+ }
464
+ }
465
+
466
+ function hasLocalCommandStdout(content: AnthropicMessageContent): boolean {
467
+ return (
468
+ typeof content === "string" && content.includes("<local-command-stdout>")
469
+ );
470
+ }
471
+
472
+ function hasLocalCommandStderr(content: AnthropicMessageContent): boolean {
473
+ return (
474
+ typeof content === "string" && content.includes("<local-command-stderr>")
475
+ );
476
+ }
477
+
478
+ function isSimpleUserMessage(message: AnthropicMessageWithContent): boolean {
479
+ return (
480
+ message.type === "user" &&
481
+ (typeof message.message.content === "string" ||
482
+ (Array.isArray(message.message.content) &&
483
+ message.message.content.length === 1 &&
484
+ message.message.content[0].type === "text"))
485
+ );
486
+ }
487
+
488
+ function isLoginRequiredMessage(message: AnthropicMessageWithContent): boolean {
489
+ return (
490
+ message.type === "assistant" &&
491
+ message.message.model === "<synthetic>" &&
492
+ Array.isArray(message.message.content) &&
493
+ message.message.content.length === 1 &&
494
+ message.message.content[0].type === "text" &&
495
+ message.message.content[0].text?.includes("Please run /login") === true
496
+ );
497
+ }
498
+
499
+ function shouldSkipUserAssistantMessage(
500
+ message: AnthropicMessageWithContent,
501
+ ): boolean {
502
+ return (
503
+ hasLocalCommandStdout(message.message.content) ||
504
+ hasLocalCommandStderr(message.message.content) ||
505
+ isSimpleUserMessage(message) ||
506
+ isLoginRequiredMessage(message)
507
+ );
508
+ }
509
+
510
+ function logSpecialMessages(
511
+ message: AnthropicMessageWithContent,
512
+ logger: Logger,
513
+ ): void {
514
+ const content = message.message.content;
515
+ if (hasLocalCommandStdout(content) && typeof content === "string") {
516
+ logger.info(content);
517
+ }
518
+ if (hasLocalCommandStderr(content) && typeof content === "string") {
519
+ logger.error(content);
520
+ }
521
+ }
522
+
523
+ function filterMessageContent(
524
+ content: AnthropicMessageContent,
525
+ ): AnthropicMessageContent {
526
+ if (!Array.isArray(content)) {
527
+ return content;
528
+ }
529
+ return content.filter(
530
+ (block) => block.type !== "text" && block.type !== "thinking",
531
+ );
532
+ }
533
+
534
+ export async function handleUserAssistantMessage(
535
+ message: SDKUserMessage | { type: "assistant"; message: any },
536
+ context: MessageHandlerContext,
537
+ ): Promise<{ shouldStop?: boolean; error?: Error }> {
538
+ const { session, sessionId, client, toolUseCache, fileContentCache, logger } =
539
+ context;
540
+
541
+ if (session.cancelled) {
542
+ return {};
543
+ }
544
+
545
+ if (shouldSkipUserAssistantMessage(message)) {
546
+ logSpecialMessages(message, logger);
547
+
548
+ if (isLoginRequiredMessage(message)) {
549
+ return { shouldStop: true, error: RequestError.authRequired() };
550
+ }
551
+ return {};
552
+ }
553
+
554
+ const content = message.message.content;
555
+ const contentToProcess = filterMessageContent(content);
556
+
557
+ for (const notification of toAcpNotifications(
558
+ contentToProcess as typeof content,
559
+ message.message.role,
560
+ sessionId,
561
+ toolUseCache,
562
+ fileContentCache,
563
+ client,
564
+ logger,
565
+ )) {
566
+ await client.sessionUpdate(notification);
567
+ session.notificationHistory.push(notification);
568
+ }
569
+
570
+ return {};
571
+ }