@gajae-code/ai 0.18.6 → 0.18.7
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 +25 -0
- package/dist/types/providers/openai-codex-responses.d.ts +1 -0
- package/dist/types/types.d.ts +15 -0
- package/dist/types/utils/idle-iterator.d.ts +2 -0
- package/package.json +3 -3
- package/src/providers/google-gemini-cli.ts +49 -3
- package/src/providers/kiro-codewhisperer.ts +77 -59
- package/src/providers/openai-codex-responses.d.ts +1 -0
- package/src/providers/openai-codex-responses.ts +204 -54
- package/src/providers/openai-opencodex-responses.ts +6 -1
- package/src/providers/openai-responses.ts +1 -1
- package/src/providers/register-builtins.ts +53 -5
- package/src/types.d.ts +15 -0
- package/src/types.ts +16 -0
- package/src/utils/idle-iterator.d.ts +2 -0
- package/src/utils/idle-iterator.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.18.7] - 2026-10-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Explicitly emit `strict: false` for non-strict OpenAI Responses and Codex Responses function tools.
|
|
10
|
+
|
|
11
|
+
- Salvage finalized Codex tool calls when an empty assistant message item opens before a transient stream close, while still failing closed on non-empty text.
|
|
12
|
+
|
|
13
|
+
- Preserve finalized Codex tool calls when the stream ends without a terminal event, including an empty trailing message; unfinished calls and visible text still fail closed.
|
|
14
|
+
|
|
15
|
+
- Add Codex request-timeout salvage refusal diagnostics and replay coverage for complete `todo_write` calls.
|
|
16
|
+
|
|
17
|
+
- Salvage complete Codex function calls when the SSE or WebSocket idle watchdog ends a stalled stream.
|
|
18
|
+
|
|
19
|
+
- **Kiro OAuth CodeWhisperer wire protocol**: Corrected critical wire protocol issues in the streaming transport:
|
|
20
|
+
- Fixed typo in request header `amzn-X-amz-target` → `x-amz-target`
|
|
21
|
+
- Changed streaming service target from `AmazonCodeWhispererService.GenerateAssistantResponse` to `AmazonCodeWhispererStreamingService.GenerateAssistantResponse`
|
|
22
|
+
- Fixed content-type from `application/json` to `application/x-amz-json-1.0`
|
|
23
|
+
- Moved `profileArn` from header to request body `conversationState`
|
|
24
|
+
- Flattened `userInputMessageContext.tools` from nested `{tools: [...]}` to direct array
|
|
25
|
+
- Fixed event payload handling for flat `{content}` structure (not nested `{assistantResponseEvent: {content}}`)
|
|
26
|
+
- Implemented accumulation of streaming tool input fragments per `toolUseId` until `stop` signal
|
|
27
|
+
- Cross-verified against kiro-api-key.ts headers implementation
|
|
28
|
+
- Reported by: nomo via wire protocol validation failures
|
|
29
|
+
|
|
5
30
|
## [0.18.6] - 2026-10-03
|
|
6
31
|
|
|
7
32
|
### Added
|
|
@@ -77,3 +77,4 @@ type CodexToolPayload = {
|
|
|
77
77
|
/** @internal Exported for tests. */
|
|
78
78
|
export declare function convertOpenAICodexResponsesTools(tools: Tool[], model: Model<"openai-codex-responses">): CodexToolPayload[];
|
|
79
79
|
declare function isRetryableCodexFailureEvent(rawEvent: Record<string, unknown>): boolean;
|
|
80
|
+
export declare function isExplicitCodexTerminalVeto(code?: string, providerMessage?: string): boolean;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -640,6 +640,8 @@ export interface AssistantMessage {
|
|
|
640
640
|
* supplied no trustworthy structured evidence.
|
|
641
641
|
*/
|
|
642
642
|
providerDiagnostic?: ProviderDiagnostic;
|
|
643
|
+
/** Redacted first-event transport timing and chunk-shape diagnostics for Google Gemini CLI. */
|
|
644
|
+
googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
|
|
643
645
|
/** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
|
|
644
646
|
transportFailure?: TransportFailureFacts;
|
|
645
647
|
/**
|
|
@@ -658,6 +660,19 @@ export interface AssistantMessage {
|
|
|
658
660
|
/** Prompt-prefix fingerprint of the request that produced this message. */
|
|
659
661
|
promptPrefix?: PromptPrefixTelemetry;
|
|
660
662
|
}
|
|
663
|
+
export interface GoogleGeminiCliDiagnostics {
|
|
664
|
+
responseAtMs?: number;
|
|
665
|
+
firstRawSseAtMs?: number;
|
|
666
|
+
chunkCounts: {
|
|
667
|
+
content: number;
|
|
668
|
+
thinking: number;
|
|
669
|
+
functionCall: number;
|
|
670
|
+
usageOnly: number;
|
|
671
|
+
other: number;
|
|
672
|
+
};
|
|
673
|
+
firstEventTimeoutMs?: number;
|
|
674
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
675
|
+
}
|
|
661
676
|
export interface ToolResultMessage<TDetails = any> {
|
|
662
677
|
role: "toolResult";
|
|
663
678
|
toolCallId: string;
|
|
@@ -72,6 +72,7 @@ export interface FirstEventTimeoutFacts {
|
|
|
72
72
|
requestBytes?: number;
|
|
73
73
|
firstEventElapsedMs?: number;
|
|
74
74
|
firstEventTimeoutMs?: number;
|
|
75
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
75
76
|
endpointClass?: "canonical" | "custom";
|
|
76
77
|
retryMaxAttempts?: number;
|
|
77
78
|
}
|
|
@@ -80,6 +81,7 @@ export declare class FirstEventTimeoutError extends Error {
|
|
|
80
81
|
readonly requestBytes?: number;
|
|
81
82
|
readonly firstEventElapsedMs?: number;
|
|
82
83
|
readonly firstEventTimeoutMs?: number;
|
|
84
|
+
readonly firstEventTimeoutSource?: FirstEventTimeoutFacts["firstEventTimeoutSource"];
|
|
83
85
|
readonly endpointClass?: "canonical" | "custom";
|
|
84
86
|
readonly retryMaxAttempts?: number;
|
|
85
87
|
constructor(message: string, facts?: FirstEventTimeoutFacts);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.18.
|
|
4
|
+
"version": "0.18.7",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
42
42
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
43
43
|
"@bufbuild/protobuf": "^2.12.0",
|
|
44
|
-
"@gajae-code/natives": "0.18.
|
|
45
|
-
"@gajae-code/utils": "0.18.
|
|
44
|
+
"@gajae-code/natives": "0.18.7",
|
|
45
|
+
"@gajae-code/utils": "0.18.7",
|
|
46
46
|
"openai": "^6.36.0",
|
|
47
47
|
"partial-json": "^0.1.7",
|
|
48
48
|
"zod": "4.4.3"
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
Api,
|
|
17
17
|
AssistantMessage,
|
|
18
18
|
Context,
|
|
19
|
+
GoogleGeminiCliDiagnostics,
|
|
19
20
|
Model,
|
|
20
21
|
StreamFunction,
|
|
21
22
|
StreamOptions,
|
|
@@ -27,6 +28,7 @@ import { normalizeSystemPrompts } from "../utils";
|
|
|
27
28
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
28
29
|
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
29
30
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
31
|
+
import { FirstEventTimeoutError } from "../utils/idle-iterator";
|
|
30
32
|
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
31
33
|
// Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
|
|
32
34
|
// the stream provider trusts the access token threaded through `options.apiKey`.
|
|
@@ -325,6 +327,29 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
325
327
|
timestamp: Date.now(),
|
|
326
328
|
};
|
|
327
329
|
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
330
|
+
const googleGeminiCliDiagnostics: GoogleGeminiCliDiagnostics = {
|
|
331
|
+
chunkCounts: { content: 0, thinking: 0, functionCall: 0, usageOnly: 0, other: 0 },
|
|
332
|
+
};
|
|
333
|
+
(
|
|
334
|
+
stream as AssistantMessageEventStream & { googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics }
|
|
335
|
+
).googleGeminiCliDiagnostics = googleGeminiCliDiagnostics;
|
|
336
|
+
const recordTimeoutDiagnostics = (): void => {
|
|
337
|
+
const reason = options?.signal?.reason;
|
|
338
|
+
if (!(reason instanceof FirstEventTimeoutError)) return;
|
|
339
|
+
googleGeminiCliDiagnostics.firstEventTimeoutMs = reason.firstEventTimeoutMs;
|
|
340
|
+
googleGeminiCliDiagnostics.firstEventTimeoutSource = reason.firstEventTimeoutSource;
|
|
341
|
+
(
|
|
342
|
+
reason as FirstEventTimeoutError & {
|
|
343
|
+
googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
|
|
344
|
+
}
|
|
345
|
+
).googleGeminiCliDiagnostics = googleGeminiCliDiagnostics;
|
|
346
|
+
};
|
|
347
|
+
options?.signal?.addEventListener("abort", recordTimeoutDiagnostics, { once: true });
|
|
348
|
+
const recordResponse = (): void => {
|
|
349
|
+
if (googleGeminiCliDiagnostics.responseAtMs === undefined) {
|
|
350
|
+
googleGeminiCliDiagnostics.responseAtMs = Date.now() - startTime;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
328
353
|
|
|
329
354
|
try {
|
|
330
355
|
const apiKeyRaw = options?.apiKey;
|
|
@@ -405,6 +430,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
405
430
|
fetch: options?.fetch,
|
|
406
431
|
},
|
|
407
432
|
);
|
|
433
|
+
recordResponse();
|
|
408
434
|
if (!response.ok && sentForcedToolChoice) {
|
|
409
435
|
const errorText = await response.text();
|
|
410
436
|
const error = createGeminiCliHttpError(response, errorText);
|
|
@@ -447,6 +473,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
447
473
|
fetch: options?.fetch,
|
|
448
474
|
},
|
|
449
475
|
);
|
|
476
|
+
recordResponse();
|
|
450
477
|
} else {
|
|
451
478
|
throw error;
|
|
452
479
|
}
|
|
@@ -496,20 +523,29 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
496
523
|
for await (const chunk of readSseJson<CloudCodeAssistResponseChunk>(
|
|
497
524
|
activeResponse.body!,
|
|
498
525
|
options?.signal,
|
|
499
|
-
event =>
|
|
526
|
+
event => {
|
|
527
|
+
if (googleGeminiCliDiagnostics.firstRawSseAtMs === undefined) {
|
|
528
|
+
googleGeminiCliDiagnostics.firstRawSseAtMs = Date.now() - startTime;
|
|
529
|
+
}
|
|
500
530
|
options?.onSseEvent?.(
|
|
501
531
|
{ event: event.event, data: event.data, raw: [...event.raw] },
|
|
502
532
|
model,
|
|
503
533
|
options?.attemptScope,
|
|
504
|
-
)
|
|
534
|
+
);
|
|
535
|
+
},
|
|
505
536
|
)) {
|
|
506
537
|
const responseData = chunk.response;
|
|
507
|
-
if (!responseData)
|
|
538
|
+
if (!responseData) {
|
|
539
|
+
googleGeminiCliDiagnostics.chunkCounts.other += 1;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
508
542
|
|
|
509
543
|
const candidate = responseData.candidates?.[0];
|
|
544
|
+
let chunkKind: keyof GoogleGeminiCliDiagnostics["chunkCounts"] = "other";
|
|
510
545
|
if (candidate?.content?.parts) {
|
|
511
546
|
for (const part of candidate.content.parts) {
|
|
512
547
|
if (part.text !== undefined) {
|
|
548
|
+
chunkKind = isThinkingPart(part) ? "thinking" : "content";
|
|
513
549
|
hasContent = true;
|
|
514
550
|
const isThinking = isThinkingPart(part);
|
|
515
551
|
if (
|
|
@@ -550,6 +586,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
550
586
|
}
|
|
551
587
|
|
|
552
588
|
if (part.functionCall) {
|
|
589
|
+
chunkKind = "functionCall";
|
|
553
590
|
hasContent = true;
|
|
554
591
|
if (currentBlock) {
|
|
555
592
|
pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
|
|
@@ -618,6 +655,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
618
655
|
}
|
|
619
656
|
|
|
620
657
|
if (responseData.usageMetadata) {
|
|
658
|
+
if (chunkKind === "other") chunkKind = "usageOnly";
|
|
621
659
|
// promptTokenCount includes cachedContentTokenCount, so subtract to get fresh input
|
|
622
660
|
const promptTokens = responseData.usageMetadata.promptTokenCount || 0;
|
|
623
661
|
const cacheReadTokens = responseData.usageMetadata.cachedContentTokenCount || 0;
|
|
@@ -639,6 +677,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
639
677
|
};
|
|
640
678
|
calculateCost(model, output.usage);
|
|
641
679
|
}
|
|
680
|
+
googleGeminiCliDiagnostics.chunkCounts[chunkKind] += 1;
|
|
642
681
|
}
|
|
643
682
|
|
|
644
683
|
if (currentBlock) {
|
|
@@ -676,6 +715,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
676
715
|
body: requestBodyJson,
|
|
677
716
|
signal: options?.signal,
|
|
678
717
|
});
|
|
718
|
+
recordResponse();
|
|
679
719
|
|
|
680
720
|
if (!currentResponse.ok) {
|
|
681
721
|
const retryErrorText = await currentResponse.text();
|
|
@@ -711,6 +751,11 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
711
751
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
712
752
|
stream.end();
|
|
713
753
|
} catch (error) {
|
|
754
|
+
const timeoutError = options?.signal?.reason;
|
|
755
|
+
if (timeoutError instanceof FirstEventTimeoutError) {
|
|
756
|
+
googleGeminiCliDiagnostics.firstEventTimeoutMs = timeoutError.firstEventTimeoutMs;
|
|
757
|
+
googleGeminiCliDiagnostics.firstEventTimeoutSource = timeoutError.firstEventTimeoutSource;
|
|
758
|
+
}
|
|
714
759
|
for (const block of output.content) {
|
|
715
760
|
if ("index" in block) {
|
|
716
761
|
delete (block as { index?: number }).index;
|
|
@@ -719,6 +764,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
719
764
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
720
765
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
721
766
|
output.transportFailure = transportFailureFacts(error);
|
|
767
|
+
output.googleGeminiCliDiagnostics = googleGeminiCliDiagnostics;
|
|
722
768
|
output.errorMessage = await appendRawHttpRequestDumpFor400(
|
|
723
769
|
error instanceof Error ? error.message : JSON.stringify(error),
|
|
724
770
|
error,
|
|
@@ -74,7 +74,7 @@ interface WireUserMessage {
|
|
|
74
74
|
content: string;
|
|
75
75
|
modelId?: string;
|
|
76
76
|
userInputMessageContext?: {
|
|
77
|
-
tools?:
|
|
77
|
+
tools?: WireToolSpec[];
|
|
78
78
|
toolResults?: WireToolResult[];
|
|
79
79
|
editorStateContext?: Record<string, unknown>;
|
|
80
80
|
};
|
|
@@ -108,25 +108,19 @@ interface GenerateAssistantResponseRequest {
|
|
|
108
108
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
109
109
|
|
|
110
110
|
interface AssistantResponseEvent {
|
|
111
|
-
|
|
112
|
-
content?: string;
|
|
113
|
-
};
|
|
111
|
+
content?: string;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
interface ToolUseEventPayload {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
stop?: { stopReason?: string };
|
|
122
|
-
};
|
|
115
|
+
toolUseId?: string;
|
|
116
|
+
name?: string;
|
|
117
|
+
input?: string;
|
|
118
|
+
stop?: boolean;
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
interface MessageMetadataEvent {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
utteranceId?: string;
|
|
129
|
-
};
|
|
122
|
+
conversationId?: string;
|
|
123
|
+
utteranceId?: string;
|
|
130
124
|
}
|
|
131
125
|
|
|
132
126
|
interface ErrorPayload {
|
|
@@ -169,6 +163,8 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
169
163
|
(async () => {
|
|
170
164
|
const startTime = Date.now();
|
|
171
165
|
let firstTokenTime: number | undefined;
|
|
166
|
+
// Accumulator for streaming tool input fragments, keyed by toolUseId
|
|
167
|
+
const toolInputAccumulator = new Map<string, { name: string; input: string }>();
|
|
172
168
|
|
|
173
169
|
const output: AssistantMessage = {
|
|
174
170
|
role: "assistant",
|
|
@@ -190,6 +186,7 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
190
186
|
|
|
191
187
|
const blocks = output.content as Block[];
|
|
192
188
|
const region = options.region ?? $env.KIRO_REGION ?? $env.AWS_REGION ?? $env.AWS_DEFAULT_REGION ?? DEFAULT_REGION;
|
|
189
|
+
let started = false; // Track whether start event has been emitted
|
|
193
190
|
|
|
194
191
|
try {
|
|
195
192
|
assertAwsRegionLabel(region);
|
|
@@ -222,24 +219,24 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
222
219
|
const bodyText = JSON.stringify(requestBody);
|
|
223
220
|
const body = new TextEncoder().encode(bodyText);
|
|
224
221
|
const requestHeaders: Record<string, string> = {
|
|
225
|
-
"
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"
|
|
222
|
+
"Content-Type": "application/x-amz-json-1.0",
|
|
223
|
+
Accept: "application/vnd.amazon.eventstream",
|
|
224
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
225
|
+
"X-Amz-Target": "AmazonCodeWhispererStreamingService.GenerateAssistantResponse",
|
|
229
226
|
};
|
|
230
227
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Merge user-provided headers
|
|
228
|
+
// Merge user-provided headers (case-insensitively)
|
|
229
|
+
const headersList = new Headers(requestHeaders);
|
|
236
230
|
if (options.headers) {
|
|
237
|
-
|
|
231
|
+
const userHeaders = new Headers(options.headers);
|
|
232
|
+
userHeaders.forEach((value, key) => {
|
|
233
|
+
headersList.set(key, value);
|
|
234
|
+
});
|
|
238
235
|
}
|
|
239
236
|
|
|
240
237
|
const response = await fetch(url, {
|
|
241
238
|
method: "POST",
|
|
242
|
-
headers:
|
|
239
|
+
headers: headersList,
|
|
243
240
|
body,
|
|
244
241
|
redirect: "error",
|
|
245
242
|
signal: options.signal,
|
|
@@ -300,11 +297,12 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
300
297
|
switch (eventType) {
|
|
301
298
|
case "assistantResponseEvent": {
|
|
302
299
|
const ev = payload as AssistantResponseEvent;
|
|
303
|
-
const content = ev.
|
|
300
|
+
const content = ev.content;
|
|
304
301
|
if (content) {
|
|
305
302
|
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
306
|
-
if (
|
|
303
|
+
if (!started) {
|
|
307
304
|
stream.push({ type: "start", partial: output });
|
|
305
|
+
started = true;
|
|
308
306
|
}
|
|
309
307
|
handleTextDelta(content, blocks, output, stream);
|
|
310
308
|
}
|
|
@@ -313,16 +311,21 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
313
311
|
case "toolUseEvent": {
|
|
314
312
|
const ev = payload as ToolUseEventPayload;
|
|
315
313
|
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
316
|
-
if (
|
|
314
|
+
if (!started) {
|
|
317
315
|
stream.push({ type: "start", partial: output });
|
|
316
|
+
started = true;
|
|
317
|
+
}
|
|
318
|
+
handleToolUseEvent(ev, blocks, output, stream, toolInputAccumulator);
|
|
319
|
+
// Clear accumulator for completed tool
|
|
320
|
+
if (ev.stop) {
|
|
321
|
+
toolInputAccumulator.delete(ev.toolUseId ?? "");
|
|
318
322
|
}
|
|
319
|
-
handleToolUseEvent(ev, blocks, output, stream);
|
|
320
323
|
break;
|
|
321
324
|
}
|
|
322
325
|
case "messageMetadataEvent": {
|
|
323
326
|
const ev = payload as MessageMetadataEvent;
|
|
324
|
-
if (ev.
|
|
325
|
-
output.responseId = ev.
|
|
327
|
+
if (ev.conversationId) {
|
|
328
|
+
output.responseId = ev.conversationId;
|
|
326
329
|
}
|
|
327
330
|
break;
|
|
328
331
|
}
|
|
@@ -349,6 +352,12 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
349
352
|
|
|
350
353
|
if (options.signal?.aborted) throw new Error("Request was aborted");
|
|
351
354
|
|
|
355
|
+
// Reject EOF while tool input is still being accumulated
|
|
356
|
+
if (toolInputAccumulator.size > 0) {
|
|
357
|
+
const unfinishedIds = Array.from(toolInputAccumulator.keys()).join(", ");
|
|
358
|
+
throw new Error(`Kiro CodeWhisperer stream ended with incomplete tool calls: ${unfinishedIds}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
352
361
|
// Finalize blocks
|
|
353
362
|
for (const block of blocks) {
|
|
354
363
|
delete block.index;
|
|
@@ -456,9 +465,7 @@ function buildConversationState(
|
|
|
456
465
|
if (!currentMessage.userInputMessage.userInputMessageContext) {
|
|
457
466
|
currentMessage.userInputMessage.userInputMessageContext = {};
|
|
458
467
|
}
|
|
459
|
-
currentMessage.userInputMessage.userInputMessageContext.tools =
|
|
460
|
-
tools: convertTools(context.tools),
|
|
461
|
-
};
|
|
468
|
+
currentMessage.userInputMessage.userInputMessageContext.tools = convertTools(context.tools);
|
|
462
469
|
}
|
|
463
470
|
|
|
464
471
|
return {
|
|
@@ -601,44 +608,55 @@ function handleToolUseEvent(
|
|
|
601
608
|
blocks: Block[],
|
|
602
609
|
output: AssistantMessage,
|
|
603
610
|
stream: AssistantMessageEventStream,
|
|
611
|
+
accumulator: Map<string, { name: string; input: string }>,
|
|
604
612
|
): void {
|
|
605
|
-
const
|
|
606
|
-
|
|
613
|
+
const name = ev.name ?? "";
|
|
614
|
+
const input = ev.input ?? "";
|
|
615
|
+
|
|
616
|
+
// Determine the tool ID: use provided ID, or find the currently active tool call
|
|
617
|
+
let toolUseId = ev.toolUseId;
|
|
618
|
+
if (!toolUseId) {
|
|
619
|
+
// ID-less fragment: associate with the currently active tool call
|
|
620
|
+
// Find the last tool call being accumulated (the most recently started tool)
|
|
621
|
+
const keys = Array.from(accumulator.keys());
|
|
622
|
+
if (keys.length > 0) {
|
|
623
|
+
toolUseId = keys[keys.length - 1];
|
|
624
|
+
} else {
|
|
625
|
+
// No active tool calls; create a placeholder entry (should rarely happen)
|
|
626
|
+
toolUseId = "";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Accumulate input fragments per toolUseId
|
|
631
|
+
let accumulated = accumulator.get(toolUseId);
|
|
632
|
+
if (!accumulated) {
|
|
633
|
+
accumulated = { name, input: "" };
|
|
634
|
+
accumulator.set(toolUseId, accumulated);
|
|
635
|
+
}
|
|
636
|
+
accumulated.input += input;
|
|
607
637
|
|
|
608
|
-
|
|
609
|
-
|
|
638
|
+
// Update name if provided and not yet set
|
|
639
|
+
if (name && !accumulated.name) {
|
|
640
|
+
accumulated.name = name;
|
|
641
|
+
}
|
|
610
642
|
|
|
611
|
-
//
|
|
612
|
-
if (
|
|
613
|
-
const inputStr =
|
|
643
|
+
// Emit toolcall_end only when we have a stop signal
|
|
644
|
+
if (ev.stop) {
|
|
645
|
+
const inputStr = accumulated.input;
|
|
614
646
|
const toolCall: ToolCall = {
|
|
615
647
|
type: "toolCall",
|
|
616
648
|
id: toolUseId,
|
|
617
|
-
name,
|
|
649
|
+
name: accumulated.name,
|
|
618
650
|
arguments: safeParseJson(inputStr) as Record<string, any>,
|
|
619
651
|
};
|
|
620
|
-
|
|
652
|
+
captureUnicodeEscapeEvidence(toolCall, inputStr);
|
|
621
653
|
|
|
622
654
|
const newBlock: Block = { ...toolCall, index: blocks.length };
|
|
623
|
-
|
|
655
|
+
captureUnicodeEscapeEvidence(newBlock, inputStr);
|
|
624
656
|
blocks.push(newBlock);
|
|
625
657
|
stream.push({ type: "toolcall_end", contentIndex: newBlock.index!, toolCall, partial: output });
|
|
626
|
-
|
|
658
|
+
accumulator.delete(toolUseId);
|
|
627
659
|
}
|
|
628
|
-
|
|
629
|
-
// Otherwise, accumulate partial input (if the service streams it in chunks)
|
|
630
|
-
// The published model does not document chunked tool input for CodeWhisperer,
|
|
631
|
-
// so this path handles the case defensively but expects complete input per event.
|
|
632
|
-
const toolCall: ToolCall = {
|
|
633
|
-
type: "toolCall",
|
|
634
|
-
id: toolUseId,
|
|
635
|
-
name,
|
|
636
|
-
arguments: {},
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const newBlock: Block = { ...toolCall, index: blocks.length };
|
|
640
|
-
blocks.push(newBlock);
|
|
641
|
-
stream.push({ type: "toolcall_end", contentIndex: newBlock.index!, toolCall, partial: output });
|
|
642
660
|
}
|
|
643
661
|
|
|
644
662
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -77,3 +77,4 @@ type CodexToolPayload = {
|
|
|
77
77
|
/** @internal Exported for tests. */
|
|
78
78
|
export declare function convertOpenAICodexResponsesTools(tools: Tool[], model: Model<"openai-codex-responses">): CodexToolPayload[];
|
|
79
79
|
declare function isRetryableCodexFailureEvent(rawEvent: Record<string, unknown>): boolean;
|
|
80
|
+
export declare function isExplicitCodexTerminalVeto(code?: string, providerMessage?: string): boolean;
|
|
@@ -190,6 +190,12 @@ const CODEX_NON_RETRYABLE_EVENT_CODES = new Set([
|
|
|
190
190
|
]);
|
|
191
191
|
const CODEX_NON_RETRYABLE_EVENT_MESSAGE =
|
|
192
192
|
/invalid[_ -]function[_ -]parameters|invalid schema for function|invalid[_ -]tool[_ -]schema|schema must have type ["']?object["']?|request blocked[^\n]*invalid[_ -]prompt|code=invalid[_ -]prompt/i;
|
|
193
|
+
const CODEX_EXPLICIT_TERMINAL_VETO_CODES = new Set([
|
|
194
|
+
"invalid_function_parameters",
|
|
195
|
+
"invalid_schema",
|
|
196
|
+
"invalid_tool_schema",
|
|
197
|
+
"invalid_prompt",
|
|
198
|
+
]);
|
|
193
199
|
const CODEX_RETRYABLE_EVENT_MESSAGE =
|
|
194
200
|
/processing your request|retry your request|temporar(?:y|ily)|overloaded|service.?unavailable|internal error|server error/i;
|
|
195
201
|
const CODEX_ACCOUNT_MODEL_UNAVAILABLE_MESSAGE = /\bnot supported when using codex with a chatgpt account\b/i;
|
|
@@ -371,6 +377,10 @@ type CodexOutputBlock =
|
|
|
371
377
|
doneInput?: string;
|
|
372
378
|
argumentsComplete?: boolean;
|
|
373
379
|
argumentsAuthoritative?: boolean;
|
|
380
|
+
sourceItemId?: string;
|
|
381
|
+
sourceCallId?: string;
|
|
382
|
+
sourceName?: string;
|
|
383
|
+
sourceOutputIndex?: number;
|
|
374
384
|
});
|
|
375
385
|
export interface OpenAICodexWebSocketDebugStats {
|
|
376
386
|
fullContextRequests: number;
|
|
@@ -470,6 +480,7 @@ interface CodexStreamRuntime {
|
|
|
470
480
|
currentItemOutputIndex?: number;
|
|
471
481
|
currentBlock: CodexOutputBlock | null;
|
|
472
482
|
nativeOutputItems: Array<Record<string, unknown>>;
|
|
483
|
+
nativeOutputItemOutputIndexes: Array<number | undefined>;
|
|
473
484
|
websocketStreamRetries: number;
|
|
474
485
|
providerRetryAttempt: number;
|
|
475
486
|
toolChoiceFallbackAttempted: boolean;
|
|
@@ -818,6 +829,10 @@ function removeTransientBlockIndices(output: AssistantMessage): void {
|
|
|
818
829
|
delete (block as { doneInput?: string }).doneInput;
|
|
819
830
|
delete (block as { argumentsComplete?: boolean }).argumentsComplete;
|
|
820
831
|
delete (block as { argumentsAuthoritative?: boolean }).argumentsAuthoritative;
|
|
832
|
+
delete (block as { sourceItemId?: string }).sourceItemId;
|
|
833
|
+
delete (block as { sourceCallId?: string }).sourceCallId;
|
|
834
|
+
delete (block as { sourceName?: string }).sourceName;
|
|
835
|
+
delete (block as { sourceOutputIndex?: number }).sourceOutputIndex;
|
|
821
836
|
}
|
|
822
837
|
}
|
|
823
838
|
}
|
|
@@ -1220,6 +1235,7 @@ function createCodexStreamRuntime(initial: {
|
|
|
1220
1235
|
currentItemOutputIndex: undefined,
|
|
1221
1236
|
currentBlock: null,
|
|
1222
1237
|
nativeOutputItems: [],
|
|
1238
|
+
nativeOutputItemOutputIndexes: [],
|
|
1223
1239
|
websocketStreamRetries: 0,
|
|
1224
1240
|
providerRetryAttempt: 0,
|
|
1225
1241
|
toolChoiceFallbackAttempted: initial.toolChoiceFallbackApplied === true,
|
|
@@ -1255,6 +1271,13 @@ async function processCodexResponseStream(
|
|
|
1255
1271
|
});
|
|
1256
1272
|
if (runtime.sawTerminalEvent) break;
|
|
1257
1273
|
}
|
|
1274
|
+
if (!runtime.sawTerminalEvent) {
|
|
1275
|
+
trySalvageCodexFinalizedToolCalls(
|
|
1276
|
+
context,
|
|
1277
|
+
runtime,
|
|
1278
|
+
new Error("Codex stream ended before terminal completion event"),
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1258
1281
|
return { firstTokenTime };
|
|
1259
1282
|
} catch (error) {
|
|
1260
1283
|
if (trySalvageCodexFinalizedToolCalls(context, runtime, error)) {
|
|
@@ -1274,38 +1297,82 @@ function trySalvageCodexFinalizedToolCalls(
|
|
|
1274
1297
|
error: unknown,
|
|
1275
1298
|
): boolean {
|
|
1276
1299
|
const toolCalls = context.output.content.filter((block): block is ToolCall => block.type === "toolCall");
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1300
|
+
const completeToolCalls = toolCalls.filter(toolCall => {
|
|
1301
|
+
const block = toolCall as ToolCall & {
|
|
1302
|
+
argumentsComplete?: boolean;
|
|
1303
|
+
argumentsAuthoritative?: boolean;
|
|
1304
|
+
sourceItemId?: string;
|
|
1305
|
+
sourceCallId?: string;
|
|
1306
|
+
};
|
|
1307
|
+
return (
|
|
1308
|
+
!runtime.finalizedToolCallIds.has(toolCall.id) &&
|
|
1309
|
+
block.argumentsComplete === true &&
|
|
1310
|
+
typeof block.sourceItemId === "string" &&
|
|
1311
|
+
typeof block.sourceCallId === "string" &&
|
|
1312
|
+
(block.argumentsAuthoritative === true || Object.keys(block.arguments).length > 0)
|
|
1313
|
+
);
|
|
1314
|
+
});
|
|
1284
1315
|
const hasCompleteArguments = toolCalls.every(
|
|
1285
|
-
toolCall =>
|
|
1286
|
-
runtime.finalizedToolCallIds.has(toolCall.id) || (toolCall === activeToolCall && completeActiveToolCall),
|
|
1316
|
+
toolCall => runtime.finalizedToolCallIds.has(toolCall.id) || completeToolCalls.includes(toolCall),
|
|
1287
1317
|
);
|
|
1288
|
-
const isIdleStall =
|
|
1289
|
-
|
|
1318
|
+
const isIdleStall = isCodexIdleStall(error);
|
|
1319
|
+
const hasEmptyActiveMessage =
|
|
1320
|
+
runtime.currentItem?.type === "message" &&
|
|
1321
|
+
runtime.currentBlock?.type === "text" &&
|
|
1322
|
+
runtime.currentItem.content.every(content =>
|
|
1323
|
+
content.type === "output_text" ? content.text === "" : content.refusal === "",
|
|
1324
|
+
);
|
|
1325
|
+
// EOF proves a closed stream, but only finalized calls are safe without a timeout event.
|
|
1326
|
+
const isUnexpectedStreamEnd =
|
|
1327
|
+
error instanceof Error && error.message === "Codex stream ended before terminal completion event";
|
|
1290
1328
|
const canSalvageFinalizedCall =
|
|
1291
|
-
isCodexTransientStreamClose(error) &&
|
|
1329
|
+
(isCodexTransientStreamClose(error) || isUnexpectedStreamEnd) &&
|
|
1292
1330
|
!runtime.toolArgumentCorrelationFailed &&
|
|
1293
1331
|
toolCalls.length > 0 &&
|
|
1294
1332
|
(runtime.currentBlock === null ||
|
|
1295
|
-
|
|
1296
|
-
|
|
1333
|
+
runtime.currentBlock?.type === "toolCall" ||
|
|
1334
|
+
(runtime.currentItem?.type === "reasoning" && runtime.currentBlock?.type === "thinking") ||
|
|
1335
|
+
hasEmptyActiveMessage) &&
|
|
1336
|
+
context.output.content.every(
|
|
1337
|
+
block =>
|
|
1338
|
+
block.type === "thinking" || block.type === "toolCall" || (block.type === "text" && block.text === ""),
|
|
1339
|
+
) &&
|
|
1297
1340
|
toolCalls.every(toolCall => runtime.finalizedToolCallIds.has(toolCall.id));
|
|
1298
1341
|
const canSalvageCompleteArguments =
|
|
1299
1342
|
(isCodexTransientStreamClose(error) || isIdleStall) &&
|
|
1300
1343
|
!runtime.toolArgumentCorrelationFailed &&
|
|
1301
1344
|
toolCalls.length > 0 &&
|
|
1302
|
-
|
|
1345
|
+
(runtime.currentBlock === null ||
|
|
1346
|
+
runtime.currentBlock?.type === "toolCall" ||
|
|
1347
|
+
(runtime.currentItem?.type === "reasoning" && runtime.currentBlock?.type === "thinking") ||
|
|
1348
|
+
hasEmptyActiveMessage) &&
|
|
1349
|
+
context.output.content.every(
|
|
1350
|
+
block =>
|
|
1351
|
+
block.type === "thinking" || block.type === "toolCall" || (block.type === "text" && block.text === ""),
|
|
1352
|
+
) &&
|
|
1303
1353
|
hasCompleteArguments;
|
|
1304
1354
|
if (!canSalvageFinalizedCall && !canSalvageCompleteArguments) {
|
|
1355
|
+
logCodexDebug("codex stream close salvage refused", {
|
|
1356
|
+
toolArgumentCorrelationFailed: runtime.toolArgumentCorrelationFailed,
|
|
1357
|
+
argumentsComplete: toolCalls.map(toolCall => {
|
|
1358
|
+
const block = toolCall as ToolCall & { argumentsComplete?: boolean };
|
|
1359
|
+
return block.argumentsComplete === true;
|
|
1360
|
+
}),
|
|
1361
|
+
sourceItemIdPresent: toolCalls.map(toolCall => {
|
|
1362
|
+
const block = toolCall as ToolCall & { sourceItemId?: string };
|
|
1363
|
+
return typeof block.sourceItemId === "string";
|
|
1364
|
+
}),
|
|
1365
|
+
sourceCallIdPresent: toolCalls.map(toolCall => {
|
|
1366
|
+
const block = toolCall as ToolCall & { sourceCallId?: string };
|
|
1367
|
+
return typeof block.sourceCallId === "string";
|
|
1368
|
+
}),
|
|
1369
|
+
currentItemType: runtime.currentItem?.type ?? null,
|
|
1370
|
+
currentBlockType: runtime.currentBlock?.type ?? null,
|
|
1371
|
+
});
|
|
1305
1372
|
return false;
|
|
1306
1373
|
}
|
|
1307
1374
|
|
|
1308
|
-
if (canSalvageFinalizedCall && runtime.currentBlock?.type === "thinking") {
|
|
1375
|
+
if ((canSalvageFinalizedCall || canSalvageCompleteArguments) && runtime.currentBlock?.type === "thinking") {
|
|
1309
1376
|
context.stream.push({
|
|
1310
1377
|
type: "thinking_end",
|
|
1311
1378
|
contentIndex: context.output.content.length - 1,
|
|
@@ -1317,33 +1384,72 @@ function trySalvageCodexFinalizedToolCalls(
|
|
|
1317
1384
|
runtime.currentBlock = null;
|
|
1318
1385
|
}
|
|
1319
1386
|
|
|
1320
|
-
if (canSalvageCompleteArguments
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1387
|
+
if (canSalvageCompleteArguments) {
|
|
1388
|
+
for (const toolCallBlock of completeToolCalls) {
|
|
1389
|
+
const activeToolCall = toolCallBlock as ToolCall & {
|
|
1390
|
+
partialJson: string;
|
|
1391
|
+
argumentsComplete?: boolean;
|
|
1392
|
+
argumentsAuthoritative?: boolean;
|
|
1393
|
+
sourceItemId?: string;
|
|
1394
|
+
sourceCallId?: string;
|
|
1395
|
+
sourceName?: string;
|
|
1396
|
+
sourceOutputIndex?: number;
|
|
1397
|
+
};
|
|
1398
|
+
const item =
|
|
1399
|
+
runtime.currentBlock === activeToolCall && runtime.currentItem?.type === "function_call"
|
|
1400
|
+
? runtime.currentItem
|
|
1401
|
+
: {
|
|
1402
|
+
type: "function_call",
|
|
1403
|
+
id: activeToolCall.sourceItemId,
|
|
1404
|
+
call_id: activeToolCall.sourceCallId,
|
|
1405
|
+
name: activeToolCall.sourceName ?? codexToolWireName(activeToolCall.name),
|
|
1406
|
+
arguments: activeToolCall.partialJson,
|
|
1407
|
+
};
|
|
1408
|
+
if (typeof item.id !== "string" || typeof item.call_id !== "string") continue;
|
|
1409
|
+
const toolCall: ToolCall = {
|
|
1410
|
+
type: "toolCall",
|
|
1411
|
+
id: activeToolCall.id,
|
|
1412
|
+
name: activeToolCall.name,
|
|
1413
|
+
arguments: activeToolCall.arguments,
|
|
1414
|
+
};
|
|
1415
|
+
const rawPartialJson = activeToolCall.partialJson;
|
|
1416
|
+
const sourceOutputIndex = activeToolCall.sourceOutputIndex;
|
|
1417
|
+
captureUnicodeEscapeEvidence(toolCall, rawPartialJson);
|
|
1418
|
+
Object.assign(activeToolCall, toolCall);
|
|
1419
|
+
captureUnicodeEscapeEvidence(activeToolCall, rawPartialJson);
|
|
1420
|
+
delete (activeToolCall as { partialJson?: string }).partialJson;
|
|
1421
|
+
delete (activeToolCall as { argumentsComplete?: boolean }).argumentsComplete;
|
|
1422
|
+
delete (activeToolCall as { argumentsAuthoritative?: boolean }).argumentsAuthoritative;
|
|
1423
|
+
delete (activeToolCall as { sourceItemId?: string }).sourceItemId;
|
|
1424
|
+
delete (activeToolCall as { sourceCallId?: string }).sourceCallId;
|
|
1425
|
+
delete (activeToolCall as { sourceName?: string }).sourceName;
|
|
1426
|
+
delete (activeToolCall as { sourceOutputIndex?: number }).sourceOutputIndex;
|
|
1427
|
+
runtime.finalizedToolCallIds.add(toolCall.id);
|
|
1428
|
+
const nativeOutputItem = {
|
|
1429
|
+
...item,
|
|
1430
|
+
name:
|
|
1431
|
+
typeof item.name === "string"
|
|
1432
|
+
? item.name
|
|
1433
|
+
: (activeToolCall.sourceName ?? codexToolWireName(activeToolCall.name)),
|
|
1434
|
+
arguments: JSON.stringify(toolCall.arguments),
|
|
1435
|
+
status: "completed",
|
|
1436
|
+
} as Record<string, unknown>;
|
|
1437
|
+
const insertionIndex =
|
|
1438
|
+
typeof sourceOutputIndex === "number"
|
|
1439
|
+
? runtime.nativeOutputItemOutputIndexes.findIndex(
|
|
1440
|
+
outputIndex => typeof outputIndex === "number" && outputIndex > sourceOutputIndex,
|
|
1441
|
+
)
|
|
1442
|
+
: -1;
|
|
1443
|
+
const resolvedInsertionIndex = insertionIndex === -1 ? runtime.nativeOutputItems.length : insertionIndex;
|
|
1444
|
+
runtime.nativeOutputItems.splice(resolvedInsertionIndex, 0, nativeOutputItem);
|
|
1445
|
+
runtime.nativeOutputItemOutputIndexes.splice(resolvedInsertionIndex, 0, sourceOutputIndex);
|
|
1446
|
+
context.stream.push({
|
|
1447
|
+
type: "toolcall_end",
|
|
1448
|
+
contentIndex: context.output.content.indexOf(activeToolCall),
|
|
1449
|
+
toolCall,
|
|
1450
|
+
partial: context.output,
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1347
1453
|
runtime.currentItem = null;
|
|
1348
1454
|
runtime.currentBlock = null;
|
|
1349
1455
|
}
|
|
@@ -1377,7 +1483,15 @@ function isCodexTransientStreamClose(error: unknown): boolean {
|
|
|
1377
1483
|
message.includes("stream disconnected before completion") ||
|
|
1378
1484
|
message.includes("stream closed before response.completed") ||
|
|
1379
1485
|
message.includes("websocket closed before response completion");
|
|
1380
|
-
return hasRequestTimeout && hasClosedStreamMessage;
|
|
1486
|
+
return (hasRequestTimeout && hasClosedStreamMessage) || isCodexIdleStall(error);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function isCodexIdleStall(error: unknown): boolean {
|
|
1490
|
+
if (!(error instanceof Error)) return false;
|
|
1491
|
+
return (
|
|
1492
|
+
error.message === "OpenAI Codex SSE stream stalled while waiting for the next event" ||
|
|
1493
|
+
error.message === `${CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX}: idle timeout waiting for websocket`
|
|
1494
|
+
);
|
|
1381
1495
|
}
|
|
1382
1496
|
|
|
1383
1497
|
function handleCodexStreamEvent(args: {
|
|
@@ -1403,6 +1517,9 @@ function handleCodexStreamEvent(args: {
|
|
|
1403
1517
|
runtime.currentItemOutputIndex = typeof rawEvent.output_index === "number" ? rawEvent.output_index : undefined;
|
|
1404
1518
|
runtime.currentBlock = createOutputBlockForItem(item);
|
|
1405
1519
|
if (!runtime.currentBlock) return firstTokenTime;
|
|
1520
|
+
if (runtime.currentBlock.type === "toolCall" && typeof runtime.currentItemOutputIndex === "number") {
|
|
1521
|
+
runtime.currentBlock.sourceOutputIndex = runtime.currentItemOutputIndex;
|
|
1522
|
+
}
|
|
1406
1523
|
const currentBlock = runtime.currentBlock;
|
|
1407
1524
|
if (
|
|
1408
1525
|
currentBlock.type === "toolCall" &&
|
|
@@ -1558,7 +1675,11 @@ function createOutputBlockForItem(item: CodexEventItem): CodexOutputBlock | null
|
|
|
1558
1675
|
const block: ToolCall & {
|
|
1559
1676
|
partialJson: string;
|
|
1560
1677
|
argumentsComplete: boolean;
|
|
1561
|
-
argumentsAuthoritative
|
|
1678
|
+
argumentsAuthoritative?: boolean;
|
|
1679
|
+
sourceItemId?: string;
|
|
1680
|
+
sourceCallId?: string;
|
|
1681
|
+
sourceName?: string;
|
|
1682
|
+
sourceOutputIndex?: number;
|
|
1562
1683
|
} = {
|
|
1563
1684
|
type: "toolCall",
|
|
1564
1685
|
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
@@ -1567,6 +1688,9 @@ function createOutputBlockForItem(item: CodexEventItem): CodexOutputBlock | null
|
|
|
1567
1688
|
partialJson: initialArguments,
|
|
1568
1689
|
argumentsComplete: hasValidInitialArguments && Object.keys(parsedArguments).length > 0,
|
|
1569
1690
|
argumentsAuthoritative: false,
|
|
1691
|
+
sourceItemId: item.id,
|
|
1692
|
+
sourceCallId: item.call_id,
|
|
1693
|
+
sourceName: item.name,
|
|
1570
1694
|
};
|
|
1571
1695
|
captureUnicodeEscapeEvidence(block, initialArguments);
|
|
1572
1696
|
return block;
|
|
@@ -1821,6 +1945,9 @@ function handleOutputItemDone(
|
|
|
1821
1945
|
): void {
|
|
1822
1946
|
const item = structuredCloneJSON(rawEvent.item) as CodexEventItem;
|
|
1823
1947
|
runtime.nativeOutputItems.push(item as unknown as Record<string, unknown>);
|
|
1948
|
+
runtime.nativeOutputItemOutputIndexes.push(
|
|
1949
|
+
typeof rawEvent.output_index === "number" ? rawEvent.output_index : undefined,
|
|
1950
|
+
);
|
|
1824
1951
|
|
|
1825
1952
|
if (item.type === "reasoning" && runtime.currentBlock?.type === "thinking") {
|
|
1826
1953
|
const block = runtime.currentBlock;
|
|
@@ -1920,6 +2047,10 @@ function handleOutputItemDone(
|
|
|
1920
2047
|
delete (runtime.currentBlock as { argumentsComplete?: boolean }).argumentsComplete;
|
|
1921
2048
|
delete (runtime.currentBlock as { argumentsAuthoritative?: boolean }).argumentsAuthoritative;
|
|
1922
2049
|
delete (runtime.currentBlock as { doneInput?: string }).doneInput;
|
|
2050
|
+
delete (runtime.currentBlock as { sourceItemId?: string }).sourceItemId;
|
|
2051
|
+
delete (runtime.currentBlock as { sourceCallId?: string }).sourceCallId;
|
|
2052
|
+
delete (runtime.currentBlock as { sourceName?: string }).sourceName;
|
|
2053
|
+
delete (runtime.currentBlock as { sourceOutputIndex?: number }).sourceOutputIndex;
|
|
1923
2054
|
runtime.canSafelyReplayWebsocketOverSse = false;
|
|
1924
2055
|
stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
|
|
1925
2056
|
runtime.currentItem = null;
|
|
@@ -2126,6 +2257,7 @@ async function tryRetryWithoutForcedToolChoice(
|
|
|
2126
2257
|
runtime.currentBlock = null;
|
|
2127
2258
|
runtime.sawTerminalEvent = false;
|
|
2128
2259
|
runtime.nativeOutputItems.length = 0;
|
|
2260
|
+
runtime.nativeOutputItemOutputIndexes.length = 0;
|
|
2129
2261
|
runtime.finalizedToolCallIds.clear();
|
|
2130
2262
|
runtime.toolArgumentCorrelationFailed = false;
|
|
2131
2263
|
resetOutputState(context.output);
|
|
@@ -2225,6 +2357,7 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
|
|
|
2225
2357
|
runtime.currentItem = null;
|
|
2226
2358
|
runtime.currentBlock = null;
|
|
2227
2359
|
runtime.nativeOutputItems.length = 0;
|
|
2360
|
+
runtime.nativeOutputItemOutputIndexes.length = 0;
|
|
2228
2361
|
runtime.finalizedToolCallIds.clear();
|
|
2229
2362
|
runtime.toolArgumentCorrelationFailed = false;
|
|
2230
2363
|
resetOutputState(context.output);
|
|
@@ -2284,6 +2417,7 @@ async function tryRecoverCodexPreviousResponseNotFound(
|
|
|
2284
2417
|
runtime.currentBlock = null;
|
|
2285
2418
|
runtime.sawTerminalEvent = false;
|
|
2286
2419
|
runtime.nativeOutputItems.length = 0;
|
|
2420
|
+
runtime.nativeOutputItemOutputIndexes.length = 0;
|
|
2287
2421
|
runtime.finalizedToolCallIds.clear();
|
|
2288
2422
|
runtime.toolArgumentCorrelationFailed = false;
|
|
2289
2423
|
resetOutputState(context.output);
|
|
@@ -2345,6 +2479,7 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
2345
2479
|
runtime.currentItem = null;
|
|
2346
2480
|
runtime.currentBlock = null;
|
|
2347
2481
|
runtime.nativeOutputItems.length = 0;
|
|
2482
|
+
runtime.nativeOutputItemOutputIndexes.length = 0;
|
|
2348
2483
|
runtime.finalizedToolCallIds.clear();
|
|
2349
2484
|
runtime.toolArgumentCorrelationFailed = false;
|
|
2350
2485
|
resetOutputState(context.output);
|
|
@@ -2389,6 +2524,7 @@ async function tryRetryCodexProviderError(
|
|
|
2389
2524
|
runtime.currentBlock = null;
|
|
2390
2525
|
runtime.sawTerminalEvent = false;
|
|
2391
2526
|
runtime.nativeOutputItems.length = 0;
|
|
2527
|
+
runtime.nativeOutputItemOutputIndexes.length = 0;
|
|
2392
2528
|
runtime.finalizedToolCallIds.clear();
|
|
2393
2529
|
runtime.toolArgumentCorrelationFailed = false;
|
|
2394
2530
|
resetOutputState(context.output);
|
|
@@ -2455,17 +2591,24 @@ async function handleCodexStreamFailure(
|
|
|
2455
2591
|
output.stopReason = context.options?.signal?.aborted ? "aborted" : "error";
|
|
2456
2592
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
2457
2593
|
const transportFailure = transportFailureFacts(error);
|
|
2594
|
+
const codexError = error instanceof CodexProviderStreamError ? error : undefined;
|
|
2595
|
+
const explicitTerminalVeto =
|
|
2596
|
+
codexError !== undefined && isExplicitCodexTerminalVeto(codexError.code, codexError.providerMessage);
|
|
2458
2597
|
const typedProviderCode =
|
|
2459
|
-
|
|
2460
|
-
?
|
|
2598
|
+
codexError !== undefined && CODEX_TYPED_TRANSPORT_PROVIDER_CODES.has(codexError.code ?? "")
|
|
2599
|
+
? codexError.code
|
|
2461
2600
|
: undefined;
|
|
2462
2601
|
output.transportFailure = typedProviderCode
|
|
2463
|
-
?
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2602
|
+
? explicitTerminalVeto
|
|
2603
|
+
? undefined
|
|
2604
|
+
: {
|
|
2605
|
+
...(transportFailure ?? { kind: "transport" as const }),
|
|
2606
|
+
providerCode: typedProviderCode,
|
|
2607
|
+
...(codexError?.deterministicVeto ? { retryMaxAttempts: 1 } : {}),
|
|
2608
|
+
}
|
|
2609
|
+
: explicitTerminalVeto
|
|
2610
|
+
? undefined
|
|
2611
|
+
: transportFailure;
|
|
2469
2612
|
output.errorMessage = await finalizeErrorMessage(error, context.requestContext.rawRequestDump);
|
|
2470
2613
|
output.duration = Date.now() - context.startTime;
|
|
2471
2614
|
if (context.firstTokenTime) {
|
|
@@ -3594,7 +3737,7 @@ export function convertOpenAICodexResponsesTools(
|
|
|
3594
3737
|
name: codexToolWireName(tool.name),
|
|
3595
3738
|
description: tool.description || "",
|
|
3596
3739
|
parameters,
|
|
3597
|
-
|
|
3740
|
+
strict: effectiveStrict,
|
|
3598
3741
|
};
|
|
3599
3742
|
});
|
|
3600
3743
|
// Tool definitions bypass the `input`/`instructions` sanitizers, so a
|
|
@@ -3673,6 +3816,13 @@ function isCodexDeterministicVeto(code: string, message: string): boolean {
|
|
|
3673
3816
|
);
|
|
3674
3817
|
}
|
|
3675
3818
|
|
|
3819
|
+
export function isExplicitCodexTerminalVeto(code?: string, providerMessage?: string): boolean {
|
|
3820
|
+
return (
|
|
3821
|
+
CODEX_EXPLICIT_TERMINAL_VETO_CODES.has(code?.toLowerCase() ?? "") ||
|
|
3822
|
+
CODEX_NON_RETRYABLE_EVENT_MESSAGE.test(providerMessage ?? "")
|
|
3823
|
+
);
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3676
3826
|
function createCodexProviderStreamError(rawEvent: Record<string, unknown>): CodexProviderStreamError {
|
|
3677
3827
|
const code = getCodexEventErrorCode(rawEvent);
|
|
3678
3828
|
const message = getCodexEventErrorMessage(rawEvent);
|
|
@@ -19,6 +19,8 @@ interface HealthPayload {
|
|
|
19
19
|
ok?: unknown;
|
|
20
20
|
pid?: unknown;
|
|
21
21
|
port?: unknown;
|
|
22
|
+
status?: unknown;
|
|
23
|
+
service?: unknown;
|
|
22
24
|
version?: unknown;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -103,7 +105,10 @@ async function fetchJson(url: string, signal?: AbortSignal): Promise<unknown> {
|
|
|
103
105
|
function isOpenCodexHealth(payload: unknown, expectedPort: number): boolean {
|
|
104
106
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
105
107
|
const health = payload as HealthPayload;
|
|
106
|
-
|
|
108
|
+
const identityOk =
|
|
109
|
+
(health.ok === true && health.version === "opencodex") ||
|
|
110
|
+
(health.status === "ok" && health.service === "opencodex");
|
|
111
|
+
return identityOk && health.port === expectedPort;
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
export async function resolveOpenCodexEndpoint(signal?: AbortSignal): Promise<OpenCodexEndpoint | undefined> {
|
|
@@ -1006,7 +1006,7 @@ export function convertTools(tools: Tool[], strictMode: boolean, model: Model<"o
|
|
|
1006
1006
|
name: tool.name,
|
|
1007
1007
|
description: tool.description || "",
|
|
1008
1008
|
parameters,
|
|
1009
|
-
|
|
1009
|
+
strict: effectiveStrict,
|
|
1010
1010
|
} as OpenAITool;
|
|
1011
1011
|
});
|
|
1012
1012
|
// Tool definitions bypass the `input`/`instructions` sanitizers, so a
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* loader: heavy SDKs stay out of the CLI startup parse graph.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { $env } from "@gajae-code/utils";
|
|
14
15
|
import type {
|
|
15
16
|
Api,
|
|
16
17
|
AssistantMessage,
|
|
@@ -280,16 +281,33 @@ function forwardStream<TApi extends Api>(
|
|
|
280
281
|
model.provider,
|
|
281
282
|
limits?.defaultFirstEventTimeoutMs,
|
|
282
283
|
);
|
|
284
|
+
const firstEventTimeoutMs =
|
|
285
|
+
options.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs, firstEventFallbackMs);
|
|
286
|
+
const firstEventTimeoutSource =
|
|
287
|
+
options.streamFirstEventTimeoutMs !== undefined
|
|
288
|
+
? "stream-option"
|
|
289
|
+
: $env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS !== undefined &&
|
|
290
|
+
Number.isFinite(Number($env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS))
|
|
291
|
+
? "env"
|
|
292
|
+
: idleTimeoutMs !== undefined &&
|
|
293
|
+
(firstEventFallbackMs === undefined || idleTimeoutMs >= firstEventFallbackMs)
|
|
294
|
+
? "idle-timeout"
|
|
295
|
+
: firstEventFallbackMs !== undefined
|
|
296
|
+
? "provider-fallback"
|
|
297
|
+
: "default";
|
|
283
298
|
watchedSource = iterateWithIdleTimeout(source, {
|
|
284
299
|
idleTimeoutMs,
|
|
285
|
-
firstItemTimeoutMs:
|
|
286
|
-
options.streamFirstEventTimeoutMs ??
|
|
287
|
-
getStreamFirstEventTimeoutMs(idleTimeoutMs, firstEventFallbackMs),
|
|
300
|
+
firstItemTimeoutMs: firstEventTimeoutMs,
|
|
288
301
|
errorMessage: LAZY_STREAM_IDLE_TIMEOUT_ERROR,
|
|
289
302
|
firstItemErrorMessage: LAZY_STREAM_FIRST_EVENT_TIMEOUT_ERROR,
|
|
290
303
|
onIdle: () => abortTracker.abortLocally(new Error(LAZY_STREAM_IDLE_TIMEOUT_ERROR)),
|
|
291
304
|
onFirstItemTimeout: () =>
|
|
292
|
-
abortTracker.abortLocally(
|
|
305
|
+
abortTracker.abortLocally(
|
|
306
|
+
new FirstEventTimeoutError(LAZY_STREAM_FIRST_EVENT_TIMEOUT_ERROR, {
|
|
307
|
+
firstEventTimeoutMs,
|
|
308
|
+
firstEventTimeoutSource,
|
|
309
|
+
}),
|
|
310
|
+
),
|
|
293
311
|
abortSignal: options.signal,
|
|
294
312
|
// Synthetic starts and tool-capability negotiation are control-plane events,
|
|
295
313
|
// not model progress. Keep the first-event window active until assistant output
|
|
@@ -313,7 +331,7 @@ function forwardStream<TApi extends Api>(
|
|
|
313
331
|
}
|
|
314
332
|
} catch (error) {
|
|
315
333
|
const stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
316
|
-
const message = createLazyLoadErrorMessage(model, error, stopReason);
|
|
334
|
+
const message = createLazyLoadErrorMessage(model, error, stopReason, source);
|
|
317
335
|
target.push({ type: "error", reason: stopReason, error: message });
|
|
318
336
|
target.end(message);
|
|
319
337
|
}
|
|
@@ -324,8 +342,37 @@ function createLazyLoadErrorMessage<TApi extends Api>(
|
|
|
324
342
|
model: Model<TApi>,
|
|
325
343
|
error: unknown,
|
|
326
344
|
stopReason: Extract<AssistantMessage["stopReason"], "aborted" | "error"> = "error",
|
|
345
|
+
source?: AsyncIterable<AssistantMessageEvent>,
|
|
327
346
|
): AssistantMessage {
|
|
328
347
|
const transportFailure = transportFailureFacts(error);
|
|
348
|
+
const carriedDiagnostics =
|
|
349
|
+
source && typeof source === "object"
|
|
350
|
+
? (source as { googleGeminiCliDiagnostics?: AssistantMessage["googleGeminiCliDiagnostics"] })
|
|
351
|
+
.googleGeminiCliDiagnostics
|
|
352
|
+
: undefined;
|
|
353
|
+
const diagnostics =
|
|
354
|
+
carriedDiagnostics ??
|
|
355
|
+
(error instanceof FirstEventTimeoutError
|
|
356
|
+
? (
|
|
357
|
+
error as FirstEventTimeoutError & {
|
|
358
|
+
googleGeminiCliDiagnostics?: AssistantMessage["googleGeminiCliDiagnostics"];
|
|
359
|
+
}
|
|
360
|
+
).googleGeminiCliDiagnostics
|
|
361
|
+
: undefined);
|
|
362
|
+
if (diagnostics && typeof error === "object" && error !== null) {
|
|
363
|
+
const timeoutFacts = error as {
|
|
364
|
+
firstEventTimeoutMs?: unknown;
|
|
365
|
+
firstEventTimeoutSource?: unknown;
|
|
366
|
+
};
|
|
367
|
+
if (typeof timeoutFacts.firstEventTimeoutMs === "number") {
|
|
368
|
+
diagnostics.firstEventTimeoutMs = timeoutFacts.firstEventTimeoutMs;
|
|
369
|
+
}
|
|
370
|
+
if (typeof timeoutFacts.firstEventTimeoutSource === "string") {
|
|
371
|
+
diagnostics.firstEventTimeoutSource = timeoutFacts.firstEventTimeoutSource as NonNullable<
|
|
372
|
+
AssistantMessage["googleGeminiCliDiagnostics"]
|
|
373
|
+
>["firstEventTimeoutSource"];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
329
376
|
return {
|
|
330
377
|
role: "assistant",
|
|
331
378
|
content: [],
|
|
@@ -344,6 +391,7 @@ function createLazyLoadErrorMessage<TApi extends Api>(
|
|
|
344
391
|
errorMessage:
|
|
345
392
|
stopReason === "aborted" ? "Request was aborted" : error instanceof Error ? error.message : String(error),
|
|
346
393
|
...(transportFailure ? { transportFailure } : {}),
|
|
394
|
+
...(diagnostics ? { googleGeminiCliDiagnostics: diagnostics } : {}),
|
|
347
395
|
timestamp: Date.now(),
|
|
348
396
|
};
|
|
349
397
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -640,6 +640,8 @@ export interface AssistantMessage {
|
|
|
640
640
|
* supplied no trustworthy structured evidence.
|
|
641
641
|
*/
|
|
642
642
|
providerDiagnostic?: ProviderDiagnostic;
|
|
643
|
+
/** Redacted first-event transport timing and chunk-shape diagnostics for Google Gemini CLI. */
|
|
644
|
+
googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
|
|
643
645
|
/** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
|
|
644
646
|
transportFailure?: TransportFailureFacts;
|
|
645
647
|
/**
|
|
@@ -658,6 +660,19 @@ export interface AssistantMessage {
|
|
|
658
660
|
/** Prompt-prefix fingerprint of the request that produced this message. */
|
|
659
661
|
promptPrefix?: PromptPrefixTelemetry;
|
|
660
662
|
}
|
|
663
|
+
export interface GoogleGeminiCliDiagnostics {
|
|
664
|
+
responseAtMs?: number;
|
|
665
|
+
firstRawSseAtMs?: number;
|
|
666
|
+
chunkCounts: {
|
|
667
|
+
content: number;
|
|
668
|
+
thinking: number;
|
|
669
|
+
functionCall: number;
|
|
670
|
+
usageOnly: number;
|
|
671
|
+
other: number;
|
|
672
|
+
};
|
|
673
|
+
firstEventTimeoutMs?: number;
|
|
674
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
675
|
+
}
|
|
661
676
|
export interface ToolResultMessage<TDetails = any> {
|
|
662
677
|
role: "toolResult";
|
|
663
678
|
toolCallId: string;
|
package/src/types.ts
CHANGED
|
@@ -881,6 +881,8 @@ export interface AssistantMessage {
|
|
|
881
881
|
* supplied no trustworthy structured evidence.
|
|
882
882
|
*/
|
|
883
883
|
providerDiagnostic?: ProviderDiagnostic;
|
|
884
|
+
/** Redacted first-event transport timing and chunk-shape diagnostics for Google Gemini CLI. */
|
|
885
|
+
googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
|
|
884
886
|
/** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
|
|
885
887
|
transportFailure?: TransportFailureFacts;
|
|
886
888
|
/**
|
|
@@ -900,6 +902,20 @@ export interface AssistantMessage {
|
|
|
900
902
|
promptPrefix?: PromptPrefixTelemetry;
|
|
901
903
|
}
|
|
902
904
|
|
|
905
|
+
export interface GoogleGeminiCliDiagnostics {
|
|
906
|
+
responseAtMs?: number;
|
|
907
|
+
firstRawSseAtMs?: number;
|
|
908
|
+
chunkCounts: {
|
|
909
|
+
content: number;
|
|
910
|
+
thinking: number;
|
|
911
|
+
functionCall: number;
|
|
912
|
+
usageOnly: number;
|
|
913
|
+
other: number;
|
|
914
|
+
};
|
|
915
|
+
firstEventTimeoutMs?: number;
|
|
916
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
917
|
+
}
|
|
918
|
+
|
|
903
919
|
export interface ToolResultMessage<TDetails = any> {
|
|
904
920
|
role: "toolResult";
|
|
905
921
|
toolCallId: string;
|
|
@@ -72,6 +72,7 @@ export interface FirstEventTimeoutFacts {
|
|
|
72
72
|
requestBytes?: number;
|
|
73
73
|
firstEventElapsedMs?: number;
|
|
74
74
|
firstEventTimeoutMs?: number;
|
|
75
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
75
76
|
endpointClass?: "canonical" | "custom";
|
|
76
77
|
retryMaxAttempts?: number;
|
|
77
78
|
}
|
|
@@ -80,6 +81,7 @@ export declare class FirstEventTimeoutError extends Error {
|
|
|
80
81
|
readonly requestBytes?: number;
|
|
81
82
|
readonly firstEventElapsedMs?: number;
|
|
82
83
|
readonly firstEventTimeoutMs?: number;
|
|
84
|
+
readonly firstEventTimeoutSource?: FirstEventTimeoutFacts["firstEventTimeoutSource"];
|
|
83
85
|
readonly endpointClass?: "canonical" | "custom";
|
|
84
86
|
readonly retryMaxAttempts?: number;
|
|
85
87
|
constructor(message: string, facts?: FirstEventTimeoutFacts);
|
|
@@ -179,6 +179,7 @@ export interface FirstEventTimeoutFacts {
|
|
|
179
179
|
requestBytes?: number;
|
|
180
180
|
firstEventElapsedMs?: number;
|
|
181
181
|
firstEventTimeoutMs?: number;
|
|
182
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
182
183
|
endpointClass?: "canonical" | "custom";
|
|
183
184
|
retryMaxAttempts?: number;
|
|
184
185
|
}
|
|
@@ -188,6 +189,7 @@ export class FirstEventTimeoutError extends Error {
|
|
|
188
189
|
readonly requestBytes?: number;
|
|
189
190
|
readonly firstEventElapsedMs?: number;
|
|
190
191
|
readonly firstEventTimeoutMs?: number;
|
|
192
|
+
readonly firstEventTimeoutSource?: FirstEventTimeoutFacts["firstEventTimeoutSource"];
|
|
191
193
|
readonly endpointClass?: "canonical" | "custom";
|
|
192
194
|
readonly retryMaxAttempts?: number;
|
|
193
195
|
|
|
@@ -197,6 +199,7 @@ export class FirstEventTimeoutError extends Error {
|
|
|
197
199
|
this.requestBytes = facts.requestBytes;
|
|
198
200
|
this.firstEventElapsedMs = facts.firstEventElapsedMs;
|
|
199
201
|
this.firstEventTimeoutMs = facts.firstEventTimeoutMs;
|
|
202
|
+
this.firstEventTimeoutSource = facts.firstEventTimeoutSource;
|
|
200
203
|
this.endpointClass = facts.endpointClass;
|
|
201
204
|
this.retryMaxAttempts = facts.retryMaxAttempts;
|
|
202
205
|
}
|