@meistrari/agent-core 0.2.0-experimental.0 → 0.2.0-experimental.2
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 +82 -0
- package/docs/consumer-capability-matrix.md +374 -0
- package/package.json +17 -10
- package/scripts/generate-codex-protocol.ts +711 -0
- package/src/agents/agent-operation.ts +8 -0
- package/src/agents/agent-provider.ts +3 -0
- package/src/agents/agent-run.ts +14 -2
- package/src/agents/agent-runtime-error.ts +28 -0
- package/src/agents/agent-session-events.ts +33 -0
- package/src/agents/agent-tool-runner.ts +12 -11
- package/src/agents/agent-tool.ts +12 -4
- package/src/agents/author-context.ts +1 -1
- package/src/agents/claude/claude-auth.ts +40 -0
- package/src/agents/claude/claude-browser-turn-metadata.ts +70 -0
- package/src/agents/claude/claude-code-process.ts +98 -0
- package/src/agents/claude/claude-command-mapper.ts +63 -3
- package/src/agents/claude/claude-desktop.ts +55 -0
- package/src/agents/claude/claude-event-mapper.ts +876 -124
- package/src/agents/claude/claude-file-change.ts +128 -0
- package/src/agents/claude/claude-provider.ts +344 -29
- package/src/agents/claude/claude-run.ts +922 -91
- package/src/agents/claude/claude-selected-repository-instructions.ts +91 -0
- package/src/agents/claude/claude-skill-catalog.ts +75 -0
- package/src/agents/claude/claude-steered-notifications.ts +180 -0
- package/src/agents/claude/claude-tool-mapper.ts +81 -2
- package/src/agents/claude/claude-workspace-skills.ts +87 -0
- package/src/agents/claude/index.ts +2 -0
- package/src/agents/codex/codex-auth-mapper.ts +27 -0
- package/src/agents/codex/codex-auth.ts +19 -0
- package/src/agents/codex/codex-browser-auth-broker.ts +320 -0
- package/src/agents/codex/codex-browser-auth-status.ts +55 -0
- package/src/agents/codex/codex-command-mapper.ts +38 -3
- package/src/agents/codex/codex-event-mapper.ts +375 -64
- package/src/agents/codex/codex-json-rpc-client.ts +66 -21
- package/src/agents/codex/codex-provider.ts +118 -236
- package/src/agents/codex/codex-run-auth.ts +113 -0
- package/src/agents/codex/codex-run.ts +428 -48
- package/src/agents/codex/codex-tool-mapper.ts +2 -2
- package/src/agents/codex/codex.errors.ts +8 -0
- package/src/agents/codex/generated/meta.gen.ts +144 -6
- package/src/agents/codex/generated/namespaces.gen.ts +92 -3
- package/src/agents/codex/generated/schema.gen.ts +24698 -15638
- package/src/agents/codex/lazy-codex-run-auth.ts +47 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/input-attachment-preparation.ts +13 -0
- package/src/agents/mcp-tool-content.ts +82 -0
- package/src/agents/model-rebinding-run.ts +14 -3
- package/src/agents/selected-repository-instructions.ts +202 -0
- package/src/agents/session-context.ts +41 -0
- package/src/agents/subscription-token-refresh.ts +73 -0
- package/src/agents/user-input-request.ts +19 -20
- package/src/errors/index.ts +1 -1
- package/src/errors/validation-error.ts +6 -0
- package/src/protocol/agent-catalog.ts +159 -0
- package/src/protocol/agent-command.ts +54 -7
- package/src/protocol/agent-content.ts +36 -16
- package/src/protocol/agent-event.ts +210 -73
- package/src/protocol/agent-identity.ts +29 -0
- package/src/protocol/agent-json.ts +122 -8
- package/src/protocol/agent-metadata.ts +2 -2
- package/src/protocol/agent-model.ts +13 -1
- package/src/protocol/agent-notification.ts +110 -0
- package/src/protocol/agent-presentation.ts +71 -20
- package/src/protocol/agent-tool.ts +66 -10
- package/src/protocol/agent-usage.ts +4 -1
- package/src/protocol/agent-user-input.ts +4 -4
- package/src/protocol/agent-work.ts +12 -10
- package/src/protocol/index.ts +2 -0
- package/src/protocol/rejected-keys.ts +11 -0
- package/src/provenance.gen.ts +3 -3
- package/src/steps/contract.ts +4 -1
- package/src/steps/projector.ts +17 -1
- package/src/supervisor/agent-provider-factory.ts +60 -5
- package/src/supervisor/bootstrap-binder.ts +3 -2
- package/src/supervisor/index.ts +1 -1
- package/src/supervisor/persistence/supervisor-store.ts +22 -8
- package/src/supervisor/runtime-handler.ts +30 -2
- package/src/supervisor-protocol/agent-event-wrapper.ts +30 -0
- package/src/supervisor-protocol/bootstrap.ts +4 -1
- package/src/supervisor-protocol/command-body.ts +4 -1
- package/src/supervisor-protocol/envelopes/supervisor-to-control-plane.ts +1 -0
- package/src/testing/in-memory-runtime-control-plane.ts +118 -12
- package/src/testing/index.ts +1 -1
- package/src/worker-runtime-client/command-pump.ts +85 -10
- package/src/worker-runtime-client/connection-attempt.ts +95 -20
- package/src/worker-runtime-client/frame-processor.ts +242 -58
- package/src/worker-runtime-client/index.ts +8 -2
- package/src/worker-runtime-client/ports.ts +34 -2
- package/src/worker-runtime-client/rpc-request-manager.ts +152 -107
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +72 -35
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type { AgentError, AgentEventActor, AgentMessageRole, AgentSubagentStatus, AgentToolCallStatus, AgentToolContent, AgentToolResult, AgentTurnEndStatus, AgentUsage, JsonObject, JsonValue } from '../../protocol'
|
|
1
|
+
import type { AgentContextSnapshot, AgentError, AgentEventActor, AgentMessageRole, AgentSubagentStatus, AgentToolCallStatus, AgentToolContent, AgentToolResult, AgentTurnEndStatus, AgentUsage, AgentUsageAccounting, JsonObject, JsonValue } from '../../protocol'
|
|
2
2
|
import type { AgentWorkStatus } from '../../protocol/agent-work'
|
|
3
3
|
import type { AgentEventDraft } from '../agent-event-stream'
|
|
4
4
|
import type { CodexJsonRpcMessage, CodexServerNotification } from './codex-json-rpc-client'
|
|
5
5
|
import type { ServerNotificationParamsByMethod } from './codex-protocol'
|
|
6
|
-
import { createHash } from 'node:crypto'
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
7
7
|
import { jsonObjectSchema, jsonValueSchema } from '../../protocol'
|
|
8
8
|
import { CANONICAL_TOOL } from '../../protocol/agent-tool-name'
|
|
9
9
|
import { createRuntimeWorkItemId } from '../agent-id'
|
|
10
10
|
import { AgentProviderProtocolError } from '../agent-runtime-error'
|
|
11
11
|
import { stripAuthorContext } from '../author-context'
|
|
12
|
+
import { jsonToolContent, mcpToolContent, ownJsonSlot } from '../mcp-tool-content'
|
|
12
13
|
import { optionalNonEmpty, removeUndefined } from '../normalize'
|
|
14
|
+
import { stripSessionContext } from '../session-context'
|
|
13
15
|
import { isCodexServerNotification } from './codex-json-rpc-client'
|
|
14
16
|
|
|
15
17
|
export type CodexNotification = CodexServerNotification
|
|
@@ -26,6 +28,7 @@ type CodexUserMessageItem = Extract<CodexCompletedItem, { type: 'userMessage' }>
|
|
|
26
28
|
type CodexHookPromptItem = Extract<CodexCompletedItem, { type: 'hookPrompt' }>
|
|
27
29
|
type CodexDynamicToolCompletedItem = Extract<CodexCompletedItem, { type: 'dynamicToolCall' }>
|
|
28
30
|
type CodexDynamicToolContentItem = NonNullable<CodexDynamicToolCompletedItem['contentItems']>[number]
|
|
31
|
+
type CodexMcpToolCompletedItem = Extract<CodexCompletedItem, { type: 'mcpToolCall' }>
|
|
29
32
|
type CodexCollabAgentToolCompletedItem = Extract<CodexCompletedItem, { type: 'collabAgentToolCall' }>
|
|
30
33
|
type CodexCollabAgentToolStartedItem = Extract<CodexStartedItem, { type: 'collabAgentToolCall' }>
|
|
31
34
|
type CodexSubAgentActivityCompletedItem = Extract<CodexCompletedItem, { type: 'subAgentActivity' }>
|
|
@@ -36,6 +39,8 @@ type CodexTurn = ServerNotificationParamsByMethod['turn/completed']['turn']
|
|
|
36
39
|
type CodexTurnError = NonNullable<CodexTurn['error']>
|
|
37
40
|
type CodexSubagentRef = Extract<AgentEventActor, { type: 'subagent' }>
|
|
38
41
|
interface CodexPromptCommandRef { commandId: string, origin: 'user' | 'supervisor' }
|
|
42
|
+
type CodexMainTurnState = { status: 'closed' } | { status: 'open', turnId: string }
|
|
43
|
+
type CodexContextSnapshotBaseline = { status: 'empty' } | { status: 'reported', snapshot: AgentContextSnapshot }
|
|
39
44
|
type CodexTurnEventDraft = Extract<AgentEventDraft, { turnId: string }>
|
|
40
45
|
type CodexUnownedTurnEventDraft = CodexTurnEventDraft extends infer Draft
|
|
41
46
|
? Draft extends CodexTurnEventDraft
|
|
@@ -44,6 +49,9 @@ type CodexUnownedTurnEventDraft = CodexTurnEventDraft extends infer Draft
|
|
|
44
49
|
: never
|
|
45
50
|
|
|
46
51
|
const mainActor = { type: 'main', actorId: 'main' } satisfies AgentEventActor
|
|
52
|
+
// Codex's own remaining-context formula (codex-rs `protocol.rs` / `tui/token_usage.rs`, 0.153.4)
|
|
53
|
+
// subtracts a fixed 12,000-token baseline from both the used count and the capacity.
|
|
54
|
+
const CODEX_CONTEXT_BASELINE_TOKENS = 12_000
|
|
47
55
|
|
|
48
56
|
export interface CodexEventMapperState {
|
|
49
57
|
readonly endedSubagentIds: Set<string>
|
|
@@ -56,11 +64,37 @@ export interface CodexEventMapperState {
|
|
|
56
64
|
readonly startedReasoningIds: Set<string>
|
|
57
65
|
readonly reasoningSummaryById: Map<string, Map<number, string>>
|
|
58
66
|
readonly promptCommandByClientUserMessageId: Map<string, CodexPromptCommandRef>
|
|
67
|
+
readonly seenMainTurnIds: Set<string>
|
|
68
|
+
readonly openContextCompactionIds: Set<string>
|
|
69
|
+
readonly closedContextCompactionIds: Set<string>
|
|
70
|
+
currentMainTurn: CodexMainTurnState
|
|
71
|
+
contextSnapshotBaseline: CodexContextSnapshotBaseline
|
|
59
72
|
mainThreadId?: string
|
|
73
|
+
// Usage accounting scope owned by this run; see codexUsageAccounting.
|
|
74
|
+
readonly usageScopeInstanceId: string
|
|
75
|
+
readonly usageAccountingByThreadId: Map<string, { total: string, usage: AgentUsage }>
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
export function createCodexEventMapperState(): CodexEventMapperState {
|
|
63
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
endedSubagentIds: new Set(),
|
|
81
|
+
subagentByThreadId: new Map(),
|
|
82
|
+
subagentById: new Map(),
|
|
83
|
+
subagentNameByThreadId: new Map(),
|
|
84
|
+
pendingDraftsByThreadId: new Map(),
|
|
85
|
+
turnErrorsById: new Map(),
|
|
86
|
+
startedTurnIds: new Set(),
|
|
87
|
+
startedReasoningIds: new Set(),
|
|
88
|
+
reasoningSummaryById: new Map(),
|
|
89
|
+
promptCommandByClientUserMessageId: new Map(),
|
|
90
|
+
seenMainTurnIds: new Set(),
|
|
91
|
+
openContextCompactionIds: new Set(),
|
|
92
|
+
closedContextCompactionIds: new Set(),
|
|
93
|
+
currentMainTurn: { status: 'closed' },
|
|
94
|
+
contextSnapshotBaseline: { status: 'empty' },
|
|
95
|
+
usageScopeInstanceId: randomUUID(),
|
|
96
|
+
usageAccountingByThreadId: new Map(),
|
|
97
|
+
}
|
|
64
98
|
}
|
|
65
99
|
|
|
66
100
|
export function codexNotificationFromMessage(message: CodexJsonRpcMessage): CodexNotification | null {
|
|
@@ -79,13 +113,14 @@ export function mapCodexNotification(notification: CodexNotification, state: Cod
|
|
|
79
113
|
cacheThreadMetadata(notification.params.thread, state)
|
|
80
114
|
return []
|
|
81
115
|
case 'turn/started':
|
|
82
|
-
state.mainThreadId ??= notification.params.threadId
|
|
83
116
|
state.startedTurnIds.add(notification.params.turn.id)
|
|
117
|
+
observeMainTurnStarted({ threadId: notification.params.threadId, turnId: notification.params.turn.id, state })
|
|
84
118
|
return emitOrBufferByThread({ draft: { type: 'turn.started', turnId: notification.params.turn.id, payload: {} }, threadId: notification.params.threadId, state })
|
|
85
119
|
case 'turn/completed': {
|
|
86
120
|
if (!state.startedTurnIds.has(notification.params.turn.id))
|
|
87
121
|
return []
|
|
88
122
|
const payload = turnEndedPayload(notification.params.turn, state)
|
|
123
|
+
observeMainTurnCompleted({ threadId: notification.params.threadId, turnId: notification.params.turn.id, state })
|
|
89
124
|
return [
|
|
90
125
|
...mapChildTurnCompleted({ threadId: notification.params.threadId, turnId: notification.params.turn.id, turnPayload: payload, state }),
|
|
91
126
|
...emitOrBufferByThread({ draft: { type: 'turn.ended', turnId: notification.params.turn.id, payload }, threadId: notification.params.threadId, state }),
|
|
@@ -95,21 +130,21 @@ export function mapCodexNotification(notification: CodexNotification, state: Cod
|
|
|
95
130
|
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
96
131
|
return []
|
|
97
132
|
const usage = usageFromTokenUsage(notification.params.tokenUsage)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
},
|
|
112
|
-
|
|
133
|
+
const usageDrafts = usage
|
|
134
|
+
? emitOrBufferByThread({ draft: { type: 'usage', turnId: notification.params.turnId, payload: {
|
|
135
|
+
usage,
|
|
136
|
+
accounting: codexUsageAccounting(notification.params.threadId, notification.params.tokenUsage, state),
|
|
137
|
+
} }, threadId: notification.params.threadId, state })
|
|
138
|
+
: []
|
|
139
|
+
return [
|
|
140
|
+
...usageDrafts,
|
|
141
|
+
...mapContextSnapshotFromTokenUsage({
|
|
142
|
+
threadId: notification.params.threadId,
|
|
143
|
+
turnId: notification.params.turnId,
|
|
144
|
+
tokenUsage: notification.params.tokenUsage,
|
|
145
|
+
state,
|
|
146
|
+
}),
|
|
147
|
+
]
|
|
113
148
|
}
|
|
114
149
|
case 'turn/plan/updated':
|
|
115
150
|
if (!state.startedTurnIds.has(notification.params.turnId) || notification.params.threadId !== state.mainThreadId)
|
|
@@ -134,7 +169,9 @@ export function mapCodexNotification(notification: CodexNotification, state: Cod
|
|
|
134
169
|
return []
|
|
135
170
|
if (!notification.params.willRetry)
|
|
136
171
|
state.turnErrorsById.set(notification.params.turnId, agentErrorFromCodexTurnError(notification.params.error))
|
|
137
|
-
return
|
|
172
|
+
return codexErrorCode(notification.params.error.codexErrorInfo) === 'contextWindowExceeded'
|
|
173
|
+
? reportContextSnapshot({ threadId: notification.params.threadId, turnId: notification.params.turnId, snapshot: { status: 'unknown' }, state })
|
|
174
|
+
: []
|
|
138
175
|
case 'item/agentMessage/delta':
|
|
139
176
|
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
140
177
|
return []
|
|
@@ -157,6 +194,10 @@ export function mapCodexNotification(notification: CodexNotification, state: Cod
|
|
|
157
194
|
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
158
195
|
return []
|
|
159
196
|
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 })
|
|
197
|
+
case 'item/mcpToolCall/progress':
|
|
198
|
+
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
199
|
+
return []
|
|
200
|
+
return emitOrBufferByThread({ draft: { type: 'tool.output.delta', turnId: notification.params.turnId, payload: { toolCallId: notification.params.itemId, delta: notification.params.message } }, threadId: notification.params.threadId, state })
|
|
160
201
|
case 'item/started':
|
|
161
202
|
if (!state.startedTurnIds.has(notification.params.turnId))
|
|
162
203
|
return []
|
|
@@ -210,27 +251,55 @@ function mapItemStarted({ turnId, threadId, item, state }: { turnId: string, thr
|
|
|
210
251
|
if (toolCall)
|
|
211
252
|
return emitOrBufferByThread({ draft: { type: 'tool.call.started', turnId, payload: { toolCallId: item.id, toolName: toolCall.toolName, input: toolCall.input } }, threadId, state })
|
|
212
253
|
|
|
213
|
-
if (item.type === 'contextCompaction')
|
|
254
|
+
if (item.type === 'contextCompaction') {
|
|
255
|
+
observeContextCompactionStarted({ threadId, turnId, compactionId: item.id, state })
|
|
214
256
|
return emitOrBufferByThread({ draft: { type: 'context.compaction.started', turnId, payload: { compactionId: item.id, trigger: 'unknown' } }, threadId, state })
|
|
257
|
+
}
|
|
215
258
|
|
|
216
259
|
return []
|
|
217
260
|
}
|
|
218
261
|
|
|
219
262
|
function toolCallStart(item: CodexStartedItem): { toolName: string, input: JsonValue } | undefined {
|
|
263
|
+
const toolName = toolNameForItem(item)
|
|
264
|
+
if (!toolName)
|
|
265
|
+
return undefined
|
|
266
|
+
|
|
267
|
+
if (item.type === 'commandExecution')
|
|
268
|
+
return { toolName, input: toJsonObject({ command: Array.isArray(item.command) ? item.command.join(' ') : String(item.command ?? ''), cwd: item.cwd }) }
|
|
269
|
+
if (item.type === 'webSearch')
|
|
270
|
+
return { toolName, input: toJsonObject({ query: optionalNonEmpty(item.query) }) }
|
|
271
|
+
if (item.type === 'fileChange')
|
|
272
|
+
return { toolName, input: jsonInput({ files: item.changes.map(change => removeUndefined({ path: change.path, kind: change.kind?.type })) }) }
|
|
273
|
+
if (item.type === 'imageView')
|
|
274
|
+
return { toolName, input: toJsonObject({ path: item.path }) }
|
|
275
|
+
if (item.type === 'imageGeneration')
|
|
276
|
+
return { toolName, input: toJsonObject({ revisedPrompt: optionalNonEmpty(item.revisedPrompt) }) }
|
|
277
|
+
if (item.type === 'dynamicToolCall')
|
|
278
|
+
return { toolName, input: jsonInput(item.arguments) }
|
|
279
|
+
if (item.type === 'mcpToolCall')
|
|
280
|
+
return { toolName, input: jsonInput(item.arguments) }
|
|
281
|
+
if (item.type === 'collabAgentToolCall')
|
|
282
|
+
return { toolName, input: collabToolInput(item) }
|
|
283
|
+
return undefined
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function toolNameForItem(item: CodexStartedItem | CodexCompletedItem): string | undefined {
|
|
220
287
|
if (item.type === 'commandExecution')
|
|
221
|
-
return
|
|
288
|
+
return CANONICAL_TOOL.bash
|
|
222
289
|
if (item.type === 'webSearch')
|
|
223
|
-
return
|
|
290
|
+
return CANONICAL_TOOL.webSearch
|
|
224
291
|
if (item.type === 'fileChange')
|
|
225
|
-
return
|
|
292
|
+
return CANONICAL_TOOL.edit
|
|
226
293
|
if (item.type === 'imageView')
|
|
227
|
-
return
|
|
294
|
+
return CANONICAL_TOOL.viewImage
|
|
228
295
|
if (item.type === 'imageGeneration')
|
|
229
|
-
return
|
|
296
|
+
return CANONICAL_TOOL.imageGeneration
|
|
230
297
|
if (item.type === 'dynamicToolCall')
|
|
231
|
-
return
|
|
298
|
+
return item.tool
|
|
299
|
+
if (item.type === 'mcpToolCall')
|
|
300
|
+
return `mcp:${item.server}/${item.tool}`
|
|
232
301
|
if (item.type === 'collabAgentToolCall')
|
|
233
|
-
return
|
|
302
|
+
return item.tool
|
|
234
303
|
return undefined
|
|
235
304
|
}
|
|
236
305
|
|
|
@@ -259,8 +328,10 @@ function mapItemCompleted({ turnId, threadId, item, state }: { turnId: string, t
|
|
|
259
328
|
if (item.type === 'subAgentActivity')
|
|
260
329
|
return mapSubAgentActivityCompleted({ turnId, threadId, item, state })
|
|
261
330
|
|
|
262
|
-
if (item.type === 'contextCompaction')
|
|
331
|
+
if (item.type === 'contextCompaction') {
|
|
332
|
+
observeContextCompactionCompleted({ threadId, turnId, compactionId: item.id, state })
|
|
263
333
|
return emitOrBufferByThread({ draft: { type: 'context.compaction.completed', turnId, payload: { compactionId: item.id, trigger: 'unknown', status: 'completed' } }, threadId, state })
|
|
334
|
+
}
|
|
264
335
|
|
|
265
336
|
if (item.type === 'reasoning')
|
|
266
337
|
return mapReasoningCompleted({ turnId, threadId, item, state })
|
|
@@ -310,26 +381,48 @@ function messageForItem(item: CodexStartedItem | CodexCompletedItem, state: Code
|
|
|
310
381
|
return removeUndefined({ role, commandId: promptCommand?.commandId })
|
|
311
382
|
}
|
|
312
383
|
|
|
313
|
-
function toolCallCompletion(item: CodexCompletedItem): { status: AgentToolCallStatus, output?: AgentToolResult, error?: AgentError } | undefined {
|
|
384
|
+
function toolCallCompletion(item: CodexCompletedItem): { toolName: string, status: AgentToolCallStatus, output?: AgentToolResult, error?: AgentError } | undefined {
|
|
385
|
+
const toolName = toolNameForItem(item)
|
|
386
|
+
if (!toolName)
|
|
387
|
+
return undefined
|
|
388
|
+
|
|
314
389
|
if (item.type === 'webSearch')
|
|
315
|
-
return { status: 'completed', output: webSearchResult(item) }
|
|
390
|
+
return { toolName, status: 'completed', output: webSearchResult(item) }
|
|
316
391
|
if (item.type === 'fileChange') {
|
|
317
392
|
const status = fileChangeStatus(item.status)
|
|
318
|
-
return { status, output: fileChangeResult(item, status), error: status === 'failed' ? { message: 'Codex failed to apply the patch.' } : undefined }
|
|
393
|
+
return { toolName, status, output: fileChangeResult(item, status), error: status === 'failed' ? { message: 'Codex failed to apply the patch.' } : undefined }
|
|
319
394
|
}
|
|
320
395
|
if (item.type === 'imageView')
|
|
321
|
-
return { status: 'completed', output: { success: true, content: [{ type: 'text', text: item.path }] } }
|
|
396
|
+
return { toolName, status: 'completed', output: { success: true, content: [{ type: 'text', text: item.path }] } }
|
|
322
397
|
if (item.type === 'imageGeneration')
|
|
323
|
-
return { status: item.status === 'failed' ? 'failed' : 'completed', output: imageGenerationResult(item) }
|
|
398
|
+
return { toolName, status: item.status === 'failed' ? 'failed' : 'completed', output: imageGenerationResult(item) }
|
|
324
399
|
if (item.type === 'commandExecution') {
|
|
325
400
|
const status = item.exitCode === undefined || item.exitCode === null || item.exitCode === 0 ? 'completed' : 'failed'
|
|
326
401
|
const error = 'error' in item && typeof item.error === 'string'
|
|
327
402
|
? { message: item.error }
|
|
328
403
|
: status === 'failed' ? { message: `Command exited with code ${item.exitCode}.` } : undefined
|
|
329
|
-
return { status, output: commandExecutionResult(item, status), error }
|
|
404
|
+
return { toolName, status, output: commandExecutionResult(item, status), error }
|
|
330
405
|
}
|
|
331
406
|
if (item.type === 'dynamicToolCall')
|
|
332
|
-
return { status: item.success === false ? 'failed' : 'completed', output: dynamicToolResult(item) }
|
|
407
|
+
return { toolName, status: item.success === false ? 'failed' : 'completed', output: dynamicToolResult(item) }
|
|
408
|
+
if (item.type === 'mcpToolCall') {
|
|
409
|
+
if (item.status === 'inProgress') {
|
|
410
|
+
return {
|
|
411
|
+
toolName,
|
|
412
|
+
status: 'failed',
|
|
413
|
+
output: mcpToolResult(item, 'failed'),
|
|
414
|
+
error: { message: 'MCP tool result could not be determined because completion status was inconsistent.' },
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const status = item.status
|
|
419
|
+
return {
|
|
420
|
+
toolName,
|
|
421
|
+
status,
|
|
422
|
+
output: mcpToolResult(item, status),
|
|
423
|
+
error: status === 'failed' ? mcpToolError(item.error?.message) : undefined,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
333
426
|
return undefined
|
|
334
427
|
}
|
|
335
428
|
|
|
@@ -373,9 +466,18 @@ function imageGenerationResult(item: CodexImageGenerationItem): AgentToolResult
|
|
|
373
466
|
const revised = optionalNonEmpty(item.revisedPrompt)
|
|
374
467
|
if (revised)
|
|
375
468
|
lines.push(`Revised prompt: ${revised}`)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
469
|
+
const content: AgentToolContent[] = lines.length > 0 ? [{ type: 'text', text: lines.join('\n') }] : []
|
|
470
|
+
if (item.result !== '')
|
|
471
|
+
content.push({ type: 'image', data: item.result, mimeType: 'image/png' })
|
|
472
|
+
const failure = ownJsonSlot(item, 'failure')
|
|
473
|
+
if (item.result === '' || failure !== undefined || item.transparentBackground !== undefined) {
|
|
474
|
+
content.push(jsonToolContent({
|
|
475
|
+
...(item.result === '' ? { result: '' } : {}),
|
|
476
|
+
...(failure !== undefined ? { failure } : {}),
|
|
477
|
+
...(item.transparentBackground !== undefined ? { transparentBackground: item.transparentBackground } : {}),
|
|
478
|
+
}))
|
|
479
|
+
}
|
|
480
|
+
return { success: item.status !== 'failed', content }
|
|
379
481
|
}
|
|
380
482
|
|
|
381
483
|
function messageEndedText(item: CodexCompletedItem): string | undefined {
|
|
@@ -391,10 +493,10 @@ function messageEndedText(item: CodexCompletedItem): string | undefined {
|
|
|
391
493
|
function userMessageText(item: CodexUserMessageItem): string | undefined {
|
|
392
494
|
const parts: string[] = []
|
|
393
495
|
for (const entry of item.content) {
|
|
394
|
-
if (entry.type === 'text')
|
|
496
|
+
if (entry.type === 'text' && typeof entry.text === 'string')
|
|
395
497
|
parts.push(entry.text)
|
|
396
498
|
}
|
|
397
|
-
return optionalNonEmpty(stripAuthorContext(parts.join('\n')))
|
|
499
|
+
return optionalNonEmpty(stripSessionContext(stripAuthorContext(parts.join('\n'))))
|
|
398
500
|
}
|
|
399
501
|
|
|
400
502
|
function hookPromptText(item: CodexHookPromptItem): string | undefined {
|
|
@@ -414,6 +516,33 @@ function commandExecutionResult(item: Extract<CodexCompletedItem, { type: 'comma
|
|
|
414
516
|
return { success: status === 'completed', content: [{ type: 'text', text }] }
|
|
415
517
|
}
|
|
416
518
|
|
|
519
|
+
function mcpToolResult(item: CodexMcpToolCompletedItem, status: 'completed' | 'failed'): AgentToolResult | undefined {
|
|
520
|
+
if (item.result === null || item.result === undefined)
|
|
521
|
+
return undefined
|
|
522
|
+
|
|
523
|
+
if (typeof item.result !== 'object' || Array.isArray(item.result)) {
|
|
524
|
+
const parsed = jsonValueSchema.safeParse(item.result)
|
|
525
|
+
return parsed.success
|
|
526
|
+
? { success: status === 'completed', content: [jsonToolContent(parsed.data)] }
|
|
527
|
+
: undefined
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const content: AgentToolContent[] = []
|
|
531
|
+
const resultContent = ownJsonSlot(item.result, 'content')
|
|
532
|
+
if (resultContent !== undefined) {
|
|
533
|
+
if (Array.isArray(resultContent))
|
|
534
|
+
content.push(...resultContent.map(mcpToolContent))
|
|
535
|
+
else
|
|
536
|
+
content.push(mcpToolContent(resultContent))
|
|
537
|
+
}
|
|
538
|
+
const structuredContent = ownJsonSlot(item.result, 'structuredContent')
|
|
539
|
+
return removeUndefined({ success: status === 'completed', content, structuredContent })
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function mcpToolError(value: unknown): AgentError {
|
|
543
|
+
return { message: typeof value === 'string' ? value : 'MCP tool call failed.' }
|
|
544
|
+
}
|
|
545
|
+
|
|
417
546
|
function dynamicToolResult(item: CodexDynamicToolCompletedItem): AgentToolResult | undefined {
|
|
418
547
|
if (!item.contentItems)
|
|
419
548
|
return undefined
|
|
@@ -428,14 +557,18 @@ function dynamicToolContent(item: CodexDynamicToolContentItem): AgentToolContent
|
|
|
428
557
|
if (item.type === 'inputText')
|
|
429
558
|
return { type: 'text', text: item.text }
|
|
430
559
|
|
|
431
|
-
if (item.type === 'inputAudio')
|
|
432
|
-
|
|
560
|
+
if (item.type === 'inputAudio') {
|
|
561
|
+
const dataUrl = /^data:([^;,]+);base64,(.+)$/s.exec(item.audioUrl)
|
|
562
|
+
return dataUrl
|
|
563
|
+
? { type: 'audio', mimeType: dataUrl[1]!, data: dataUrl[2]! }
|
|
564
|
+
: jsonToolContent({ type: 'audio', uri: item.audioUrl })
|
|
565
|
+
}
|
|
433
566
|
|
|
434
567
|
const dataUrl = /^data:([^;,]+);base64,(.*)$/s.exec(item.imageUrl)
|
|
435
568
|
if (dataUrl)
|
|
436
569
|
return { type: 'image', mimeType: dataUrl[1] ?? 'application/octet-stream', data: dataUrl[2] ?? '' }
|
|
437
570
|
|
|
438
|
-
return { type: '
|
|
571
|
+
return { type: 'image', uri: item.imageUrl }
|
|
439
572
|
}
|
|
440
573
|
|
|
441
574
|
function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: { turnId: string, threadId: string, item: CodexCollabAgentToolCompletedItem, mapperState: CodexEventMapperState }): AgentEventDraft[] {
|
|
@@ -445,6 +578,7 @@ function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: {
|
|
|
445
578
|
turnId,
|
|
446
579
|
payload: removeUndefined({
|
|
447
580
|
toolCallId: item.id,
|
|
581
|
+
toolName: item.tool,
|
|
448
582
|
status: item.status === 'failed' ? 'failed' as const : 'completed' as const,
|
|
449
583
|
error: item.status === 'failed' ? { message: `Codex ${item.tool} failed.` } : undefined,
|
|
450
584
|
}),
|
|
@@ -454,23 +588,33 @@ function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: {
|
|
|
454
588
|
})
|
|
455
589
|
|
|
456
590
|
if (item.tool === 'spawnAgent') {
|
|
457
|
-
|
|
458
|
-
if (!subagentId || item.status === 'failed' || mapperState.subagentByThreadId.has(subagentId))
|
|
591
|
+
if (item.status === 'failed')
|
|
459
592
|
return drafts
|
|
460
593
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
|
|
594
|
+
for (const subagentId of new Set(item.receiverThreadIds)) {
|
|
595
|
+
if (!subagentId || mapperState.subagentByThreadId.has(subagentId))
|
|
596
|
+
continue
|
|
597
|
+
|
|
598
|
+
const actor = subagentActorFromCollabItem({
|
|
599
|
+
subagentId,
|
|
600
|
+
parentThreadId: item.senderThreadId,
|
|
601
|
+
parentToolCallId: item.id,
|
|
602
|
+
name: mapperState.subagentNameByThreadId.get(subagentId),
|
|
603
|
+
state: mapperState,
|
|
604
|
+
})
|
|
605
|
+
mapperState.subagentByThreadId.set(subagentId, actor)
|
|
606
|
+
mapperState.subagentById.set(subagentId, actor)
|
|
607
|
+
drafts.push({
|
|
608
|
+
type: 'subagent.started',
|
|
609
|
+
turnId,
|
|
610
|
+
actor,
|
|
611
|
+
payload: removeUndefined({
|
|
612
|
+
...subagentPayloadRef(actor),
|
|
613
|
+
description: optionalNonEmpty(item.prompt),
|
|
614
|
+
}),
|
|
615
|
+
})
|
|
616
|
+
drafts.push(...flushPendingDrafts(subagentId, mapperState))
|
|
617
|
+
}
|
|
474
618
|
return drafts
|
|
475
619
|
}
|
|
476
620
|
|
|
@@ -507,14 +651,16 @@ function mapSubAgentActivityCompleted(input: { turnId: string, threadId: string,
|
|
|
507
651
|
|
|
508
652
|
const actor = subagentActorFromCollabItem({
|
|
509
653
|
subagentId: item.agentThreadId,
|
|
654
|
+
parentThreadId: threadId,
|
|
510
655
|
parentToolCallId: item.id,
|
|
511
656
|
name: item.agentPath,
|
|
657
|
+
state,
|
|
512
658
|
})
|
|
513
659
|
state.subagentByThreadId.set(item.agentThreadId, actor)
|
|
514
660
|
state.subagentById.set(item.agentThreadId, actor)
|
|
515
661
|
return [
|
|
516
662
|
...emitOrBufferByThread({ draft: { type: 'tool.call.started', turnId, payload: { toolCallId: item.id, toolName: 'spawnAgent' } }, threadId, state }),
|
|
517
|
-
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, status: 'completed' } }, threadId, state }),
|
|
663
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, toolName: 'spawnAgent', status: 'completed' } }, threadId, state }),
|
|
518
664
|
{
|
|
519
665
|
type: 'subagent.started',
|
|
520
666
|
turnId,
|
|
@@ -528,7 +674,7 @@ function mapSubAgentActivityCompleted(input: { turnId: string, threadId: string,
|
|
|
528
674
|
if (item.kind === 'interacted') {
|
|
529
675
|
return [
|
|
530
676
|
...emitOrBufferByThread({ draft: { type: 'tool.call.started', turnId, payload: { toolCallId: item.id, toolName: 'sendInput' } }, threadId, state }),
|
|
531
|
-
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, status: 'completed' } }, threadId, state }),
|
|
677
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, toolName: 'sendInput', status: 'completed' } }, threadId, state }),
|
|
532
678
|
]
|
|
533
679
|
}
|
|
534
680
|
|
|
@@ -624,7 +770,7 @@ function actorForThread(threadId: string, state: CodexEventMapperState): AgentEv
|
|
|
624
770
|
const subagentActor = state.subagentByThreadId.get(threadId)
|
|
625
771
|
if (subagentActor)
|
|
626
772
|
return subagentActor
|
|
627
|
-
return
|
|
773
|
+
return threadId === state.mainThreadId ? mainActor : undefined
|
|
628
774
|
}
|
|
629
775
|
|
|
630
776
|
function flushPendingDrafts(threadId: string, state: CodexEventMapperState): AgentEventDraft[] {
|
|
@@ -670,12 +816,18 @@ function codexSubagentDisplayName(thread: CodexThreadMetadata): string | undefin
|
|
|
670
816
|
return nickname ?? role
|
|
671
817
|
}
|
|
672
818
|
|
|
673
|
-
function subagentActorFromCollabItem(input: {
|
|
819
|
+
function subagentActorFromCollabItem(input: {
|
|
820
|
+
subagentId: string
|
|
821
|
+
parentThreadId: string
|
|
822
|
+
parentToolCallId: string
|
|
823
|
+
name?: string
|
|
824
|
+
state: CodexEventMapperState
|
|
825
|
+
}): CodexSubagentRef {
|
|
674
826
|
return removeUndefined({
|
|
675
827
|
type: 'subagent' as const,
|
|
676
828
|
actorId: input.subagentId,
|
|
677
829
|
subagentId: input.subagentId,
|
|
678
|
-
parentActorId: 'main',
|
|
830
|
+
parentActorId: input.parentThreadId === input.state.mainThreadId ? 'main' : input.parentThreadId,
|
|
679
831
|
origin: { type: 'tool_call' as const, toolCallId: input.parentToolCallId },
|
|
680
832
|
parentToolCallId: input.parentToolCallId,
|
|
681
833
|
name: input.name,
|
|
@@ -703,10 +855,169 @@ function collabToolInput(item: CodexCollabAgentToolStartedItem): JsonObject {
|
|
|
703
855
|
})
|
|
704
856
|
}
|
|
705
857
|
|
|
858
|
+
function observeMainTurnStarted({ threadId, turnId, state }: { threadId: string, turnId: string, state: CodexEventMapperState }): void {
|
|
859
|
+
if (threadId !== state.mainThreadId || state.seenMainTurnIds.has(turnId))
|
|
860
|
+
return
|
|
861
|
+
|
|
862
|
+
state.seenMainTurnIds.add(turnId)
|
|
863
|
+
state.currentMainTurn = { status: 'open', turnId }
|
|
864
|
+
state.openContextCompactionIds.clear()
|
|
865
|
+
state.closedContextCompactionIds.clear()
|
|
866
|
+
invalidateContextSnapshotBaseline(state)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function observeMainTurnCompleted({ threadId, turnId, state }: { threadId: string, turnId: string, state: CodexEventMapperState }): void {
|
|
870
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state }))
|
|
871
|
+
return
|
|
872
|
+
|
|
873
|
+
state.currentMainTurn = { status: 'closed' }
|
|
874
|
+
state.openContextCompactionIds.clear()
|
|
875
|
+
state.closedContextCompactionIds.clear()
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function observeContextCompactionStarted({ threadId, turnId, compactionId, state }: { threadId: string, turnId: string, compactionId: string, state: CodexEventMapperState }): void {
|
|
879
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state })
|
|
880
|
+
|| state.openContextCompactionIds.has(compactionId)
|
|
881
|
+
|| state.closedContextCompactionIds.has(compactionId)) {
|
|
882
|
+
return
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
state.openContextCompactionIds.add(compactionId)
|
|
886
|
+
invalidateContextSnapshotBaseline(state)
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function observeContextCompactionCompleted({ threadId, turnId, compactionId, state }: { threadId: string, turnId: string, compactionId: string, state: CodexEventMapperState }): void {
|
|
890
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state }) || state.closedContextCompactionIds.has(compactionId))
|
|
891
|
+
return
|
|
892
|
+
|
|
893
|
+
state.openContextCompactionIds.delete(compactionId)
|
|
894
|
+
state.closedContextCompactionIds.add(compactionId)
|
|
895
|
+
invalidateContextSnapshotBaseline(state)
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function mapContextSnapshotFromTokenUsage({ threadId, turnId, tokenUsage, state }: { threadId: string, turnId: string, tokenUsage: CodexTokenUsage, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
899
|
+
if (state.openContextCompactionIds.size > 0)
|
|
900
|
+
return []
|
|
901
|
+
|
|
902
|
+
return reportContextSnapshot({
|
|
903
|
+
threadId,
|
|
904
|
+
turnId,
|
|
905
|
+
snapshot: contextSnapshotFromTokenUsage(tokenUsage),
|
|
906
|
+
state,
|
|
907
|
+
})
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function contextSnapshotFromTokenUsage(tokenUsage: CodexTokenUsage): AgentContextSnapshot {
|
|
911
|
+
const { last } = tokenUsage
|
|
912
|
+
if (!Number.isSafeInteger(last.totalTokens) || last.totalTokens < 0
|
|
913
|
+
|| (last.inputTokens === 0 && last.outputTokens === 0)) {
|
|
914
|
+
return { status: 'unknown' }
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const capacityTokens = tokenUsage.modelContextWindow
|
|
918
|
+
if (!Number.isSafeInteger(capacityTokens) || capacityTokens === null || capacityTokens === undefined || capacityTokens <= 0)
|
|
919
|
+
return { status: 'observed', usedTokens: last.totalTokens, window: { status: 'unknown' } }
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
status: 'observed',
|
|
923
|
+
usedTokens: last.totalTokens,
|
|
924
|
+
window: {
|
|
925
|
+
status: 'known',
|
|
926
|
+
capacityTokens,
|
|
927
|
+
remainingPercent: remainingContextPercent({ usedTokens: last.totalTokens, capacityTokens }),
|
|
928
|
+
},
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function remainingContextPercent({ usedTokens, capacityTokens }: { usedTokens: number, capacityTokens: number }): number {
|
|
933
|
+
if (capacityTokens <= CODEX_CONTEXT_BASELINE_TOKENS)
|
|
934
|
+
return 0
|
|
935
|
+
|
|
936
|
+
const usableTokens = capacityTokens - CODEX_CONTEXT_BASELINE_TOKENS
|
|
937
|
+
const usedUsableTokens = Math.max(usedTokens - CODEX_CONTEXT_BASELINE_TOKENS, 0)
|
|
938
|
+
const remainingTokens = Math.max(usableTokens - usedUsableTokens, 0)
|
|
939
|
+
return Math.round(Math.min(Math.max(100 * remainingTokens / usableTokens, 0), 100))
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function reportContextSnapshot({ threadId, turnId, snapshot, state }: { threadId: string, turnId: string, snapshot: AgentContextSnapshot, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
943
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state })
|
|
944
|
+
|| !state.startedTurnIds.has(turnId)
|
|
945
|
+
|| state.openContextCompactionIds.size > 0
|
|
946
|
+
|| contextSnapshotMatchesBaseline(snapshot, state.contextSnapshotBaseline)) {
|
|
947
|
+
return []
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
state.contextSnapshotBaseline = { status: 'reported', snapshot }
|
|
951
|
+
return emitOrBufferByThread({
|
|
952
|
+
draft: { type: 'context.updated', turnId, payload: { snapshot } },
|
|
953
|
+
threadId,
|
|
954
|
+
state,
|
|
955
|
+
})
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function isCurrentOpenMainTurn({ threadId, turnId, state }: { threadId: string, turnId: string, state: CodexEventMapperState }): boolean {
|
|
959
|
+
return threadId === state.mainThreadId
|
|
960
|
+
&& state.currentMainTurn.status === 'open'
|
|
961
|
+
&& state.currentMainTurn.turnId === turnId
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function contextSnapshotMatchesBaseline(snapshot: AgentContextSnapshot, baseline: CodexContextSnapshotBaseline): boolean {
|
|
965
|
+
if (baseline.status === 'empty')
|
|
966
|
+
return snapshot.status === 'unknown'
|
|
967
|
+
if (snapshot.status !== baseline.snapshot.status)
|
|
968
|
+
return false
|
|
969
|
+
if (snapshot.status === 'unknown' || baseline.snapshot.status === 'unknown')
|
|
970
|
+
return true
|
|
971
|
+
if (snapshot.usedTokens !== baseline.snapshot.usedTokens || snapshot.window.status !== baseline.snapshot.window.status)
|
|
972
|
+
return false
|
|
973
|
+
if (snapshot.window.status === 'unknown' || baseline.snapshot.window.status === 'unknown')
|
|
974
|
+
return true
|
|
975
|
+
return snapshot.window.capacityTokens === baseline.snapshot.window.capacityTokens
|
|
976
|
+
&& snapshot.window.remainingPercent === baseline.snapshot.window.remainingPercent
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function invalidateContextSnapshotBaseline(state: CodexEventMapperState): void {
|
|
980
|
+
state.contextSnapshotBaseline = { status: 'empty' }
|
|
981
|
+
}
|
|
982
|
+
|
|
706
983
|
function usageFromTokenUsage(value: CodexTokenUsage | undefined): AgentUsage | undefined {
|
|
707
984
|
return value ? usageFromCounts(value.last) : undefined
|
|
708
985
|
}
|
|
709
986
|
|
|
987
|
+
// Codex reports `total` for the thread loaded in one app-server, and whether a resumed thread
|
|
988
|
+
// restores it depends on the release: 0.153.4 restarts it at zero, 0.155.0 restores it. Accounting
|
|
989
|
+
// therefore sums each model call's `last` usage, once per distinct `total`, in a scope owned by this
|
|
990
|
+
// run. A resumed run opens a new scope, so a checkpoint never regresses or counts a call twice.
|
|
991
|
+
function codexUsageAccounting(threadId: string, tokenUsage: CodexTokenUsage, state: CodexEventMapperState): AgentUsageAccounting {
|
|
992
|
+
const scopeId = `${threadId}:${state.usageScopeInstanceId}`
|
|
993
|
+
const total = JSON.stringify(tokenUsage.total)
|
|
994
|
+
const previous = state.usageAccountingByThreadId.get(threadId)
|
|
995
|
+
let usage = previous?.usage ?? {}
|
|
996
|
+
if (previous?.total !== total) {
|
|
997
|
+
usage = addUsage(usage, {
|
|
998
|
+
...usageFromCounts(tokenUsage.last),
|
|
999
|
+
inputTokens: Math.max(0, tokenUsage.last.inputTokens - tokenUsage.last.cachedInputTokens),
|
|
1000
|
+
})
|
|
1001
|
+
state.usageAccountingByThreadId.set(threadId, { total, usage })
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
observationId: createHash('sha256').update(scopeId).update(JSON.stringify(usage)).digest('hex'),
|
|
1005
|
+
mode: 'cumulative',
|
|
1006
|
+
scopeId,
|
|
1007
|
+
includesChildren: false,
|
|
1008
|
+
usage,
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function addUsage(accumulated: AgentUsage, call: AgentUsage): AgentUsage {
|
|
1013
|
+
const sum: AgentUsage = { ...accumulated }
|
|
1014
|
+
for (const [key, value] of Object.entries(call) as [keyof AgentUsage, number | undefined][]) {
|
|
1015
|
+
if (value !== undefined)
|
|
1016
|
+
sum[key] = (sum[key] ?? 0) + value
|
|
1017
|
+
}
|
|
1018
|
+
return sum
|
|
1019
|
+
}
|
|
1020
|
+
|
|
710
1021
|
function usageFromCounts(value: CodexTokenCounts): AgentUsage {
|
|
711
1022
|
return removeUndefined({
|
|
712
1023
|
inputTokens: value.inputTokens,
|