@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.
- package/README.md +24 -3
- package/bin/supervisor.ts +116 -0
- package/package.json +42 -3
- package/scripts/build-supervisor-executable.ts +32 -0
- package/src/agents/agent-error-serializer.ts +23 -0
- package/src/agents/agent-event-stream.ts +81 -0
- package/src/agents/agent-id.ts +17 -0
- package/src/agents/agent-operation.ts +11 -0
- package/src/agents/agent-provider.ts +37 -0
- package/src/agents/agent-run.ts +32 -0
- package/src/agents/agent-runtime-error.ts +161 -0
- package/src/agents/agent-session-events.ts +32 -0
- package/src/agents/agent-tool-runner.ts +83 -0
- package/src/agents/agent-tool.ts +111 -0
- package/src/agents/author-context.ts +36 -0
- package/src/agents/claude/claude-command-mapper.ts +234 -0
- package/src/agents/claude/claude-event-mapper.ts +736 -0
- package/src/agents/claude/claude-provider.ts +191 -0
- package/src/agents/claude/claude-run.ts +464 -0
- package/src/agents/claude/claude-tool-mapper.ts +186 -0
- package/src/agents/claude/index.ts +1 -0
- package/src/agents/codex/codex-auth.ts +34 -0
- package/src/agents/codex/codex-command-mapper.ts +78 -0
- package/src/agents/codex/codex-event-mapper.ts +708 -0
- package/src/agents/codex/codex-json-rpc-client.ts +326 -0
- package/src/agents/codex/codex-protocol.ts +36 -0
- package/src/agents/codex/codex-provider.ts +1050 -0
- package/src/agents/codex/codex-run.ts +404 -0
- package/src/agents/codex/codex-skill-catalog.ts +158 -0
- package/src/agents/codex/codex-skill-roots.ts +19 -0
- package/src/agents/codex/codex-tool-mapper.ts +55 -0
- package/src/agents/codex/codex.errors.ts +68 -0
- package/src/agents/codex/generated/meta.gen.ts +606 -0
- package/src/agents/codex/generated/namespaces.gen.ts +311 -0
- package/src/agents/codex/generated/schema.gen.ts +34883 -0
- package/src/agents/codex/index.ts +2 -0
- package/src/agents/index.ts +14 -0
- package/src/agents/input-attachment-preparation.ts +86 -0
- package/src/agents/input-attachment.errors.ts +16 -0
- package/src/agents/instructions.ts +8 -0
- package/src/agents/materialized-input-attachment.ts +26 -0
- package/src/agents/message-id.ts +23 -0
- package/src/agents/normalize.ts +8 -0
- package/src/agents/sandbox-environment.ts +1 -0
- package/src/agents/tools/ping.tool.ts +13 -0
- package/src/agents/user-input-request.ts +470 -0
- package/src/provenance.gen.ts +3 -3
- package/src/supervisor/agent-provider-factory.ts +189 -0
- package/src/supervisor/bootstrap-binder.ts +125 -0
- package/src/supervisor/config.ts +49 -0
- package/src/supervisor/control-authority-verifier.ts +135 -0
- package/src/supervisor/create-supervisor-runtime.ts +25 -0
- package/src/supervisor/errors.ts +24 -0
- package/src/supervisor/index.ts +34 -0
- package/src/supervisor/persistence/json.ts +21 -0
- package/src/supervisor/persistence/state-discovery.ts +56 -0
- package/src/supervisor/persistence/supervisor-store.ts +364 -0
- package/src/supervisor/ports/index.ts +109 -0
- package/src/supervisor/provider-factory.ts +37 -0
- package/src/supervisor/resident.ts +143 -0
- package/src/supervisor/rpc-client.ts +120 -0
- package/src/supervisor/runtime-handler.ts +309 -0
- package/src/supervisor/websocket-server.ts +434 -0
- package/src/supervisor-protocol/bootstrap.ts +1 -1
- package/src/template-onboarding.ts +47 -0
- package/src/testing/es256-test-keys.ts +73 -0
- package/src/testing/in-memory-runtime-control-plane.ts +205 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/loopback-supervisor-connection.ts +71 -0
- package/src/testing/scripted-provider.ts +64 -0
- package/src/worker-runtime-client/command-pump.ts +132 -0
- package/src/worker-runtime-client/connection-attempt.ts +340 -0
- package/src/worker-runtime-client/control-authority-signer.ts +100 -0
- package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
- package/src/worker-runtime-client/frame-processor.ts +178 -0
- package/src/worker-runtime-client/index.ts +27 -0
- package/src/worker-runtime-client/lease-reconciler.ts +14 -0
- package/src/worker-runtime-client/ports.ts +137 -0
- package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
- package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
- package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +300 -0
- 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
|
+
}
|