@meistrari/agent-core 0.0.0 → 0.1.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 (85) hide show
  1. package/README.md +36 -3
  2. package/bin/supervisor.ts +116 -0
  3. package/package.json +42 -3
  4. package/scripts/build-supervisor-executable.ts +32 -0
  5. package/src/agents/agent-error-serializer.ts +23 -0
  6. package/src/agents/agent-event-stream.ts +81 -0
  7. package/src/agents/agent-id.ts +17 -0
  8. package/src/agents/agent-operation.ts +11 -0
  9. package/src/agents/agent-provider.ts +37 -0
  10. package/src/agents/agent-run.ts +32 -0
  11. package/src/agents/agent-runtime-error.ts +161 -0
  12. package/src/agents/agent-session-events.ts +32 -0
  13. package/src/agents/agent-tool-runner.ts +83 -0
  14. package/src/agents/agent-tool.ts +111 -0
  15. package/src/agents/author-context.ts +36 -0
  16. package/src/agents/claude/claude-command-mapper.ts +234 -0
  17. package/src/agents/claude/claude-event-mapper.ts +748 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +468 -0
  20. package/src/agents/claude/claude-tool-mapper.ts +186 -0
  21. package/src/agents/claude/index.ts +1 -0
  22. package/src/agents/codex/codex-auth.ts +34 -0
  23. package/src/agents/codex/codex-command-mapper.ts +78 -0
  24. package/src/agents/codex/codex-event-mapper.ts +721 -0
  25. package/src/agents/codex/codex-json-rpc-client.ts +326 -0
  26. package/src/agents/codex/codex-protocol.ts +36 -0
  27. package/src/agents/codex/codex-provider.ts +1050 -0
  28. package/src/agents/codex/codex-run.ts +404 -0
  29. package/src/agents/codex/codex-skill-catalog.ts +158 -0
  30. package/src/agents/codex/codex-skill-roots.ts +19 -0
  31. package/src/agents/codex/codex-tool-mapper.ts +55 -0
  32. package/src/agents/codex/codex.errors.ts +68 -0
  33. package/src/agents/codex/generated/meta.gen.ts +606 -0
  34. package/src/agents/codex/generated/namespaces.gen.ts +311 -0
  35. package/src/agents/codex/generated/schema.gen.ts +34883 -0
  36. package/src/agents/codex/index.ts +2 -0
  37. package/src/agents/index.ts +14 -0
  38. package/src/agents/input-attachment-preparation.ts +86 -0
  39. package/src/agents/input-attachment.errors.ts +16 -0
  40. package/src/agents/instructions.ts +8 -0
  41. package/src/agents/materialized-input-attachment.ts +26 -0
  42. package/src/agents/message-id.ts +23 -0
  43. package/src/agents/normalize.ts +8 -0
  44. package/src/agents/sandbox-environment.ts +1 -0
  45. package/src/agents/tools/ping.tool.ts +13 -0
  46. package/src/agents/user-input-request.ts +470 -0
  47. package/src/protocol/agent-event.ts +2 -1
  48. package/src/protocol/agent-usage.ts +14 -0
  49. package/src/provenance.gen.ts +3 -3
  50. package/src/supervisor/agent-provider-factory.ts +189 -0
  51. package/src/supervisor/bootstrap-binder.ts +133 -0
  52. package/src/supervisor/config.ts +49 -0
  53. package/src/supervisor/control-authority-verifier.ts +135 -0
  54. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  55. package/src/supervisor/errors.ts +24 -0
  56. package/src/supervisor/index.ts +34 -0
  57. package/src/supervisor/persistence/json.ts +21 -0
  58. package/src/supervisor/persistence/state-discovery.ts +56 -0
  59. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  60. package/src/supervisor/ports/index.ts +109 -0
  61. package/src/supervisor/provider-factory.ts +37 -0
  62. package/src/supervisor/resident.ts +143 -0
  63. package/src/supervisor/rpc-client.ts +120 -0
  64. package/src/supervisor/runtime-handler.ts +309 -0
  65. package/src/supervisor/websocket-server.ts +435 -0
  66. package/src/supervisor-protocol/bootstrap.ts +1 -1
  67. package/src/template-onboarding.ts +47 -0
  68. package/src/testing/es256-test-keys.ts +73 -0
  69. package/src/testing/in-memory-runtime-control-plane.ts +206 -0
  70. package/src/testing/index.ts +6 -0
  71. package/src/testing/loopback-supervisor-connection.ts +71 -0
  72. package/src/testing/scripted-provider.ts +64 -0
  73. package/src/worker-runtime-client/command-pump.ts +127 -0
  74. package/src/worker-runtime-client/connection-attempt.ts +349 -0
  75. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  76. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  77. package/src/worker-runtime-client/frame-processor.ts +178 -0
  78. package/src/worker-runtime-client/index.ts +27 -0
  79. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  80. package/src/worker-runtime-client/ports.ts +139 -0
  81. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  82. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  83. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  84. package/src/worker-runtime-client/sandbox-connection-runtime.ts +301 -0
  85. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,748 @@
1
+ import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'
2
+ import type { TaskCreateInput, TaskCreateOutput, TaskGetInput, TaskGetOutput, TaskListInput, TaskListOutput, TaskUpdateInput, TaskUpdateOutput } from '@anthropic-ai/claude-agent-sdk/sdk-tools'
3
+ import type { MessageParam } from '@anthropic-ai/sdk/resources'
4
+ import type { BetaMessage, BetaRawMessageStreamEvent, BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages'
5
+ import type { AgentError, AgentEventActor, AgentUsage, JsonValue } from '../../protocol'
6
+ import type { AgentWorkObservation } from '../../protocol/agent-work'
7
+ import type { AgentEventDraft } from '../agent-event-stream'
8
+ import z from 'zod'
9
+ import { jsonValueSchema } from '../../protocol'
10
+ import { canonicalToolName } from '../../protocol/agent-tool-name'
11
+ import { agentWorkStatusSchema } from '../../protocol/agent-work'
12
+ import { createRuntimeTurnId } from '../agent-id'
13
+ import { AgentProviderProtocolError } from '../agent-runtime-error'
14
+ import { optionalNonEmpty, removeUndefined } from '../normalize'
15
+ import { CLAUDE_AGENT_TOOL_SERVER } from './claude-tool-mapper'
16
+
17
+ type UserContentBlock = Exclude<MessageParam['content'], string>[number]
18
+ type UserToolResultBlock = Extract<UserContentBlock, { type: 'tool_result' }>
19
+ type ClaudeStatusMessage = Extract<SDKMessage, { type: 'system', subtype: 'status' }>
20
+ type ClaudeCompactBoundaryMessage = Extract<SDKMessage, { type: 'system', subtype: 'compact_boundary' }>
21
+ type ClaudeTaskStartedMessage = Extract<SDKMessage, { type: 'system', subtype: 'task_started' }>
22
+ type ClaudeTaskProgressMessage = Extract<SDKMessage, { type: 'system', subtype: 'task_progress' }>
23
+ type ClaudeTaskNotificationMessage = Extract<SDKMessage, { type: 'system', subtype: 'task_notification' }>
24
+ type ClaudeUserMessage = Extract<SDKMessage, { type: 'user' }>
25
+ type ClaudeSubagentActor = Extract<AgentEventActor, { type: 'subagent' }>
26
+ type TaskCreateWorkInput = Pick<TaskCreateInput, 'subject' | 'description'>
27
+ type TaskGetWorkInput = Pick<TaskGetInput, 'taskId'>
28
+ type TaskUpdateWorkInput = Pick<TaskUpdateInput, 'taskId' | 'subject'>
29
+ type TaskListWorkInput = TaskListInput
30
+ interface TaskCreateWorkOutput { task: Pick<TaskCreateOutput['task'], 'id' | 'subject'> }
31
+ interface TaskGetWorkOutput { task: Pick<NonNullable<TaskGetOutput['task']>, 'id' | 'subject' | 'status'> | null }
32
+ type TaskUpdateWorkOutput = Pick<TaskUpdateOutput, 'success' | 'taskId' | 'updatedFields' | 'statusChange'>
33
+ interface TaskListWorkOutput { tasks: Array<Pick<TaskListOutput['tasks'][number], 'id' | 'subject' | 'status'>> }
34
+ type PendingClaudeWorkCall
35
+ = | { kind: 'create', input: TaskCreateWorkInput }
36
+ | { kind: 'get', input: TaskGetWorkInput }
37
+ | { kind: 'update', input: TaskUpdateWorkInput }
38
+ | { kind: 'list', input: TaskListWorkInput }
39
+ type ClaudeTurnEventDraft = Extract<AgentEventDraft, { turnId: string }>
40
+ type ClaudeUnownedTurnEventDraft = ClaudeTurnEventDraft extends infer Draft
41
+ ? Draft extends ClaudeTurnEventDraft
42
+ ? Omit<Draft, 'actor'>
43
+ : never
44
+ : never
45
+
46
+ const mainActor = { type: 'main', actorId: 'main' } satisfies AgentEventActor
47
+
48
+ const taskCreateWorkInputSchema = z.object({
49
+ subject: z.string().min(1),
50
+ description: z.string(),
51
+ }).passthrough() satisfies z.ZodType<TaskCreateWorkInput>
52
+
53
+ const taskGetWorkInputSchema = z.object({
54
+ taskId: z.string().min(1),
55
+ }).passthrough() satisfies z.ZodType<TaskGetWorkInput>
56
+
57
+ const taskUpdateWorkInputSchema = z.object({
58
+ taskId: z.string().min(1),
59
+ subject: z.string().min(1).optional(),
60
+ }).passthrough() satisfies z.ZodType<TaskUpdateWorkInput>
61
+
62
+ const taskListWorkInputSchema = z.object({}).passthrough() satisfies z.ZodType<TaskListWorkInput>
63
+
64
+ const taskCreateWorkOutputSchema = z.object({
65
+ task: z.object({
66
+ id: z.string().min(1),
67
+ subject: z.string().min(1),
68
+ }).passthrough(),
69
+ }).passthrough() satisfies z.ZodType<TaskCreateWorkOutput>
70
+
71
+ const taskGetWorkOutputSchema = z.object({
72
+ task: z.object({
73
+ id: z.string().min(1),
74
+ subject: z.string().min(1),
75
+ status: agentWorkStatusSchema,
76
+ }).passthrough().nullable(),
77
+ }).passthrough() satisfies z.ZodType<TaskGetWorkOutput>
78
+
79
+ const taskUpdateWorkOutputSchema = z.object({
80
+ success: z.boolean(),
81
+ taskId: z.string().min(1),
82
+ updatedFields: z.array(z.string()),
83
+ statusChange: z.object({
84
+ from: z.string(),
85
+ to: z.enum(['pending', 'in_progress', 'completed', 'deleted']),
86
+ }).passthrough().optional(),
87
+ }).passthrough() satisfies z.ZodType<TaskUpdateWorkOutput>
88
+
89
+ const taskListWorkOutputSchema = z.object({
90
+ tasks: z.array(z.object({
91
+ id: z.string().min(1),
92
+ subject: z.string().min(1),
93
+ status: agentWorkStatusSchema,
94
+ }).passthrough()),
95
+ }).passthrough() satisfies z.ZodType<TaskListWorkOutput>
96
+
97
+ export interface ClaudeEventMapperState {
98
+ readonly toolNameById: Map<string, string>
99
+ readonly pendingWorkCallById: Map<string, PendingClaudeWorkCall>
100
+ readonly startedMessageIds: Set<string>
101
+ readonly endedMessageIds: Set<string>
102
+ readonly streamedTextByMessageId: Map<string, string>
103
+ readonly streamedReasoningMessageIds: Set<string>
104
+ readonly streamedReasoningByMessageId: Map<string, string>
105
+ readonly contentBlockTypeByIndex: Map<number, string>
106
+ readonly subagentByToolCallId: Map<string, ClaudeSubagentActor>
107
+ readonly actorByMessageId: Map<string, AgentEventActor>
108
+ readonly startedReasoningIds: Set<string>
109
+ readonly endedReasoningIds: Set<string>
110
+ turnStarted: boolean
111
+ interrupt: { status: 'none' } | { status: 'requesting' | 'accepted', turnId: string }
112
+ sessionState?: 'running' | 'idle'
113
+ currentTurnId?: string
114
+ currentMessageId?: string
115
+ currentCompactionId?: string
116
+ }
117
+
118
+ export function createClaudeEventMapperState(): ClaudeEventMapperState {
119
+ return {
120
+ toolNameById: new Map(),
121
+ pendingWorkCallById: new Map(),
122
+ startedMessageIds: new Set(),
123
+ endedMessageIds: new Set(),
124
+ streamedTextByMessageId: new Map(),
125
+ streamedReasoningMessageIds: new Set(),
126
+ streamedReasoningByMessageId: new Map(),
127
+ contentBlockTypeByIndex: new Map(),
128
+ subagentByToolCallId: new Map(),
129
+ actorByMessageId: new Map(),
130
+ startedReasoningIds: new Set(),
131
+ endedReasoningIds: new Set(),
132
+ turnStarted: false,
133
+ interrupt: { status: 'none' },
134
+ }
135
+ }
136
+
137
+ export function mapClaudeMessage(message: SDKMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
138
+ const drafts: AgentEventDraft[] = []
139
+
140
+ if (message.type === 'system') {
141
+ if (message.subtype === 'session_state_changed' && message.state === 'running')
142
+ ensureTurnStarted(drafts, state)
143
+ if (message.subtype === 'session_state_changed' && message.state === 'idle')
144
+ ensureSessionIdle(drafts, state)
145
+ if (message.subtype === 'status')
146
+ drafts.push(...mapStatusMessage(message, state))
147
+ if (message.subtype === 'compact_boundary')
148
+ drafts.push(...mapCompactBoundaryMessage(message, state))
149
+ if (message.subtype === 'task_started')
150
+ drafts.push(...mapTaskStartedMessage(message, state))
151
+ if (message.subtype === 'task_progress')
152
+ drafts.push(...mapTaskProgressMessage(message, state))
153
+ if (message.subtype === 'task_notification')
154
+ drafts.push(...mapTaskNotificationMessage(message, state))
155
+ return drafts
156
+ }
157
+
158
+ if (message.type === 'assistant') {
159
+ const turnId = ensureTurnStarted(drafts, state)
160
+ const actor = actorFromParentToolUseId(message.parent_tool_use_id, state)
161
+ ensureAssistantMessageStarted(drafts, state, message.message, turnId, actor)
162
+ drafts.push(...mapAssistantContent(message.message, state, turnId, actor, message.parent_tool_use_id === null))
163
+ if (message.message.stop_reason !== null || shouldEndAssistantSnapshot(message.message, state, actor))
164
+ endAssistantMessage(drafts, state, message.message, turnId, actor)
165
+ return drafts
166
+ }
167
+
168
+ if (message.type === 'stream_event') {
169
+ const turnId = ensureTurnStarted(drafts, state)
170
+ const actor = actorFromParentToolUseId(message.parent_tool_use_id, state)
171
+ drafts.push(...mapStreamEvent(message.event, state, turnId, actor))
172
+ return drafts
173
+ }
174
+
175
+ if (message.type === 'user') {
176
+ const turnId = ensureTurnStarted(drafts, state)
177
+ const actor = actorFromParentToolUseId(message.parent_tool_use_id, state)
178
+ const content = message.message.content
179
+ const messageId = message.uuid ?? `user-${turnId}`
180
+ if (typeof content === 'string')
181
+ return actor ? subagentUserMessageEvents({ messageId, text: content, turnId, actor }) : drafts
182
+ if (actor)
183
+ drafts.push(...subagentUserContentEvents({ messageId, content, turnId, actor }))
184
+ drafts.push(...content.flatMap(block => mapUserContentBlock(block, message, state, turnId, actor, message.parent_tool_use_id === null)))
185
+ return drafts
186
+ }
187
+
188
+ if (message.type === 'result') {
189
+ const turnId = ensureTurnStarted(drafts, state)
190
+ const usage = usageFromObject(message.usage)
191
+ // Claude Agent SDK result usage is per completed ask/turn for the stream-json path we use,
192
+ // not a monotonic counter across the long-lived query. Emit it directly so summing
193
+ // normalized usage events gives the run total; do not diff against prior results.
194
+ if (usage) {
195
+ drafts.push({ type: 'usage', turnId, actor: mainActor, payload: { usage, accounting: {
196
+ observationId: message.uuid,
197
+ mode: 'increment',
198
+ scopeId: turnId,
199
+ includesChildren: true,
200
+ models: Object.fromEntries(Object.entries(message.modelUsage ?? {}).map(([model, counts]) => [model, {
201
+ inputTokens: counts.inputTokens,
202
+ outputTokens: counts.outputTokens,
203
+ cacheReadTokens: counts.cacheReadInputTokens,
204
+ cacheWriteTokens: counts.cacheCreationInputTokens,
205
+ }])),
206
+ } } })
207
+ }
208
+ endOpenReasoning(drafts, state, turnId)
209
+ const interruptAccepted = state.interrupt.status === 'accepted' && state.interrupt.turnId === turnId
210
+ drafts.push({ type: 'turn.ended', turnId, actor: mainActor, payload: turnEndedPayload(message, interruptAccepted) })
211
+ ensureSessionIdle(drafts, state)
212
+ state.pendingWorkCallById.clear()
213
+ state.turnStarted = false
214
+ state.interrupt = { status: 'none' }
215
+ state.currentTurnId = undefined
216
+ state.currentMessageId = undefined
217
+ state.currentCompactionId = undefined
218
+ return drafts
219
+ }
220
+
221
+ return drafts
222
+ }
223
+
224
+ function mapStatusMessage(message: ClaudeStatusMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
225
+ const drafts: AgentEventDraft[] = []
226
+ if (message.status === 'compacting') {
227
+ const turnId = ensureTurnStarted(drafts, state)
228
+ if (state.currentCompactionId)
229
+ return drafts
230
+ state.currentCompactionId = message.uuid
231
+ drafts.push({ type: 'context.compaction.started', turnId, actor: mainActor, payload: { compactionId: state.currentCompactionId, trigger: 'unknown' } })
232
+ return drafts
233
+ }
234
+
235
+ if (message.compact_result === 'failed') {
236
+ const turnId = ensureTurnStarted(drafts, state)
237
+ const compactionId = state.currentCompactionId ?? message.uuid
238
+ drafts.push({
239
+ type: 'context.compaction.completed',
240
+ turnId,
241
+ actor: mainActor,
242
+ payload: {
243
+ compactionId,
244
+ trigger: 'unknown',
245
+ status: 'failed',
246
+ error: { message: message.compact_error ?? 'Claude compaction failed.' },
247
+ },
248
+ })
249
+ state.currentCompactionId = undefined
250
+ }
251
+
252
+ return drafts
253
+ }
254
+
255
+ function mapTaskStartedMessage(message: ClaudeTaskStartedMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
256
+ const drafts: AgentEventDraft[] = []
257
+ const turnId = ensureTurnStarted(drafts, state)
258
+ const actor = subagentActorFromTask({ subagentId: message.task_id, parentToolCallId: message.tool_use_id, name: message.subagent_type })
259
+ if (message.tool_use_id)
260
+ state.subagentByToolCallId.set(message.tool_use_id, actor)
261
+ drafts.push({
262
+ type: 'subagent.started',
263
+ turnId,
264
+ actor,
265
+ payload: removeUndefined({
266
+ ...subagentPayloadRef(actor),
267
+ description: message.description,
268
+ }),
269
+ })
270
+ return drafts
271
+ }
272
+
273
+ function mapTaskProgressMessage(message: ClaudeTaskProgressMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
274
+ const drafts: AgentEventDraft[] = []
275
+ const turnId = ensureTurnStarted(drafts, state)
276
+ const actor = subagentActorForTaskMessage({ subagentId: message.task_id, parentToolCallId: message.tool_use_id, name: message.subagent_type }, state)
277
+ drafts.push({
278
+ type: 'subagent.progress',
279
+ turnId,
280
+ actor,
281
+ payload: removeUndefined({
282
+ ...subagentPayloadRef(actor),
283
+ summary: optionalNonEmpty(message.summary),
284
+ lastToolName: optionalNonEmpty(message.last_tool_name),
285
+ usage: usageFromTaskUsage(message.usage),
286
+ }),
287
+ })
288
+ return drafts
289
+ }
290
+
291
+ function mapTaskNotificationMessage(message: ClaudeTaskNotificationMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
292
+ const drafts: AgentEventDraft[] = []
293
+ const turnId = ensureTurnStarted(drafts, state)
294
+ const actor = subagentActorForTaskMessage({ subagentId: message.task_id, parentToolCallId: message.tool_use_id }, state)
295
+ drafts.push({
296
+ type: 'subagent.ended',
297
+ turnId,
298
+ actor,
299
+ payload: removeUndefined({
300
+ ...subagentPayloadRef(actor),
301
+ status: taskNotificationStatus(message.status),
302
+ summary: optionalNonEmpty(message.summary),
303
+ usage: message.usage ? usageFromTaskUsage(message.usage) : undefined,
304
+ error: message.status === 'failed' ? { message: optionalNonEmpty(message.summary) ?? `Subagent ${message.task_id} failed.` } : undefined,
305
+ }),
306
+ })
307
+ return drafts
308
+ }
309
+
310
+ function mapCompactBoundaryMessage(message: ClaudeCompactBoundaryMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
311
+ const drafts: AgentEventDraft[] = []
312
+ const turnId = ensureTurnStarted(drafts, state)
313
+ const compactionId = state.currentCompactionId ?? message.uuid
314
+ drafts.push({
315
+ type: 'context.compaction.completed',
316
+ turnId,
317
+ actor: mainActor,
318
+ payload: removeUndefined({
319
+ compactionId,
320
+ trigger: message.compact_metadata.trigger,
321
+ status: 'completed' as const,
322
+ duration: message.compact_metadata.duration_ms,
323
+ inputTokensBefore: message.compact_metadata.pre_tokens,
324
+ inputTokensAfter: message.compact_metadata.post_tokens,
325
+ }),
326
+ })
327
+ state.currentCompactionId = undefined
328
+ return drafts
329
+ }
330
+
331
+ function mapAssistantContent(message: BetaMessage, state: ClaudeEventMapperState, turnId: string, actor: AgentEventActor | undefined, isMain: boolean): AgentEventDraft[] {
332
+ const drafts: AgentEventDraft[] = []
333
+ const streamedText = state.streamedTextByMessageId.has(message.id)
334
+ const streamedReasoning = state.streamedReasoningMessageIds.has(message.id)
335
+ for (const block of message.content) {
336
+ if (block.type === 'text' && !streamedText) {
337
+ drafts.push(withActor({ type: 'message.delta', turnId, payload: { messageId: message.id, role: 'assistant', delta: block.text } }, actor))
338
+ }
339
+ if (block.type === 'thinking' && !streamedReasoning) {
340
+ const reasoningId = ensureReasoningStarted(drafts, state, turnId, message.id, actor)
341
+ const text = optionalNonEmpty(block.thinking)
342
+ if (text)
343
+ drafts.push(withActor({ type: 'reasoning.summary.delta', turnId, payload: { reasoningId, text } }, actor))
344
+ }
345
+ if (block.type === 'tool_use') {
346
+ const toolName = normalizeToolName(block.name)
347
+ state.toolNameById.set(block.id, toolName)
348
+ if (isMain) {
349
+ const pendingWorkCall = pendingClaudeWorkCall(block.name, block.input)
350
+ if (pendingWorkCall)
351
+ state.pendingWorkCallById.set(block.id, pendingWorkCall)
352
+ }
353
+ drafts.push(withActor({ type: 'tool.call.started', turnId, payload: { toolCallId: block.id, toolName, input: jsonInput(block.input) } }, actor))
354
+ }
355
+ }
356
+ return drafts
357
+ }
358
+
359
+ function mapUserContentBlock(block: UserContentBlock, message: ClaudeUserMessage, state: ClaudeEventMapperState, turnId: string, actor: AgentEventActor | undefined, isMain: boolean): AgentEventDraft[] {
360
+ if (block.type !== 'tool_result')
361
+ return []
362
+ const toolCallId = block.tool_use_id
363
+ const pendingWorkCall = state.pendingWorkCallById.get(toolCallId)
364
+ if (pendingWorkCall)
365
+ state.pendingWorkCallById.delete(toolCallId)
366
+
367
+ const text = textFromToolResultContent(block.content)
368
+ const output = { success: block.is_error !== true, content: text ? [{ type: 'text' as const, text }] : [] }
369
+ const drafts: AgentEventDraft[] = []
370
+ if (text)
371
+ drafts.push(withActor({ type: 'tool.output.delta', turnId, payload: { toolCallId, delta: text } }, actor))
372
+ drafts.push(withActor({
373
+ type: 'tool.call.completed',
374
+ turnId,
375
+ payload: removeUndefined({
376
+ toolCallId,
377
+ status: block.is_error === true ? 'failed' : 'completed',
378
+ output,
379
+ error: block.is_error === true ? { message: text || `Tool ${state.toolNameById.get(toolCallId) ?? toolCallId} failed.` } : undefined,
380
+ }),
381
+ }, actor))
382
+
383
+ if (!pendingWorkCall || block.is_error === true || !isMain)
384
+ return drafts
385
+
386
+ const observations = workObservationsFromClaudeResult(pendingWorkCall, message.tool_use_result)
387
+ if (observations.length > 0)
388
+ drafts.push({ type: 'work.observed', turnId, actor: mainActor, payload: { observations } })
389
+ return drafts
390
+ }
391
+
392
+ function pendingClaudeWorkCall(name: string, input: unknown): PendingClaudeWorkCall | undefined {
393
+ if (name === 'TaskCreate') {
394
+ const parsed = taskCreateWorkInputSchema.safeParse(input)
395
+ return parsed.success ? { kind: 'create', input: parsed.data } : undefined
396
+ }
397
+ if (name === 'TaskGet') {
398
+ const parsed = taskGetWorkInputSchema.safeParse(input)
399
+ return parsed.success ? { kind: 'get', input: parsed.data } : undefined
400
+ }
401
+ if (name === 'TaskUpdate') {
402
+ const parsed = taskUpdateWorkInputSchema.safeParse(input)
403
+ return parsed.success ? { kind: 'update', input: parsed.data } : undefined
404
+ }
405
+ if (name === 'TaskList') {
406
+ const parsed = taskListWorkInputSchema.safeParse(input)
407
+ return parsed.success ? { kind: 'list', input: parsed.data } : undefined
408
+ }
409
+ return undefined
410
+ }
411
+
412
+ function workObservationsFromClaudeResult(call: PendingClaudeWorkCall, structuredResult: unknown): AgentWorkObservation[] {
413
+ if (call.kind === 'create') {
414
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskCreate', schema: taskCreateWorkOutputSchema })
415
+ return [{ kind: 'item_observed', item: { id: output.task.id, title: output.task.subject, status: 'pending' } }]
416
+ }
417
+ if (call.kind === 'get') {
418
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskGet', schema: taskGetWorkOutputSchema })
419
+ return output.task
420
+ ? [{ kind: 'item_observed', item: { id: output.task.id, title: output.task.subject, status: output.task.status } }]
421
+ : [{ kind: 'item_removed', itemId: call.input.taskId }]
422
+ }
423
+ if (call.kind === 'list') {
424
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskList', schema: taskListWorkOutputSchema })
425
+ return [{
426
+ kind: 'snapshot',
427
+ items: output.tasks.map(task => ({ id: task.id, title: task.subject, status: task.status })),
428
+ }]
429
+ }
430
+
431
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskUpdate', schema: taskUpdateWorkOutputSchema })
432
+ if (!output.success)
433
+ return []
434
+
435
+ const observations: AgentWorkObservation[] = []
436
+ if (output.updatedFields.includes('subject') && call.input.subject)
437
+ observations.push({ kind: 'item_renamed', itemId: output.taskId, title: call.input.subject })
438
+ if (output.updatedFields.includes('status') && output.statusChange) {
439
+ if (output.statusChange.to === 'deleted')
440
+ observations.push({ kind: 'item_removed', itemId: output.taskId })
441
+ else observations.push({ kind: 'item_status_changed', itemId: output.taskId, status: output.statusChange.to })
442
+ }
443
+ return observations
444
+ }
445
+
446
+ function parseClaudeTaskResult<Output>({ value, toolName, schema }: { value: unknown, toolName: string, schema: z.ZodType<Output> }): Output {
447
+ const parsed = schema.safeParse(value)
448
+ if (parsed.success)
449
+ return parsed.data
450
+ throw new AgentProviderProtocolError({
451
+ message: `Claude ${toolName} returned missing or malformed structured output.`,
452
+ cause: parsed.error,
453
+ })
454
+ }
455
+
456
+ function mapStreamEvent(event: BetaRawMessageStreamEvent, state: ClaudeEventMapperState, turnId: string, actor: AgentEventActor | undefined): AgentEventDraft[] {
457
+ const drafts: AgentEventDraft[] = []
458
+ if (event.type === 'message_start') {
459
+ state.currentMessageId = event.message.id
460
+ if (actor)
461
+ state.actorByMessageId.set(event.message.id, actor)
462
+ ensureAssistantMessageStarted(drafts, state, event.message, turnId, actor)
463
+ return drafts
464
+ }
465
+
466
+ if (event.type === 'content_block_start') {
467
+ const messageId = currentMessageId(state)
468
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
469
+ state.contentBlockTypeByIndex.set(event.index, event.content_block.type)
470
+ if (event.content_block.type === 'thinking') {
471
+ state.streamedReasoningMessageIds.add(messageId)
472
+ ensureReasoningStarted(drafts, state, turnId, messageId, messageActor)
473
+ }
474
+ return drafts
475
+ }
476
+
477
+ if (event.type === 'content_block_stop') {
478
+ const messageId = currentMessageId(state)
479
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
480
+ const blockType = state.contentBlockTypeByIndex.get(event.index)
481
+ if (blockType === 'thinking')
482
+ endReasoning(drafts, state, turnId, messageId, state.streamedReasoningByMessageId.get(messageId), messageActor)
483
+ state.contentBlockTypeByIndex.delete(event.index)
484
+ return drafts
485
+ }
486
+
487
+ if (event.type === 'message_stop') {
488
+ const messageId = currentMessageId(state)
489
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
490
+ endAssistantMessageById(drafts, state, turnId, messageId, state.streamedTextByMessageId.get(messageId), messageActor)
491
+ state.actorByMessageId.delete(messageId)
492
+ state.currentMessageId = undefined
493
+ state.contentBlockTypeByIndex.clear()
494
+ return drafts
495
+ }
496
+
497
+ if (event.type !== 'content_block_delta')
498
+ return drafts
499
+
500
+ const messageId = currentMessageId(state)
501
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
502
+ const delta = event.delta
503
+ if (delta.type === 'thinking_delta') {
504
+ state.streamedReasoningMessageIds.add(messageId)
505
+ state.streamedReasoningByMessageId.set(messageId, `${state.streamedReasoningByMessageId.get(messageId) ?? ''}${delta.thinking}`)
506
+ const reasoningId = ensureReasoningStarted(drafts, state, turnId, messageId, messageActor)
507
+ if (delta.thinking)
508
+ drafts.push(withActor({ type: 'reasoning.summary.delta', turnId, payload: { reasoningId, text: delta.thinking } }, messageActor))
509
+ }
510
+ if (delta.type === 'text_delta') {
511
+ state.streamedTextByMessageId.set(messageId, `${state.streamedTextByMessageId.get(messageId) ?? ''}${delta.text}`)
512
+ drafts.push(withActor({ type: 'message.delta', turnId, payload: { messageId, role: 'assistant', delta: delta.text } }, messageActor))
513
+ }
514
+ return drafts
515
+ }
516
+
517
+ function ensureTurnStarted(drafts: AgentEventDraft[], state: ClaudeEventMapperState): string {
518
+ if (state.turnStarted && state.currentTurnId)
519
+ return state.currentTurnId
520
+ state.turnStarted = true
521
+ state.interrupt = { status: 'none' }
522
+ state.currentTurnId = createRuntimeTurnId()
523
+ state.currentMessageId = undefined
524
+ state.contentBlockTypeByIndex.clear()
525
+ drafts.push({ type: 'turn.started', turnId: state.currentTurnId, actor: mainActor, payload: {} })
526
+ ensureSessionRunning(drafts, state)
527
+ return state.currentTurnId
528
+ }
529
+
530
+ function ensureSessionRunning(drafts: AgentEventDraft[], state: ClaudeEventMapperState): void {
531
+ if (state.sessionState === 'running')
532
+ return
533
+ state.sessionState = 'running'
534
+ drafts.push({ type: 'session.state.changed', payload: { state: 'running' } })
535
+ }
536
+
537
+ function ensureSessionIdle(drafts: AgentEventDraft[], state: ClaudeEventMapperState): void {
538
+ if (state.sessionState === 'idle')
539
+ return
540
+ state.sessionState = 'idle'
541
+ drafts.push({ type: 'session.state.changed', payload: { state: 'idle' } })
542
+ }
543
+
544
+ function ensureAssistantMessageStarted(drafts: AgentEventDraft[], state: ClaudeEventMapperState, message: BetaMessage, turnId: string, actor: AgentEventActor | undefined): void {
545
+ state.currentMessageId = message.id
546
+ if (actor)
547
+ state.actorByMessageId.set(message.id, actor)
548
+ if (state.startedMessageIds.has(message.id))
549
+ return
550
+ state.startedMessageIds.add(message.id)
551
+ drafts.push(withActor({ type: 'message.started', turnId, payload: { messageId: message.id, role: 'assistant' } }, actor))
552
+ }
553
+
554
+ function currentMessageId(state: ClaudeEventMapperState): string {
555
+ if (state.currentMessageId)
556
+ return state.currentMessageId
557
+ throw new AgentProviderProtocolError({ message: 'Claude stream emitted content_block_delta before message_start.' })
558
+ }
559
+
560
+ function endAssistantMessage(drafts: AgentEventDraft[], state: ClaudeEventMapperState, message: BetaMessage, turnId: string, actor: AgentEventActor | undefined): void {
561
+ if (state.endedMessageIds.has(message.id))
562
+ return
563
+ endReasoning(drafts, state, turnId, message.id, thinkingFromAssistantContent(message.content), actor)
564
+ endAssistantMessageById(drafts, state, turnId, message.id, textFromAssistantContent(message.content), actor)
565
+ }
566
+
567
+ function endAssistantMessageById(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string, messageId: string, text: string | undefined, actor: AgentEventActor | undefined): void {
568
+ if (state.endedMessageIds.has(messageId))
569
+ return
570
+ state.endedMessageIds.add(messageId)
571
+ drafts.push(withActor({ type: 'message.ended', turnId, payload: removeUndefined({ messageId, role: 'assistant' as const, text: optionalNonEmpty(text) }) }, actor))
572
+ }
573
+
574
+ function ensureReasoningStarted(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string, messageId: string, actor: AgentEventActor | undefined): string {
575
+ const reasoningId = reasoningIdForMessage(messageId)
576
+ if (!state.startedReasoningIds.has(reasoningId)) {
577
+ state.startedReasoningIds.add(reasoningId)
578
+ drafts.push(withActor({ type: 'reasoning.started', turnId, payload: { reasoningId } }, actor))
579
+ }
580
+ return reasoningId
581
+ }
582
+
583
+ function endReasoning(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string, messageId: string, summary: string | undefined, actor: AgentEventActor | undefined): void {
584
+ const reasoningId = reasoningIdForMessage(messageId)
585
+ if (!state.startedReasoningIds.has(reasoningId) || state.endedReasoningIds.has(reasoningId))
586
+ return
587
+ state.endedReasoningIds.add(reasoningId)
588
+ drafts.push(withActor({ type: 'reasoning.ended', turnId, payload: removeUndefined({ reasoningId, summary: optionalNonEmpty(summary) }) }, actor))
589
+ }
590
+
591
+ function endOpenReasoning(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string): void {
592
+ for (const reasoningId of state.startedReasoningIds) {
593
+ if (!state.endedReasoningIds.has(reasoningId)) {
594
+ state.endedReasoningIds.add(reasoningId)
595
+ drafts.push({ type: 'reasoning.ended', turnId, actor: mainActor, payload: { reasoningId } })
596
+ }
597
+ }
598
+ }
599
+
600
+ function reasoningIdForMessage(messageId: string): string {
601
+ return `reasoning-${messageId}`
602
+ }
603
+
604
+ function turnEndedPayload(message: Extract<SDKMessage, { type: 'result' }>, interruptAccepted: boolean): Extract<AgentEventDraft, { type: 'turn.ended' }>['payload'] {
605
+ if (interruptAccepted && message.subtype === 'error_during_execution') {
606
+ return removeUndefined({
607
+ status: 'interrupted' as const,
608
+ reason: 'interrupted',
609
+ duration: message.duration_ms,
610
+ })
611
+ }
612
+
613
+ if (message.subtype === 'success' && message.is_error !== true) {
614
+ return removeUndefined({
615
+ status: 'completed' as const,
616
+ reason: message.stop_reason ?? message.subtype,
617
+ duration: message.duration_ms,
618
+ })
619
+ }
620
+
621
+ return removeUndefined({
622
+ status: 'failed' as const,
623
+ reason: message.stop_reason ?? message.subtype,
624
+ duration: message.duration_ms,
625
+ error: resultError(message),
626
+ })
627
+ }
628
+
629
+ function resultError(message: Extract<SDKMessage, { type: 'result' }>): AgentError {
630
+ const errors = 'errors' in message && Array.isArray(message.errors) ? message.errors : []
631
+ return {
632
+ message: errors.length > 0 ? errors.join('\n') : `Claude result ended with ${message.subtype}.`,
633
+ code: message.subtype,
634
+ }
635
+ }
636
+
637
+ function shouldEndAssistantSnapshot(message: BetaMessage, state: ClaudeEventMapperState, actor: ClaudeSubagentActor | undefined): boolean {
638
+ return actor !== undefined
639
+ && !state.streamedTextByMessageId.has(message.id)
640
+ && !state.streamedReasoningMessageIds.has(message.id)
641
+ }
642
+
643
+ function actorFromParentToolUseId(parentToolUseId: string | null, state: ClaudeEventMapperState): ClaudeSubagentActor | undefined {
644
+ return parentToolUseId ? state.subagentByToolCallId.get(parentToolUseId) : undefined
645
+ }
646
+
647
+ function subagentActorForTaskMessage(input: { subagentId: string, parentToolCallId?: string, name?: string }, state: ClaudeEventMapperState): ClaudeSubagentActor {
648
+ const existing = input.parentToolCallId ? state.subagentByToolCallId.get(input.parentToolCallId) : undefined
649
+ return existing ?? subagentActorFromTask(input)
650
+ }
651
+
652
+ function subagentActorFromTask(input: { subagentId: string, parentToolCallId?: string, name?: string }): ClaudeSubagentActor {
653
+ return removeUndefined({
654
+ type: 'subagent' as const,
655
+ actorId: input.subagentId,
656
+ subagentId: input.subagentId,
657
+ parentActorId: 'main',
658
+ origin: input.parentToolCallId ? { type: 'tool_call' as const, toolCallId: input.parentToolCallId } : { type: 'provider_task' as const, taskId: input.subagentId },
659
+ parentToolCallId: input.parentToolCallId,
660
+ name: input.name,
661
+ })
662
+ }
663
+
664
+ function subagentPayloadRef(actor: ClaudeSubagentActor): Pick<ClaudeSubagentActor, 'actorId' | 'subagentId' | 'parentActorId' | 'origin' | 'parentToolCallId' | 'name'> {
665
+ return removeUndefined({
666
+ actorId: actor.actorId,
667
+ subagentId: actor.subagentId,
668
+ parentActorId: actor.parentActorId,
669
+ origin: actor.origin,
670
+ parentToolCallId: actor.parentToolCallId,
671
+ name: actor.name,
672
+ })
673
+ }
674
+
675
+ function subagentUserContentEvents(input: { messageId: string, content: UserContentBlock[], turnId: string, actor: ClaudeSubagentActor }): AgentEventDraft[] {
676
+ const text = input.content.map(block => block.type === 'text' ? block.text : '').join('')
677
+ return text ? subagentUserMessageEvents({ messageId: input.messageId, text, turnId: input.turnId, actor: input.actor }) : []
678
+ }
679
+
680
+ function subagentUserMessageEvents(input: { messageId: string, text: string, turnId: string, actor: ClaudeSubagentActor }): AgentEventDraft[] {
681
+ return [
682
+ withActor({ type: 'message.started', turnId: input.turnId, payload: { messageId: input.messageId, role: 'user' } }, input.actor),
683
+ withActor({ type: 'message.delta', turnId: input.turnId, payload: { messageId: input.messageId, role: 'user', delta: input.text } }, input.actor),
684
+ withActor({ type: 'message.ended', turnId: input.turnId, payload: { messageId: input.messageId, role: 'user', text: input.text } }, input.actor),
685
+ ]
686
+ }
687
+
688
+ function taskNotificationStatus(status: ClaudeTaskNotificationMessage['status']): 'completed' | 'failed' | 'cancelled' {
689
+ if (status === 'completed')
690
+ return 'completed'
691
+ if (status === 'failed')
692
+ return 'failed'
693
+ return 'cancelled'
694
+ }
695
+
696
+ function usageFromTaskUsage(usage: ClaudeTaskProgressMessage['usage']): AgentUsage {
697
+ return { totalTokens: usage.total_tokens }
698
+ }
699
+
700
+ function withActor(draft: ClaudeUnownedTurnEventDraft, actor: AgentEventActor | undefined): AgentEventDraft {
701
+ return { ...draft, actor: actor ?? mainActor }
702
+ }
703
+
704
+ const CLAUDE_RUNTIME_TOOL_PREFIX = `mcp__${CLAUDE_AGENT_TOOL_SERVER}__`
705
+
706
+ function normalizeToolName(name: string): string {
707
+ // Our runtime tools arrive as mcp__coding_agent__<tool>; strip the prefix so they match Codex.
708
+ const local = name.startsWith(CLAUDE_RUNTIME_TOOL_PREFIX) ? name.slice(CLAUDE_RUNTIME_TOOL_PREFIX.length) : name
709
+ return canonicalToolName(local)
710
+ }
711
+
712
+ function usageFromObject(value: BetaUsage | undefined): AgentUsage | undefined {
713
+ if (!value)
714
+ return undefined
715
+ return removeUndefined({
716
+ inputTokens: value.input_tokens,
717
+ outputTokens: value.output_tokens,
718
+ cacheReadTokens: value.cache_read_input_tokens ?? undefined,
719
+ cacheWriteTokens: value.cache_creation_input_tokens ?? undefined,
720
+ totalTokens: value.input_tokens + value.output_tokens + (value.cache_read_input_tokens ?? 0) + (value.cache_creation_input_tokens ?? 0),
721
+ })
722
+ }
723
+
724
+ function textFromAssistantContent(content: BetaMessage['content']): string | undefined {
725
+ const text = content.map(block => block.type === 'text' ? block.text : '').join('')
726
+ return text || undefined
727
+ }
728
+
729
+ function thinkingFromAssistantContent(content: BetaMessage['content']): string | undefined {
730
+ const text = content.map(block => block.type === 'thinking' ? block.thinking : '').join('')
731
+ return text || undefined
732
+ }
733
+
734
+ function textFromToolResultContent(content: UserToolResultBlock['content']): string | undefined {
735
+ if (!content)
736
+ return undefined
737
+ if (typeof content === 'string')
738
+ return content
739
+ const text = content.map(block => block.type === 'text' ? block.text : '').join('\n')
740
+ return text || undefined
741
+ }
742
+
743
+ function jsonInput(value: unknown): JsonValue {
744
+ const parsed = jsonValueSchema.safeParse(value)
745
+ if (parsed.success)
746
+ return parsed.data
747
+ return {}
748
+ }