@oh-my-pi/pi-ai 16.3.10 → 16.3.11
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
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.3.11] - 2026-07-06
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed `openai-codex-responses` fresh plan execution requests that contained only system/developer guidance by mirroring the final instruction as user input so Codex accepts the first turn. ([#4714](https://github.com/can1357/oh-my-pi/issues/4714))
|
|
10
|
+
- Fixed Codex WebSocket compact/resume delta diagnostics to record request shape and raw-vs-displayed usage buckets, so persistent server-reported uncached suffixes without `orchestration_*` fields are visible in debug stats. ([#4707](https://github.com/can1357/oh-my-pi/issues/4707))
|
|
11
|
+
|
|
5
12
|
## [16.3.10] - 2026-07-06
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -35,10 +35,46 @@ export interface OpenAICodexResponsesOptions extends StreamOptions {
|
|
|
35
35
|
onModerationMetadata?: (metadata: unknown) => void;
|
|
36
36
|
}
|
|
37
37
|
type CodexTransport = "sse" | "websocket";
|
|
38
|
+
/** Shape of the Codex request sent on the latest provider turn. */
|
|
39
|
+
export interface OpenAICodexTurnRequestDiagnostics {
|
|
40
|
+
transport: "sse" | "websocket";
|
|
41
|
+
previousResponseIdPresent: boolean;
|
|
42
|
+
inputItemCount: number;
|
|
43
|
+
inputItemTypes: string[];
|
|
44
|
+
firstInputItemType?: string;
|
|
45
|
+
inputJsonBytes: number;
|
|
46
|
+
promptCacheKey?: string;
|
|
47
|
+
toolsHash?: string;
|
|
48
|
+
optionsHash: string;
|
|
49
|
+
canAppendBeforeRequest: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Raw provider usage plus the normalized buckets OMP displays for the latest Codex turn. */
|
|
52
|
+
export interface OpenAICodexTurnUsageDiagnostics {
|
|
53
|
+
rawInputTokens: number;
|
|
54
|
+
rawCachedTokens: number;
|
|
55
|
+
rawUncachedTokens: number;
|
|
56
|
+
rawOutputTokens: number;
|
|
57
|
+
rawTotalTokens?: number;
|
|
58
|
+
rawOrchestrationInputTokens?: number;
|
|
59
|
+
rawOrchestrationCachedTokens?: number;
|
|
60
|
+
rawOrchestrationOutputTokens?: number;
|
|
61
|
+
displayedInputTokens: number;
|
|
62
|
+
displayedOutputTokens: number;
|
|
63
|
+
displayedCacheReadTokens: number;
|
|
64
|
+
displayedCacheWriteTokens: number;
|
|
65
|
+
displayedTotalTokens: number;
|
|
66
|
+
displayedOrchestrationInputTokens: number;
|
|
67
|
+
displayedOrchestrationCacheReadTokens: number;
|
|
68
|
+
displayedOrchestrationOutputTokens: number;
|
|
69
|
+
}
|
|
70
|
+
/** Latest Codex turn request/usage diagnostics exposed to debug UIs and tests. */
|
|
71
|
+
export interface OpenAICodexTurnDiagnostics {
|
|
72
|
+
request: OpenAICodexTurnRequestDiagnostics;
|
|
73
|
+
usage?: OpenAICodexTurnUsageDiagnostics;
|
|
74
|
+
}
|
|
38
75
|
/**
|
|
39
|
-
* Per-session request-shape counters
|
|
40
|
-
*
|
|
41
|
-
* too (the shared chained-request builder records every request it shapes).
|
|
76
|
+
* Per-session request-shape counters and latest turn diagnostics. Despite the
|
|
77
|
+
* name, these cover both transports.
|
|
42
78
|
*/
|
|
43
79
|
export interface OpenAICodexWebSocketDebugStats {
|
|
44
80
|
fullContextRequests: number;
|
|
@@ -46,6 +82,7 @@ export interface OpenAICodexWebSocketDebugStats {
|
|
|
46
82
|
lastInputItems: number;
|
|
47
83
|
lastDeltaInputItems?: number;
|
|
48
84
|
lastPreviousResponseId?: string;
|
|
85
|
+
lastTurn?: OpenAICodexTurnDiagnostics;
|
|
49
86
|
}
|
|
50
87
|
/** @internal Exported for tests. */
|
|
51
88
|
export declare function normalizeCodexToolChoice(choice: ToolChoice | undefined, tools?: Tool[], model?: Model<"openai-codex-responses">): string | Record<string, unknown> | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "16.3.
|
|
4
|
+
"version": "16.3.11",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.0",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "16.3.
|
|
42
|
-
"@oh-my-pi/pi-utils": "16.3.
|
|
43
|
-
"@oh-my-pi/pi-wire": "16.3.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "16.3.11",
|
|
42
|
+
"@oh-my-pi/pi-utils": "16.3.11",
|
|
43
|
+
"@oh-my-pi/pi-wire": "16.3.11",
|
|
44
44
|
"arktype": "^2.2.0",
|
|
45
45
|
"zod": "^4"
|
|
46
46
|
},
|
|
@@ -230,16 +230,61 @@ export async function transformRequestBody(
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
if (prompt?.developerMessages && prompt.developerMessages.length > 0
|
|
234
|
-
const developerMessages = prompt.developerMessages.map(
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
if (prompt?.developerMessages && prompt.developerMessages.length > 0) {
|
|
234
|
+
const developerMessages: InputItem[] = prompt.developerMessages.map(text => ({
|
|
235
|
+
type: "message",
|
|
236
|
+
role: "developer",
|
|
237
|
+
content: [{ type: "input_text", text }],
|
|
238
|
+
}));
|
|
239
|
+
const input = Array.isArray(body.input) ? body.input : [];
|
|
240
|
+
body.input = [...developerMessages, ...input];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let finalInstruction = prompt?.developerMessages.findLast(text => text.trim().length > 0);
|
|
244
|
+
if (finalInstruction === undefined && Array.isArray(body.input)) {
|
|
245
|
+
for (let itemIndex = body.input.length - 1; itemIndex >= 0; itemIndex -= 1) {
|
|
246
|
+
const item = body.input[itemIndex];
|
|
247
|
+
if (item.role !== "developer" || !Array.isArray(item.content)) continue;
|
|
248
|
+
for (let partIndex = item.content.length - 1; partIndex >= 0; partIndex -= 1) {
|
|
249
|
+
const part = item.content[partIndex];
|
|
250
|
+
if (
|
|
251
|
+
part &&
|
|
252
|
+
typeof part === "object" &&
|
|
253
|
+
"type" in part &&
|
|
254
|
+
part.type === "input_text" &&
|
|
255
|
+
"text" in part &&
|
|
256
|
+
typeof part.text === "string" &&
|
|
257
|
+
part.text.trim().length > 0
|
|
258
|
+
) {
|
|
259
|
+
finalInstruction = part.text;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (finalInstruction !== undefined) break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (finalInstruction === undefined && typeof body.instructions === "string" && body.instructions.trim().length > 0) {
|
|
267
|
+
finalInstruction = body.instructions;
|
|
268
|
+
}
|
|
269
|
+
if (finalInstruction !== undefined) {
|
|
270
|
+
const input = Array.isArray(body.input) ? body.input : [];
|
|
271
|
+
let hasVisibleInput = false;
|
|
272
|
+
for (const item of input) {
|
|
273
|
+
if (item.role !== "developer") {
|
|
274
|
+
hasVisibleInput = true;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!hasVisibleInput) {
|
|
279
|
+
body.input = [
|
|
280
|
+
...input,
|
|
281
|
+
{
|
|
237
282
|
type: "message",
|
|
238
|
-
role: "
|
|
239
|
-
content: [{ type: "input_text", text }],
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
283
|
+
role: "user",
|
|
284
|
+
content: [{ type: "input_text", text: finalInstruction }],
|
|
285
|
+
},
|
|
286
|
+
];
|
|
287
|
+
}
|
|
243
288
|
}
|
|
244
289
|
|
|
245
290
|
const responsesLite = shouldUseCodexResponsesLite(body, options.responsesLite);
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
Tool,
|
|
38
38
|
ToolCall,
|
|
39
39
|
ToolChoice,
|
|
40
|
+
Usage,
|
|
40
41
|
} from "../types";
|
|
41
42
|
import {
|
|
42
43
|
createOpenAIResponsesHistoryPayload,
|
|
@@ -246,10 +247,66 @@ type CodexOutputBlock =
|
|
|
246
247
|
| TextContent
|
|
247
248
|
| (ToolCall & { [kStreamingPartialJson]: string; [kStreamingLastParseLen]?: number });
|
|
248
249
|
|
|
250
|
+
interface CodexResponseUsage {
|
|
251
|
+
input_tokens?: number;
|
|
252
|
+
output_tokens?: number;
|
|
253
|
+
total_tokens?: number;
|
|
254
|
+
prompt_cache_hit_tokens?: number;
|
|
255
|
+
input_tokens_details?: {
|
|
256
|
+
cached_tokens?: number;
|
|
257
|
+
cache_write_tokens?: number;
|
|
258
|
+
orchestration_input_tokens?: number;
|
|
259
|
+
orchestration_input_cached_tokens?: number;
|
|
260
|
+
};
|
|
261
|
+
output_tokens_details?: {
|
|
262
|
+
reasoning_tokens?: number;
|
|
263
|
+
orchestration_output_tokens?: number;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Shape of the Codex request sent on the latest provider turn. */
|
|
268
|
+
export interface OpenAICodexTurnRequestDiagnostics {
|
|
269
|
+
transport: "sse" | "websocket";
|
|
270
|
+
previousResponseIdPresent: boolean;
|
|
271
|
+
inputItemCount: number;
|
|
272
|
+
inputItemTypes: string[];
|
|
273
|
+
firstInputItemType?: string;
|
|
274
|
+
inputJsonBytes: number;
|
|
275
|
+
promptCacheKey?: string;
|
|
276
|
+
toolsHash?: string;
|
|
277
|
+
optionsHash: string;
|
|
278
|
+
canAppendBeforeRequest: boolean;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Raw provider usage plus the normalized buckets OMP displays for the latest Codex turn. */
|
|
282
|
+
export interface OpenAICodexTurnUsageDiagnostics {
|
|
283
|
+
rawInputTokens: number;
|
|
284
|
+
rawCachedTokens: number;
|
|
285
|
+
rawUncachedTokens: number;
|
|
286
|
+
rawOutputTokens: number;
|
|
287
|
+
rawTotalTokens?: number;
|
|
288
|
+
rawOrchestrationInputTokens?: number;
|
|
289
|
+
rawOrchestrationCachedTokens?: number;
|
|
290
|
+
rawOrchestrationOutputTokens?: number;
|
|
291
|
+
displayedInputTokens: number;
|
|
292
|
+
displayedOutputTokens: number;
|
|
293
|
+
displayedCacheReadTokens: number;
|
|
294
|
+
displayedCacheWriteTokens: number;
|
|
295
|
+
displayedTotalTokens: number;
|
|
296
|
+
displayedOrchestrationInputTokens: number;
|
|
297
|
+
displayedOrchestrationCacheReadTokens: number;
|
|
298
|
+
displayedOrchestrationOutputTokens: number;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Latest Codex turn request/usage diagnostics exposed to debug UIs and tests. */
|
|
302
|
+
export interface OpenAICodexTurnDiagnostics {
|
|
303
|
+
request: OpenAICodexTurnRequestDiagnostics;
|
|
304
|
+
usage?: OpenAICodexTurnUsageDiagnostics;
|
|
305
|
+
}
|
|
306
|
+
|
|
249
307
|
/**
|
|
250
|
-
* Per-session request-shape counters
|
|
251
|
-
*
|
|
252
|
-
* too (the shared chained-request builder records every request it shapes).
|
|
308
|
+
* Per-session request-shape counters and latest turn diagnostics. Despite the
|
|
309
|
+
* name, these cover both transports.
|
|
253
310
|
*/
|
|
254
311
|
export interface OpenAICodexWebSocketDebugStats {
|
|
255
312
|
fullContextRequests: number;
|
|
@@ -257,6 +314,7 @@ export interface OpenAICodexWebSocketDebugStats {
|
|
|
257
314
|
lastInputItems: number;
|
|
258
315
|
lastDeltaInputItems?: number;
|
|
259
316
|
lastPreviousResponseId?: string;
|
|
317
|
+
lastTurn?: OpenAICodexTurnDiagnostics;
|
|
260
318
|
}
|
|
261
319
|
|
|
262
320
|
/**
|
|
@@ -1002,6 +1060,7 @@ async function openCodexWebSocketTransport(
|
|
|
1002
1060
|
requestBodyForState: RequestBody;
|
|
1003
1061
|
transport: CodexTransport;
|
|
1004
1062
|
}> {
|
|
1063
|
+
const canAppendBeforeRequest = websocketState.canAppend === true;
|
|
1005
1064
|
const chainedBody = buildCodexChainedRequestBody(requestContext.transformedBody, websocketState);
|
|
1006
1065
|
// WebSocket frames cannot carry per-request HTTP headers, so the Responses
|
|
1007
1066
|
// Lite marker rides in `client_metadata` on every `response.create`.
|
|
@@ -1021,6 +1080,7 @@ async function openCodexWebSocketTransport(
|
|
|
1021
1080
|
if (replacementWebsocketRequest !== undefined) {
|
|
1022
1081
|
websocketRequest = replacementWebsocketRequest as typeof websocketRequest;
|
|
1023
1082
|
}
|
|
1083
|
+
recordCodexTurnRequestDiagnostics(websocketState, websocketRequest, "websocket", canAppendBeforeRequest);
|
|
1024
1084
|
const websocketHeaders = createCodexHeaders(
|
|
1025
1085
|
requestContext.requestHeaders,
|
|
1026
1086
|
requestContext.accountId,
|
|
@@ -1113,12 +1173,13 @@ async function openCodexSseTransport(
|
|
|
1113
1173
|
),
|
|
1114
1174
|
);
|
|
1115
1175
|
};
|
|
1176
|
+
const canAppendBeforeRequest = state?.canAppend === true;
|
|
1116
1177
|
let wireBody = body;
|
|
1117
1178
|
const replacementWireBody = await options?.onPayload?.(wireBody, model);
|
|
1118
1179
|
if (replacementWireBody !== undefined) {
|
|
1119
1180
|
wireBody = replacementWireBody as RequestBody;
|
|
1120
1181
|
}
|
|
1121
|
-
|
|
1182
|
+
recordCodexTurnRequestDiagnostics(state, wireBody, "sse", canAppendBeforeRequest);
|
|
1122
1183
|
return { eventStream: await open(wireBody), requestBodyForState: structuredCloneJSON(wireBody), transport: "sse" };
|
|
1123
1184
|
}
|
|
1124
1185
|
|
|
@@ -1525,34 +1586,19 @@ class CodexStreamProcessor {
|
|
|
1525
1586
|
#handleResponseCompleted(rawEvent: Record<string, unknown>): void {
|
|
1526
1587
|
const { runtime, model, output } = this;
|
|
1527
1588
|
runtime.sawTerminalEvent = true;
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
input_tokens_details?: {
|
|
1537
|
-
cached_tokens?: number;
|
|
1538
|
-
orchestration_input_tokens?: number;
|
|
1539
|
-
orchestration_input_cached_tokens?: number;
|
|
1540
|
-
};
|
|
1541
|
-
output_tokens_details?: {
|
|
1542
|
-
reasoning_tokens?: number;
|
|
1543
|
-
orchestration_output_tokens?: number;
|
|
1544
|
-
};
|
|
1545
|
-
};
|
|
1546
|
-
status?: string;
|
|
1547
|
-
service_tier?: ServiceTier | "default";
|
|
1548
|
-
end_turn?: boolean;
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
).response;
|
|
1589
|
+
const rawResponse = rawEvent.response;
|
|
1590
|
+
const response = rawResponse && typeof rawResponse === "object" ? rawResponse : undefined;
|
|
1591
|
+
const responseId = response && "id" in response && typeof response.id === "string" ? response.id : undefined;
|
|
1592
|
+
const usage = response && "usage" in response ? parseCodexResponseUsage(response.usage) : undefined;
|
|
1593
|
+
const serviceTier =
|
|
1594
|
+
response && "service_tier" in response ? parseCodexServiceTier(response.service_tier) : undefined;
|
|
1595
|
+
const status = response && "status" in response ? parseCodexResponseStatus(response.status) : undefined;
|
|
1596
|
+
const endTurn = response && "end_turn" in response ? response.end_turn : undefined;
|
|
1552
1597
|
|
|
1553
|
-
populateResponsesUsageFromResponse(output,
|
|
1554
|
-
|
|
1555
|
-
|
|
1598
|
+
populateResponsesUsageFromResponse(output, usage);
|
|
1599
|
+
recordCodexTurnUsageDiagnostics(runtime.websocketState, usage, output.usage);
|
|
1600
|
+
if (responseId) {
|
|
1601
|
+
output.responseId = responseId;
|
|
1556
1602
|
}
|
|
1557
1603
|
|
|
1558
1604
|
const state = runtime.websocketState;
|
|
@@ -1564,8 +1610,8 @@ class CodexStreamProcessor {
|
|
|
1564
1610
|
resetCodexWebSocketAppendState(state);
|
|
1565
1611
|
} else {
|
|
1566
1612
|
state.lastRequest = structuredCloneJSON(runtime.requestBodyForState);
|
|
1567
|
-
if (
|
|
1568
|
-
state.lastResponseId =
|
|
1613
|
+
if (responseId) {
|
|
1614
|
+
state.lastResponseId = responseId;
|
|
1569
1615
|
state.lastResponseItems = stripInputItemIds(structuredCloneJSON(runtime.nativeOutputItems));
|
|
1570
1616
|
state.canAppend = rawEvent.type === "response.done" || rawEvent.type === "response.completed";
|
|
1571
1617
|
} else {
|
|
@@ -1578,14 +1624,9 @@ class CodexStreamProcessor {
|
|
|
1578
1624
|
finalizePendingResponsesToolCalls(output);
|
|
1579
1625
|
|
|
1580
1626
|
calculateCost(model, output.usage);
|
|
1581
|
-
applyCodexServiceTierPricing(
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
response?.service_tier,
|
|
1585
|
-
runtime.requestBodyForState.service_tier,
|
|
1586
|
-
);
|
|
1587
|
-
output.stopReason = mapOpenAIResponsesStopReason(response?.status as ResponseStatus | undefined);
|
|
1588
|
-
promoteResponsesToolUseStopReason(output, response?.end_turn);
|
|
1627
|
+
applyCodexServiceTierPricing(model, output.usage, serviceTier, runtime.requestBodyForState.service_tier);
|
|
1628
|
+
output.stopReason = mapOpenAIResponsesStopReason(status);
|
|
1629
|
+
promoteResponsesToolUseStopReason(output, endTurn === true ? true : endTurn === false ? false : undefined);
|
|
1589
1630
|
}
|
|
1590
1631
|
|
|
1591
1632
|
async #recoverStreamError(error: unknown): Promise<boolean> {
|
|
@@ -2270,22 +2311,230 @@ function stripInputItemIds(items: Array<Record<string, unknown>>): InputItem[] {
|
|
|
2270
2311
|
});
|
|
2271
2312
|
}
|
|
2272
2313
|
|
|
2273
|
-
|
|
2314
|
+
const codexDiagnosticsTextEncoder = new TextEncoder();
|
|
2315
|
+
|
|
2316
|
+
function jsonByteLength(value: unknown): number {
|
|
2317
|
+
const json = JSON.stringify(value);
|
|
2318
|
+
return codexDiagnosticsTextEncoder.encode(json === undefined ? "undefined" : json).byteLength;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function hashJson(value: unknown): string {
|
|
2322
|
+
const json = JSON.stringify(value);
|
|
2323
|
+
return String(Bun.hash(json === undefined ? "undefined" : json));
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
function parseCodexServiceTier(value: unknown): ServiceTier | undefined {
|
|
2327
|
+
switch (value) {
|
|
2328
|
+
case "auto":
|
|
2329
|
+
case "default":
|
|
2330
|
+
case "flex":
|
|
2331
|
+
case "scale":
|
|
2332
|
+
case "priority":
|
|
2333
|
+
return value;
|
|
2334
|
+
default:
|
|
2335
|
+
return undefined;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
function parseCodexResponseStatus(value: unknown): ResponseStatus | undefined {
|
|
2340
|
+
switch (value) {
|
|
2341
|
+
case "completed":
|
|
2342
|
+
case "failed":
|
|
2343
|
+
case "in_progress":
|
|
2344
|
+
case "cancelled":
|
|
2345
|
+
case "queued":
|
|
2346
|
+
case "incomplete":
|
|
2347
|
+
return value;
|
|
2348
|
+
default:
|
|
2349
|
+
return undefined;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
function parseCodexResponseUsage(value: unknown): CodexResponseUsage | undefined {
|
|
2354
|
+
if (!value || typeof value !== "object") return undefined;
|
|
2355
|
+
const usage: CodexResponseUsage = {};
|
|
2356
|
+
let hasUsage = false;
|
|
2357
|
+
if ("input_tokens" in value && typeof value.input_tokens === "number") {
|
|
2358
|
+
usage.input_tokens = value.input_tokens;
|
|
2359
|
+
hasUsage = true;
|
|
2360
|
+
}
|
|
2361
|
+
if ("output_tokens" in value && typeof value.output_tokens === "number") {
|
|
2362
|
+
usage.output_tokens = value.output_tokens;
|
|
2363
|
+
hasUsage = true;
|
|
2364
|
+
}
|
|
2365
|
+
if ("total_tokens" in value && typeof value.total_tokens === "number") {
|
|
2366
|
+
usage.total_tokens = value.total_tokens;
|
|
2367
|
+
hasUsage = true;
|
|
2368
|
+
}
|
|
2369
|
+
if ("prompt_cache_hit_tokens" in value && typeof value.prompt_cache_hit_tokens === "number") {
|
|
2370
|
+
usage.prompt_cache_hit_tokens = value.prompt_cache_hit_tokens;
|
|
2371
|
+
hasUsage = true;
|
|
2372
|
+
}
|
|
2373
|
+
if (
|
|
2374
|
+
"input_tokens_details" in value &&
|
|
2375
|
+
value.input_tokens_details &&
|
|
2376
|
+
typeof value.input_tokens_details === "object"
|
|
2377
|
+
) {
|
|
2378
|
+
const details = value.input_tokens_details;
|
|
2379
|
+
const parsedDetails: NonNullable<CodexResponseUsage["input_tokens_details"]> = {};
|
|
2380
|
+
let hasDetails = false;
|
|
2381
|
+
if ("cached_tokens" in details && typeof details.cached_tokens === "number") {
|
|
2382
|
+
parsedDetails.cached_tokens = details.cached_tokens;
|
|
2383
|
+
hasDetails = true;
|
|
2384
|
+
}
|
|
2385
|
+
if ("cache_write_tokens" in details && typeof details.cache_write_tokens === "number") {
|
|
2386
|
+
parsedDetails.cache_write_tokens = details.cache_write_tokens;
|
|
2387
|
+
hasDetails = true;
|
|
2388
|
+
}
|
|
2389
|
+
if ("orchestration_input_tokens" in details && typeof details.orchestration_input_tokens === "number") {
|
|
2390
|
+
parsedDetails.orchestration_input_tokens = details.orchestration_input_tokens;
|
|
2391
|
+
hasDetails = true;
|
|
2392
|
+
}
|
|
2393
|
+
if (
|
|
2394
|
+
"orchestration_input_cached_tokens" in details &&
|
|
2395
|
+
typeof details.orchestration_input_cached_tokens === "number"
|
|
2396
|
+
) {
|
|
2397
|
+
parsedDetails.orchestration_input_cached_tokens = details.orchestration_input_cached_tokens;
|
|
2398
|
+
hasDetails = true;
|
|
2399
|
+
}
|
|
2400
|
+
if (hasDetails) {
|
|
2401
|
+
usage.input_tokens_details = parsedDetails;
|
|
2402
|
+
hasUsage = true;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
if (
|
|
2406
|
+
"output_tokens_details" in value &&
|
|
2407
|
+
value.output_tokens_details &&
|
|
2408
|
+
typeof value.output_tokens_details === "object"
|
|
2409
|
+
) {
|
|
2410
|
+
const details = value.output_tokens_details;
|
|
2411
|
+
const parsedDetails: NonNullable<CodexResponseUsage["output_tokens_details"]> = {};
|
|
2412
|
+
let hasDetails = false;
|
|
2413
|
+
if ("reasoning_tokens" in details && typeof details.reasoning_tokens === "number") {
|
|
2414
|
+
parsedDetails.reasoning_tokens = details.reasoning_tokens;
|
|
2415
|
+
hasDetails = true;
|
|
2416
|
+
}
|
|
2417
|
+
if ("orchestration_output_tokens" in details && typeof details.orchestration_output_tokens === "number") {
|
|
2418
|
+
parsedDetails.orchestration_output_tokens = details.orchestration_output_tokens;
|
|
2419
|
+
hasDetails = true;
|
|
2420
|
+
}
|
|
2421
|
+
if (hasDetails) {
|
|
2422
|
+
usage.output_tokens_details = parsedDetails;
|
|
2423
|
+
hasUsage = true;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
return hasUsage ? usage : undefined;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
function describeCodexInputItemType(item: unknown): string {
|
|
2430
|
+
if (item && typeof item === "object") {
|
|
2431
|
+
if ("type" in item && typeof item.type === "string") return item.type;
|
|
2432
|
+
if ("role" in item && typeof item.role === "string") return item.role;
|
|
2433
|
+
}
|
|
2434
|
+
return typeof item;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
function createCodexOptionsHash(request: Record<string, unknown>): string {
|
|
2438
|
+
const options: Record<string, unknown> = {};
|
|
2439
|
+
for (const key in request) {
|
|
2440
|
+
if (key === "input" || key === "previous_response_id" || key === "type" || key === "client_metadata") {
|
|
2441
|
+
continue;
|
|
2442
|
+
}
|
|
2443
|
+
options[key] = request[key];
|
|
2444
|
+
}
|
|
2445
|
+
return hashJson(options);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
function buildCodexTurnRequestDiagnostics(
|
|
2449
|
+
request: Record<string, unknown>,
|
|
2450
|
+
transport: CodexTransport,
|
|
2451
|
+
canAppendBeforeRequest: boolean,
|
|
2452
|
+
): OpenAICodexTurnRequestDiagnostics {
|
|
2453
|
+
const input = request.input;
|
|
2454
|
+
const inputItems = Array.isArray(input) ? input : [];
|
|
2455
|
+
const inputItemTypes = inputItems.map(describeCodexInputItemType);
|
|
2456
|
+
const promptCacheKey = typeof request.prompt_cache_key === "string" ? request.prompt_cache_key : undefined;
|
|
2457
|
+
const toolsHash = request.tools === undefined ? undefined : hashJson(request.tools);
|
|
2458
|
+
return {
|
|
2459
|
+
transport,
|
|
2460
|
+
previousResponseIdPresent:
|
|
2461
|
+
typeof request.previous_response_id === "string" && request.previous_response_id.length > 0,
|
|
2462
|
+
inputItemCount: inputItems.length,
|
|
2463
|
+
inputItemTypes,
|
|
2464
|
+
...(inputItemTypes[0] ? { firstInputItemType: inputItemTypes[0] } : {}),
|
|
2465
|
+
inputJsonBytes: jsonByteLength(inputItems),
|
|
2466
|
+
...(promptCacheKey !== undefined ? { promptCacheKey } : {}),
|
|
2467
|
+
...(toolsHash !== undefined ? { toolsHash } : {}),
|
|
2468
|
+
optionsHash: createCodexOptionsHash(request),
|
|
2469
|
+
canAppendBeforeRequest,
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
function recordCodexTurnRequestDiagnostics(
|
|
2274
2474
|
state: CodexWebSocketSessionState | undefined,
|
|
2275
2475
|
request: Record<string, unknown>,
|
|
2476
|
+
transport: CodexTransport,
|
|
2477
|
+
canAppendBeforeRequest: boolean,
|
|
2276
2478
|
): void {
|
|
2277
2479
|
if (!state) return;
|
|
2278
2480
|
const input = request.input;
|
|
2279
2481
|
state.stats.lastInputItems = Array.isArray(input) ? input.length : 0;
|
|
2280
|
-
|
|
2482
|
+
const previousResponseId =
|
|
2483
|
+
typeof request.previous_response_id === "string" ? request.previous_response_id : undefined;
|
|
2484
|
+
if (previousResponseId && previousResponseId.length > 0) {
|
|
2281
2485
|
state.stats.deltaRequests += 1;
|
|
2282
2486
|
state.stats.lastDeltaInputItems = state.stats.lastInputItems;
|
|
2283
|
-
state.stats.lastPreviousResponseId =
|
|
2284
|
-
|
|
2487
|
+
state.stats.lastPreviousResponseId = previousResponseId;
|
|
2488
|
+
} else {
|
|
2489
|
+
state.stats.fullContextRequests += 1;
|
|
2490
|
+
state.stats.lastDeltaInputItems = undefined;
|
|
2491
|
+
state.stats.lastPreviousResponseId = undefined;
|
|
2285
2492
|
}
|
|
2286
|
-
state.stats.
|
|
2287
|
-
|
|
2288
|
-
|
|
2493
|
+
state.stats.lastTurn = {
|
|
2494
|
+
request: buildCodexTurnRequestDiagnostics(request, transport, canAppendBeforeRequest),
|
|
2495
|
+
};
|
|
2496
|
+
CODEX_DEBUG && logger.debug("[codex] codex turn request diagnostics", { diagnostics: state.stats.lastTurn.request });
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
function recordCodexTurnUsageDiagnostics(
|
|
2500
|
+
state: CodexWebSocketSessionState | undefined,
|
|
2501
|
+
rawUsage: CodexResponseUsage | undefined,
|
|
2502
|
+
displayedUsage: Usage,
|
|
2503
|
+
): void {
|
|
2504
|
+
if (!state?.stats.lastTurn || !rawUsage) return;
|
|
2505
|
+
const details = rawUsage.input_tokens_details;
|
|
2506
|
+
const outputDetails = rawUsage.output_tokens_details;
|
|
2507
|
+
const rawInputTokens = rawUsage.input_tokens ?? 0;
|
|
2508
|
+
const rawCachedTokens = details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens ?? 0;
|
|
2509
|
+
const usageDiagnostics: OpenAICodexTurnUsageDiagnostics = {
|
|
2510
|
+
rawInputTokens,
|
|
2511
|
+
rawCachedTokens,
|
|
2512
|
+
rawUncachedTokens: Math.max(0, rawInputTokens - rawCachedTokens),
|
|
2513
|
+
rawOutputTokens: rawUsage.output_tokens ?? 0,
|
|
2514
|
+
...(typeof rawUsage.total_tokens === "number" ? { rawTotalTokens: rawUsage.total_tokens } : {}),
|
|
2515
|
+
...(typeof details?.orchestration_input_tokens === "number"
|
|
2516
|
+
? { rawOrchestrationInputTokens: details.orchestration_input_tokens }
|
|
2517
|
+
: {}),
|
|
2518
|
+
...(typeof details?.orchestration_input_cached_tokens === "number"
|
|
2519
|
+
? { rawOrchestrationCachedTokens: details.orchestration_input_cached_tokens }
|
|
2520
|
+
: {}),
|
|
2521
|
+
...(typeof outputDetails?.orchestration_output_tokens === "number"
|
|
2522
|
+
? { rawOrchestrationOutputTokens: outputDetails.orchestration_output_tokens }
|
|
2523
|
+
: {}),
|
|
2524
|
+
displayedInputTokens: displayedUsage.input,
|
|
2525
|
+
displayedOutputTokens: displayedUsage.output,
|
|
2526
|
+
displayedCacheReadTokens: displayedUsage.cacheRead,
|
|
2527
|
+
displayedCacheWriteTokens: displayedUsage.cacheWrite,
|
|
2528
|
+
displayedTotalTokens: displayedUsage.totalTokens,
|
|
2529
|
+
displayedOrchestrationInputTokens: displayedUsage.orchestration?.input ?? 0,
|
|
2530
|
+
displayedOrchestrationCacheReadTokens: displayedUsage.orchestration?.cacheRead ?? 0,
|
|
2531
|
+
displayedOrchestrationOutputTokens: displayedUsage.orchestration?.output ?? 0,
|
|
2532
|
+
};
|
|
2533
|
+
state.stats.lastTurn = {
|
|
2534
|
+
...state.stats.lastTurn,
|
|
2535
|
+
usage: usageDiagnostics,
|
|
2536
|
+
};
|
|
2537
|
+
CODEX_DEBUG && logger.debug("[codex] codex turn diagnostics", { diagnostics: state.stats.lastTurn });
|
|
2289
2538
|
}
|
|
2290
2539
|
|
|
2291
2540
|
/**
|
|
@@ -2306,9 +2555,7 @@ function buildCodexChainedRequestBody(
|
|
|
2306
2555
|
? buildResponsesDeltaInput(state.lastRequest, state.lastResponseItems, requestBody)
|
|
2307
2556
|
: null;
|
|
2308
2557
|
if (appendInput && appendInput.length > 0 && state?.lastResponseId) {
|
|
2309
|
-
|
|
2310
|
-
recordCodexWebSocketRequestStats(state, body);
|
|
2311
|
-
return body;
|
|
2558
|
+
return { ...requestBody, previous_response_id: state.lastResponseId, input: appendInput };
|
|
2312
2559
|
}
|
|
2313
2560
|
if (chainable && state) {
|
|
2314
2561
|
// Chaining was eligible but the prefix/options check failed: history
|
|
@@ -2322,7 +2569,6 @@ function buildCodexChainedRequestBody(
|
|
|
2322
2569
|
state.turnState = undefined;
|
|
2323
2570
|
state.modelsEtag = undefined;
|
|
2324
2571
|
}
|
|
2325
|
-
recordCodexWebSocketRequestStats(state, requestBody);
|
|
2326
2572
|
return requestBody;
|
|
2327
2573
|
}
|
|
2328
2574
|
|