@opengeni/codex 0.2.23 → 0.2.24
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/dist/fetch.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +152 -30
- package/dist/index.js.map +1 -1
- package/dist/meaningful-output.d.ts +2 -0
- package/dist/reasoning-configuration.d.ts +16 -0
- package/dist/request-context.d.ts +2 -0
- package/package.json +1 -1
- package/src/fetch.ts +93 -32
- package/src/index.ts +1 -0
- package/src/meaningful-output.ts +36 -0
- package/src/reasoning-configuration.ts +85 -0
- package/src/request-context.ts +2 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Durable control records; only providerData is sent by the SDK unknown-item adapter. */
|
|
2
|
+
export declare const REASONING_CONFIGURATION_FIELD = "opengeniReasoningConfiguration";
|
|
3
|
+
export type ConfigurationEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
4
|
+
export declare function supportsReasoningConfiguration(model: string, effort: string): effort is ConfigurationEffort;
|
|
5
|
+
export type ReasoningConfiguration = {
|
|
6
|
+
version: 1;
|
|
7
|
+
baselineEffort: ConfigurationEffort;
|
|
8
|
+
effort: ConfigurationEffort;
|
|
9
|
+
turnId: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function readReasoningConfiguration(item: Record<string, unknown>): ReasoningConfiguration | null;
|
|
12
|
+
export declare function reasoningConfigurationItem(state: ReasoningConfiguration): Record<string, unknown>;
|
|
13
|
+
export declare function latestReasoningConfiguration(items: readonly Record<string, unknown>[]): ReasoningConfiguration | null;
|
|
14
|
+
export declare function isReasoningConfigurationUpdate(item: Record<string, unknown>): boolean;
|
|
15
|
+
/** Never leak Astra controls to an incompatible model. Coalesce only adjacent controls. */
|
|
16
|
+
export declare function projectReasoningConfigurations(items: Record<string, unknown>[], enabled: boolean): Record<string, unknown>[];
|
|
@@ -48,6 +48,8 @@ export type CodexModelRequestEvent = {
|
|
|
48
48
|
providerRequestId?: string;
|
|
49
49
|
status?: number;
|
|
50
50
|
willRetry?: boolean;
|
|
51
|
+
/** Only true for a successful response with substantive assistant/tool output. */
|
|
52
|
+
meaningfulOutput?: boolean;
|
|
51
53
|
};
|
|
52
54
|
export type CodexRequestOpaqueArtifacts = {
|
|
53
55
|
requestId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/codex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "ChatGPT/Codex subscription auth + transport: device-code login, token refresh, and the Responses-backend fetch. Pure HTTP + transforms; no database dependency.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
package/src/fetch.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// Stream parsing is delegated to the SDK (SSE passthrough; spec §0(d)).
|
|
12
12
|
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { hasMeaningfulCodexOutput } from "./meaningful-output";
|
|
14
15
|
import { CODEX_ORIGINATOR } from "./constants";
|
|
15
16
|
import { normalizeCodexRequestBody } from "./normalize";
|
|
16
17
|
import { opaqueProviderArtifactFingerprints } from "./opaque-artifact";
|
|
@@ -95,6 +96,9 @@ export type CodexEncryptedArtifactRejection = {
|
|
|
95
96
|
kind: "encrypted_content_rejected";
|
|
96
97
|
};
|
|
97
98
|
|
|
99
|
+
/** Exact Codex `error.code` for an opaque artifact the backend can no longer use. */
|
|
100
|
+
export const CODEX_ENCRYPTED_CONTENT_REJECTION_CODE = "invalid_encrypted_content";
|
|
101
|
+
|
|
98
102
|
/**
|
|
99
103
|
* Classify only the provider's definitive request rejection for an opaque
|
|
100
104
|
* reasoning artifact that it can no longer decrypt/parse. A Codex transport
|
|
@@ -113,6 +117,14 @@ export function classifyCodexEncryptedArtifactRejection(
|
|
|
113
117
|
? (value.error as Record<string, unknown>)
|
|
114
118
|
: null;
|
|
115
119
|
const status = Number(value.status ?? body?.status);
|
|
120
|
+
// The exact provider error code names this family without a message
|
|
121
|
+
// match: production compaction requests have been rejected with it while
|
|
122
|
+
// the human-readable text varied. It is never a generic 400.
|
|
123
|
+
const exactCode =
|
|
124
|
+
typeof body?.code === "string" ? body.code : typeof value.code === "string" ? value.code : "";
|
|
125
|
+
if (status === 400 && exactCode === CODEX_ENCRYPTED_CONTENT_REJECTION_CODE) {
|
|
126
|
+
return { status: 400, kind: "encrypted_content_rejected" };
|
|
127
|
+
}
|
|
116
128
|
const message = [
|
|
117
129
|
typeof value.message === "string" ? value.message : "",
|
|
118
130
|
typeof body?.message === "string" ? body.message : "",
|
|
@@ -221,8 +233,7 @@ type RequestTerminalOutcome = "completed" | "failed" | "timed_out";
|
|
|
221
233
|
|
|
222
234
|
type SemanticTerminalState = {
|
|
223
235
|
phase: "completed" | "failed" | null;
|
|
224
|
-
|
|
225
|
-
deferTransportTerminal: boolean;
|
|
236
|
+
meaningfulOutput?: boolean;
|
|
226
237
|
};
|
|
227
238
|
|
|
228
239
|
type CodexSseEvent = {
|
|
@@ -312,6 +323,13 @@ function markSemanticTerminal(state: SemanticTerminalState, phase: "completed" |
|
|
|
312
323
|
}
|
|
313
324
|
}
|
|
314
325
|
|
|
326
|
+
/** EOF and post-terminal cleanup must preserve the same recovery evidence. */
|
|
327
|
+
function semanticCompletionEvidence(state: SemanticTerminalState | undefined): {
|
|
328
|
+
meaningfulOutput?: boolean;
|
|
329
|
+
} {
|
|
330
|
+
return state?.phase === "completed" ? { meaningfulOutput: state.meaningfulOutput === true } : {};
|
|
331
|
+
}
|
|
332
|
+
|
|
315
333
|
function terminalOutcomeForPhase(
|
|
316
334
|
phase: CodexModelRequestEvent["phase"],
|
|
317
335
|
): RequestTerminalOutcome | null {
|
|
@@ -426,6 +444,7 @@ async function observedResponse(
|
|
|
426
444
|
if (semanticTerminal) markSemanticTerminal(semanticTerminal, "failed");
|
|
427
445
|
await emitRequestEvent(audit, {
|
|
428
446
|
phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
|
|
447
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
429
448
|
responseObserved: true,
|
|
430
449
|
status: res.status,
|
|
431
450
|
...(requestId ? { providerRequestId: requestId } : {}),
|
|
@@ -459,6 +478,7 @@ async function observedResponse(
|
|
|
459
478
|
void reader.cancel(error).catch(() => undefined);
|
|
460
479
|
void emitRequestEvent(audit, {
|
|
461
480
|
phase,
|
|
481
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
462
482
|
responseObserved: true,
|
|
463
483
|
...(phase === "timed_out" ? { timeoutClass: klass } : {}),
|
|
464
484
|
status: res.status,
|
|
@@ -486,6 +506,7 @@ async function observedResponse(
|
|
|
486
506
|
void reader.cancel(reason).catch(() => undefined);
|
|
487
507
|
void emitRequestEvent(audit, {
|
|
488
508
|
phase: semanticTerminal?.phase ?? "failed",
|
|
509
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
489
510
|
responseObserved: true,
|
|
490
511
|
status: res.status,
|
|
491
512
|
...(requestId ? { providerRequestId: requestId } : {}),
|
|
@@ -512,14 +533,12 @@ async function observedResponse(
|
|
|
512
533
|
if (chunk.done) {
|
|
513
534
|
terminal = true;
|
|
514
535
|
clearTimers();
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
if (!semanticTerminal?.deferTransportTerminal || semanticTerminal.phase !== null) {
|
|
536
|
+
// The SSE parser owns EOF classification: it may still have a final
|
|
537
|
+
// terminal block buffered without a blank separator.
|
|
538
|
+
if (!semanticTerminal || semanticTerminal.phase !== null) {
|
|
521
539
|
await emitRequestEvent(audit, {
|
|
522
540
|
phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
|
|
541
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
523
542
|
responseObserved: true,
|
|
524
543
|
status: res.status,
|
|
525
544
|
...(requestId ? { providerRequestId: requestId } : {}),
|
|
@@ -555,6 +574,7 @@ async function observedResponse(
|
|
|
555
574
|
}
|
|
556
575
|
await emitRequestEvent(audit, {
|
|
557
576
|
phase: semanticPhase ?? "failed",
|
|
577
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
558
578
|
responseObserved: true,
|
|
559
579
|
status: res.status,
|
|
560
580
|
...(requestId ? { providerRequestId: requestId } : {}),
|
|
@@ -575,6 +595,7 @@ async function observedResponse(
|
|
|
575
595
|
}
|
|
576
596
|
await emitRequestEvent(audit, {
|
|
577
597
|
phase: semanticTerminal?.phase ?? "failed",
|
|
598
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
578
599
|
responseObserved: true,
|
|
579
600
|
status: res.status,
|
|
580
601
|
...(requestId ? { providerRequestId: requestId } : {}),
|
|
@@ -749,7 +770,6 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
|
|
|
749
770
|
});
|
|
750
771
|
const semanticTerminal: SemanticTerminalState = {
|
|
751
772
|
phase: null,
|
|
752
|
-
deferTransportTerminal: !callerWantsStream,
|
|
753
773
|
};
|
|
754
774
|
try {
|
|
755
775
|
await ctx.beforeProviderDispatch?.();
|
|
@@ -835,9 +855,23 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
|
|
|
835
855
|
return buffered;
|
|
836
856
|
}
|
|
837
857
|
if (callerWantsStream) {
|
|
838
|
-
res = validateCodexStream(
|
|
839
|
-
|
|
840
|
-
|
|
858
|
+
res = validateCodexStream(
|
|
859
|
+
res,
|
|
860
|
+
(phase, meaningfulOutput) => {
|
|
861
|
+
markSemanticTerminal(semanticTerminal, phase);
|
|
862
|
+
semanticTerminal.meaningfulOutput = meaningfulOutput === true;
|
|
863
|
+
},
|
|
864
|
+
async () => {
|
|
865
|
+
const upstreamRequestId = providerRequestId(res.headers);
|
|
866
|
+
await emitRequestEvent(audit, {
|
|
867
|
+
phase: semanticTerminal.phase ?? "failed",
|
|
868
|
+
...semanticCompletionEvidence(semanticTerminal),
|
|
869
|
+
responseObserved: true,
|
|
870
|
+
status: res.status,
|
|
871
|
+
...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
|
|
872
|
+
});
|
|
873
|
+
},
|
|
874
|
+
);
|
|
841
875
|
} else {
|
|
842
876
|
res = await sseToJsonResponse(res, audit, semanticTerminal);
|
|
843
877
|
}
|
|
@@ -1081,6 +1115,7 @@ async function sseToJsonResponse(
|
|
|
1081
1115
|
markSemanticTerminal(semanticTerminal, "completed");
|
|
1082
1116
|
await emitRequestEvent(audit, {
|
|
1083
1117
|
phase: "completed",
|
|
1118
|
+
meaningfulOutput: hasMeaningfulCodexOutput(final?.output),
|
|
1084
1119
|
responseObserved: true,
|
|
1085
1120
|
status: res.status,
|
|
1086
1121
|
...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
|
|
@@ -1362,7 +1397,8 @@ function codexSseFailureError(
|
|
|
1362
1397
|
*/
|
|
1363
1398
|
function validateCodexStream(
|
|
1364
1399
|
res: Response,
|
|
1365
|
-
onSemanticTerminal?: (phase: "completed" | "failed") => void,
|
|
1400
|
+
onSemanticTerminal?: (phase: "completed" | "failed", meaningfulOutput?: boolean) => void,
|
|
1401
|
+
onParsedEof?: () => Promise<void>,
|
|
1366
1402
|
): Response {
|
|
1367
1403
|
if (!res.body) {
|
|
1368
1404
|
onSemanticTerminal?.("failed");
|
|
@@ -1389,6 +1425,12 @@ function validateCodexStream(
|
|
|
1389
1425
|
const encoder = new TextEncoder();
|
|
1390
1426
|
let buffer = "";
|
|
1391
1427
|
let successfulTerminalSeen = false;
|
|
1428
|
+
let meaningfulOutput = false;
|
|
1429
|
+
const observeOutput = (output: unknown) => {
|
|
1430
|
+
meaningfulOutput ||= hasMeaningfulCodexOutput(output);
|
|
1431
|
+
};
|
|
1432
|
+
const observeTerminal = (phase: "completed" | "failed") =>
|
|
1433
|
+
onSemanticTerminal?.(phase, phase === "completed" && meaningfulOutput);
|
|
1392
1434
|
const emitCompleteBlocks = (
|
|
1393
1435
|
controller: TransformStreamDefaultController<Uint8Array>,
|
|
1394
1436
|
final: boolean,
|
|
@@ -1398,7 +1440,7 @@ function validateCodexStream(
|
|
|
1398
1440
|
const block = buffer.slice(0, boundary.start);
|
|
1399
1441
|
const separator = buffer.slice(boundary.start, boundary.end);
|
|
1400
1442
|
buffer = buffer.slice(boundary.end);
|
|
1401
|
-
successfulTerminalSeen ||= inspectCodexSseBlock(block, res,
|
|
1443
|
+
successfulTerminalSeen ||= inspectCodexSseBlock(block, res, observeTerminal, observeOutput);
|
|
1402
1444
|
controller.enqueue(encoder.encode(`${block}${separator}`));
|
|
1403
1445
|
boundary = findSseBlockBoundary(buffer, final);
|
|
1404
1446
|
}
|
|
@@ -1408,21 +1450,34 @@ function validateCodexStream(
|
|
|
1408
1450
|
buffer += decoder.decode(chunk, { stream: true });
|
|
1409
1451
|
emitCompleteBlocks(controller, false);
|
|
1410
1452
|
},
|
|
1411
|
-
flush(controller) {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
"
|
|
1424
|
-
|
|
1425
|
-
)
|
|
1453
|
+
async flush(controller) {
|
|
1454
|
+
try {
|
|
1455
|
+
buffer += decoder.decode();
|
|
1456
|
+
emitCompleteBlocks(controller, true);
|
|
1457
|
+
if (buffer.length > 0) {
|
|
1458
|
+
successfulTerminalSeen ||= inspectCodexSseBlock(
|
|
1459
|
+
buffer,
|
|
1460
|
+
res,
|
|
1461
|
+
observeTerminal,
|
|
1462
|
+
observeOutput,
|
|
1463
|
+
);
|
|
1464
|
+
controller.enqueue(encoder.encode(buffer));
|
|
1465
|
+
buffer = "";
|
|
1466
|
+
}
|
|
1467
|
+
if (!successfulTerminalSeen) {
|
|
1468
|
+
observeTerminal("failed");
|
|
1469
|
+
throw codexSseFailureError(
|
|
1470
|
+
res,
|
|
1471
|
+
null,
|
|
1472
|
+
"invalid_sse_terminal",
|
|
1473
|
+
"The Codex response stream ended without a terminal response",
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
} finally {
|
|
1477
|
+
// Raw transport EOF can precede parsing a final block without a blank
|
|
1478
|
+
// separator. Settle only after that parse; the audit fence deduplicates
|
|
1479
|
+
// an earlier terminal already observed from a complete block.
|
|
1480
|
+
await onParsedEof?.();
|
|
1426
1481
|
}
|
|
1427
1482
|
},
|
|
1428
1483
|
});
|
|
@@ -1475,14 +1530,15 @@ const CODEX_TERMINAL_TYPE_HINTS = [
|
|
|
1475
1530
|
] as const;
|
|
1476
1531
|
|
|
1477
1532
|
/**
|
|
1478
|
-
* Parse
|
|
1479
|
-
* without object allocation;
|
|
1533
|
+
* Parse terminal blocks and completed output items (retaining only a progress
|
|
1534
|
+
* boolean). Ordinary deltas pass without object allocation; failure terminals throw before the
|
|
1480
1535
|
* model can mistake them for an ordinary response_done event.
|
|
1481
1536
|
*/
|
|
1482
1537
|
function inspectCodexSseBlock(
|
|
1483
1538
|
block: string,
|
|
1484
1539
|
source: Response,
|
|
1485
1540
|
onSemanticTerminal?: (phase: "completed" | "failed") => void,
|
|
1541
|
+
onOutput?: (output: unknown) => void,
|
|
1486
1542
|
): boolean {
|
|
1487
1543
|
const lines = block.split(/\r\n|\r|\n/);
|
|
1488
1544
|
const dataStr = lines
|
|
@@ -1492,7 +1548,10 @@ function inspectCodexSseBlock(
|
|
|
1492
1548
|
if (!dataStr || dataStr === "[DONE]") {
|
|
1493
1549
|
return false;
|
|
1494
1550
|
}
|
|
1495
|
-
if (
|
|
1551
|
+
if (
|
|
1552
|
+
!dataStr.includes('"response.output_item.done"') &&
|
|
1553
|
+
!CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))
|
|
1554
|
+
) {
|
|
1496
1555
|
return false;
|
|
1497
1556
|
}
|
|
1498
1557
|
let ev: CodexSseEvent;
|
|
@@ -1502,6 +1561,7 @@ function inspectCodexSseBlock(
|
|
|
1502
1561
|
return false;
|
|
1503
1562
|
}
|
|
1504
1563
|
const terminal = classifyCodexSseTerminal(ev);
|
|
1564
|
+
if (ev.type === "response.output_item.done") onOutput?.([ev.item]);
|
|
1505
1565
|
if (terminal?.phase === "failed") {
|
|
1506
1566
|
onSemanticTerminal?.("failed");
|
|
1507
1567
|
throw codexSseFailureError(
|
|
@@ -1517,6 +1577,7 @@ function inspectCodexSseBlock(
|
|
|
1517
1577
|
);
|
|
1518
1578
|
}
|
|
1519
1579
|
if (terminal?.phase === "completed") {
|
|
1580
|
+
onOutput?.(ev.response?.output);
|
|
1520
1581
|
onSemanticTerminal?.("completed");
|
|
1521
1582
|
return true;
|
|
1522
1583
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Conservative progress evidence; reasoning/usage/empty terminal bookkeeping is not work. */
|
|
2
|
+
export function hasMeaningfulCodexOutput(output: unknown): boolean {
|
|
3
|
+
if (!Array.isArray(output)) return false;
|
|
4
|
+
return output.some((value) => {
|
|
5
|
+
if (!value || typeof value !== "object") return false;
|
|
6
|
+
const item = value as Record<string, unknown>;
|
|
7
|
+
if (item.status !== undefined && item.status !== "completed") return false;
|
|
8
|
+
if (item.type === "message" && item.role === "assistant" && Array.isArray(item.content)) {
|
|
9
|
+
return item.content.some((part: unknown) => {
|
|
10
|
+
if (!part || typeof part !== "object") return false;
|
|
11
|
+
const content = part as Record<string, unknown>;
|
|
12
|
+
return (
|
|
13
|
+
content.type === "output_text" &&
|
|
14
|
+
typeof content.text === "string" &&
|
|
15
|
+
content.text.trim().length > 0
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (item.type === "function_call" || item.type === "custom_tool_call") {
|
|
20
|
+
const payload = item.type === "function_call" ? item.arguments : item.input;
|
|
21
|
+
return (
|
|
22
|
+
typeof item.name === "string" &&
|
|
23
|
+
item.name.trim().length > 0 &&
|
|
24
|
+
typeof item.call_id === "string" &&
|
|
25
|
+
item.call_id.length > 0 &&
|
|
26
|
+
typeof payload === "string" &&
|
|
27
|
+
payload.trim().length > 0
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
item.type === "image_generation_call" &&
|
|
32
|
+
typeof item.result === "string" &&
|
|
33
|
+
item.result.length > 0
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Durable control records; only providerData is sent by the SDK unknown-item adapter. */
|
|
2
|
+
export const REASONING_CONFIGURATION_FIELD = "opengeniReasoningConfiguration";
|
|
3
|
+
export type ConfigurationEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
4
|
+
const efforts = new Set<string>(["low", "medium", "high", "xhigh", "max"]);
|
|
5
|
+
export function supportsReasoningConfiguration(
|
|
6
|
+
model: string,
|
|
7
|
+
effort: string,
|
|
8
|
+
): effort is ConfigurationEffort {
|
|
9
|
+
return model === "gpt-6-astra" && efforts.has(effort);
|
|
10
|
+
}
|
|
11
|
+
export type ReasoningConfiguration = {
|
|
12
|
+
version: 1;
|
|
13
|
+
baselineEffort: ConfigurationEffort;
|
|
14
|
+
effort: ConfigurationEffort;
|
|
15
|
+
turnId: string;
|
|
16
|
+
};
|
|
17
|
+
export function readReasoningConfiguration(
|
|
18
|
+
item: Record<string, unknown>,
|
|
19
|
+
): ReasoningConfiguration | null {
|
|
20
|
+
const state = item[REASONING_CONFIGURATION_FIELD] as ReasoningConfiguration | undefined;
|
|
21
|
+
if (
|
|
22
|
+
!state ||
|
|
23
|
+
state.version !== 1 ||
|
|
24
|
+
!efforts.has(state.baselineEffort) ||
|
|
25
|
+
!efforts.has(state.effort) ||
|
|
26
|
+
typeof state.turnId !== "string"
|
|
27
|
+
)
|
|
28
|
+
return null;
|
|
29
|
+
const data = item.providerData as Record<string, unknown> | undefined;
|
|
30
|
+
if (
|
|
31
|
+
item.type !== "unknown" ||
|
|
32
|
+
data?.type !== "configuration_update" ||
|
|
33
|
+
(data.reasoning as { effort?: unknown })?.effort !== state.effort
|
|
34
|
+
)
|
|
35
|
+
return null;
|
|
36
|
+
return state;
|
|
37
|
+
}
|
|
38
|
+
export function reasoningConfigurationItem(state: ReasoningConfiguration): Record<string, unknown> {
|
|
39
|
+
return {
|
|
40
|
+
type: "unknown",
|
|
41
|
+
providerData: { type: "configuration_update", reasoning: { effort: state.effort } },
|
|
42
|
+
[REASONING_CONFIGURATION_FIELD]: state,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function latestReasoningConfiguration(
|
|
46
|
+
items: readonly Record<string, unknown>[],
|
|
47
|
+
): ReasoningConfiguration | null {
|
|
48
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
49
|
+
const state = readReasoningConfiguration(items[i]!);
|
|
50
|
+
if (state) return state;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
export function isReasoningConfigurationUpdate(item: Record<string, unknown>): boolean {
|
|
55
|
+
return (
|
|
56
|
+
item.type === "configuration_update" ||
|
|
57
|
+
(item.type === "unknown" &&
|
|
58
|
+
(item.providerData as { type?: unknown } | undefined)?.type === "configuration_update")
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
/** Never leak Astra controls to an incompatible model. Coalesce only adjacent controls. */
|
|
62
|
+
export function projectReasoningConfigurations(
|
|
63
|
+
items: Record<string, unknown>[],
|
|
64
|
+
enabled: boolean,
|
|
65
|
+
): Record<string, unknown>[] {
|
|
66
|
+
const projected: Record<string, unknown>[] = [];
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
if (!isReasoningConfigurationUpdate(item)) {
|
|
69
|
+
projected.push(item);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!enabled) continue;
|
|
73
|
+
if (projected.length && isReasoningConfigurationUpdate(projected[projected.length - 1]!))
|
|
74
|
+
projected.pop();
|
|
75
|
+
projected.push(
|
|
76
|
+
item.type === "unknown"
|
|
77
|
+
? { type: "unknown", providerData: item.providerData }
|
|
78
|
+
: {
|
|
79
|
+
type: "unknown",
|
|
80
|
+
providerData: { type: "configuration_update", reasoning: item.reasoning },
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return projected;
|
|
85
|
+
}
|
package/src/request-context.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type CodexModelRequestEvent = {
|
|
|
60
60
|
providerRequestId?: string;
|
|
61
61
|
status?: number;
|
|
62
62
|
willRetry?: boolean;
|
|
63
|
+
/** Only true for a successful response with substantive assistant/tool output. */
|
|
64
|
+
meaningfulOutput?: boolean;
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
export type CodexRequestOpaqueArtifacts = {
|