@ai-sdk/harness-codex 1.0.126 → 1.0.128
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/CHANGELOG.md +20 -0
- package/README.md +12 -13
- package/dist/bridge/index.mjs +1248 -484
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/bridge/package.json +1 -1
- package/dist/bridge/pnpm-lock.yaml +1 -9
- package/dist/index.d.ts +1399 -0
- package/dist/index.js +46 -73
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/bridge/codex-app-server-client.ts +320 -0
- package/src/bridge/codex-app-server-driver.ts +617 -0
- package/src/bridge/codex-step-tracker.ts +2 -0
- package/src/bridge/codex-tool-filtering-hook.ts +148 -0
- package/src/bridge/codex-tool-filtering.ts +42 -0
- package/src/bridge/create-app-server-event-handler.ts +531 -0
- package/src/bridge/create-emit-stream-event.ts +27 -2
- package/src/bridge/index.ts +64 -193
- package/src/bridge/package.json +1 -1
- package/src/bridge/pnpm-lock.yaml +1 -9
- package/src/codex-bridge-protocol.ts +4 -4
- package/src/codex-harness.ts +56 -117
- package/src/bridge/cli-relay.ts +0 -268
- package/src/bridge/tool-relay-auth.ts +0 -156
- package/src/bridge/tool-relay.ts +0 -119
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import type { BridgeTurn } from '@ai-sdk/harness/bridge';
|
|
2
|
+
import type { CodexStepTracker } from './codex-step-tracker';
|
|
3
|
+
import type { CodexEvent, CodexItem } from './create-emit-stream-event';
|
|
4
|
+
import type { CodexAppServerNotification } from './codex-app-server-client';
|
|
5
|
+
|
|
6
|
+
type UsageBreakdown = {
|
|
7
|
+
inputTokens: number;
|
|
8
|
+
cachedInputTokens: number;
|
|
9
|
+
cacheWriteInputTokens: number;
|
|
10
|
+
outputTokens: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AppServerTurnResult = {
|
|
14
|
+
status: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type NativeToolName = 'apply_patch' | 'view_image';
|
|
19
|
+
|
|
20
|
+
type NativeToolCall = {
|
|
21
|
+
name: NativeToolName;
|
|
22
|
+
resultEmitted: boolean;
|
|
23
|
+
successConfirmed: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function createAppServerEventHandler({
|
|
27
|
+
stepTracker,
|
|
28
|
+
emitStreamEvent,
|
|
29
|
+
emitWarning,
|
|
30
|
+
emitError,
|
|
31
|
+
}: {
|
|
32
|
+
stepTracker: CodexStepTracker;
|
|
33
|
+
emitStreamEvent: (event: CodexEvent) => void;
|
|
34
|
+
emitWarning: BridgeTurn['emitWarning'];
|
|
35
|
+
emitError: BridgeTurn['emitError'];
|
|
36
|
+
}): {
|
|
37
|
+
announceThread(threadId: string): void;
|
|
38
|
+
handle(notification: CodexAppServerNotification): void;
|
|
39
|
+
setTurnId(turnId: string): void;
|
|
40
|
+
waitForCompletion(): Promise<AppServerTurnResult>;
|
|
41
|
+
} {
|
|
42
|
+
let activeThreadId: string | undefined;
|
|
43
|
+
let activeTurnId: string | undefined;
|
|
44
|
+
let settled = false;
|
|
45
|
+
let accumulatedUsage: UsageBreakdown = emptyUsageBreakdown();
|
|
46
|
+
let lastCumulativeUsageKey: string | undefined;
|
|
47
|
+
const textByItem = new Map<string, string>();
|
|
48
|
+
const reasoningByItem = new Map<string, string>();
|
|
49
|
+
const nativeToolCalls = new Map<string, NativeToolCall>();
|
|
50
|
+
let resolveCompletion: (result: AppServerTurnResult) => void = () => {};
|
|
51
|
+
const completion = new Promise<AppServerTurnResult>(resolve => {
|
|
52
|
+
resolveCompletion = resolve;
|
|
53
|
+
});
|
|
54
|
+
const announceThread = (threadId: string): void => {
|
|
55
|
+
if (activeThreadId != null) return;
|
|
56
|
+
activeThreadId = threadId;
|
|
57
|
+
emitStreamEvent({ type: 'thread.started', thread_id: threadId });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleItem = ({
|
|
61
|
+
eventType,
|
|
62
|
+
params,
|
|
63
|
+
}: {
|
|
64
|
+
eventType: 'item.started' | 'item.completed';
|
|
65
|
+
params: Record<string, unknown>;
|
|
66
|
+
}): void => {
|
|
67
|
+
if (!matchesActiveTurn({ params, activeThreadId, activeTurnId })) return;
|
|
68
|
+
const item = asRecord(params.item);
|
|
69
|
+
if (item == null || typeof item.type !== 'string') return;
|
|
70
|
+
if (eventType === 'item.completed' && typeof item.id === 'string') {
|
|
71
|
+
const call = nativeToolCalls.get(item.id);
|
|
72
|
+
if (call?.name === 'apply_patch' && item.type === 'fileChange') {
|
|
73
|
+
call.successConfirmed = item.status === 'completed';
|
|
74
|
+
} else if (call?.name === 'view_image' && item.type === 'imageView') {
|
|
75
|
+
call.successConfirmed = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const normalized = normalizeItem({ item, textByItem, reasoningByItem });
|
|
79
|
+
if (normalized == null) return;
|
|
80
|
+
if (normalized.type === 'dynamic_tool_call') {
|
|
81
|
+
stepTracker.observeEvent({
|
|
82
|
+
event: { type: eventType, item: normalized },
|
|
83
|
+
itemId: normalized.id,
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
emitStreamEvent({ type: eventType, item: normalized });
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const emitNativeToolResult = ({
|
|
91
|
+
callId,
|
|
92
|
+
result,
|
|
93
|
+
}: {
|
|
94
|
+
callId: string;
|
|
95
|
+
result: unknown;
|
|
96
|
+
}): void => {
|
|
97
|
+
const call = nativeToolCalls.get(callId);
|
|
98
|
+
if (call == null || call.resultEmitted) return;
|
|
99
|
+
call.resultEmitted = true;
|
|
100
|
+
emitStreamEvent({
|
|
101
|
+
type: 'item.completed',
|
|
102
|
+
item: {
|
|
103
|
+
type: 'native_tool',
|
|
104
|
+
id: callId,
|
|
105
|
+
tool: call.name,
|
|
106
|
+
result,
|
|
107
|
+
...(!call.successConfirmed ? { isError: true } : {}),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleRawItem = (params: Record<string, unknown>): void => {
|
|
113
|
+
if (!matchesActiveTurn({ params, activeThreadId, activeTurnId })) return;
|
|
114
|
+
const item = asRecord(params.item);
|
|
115
|
+
if (item == null) return;
|
|
116
|
+
const toolCall = normalizeNativeToolCall({ item });
|
|
117
|
+
if (toolCall != null) {
|
|
118
|
+
if (nativeToolCalls.has(toolCall.callId)) return;
|
|
119
|
+
nativeToolCalls.set(toolCall.callId, {
|
|
120
|
+
name: toolCall.name,
|
|
121
|
+
resultEmitted: false,
|
|
122
|
+
successConfirmed: false,
|
|
123
|
+
});
|
|
124
|
+
emitStreamEvent({
|
|
125
|
+
type: 'item.started',
|
|
126
|
+
item: {
|
|
127
|
+
type: 'native_tool',
|
|
128
|
+
id: toolCall.callId,
|
|
129
|
+
tool: toolCall.name,
|
|
130
|
+
input: toolCall.input,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (typeof item.call_id !== 'string') return;
|
|
136
|
+
const call = nativeToolCalls.get(item.call_id);
|
|
137
|
+
if (
|
|
138
|
+
call == null ||
|
|
139
|
+
(call.name === 'apply_patch'
|
|
140
|
+
? item.type !== 'custom_tool_call_output'
|
|
141
|
+
: item.type !== 'function_call_output') ||
|
|
142
|
+
!Object.prototype.hasOwnProperty.call(item, 'output')
|
|
143
|
+
) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
emitNativeToolResult({
|
|
147
|
+
callId: item.call_id,
|
|
148
|
+
result:
|
|
149
|
+
call.name === 'view_image' &&
|
|
150
|
+
call.successConfirmed &&
|
|
151
|
+
Array.isArray(item.output) &&
|
|
152
|
+
item.output.length === 0
|
|
153
|
+
? 'Image viewed.'
|
|
154
|
+
: item.output,
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
announceThread,
|
|
160
|
+
setTurnId(turnId) {
|
|
161
|
+
activeTurnId = turnId;
|
|
162
|
+
},
|
|
163
|
+
handle(notification) {
|
|
164
|
+
const params = asRecord(notification.params);
|
|
165
|
+
if (notification.method === 'thread/started') {
|
|
166
|
+
const thread = asRecord(params?.thread);
|
|
167
|
+
if (typeof thread?.id === 'string') announceThread(thread.id);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (notification.method === 'turn/started') {
|
|
171
|
+
const turn = asRecord(params?.turn);
|
|
172
|
+
if (
|
|
173
|
+
params?.threadId === activeThreadId &&
|
|
174
|
+
typeof turn?.id === 'string'
|
|
175
|
+
) {
|
|
176
|
+
activeTurnId = turn.id;
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (notification.method === 'item/started' && params != null) {
|
|
181
|
+
handleItem({ eventType: 'item.started', params });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (notification.method === 'item/completed' && params != null) {
|
|
185
|
+
handleItem({ eventType: 'item.completed', params });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
notification.method === 'rawResponseItem/completed' &&
|
|
190
|
+
params != null
|
|
191
|
+
) {
|
|
192
|
+
handleRawItem(params);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (
|
|
196
|
+
notification.method === 'item/agentMessage/delta' &&
|
|
197
|
+
params != null &&
|
|
198
|
+
matchesActiveTurn({ params, activeThreadId, activeTurnId }) &&
|
|
199
|
+
typeof params.itemId === 'string' &&
|
|
200
|
+
typeof params.delta === 'string'
|
|
201
|
+
) {
|
|
202
|
+
const text = (textByItem.get(params.itemId) ?? '') + params.delta;
|
|
203
|
+
textByItem.set(params.itemId, text);
|
|
204
|
+
emitStreamEvent({
|
|
205
|
+
type: 'item.updated',
|
|
206
|
+
item: { type: 'agent_message', id: params.itemId, text },
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (
|
|
211
|
+
notification.method === 'item/reasoning/summaryTextDelta' &&
|
|
212
|
+
params != null &&
|
|
213
|
+
matchesActiveTurn({ params, activeThreadId, activeTurnId }) &&
|
|
214
|
+
typeof params.itemId === 'string' &&
|
|
215
|
+
typeof params.delta === 'string'
|
|
216
|
+
) {
|
|
217
|
+
const text = (reasoningByItem.get(params.itemId) ?? '') + params.delta;
|
|
218
|
+
reasoningByItem.set(params.itemId, text);
|
|
219
|
+
emitStreamEvent({
|
|
220
|
+
type: 'item.updated',
|
|
221
|
+
item: { type: 'reasoning', id: params.itemId, text },
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (
|
|
226
|
+
notification.method === 'thread/tokenUsage/updated' &&
|
|
227
|
+
params != null &&
|
|
228
|
+
matchesActiveTurn({ params, activeThreadId, activeTurnId })
|
|
229
|
+
) {
|
|
230
|
+
const tokenUsage = asRecord(params.tokenUsage);
|
|
231
|
+
const total = readUsageBreakdown(tokenUsage?.total);
|
|
232
|
+
const last = readUsageBreakdown(tokenUsage?.last);
|
|
233
|
+
if (total != null && last != null) {
|
|
234
|
+
const key = usageKey(total);
|
|
235
|
+
if (key !== lastCumulativeUsageKey) {
|
|
236
|
+
lastCumulativeUsageKey = key;
|
|
237
|
+
accumulatedUsage = addUsage({
|
|
238
|
+
total: accumulatedUsage,
|
|
239
|
+
increment: last,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (notification.method === 'error' && params != null) {
|
|
246
|
+
if (!matchesActiveTurn({ params, activeThreadId, activeTurnId }))
|
|
247
|
+
return;
|
|
248
|
+
const error = asRecord(params.error);
|
|
249
|
+
const message =
|
|
250
|
+
typeof error?.message === 'string'
|
|
251
|
+
? error.message
|
|
252
|
+
: 'Codex app-server reported an error.';
|
|
253
|
+
if (params.willRetry === true) {
|
|
254
|
+
emitWarning({ message });
|
|
255
|
+
} else {
|
|
256
|
+
emitError({ error: message, message: 'codex turn failed' });
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
(notification.method === 'warning' ||
|
|
262
|
+
notification.method === 'configWarning' ||
|
|
263
|
+
notification.method === 'deprecationNotice') &&
|
|
264
|
+
typeof params?.message === 'string'
|
|
265
|
+
) {
|
|
266
|
+
emitWarning({ message: params.message });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (
|
|
270
|
+
notification.method === 'turn/completed' &&
|
|
271
|
+
params != null &&
|
|
272
|
+
matchesActiveTurn({ params, activeThreadId, activeTurnId })
|
|
273
|
+
) {
|
|
274
|
+
if (settled) return;
|
|
275
|
+
settled = true;
|
|
276
|
+
const turn = asRecord(params.turn);
|
|
277
|
+
const status =
|
|
278
|
+
typeof turn?.status === 'string' ? turn.status : 'failed';
|
|
279
|
+
const turnError = asRecord(turn?.error);
|
|
280
|
+
if (status === 'completed') {
|
|
281
|
+
for (const [callId, call] of nativeToolCalls) {
|
|
282
|
+
if (call.resultEmitted) continue;
|
|
283
|
+
emitNativeToolResult({
|
|
284
|
+
callId,
|
|
285
|
+
result: call.successConfirmed
|
|
286
|
+
? call.name === 'apply_patch'
|
|
287
|
+
? 'Patch applied.'
|
|
288
|
+
: 'Image viewed.'
|
|
289
|
+
: 'Codex did not report the tool result.',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
emitStreamEvent({
|
|
294
|
+
type: 'turn.completed',
|
|
295
|
+
usage: toLegacyUsage(accumulatedUsage),
|
|
296
|
+
});
|
|
297
|
+
resolveCompletion({
|
|
298
|
+
status,
|
|
299
|
+
...(typeof turnError?.message === 'string'
|
|
300
|
+
? { error: turnError.message }
|
|
301
|
+
: {}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
waitForCompletion: () => completion,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function normalizeNativeToolCall({
|
|
310
|
+
item,
|
|
311
|
+
}: {
|
|
312
|
+
item: Record<string, unknown>;
|
|
313
|
+
}): { callId: string; name: NativeToolName; input: string } | undefined {
|
|
314
|
+
if (
|
|
315
|
+
item.namespace != null ||
|
|
316
|
+
typeof item.call_id !== 'string' ||
|
|
317
|
+
item.call_id.length === 0
|
|
318
|
+
) {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
if (
|
|
322
|
+
item.type === 'custom_tool_call' &&
|
|
323
|
+
item.name === 'apply_patch' &&
|
|
324
|
+
typeof item.input === 'string'
|
|
325
|
+
) {
|
|
326
|
+
return {
|
|
327
|
+
callId: item.call_id,
|
|
328
|
+
name: 'apply_patch',
|
|
329
|
+
input: JSON.stringify(item.input),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (
|
|
333
|
+
item.type === 'function_call' &&
|
|
334
|
+
item.name === 'view_image' &&
|
|
335
|
+
typeof item.arguments === 'string'
|
|
336
|
+
) {
|
|
337
|
+
return {
|
|
338
|
+
callId: item.call_id,
|
|
339
|
+
name: 'view_image',
|
|
340
|
+
input: item.arguments,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function normalizeItem({
|
|
347
|
+
item,
|
|
348
|
+
textByItem,
|
|
349
|
+
reasoningByItem,
|
|
350
|
+
}: {
|
|
351
|
+
item: Record<string, unknown>;
|
|
352
|
+
textByItem: Map<string, string>;
|
|
353
|
+
reasoningByItem: Map<string, string>;
|
|
354
|
+
}): CodexItem | undefined {
|
|
355
|
+
const id = typeof item.id === 'string' ? item.id : undefined;
|
|
356
|
+
if (item.type === 'agentMessage') {
|
|
357
|
+
const text = typeof item.text === 'string' ? item.text : '';
|
|
358
|
+
if (id != null) textByItem.set(id, text);
|
|
359
|
+
return { type: 'agent_message', id, text };
|
|
360
|
+
}
|
|
361
|
+
if (item.type === 'reasoning') {
|
|
362
|
+
const summary = stringArray(item.summary).join('\n\n');
|
|
363
|
+
const content = stringArray(item.content).join('\n\n');
|
|
364
|
+
const text = summary || reasoningByItem.get(id ?? '') || content;
|
|
365
|
+
if (id != null) reasoningByItem.set(id, text);
|
|
366
|
+
return { type: 'reasoning', id, text };
|
|
367
|
+
}
|
|
368
|
+
if (item.type === 'commandExecution') {
|
|
369
|
+
return {
|
|
370
|
+
type: 'command_execution',
|
|
371
|
+
id,
|
|
372
|
+
command: typeof item.command === 'string' ? item.command : '',
|
|
373
|
+
exit_code: typeof item.exitCode === 'number' ? item.exitCode : undefined,
|
|
374
|
+
aggregated_output:
|
|
375
|
+
typeof item.aggregatedOutput === 'string'
|
|
376
|
+
? item.aggregatedOutput
|
|
377
|
+
: undefined,
|
|
378
|
+
status: normalizeCommandStatus(item.status),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (item.type === 'mcpToolCall') {
|
|
382
|
+
const result = asRecord(item.result);
|
|
383
|
+
return {
|
|
384
|
+
type: 'mcp_tool_call',
|
|
385
|
+
id,
|
|
386
|
+
server: typeof item.server === 'string' ? item.server : undefined,
|
|
387
|
+
tool: typeof item.tool === 'string' ? item.tool : undefined,
|
|
388
|
+
arguments: item.arguments,
|
|
389
|
+
result:
|
|
390
|
+
result == null
|
|
391
|
+
? item.result
|
|
392
|
+
: {
|
|
393
|
+
content: result.content,
|
|
394
|
+
structured_content: result.structuredContent,
|
|
395
|
+
},
|
|
396
|
+
error: asRecord(item.error) as { message?: string } | undefined,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (item.type === 'dynamicToolCall') {
|
|
400
|
+
return { type: 'dynamic_tool_call', id };
|
|
401
|
+
}
|
|
402
|
+
if (item.type === 'webSearch') {
|
|
403
|
+
return {
|
|
404
|
+
type: 'web_search',
|
|
405
|
+
id,
|
|
406
|
+
query: typeof item.query === 'string' ? item.query : undefined,
|
|
407
|
+
action: asRecord(item.action) ?? undefined,
|
|
408
|
+
result: item.results,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (item.type === 'fileChange') {
|
|
412
|
+
return {
|
|
413
|
+
type: 'file_change',
|
|
414
|
+
id,
|
|
415
|
+
changes: Array.isArray(item.changes)
|
|
416
|
+
? item.changes.flatMap(change => {
|
|
417
|
+
const value = asRecord(change);
|
|
418
|
+
const kind = asRecord(value?.kind)?.type;
|
|
419
|
+
return typeof value?.path === 'string' &&
|
|
420
|
+
(kind === 'add' || kind === 'delete' || kind === 'update')
|
|
421
|
+
? [{ path: value.path, kind }]
|
|
422
|
+
: [];
|
|
423
|
+
})
|
|
424
|
+
: [],
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (item.type === 'plan') return { type: 'todo_list', id };
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function normalizeCommandStatus(
|
|
432
|
+
value: unknown,
|
|
433
|
+
): 'in_progress' | 'completed' | 'failed' {
|
|
434
|
+
if (value === 'completed') return 'completed';
|
|
435
|
+
if (value === 'inProgress') return 'in_progress';
|
|
436
|
+
return 'failed';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function matchesActiveTurn({
|
|
440
|
+
params,
|
|
441
|
+
activeThreadId,
|
|
442
|
+
activeTurnId,
|
|
443
|
+
}: {
|
|
444
|
+
params: Record<string, unknown>;
|
|
445
|
+
activeThreadId: string | undefined;
|
|
446
|
+
activeTurnId: string | undefined;
|
|
447
|
+
}): boolean {
|
|
448
|
+
const nestedTurn = asRecord(params.turn);
|
|
449
|
+
const turnId =
|
|
450
|
+
typeof params.turnId === 'string'
|
|
451
|
+
? params.turnId
|
|
452
|
+
: typeof nestedTurn?.id === 'string'
|
|
453
|
+
? nestedTurn.id
|
|
454
|
+
: undefined;
|
|
455
|
+
return (
|
|
456
|
+
activeThreadId != null &&
|
|
457
|
+
activeTurnId != null &&
|
|
458
|
+
params.threadId === activeThreadId &&
|
|
459
|
+
turnId === activeTurnId
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
464
|
+
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
465
|
+
? (value as Record<string, unknown>)
|
|
466
|
+
: undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function stringArray(value: unknown): string[] {
|
|
470
|
+
return Array.isArray(value)
|
|
471
|
+
? value.filter((entry): entry is string => typeof entry === 'string')
|
|
472
|
+
: [];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function emptyUsageBreakdown(): UsageBreakdown {
|
|
476
|
+
return {
|
|
477
|
+
inputTokens: 0,
|
|
478
|
+
cachedInputTokens: 0,
|
|
479
|
+
cacheWriteInputTokens: 0,
|
|
480
|
+
outputTokens: 0,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function readUsageBreakdown(value: unknown): UsageBreakdown | undefined {
|
|
485
|
+
const usage = asRecord(value);
|
|
486
|
+
if (usage == null) return undefined;
|
|
487
|
+
return {
|
|
488
|
+
inputTokens: numberOrZero(usage.inputTokens),
|
|
489
|
+
cachedInputTokens: numberOrZero(usage.cachedInputTokens),
|
|
490
|
+
cacheWriteInputTokens: numberOrZero(usage.cacheWriteInputTokens),
|
|
491
|
+
outputTokens: numberOrZero(usage.outputTokens),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function addUsage({
|
|
496
|
+
total,
|
|
497
|
+
increment,
|
|
498
|
+
}: {
|
|
499
|
+
total: UsageBreakdown;
|
|
500
|
+
increment: UsageBreakdown;
|
|
501
|
+
}): UsageBreakdown {
|
|
502
|
+
return {
|
|
503
|
+
inputTokens: total.inputTokens + increment.inputTokens,
|
|
504
|
+
cachedInputTokens: total.cachedInputTokens + increment.cachedInputTokens,
|
|
505
|
+
cacheWriteInputTokens:
|
|
506
|
+
total.cacheWriteInputTokens + increment.cacheWriteInputTokens,
|
|
507
|
+
outputTokens: total.outputTokens + increment.outputTokens,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function usageKey(usage: UsageBreakdown): string {
|
|
512
|
+
return [
|
|
513
|
+
usage.inputTokens,
|
|
514
|
+
usage.cachedInputTokens,
|
|
515
|
+
usage.cacheWriteInputTokens,
|
|
516
|
+
usage.outputTokens,
|
|
517
|
+
].join(':');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function toLegacyUsage(usage: UsageBreakdown): Record<string, number> {
|
|
521
|
+
return {
|
|
522
|
+
input_tokens: usage.inputTokens,
|
|
523
|
+
cached_input_tokens: usage.cachedInputTokens,
|
|
524
|
+
cache_write_input_tokens: usage.cacheWriteInputTokens,
|
|
525
|
+
output_tokens: usage.outputTokens,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function numberOrZero(value: unknown): number {
|
|
530
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
531
|
+
}
|
|
@@ -15,6 +15,8 @@ export type CodexItem = {
|
|
|
15
15
|
status?: 'in_progress' | 'completed' | 'failed';
|
|
16
16
|
server?: string;
|
|
17
17
|
tool?: string;
|
|
18
|
+
input?: string;
|
|
19
|
+
isError?: boolean;
|
|
18
20
|
arguments?: unknown;
|
|
19
21
|
result?: { content?: unknown; structured_content?: unknown } | unknown;
|
|
20
22
|
error?: { message?: string };
|
|
@@ -114,6 +116,28 @@ export function createEmitStreamEvent({
|
|
|
114
116
|
stepTracker.observeEvent({ event, itemId: id });
|
|
115
117
|
};
|
|
116
118
|
|
|
119
|
+
if (item.type === 'native_tool' && item.tool != null) {
|
|
120
|
+
if (event.type === 'item.started' && item.input != null) {
|
|
121
|
+
send({
|
|
122
|
+
type: 'tool-call',
|
|
123
|
+
toolCallId: id,
|
|
124
|
+
toolName: item.tool,
|
|
125
|
+
input: item.input,
|
|
126
|
+
providerExecuted: true,
|
|
127
|
+
});
|
|
128
|
+
} else if (event.type === 'item.completed') {
|
|
129
|
+
send({
|
|
130
|
+
type: 'tool-result',
|
|
131
|
+
toolCallId: id,
|
|
132
|
+
toolName: item.tool,
|
|
133
|
+
result: item.result,
|
|
134
|
+
...(item.isError ? { isError: true } : {}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
stepTracker.observeEvent({ event, itemId: `native-tool:${id}` });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
117
141
|
if (item.type === 'agent_message' && typeof item.text === 'string') {
|
|
118
142
|
/*
|
|
119
143
|
* The presence of `id` in `textByItem` — not the `item.started` event —
|
|
@@ -323,12 +347,13 @@ function extractMcpToolCallResult(item: CodexItem): unknown {
|
|
|
323
347
|
function mapUsage(usage: Record<string, number>): Record<string, unknown> {
|
|
324
348
|
const input = usage.input_tokens ?? 0;
|
|
325
349
|
const cacheRead = usage.cached_input_tokens ?? 0;
|
|
350
|
+
const cacheWrite = usage.cache_write_input_tokens ?? 0;
|
|
326
351
|
return {
|
|
327
352
|
inputTokens: {
|
|
328
353
|
total: input,
|
|
329
|
-
noCache: Math.max(0, input - cacheRead),
|
|
354
|
+
noCache: Math.max(0, input - cacheRead - cacheWrite),
|
|
330
355
|
cacheRead,
|
|
331
|
-
cacheWrite
|
|
356
|
+
cacheWrite,
|
|
332
357
|
},
|
|
333
358
|
outputTokens: {
|
|
334
359
|
total: usage.output_tokens ?? 0,
|