@meistrari/agent-core 0.2.0-experimental.1 → 0.2.0-experimental.3
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 +50 -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 +868 -135
- package/src/agents/claude/claude-file-change.ts +128 -0
- package/src/agents/claude/claude-provider.ts +356 -30
- package/src/agents/claude/claude-run.ts +898 -130
- 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 +384 -67
- 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 +423 -53
- 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 +52 -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 +3 -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/supervisor-protocol/agent-event-wrapper.ts +30 -0
|
@@ -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,54 @@ 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
|
-
const status = item.
|
|
400
|
+
const status = item.status === 'declined'
|
|
401
|
+
? 'cancelled'
|
|
402
|
+
: item.status !== 'completed' || (item.exitCode != null && item.exitCode !== 0) ? 'failed' : 'completed'
|
|
326
403
|
const error = 'error' in item && typeof item.error === 'string'
|
|
327
404
|
? { message: item.error }
|
|
328
|
-
: status === 'failed'
|
|
329
|
-
|
|
405
|
+
: status === 'failed'
|
|
406
|
+
? { message: item.exitCode != null && item.exitCode !== 0
|
|
407
|
+
? `Command exited with code ${item.exitCode}.`
|
|
408
|
+
: item.status === 'inProgress' ? 'Command completion status was inconsistent.' : 'Command execution failed.' }
|
|
409
|
+
: undefined
|
|
410
|
+
return { toolName, status, output: commandExecutionResult(item, status), error }
|
|
330
411
|
}
|
|
331
412
|
if (item.type === 'dynamicToolCall')
|
|
332
|
-
return { status: item.success === false ? 'failed' : 'completed', output: dynamicToolResult(item) }
|
|
413
|
+
return { toolName, status: item.success === false ? 'failed' : 'completed', output: dynamicToolResult(item) }
|
|
414
|
+
if (item.type === 'mcpToolCall') {
|
|
415
|
+
if (item.status === 'inProgress') {
|
|
416
|
+
return {
|
|
417
|
+
toolName,
|
|
418
|
+
status: 'failed',
|
|
419
|
+
output: mcpToolResult(item, 'failed'),
|
|
420
|
+
error: { message: 'MCP tool result could not be determined because completion status was inconsistent.' },
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const status = item.status
|
|
425
|
+
return {
|
|
426
|
+
toolName,
|
|
427
|
+
status,
|
|
428
|
+
output: mcpToolResult(item, status),
|
|
429
|
+
error: status === 'failed' ? mcpToolError(item.error?.message) : undefined,
|
|
430
|
+
}
|
|
431
|
+
}
|
|
333
432
|
return undefined
|
|
334
433
|
}
|
|
335
434
|
|
|
@@ -373,9 +472,18 @@ function imageGenerationResult(item: CodexImageGenerationItem): AgentToolResult
|
|
|
373
472
|
const revised = optionalNonEmpty(item.revisedPrompt)
|
|
374
473
|
if (revised)
|
|
375
474
|
lines.push(`Revised prompt: ${revised}`)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
475
|
+
const content: AgentToolContent[] = lines.length > 0 ? [{ type: 'text', text: lines.join('\n') }] : []
|
|
476
|
+
if (item.result !== '')
|
|
477
|
+
content.push({ type: 'image', data: item.result, mimeType: 'image/png' })
|
|
478
|
+
const failure = ownJsonSlot(item, 'failure')
|
|
479
|
+
if (item.result === '' || failure !== undefined || item.transparentBackground !== undefined) {
|
|
480
|
+
content.push(jsonToolContent({
|
|
481
|
+
...(item.result === '' ? { result: '' } : {}),
|
|
482
|
+
...(failure !== undefined ? { failure } : {}),
|
|
483
|
+
...(item.transparentBackground !== undefined ? { transparentBackground: item.transparentBackground } : {}),
|
|
484
|
+
}))
|
|
485
|
+
}
|
|
486
|
+
return { success: item.status !== 'failed', content }
|
|
379
487
|
}
|
|
380
488
|
|
|
381
489
|
function messageEndedText(item: CodexCompletedItem): string | undefined {
|
|
@@ -391,17 +499,17 @@ function messageEndedText(item: CodexCompletedItem): string | undefined {
|
|
|
391
499
|
function userMessageText(item: CodexUserMessageItem): string | undefined {
|
|
392
500
|
const parts: string[] = []
|
|
393
501
|
for (const entry of item.content) {
|
|
394
|
-
if (entry.type === 'text')
|
|
502
|
+
if (entry.type === 'text' && typeof entry.text === 'string')
|
|
395
503
|
parts.push(entry.text)
|
|
396
504
|
}
|
|
397
|
-
return optionalNonEmpty(stripAuthorContext(parts.join('\n')))
|
|
505
|
+
return optionalNonEmpty(stripSessionContext(stripAuthorContext(parts.join('\n'))))
|
|
398
506
|
}
|
|
399
507
|
|
|
400
508
|
function hookPromptText(item: CodexHookPromptItem): string | undefined {
|
|
401
509
|
return optionalNonEmpty(item.fragments.map(fragment => fragment.text).join('\n'))
|
|
402
510
|
}
|
|
403
511
|
|
|
404
|
-
function commandExecutionResult(item: Extract<CodexCompletedItem, { type: 'commandExecution' }>, status:
|
|
512
|
+
function commandExecutionResult(item: Extract<CodexCompletedItem, { type: 'commandExecution' }>, status: AgentToolCallStatus): AgentToolResult | undefined {
|
|
405
513
|
// Stdout is content, not a label: whitespace and an explicitly empty
|
|
406
514
|
// successful output must survive normalization into the complete step.
|
|
407
515
|
// Native build_command_execution_end_item encodes empty captured output as
|
|
@@ -414,6 +522,33 @@ function commandExecutionResult(item: Extract<CodexCompletedItem, { type: 'comma
|
|
|
414
522
|
return { success: status === 'completed', content: [{ type: 'text', text }] }
|
|
415
523
|
}
|
|
416
524
|
|
|
525
|
+
function mcpToolResult(item: CodexMcpToolCompletedItem, status: 'completed' | 'failed'): AgentToolResult | undefined {
|
|
526
|
+
if (item.result === null || item.result === undefined)
|
|
527
|
+
return undefined
|
|
528
|
+
|
|
529
|
+
if (typeof item.result !== 'object' || Array.isArray(item.result)) {
|
|
530
|
+
const parsed = jsonValueSchema.safeParse(item.result)
|
|
531
|
+
return parsed.success
|
|
532
|
+
? { success: status === 'completed', content: [jsonToolContent(parsed.data)] }
|
|
533
|
+
: undefined
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const content: AgentToolContent[] = []
|
|
537
|
+
const resultContent = ownJsonSlot(item.result, 'content')
|
|
538
|
+
if (resultContent !== undefined) {
|
|
539
|
+
if (Array.isArray(resultContent))
|
|
540
|
+
content.push(...resultContent.map(mcpToolContent))
|
|
541
|
+
else
|
|
542
|
+
content.push(mcpToolContent(resultContent))
|
|
543
|
+
}
|
|
544
|
+
const structuredContent = ownJsonSlot(item.result, 'structuredContent')
|
|
545
|
+
return removeUndefined({ success: status === 'completed', content, structuredContent })
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function mcpToolError(value: unknown): AgentError {
|
|
549
|
+
return { message: typeof value === 'string' ? value : 'MCP tool call failed.' }
|
|
550
|
+
}
|
|
551
|
+
|
|
417
552
|
function dynamicToolResult(item: CodexDynamicToolCompletedItem): AgentToolResult | undefined {
|
|
418
553
|
if (!item.contentItems)
|
|
419
554
|
return undefined
|
|
@@ -428,14 +563,18 @@ function dynamicToolContent(item: CodexDynamicToolContentItem): AgentToolContent
|
|
|
428
563
|
if (item.type === 'inputText')
|
|
429
564
|
return { type: 'text', text: item.text }
|
|
430
565
|
|
|
431
|
-
if (item.type === 'inputAudio')
|
|
432
|
-
|
|
566
|
+
if (item.type === 'inputAudio') {
|
|
567
|
+
const dataUrl = /^data:([^;,]+);base64,(.+)$/s.exec(item.audioUrl)
|
|
568
|
+
return dataUrl
|
|
569
|
+
? { type: 'audio', mimeType: dataUrl[1]!, data: dataUrl[2]! }
|
|
570
|
+
: jsonToolContent({ type: 'audio', uri: item.audioUrl })
|
|
571
|
+
}
|
|
433
572
|
|
|
434
573
|
const dataUrl = /^data:([^;,]+);base64,(.*)$/s.exec(item.imageUrl)
|
|
435
574
|
if (dataUrl)
|
|
436
575
|
return { type: 'image', mimeType: dataUrl[1] ?? 'application/octet-stream', data: dataUrl[2] ?? '' }
|
|
437
576
|
|
|
438
|
-
return { type: '
|
|
577
|
+
return { type: 'image', uri: item.imageUrl }
|
|
439
578
|
}
|
|
440
579
|
|
|
441
580
|
function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: { turnId: string, threadId: string, item: CodexCollabAgentToolCompletedItem, mapperState: CodexEventMapperState }): AgentEventDraft[] {
|
|
@@ -445,6 +584,7 @@ function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: {
|
|
|
445
584
|
turnId,
|
|
446
585
|
payload: removeUndefined({
|
|
447
586
|
toolCallId: item.id,
|
|
587
|
+
toolName: item.tool,
|
|
448
588
|
status: item.status === 'failed' ? 'failed' as const : 'completed' as const,
|
|
449
589
|
error: item.status === 'failed' ? { message: `Codex ${item.tool} failed.` } : undefined,
|
|
450
590
|
}),
|
|
@@ -454,23 +594,33 @@ function mapCollabAgentToolCompleted({ turnId, threadId, item, mapperState }: {
|
|
|
454
594
|
})
|
|
455
595
|
|
|
456
596
|
if (item.tool === 'spawnAgent') {
|
|
457
|
-
|
|
458
|
-
if (!subagentId || item.status === 'failed' || mapperState.subagentByThreadId.has(subagentId))
|
|
597
|
+
if (item.status === 'failed')
|
|
459
598
|
return drafts
|
|
460
599
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
|
|
600
|
+
for (const subagentId of new Set(item.receiverThreadIds)) {
|
|
601
|
+
if (!subagentId || mapperState.subagentByThreadId.has(subagentId))
|
|
602
|
+
continue
|
|
603
|
+
|
|
604
|
+
const actor = subagentActorFromCollabItem({
|
|
605
|
+
subagentId,
|
|
606
|
+
parentThreadId: item.senderThreadId,
|
|
607
|
+
parentToolCallId: item.id,
|
|
608
|
+
name: mapperState.subagentNameByThreadId.get(subagentId),
|
|
609
|
+
state: mapperState,
|
|
610
|
+
})
|
|
611
|
+
mapperState.subagentByThreadId.set(subagentId, actor)
|
|
612
|
+
mapperState.subagentById.set(subagentId, actor)
|
|
613
|
+
drafts.push({
|
|
614
|
+
type: 'subagent.started',
|
|
615
|
+
turnId,
|
|
616
|
+
actor,
|
|
617
|
+
payload: removeUndefined({
|
|
618
|
+
...subagentPayloadRef(actor),
|
|
619
|
+
description: optionalNonEmpty(item.prompt),
|
|
620
|
+
}),
|
|
621
|
+
})
|
|
622
|
+
drafts.push(...flushPendingDrafts(subagentId, mapperState))
|
|
623
|
+
}
|
|
474
624
|
return drafts
|
|
475
625
|
}
|
|
476
626
|
|
|
@@ -507,14 +657,16 @@ function mapSubAgentActivityCompleted(input: { turnId: string, threadId: string,
|
|
|
507
657
|
|
|
508
658
|
const actor = subagentActorFromCollabItem({
|
|
509
659
|
subagentId: item.agentThreadId,
|
|
660
|
+
parentThreadId: threadId,
|
|
510
661
|
parentToolCallId: item.id,
|
|
511
662
|
name: item.agentPath,
|
|
663
|
+
state,
|
|
512
664
|
})
|
|
513
665
|
state.subagentByThreadId.set(item.agentThreadId, actor)
|
|
514
666
|
state.subagentById.set(item.agentThreadId, actor)
|
|
515
667
|
return [
|
|
516
668
|
...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 }),
|
|
669
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, toolName: 'spawnAgent', status: 'completed' } }, threadId, state }),
|
|
518
670
|
{
|
|
519
671
|
type: 'subagent.started',
|
|
520
672
|
turnId,
|
|
@@ -528,7 +680,7 @@ function mapSubAgentActivityCompleted(input: { turnId: string, threadId: string,
|
|
|
528
680
|
if (item.kind === 'interacted') {
|
|
529
681
|
return [
|
|
530
682
|
...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 }),
|
|
683
|
+
...emitOrBufferByThread({ draft: { type: 'tool.call.completed', turnId, payload: { toolCallId: item.id, toolName: 'sendInput', status: 'completed' } }, threadId, state }),
|
|
532
684
|
]
|
|
533
685
|
}
|
|
534
686
|
|
|
@@ -624,7 +776,7 @@ function actorForThread(threadId: string, state: CodexEventMapperState): AgentEv
|
|
|
624
776
|
const subagentActor = state.subagentByThreadId.get(threadId)
|
|
625
777
|
if (subagentActor)
|
|
626
778
|
return subagentActor
|
|
627
|
-
return
|
|
779
|
+
return threadId === state.mainThreadId ? mainActor : undefined
|
|
628
780
|
}
|
|
629
781
|
|
|
630
782
|
function flushPendingDrafts(threadId: string, state: CodexEventMapperState): AgentEventDraft[] {
|
|
@@ -670,12 +822,18 @@ function codexSubagentDisplayName(thread: CodexThreadMetadata): string | undefin
|
|
|
670
822
|
return nickname ?? role
|
|
671
823
|
}
|
|
672
824
|
|
|
673
|
-
function subagentActorFromCollabItem(input: {
|
|
825
|
+
function subagentActorFromCollabItem(input: {
|
|
826
|
+
subagentId: string
|
|
827
|
+
parentThreadId: string
|
|
828
|
+
parentToolCallId: string
|
|
829
|
+
name?: string
|
|
830
|
+
state: CodexEventMapperState
|
|
831
|
+
}): CodexSubagentRef {
|
|
674
832
|
return removeUndefined({
|
|
675
833
|
type: 'subagent' as const,
|
|
676
834
|
actorId: input.subagentId,
|
|
677
835
|
subagentId: input.subagentId,
|
|
678
|
-
parentActorId: 'main',
|
|
836
|
+
parentActorId: input.parentThreadId === input.state.mainThreadId ? 'main' : input.parentThreadId,
|
|
679
837
|
origin: { type: 'tool_call' as const, toolCallId: input.parentToolCallId },
|
|
680
838
|
parentToolCallId: input.parentToolCallId,
|
|
681
839
|
name: input.name,
|
|
@@ -703,10 +861,169 @@ function collabToolInput(item: CodexCollabAgentToolStartedItem): JsonObject {
|
|
|
703
861
|
})
|
|
704
862
|
}
|
|
705
863
|
|
|
864
|
+
function observeMainTurnStarted({ threadId, turnId, state }: { threadId: string, turnId: string, state: CodexEventMapperState }): void {
|
|
865
|
+
if (threadId !== state.mainThreadId || state.seenMainTurnIds.has(turnId))
|
|
866
|
+
return
|
|
867
|
+
|
|
868
|
+
state.seenMainTurnIds.add(turnId)
|
|
869
|
+
state.currentMainTurn = { status: 'open', turnId }
|
|
870
|
+
state.openContextCompactionIds.clear()
|
|
871
|
+
state.closedContextCompactionIds.clear()
|
|
872
|
+
invalidateContextSnapshotBaseline(state)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function observeMainTurnCompleted({ threadId, turnId, state }: { threadId: string, turnId: string, state: CodexEventMapperState }): void {
|
|
876
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state }))
|
|
877
|
+
return
|
|
878
|
+
|
|
879
|
+
state.currentMainTurn = { status: 'closed' }
|
|
880
|
+
state.openContextCompactionIds.clear()
|
|
881
|
+
state.closedContextCompactionIds.clear()
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function observeContextCompactionStarted({ threadId, turnId, compactionId, state }: { threadId: string, turnId: string, compactionId: string, state: CodexEventMapperState }): void {
|
|
885
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state })
|
|
886
|
+
|| state.openContextCompactionIds.has(compactionId)
|
|
887
|
+
|| state.closedContextCompactionIds.has(compactionId)) {
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
state.openContextCompactionIds.add(compactionId)
|
|
892
|
+
invalidateContextSnapshotBaseline(state)
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function observeContextCompactionCompleted({ threadId, turnId, compactionId, state }: { threadId: string, turnId: string, compactionId: string, state: CodexEventMapperState }): void {
|
|
896
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state }) || state.closedContextCompactionIds.has(compactionId))
|
|
897
|
+
return
|
|
898
|
+
|
|
899
|
+
state.openContextCompactionIds.delete(compactionId)
|
|
900
|
+
state.closedContextCompactionIds.add(compactionId)
|
|
901
|
+
invalidateContextSnapshotBaseline(state)
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function mapContextSnapshotFromTokenUsage({ threadId, turnId, tokenUsage, state }: { threadId: string, turnId: string, tokenUsage: CodexTokenUsage, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
905
|
+
if (state.openContextCompactionIds.size > 0)
|
|
906
|
+
return []
|
|
907
|
+
|
|
908
|
+
return reportContextSnapshot({
|
|
909
|
+
threadId,
|
|
910
|
+
turnId,
|
|
911
|
+
snapshot: contextSnapshotFromTokenUsage(tokenUsage),
|
|
912
|
+
state,
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function contextSnapshotFromTokenUsage(tokenUsage: CodexTokenUsage): AgentContextSnapshot {
|
|
917
|
+
const { last } = tokenUsage
|
|
918
|
+
if (!Number.isSafeInteger(last.totalTokens) || last.totalTokens < 0
|
|
919
|
+
|| (last.inputTokens === 0 && last.outputTokens === 0)) {
|
|
920
|
+
return { status: 'unknown' }
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const capacityTokens = tokenUsage.modelContextWindow
|
|
924
|
+
if (!Number.isSafeInteger(capacityTokens) || capacityTokens === null || capacityTokens === undefined || capacityTokens <= 0)
|
|
925
|
+
return { status: 'observed', usedTokens: last.totalTokens, window: { status: 'unknown' } }
|
|
926
|
+
|
|
927
|
+
return {
|
|
928
|
+
status: 'observed',
|
|
929
|
+
usedTokens: last.totalTokens,
|
|
930
|
+
window: {
|
|
931
|
+
status: 'known',
|
|
932
|
+
capacityTokens,
|
|
933
|
+
remainingPercent: remainingContextPercent({ usedTokens: last.totalTokens, capacityTokens }),
|
|
934
|
+
},
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function remainingContextPercent({ usedTokens, capacityTokens }: { usedTokens: number, capacityTokens: number }): number {
|
|
939
|
+
if (capacityTokens <= CODEX_CONTEXT_BASELINE_TOKENS)
|
|
940
|
+
return 0
|
|
941
|
+
|
|
942
|
+
const usableTokens = capacityTokens - CODEX_CONTEXT_BASELINE_TOKENS
|
|
943
|
+
const usedUsableTokens = Math.max(usedTokens - CODEX_CONTEXT_BASELINE_TOKENS, 0)
|
|
944
|
+
const remainingTokens = Math.max(usableTokens - usedUsableTokens, 0)
|
|
945
|
+
return Math.round(Math.min(Math.max(100 * remainingTokens / usableTokens, 0), 100))
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function reportContextSnapshot({ threadId, turnId, snapshot, state }: { threadId: string, turnId: string, snapshot: AgentContextSnapshot, state: CodexEventMapperState }): AgentEventDraft[] {
|
|
949
|
+
if (!isCurrentOpenMainTurn({ threadId, turnId, state })
|
|
950
|
+
|| !state.startedTurnIds.has(turnId)
|
|
951
|
+
|| state.openContextCompactionIds.size > 0
|
|
952
|
+
|| contextSnapshotMatchesBaseline(snapshot, state.contextSnapshotBaseline)) {
|
|
953
|
+
return []
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
state.contextSnapshotBaseline = { status: 'reported', snapshot }
|
|
957
|
+
return emitOrBufferByThread({
|
|
958
|
+
draft: { type: 'context.updated', turnId, payload: { snapshot } },
|
|
959
|
+
threadId,
|
|
960
|
+
state,
|
|
961
|
+
})
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function isCurrentOpenMainTurn({ threadId, turnId, state }: { threadId: string, turnId: string, state: CodexEventMapperState }): boolean {
|
|
965
|
+
return threadId === state.mainThreadId
|
|
966
|
+
&& state.currentMainTurn.status === 'open'
|
|
967
|
+
&& state.currentMainTurn.turnId === turnId
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function contextSnapshotMatchesBaseline(snapshot: AgentContextSnapshot, baseline: CodexContextSnapshotBaseline): boolean {
|
|
971
|
+
if (baseline.status === 'empty')
|
|
972
|
+
return snapshot.status === 'unknown'
|
|
973
|
+
if (snapshot.status !== baseline.snapshot.status)
|
|
974
|
+
return false
|
|
975
|
+
if (snapshot.status === 'unknown' || baseline.snapshot.status === 'unknown')
|
|
976
|
+
return true
|
|
977
|
+
if (snapshot.usedTokens !== baseline.snapshot.usedTokens || snapshot.window.status !== baseline.snapshot.window.status)
|
|
978
|
+
return false
|
|
979
|
+
if (snapshot.window.status === 'unknown' || baseline.snapshot.window.status === 'unknown')
|
|
980
|
+
return true
|
|
981
|
+
return snapshot.window.capacityTokens === baseline.snapshot.window.capacityTokens
|
|
982
|
+
&& snapshot.window.remainingPercent === baseline.snapshot.window.remainingPercent
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function invalidateContextSnapshotBaseline(state: CodexEventMapperState): void {
|
|
986
|
+
state.contextSnapshotBaseline = { status: 'empty' }
|
|
987
|
+
}
|
|
988
|
+
|
|
706
989
|
function usageFromTokenUsage(value: CodexTokenUsage | undefined): AgentUsage | undefined {
|
|
707
990
|
return value ? usageFromCounts(value.last) : undefined
|
|
708
991
|
}
|
|
709
992
|
|
|
993
|
+
// Codex reports `total` for the thread loaded in one app-server, and whether a resumed thread
|
|
994
|
+
// restores it depends on the release: 0.153.4 restarts it at zero, 0.155.0 restores it. Accounting
|
|
995
|
+
// therefore sums each model call's `last` usage, once per distinct `total`, in a scope owned by this
|
|
996
|
+
// run. A resumed run opens a new scope, so a checkpoint never regresses or counts a call twice.
|
|
997
|
+
function codexUsageAccounting(threadId: string, tokenUsage: CodexTokenUsage, state: CodexEventMapperState): AgentUsageAccounting {
|
|
998
|
+
const scopeId = `${threadId}:${state.usageScopeInstanceId}`
|
|
999
|
+
const total = JSON.stringify(tokenUsage.total)
|
|
1000
|
+
const previous = state.usageAccountingByThreadId.get(threadId)
|
|
1001
|
+
let usage = previous?.usage ?? {}
|
|
1002
|
+
if (previous?.total !== total) {
|
|
1003
|
+
usage = addUsage(usage, {
|
|
1004
|
+
...usageFromCounts(tokenUsage.last),
|
|
1005
|
+
inputTokens: Math.max(0, tokenUsage.last.inputTokens - tokenUsage.last.cachedInputTokens),
|
|
1006
|
+
})
|
|
1007
|
+
state.usageAccountingByThreadId.set(threadId, { total, usage })
|
|
1008
|
+
}
|
|
1009
|
+
return {
|
|
1010
|
+
observationId: createHash('sha256').update(scopeId).update(JSON.stringify(usage)).digest('hex'),
|
|
1011
|
+
mode: 'cumulative',
|
|
1012
|
+
scopeId,
|
|
1013
|
+
includesChildren: false,
|
|
1014
|
+
usage,
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function addUsage(accumulated: AgentUsage, call: AgentUsage): AgentUsage {
|
|
1019
|
+
const sum: AgentUsage = { ...accumulated }
|
|
1020
|
+
for (const [key, value] of Object.entries(call) as [keyof AgentUsage, number | undefined][]) {
|
|
1021
|
+
if (value !== undefined)
|
|
1022
|
+
sum[key] = (sum[key] ?? 0) + value
|
|
1023
|
+
}
|
|
1024
|
+
return sum
|
|
1025
|
+
}
|
|
1026
|
+
|
|
710
1027
|
function usageFromCounts(value: CodexTokenCounts): AgentUsage {
|
|
711
1028
|
return removeUndefined({
|
|
712
1029
|
inputTokens: value.inputTokens,
|