@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,708 @@
|
|
|
1
|
+
import type { AgentError, AgentEventActor, AgentMessageRole, AgentSubagentStatus, AgentToolCallStatus, AgentToolContent, AgentToolResult, AgentTurnEndStatus, AgentUsage, JsonObject, JsonValue } from '../../protocol'
|
|
2
|
+
import type { AgentWorkStatus } from '../../protocol/agent-work'
|
|
3
|
+
import type { AgentEventDraft } from '../agent-event-stream'
|
|
4
|
+
import type { CodexJsonRpcMessage, CodexServerNotification } from './codex-json-rpc-client'
|
|
5
|
+
import type { ServerNotificationParamsByMethod } from './codex-protocol'
|
|
6
|
+
import { jsonObjectSchema, jsonValueSchema } from '../../protocol'
|
|
7
|
+
import { CANONICAL_TOOL } from '../../protocol/agent-tool-name'
|
|
8
|
+
import { createRuntimeWorkItemId } from '../agent-id'
|
|
9
|
+
import { AgentProviderProtocolError } from '../agent-runtime-error'
|
|
10
|
+
import { stripAuthorContext } from '../author-context'
|
|
11
|
+
import { optionalNonEmpty, removeUndefined } from '../normalize'
|
|
12
|
+
import { isCodexServerNotification } from './codex-json-rpc-client'
|
|
13
|
+
|
|
14
|
+
export type CodexNotification = CodexServerNotification
|
|
15
|
+
|
|
16
|
+
type CodexTokenUsage = ServerNotificationParamsByMethod['thread/tokenUsage/updated']['tokenUsage']
|
|
17
|
+
type CodexTokenCounts = CodexTokenUsage['total']
|
|
18
|
+
type CodexPlanStepStatus = ServerNotificationParamsByMethod['turn/plan/updated']['plan'][number]['status']
|
|
19
|
+
type CodexStartedItem = ServerNotificationParamsByMethod['item/started']['item']
|
|
20
|
+
type CodexCompletedItem = ServerNotificationParamsByMethod['item/completed']['item']
|
|
21
|
+
type CodexWebSearchItem = Extract<CodexCompletedItem, { type: 'webSearch' }>
|
|
22
|
+
type CodexFileChangeItem = Extract<CodexCompletedItem, { type: 'fileChange' }>
|
|
23
|
+
type CodexImageGenerationItem = Extract<CodexCompletedItem, { type: 'imageGeneration' }>
|
|
24
|
+
type CodexUserMessageItem = Extract<CodexCompletedItem, { type: 'userMessage' }>
|
|
25
|
+
type CodexHookPromptItem = Extract<CodexCompletedItem, { type: 'hookPrompt' }>
|
|
26
|
+
type CodexDynamicToolCompletedItem = Extract<CodexCompletedItem, { type: 'dynamicToolCall' }>
|
|
27
|
+
type CodexDynamicToolContentItem = NonNullable<CodexDynamicToolCompletedItem['contentItems']>[number]
|
|
28
|
+
type CodexCollabAgentToolCompletedItem = Extract<CodexCompletedItem, { type: 'collabAgentToolCall' }>
|
|
29
|
+
type CodexCollabAgentToolStartedItem = Extract<CodexStartedItem, { type: 'collabAgentToolCall' }>
|
|
30
|
+
type CodexSubAgentActivityCompletedItem = Extract<CodexCompletedItem, { type: 'subAgentActivity' }>
|
|
31
|
+
type CodexCollabAgentState = CodexCollabAgentToolCompletedItem['agentsStates'][string]
|
|
32
|
+
type CodexThread = ServerNotificationParamsByMethod['thread/started']['thread']
|
|
33
|
+
type CodexThreadMetadata = Pick<CodexThread, 'id' | 'agentNickname' | 'agentRole'>
|
|
34
|
+
type CodexTurn = ServerNotificationParamsByMethod['turn/completed']['turn']
|
|
35
|
+
type CodexTurnError = NonNullable<CodexTurn['error']>
|
|
36
|
+
type CodexSubagentRef = Extract<AgentEventActor, { type: 'subagent' }>
|
|
37
|
+
interface CodexPromptCommandRef { commandId: string, origin: 'user' | 'supervisor' }
|
|
38
|
+
type CodexTurnEventDraft = Extract<AgentEventDraft, { turnId: string }>
|
|
39
|
+
type CodexUnownedTurnEventDraft = CodexTurnEventDraft extends infer Draft
|
|
40
|
+
? Draft extends CodexTurnEventDraft
|
|
41
|
+
? Omit<Draft, 'actor'>
|
|
42
|
+
: never
|
|
43
|
+
: never
|
|
44
|
+
|
|
45
|
+
const mainActor = { type: 'main', actorId: 'main' } satisfies AgentEventActor
|
|
46
|
+
|
|
47
|
+
export interface CodexEventMapperState {
|
|
48
|
+
readonly endedSubagentIds: Set<string>
|
|
49
|
+
readonly subagentByThreadId: Map<string, CodexSubagentRef>
|
|
50
|
+
readonly subagentById: Map<string, CodexSubagentRef>
|
|
51
|
+
readonly subagentNameByThreadId: Map<string, string>
|
|
52
|
+
readonly pendingDraftsByThreadId: Map<string, CodexUnownedTurnEventDraft[]>
|
|
53
|
+
readonly turnErrorsById: Map<string, AgentError>
|
|
54
|
+
readonly startedTurnIds: Set<string>
|
|
55
|
+
readonly startedReasoningIds: Set<string>
|
|
56
|
+
readonly reasoningSummaryById: Map<string, Map<number, string>>
|
|
57
|
+
readonly promptCommandByClientUserMessageId: Map<string, CodexPromptCommandRef>
|
|
58
|
+
mainThreadId?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createCodexEventMapperState(): CodexEventMapperState {
|
|
62
|
+
return { endedSubagentIds: new Set(), subagentByThreadId: new Map(), subagentById: new Map(), subagentNameByThreadId: new Map(), pendingDraftsByThreadId: new Map(), turnErrorsById: new Map(), startedTurnIds: new Set(), startedReasoningIds: new Set(), reasoningSummaryById: new Map(), promptCommandByClientUserMessageId: new Map() }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function codexNotificationFromMessage(message: CodexJsonRpcMessage): CodexNotification | null {
|
|
66
|
+
return isCodexServerNotification(message) ? message : null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function cacheCodexThreadMetadata(thread: CodexThreadMetadata, state: CodexEventMapperState): void {
|
|
70
|
+
cacheThreadMetadata(thread, state)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function mapCodexNotification(notification: CodexNotification, state: CodexEventMapperState): AgentEventDraft[] {
|
|
74
|
+
// Codex has many app-server notifications; the runtime maps the subset that has a provider-neutral AgentEvent.
|
|
75
|
+
|
|
76
|
+
switch (notification.method) {
|
|
77
|
+
case 'thread/started':
|
|
78
|
+
cacheThreadMetadata(notification.params.thread, state)
|
|
79
|
+
return []
|
|
80
|
+
case 'turn/started':
|
|
81
|
+
state.mainThreadId ??= notification.params.threadId
|
|
82
|
+
state.startedTurnIds.add(notification.params.turn.id)
|
|
83
|
+
return emitOrBufferByThread({ draft: { type: 'turn.started', turnId: notification.params.turn.id, payload: {} }, threadId: notification.params.threadId, state })
|
|
84
|
+
case 'turn/completed': {
|
|
85
|
+
if (!state.startedTurnIds.has(notification.params.turn.id))
|
|
86
|
+
return []
|
|
87
|
+
const payload = turnEndedPayload(notification.params.turn, state)
|
|
88
|
+
return [
|
|
89
|
+
...mapChildTurnCompleted({ threadId: notification.params.threadId, turnId: notification.params.turn.id, turnPayload: payload, state }),
|
|
90
|
+
...emitOrBufferByThread({ draft: { type: 'turn.ended', turnId: notification.params.turn.id, payload }, threadId: notification.params.threadId, state }),
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
case 'thread/tokenUsage/updated': {
|
|
94
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
95
|
+
return []
|
|
96
|
+
const usage = usageFromTokenUsage(notification.params.tokenUsage)
|
|
97
|
+
if (!usage)
|
|
98
|
+
return []
|
|
99
|
+
return emitOrBufferByThread({ draft: { type: 'usage', turnId: notification.params.turnId, payload: { usage } }, threadId: notification.params.threadId, state })
|
|
100
|
+
}
|
|
101
|
+
case 'turn/plan/updated':
|
|
102
|
+
if (!state.startedTurnIds.has(notification.params.turnId) || notification.params.threadId !== state.mainThreadId)
|
|
103
|
+
return []
|
|
104
|
+
return [{
|
|
105
|
+
type: 'work.observed',
|
|
106
|
+
turnId: notification.params.turnId,
|
|
107
|
+
actor: mainActor,
|
|
108
|
+
payload: {
|
|
109
|
+
observations: [{
|
|
110
|
+
kind: 'snapshot',
|
|
111
|
+
items: notification.params.plan.map(item => ({
|
|
112
|
+
id: createRuntimeWorkItemId(),
|
|
113
|
+
title: item.step,
|
|
114
|
+
status: workStatusFromCodexPlan(item.status),
|
|
115
|
+
})),
|
|
116
|
+
}],
|
|
117
|
+
},
|
|
118
|
+
}]
|
|
119
|
+
case 'error':
|
|
120
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
121
|
+
return []
|
|
122
|
+
if (!notification.params.willRetry)
|
|
123
|
+
state.turnErrorsById.set(notification.params.turnId, agentErrorFromCodexTurnError(notification.params.error))
|
|
124
|
+
return []
|
|
125
|
+
case 'item/agentMessage/delta':
|
|
126
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
127
|
+
return []
|
|
128
|
+
return emitOrBufferByThread({ draft: { type: 'message.delta', turnId: notification.params.turnId, payload: { messageId: notification.params.itemId, role: 'assistant', delta: notification.params.delta } }, threadId: notification.params.threadId, state })
|
|
129
|
+
case 'item/reasoning/summaryTextDelta': {
|
|
130
|
+
if (!state.startedTurnIds.has(notification.params.turnId) || !notification.params.delta)
|
|
131
|
+
return []
|
|
132
|
+
appendReasoningSummaryDelta({ reasoningId: notification.params.itemId, summaryIndex: notification.params.summaryIndex, delta: notification.params.delta, state })
|
|
133
|
+
if (!state.startedReasoningIds.has(notification.params.itemId)) {
|
|
134
|
+
const summary = accumulatedReasoningSummary(notification.params.itemId, state)
|
|
135
|
+
if (!summary)
|
|
136
|
+
return []
|
|
137
|
+
const started = ensureReasoningStarted({ reasoningId: notification.params.itemId, turnId: notification.params.turnId, threadId: notification.params.threadId, state })
|
|
138
|
+
return [...started, ...emitOrBufferByThread({ draft: { type: 'reasoning.summary.delta', turnId: notification.params.turnId, payload: { reasoningId: notification.params.itemId, text: summary } }, threadId: notification.params.threadId, state })]
|
|
139
|
+
}
|
|
140
|
+
return emitOrBufferByThread({ draft: { type: 'reasoning.summary.delta', turnId: notification.params.turnId, payload: { reasoningId: notification.params.itemId, text: notification.params.delta } }, threadId: notification.params.threadId, state })
|
|
141
|
+
}
|
|
142
|
+
case 'item/commandExecution/outputDelta':
|
|
143
|
+
case 'item/fileChange/outputDelta':
|
|
144
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
145
|
+
return []
|
|
146
|
+
return emitOrBufferByThread({ draft: { type: 'tool.output.delta', turnId: notification.params.turnId, payload: { toolCallId: notification.params.itemId, delta: notification.params.delta } }, threadId: notification.params.threadId, state })
|
|
147
|
+
case 'item/started':
|
|
148
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
149
|
+
return []
|
|
150
|
+
return mapItemStarted({ turnId: notification.params.turnId, threadId: notification.params.threadId, item: notification.params.item, state })
|
|
151
|
+
case 'item/completed':
|
|
152
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
153
|
+
return []
|
|
154
|
+
return mapItemCompleted({ turnId: notification.params.turnId, threadId: notification.params.threadId, item: notification.params.item, state })
|
|
155
|
+
default:
|
|
156
|
+
return []
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function workStatusFromCodexPlan(status: CodexPlanStepStatus): AgentWorkStatus {
|
|
161
|
+
if (status === 'inProgress')
|
|
162
|
+
return 'in_progress'
|
|
163
|
+
return status
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function turnEndedPayload(turn: CodexTurn, state: CodexEventMapperState): Extract<AgentEventDraft, { type: 'turn.ended' }>['payload'] {
|
|
167
|
+
const status = turnEndStatusFromCodex(turn.status)
|
|
168
|
+
const providerError = turn.error ? agentErrorFromCodexTurnError(turn.error) : state.turnErrorsById.get(turn.id)
|
|
169
|
+
if (providerError)
|
|
170
|
+
state.turnErrorsById.delete(turn.id)
|
|
171
|
+
|
|
172
|
+
return removeUndefined({
|
|
173
|
+
status,
|
|
174
|
+
reason: turn.status,
|
|
175
|
+
error: providerError,
|
|
176
|
+
duration: turn.durationMs ?? undefined,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function turnEndStatusFromCodex(status: CodexTurn['status']): AgentTurnEndStatus {
|
|
181
|
+
if (status === 'completed')
|
|
182
|
+
return 'completed'
|
|
183
|
+
if (status === 'failed')
|
|
184
|
+
return 'failed'
|
|
185
|
+
if (status === 'interrupted')
|
|
186
|
+
return 'interrupted'
|
|
187
|
+
|
|
188
|
+
throw new AgentProviderProtocolError({ message: `Codex emitted turn/completed with non-terminal status: ${status}` })
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function mapItemStarted({ turnId, threadId, item, state }: { turnId: string, threadId: string, item: CodexStartedItem, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
192
|
+
const message = messageForItem(item, state)
|
|
193
|
+
if (message)
|
|
194
|
+
return emitOrBufferByThread({ draft: { type: 'message.started', turnId, payload: removeUndefined({ messageId: item.id, ...message }) }, threadId, state })
|
|
195
|
+
|
|
196
|
+
const toolCall = toolCallStart(item)
|
|
197
|
+
if (toolCall)
|
|
198
|
+
return emitOrBufferByThread({ draft: { type: 'tool.call.started', turnId, payload: { toolCallId: item.id, toolName: toolCall.toolName, input: toolCall.input } }, threadId, state })
|
|
199
|
+
|
|
200
|
+
if (item.type === 'contextCompaction')
|
|
201
|
+
return emitOrBufferByThread({ draft: { type: 'context.compaction.started', turnId, payload: { compactionId: item.id, trigger: 'unknown' } }, threadId, state })
|
|
202
|
+
|
|
203
|
+
return []
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function toolCallStart(item: CodexStartedItem): { toolName: string, input: JsonValue } | undefined {
|
|
207
|
+
if (item.type === 'commandExecution')
|
|
208
|
+
return { toolName: CANONICAL_TOOL.bash, input: toJsonObject({ command: Array.isArray(item.command) ? item.command.join(' ') : String(item.command ?? ''), cwd: item.cwd }) }
|
|
209
|
+
if (item.type === 'webSearch')
|
|
210
|
+
return { toolName: CANONICAL_TOOL.webSearch, input: toJsonObject({ query: optionalNonEmpty(item.query) }) }
|
|
211
|
+
if (item.type === 'fileChange')
|
|
212
|
+
return { toolName: CANONICAL_TOOL.edit, input: jsonInput({ files: item.changes.map(change => removeUndefined({ path: change.path, kind: change.kind?.type })) }) }
|
|
213
|
+
if (item.type === 'imageView')
|
|
214
|
+
return { toolName: CANONICAL_TOOL.viewImage, input: toJsonObject({ path: item.path }) }
|
|
215
|
+
if (item.type === 'imageGeneration')
|
|
216
|
+
return { toolName: CANONICAL_TOOL.imageGeneration, input: toJsonObject({ revisedPrompt: optionalNonEmpty(item.revisedPrompt) }) }
|
|
217
|
+
if (item.type === 'dynamicToolCall')
|
|
218
|
+
return { toolName: item.tool, input: jsonInput(item.arguments) }
|
|
219
|
+
if (item.type === 'collabAgentToolCall')
|
|
220
|
+
return { toolName: item.tool, input: collabToolInput(item) }
|
|
221
|
+
return undefined
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function appendReasoningSummaryDelta({ reasoningId, summaryIndex, delta, state }: { reasoningId: string, summaryIndex: number, delta: string, state: CodexEventMapperState }): void {
|
|
225
|
+
const sections = state.reasoningSummaryById.get(reasoningId) ?? new Map<number, string>()
|
|
226
|
+
sections.set(summaryIndex, (sections.get(summaryIndex) ?? '') + delta)
|
|
227
|
+
state.reasoningSummaryById.set(reasoningId, sections)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function ensureReasoningStarted({ reasoningId, turnId, threadId, state }: { reasoningId: string, turnId: string, threadId: string, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
231
|
+
if (state.startedReasoningIds.has(reasoningId))
|
|
232
|
+
return []
|
|
233
|
+
state.startedReasoningIds.add(reasoningId)
|
|
234
|
+
return emitOrBufferByThread({ draft: { type: 'reasoning.started', turnId, payload: { reasoningId } }, threadId, state })
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function mapItemCompleted({ turnId, threadId, item, state }: { turnId: string, threadId: string, item: CodexCompletedItem, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
238
|
+
const message = messageForItem(item, state)
|
|
239
|
+
if (message)
|
|
240
|
+
return emitOrBufferByThread({ draft: { type: 'message.ended', turnId, payload: removeUndefined({ messageId: item.id, ...message, text: messageEndedText(item) }) }, threadId, state })
|
|
241
|
+
|
|
242
|
+
const completion = toolCallCompletion(item)
|
|
243
|
+
if (completion)
|
|
244
|
+
return emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: removeUndefined({ toolCallId: item.id, ...completion }) }, threadId, state })
|
|
245
|
+
|
|
246
|
+
if (item.type === 'subAgentActivity')
|
|
247
|
+
return mapSubAgentActivityCompleted({ turnId, threadId, item, state })
|
|
248
|
+
|
|
249
|
+
if (item.type === 'contextCompaction')
|
|
250
|
+
return emitOrBufferByThread({ draft: { type: 'context.compaction.completed', turnId, payload: { compactionId: item.id, trigger: 'unknown', status: 'completed' } }, threadId, state })
|
|
251
|
+
|
|
252
|
+
if (item.type === 'reasoning')
|
|
253
|
+
return mapReasoningCompleted({ turnId, threadId, item, state })
|
|
254
|
+
|
|
255
|
+
if (item.type === 'collabAgentToolCall')
|
|
256
|
+
return mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState: state })
|
|
257
|
+
|
|
258
|
+
return []
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function mapReasoningCompleted({ turnId, threadId, item, state }: { turnId: string, threadId: string, item: Extract<CodexCompletedItem, { type: 'reasoning' }>, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
262
|
+
const accumulatedSummary = accumulatedReasoningSummary(item.id, state)
|
|
263
|
+
state.reasoningSummaryById.delete(item.id)
|
|
264
|
+
|
|
265
|
+
const authoritativeSummary = summaryIfDisplayable(item.summary?.join('\n'))
|
|
266
|
+
const summary = authoritativeSummary ?? accumulatedSummary
|
|
267
|
+
if (!summary)
|
|
268
|
+
return []
|
|
269
|
+
|
|
270
|
+
return [
|
|
271
|
+
...ensureReasoningStarted({ reasoningId: item.id, turnId, threadId, state }),
|
|
272
|
+
...emitOrBufferByThread({ draft: { type: 'reasoning.ended', turnId, payload: removeUndefined({ reasoningId: item.id, summary }) }, threadId, state }),
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function accumulatedReasoningSummary(reasoningId: string, state: CodexEventMapperState): string | undefined {
|
|
277
|
+
return summaryIfDisplayable([...state.reasoningSummaryById.get(reasoningId)?.entries() ?? []]
|
|
278
|
+
.sort(([left], [right]) => left - right)
|
|
279
|
+
.map(([, section]) => section)
|
|
280
|
+
.join('\n'))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function summaryIfDisplayable(value: string | undefined): string | undefined {
|
|
284
|
+
return optionalNonEmpty(value) ? value : undefined
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function messageForItem(item: CodexStartedItem | CodexCompletedItem, state: CodexEventMapperState): { role: AgentMessageRole, commandId?: string } | undefined {
|
|
288
|
+
if (item.type === 'agentMessage')
|
|
289
|
+
return { role: 'assistant' }
|
|
290
|
+
if (item.type === 'hookPrompt')
|
|
291
|
+
return { role: 'system' }
|
|
292
|
+
if (item.type !== 'userMessage')
|
|
293
|
+
return undefined
|
|
294
|
+
|
|
295
|
+
const promptCommand = item.clientId ? state.promptCommandByClientUserMessageId.get(item.clientId) : undefined
|
|
296
|
+
const role: AgentMessageRole = promptCommand?.origin === 'supervisor' ? 'system' : 'user'
|
|
297
|
+
return removeUndefined({ role, commandId: promptCommand?.commandId })
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function toolCallCompletion(item: CodexCompletedItem): { status: AgentToolCallStatus, output?: AgentToolResult, error?: AgentError } | undefined {
|
|
301
|
+
if (item.type === 'webSearch')
|
|
302
|
+
return { status: 'completed', output: webSearchResult(item) }
|
|
303
|
+
if (item.type === 'fileChange') {
|
|
304
|
+
const status = fileChangeStatus(item.status)
|
|
305
|
+
return { status, output: fileChangeResult(item, status), error: status === 'failed' ? { message: 'Codex failed to apply the patch.' } : undefined }
|
|
306
|
+
}
|
|
307
|
+
if (item.type === 'imageView')
|
|
308
|
+
return { status: 'completed', output: { success: true, content: [{ type: 'text', text: item.path }] } }
|
|
309
|
+
if (item.type === 'imageGeneration')
|
|
310
|
+
return { status: item.status === 'failed' ? 'failed' : 'completed', output: imageGenerationResult(item) }
|
|
311
|
+
if (item.type === 'commandExecution') {
|
|
312
|
+
const status = item.exitCode === undefined || item.exitCode === null || item.exitCode === 0 ? 'completed' : 'failed'
|
|
313
|
+
return { status, output: commandExecutionResult(item, status), error: 'error' in item && typeof item.error === 'string' ? { message: item.error } : undefined }
|
|
314
|
+
}
|
|
315
|
+
if (item.type === 'dynamicToolCall')
|
|
316
|
+
return { status: item.success === false ? 'failed' : 'completed', output: dynamicToolResult(item) }
|
|
317
|
+
return undefined
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function webSearchResult(item: CodexWebSearchItem): AgentToolResult {
|
|
321
|
+
const lines: string[] = []
|
|
322
|
+
const query = optionalNonEmpty(item.query)
|
|
323
|
+
if (query)
|
|
324
|
+
lines.push(`Query: ${query}`)
|
|
325
|
+
const url = webSearchActionUrl(item.action)
|
|
326
|
+
if (url)
|
|
327
|
+
lines.push(url)
|
|
328
|
+
return { success: true, content: [{ type: 'text', text: lines.join('\n') || 'Web search completed.' }] }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function webSearchActionUrl(action: CodexWebSearchItem['action']): string | undefined {
|
|
332
|
+
if (action && (action.type === 'openPage' || action.type === 'findInPage'))
|
|
333
|
+
return optionalNonEmpty(action.url)
|
|
334
|
+
return undefined
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function fileChangeStatus(status: CodexFileChangeItem['status']): AgentToolCallStatus {
|
|
338
|
+
if (status === 'failed')
|
|
339
|
+
return 'failed'
|
|
340
|
+
if (status === 'declined')
|
|
341
|
+
return 'cancelled'
|
|
342
|
+
return 'completed'
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function fileChangeResult(item: CodexFileChangeItem, status: AgentToolCallStatus): AgentToolResult | undefined {
|
|
346
|
+
const text = item.changes.map(change => change.diff).filter(diff => diff && diff.trim()).join('\n')
|
|
347
|
+
if (!text)
|
|
348
|
+
return undefined
|
|
349
|
+
return { success: status === 'completed', content: [{ type: 'text', text }] }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function imageGenerationResult(item: CodexImageGenerationItem): AgentToolResult | undefined {
|
|
353
|
+
const lines: string[] = []
|
|
354
|
+
const savedPath = optionalNonEmpty(item.savedPath)
|
|
355
|
+
if (savedPath)
|
|
356
|
+
lines.push(`Saved to ${savedPath}`)
|
|
357
|
+
const revised = optionalNonEmpty(item.revisedPrompt)
|
|
358
|
+
if (revised)
|
|
359
|
+
lines.push(`Revised prompt: ${revised}`)
|
|
360
|
+
if (!lines.length)
|
|
361
|
+
return undefined
|
|
362
|
+
return { success: item.status !== 'failed', content: [{ type: 'text', text: lines.join('\n') }] }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function messageEndedText(item: CodexCompletedItem): string | undefined {
|
|
366
|
+
if (item.type === 'userMessage')
|
|
367
|
+
return userMessageText(item)
|
|
368
|
+
if (item.type === 'hookPrompt')
|
|
369
|
+
return hookPromptText(item)
|
|
370
|
+
if (item.type === 'agentMessage')
|
|
371
|
+
return item.text || undefined
|
|
372
|
+
return undefined
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function userMessageText(item: CodexUserMessageItem): string | undefined {
|
|
376
|
+
const parts: string[] = []
|
|
377
|
+
for (const entry of item.content) {
|
|
378
|
+
if (entry.type === 'text')
|
|
379
|
+
parts.push(entry.text)
|
|
380
|
+
}
|
|
381
|
+
return optionalNonEmpty(stripAuthorContext(parts.join('\n')))
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function hookPromptText(item: CodexHookPromptItem): string | undefined {
|
|
385
|
+
return optionalNonEmpty(item.fragments.map(fragment => fragment.text).join('\n'))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function commandExecutionResult(item: Extract<CodexCompletedItem, { type: 'commandExecution' }>, status: 'completed' | 'failed'): AgentToolResult | undefined {
|
|
389
|
+
const text = optionalNonEmpty(item.aggregatedOutput ?? undefined)
|
|
390
|
+
if (!text)
|
|
391
|
+
return undefined
|
|
392
|
+
|
|
393
|
+
return { success: status === 'completed', content: [{ type: 'text', text }] }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function dynamicToolResult(item: CodexDynamicToolCompletedItem): AgentToolResult | undefined {
|
|
397
|
+
if (!item.contentItems)
|
|
398
|
+
return undefined
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
success: item.success ?? item.status !== 'failed',
|
|
402
|
+
content: item.contentItems.map(dynamicToolContent),
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function dynamicToolContent(item: CodexDynamicToolContentItem): AgentToolContent {
|
|
407
|
+
if (item.type === 'inputText')
|
|
408
|
+
return { type: 'text', text: item.text }
|
|
409
|
+
|
|
410
|
+
if (item.type === 'inputAudio')
|
|
411
|
+
return { type: 'text', text: item.audioUrl }
|
|
412
|
+
|
|
413
|
+
const dataUrl = /^data:([^;,]+);base64,(.*)$/s.exec(item.imageUrl)
|
|
414
|
+
if (dataUrl)
|
|
415
|
+
return { type: 'image', mimeType: dataUrl[1] ?? 'application/octet-stream', data: dataUrl[2] ?? '' }
|
|
416
|
+
|
|
417
|
+
return { type: 'text', text: item.imageUrl }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: { turnId: string, threadId: string, item: CodexCollabAgentToolCompletedItem, mapperState: CodexEventMapperState }): AgentEventDraft[] {
|
|
421
|
+
const drafts = emitOrBufferByThread({
|
|
422
|
+
draft: {
|
|
423
|
+
type: 'tool.call.completed',
|
|
424
|
+
turnId,
|
|
425
|
+
payload: removeUndefined({
|
|
426
|
+
toolCallId: item.id,
|
|
427
|
+
status: item.status === 'failed' ? 'failed' as const : 'completed' as const,
|
|
428
|
+
error: item.status === 'failed' ? { message: `Codex ${item.tool} failed.` } : undefined,
|
|
429
|
+
}),
|
|
430
|
+
},
|
|
431
|
+
threadId,
|
|
432
|
+
state: mapperState,
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
if (item.tool === 'spawnAgent') {
|
|
436
|
+
const subagentId = item.receiverThreadIds.at(0)
|
|
437
|
+
if (!subagentId || item.status === 'failed' || mapperState.subagentByThreadId.has(subagentId))
|
|
438
|
+
return drafts
|
|
439
|
+
|
|
440
|
+
const actor = subagentActorFromCollabItem({ subagentId, parentToolCallId: item.id, name: mapperState.subagentNameByThreadId.get(subagentId) })
|
|
441
|
+
mapperState.subagentByThreadId.set(subagentId, actor)
|
|
442
|
+
mapperState.subagentById.set(subagentId, actor)
|
|
443
|
+
drafts.push({
|
|
444
|
+
type: 'subagent.started',
|
|
445
|
+
turnId,
|
|
446
|
+
actor,
|
|
447
|
+
payload: removeUndefined({
|
|
448
|
+
...subagentPayloadRef(actor),
|
|
449
|
+
description: optionalNonEmpty(item.prompt),
|
|
450
|
+
}),
|
|
451
|
+
})
|
|
452
|
+
drafts.push(...flushPendingDrafts(subagentId, mapperState))
|
|
453
|
+
return drafts
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (item.tool !== 'wait' && item.tool !== 'closeAgent')
|
|
457
|
+
return drafts
|
|
458
|
+
|
|
459
|
+
drafts.push(...Object.entries(item.agentsStates).flatMap(([subagentId, state]) => {
|
|
460
|
+
const status = subagentStatusFromAgentState(state)
|
|
461
|
+
const actor = mapperState.subagentById.get(subagentId)
|
|
462
|
+
if (!status || !actor || mapperState.endedSubagentIds.has(subagentId))
|
|
463
|
+
return []
|
|
464
|
+
|
|
465
|
+
mapperState.endedSubagentIds.add(subagentId)
|
|
466
|
+
return [{
|
|
467
|
+
type: 'subagent.ended',
|
|
468
|
+
turnId,
|
|
469
|
+
actor,
|
|
470
|
+
payload: removeUndefined({
|
|
471
|
+
...subagentPayloadRef(actor),
|
|
472
|
+
status,
|
|
473
|
+
summary: optionalNonEmpty(state.message),
|
|
474
|
+
error: status === 'failed' ? { message: optionalNonEmpty(state.message) ?? `Subagent ${subagentId} failed.` } : undefined,
|
|
475
|
+
}),
|
|
476
|
+
} satisfies AgentEventDraft]
|
|
477
|
+
}))
|
|
478
|
+
return drafts
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function mapSubAgentActivityCompleted(input: { turnId: string, threadId: string, item: CodexSubAgentActivityCompletedItem, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
482
|
+
const { turnId, threadId, item, state } = input
|
|
483
|
+
if (item.kind === 'started') {
|
|
484
|
+
if (state.subagentByThreadId.has(item.agentThreadId))
|
|
485
|
+
return []
|
|
486
|
+
|
|
487
|
+
const actor = subagentActorFromCollabItem({
|
|
488
|
+
subagentId: item.agentThreadId,
|
|
489
|
+
parentToolCallId: item.id,
|
|
490
|
+
name: item.agentPath,
|
|
491
|
+
})
|
|
492
|
+
state.subagentByThreadId.set(item.agentThreadId, actor)
|
|
493
|
+
state.subagentById.set(item.agentThreadId, actor)
|
|
494
|
+
return [
|
|
495
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.started', turnId, payload: { toolCallId: item.id, toolName: 'spawnAgent' } }, threadId, state }),
|
|
496
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, status: 'completed' } }, threadId, state }),
|
|
497
|
+
{
|
|
498
|
+
type: 'subagent.started',
|
|
499
|
+
turnId,
|
|
500
|
+
actor,
|
|
501
|
+
payload: subagentPayloadRef(actor),
|
|
502
|
+
},
|
|
503
|
+
...flushPendingDrafts(item.agentThreadId, state),
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (item.kind === 'interacted') {
|
|
508
|
+
return [
|
|
509
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.started', turnId, payload: { toolCallId: item.id, toolName: 'sendInput' } }, threadId, state }),
|
|
510
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, status: 'completed' } }, threadId, state }),
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (item.kind !== 'interrupted')
|
|
515
|
+
return []
|
|
516
|
+
|
|
517
|
+
const actor = state.subagentByThreadId.get(item.agentThreadId)
|
|
518
|
+
if (!actor || state.endedSubagentIds.has(item.agentThreadId))
|
|
519
|
+
return []
|
|
520
|
+
|
|
521
|
+
state.endedSubagentIds.add(item.agentThreadId)
|
|
522
|
+
return [{
|
|
523
|
+
type: 'subagent.ended',
|
|
524
|
+
turnId,
|
|
525
|
+
actor,
|
|
526
|
+
payload: {
|
|
527
|
+
...subagentPayloadRef(actor),
|
|
528
|
+
status: 'cancelled',
|
|
529
|
+
},
|
|
530
|
+
}]
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function mapChildTurnCompleted(input: {
|
|
534
|
+
threadId: string
|
|
535
|
+
turnId: string
|
|
536
|
+
turnPayload: Extract<AgentEventDraft, { type: 'turn.ended' }>['payload']
|
|
537
|
+
state: CodexEventMapperState
|
|
538
|
+
}): AgentEventDraft[] {
|
|
539
|
+
const actor = input.state.subagentByThreadId.get(input.threadId)
|
|
540
|
+
if (!actor || input.state.endedSubagentIds.has(actor.subagentId))
|
|
541
|
+
return []
|
|
542
|
+
|
|
543
|
+
input.state.endedSubagentIds.add(actor.subagentId)
|
|
544
|
+
return [subagentEndedFromTurn({ actor, turnId: input.turnId, turnPayload: input.turnPayload })]
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function subagentEndedFromTurn(input: {
|
|
548
|
+
actor: CodexSubagentRef
|
|
549
|
+
turnId: string
|
|
550
|
+
turnPayload: Extract<AgentEventDraft, { type: 'turn.ended' }>['payload']
|
|
551
|
+
}): AgentEventDraft {
|
|
552
|
+
return {
|
|
553
|
+
type: 'subagent.ended',
|
|
554
|
+
turnId: input.turnId,
|
|
555
|
+
actor: input.actor,
|
|
556
|
+
payload: removeUndefined({
|
|
557
|
+
...subagentPayloadRef(input.actor),
|
|
558
|
+
status: input.turnPayload.status === 'interrupted' ? 'cancelled' : input.turnPayload.status,
|
|
559
|
+
error: input.turnPayload.error,
|
|
560
|
+
}),
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function subagentStatusFromAgentState(state: CodexCollabAgentState): AgentSubagentStatus | undefined {
|
|
565
|
+
if (state.status === 'completed')
|
|
566
|
+
return 'completed'
|
|
567
|
+
if (state.status === 'errored' || state.status === 'notFound')
|
|
568
|
+
return 'failed'
|
|
569
|
+
if (state.status === 'interrupted' || state.status === 'shutdown')
|
|
570
|
+
return 'cancelled'
|
|
571
|
+
|
|
572
|
+
return undefined
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function agentErrorFromCodexTurnError(error: CodexTurnError): AgentError {
|
|
576
|
+
return removeUndefined({
|
|
577
|
+
message: error.message,
|
|
578
|
+
code: codexErrorCode(error.codexErrorInfo),
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function codexErrorCode(info: CodexTurnError['codexErrorInfo']): string | undefined {
|
|
583
|
+
if (!info)
|
|
584
|
+
return undefined
|
|
585
|
+
if (typeof info === 'string')
|
|
586
|
+
return info
|
|
587
|
+
const key = Object.keys(info).at(0)
|
|
588
|
+
return key || undefined
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function emitOrBufferByThread({ draft, threadId, state }: { draft: CodexUnownedTurnEventDraft, threadId: string, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
592
|
+
const actor = actorForThread(threadId, state)
|
|
593
|
+
if (actor)
|
|
594
|
+
return [{ ...draft, actor }]
|
|
595
|
+
|
|
596
|
+
const pending = state.pendingDraftsByThreadId.get(threadId) ?? []
|
|
597
|
+
pending.push(draft)
|
|
598
|
+
state.pendingDraftsByThreadId.set(threadId, pending)
|
|
599
|
+
return []
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function actorForThread(threadId: string, state: CodexEventMapperState): AgentEventActor | undefined {
|
|
603
|
+
const subagentActor = state.subagentByThreadId.get(threadId)
|
|
604
|
+
if (subagentActor)
|
|
605
|
+
return subagentActor
|
|
606
|
+
return state.mainThreadId === undefined || threadId === state.mainThreadId ? mainActor : undefined
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function flushPendingDrafts(threadId: string, state: CodexEventMapperState): AgentEventDraft[] {
|
|
610
|
+
const actor = state.subagentByThreadId.get(threadId)
|
|
611
|
+
if (!actor)
|
|
612
|
+
return []
|
|
613
|
+
const pending = state.pendingDraftsByThreadId.get(threadId) ?? []
|
|
614
|
+
state.pendingDraftsByThreadId.delete(threadId)
|
|
615
|
+
|
|
616
|
+
const drafts: AgentEventDraft[] = []
|
|
617
|
+
for (const draft of pending) {
|
|
618
|
+
if (draft.type === 'turn.ended' && !state.endedSubagentIds.has(actor.subagentId)) {
|
|
619
|
+
state.endedSubagentIds.add(actor.subagentId)
|
|
620
|
+
drafts.push(subagentEndedFromTurn({ actor, turnId: draft.turnId, turnPayload: draft.payload }))
|
|
621
|
+
}
|
|
622
|
+
drafts.push({ ...draft, actor })
|
|
623
|
+
}
|
|
624
|
+
return drafts
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function cacheThreadMetadata(thread: CodexThreadMetadata, state: CodexEventMapperState): void {
|
|
628
|
+
const name = codexSubagentDisplayName(thread)
|
|
629
|
+
if (name)
|
|
630
|
+
cacheSubagentName({ subagentId: thread.id, name }, state)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function cacheSubagentName(input: { subagentId: string, name: string }, state: CodexEventMapperState): void {
|
|
634
|
+
state.subagentNameByThreadId.set(input.subagentId, input.name)
|
|
635
|
+
const existing = state.subagentByThreadId.get(input.subagentId)
|
|
636
|
+
if (!existing || existing.name === input.name)
|
|
637
|
+
return
|
|
638
|
+
|
|
639
|
+
const renamed = { ...existing, name: input.name }
|
|
640
|
+
state.subagentByThreadId.set(input.subagentId, renamed)
|
|
641
|
+
state.subagentById.set(existing.subagentId, renamed)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function codexSubagentDisplayName(thread: CodexThreadMetadata): string | undefined {
|
|
645
|
+
const nickname = optionalNonEmpty(thread.agentNickname)
|
|
646
|
+
const role = optionalNonEmpty(thread.agentRole)
|
|
647
|
+
if (nickname && role)
|
|
648
|
+
return `${nickname} [${role}]`
|
|
649
|
+
return nickname ?? role
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function subagentActorFromCollabItem(input: { subagentId: string, parentToolCallId: string, name?: string }): CodexSubagentRef {
|
|
653
|
+
return removeUndefined({
|
|
654
|
+
type: 'subagent' as const,
|
|
655
|
+
actorId: input.subagentId,
|
|
656
|
+
subagentId: input.subagentId,
|
|
657
|
+
parentActorId: 'main',
|
|
658
|
+
origin: { type: 'tool_call' as const, toolCallId: input.parentToolCallId },
|
|
659
|
+
parentToolCallId: input.parentToolCallId,
|
|
660
|
+
name: input.name,
|
|
661
|
+
})
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function subagentPayloadRef(actor: CodexSubagentRef): Pick<CodexSubagentRef, '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 collabToolInput(item: CodexCollabAgentToolStartedItem): JsonObject {
|
|
676
|
+
return toJsonObject({
|
|
677
|
+
senderThreadId: item.senderThreadId,
|
|
678
|
+
receiverThreadIds: item.receiverThreadIds.join(','),
|
|
679
|
+
prompt: item.prompt,
|
|
680
|
+
model: item.model,
|
|
681
|
+
reasoningEffort: typeof item.reasoningEffort === 'string' ? item.reasoningEffort : undefined,
|
|
682
|
+
})
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function usageFromTokenUsage(value: CodexTokenUsage | undefined): AgentUsage | undefined {
|
|
686
|
+
return value ? usageFromCounts(value.last) : undefined
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function usageFromCounts(value: CodexTokenCounts): AgentUsage {
|
|
690
|
+
return removeUndefined({
|
|
691
|
+
inputTokens: value.inputTokens,
|
|
692
|
+
outputTokens: value.outputTokens,
|
|
693
|
+
cacheReadTokens: value.cachedInputTokens,
|
|
694
|
+
reasoningOutputTokens: value.reasoningOutputTokens,
|
|
695
|
+
totalTokens: value.totalTokens,
|
|
696
|
+
})
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function jsonInput(value: unknown): JsonValue {
|
|
700
|
+
const parsed = jsonValueSchema.safeParse(value)
|
|
701
|
+
if (parsed.success)
|
|
702
|
+
return parsed.data
|
|
703
|
+
return {}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function toJsonObject(value: { readonly [K in string]?: string | number | boolean | null | undefined }): JsonObject {
|
|
707
|
+
return jsonObjectSchema.parse(Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)))
|
|
708
|
+
}
|