@meistrari/agent-core 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +24 -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 +736 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +464 -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 +708 -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/provenance.gen.ts +3 -3
  48. package/src/supervisor/agent-provider-factory.ts +189 -0
  49. package/src/supervisor/bootstrap-binder.ts +125 -0
  50. package/src/supervisor/config.ts +49 -0
  51. package/src/supervisor/control-authority-verifier.ts +135 -0
  52. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  53. package/src/supervisor/errors.ts +24 -0
  54. package/src/supervisor/index.ts +34 -0
  55. package/src/supervisor/persistence/json.ts +21 -0
  56. package/src/supervisor/persistence/state-discovery.ts +56 -0
  57. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  58. package/src/supervisor/ports/index.ts +109 -0
  59. package/src/supervisor/provider-factory.ts +37 -0
  60. package/src/supervisor/resident.ts +143 -0
  61. package/src/supervisor/rpc-client.ts +120 -0
  62. package/src/supervisor/runtime-handler.ts +309 -0
  63. package/src/supervisor/websocket-server.ts +434 -0
  64. package/src/supervisor-protocol/bootstrap.ts +1 -1
  65. package/src/template-onboarding.ts +47 -0
  66. package/src/testing/es256-test-keys.ts +73 -0
  67. package/src/testing/in-memory-runtime-control-plane.ts +205 -0
  68. package/src/testing/index.ts +6 -0
  69. package/src/testing/loopback-supervisor-connection.ts +71 -0
  70. package/src/testing/scripted-provider.ts +64 -0
  71. package/src/worker-runtime-client/command-pump.ts +132 -0
  72. package/src/worker-runtime-client/connection-attempt.ts +340 -0
  73. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  74. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  75. package/src/worker-runtime-client/frame-processor.ts +178 -0
  76. package/src/worker-runtime-client/index.ts +27 -0
  77. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  78. package/src/worker-runtime-client/ports.ts +137 -0
  79. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  80. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  81. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  82. package/src/worker-runtime-client/sandbox-connection-runtime.ts +300 -0
  83. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,736 @@
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 } })
196
+ endOpenReasoning(drafts, state, turnId)
197
+ const interruptAccepted = state.interrupt.status === 'accepted' && state.interrupt.turnId === turnId
198
+ drafts.push({ type: 'turn.ended', turnId, actor: mainActor, payload: turnEndedPayload(message, interruptAccepted) })
199
+ ensureSessionIdle(drafts, state)
200
+ state.pendingWorkCallById.clear()
201
+ state.turnStarted = false
202
+ state.interrupt = { status: 'none' }
203
+ state.currentTurnId = undefined
204
+ state.currentMessageId = undefined
205
+ state.currentCompactionId = undefined
206
+ return drafts
207
+ }
208
+
209
+ return drafts
210
+ }
211
+
212
+ function mapStatusMessage(message: ClaudeStatusMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
213
+ const drafts: AgentEventDraft[] = []
214
+ if (message.status === 'compacting') {
215
+ const turnId = ensureTurnStarted(drafts, state)
216
+ if (state.currentCompactionId)
217
+ return drafts
218
+ state.currentCompactionId = message.uuid
219
+ drafts.push({ type: 'context.compaction.started', turnId, actor: mainActor, payload: { compactionId: state.currentCompactionId, trigger: 'unknown' } })
220
+ return drafts
221
+ }
222
+
223
+ if (message.compact_result === 'failed') {
224
+ const turnId = ensureTurnStarted(drafts, state)
225
+ const compactionId = state.currentCompactionId ?? message.uuid
226
+ drafts.push({
227
+ type: 'context.compaction.completed',
228
+ turnId,
229
+ actor: mainActor,
230
+ payload: {
231
+ compactionId,
232
+ trigger: 'unknown',
233
+ status: 'failed',
234
+ error: { message: message.compact_error ?? 'Claude compaction failed.' },
235
+ },
236
+ })
237
+ state.currentCompactionId = undefined
238
+ }
239
+
240
+ return drafts
241
+ }
242
+
243
+ function mapTaskStartedMessage(message: ClaudeTaskStartedMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
244
+ const drafts: AgentEventDraft[] = []
245
+ const turnId = ensureTurnStarted(drafts, state)
246
+ const actor = subagentActorFromTask({ subagentId: message.task_id, parentToolCallId: message.tool_use_id, name: message.subagent_type })
247
+ if (message.tool_use_id)
248
+ state.subagentByToolCallId.set(message.tool_use_id, actor)
249
+ drafts.push({
250
+ type: 'subagent.started',
251
+ turnId,
252
+ actor,
253
+ payload: removeUndefined({
254
+ ...subagentPayloadRef(actor),
255
+ description: message.description,
256
+ }),
257
+ })
258
+ return drafts
259
+ }
260
+
261
+ function mapTaskProgressMessage(message: ClaudeTaskProgressMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
262
+ const drafts: AgentEventDraft[] = []
263
+ const turnId = ensureTurnStarted(drafts, state)
264
+ const actor = subagentActorForTaskMessage({ subagentId: message.task_id, parentToolCallId: message.tool_use_id, name: message.subagent_type }, state)
265
+ drafts.push({
266
+ type: 'subagent.progress',
267
+ turnId,
268
+ actor,
269
+ payload: removeUndefined({
270
+ ...subagentPayloadRef(actor),
271
+ summary: optionalNonEmpty(message.summary),
272
+ lastToolName: optionalNonEmpty(message.last_tool_name),
273
+ usage: usageFromTaskUsage(message.usage),
274
+ }),
275
+ })
276
+ return drafts
277
+ }
278
+
279
+ function mapTaskNotificationMessage(message: ClaudeTaskNotificationMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
280
+ const drafts: AgentEventDraft[] = []
281
+ const turnId = ensureTurnStarted(drafts, state)
282
+ const actor = subagentActorForTaskMessage({ subagentId: message.task_id, parentToolCallId: message.tool_use_id }, state)
283
+ drafts.push({
284
+ type: 'subagent.ended',
285
+ turnId,
286
+ actor,
287
+ payload: removeUndefined({
288
+ ...subagentPayloadRef(actor),
289
+ status: taskNotificationStatus(message.status),
290
+ summary: optionalNonEmpty(message.summary),
291
+ usage: message.usage ? usageFromTaskUsage(message.usage) : undefined,
292
+ error: message.status === 'failed' ? { message: optionalNonEmpty(message.summary) ?? `Subagent ${message.task_id} failed.` } : undefined,
293
+ }),
294
+ })
295
+ return drafts
296
+ }
297
+
298
+ function mapCompactBoundaryMessage(message: ClaudeCompactBoundaryMessage, state: ClaudeEventMapperState): AgentEventDraft[] {
299
+ const drafts: AgentEventDraft[] = []
300
+ const turnId = ensureTurnStarted(drafts, state)
301
+ const compactionId = state.currentCompactionId ?? message.uuid
302
+ drafts.push({
303
+ type: 'context.compaction.completed',
304
+ turnId,
305
+ actor: mainActor,
306
+ payload: removeUndefined({
307
+ compactionId,
308
+ trigger: message.compact_metadata.trigger,
309
+ status: 'completed' as const,
310
+ duration: message.compact_metadata.duration_ms,
311
+ inputTokensBefore: message.compact_metadata.pre_tokens,
312
+ inputTokensAfter: message.compact_metadata.post_tokens,
313
+ }),
314
+ })
315
+ state.currentCompactionId = undefined
316
+ return drafts
317
+ }
318
+
319
+ function mapAssistantContent(message: BetaMessage, state: ClaudeEventMapperState, turnId: string, actor: AgentEventActor | undefined, isMain: boolean): AgentEventDraft[] {
320
+ const drafts: AgentEventDraft[] = []
321
+ const streamedText = state.streamedTextByMessageId.has(message.id)
322
+ const streamedReasoning = state.streamedReasoningMessageIds.has(message.id)
323
+ for (const block of message.content) {
324
+ if (block.type === 'text' && !streamedText) {
325
+ drafts.push(withActor({ type: 'message.delta', turnId, payload: { messageId: message.id, role: 'assistant', delta: block.text } }, actor))
326
+ }
327
+ if (block.type === 'thinking' && !streamedReasoning) {
328
+ const reasoningId = ensureReasoningStarted(drafts, state, turnId, message.id, actor)
329
+ const text = optionalNonEmpty(block.thinking)
330
+ if (text)
331
+ drafts.push(withActor({ type: 'reasoning.summary.delta', turnId, payload: { reasoningId, text } }, actor))
332
+ }
333
+ if (block.type === 'tool_use') {
334
+ const toolName = normalizeToolName(block.name)
335
+ state.toolNameById.set(block.id, toolName)
336
+ if (isMain) {
337
+ const pendingWorkCall = pendingClaudeWorkCall(block.name, block.input)
338
+ if (pendingWorkCall)
339
+ state.pendingWorkCallById.set(block.id, pendingWorkCall)
340
+ }
341
+ drafts.push(withActor({ type: 'tool.call.started', turnId, payload: { toolCallId: block.id, toolName, input: jsonInput(block.input) } }, actor))
342
+ }
343
+ }
344
+ return drafts
345
+ }
346
+
347
+ function mapUserContentBlock(block: UserContentBlock, message: ClaudeUserMessage, state: ClaudeEventMapperState, turnId: string, actor: AgentEventActor | undefined, isMain: boolean): AgentEventDraft[] {
348
+ if (block.type !== 'tool_result')
349
+ return []
350
+ const toolCallId = block.tool_use_id
351
+ const pendingWorkCall = state.pendingWorkCallById.get(toolCallId)
352
+ if (pendingWorkCall)
353
+ state.pendingWorkCallById.delete(toolCallId)
354
+
355
+ const text = textFromToolResultContent(block.content)
356
+ const output = { success: block.is_error !== true, content: text ? [{ type: 'text' as const, text }] : [] }
357
+ const drafts: AgentEventDraft[] = []
358
+ if (text)
359
+ drafts.push(withActor({ type: 'tool.output.delta', turnId, payload: { toolCallId, delta: text } }, actor))
360
+ drafts.push(withActor({
361
+ type: 'tool.call.completed',
362
+ turnId,
363
+ payload: removeUndefined({
364
+ toolCallId,
365
+ status: block.is_error === true ? 'failed' : 'completed',
366
+ output,
367
+ error: block.is_error === true ? { message: text || `Tool ${state.toolNameById.get(toolCallId) ?? toolCallId} failed.` } : undefined,
368
+ }),
369
+ }, actor))
370
+
371
+ if (!pendingWorkCall || block.is_error === true || !isMain)
372
+ return drafts
373
+
374
+ const observations = workObservationsFromClaudeResult(pendingWorkCall, message.tool_use_result)
375
+ if (observations.length > 0)
376
+ drafts.push({ type: 'work.observed', turnId, actor: mainActor, payload: { observations } })
377
+ return drafts
378
+ }
379
+
380
+ function pendingClaudeWorkCall(name: string, input: unknown): PendingClaudeWorkCall | undefined {
381
+ if (name === 'TaskCreate') {
382
+ const parsed = taskCreateWorkInputSchema.safeParse(input)
383
+ return parsed.success ? { kind: 'create', input: parsed.data } : undefined
384
+ }
385
+ if (name === 'TaskGet') {
386
+ const parsed = taskGetWorkInputSchema.safeParse(input)
387
+ return parsed.success ? { kind: 'get', input: parsed.data } : undefined
388
+ }
389
+ if (name === 'TaskUpdate') {
390
+ const parsed = taskUpdateWorkInputSchema.safeParse(input)
391
+ return parsed.success ? { kind: 'update', input: parsed.data } : undefined
392
+ }
393
+ if (name === 'TaskList') {
394
+ const parsed = taskListWorkInputSchema.safeParse(input)
395
+ return parsed.success ? { kind: 'list', input: parsed.data } : undefined
396
+ }
397
+ return undefined
398
+ }
399
+
400
+ function workObservationsFromClaudeResult(call: PendingClaudeWorkCall, structuredResult: unknown): AgentWorkObservation[] {
401
+ if (call.kind === 'create') {
402
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskCreate', schema: taskCreateWorkOutputSchema })
403
+ return [{ kind: 'item_observed', item: { id: output.task.id, title: output.task.subject, status: 'pending' } }]
404
+ }
405
+ if (call.kind === 'get') {
406
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskGet', schema: taskGetWorkOutputSchema })
407
+ return output.task
408
+ ? [{ kind: 'item_observed', item: { id: output.task.id, title: output.task.subject, status: output.task.status } }]
409
+ : [{ kind: 'item_removed', itemId: call.input.taskId }]
410
+ }
411
+ if (call.kind === 'list') {
412
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskList', schema: taskListWorkOutputSchema })
413
+ return [{
414
+ kind: 'snapshot',
415
+ items: output.tasks.map(task => ({ id: task.id, title: task.subject, status: task.status })),
416
+ }]
417
+ }
418
+
419
+ const output = parseClaudeTaskResult({ value: structuredResult, toolName: 'TaskUpdate', schema: taskUpdateWorkOutputSchema })
420
+ if (!output.success)
421
+ return []
422
+
423
+ const observations: AgentWorkObservation[] = []
424
+ if (output.updatedFields.includes('subject') && call.input.subject)
425
+ observations.push({ kind: 'item_renamed', itemId: output.taskId, title: call.input.subject })
426
+ if (output.updatedFields.includes('status') && output.statusChange) {
427
+ if (output.statusChange.to === 'deleted')
428
+ observations.push({ kind: 'item_removed', itemId: output.taskId })
429
+ else observations.push({ kind: 'item_status_changed', itemId: output.taskId, status: output.statusChange.to })
430
+ }
431
+ return observations
432
+ }
433
+
434
+ function parseClaudeTaskResult<Output>({ value, toolName, schema }: { value: unknown, toolName: string, schema: z.ZodType<Output> }): Output {
435
+ const parsed = schema.safeParse(value)
436
+ if (parsed.success)
437
+ return parsed.data
438
+ throw new AgentProviderProtocolError({
439
+ message: `Claude ${toolName} returned missing or malformed structured output.`,
440
+ cause: parsed.error,
441
+ })
442
+ }
443
+
444
+ function mapStreamEvent(event: BetaRawMessageStreamEvent, state: ClaudeEventMapperState, turnId: string, actor: AgentEventActor | undefined): AgentEventDraft[] {
445
+ const drafts: AgentEventDraft[] = []
446
+ if (event.type === 'message_start') {
447
+ state.currentMessageId = event.message.id
448
+ if (actor)
449
+ state.actorByMessageId.set(event.message.id, actor)
450
+ ensureAssistantMessageStarted(drafts, state, event.message, turnId, actor)
451
+ return drafts
452
+ }
453
+
454
+ if (event.type === 'content_block_start') {
455
+ const messageId = currentMessageId(state)
456
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
457
+ state.contentBlockTypeByIndex.set(event.index, event.content_block.type)
458
+ if (event.content_block.type === 'thinking') {
459
+ state.streamedReasoningMessageIds.add(messageId)
460
+ ensureReasoningStarted(drafts, state, turnId, messageId, messageActor)
461
+ }
462
+ return drafts
463
+ }
464
+
465
+ if (event.type === 'content_block_stop') {
466
+ const messageId = currentMessageId(state)
467
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
468
+ const blockType = state.contentBlockTypeByIndex.get(event.index)
469
+ if (blockType === 'thinking')
470
+ endReasoning(drafts, state, turnId, messageId, state.streamedReasoningByMessageId.get(messageId), messageActor)
471
+ state.contentBlockTypeByIndex.delete(event.index)
472
+ return drafts
473
+ }
474
+
475
+ if (event.type === 'message_stop') {
476
+ const messageId = currentMessageId(state)
477
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
478
+ endAssistantMessageById(drafts, state, turnId, messageId, state.streamedTextByMessageId.get(messageId), messageActor)
479
+ state.actorByMessageId.delete(messageId)
480
+ state.currentMessageId = undefined
481
+ state.contentBlockTypeByIndex.clear()
482
+ return drafts
483
+ }
484
+
485
+ if (event.type !== 'content_block_delta')
486
+ return drafts
487
+
488
+ const messageId = currentMessageId(state)
489
+ const messageActor = actor ?? state.actorByMessageId.get(messageId)
490
+ const delta = event.delta
491
+ if (delta.type === 'thinking_delta') {
492
+ state.streamedReasoningMessageIds.add(messageId)
493
+ state.streamedReasoningByMessageId.set(messageId, `${state.streamedReasoningByMessageId.get(messageId) ?? ''}${delta.thinking}`)
494
+ const reasoningId = ensureReasoningStarted(drafts, state, turnId, messageId, messageActor)
495
+ if (delta.thinking)
496
+ drafts.push(withActor({ type: 'reasoning.summary.delta', turnId, payload: { reasoningId, text: delta.thinking } }, messageActor))
497
+ }
498
+ if (delta.type === 'text_delta') {
499
+ state.streamedTextByMessageId.set(messageId, `${state.streamedTextByMessageId.get(messageId) ?? ''}${delta.text}`)
500
+ drafts.push(withActor({ type: 'message.delta', turnId, payload: { messageId, role: 'assistant', delta: delta.text } }, messageActor))
501
+ }
502
+ return drafts
503
+ }
504
+
505
+ function ensureTurnStarted(drafts: AgentEventDraft[], state: ClaudeEventMapperState): string {
506
+ if (state.turnStarted && state.currentTurnId)
507
+ return state.currentTurnId
508
+ state.turnStarted = true
509
+ state.interrupt = { status: 'none' }
510
+ state.currentTurnId = createRuntimeTurnId()
511
+ state.currentMessageId = undefined
512
+ state.contentBlockTypeByIndex.clear()
513
+ drafts.push({ type: 'turn.started', turnId: state.currentTurnId, actor: mainActor, payload: {} })
514
+ ensureSessionRunning(drafts, state)
515
+ return state.currentTurnId
516
+ }
517
+
518
+ function ensureSessionRunning(drafts: AgentEventDraft[], state: ClaudeEventMapperState): void {
519
+ if (state.sessionState === 'running')
520
+ return
521
+ state.sessionState = 'running'
522
+ drafts.push({ type: 'session.state.changed', payload: { state: 'running' } })
523
+ }
524
+
525
+ function ensureSessionIdle(drafts: AgentEventDraft[], state: ClaudeEventMapperState): void {
526
+ if (state.sessionState === 'idle')
527
+ return
528
+ state.sessionState = 'idle'
529
+ drafts.push({ type: 'session.state.changed', payload: { state: 'idle' } })
530
+ }
531
+
532
+ function ensureAssistantMessageStarted(drafts: AgentEventDraft[], state: ClaudeEventMapperState, message: BetaMessage, turnId: string, actor: AgentEventActor | undefined): void {
533
+ state.currentMessageId = message.id
534
+ if (actor)
535
+ state.actorByMessageId.set(message.id, actor)
536
+ if (state.startedMessageIds.has(message.id))
537
+ return
538
+ state.startedMessageIds.add(message.id)
539
+ drafts.push(withActor({ type: 'message.started', turnId, payload: { messageId: message.id, role: 'assistant' } }, actor))
540
+ }
541
+
542
+ function currentMessageId(state: ClaudeEventMapperState): string {
543
+ if (state.currentMessageId)
544
+ return state.currentMessageId
545
+ throw new AgentProviderProtocolError({ message: 'Claude stream emitted content_block_delta before message_start.' })
546
+ }
547
+
548
+ function endAssistantMessage(drafts: AgentEventDraft[], state: ClaudeEventMapperState, message: BetaMessage, turnId: string, actor: AgentEventActor | undefined): void {
549
+ if (state.endedMessageIds.has(message.id))
550
+ return
551
+ endReasoning(drafts, state, turnId, message.id, thinkingFromAssistantContent(message.content), actor)
552
+ endAssistantMessageById(drafts, state, turnId, message.id, textFromAssistantContent(message.content), actor)
553
+ }
554
+
555
+ function endAssistantMessageById(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string, messageId: string, text: string | undefined, actor: AgentEventActor | undefined): void {
556
+ if (state.endedMessageIds.has(messageId))
557
+ return
558
+ state.endedMessageIds.add(messageId)
559
+ drafts.push(withActor({ type: 'message.ended', turnId, payload: removeUndefined({ messageId, role: 'assistant' as const, text: optionalNonEmpty(text) }) }, actor))
560
+ }
561
+
562
+ function ensureReasoningStarted(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string, messageId: string, actor: AgentEventActor | undefined): string {
563
+ const reasoningId = reasoningIdForMessage(messageId)
564
+ if (!state.startedReasoningIds.has(reasoningId)) {
565
+ state.startedReasoningIds.add(reasoningId)
566
+ drafts.push(withActor({ type: 'reasoning.started', turnId, payload: { reasoningId } }, actor))
567
+ }
568
+ return reasoningId
569
+ }
570
+
571
+ function endReasoning(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string, messageId: string, summary: string | undefined, actor: AgentEventActor | undefined): void {
572
+ const reasoningId = reasoningIdForMessage(messageId)
573
+ if (!state.startedReasoningIds.has(reasoningId) || state.endedReasoningIds.has(reasoningId))
574
+ return
575
+ state.endedReasoningIds.add(reasoningId)
576
+ drafts.push(withActor({ type: 'reasoning.ended', turnId, payload: removeUndefined({ reasoningId, summary: optionalNonEmpty(summary) }) }, actor))
577
+ }
578
+
579
+ function endOpenReasoning(drafts: AgentEventDraft[], state: ClaudeEventMapperState, turnId: string): void {
580
+ for (const reasoningId of state.startedReasoningIds) {
581
+ if (!state.endedReasoningIds.has(reasoningId)) {
582
+ state.endedReasoningIds.add(reasoningId)
583
+ drafts.push({ type: 'reasoning.ended', turnId, actor: mainActor, payload: { reasoningId } })
584
+ }
585
+ }
586
+ }
587
+
588
+ function reasoningIdForMessage(messageId: string): string {
589
+ return `reasoning-${messageId}`
590
+ }
591
+
592
+ function turnEndedPayload(message: Extract<SDKMessage, { type: 'result' }>, interruptAccepted: boolean): Extract<AgentEventDraft, { type: 'turn.ended' }>['payload'] {
593
+ if (interruptAccepted && message.subtype === 'error_during_execution') {
594
+ return removeUndefined({
595
+ status: 'interrupted' as const,
596
+ reason: 'interrupted',
597
+ duration: message.duration_ms,
598
+ })
599
+ }
600
+
601
+ if (message.subtype === 'success' && message.is_error !== true) {
602
+ return removeUndefined({
603
+ status: 'completed' as const,
604
+ reason: message.stop_reason ?? message.subtype,
605
+ duration: message.duration_ms,
606
+ })
607
+ }
608
+
609
+ return removeUndefined({
610
+ status: 'failed' as const,
611
+ reason: message.stop_reason ?? message.subtype,
612
+ duration: message.duration_ms,
613
+ error: resultError(message),
614
+ })
615
+ }
616
+
617
+ function resultError(message: Extract<SDKMessage, { type: 'result' }>): AgentError {
618
+ const errors = 'errors' in message && Array.isArray(message.errors) ? message.errors : []
619
+ return {
620
+ message: errors.length > 0 ? errors.join('\n') : `Claude result ended with ${message.subtype}.`,
621
+ code: message.subtype,
622
+ }
623
+ }
624
+
625
+ function shouldEndAssistantSnapshot(message: BetaMessage, state: ClaudeEventMapperState, actor: ClaudeSubagentActor | undefined): boolean {
626
+ return actor !== undefined
627
+ && !state.streamedTextByMessageId.has(message.id)
628
+ && !state.streamedReasoningMessageIds.has(message.id)
629
+ }
630
+
631
+ function actorFromParentToolUseId(parentToolUseId: string | null, state: ClaudeEventMapperState): ClaudeSubagentActor | undefined {
632
+ return parentToolUseId ? state.subagentByToolCallId.get(parentToolUseId) : undefined
633
+ }
634
+
635
+ function subagentActorForTaskMessage(input: { subagentId: string, parentToolCallId?: string, name?: string }, state: ClaudeEventMapperState): ClaudeSubagentActor {
636
+ const existing = input.parentToolCallId ? state.subagentByToolCallId.get(input.parentToolCallId) : undefined
637
+ return existing ?? subagentActorFromTask(input)
638
+ }
639
+
640
+ function subagentActorFromTask(input: { subagentId: string, parentToolCallId?: string, name?: string }): ClaudeSubagentActor {
641
+ return removeUndefined({
642
+ type: 'subagent' as const,
643
+ actorId: input.subagentId,
644
+ subagentId: input.subagentId,
645
+ parentActorId: 'main',
646
+ origin: input.parentToolCallId ? { type: 'tool_call' as const, toolCallId: input.parentToolCallId } : { type: 'provider_task' as const, taskId: input.subagentId },
647
+ parentToolCallId: input.parentToolCallId,
648
+ name: input.name,
649
+ })
650
+ }
651
+
652
+ function subagentPayloadRef(actor: ClaudeSubagentActor): Pick<ClaudeSubagentActor, 'actorId' | 'subagentId' | 'parentActorId' | 'origin' | 'parentToolCallId' | 'name'> {
653
+ return removeUndefined({
654
+ actorId: actor.actorId,
655
+ subagentId: actor.subagentId,
656
+ parentActorId: actor.parentActorId,
657
+ origin: actor.origin,
658
+ parentToolCallId: actor.parentToolCallId,
659
+ name: actor.name,
660
+ })
661
+ }
662
+
663
+ function subagentUserContentEvents(input: { messageId: string, content: UserContentBlock[], turnId: string, actor: ClaudeSubagentActor }): AgentEventDraft[] {
664
+ const text = input.content.map(block => block.type === 'text' ? block.text : '').join('')
665
+ return text ? subagentUserMessageEvents({ messageId: input.messageId, text, turnId: input.turnId, actor: input.actor }) : []
666
+ }
667
+
668
+ function subagentUserMessageEvents(input: { messageId: string, text: string, turnId: string, actor: ClaudeSubagentActor }): AgentEventDraft[] {
669
+ return [
670
+ withActor({ type: 'message.started', turnId: input.turnId, payload: { messageId: input.messageId, role: 'user' } }, input.actor),
671
+ withActor({ type: 'message.delta', turnId: input.turnId, payload: { messageId: input.messageId, role: 'user', delta: input.text } }, input.actor),
672
+ withActor({ type: 'message.ended', turnId: input.turnId, payload: { messageId: input.messageId, role: 'user', text: input.text } }, input.actor),
673
+ ]
674
+ }
675
+
676
+ function taskNotificationStatus(status: ClaudeTaskNotificationMessage['status']): 'completed' | 'failed' | 'cancelled' {
677
+ if (status === 'completed')
678
+ return 'completed'
679
+ if (status === 'failed')
680
+ return 'failed'
681
+ return 'cancelled'
682
+ }
683
+
684
+ function usageFromTaskUsage(usage: ClaudeTaskProgressMessage['usage']): AgentUsage {
685
+ return { totalTokens: usage.total_tokens }
686
+ }
687
+
688
+ function withActor(draft: ClaudeUnownedTurnEventDraft, actor: AgentEventActor | undefined): AgentEventDraft {
689
+ return { ...draft, actor: actor ?? mainActor }
690
+ }
691
+
692
+ const CLAUDE_RUNTIME_TOOL_PREFIX = `mcp__${CLAUDE_AGENT_TOOL_SERVER}__`
693
+
694
+ function normalizeToolName(name: string): string {
695
+ // Our runtime tools arrive as mcp__coding_agent__<tool>; strip the prefix so they match Codex.
696
+ const local = name.startsWith(CLAUDE_RUNTIME_TOOL_PREFIX) ? name.slice(CLAUDE_RUNTIME_TOOL_PREFIX.length) : name
697
+ return canonicalToolName(local)
698
+ }
699
+
700
+ function usageFromObject(value: BetaUsage | undefined): AgentUsage | undefined {
701
+ if (!value)
702
+ return undefined
703
+ return removeUndefined({
704
+ inputTokens: value.input_tokens,
705
+ outputTokens: value.output_tokens,
706
+ cacheReadTokens: value.cache_read_input_tokens ?? undefined,
707
+ cacheWriteTokens: value.cache_creation_input_tokens ?? undefined,
708
+ totalTokens: value.input_tokens + value.output_tokens + (value.cache_read_input_tokens ?? 0) + (value.cache_creation_input_tokens ?? 0),
709
+ })
710
+ }
711
+
712
+ function textFromAssistantContent(content: BetaMessage['content']): string | undefined {
713
+ const text = content.map(block => block.type === 'text' ? block.text : '').join('')
714
+ return text || undefined
715
+ }
716
+
717
+ function thinkingFromAssistantContent(content: BetaMessage['content']): string | undefined {
718
+ const text = content.map(block => block.type === 'thinking' ? block.thinking : '').join('')
719
+ return text || undefined
720
+ }
721
+
722
+ function textFromToolResultContent(content: UserToolResultBlock['content']): string | undefined {
723
+ if (!content)
724
+ return undefined
725
+ if (typeof content === 'string')
726
+ return content
727
+ const text = content.map(block => block.type === 'text' ? block.text : '').join('\n')
728
+ return text || undefined
729
+ }
730
+
731
+ function jsonInput(value: unknown): JsonValue {
732
+ const parsed = jsonValueSchema.safeParse(value)
733
+ if (parsed.success)
734
+ return parsed.data
735
+ return {}
736
+ }