@sayknow-cli/ai 0.3.16 → 0.4.1
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/package.json +24 -25
- package/src/auth-gateway/server.ts +164 -22
- package/src/auth-storage.ts +76 -43
- package/src/context-cap-policy.ts +59 -0
- package/src/index.ts +2 -0
- package/src/model-cache.ts +10 -0
- package/src/model-manager.ts +30 -17
- package/src/model-thinking.ts +14 -8
- package/src/providers/amazon-bedrock.ts +9 -1
- package/src/providers/anthropic-messages-server.ts +178 -17
- package/src/providers/anthropic.ts +199 -213
- package/src/providers/azure-openai-responses.ts +6 -1
- package/src/providers/google-gemini-cli.ts +26 -13
- package/src/providers/google-shared.ts +7 -1
- package/src/providers/ollama.ts +7 -1
- package/src/providers/openai-chat-server.ts +93 -5
- package/src/providers/openai-codex/response-handler.ts +11 -3
- package/src/providers/openai-codex-responses.ts +107 -20
- package/src/providers/openai-responses-server.ts +111 -36
- package/src/providers/openai-responses-shared.ts +71 -12
- package/src/providers/openai-responses.ts +26 -3
- package/src/providers/pi-native-client.ts +24 -12
- package/src/providers/pi-native-server.ts +275 -21
- package/src/types.ts +53 -4
- package/src/utils/discovery/codex.ts +3 -12
- package/src/utils/event-stream.ts +146 -58
- package/src/utils/fallback-transport.ts +269 -0
- package/src/utils/overflow.ts +72 -31
- package/src/utils/retry.ts +2 -2
- package/src/utils/validation.ts +6 -1
- package/src/utils.ts +120 -12
- package/dist/types/api-registry.d.ts +0 -30
- package/dist/types/auth-broker/client.d.ts +0 -67
- package/dist/types/auth-broker/index.d.ts +0 -5
- package/dist/types/auth-broker/refresher.d.ts +0 -25
- package/dist/types/auth-broker/remote-store.d.ts +0 -99
- package/dist/types/auth-broker/server.d.ts +0 -32
- package/dist/types/auth-broker/types.d.ts +0 -110
- package/dist/types/auth-broker/wire-schemas.d.ts +0 -443
- package/dist/types/auth-gateway/http.d.ts +0 -40
- package/dist/types/auth-gateway/index.d.ts +0 -3
- package/dist/types/auth-gateway/server.d.ts +0 -17
- package/dist/types/auth-gateway/types.d.ts +0 -115
- package/dist/types/auth-storage.d.ts +0 -695
- package/dist/types/cli.d.ts +0 -2
- package/dist/types/index.d.ts +0 -51
- package/dist/types/model-cache.d.ts +0 -17
- package/dist/types/model-manager.d.ts +0 -62
- package/dist/types/model-retirements.d.ts +0 -6
- package/dist/types/model-thinking.d.ts +0 -74
- package/dist/types/models.d.ts +0 -21
- package/dist/types/provider-details.d.ts +0 -24
- package/dist/types/provider-models/bundled-references.d.ts +0 -4
- package/dist/types/provider-models/descriptors.d.ts +0 -48
- package/dist/types/provider-models/google.d.ts +0 -20
- package/dist/types/provider-models/index.d.ts +0 -5
- package/dist/types/provider-models/ollama.d.ts +0 -7
- package/dist/types/provider-models/openai-compat.d.ts +0 -256
- package/dist/types/provider-models/special.d.ts +0 -19
- package/dist/types/providers/amazon-bedrock.d.ts +0 -60
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -450
- package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
- package/dist/types/providers/anthropic.d.ts +0 -208
- package/dist/types/providers/aws-credential-config.d.ts +0 -19
- package/dist/types/providers/aws-credentials.d.ts +0 -43
- package/dist/types/providers/aws-eventstream.d.ts +0 -38
- package/dist/types/providers/aws-sigv4.d.ts +0 -55
- package/dist/types/providers/azure-openai-responses.d.ts +0 -15
- package/dist/types/providers/composer-discipline.d.ts +0 -26
- package/dist/types/providers/cursor/client-version.d.ts +0 -10
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +0 -13022
- package/dist/types/providers/cursor.d.ts +0 -45
- package/dist/types/providers/error-message.d.ts +0 -27
- package/dist/types/providers/github-copilot-headers.d.ts +0 -40
- package/dist/types/providers/gitlab-duo.d.ts +0 -27
- package/dist/types/providers/google-auth.d.ts +0 -24
- package/dist/types/providers/google-gemini-cli.d.ts +0 -75
- package/dist/types/providers/google-gemini-headers.d.ts +0 -43
- package/dist/types/providers/google-shared.d.ts +0 -179
- package/dist/types/providers/google-types.d.ts +0 -138
- package/dist/types/providers/google-vertex.d.ts +0 -7
- package/dist/types/providers/google.d.ts +0 -4
- package/dist/types/providers/grammar.d.ts +0 -1
- package/dist/types/providers/kimi.d.ts +0 -27
- package/dist/types/providers/mock.d.ts +0 -177
- package/dist/types/providers/ollama.d.ts +0 -41
- package/dist/types/providers/openai-anthropic-shim.d.ts +0 -31
- package/dist/types/providers/openai-bounded-rate-limits.d.ts +0 -3
- package/dist/types/providers/openai-chat-server-schema.d.ts +0 -815
- package/dist/types/providers/openai-chat-server.d.ts +0 -16
- package/dist/types/providers/openai-codex/constants.d.ts +0 -26
- package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -50
- package/dist/types/providers/openai-codex/response-handler.d.ts +0 -17
- package/dist/types/providers/openai-codex-responses.d.ts +0 -67
- package/dist/types/providers/openai-completions-compat.d.ts +0 -27
- package/dist/types/providers/openai-completions.d.ts +0 -33
- package/dist/types/providers/openai-request-transform.d.ts +0 -4
- package/dist/types/providers/openai-responses-server-schema.d.ts +0 -392
- package/dist/types/providers/openai-responses-server.d.ts +0 -17
- package/dist/types/providers/openai-responses-shared.d.ts +0 -104
- package/dist/types/providers/openai-responses.d.ts +0 -32
- package/dist/types/providers/pi-native-client.d.ts +0 -13
- package/dist/types/providers/pi-native-server.d.ts +0 -68
- package/dist/types/providers/register-builtins.d.ts +0 -31
- package/dist/types/providers/synthetic.d.ts +0 -26
- package/dist/types/providers/transform-messages.d.ts +0 -14
- package/dist/types/providers/vision-guard.d.ts +0 -8
- package/dist/types/rate-limit-utils.d.ts +0 -19
- package/dist/types/stream.d.ts +0 -43
- package/dist/types/types.d.ts +0 -831
- package/dist/types/usage/claude.d.ts +0 -3
- package/dist/types/usage/gemini.d.ts +0 -2
- package/dist/types/usage/github-copilot.d.ts +0 -7
- package/dist/types/usage/google-antigravity.d.ts +0 -2
- package/dist/types/usage/grok-cli.d.ts +0 -10
- package/dist/types/usage/kimi.d.ts +0 -2
- package/dist/types/usage/minimax-code.d.ts +0 -2
- package/dist/types/usage/openai-codex.d.ts +0 -3
- package/dist/types/usage/shared.d.ts +0 -1
- package/dist/types/usage/zai.d.ts +0 -2
- package/dist/types/usage.d.ts +0 -258
- package/dist/types/utils/abort.d.ts +0 -19
- package/dist/types/utils/anthropic-auth.d.ts +0 -31
- package/dist/types/utils/discovery/antigravity.d.ts +0 -67
- package/dist/types/utils/discovery/codex.d.ts +0 -38
- package/dist/types/utils/discovery/cursor.d.ts +0 -23
- package/dist/types/utils/discovery/gemini.d.ts +0 -25
- package/dist/types/utils/discovery/index.d.ts +0 -4
- package/dist/types/utils/discovery/openai-compatible.d.ts +0 -74
- package/dist/types/utils/event-stream.d.ts +0 -33
- package/dist/types/utils/fireworks-model-id.d.ts +0 -10
- package/dist/types/utils/foundry.d.ts +0 -1
- package/dist/types/utils/h2-fetch.d.ts +0 -22
- package/dist/types/utils/http-inspector.d.ts +0 -35
- package/dist/types/utils/idle-iterator.d.ts +0 -67
- package/dist/types/utils/json-parse.d.ts +0 -18
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
- package/dist/types/utils/oauth/anthropic.d.ts +0 -22
- package/dist/types/utils/oauth/api-key-login.d.ts +0 -35
- package/dist/types/utils/oauth/api-key-validation.d.ts +0 -27
- package/dist/types/utils/oauth/callback-server.d.ts +0 -60
- package/dist/types/utils/oauth/cerebras.d.ts +0 -1
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/cursor.d.ts +0 -15
- package/dist/types/utils/oauth/deepinfra.d.ts +0 -1
- package/dist/types/utils/oauth/deepseek.d.ts +0 -10
- package/dist/types/utils/oauth/firepass.d.ts +0 -1
- package/dist/types/utils/oauth/fireworks.d.ts +0 -1
- package/dist/types/utils/oauth/fugu.d.ts +0 -1
- package/dist/types/utils/oauth/github-copilot.d.ts +0 -38
- package/dist/types/utils/oauth/gitlab-duo.d.ts +0 -3
- package/dist/types/utils/oauth/glm-zcode.d.ts +0 -71
- package/dist/types/utils/oauth/google-antigravity.d.ts +0 -11
- package/dist/types/utils/oauth/google-gemini-cli.d.ts +0 -10
- package/dist/types/utils/oauth/google-oauth-shared.d.ts +0 -28
- package/dist/types/utils/oauth/huggingface.d.ts +0 -19
- package/dist/types/utils/oauth/index.d.ts +0 -39
- package/dist/types/utils/oauth/kagi.d.ts +0 -17
- package/dist/types/utils/oauth/kilo.d.ts +0 -5
- package/dist/types/utils/oauth/kimi.d.ts +0 -21
- package/dist/types/utils/oauth/litellm.d.ts +0 -18
- package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
- package/dist/types/utils/oauth/minimax-code.d.ts +0 -28
- package/dist/types/utils/oauth/moonshot.d.ts +0 -1
- package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
- package/dist/types/utils/oauth/nvidia.d.ts +0 -18
- package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
- package/dist/types/utils/oauth/ollama.d.ts +0 -18
- package/dist/types/utils/oauth/openai-codex.d.ts +0 -21
- package/dist/types/utils/oauth/opencode.d.ts +0 -18
- package/dist/types/utils/oauth/parallel.d.ts +0 -17
- package/dist/types/utils/oauth/perplexity.d.ts +0 -9
- package/dist/types/utils/oauth/pkce.d.ts +0 -8
- package/dist/types/utils/oauth/qianfan.d.ts +0 -17
- package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
- package/dist/types/utils/oauth/synthetic.d.ts +0 -1
- package/dist/types/utils/oauth/tavily.d.ts +0 -17
- package/dist/types/utils/oauth/together.d.ts +0 -1
- package/dist/types/utils/oauth/types.d.ts +0 -45
- package/dist/types/utils/oauth/venice.d.ts +0 -18
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/vllm.d.ts +0 -16
- package/dist/types/utils/oauth/xai.d.ts +0 -30
- package/dist/types/utils/oauth/xiaomi.d.ts +0 -25
- package/dist/types/utils/oauth/zai.d.ts +0 -18
- package/dist/types/utils/oauth/zenmux.d.ts +0 -1
- package/dist/types/utils/overflow.d.ts +0 -64
- package/dist/types/utils/parse-bind.d.ts +0 -23
- package/dist/types/utils/provider-response.d.ts +0 -3
- package/dist/types/utils/retry-after.d.ts +0 -3
- package/dist/types/utils/retry-budget.d.ts +0 -1
- package/dist/types/utils/retry.d.ts +0 -26
- package/dist/types/utils/schema/adapt.d.ts +0 -24
- package/dist/types/utils/schema/compatibility.d.ts +0 -30
- package/dist/types/utils/schema/dereference.d.ts +0 -11
- package/dist/types/utils/schema/draft.d.ts +0 -10
- package/dist/types/utils/schema/equality.d.ts +0 -4
- package/dist/types/utils/schema/fields.d.ts +0 -49
- package/dist/types/utils/schema/index.d.ts +0 -14
- package/dist/types/utils/schema/json-schema-validator.d.ts +0 -12
- package/dist/types/utils/schema/meta-validator.d.ts +0 -2
- package/dist/types/utils/schema/normalize.d.ts +0 -93
- package/dist/types/utils/schema/root-combinator.d.ts +0 -12
- package/dist/types/utils/schema/spill.d.ts +0 -8
- package/dist/types/utils/schema/stamps.d.ts +0 -25
- package/dist/types/utils/schema/types.d.ts +0 -4
- package/dist/types/utils/schema/wire.d.ts +0 -54
- package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
- package/dist/types/utils/sse-debug.d.ts +0 -10
- package/dist/types/utils/tool-call-healing.d.ts +0 -71
- package/dist/types/utils/tool-choice-capability.d.ts +0 -41
- package/dist/types/utils/tool-choice.d.ts +0 -50
- package/dist/types/utils/validation.d.ts +0 -17
- package/dist/types/utils.d.ts +0 -35
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
} from "../types";
|
|
21
21
|
import { normalizeSystemPrompts } from "../utils";
|
|
22
22
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
23
|
+
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
23
24
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
24
25
|
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
25
26
|
// Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
|
|
@@ -130,6 +131,22 @@ function extractErrorMessage(errorText: string): string {
|
|
|
130
131
|
return errorText;
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
function createGeminiCliHttpError(response: Response, errorText: string, formatErrorMessage = true): Error {
|
|
135
|
+
const message = formatErrorMessage ? extractErrorMessage(errorText) : errorText;
|
|
136
|
+
const error = withHttpStatus(
|
|
137
|
+
new Error(`Cloud Code Assist API error (${response.status}): ${message}`),
|
|
138
|
+
response.status,
|
|
139
|
+
) as Error & { code?: string; headers?: Headers };
|
|
140
|
+
error.headers = response.headers;
|
|
141
|
+
try {
|
|
142
|
+
const code = (JSON.parse(errorText) as { error?: { code?: unknown } }).error?.code;
|
|
143
|
+
if (typeof code === "string") error.code = code;
|
|
144
|
+
} catch {
|
|
145
|
+
// The response body is not JSON.
|
|
146
|
+
}
|
|
147
|
+
return error;
|
|
148
|
+
}
|
|
149
|
+
|
|
133
150
|
interface GeminiCliApiKeyPayload {
|
|
134
151
|
token?: unknown;
|
|
135
152
|
projectId?: unknown;
|
|
@@ -380,11 +397,12 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
380
397
|
);
|
|
381
398
|
if (!response.ok && sentForcedToolChoice) {
|
|
382
399
|
const errorText = await response.text();
|
|
383
|
-
const error =
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
const error = createGeminiCliHttpError(response, errorText);
|
|
401
|
+
if (
|
|
402
|
+
!options?.fallbackManaged &&
|
|
403
|
+
firstTokenTime === undefined &&
|
|
404
|
+
isForcedToolChoiceUnsupportedError(error, true)
|
|
405
|
+
) {
|
|
388
406
|
const beforeMark = resolveToolChoice(model, options?.toolChoice);
|
|
389
407
|
markToolChoiceIncapability(model, "auto", error.message);
|
|
390
408
|
stream.push({
|
|
@@ -423,10 +441,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
423
441
|
}
|
|
424
442
|
if (!response.ok) {
|
|
425
443
|
const errorText = await response.text();
|
|
426
|
-
throw
|
|
427
|
-
new Error(`Cloud Code Assist API error (${response.status}): ${extractErrorMessage(errorText)}`),
|
|
428
|
-
response.status,
|
|
429
|
-
);
|
|
444
|
+
throw createGeminiCliHttpError(response, errorText);
|
|
430
445
|
}
|
|
431
446
|
const requestUrl = response.url;
|
|
432
447
|
|
|
@@ -629,10 +644,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
629
644
|
|
|
630
645
|
if (!currentResponse.ok) {
|
|
631
646
|
const retryErrorText = await currentResponse.text();
|
|
632
|
-
throw
|
|
633
|
-
new Error(`Cloud Code Assist API error (${currentResponse.status}): ${retryErrorText}`),
|
|
634
|
-
currentResponse.status,
|
|
635
|
-
);
|
|
647
|
+
throw createGeminiCliHttpError(currentResponse, retryErrorText, false);
|
|
636
648
|
}
|
|
637
649
|
}
|
|
638
650
|
|
|
@@ -671,6 +683,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
671
683
|
}
|
|
672
684
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
673
685
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
686
|
+
output.transportFailure = transportFailureFacts(error);
|
|
674
687
|
output.errorMessage = await appendRawHttpRequestDumpFor400(
|
|
675
688
|
error instanceof Error ? error.message : JSON.stringify(error),
|
|
676
689
|
error,
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
} from "../types";
|
|
21
21
|
import { normalizeSystemPrompts, sanitizeJsonStrings } from "../utils";
|
|
22
22
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
23
|
+
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
23
24
|
import { finalizeErrorMessage, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
24
25
|
import { normalizeSchemaForCCA, normalizeSchemaForGoogle, toolWireSchema } from "../utils/schema";
|
|
25
26
|
import {
|
|
@@ -868,7 +869,11 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
|
|
|
868
869
|
new Error(`Google API error (${response.status}): ${extractGoogleErrorMessage(errorText)}`),
|
|
869
870
|
response.status,
|
|
870
871
|
);
|
|
871
|
-
if (
|
|
872
|
+
if (
|
|
873
|
+
!options?.fallbackManaged &&
|
|
874
|
+
firstTokenTime === undefined &&
|
|
875
|
+
isForcedToolChoiceUnsupportedError(error, true)
|
|
876
|
+
) {
|
|
872
877
|
const beforeMark = resolveToolChoice(model, options?.toolChoice);
|
|
873
878
|
markToolChoiceIncapability(model, "auto", error.message);
|
|
874
879
|
stream.push({
|
|
@@ -938,6 +943,7 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
|
|
|
938
943
|
}
|
|
939
944
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
940
945
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
946
|
+
output.transportFailure = transportFailureFacts(error);
|
|
941
947
|
output.errorMessage = await finalizeErrorMessage(error, rawRequestDump);
|
|
942
948
|
output.duration = Date.now() - startTime;
|
|
943
949
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
package/src/providers/ollama.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
} from "../types";
|
|
17
17
|
import { normalizeSystemPrompts } from "../utils";
|
|
18
18
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
19
|
+
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
19
20
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
20
21
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
21
22
|
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
@@ -431,7 +432,11 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
|
|
|
431
432
|
`HTTP ${response.status} from ${baseUrl}/api/chat: ${await response.text().catch(() => "")}`,
|
|
432
433
|
);
|
|
433
434
|
(error as Error & { status?: number }).status = response.status;
|
|
434
|
-
if (
|
|
435
|
+
if (
|
|
436
|
+
firstTokenTime === undefined &&
|
|
437
|
+
!options.fallbackManaged &&
|
|
438
|
+
isForcedToolChoiceUnsupportedError(error, true)
|
|
439
|
+
) {
|
|
435
440
|
markToolChoiceIncapability(model, "auto", error.message);
|
|
436
441
|
stream.push({
|
|
437
442
|
type: "toolChoiceIncapability",
|
|
@@ -590,6 +595,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
|
|
|
590
595
|
}
|
|
591
596
|
output.stopReason = options.signal?.aborted ? "aborted" : "error";
|
|
592
597
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
598
|
+
output.transportFailure = transportFailureFacts(error);
|
|
593
599
|
output.errorMessage = await finalizeErrorMessage(error, rawRequestDump);
|
|
594
600
|
output.duration = Date.now() - startTime;
|
|
595
601
|
if (firstTokenTime) {
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ResolvedServiceTier,
|
|
15
15
|
StopReason,
|
|
16
16
|
TextContent,
|
|
17
|
+
ThinkingContent,
|
|
17
18
|
Tool,
|
|
18
19
|
ToolCall,
|
|
19
20
|
ToolResultMessage,
|
|
@@ -416,6 +417,29 @@ function buildUsage(message: AssistantMessage): Record<string, unknown> {
|
|
|
416
417
|
return usage;
|
|
417
418
|
}
|
|
418
419
|
|
|
420
|
+
function isResponsesFamilyApi(api: AssistantMessage["api"]): boolean {
|
|
421
|
+
return api === "openai-responses" || api === "openai-codex-responses";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function safeThinkingText(content: ThinkingContent, api: AssistantMessage["api"]): string | undefined {
|
|
425
|
+
if (isResponsesFamilyApi(api) && content.provenance === undefined) return undefined;
|
|
426
|
+
if (content.provenance === "raw") return undefined;
|
|
427
|
+
if (content.provenance === "mixed") return content.summaryText;
|
|
428
|
+
if (content.provenance === "summary") return content.summaryText ?? content.thinking;
|
|
429
|
+
return content.thinking;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function hasRawOrMixedThinking(partial: AssistantMessage, contentIndex: number): boolean {
|
|
433
|
+
const content = partial.content[contentIndex];
|
|
434
|
+
return content?.type === "thinking" && (content.provenance === "raw" || content.provenance === "mixed");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Responses-family reasoning is untrusted until output_item.done assigns provenance. */
|
|
438
|
+
function hasUnfinalizedResponsesThinking(partial: AssistantMessage, contentIndex: number): boolean {
|
|
439
|
+
const content = partial.content[contentIndex];
|
|
440
|
+
return content?.type !== "thinking" || (isResponsesFamilyApi(partial.api) && content.provenance === undefined);
|
|
441
|
+
}
|
|
442
|
+
|
|
419
443
|
function flattenAssistant(message: AssistantMessage): {
|
|
420
444
|
text: string;
|
|
421
445
|
reasoning: string;
|
|
@@ -429,9 +453,11 @@ function flattenAssistant(message: AssistantMessage): {
|
|
|
429
453
|
case "text":
|
|
430
454
|
text += part.text;
|
|
431
455
|
break;
|
|
432
|
-
case "thinking":
|
|
433
|
-
|
|
456
|
+
case "thinking": {
|
|
457
|
+
const thinking = safeThinkingText(part, message.api);
|
|
458
|
+
if (thinking !== undefined) reasoning += thinking;
|
|
434
459
|
break;
|
|
460
|
+
}
|
|
435
461
|
case "redactedThinking":
|
|
436
462
|
// Opaque blob — surface verbatim on the reasoning channel so the
|
|
437
463
|
// concatenation round-trips through clients that just echo it.
|
|
@@ -521,6 +547,18 @@ export function encodeStream(
|
|
|
521
547
|
let nextToolIndex = 0;
|
|
522
548
|
let hasToolCalls = false;
|
|
523
549
|
let finishReason: string = "stop";
|
|
550
|
+
// contentIndexes that already streamed a reasoning summary delta, so a
|
|
551
|
+
// final-only reasoning_summary_end does not duplicate streamed summary text.
|
|
552
|
+
const summaryDeltaSeen = new Set<number>();
|
|
553
|
+
// Responses assigns reasoning provenance only at output_item.done. Keep its
|
|
554
|
+
// pre-classification bytes out of this public compatibility stream.
|
|
555
|
+
const pendingThinkingDeltas = new Map<number, string[]>();
|
|
556
|
+
|
|
557
|
+
const writeThinkingDelta = (thinking: string) => {
|
|
558
|
+
// DeepSeek-style / o-series reasoning channel. Clients that don't
|
|
559
|
+
// understand it ignore the unknown delta key.
|
|
560
|
+
if (thinking.length > 0) writeSse(controller, baseChunk({ reasoning_content: thinking }, null));
|
|
561
|
+
};
|
|
524
562
|
|
|
525
563
|
try {
|
|
526
564
|
// Initial role chunk.
|
|
@@ -534,14 +572,60 @@ export function encodeStream(
|
|
|
534
572
|
}
|
|
535
573
|
break;
|
|
536
574
|
|
|
537
|
-
case "thinking_delta":
|
|
538
|
-
|
|
539
|
-
|
|
575
|
+
case "thinking_delta": {
|
|
576
|
+
if (hasRawOrMixedThinking(event.partial, event.contentIndex)) break;
|
|
577
|
+
if (hasUnfinalizedResponsesThinking(event.partial, event.contentIndex)) {
|
|
578
|
+
const deltas = pendingThinkingDeltas.get(event.contentIndex) ?? [];
|
|
579
|
+
deltas.push(event.delta);
|
|
580
|
+
pendingThinkingDeltas.set(event.contentIndex, deltas);
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
writeThinkingDelta(event.delta);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
case "thinking_start":
|
|
587
|
+
if (
|
|
588
|
+
!hasRawOrMixedThinking(event.partial, event.contentIndex) &&
|
|
589
|
+
hasUnfinalizedResponsesThinking(event.partial, event.contentIndex)
|
|
590
|
+
) {
|
|
591
|
+
pendingThinkingDeltas.set(event.contentIndex, []);
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
case "thinking_end": {
|
|
595
|
+
const pending = pendingThinkingDeltas.get(event.contentIndex);
|
|
596
|
+
pendingThinkingDeltas.delete(event.contentIndex);
|
|
597
|
+
if (
|
|
598
|
+
hasRawOrMixedThinking(event.partial, event.contentIndex) ||
|
|
599
|
+
hasUnfinalizedResponsesThinking(event.partial, event.contentIndex)
|
|
600
|
+
)
|
|
601
|
+
break;
|
|
602
|
+
if (pending) for (const delta of pending) writeThinkingDelta(delta);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case "reasoning_summary_start":
|
|
606
|
+
// Chat format has no explicit reasoning open frame.
|
|
607
|
+
break;
|
|
608
|
+
|
|
609
|
+
case "reasoning_summary_delta":
|
|
610
|
+
// Provider-displayable summary reasoning surfaces on the same
|
|
611
|
+
// reasoning_content channel as raw thinking for this legacy format.
|
|
540
612
|
if (event.delta.length > 0) {
|
|
613
|
+
// Only a non-whitespace delta counts as a delivered summary; a bare
|
|
614
|
+
// separator ("\n\n") must not suppress a later final-only end content.
|
|
615
|
+
if (event.delta.trim().length > 0) summaryDeltaSeen.add(event.contentIndex);
|
|
541
616
|
writeSse(controller, baseChunk({ reasoning_content: event.delta }, null));
|
|
542
617
|
}
|
|
543
618
|
break;
|
|
544
619
|
|
|
620
|
+
case "reasoning_summary_end":
|
|
621
|
+
// Final-only summary: text arrives only on the end event with no prior
|
|
622
|
+
// deltas, so surface it now (skip when deltas already streamed to avoid
|
|
623
|
+
// duplicating the summary).
|
|
624
|
+
if (event.content.length > 0 && !summaryDeltaSeen.has(event.contentIndex)) {
|
|
625
|
+
writeSse(controller, baseChunk({ reasoning_content: event.content }, null));
|
|
626
|
+
}
|
|
627
|
+
break;
|
|
628
|
+
|
|
545
629
|
case "toolcall_start": {
|
|
546
630
|
hasToolCalls = true;
|
|
547
631
|
const idx = nextToolIndex++;
|
|
@@ -578,6 +662,7 @@ export function encodeStream(
|
|
|
578
662
|
}
|
|
579
663
|
|
|
580
664
|
case "done":
|
|
665
|
+
pendingThinkingDeltas.clear();
|
|
581
666
|
finishReason =
|
|
582
667
|
event.reason === "toolUse"
|
|
583
668
|
? "tool_calls"
|
|
@@ -593,6 +678,7 @@ export function encodeStream(
|
|
|
593
678
|
return;
|
|
594
679
|
|
|
595
680
|
case "error": {
|
|
681
|
+
pendingThinkingDeltas.clear();
|
|
596
682
|
const msg = event.error.errorMessage ?? "stream error";
|
|
597
683
|
writeSse(controller, { error: { message: msg, type: "upstream_error" } });
|
|
598
684
|
controller.close();
|
|
@@ -607,10 +693,12 @@ export function encodeStream(
|
|
|
607
693
|
}
|
|
608
694
|
|
|
609
695
|
// Stream ended without a terminal `done` (defensive). Close gracefully.
|
|
696
|
+
pendingThinkingDeltas.clear();
|
|
610
697
|
writeSse(controller, baseChunk({}, hasToolCalls ? "tool_calls" : "stop"));
|
|
611
698
|
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
612
699
|
controller.close();
|
|
613
700
|
} catch (err) {
|
|
701
|
+
pendingThinkingDeltas.clear();
|
|
614
702
|
const msg = err instanceof Error ? err.message : String(err);
|
|
615
703
|
writeSse(controller, { error: { message: msg, type: "upstream_error" } });
|
|
616
704
|
controller.close();
|
|
@@ -14,6 +14,7 @@ export type CodexRateLimits = {
|
|
|
14
14
|
export type CodexErrorInfo = {
|
|
15
15
|
message: string;
|
|
16
16
|
status: number;
|
|
17
|
+
code?: string;
|
|
17
18
|
friendlyMessage?: string;
|
|
18
19
|
rateLimits?: CodexRateLimits;
|
|
19
20
|
raw?: string;
|
|
@@ -24,6 +25,7 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
|
|
24
25
|
let message = raw || response.statusText || "Request failed";
|
|
25
26
|
let friendlyMessage: string | undefined;
|
|
26
27
|
let rateLimits: CodexRateLimits | undefined;
|
|
28
|
+
let code: string | undefined;
|
|
27
29
|
|
|
28
30
|
try {
|
|
29
31
|
const parsed = JSON.parse(raw) as { error?: Record<string, unknown> };
|
|
@@ -45,16 +47,21 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
|
|
45
47
|
? { primary, secondary }
|
|
46
48
|
: undefined;
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
code =
|
|
51
|
+
typeof (err as { code?: unknown }).code === "string"
|
|
52
|
+
? (err as { code: string }).code
|
|
53
|
+
: typeof (err as { type?: unknown }).type === "string"
|
|
54
|
+
? (err as { type: string }).type
|
|
55
|
+
: undefined;
|
|
49
56
|
const resetsAt = (err as { resets_at?: number }).resets_at ?? primary.resets_at ?? secondary.resets_at;
|
|
50
57
|
const mins = resetsAt ? Math.max(0, Math.round((resetsAt * 1000 - Date.now()) / 60000)) : undefined;
|
|
51
58
|
|
|
52
|
-
if (/usage_limit_reached|usage_not_included/i.test(code)) {
|
|
59
|
+
if (/usage_limit_reached|usage_not_included/i.test(code ?? "")) {
|
|
53
60
|
const planType = (err as { plan_type?: string }).plan_type;
|
|
54
61
|
const plan = planType ? ` (${String(planType).toLowerCase()} plan)` : "";
|
|
55
62
|
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
|
|
56
63
|
friendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();
|
|
57
|
-
} else if (/rate_limit_exceeded/i.test(code) || response.status === 429) {
|
|
64
|
+
} else if (/rate_limit_exceeded/i.test(code ?? "") || response.status === 429) {
|
|
58
65
|
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
|
|
59
66
|
friendlyMessage = `ChatGPT rate limit exceeded.${when}`.trim();
|
|
60
67
|
}
|
|
@@ -69,6 +76,7 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
|
|
69
76
|
message,
|
|
70
77
|
status: response.status,
|
|
71
78
|
friendlyMessage,
|
|
79
|
+
code,
|
|
72
80
|
rateLimits,
|
|
73
81
|
raw: raw,
|
|
74
82
|
};
|
|
@@ -43,10 +43,12 @@ import {
|
|
|
43
43
|
createOpenAIResponsesHistoryPayload,
|
|
44
44
|
getOpenAIResponsesHistoryItems,
|
|
45
45
|
getOpenAIResponsesHistoryPayload,
|
|
46
|
+
neutralizeResponsesInputControlTokens,
|
|
46
47
|
normalizeSystemPrompts,
|
|
47
48
|
sanitizeOpenAIResponsesHistoryItemsForReplay,
|
|
48
49
|
} from "../utils";
|
|
49
50
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
51
|
+
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
50
52
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
51
53
|
import { getOpenAIStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
|
|
52
54
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
@@ -111,9 +113,15 @@ const CODEX_NON_RETRYABLE_EVENT_CODES = new Set([
|
|
|
111
113
|
"invalid_request_error",
|
|
112
114
|
"invalid_schema",
|
|
113
115
|
"invalid_tool_schema",
|
|
116
|
+
// A poisoned-history rejection (`Request blocked (code=invalid_prompt)`) is a
|
|
117
|
+
// deterministic content fault, not a transient upstream failure: retrying the
|
|
118
|
+
// same request re-sends the same offending item and re-triggers the block, so
|
|
119
|
+
// classify it as explicitly non-retryable instead of relying on omission
|
|
120
|
+
// (the request-boundary sanitizer, not a provider retry, is the recovery path).
|
|
121
|
+
"invalid_prompt",
|
|
114
122
|
]);
|
|
115
123
|
const CODEX_NON_RETRYABLE_EVENT_MESSAGE =
|
|
116
|
-
/invalid[_ -]function[_ -]parameters|invalid schema for function|invalid[_ -]tool[_ -]schema|schema must have type ["']?object["']
|
|
124
|
+
/invalid[_ -]function[_ -]parameters|invalid schema for function|invalid[_ -]tool[_ -]schema|schema must have type ["']?object["']?|request blocked[^\n]*invalid[_ -]prompt|code=invalid[_ -]prompt/i;
|
|
117
125
|
const CODEX_RETRYABLE_EVENT_MESSAGE =
|
|
118
126
|
/processing your request|retry your request|temporar(?:y|ily)|overloaded|service.?unavailable|internal error|server error/i;
|
|
119
127
|
const CODEX_PROVIDER_SESSION_STATE_KEY = "openai-codex-responses";
|
|
@@ -131,6 +139,7 @@ const CODEX_PROGRESS_EVENT_TYPES = new Set([
|
|
|
131
139
|
"response.reasoning_summary_part.added",
|
|
132
140
|
"response.reasoning_summary_text.delta",
|
|
133
141
|
"response.reasoning_summary_part.done",
|
|
142
|
+
"response.reasoning_text.delta",
|
|
134
143
|
"response.content_part.added",
|
|
135
144
|
"response.output_text.delta",
|
|
136
145
|
"response.refusal.delta",
|
|
@@ -153,8 +162,8 @@ function isCodexStreamProgressEvent(event: unknown): boolean {
|
|
|
153
162
|
}
|
|
154
163
|
type CodexTransport = "sse" | "websocket";
|
|
155
164
|
type CodexEventItem = ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | ResponseCustomToolCall;
|
|
156
|
-
type
|
|
157
|
-
|
|
165
|
+
type CodexThinkingBlock = ThinkingContent & { summaryBuffer: string; rawBuffer: string; summaryStarted: boolean };
|
|
166
|
+
type CodexOutputBlock = CodexThinkingBlock | TextContent | (ToolCall & { partialJson: string });
|
|
158
167
|
export interface OpenAICodexWebSocketDebugStats {
|
|
159
168
|
fullContextRequests: number;
|
|
160
169
|
deltaRequests: number;
|
|
@@ -633,7 +642,7 @@ async function buildTransformedCodexRequestBody(
|
|
|
633
642
|
): Promise<RequestBody> {
|
|
634
643
|
const params: RequestBody = {
|
|
635
644
|
model: model.id,
|
|
636
|
-
input:
|
|
645
|
+
input: neutralizeResponsesInputControlTokens(convertMessages(model, context)),
|
|
637
646
|
stream: true,
|
|
638
647
|
prompt_cache_key: normalizeOpenAIResponsesPromptCacheKey(options?.sessionId),
|
|
639
648
|
};
|
|
@@ -993,6 +1002,11 @@ function handleCodexStreamEvent(args: {
|
|
|
993
1002
|
return firstTokenTime;
|
|
994
1003
|
}
|
|
995
1004
|
|
|
1005
|
+
if (eventType === "response.reasoning_text.delta") {
|
|
1006
|
+
handleReasoningTextDelta(runtime.currentItem, runtime.currentBlock, rawEvent, stream, output, blockIndex);
|
|
1007
|
+
return firstTokenTime;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
996
1010
|
if (eventType === "response.content_part.added") {
|
|
997
1011
|
handleContentPartAdded(runtime.currentItem, rawEvent);
|
|
998
1012
|
return firstTokenTime;
|
|
@@ -1067,7 +1081,7 @@ function handleCodexStreamEvent(args: {
|
|
|
1067
1081
|
|
|
1068
1082
|
function createOutputBlockForItem(item: CodexEventItem): CodexOutputBlock | null {
|
|
1069
1083
|
if (item.type === "reasoning") {
|
|
1070
|
-
return { type: "thinking", thinking: "" };
|
|
1084
|
+
return { type: "thinking", thinking: "", summaryBuffer: "", rawBuffer: "", summaryStarted: false };
|
|
1071
1085
|
}
|
|
1072
1086
|
if (item.type === "message") {
|
|
1073
1087
|
return { type: "text", text: "" };
|
|
@@ -1118,13 +1132,18 @@ function handleReasoningSummaryTextDelta(
|
|
|
1118
1132
|
blockIndex: () => number,
|
|
1119
1133
|
): void {
|
|
1120
1134
|
if (currentItem?.type !== "reasoning" || currentBlock?.type !== "thinking") return;
|
|
1135
|
+
if (!currentBlock.summaryStarted) {
|
|
1136
|
+
currentBlock.summaryStarted = true;
|
|
1137
|
+
stream.push({ type: "reasoning_summary_start", contentIndex: blockIndex(), partial: output });
|
|
1138
|
+
}
|
|
1121
1139
|
currentItem.summary = currentItem.summary || [];
|
|
1122
1140
|
const lastPart = currentItem.summary[currentItem.summary.length - 1];
|
|
1123
1141
|
if (!lastPart) return;
|
|
1124
1142
|
const delta = (rawEvent as { delta?: string }).delta || "";
|
|
1125
1143
|
currentBlock.thinking += delta;
|
|
1144
|
+
currentBlock.summaryBuffer += delta;
|
|
1126
1145
|
lastPart.text += delta;
|
|
1127
|
-
stream.push({ type: "
|
|
1146
|
+
stream.push({ type: "reasoning_summary_delta", contentIndex: blockIndex(), delta, partial: output });
|
|
1128
1147
|
}
|
|
1129
1148
|
|
|
1130
1149
|
function handleReasoningSummaryPartDone(
|
|
@@ -1139,8 +1158,24 @@ function handleReasoningSummaryPartDone(
|
|
|
1139
1158
|
const lastPart = currentItem.summary[currentItem.summary.length - 1];
|
|
1140
1159
|
if (!lastPart) return;
|
|
1141
1160
|
currentBlock.thinking += "\n\n";
|
|
1161
|
+
currentBlock.summaryBuffer += "\n\n";
|
|
1142
1162
|
lastPart.text += "\n\n";
|
|
1143
|
-
stream.push({ type: "
|
|
1163
|
+
stream.push({ type: "reasoning_summary_delta", contentIndex: blockIndex(), delta: "\n\n", partial: output });
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function handleReasoningTextDelta(
|
|
1167
|
+
currentItem: CodexEventItem | null,
|
|
1168
|
+
currentBlock: CodexOutputBlock | null,
|
|
1169
|
+
rawEvent: Record<string, unknown>,
|
|
1170
|
+
stream: AssistantMessageEventStream,
|
|
1171
|
+
output: AssistantMessage,
|
|
1172
|
+
blockIndex: () => number,
|
|
1173
|
+
): void {
|
|
1174
|
+
if (currentItem?.type !== "reasoning" || currentBlock?.type !== "thinking") return;
|
|
1175
|
+
const delta = (rawEvent as { delta?: string }).delta || "";
|
|
1176
|
+
currentBlock.thinking += delta;
|
|
1177
|
+
currentBlock.rawBuffer += delta;
|
|
1178
|
+
stream.push({ type: "thinking_delta", contentIndex: blockIndex(), delta, partial: output });
|
|
1144
1179
|
}
|
|
1145
1180
|
|
|
1146
1181
|
function handleContentPartAdded(currentItem: CodexEventItem | null, rawEvent: Record<string, unknown>): void {
|
|
@@ -1243,14 +1278,50 @@ function handleOutputItemDone(
|
|
|
1243
1278
|
runtime.nativeOutputItems.push(item as unknown as Record<string, unknown>);
|
|
1244
1279
|
|
|
1245
1280
|
if (item.type === "reasoning" && runtime.currentBlock?.type === "thinking") {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1281
|
+
const block = runtime.currentBlock;
|
|
1282
|
+
// Prefer the streamed summary buffer only when it carries real text; a
|
|
1283
|
+
// part.done before/without any summary_text delta leaves only separators, so
|
|
1284
|
+
// fall back to the canonical item.summary from output_item.done (matches the
|
|
1285
|
+
// shared Responses decoder).
|
|
1286
|
+
const bufferSummary = block.summaryBuffer ?? "";
|
|
1287
|
+
const itemSummary = item.summary?.map(summary => summary.text).join("\n\n") ?? "";
|
|
1288
|
+
const summaryText = bufferSummary.trim() ? bufferSummary : itemSummary;
|
|
1289
|
+
const rawText = block.rawBuffer;
|
|
1290
|
+
const mutable = block as { provenance?: "summary" | "raw" | "mixed"; summaryText?: string; rawText?: string };
|
|
1291
|
+
if (mutable.provenance === undefined) {
|
|
1292
|
+
if (mutable.summaryText === undefined && summaryText) mutable.summaryText = summaryText;
|
|
1293
|
+
if (mutable.rawText === undefined && rawText) mutable.rawText = rawText;
|
|
1294
|
+
mutable.provenance = summaryText && rawText ? "mixed" : summaryText ? "summary" : rawText ? "raw" : undefined;
|
|
1295
|
+
}
|
|
1296
|
+
// Finalized display string must exclude raw CoT when a summary exists (parity
|
|
1297
|
+
// with openai-responses-shared). Derive from STORED write-once provenance fields
|
|
1298
|
+
// so a later/duplicate raw-only finalization cannot overwrite a summary/mixed
|
|
1299
|
+
// block's safe display with raw CoT; raw-only stays raw.
|
|
1300
|
+
{
|
|
1301
|
+
const effSummary = mutable.summaryText ?? summaryText;
|
|
1302
|
+
const effRaw = mutable.rawText ?? rawText;
|
|
1303
|
+
block.thinking = mutable.provenance === "raw" ? effRaw : effSummary || effRaw;
|
|
1304
|
+
}
|
|
1305
|
+
block.thinkingSignature = JSON.stringify(item);
|
|
1306
|
+
delete (block as { summaryBuffer?: string }).summaryBuffer;
|
|
1307
|
+
delete (block as { rawBuffer?: string }).rawBuffer;
|
|
1308
|
+
const wasSummaryStarted = block.summaryStarted;
|
|
1309
|
+
delete (block as { summaryStarted?: boolean }).summaryStarted;
|
|
1310
|
+
if (summaryText) {
|
|
1311
|
+
// Emit a summary start first when none was streamed (part.added/done or
|
|
1312
|
+
// canonical done-item summary with no summary_text delta), so consumers that
|
|
1313
|
+
// open a summary on start don't receive an orphaned reasoning_summary_end.
|
|
1314
|
+
if (!wasSummaryStarted) {
|
|
1315
|
+
stream.push({ type: "reasoning_summary_start", contentIndex: blockIndex(), partial: output });
|
|
1316
|
+
}
|
|
1317
|
+
stream.push({
|
|
1318
|
+
type: "reasoning_summary_end",
|
|
1319
|
+
contentIndex: blockIndex(),
|
|
1320
|
+
content: summaryText,
|
|
1321
|
+
partial: output,
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
stream.push({ type: "thinking_end", contentIndex: blockIndex(), content: block.thinking, partial: output });
|
|
1254
1325
|
runtime.currentBlock = null;
|
|
1255
1326
|
return;
|
|
1256
1327
|
}
|
|
@@ -1372,6 +1443,7 @@ async function recoverCodexStreamError(
|
|
|
1372
1443
|
runtime: CodexStreamRuntime,
|
|
1373
1444
|
error: unknown,
|
|
1374
1445
|
): Promise<boolean> {
|
|
1446
|
+
if (context.options?.fallbackManaged) return false;
|
|
1375
1447
|
if (await tryRetryWithoutForcedToolChoice(context, runtime, error)) {
|
|
1376
1448
|
return true;
|
|
1377
1449
|
}
|
|
@@ -1396,6 +1468,7 @@ async function tryRetryWithoutForcedToolChoice(
|
|
|
1396
1468
|
error: unknown,
|
|
1397
1469
|
): Promise<boolean> {
|
|
1398
1470
|
if (
|
|
1471
|
+
context.options?.fallbackManaged ||
|
|
1399
1472
|
runtime.providerRetryAttempt > 0 ||
|
|
1400
1473
|
context.output.content.length > 0 ||
|
|
1401
1474
|
context.firstTokenTime !== undefined ||
|
|
@@ -1468,7 +1541,12 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
|
|
|
1468
1541
|
return false;
|
|
1469
1542
|
}
|
|
1470
1543
|
const websocketState = context.requestContext.websocketState;
|
|
1471
|
-
if (
|
|
1544
|
+
if (
|
|
1545
|
+
!websocketState ||
|
|
1546
|
+
runtime.transport !== "websocket" ||
|
|
1547
|
+
context.options?.signal?.aborted ||
|
|
1548
|
+
context.options?.fallbackManaged
|
|
1549
|
+
) {
|
|
1472
1550
|
return false;
|
|
1473
1551
|
}
|
|
1474
1552
|
|
|
@@ -1519,6 +1597,7 @@ async function tryRecoverCodexPreviousResponseNotFound(
|
|
|
1519
1597
|
if (
|
|
1520
1598
|
!isCodexPreviousResponseNotFound(error) ||
|
|
1521
1599
|
!websocketState ||
|
|
1600
|
+
context.options?.fallbackManaged ||
|
|
1522
1601
|
runtime.transport !== "websocket" ||
|
|
1523
1602
|
context.output.content.length > 0 ||
|
|
1524
1603
|
context.options?.signal?.aborted ||
|
|
@@ -1556,7 +1635,8 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
1556
1635
|
isCodexWebSocketRetryableStreamError(error) &&
|
|
1557
1636
|
runtime.canSafelyReplayWebsocketOverSse &&
|
|
1558
1637
|
!runtime.sawTerminalEvent &&
|
|
1559
|
-
!context.options?.signal?.aborted
|
|
1638
|
+
!context.options?.signal?.aborted &&
|
|
1639
|
+
!context.options?.fallbackManaged;
|
|
1560
1640
|
if (!canReplay) return false;
|
|
1561
1641
|
|
|
1562
1642
|
const state = websocketState;
|
|
@@ -1608,7 +1688,8 @@ async function tryRetryCodexProviderError(
|
|
|
1608
1688
|
!isRetryableCodexProviderError(error) ||
|
|
1609
1689
|
context.output.content.length > 0 ||
|
|
1610
1690
|
runtime.providerRetryAttempt >= resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES) ||
|
|
1611
|
-
context.options?.signal?.aborted
|
|
1691
|
+
context.options?.signal?.aborted ||
|
|
1692
|
+
context.options?.fallbackManaged
|
|
1612
1693
|
) {
|
|
1613
1694
|
return false;
|
|
1614
1695
|
}
|
|
@@ -1692,6 +1773,7 @@ async function handleCodexStreamFailure(
|
|
|
1692
1773
|
}
|
|
1693
1774
|
output.stopReason = context.options?.signal?.aborted ? "aborted" : "error";
|
|
1694
1775
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
1776
|
+
output.transportFailure = transportFailureFacts(error);
|
|
1695
1777
|
output.errorMessage = await finalizeErrorMessage(error, context.requestContext.rawRequestDump);
|
|
1696
1778
|
output.duration = Date.now() - context.startTime;
|
|
1697
1779
|
if (context.firstTokenTime) {
|
|
@@ -1719,6 +1801,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|
|
1719
1801
|
try {
|
|
1720
1802
|
initialTransport = await openInitialCodexEventStream(model, options, requestSetup, requestContext);
|
|
1721
1803
|
} catch (error) {
|
|
1804
|
+
if (options?.fallbackManaged) throw error;
|
|
1722
1805
|
initialTransport = await retryCodexInitialTransportWithoutToolChoice(
|
|
1723
1806
|
model,
|
|
1724
1807
|
options,
|
|
@@ -2408,6 +2491,7 @@ async function openCodexSseEventStream(
|
|
|
2408
2491
|
const error = new Error(info.friendlyMessage || info.message);
|
|
2409
2492
|
(error as { headers?: Headers; status?: number }).headers = response.headers;
|
|
2410
2493
|
(error as { headers?: Headers; status?: number }).status = response.status;
|
|
2494
|
+
(error as { code?: string }).code = info.code;
|
|
2411
2495
|
throw error;
|
|
2412
2496
|
}
|
|
2413
2497
|
if (!response.body) {
|
|
@@ -2635,8 +2719,11 @@ function normalizeInputMessageContent(
|
|
|
2635
2719
|
return convertResponsesInputContent(content, model.input.includes("image")) ?? [];
|
|
2636
2720
|
}
|
|
2637
2721
|
|
|
2638
|
-
/** @internal Exported for tests. */
|
|
2639
|
-
export {
|
|
2722
|
+
/** @internal Exported for tests. `classifyCodexFailureEventRetryable` is the retry classification of a Codex failure event. */
|
|
2723
|
+
export {
|
|
2724
|
+
convertMessages as convertCodexResponsesMessages,
|
|
2725
|
+
isRetryableCodexFailureEvent as classifyCodexFailureEventRetryable,
|
|
2726
|
+
};
|
|
2640
2727
|
|
|
2641
2728
|
/**
|
|
2642
2729
|
* Whether this OpenAI code backend-backend model should get the custom-tool grammar
|