@bitkyc08/opencodex 2.58.0 → 2.60.0
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/README.md +28 -10
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/crusoe.svg +1 -0
- package/gui/dist/provider-icons/opper.svg +3 -0
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +34 -1
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/effort-map.ts +16 -2
- package/src/adapters/cursor/envelope-echo.ts +55 -2
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/message-mapper.ts +3 -2
- package/src/adapters/cursor/protobuf-request.ts +8 -5
- package/src/adapters/cursor/request-builder.ts +21 -4
- package/src/adapters/cursor/thread-continuity.ts +105 -31
- package/src/adapters/cursor/tool-guidance.ts +5 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +45 -2
- package/src/adapters/devin/cloud-direct/chat.ts +14 -3
- package/src/adapters/devin/cloud-direct/index.ts +7 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +140 -0
- package/src/adapters/devin.ts +125 -26
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +55 -4
- package/src/adapters/google-http.ts +57 -11
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +60 -10
- package/src/adapters/openai-chat/response-events.ts +61 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +10 -11
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +25 -4
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +75 -0
- package/src/adapters/openai-responses/tool-schema.ts +19 -7
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/responses-tool-schema.ts +76 -46
- package/src/adapters/run-turn-queue.ts +17 -4
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +28 -35
- package/src/claude/context-windows.ts +22 -0
- package/src/claude/outbound.ts +35 -4
- package/src/cli/account-api.ts +4 -3
- package/src/cli/account-extended.ts +26 -6
- package/src/cli/account-orca-import.ts +63 -0
- package/src/cli/account.ts +32 -4
- package/src/cli/capabilities.ts +40 -0
- package/src/cli/claude.ts +29 -1
- package/src/cli/codex-cli-update.ts +97 -2
- package/src/cli/dispatch.ts +57 -3
- package/src/cli/doctor.ts +218 -4
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +95 -22
- package/src/cli/models-runtime.ts +33 -4
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +11 -1
- package/src/cli/runtime-api.ts +44 -0
- package/src/cli/start-args.ts +94 -0
- package/src/cli/system-command.ts +2 -0
- package/src/client/machine-api.ts +6 -5
- package/src/client/machine-listener.ts +16 -3
- package/src/client/runtime.ts +26 -2
- package/src/clients/config-export/constants.ts +2 -3
- package/src/clients/config-export.ts +5 -5
- package/src/codex/account-store.ts +146 -5
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +44 -10
- package/src/codex/auth-api/routes.ts +17 -2
- package/src/codex/auth-context.ts +16 -12
- package/src/codex/catalog/build-entries.ts +25 -4
- package/src/codex/catalog/derive-entry.ts +8 -1
- package/src/codex/catalog/effort.ts +10 -6
- package/src/codex/catalog/gather-capture.ts +22 -2
- package/src/codex/catalog/model-hints.ts +66 -33
- package/src/codex/catalog/parsing.ts +90 -5
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/reserve-warn.ts +96 -0
- package/src/codex/catalog/retained-sync.ts +41 -26
- package/src/codex/catalog/routed-gather.ts +61 -3
- package/src/codex/cli-installation-identity.ts +210 -0
- package/src/codex/cli-installation-targets.ts +158 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +24 -167
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/history-state-open.ts +105 -0
- package/src/codex/inject/config-toml.ts +44 -2
- package/src/codex/inject.ts +3 -2
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/lineage.ts +83 -32
- package/src/codex/loopback-target.ts +31 -0
- package/src/codex/main-account-hard-lock.ts +2 -1
- package/src/codex/main-account.ts +10 -3
- package/src/codex/main-device-reauth.ts +17 -9
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +80 -2
- package/src/codex/observed-model-denials.ts +230 -0
- package/src/codex/orca-auth-source.ts +94 -0
- package/src/codex/orca-import.ts +219 -0
- package/src/codex/prompt-text-probe.ts +289 -16
- package/src/codex/quota-401-recovery.ts +12 -0
- package/src/codex/quota-types.ts +65 -0
- package/src/codex/quota.ts +24 -19
- package/src/codex/routing/cooldown-math.ts +8 -47
- package/src/codex/routing/pin-drain.ts +57 -0
- package/src/codex/routing.ts +20 -16
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +114 -2
- package/src/codex/windows-installation-files.ts +224 -0
- package/src/combos/failover.ts +125 -5
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +43 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +33 -0
- package/src/config/pending-teardown.ts +8 -0
- package/src/config/process-state.ts +36 -3
- package/src/config/provider-relative-send-path.ts +16 -0
- package/src/config/proxy-env.ts +31 -7
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +25 -0
- package/src/config/schema/leaf-validators.ts +76 -17
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +410 -258
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/bounded-body.ts +4 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/destination-policy.ts +48 -6
- package/src/lib/errors.ts +82 -15
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/local-destinations.ts +32 -5
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +53 -5
- package/src/lib/proxy-env.ts +70 -3
- package/src/lib/request-execution-budget.ts +11 -3
- package/src/lib/response-body-inactivity.ts +193 -0
- package/src/lib/retry-delay.ts +69 -0
- package/src/lib/socks5-fetch.ts +741 -0
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +332 -35
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/workflow-budget.ts +145 -8
- package/src/oauth/account-quota-rank.ts +72 -15
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +41 -27
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/oauth/orcarouter.ts +15 -2
- package/src/oauth/store.ts +8 -0
- package/src/providers/codex-capacity.ts +9 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/devin-provider-merge-migration.ts +33 -12
- package/src/providers/free-directory.ts +20 -2
- package/src/providers/key-failover.ts +327 -25
- package/src/providers/model-rename-migration.ts +56 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-sidecar.ts +4 -0
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/opencode-go-transport.ts +14 -5
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/report-cache.ts +3 -0
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +131 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +87 -21
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +32 -11
- package/src/responses/bridge-search-replay-cache.ts +152 -0
- package/src/responses/code-mode-helper-compat.ts +26 -16
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/hosted-tool-policy.ts +85 -2
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +9 -2
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +13 -5
- package/src/server/chat-native-sse.ts +26 -9
- package/src/server/chat-native.ts +10 -4
- package/src/server/claude-messages.ts +30 -5
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/gui-static.ts +36 -2
- package/src/server/inbound-body-admission.ts +187 -0
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +24 -25
- package/src/server/management/api-access.ts +3 -4
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +48 -7
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/provider-capability-config.ts +35 -7
- package/src/server/management/provider-routes.ts +70 -18
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +97 -2
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +136 -34
- package/src/server/request-log.ts +80 -3
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +74 -30
- package/src/server/responses/adapter-delivery.ts +39 -8
- package/src/server/responses/adapter-dispatch.ts +63 -30
- package/src/server/responses/compact.ts +89 -18
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +90 -24
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +16 -15
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +68 -2
- package/src/server/responses/passthrough-delivery.ts +41 -15
- package/src/server/responses/passthrough-dispatch.ts +145 -53
- package/src/server/responses/passthrough-execution.ts +11 -1
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +96 -17
- package/src/server/responses/request-send-budget.ts +89 -8
- package/src/server/responses/request-sidecar-auth.ts +17 -9
- package/src/server/responses/request-spend.ts +38 -9
- package/src/server/responses/request-transport.ts +15 -12
- package/src/server/responses/run-turn-execution.ts +45 -8
- package/src/server/responses/sidecar-execution.ts +19 -2
- package/src/server/responses/ws-upstream.ts +16 -28
- package/src/server/responses-custom-tool-repair.ts +29 -56
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-frame-buffer.ts +12 -10
- package/src/server/sse-payload-rewrite.ts +37 -10
- package/src/server/system-env-shell.ts +5 -1
- package/src/server/system-env.ts +7 -1
- package/src/server/workflow-refusal.ts +56 -2
- package/src/service/cli.ts +16 -6
- package/src/service/guards.ts +10 -0
- package/src/service/health.ts +43 -0
- package/src/service/state.ts +7 -2
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/accounts.ts +4 -0
- package/src/types/config.ts +111 -6
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +9 -1
- package/src/types/tools.ts +14 -0
- package/src/types/wire.ts +9 -1
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +28 -0
- package/src/usage/log.ts +87 -4
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/src/web-search/passthrough-bridge.ts +39 -5
- package/gui/dist/assets/index-BbrHOIY0.js +0 -128
- package/gui/dist/assets/index-C5-RdDmD.css +0 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { ResponsesTerminalStatus } from "../bridge";
|
|
2
|
+
import type { AttemptRecoveryKind } from "../usage/log";
|
|
3
|
+
|
|
4
|
+
export const REQUEST_METRICS_PROTOCOLS = Object.freeze(["responses", "chat", "messages", "unknown"] as const);
|
|
5
|
+
export const REQUEST_METRICS_RESULTS = Object.freeze(["completed", "failed", "incomplete", "aborted"] as const);
|
|
6
|
+
export const REQUEST_METRICS_RECOVERY_CLASSES = Object.freeze([
|
|
7
|
+
"transient",
|
|
8
|
+
"connection",
|
|
9
|
+
"credential",
|
|
10
|
+
"rate_limit",
|
|
11
|
+
"payload",
|
|
12
|
+
"empty_completion",
|
|
13
|
+
"effort_downgrade",
|
|
14
|
+
"other",
|
|
15
|
+
] as const);
|
|
16
|
+
|
|
17
|
+
export const REQUEST_DURATION_BUCKETS_SECONDS = Object.freeze([0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60] as const);
|
|
18
|
+
export const REQUEST_TTFT_BUCKETS_SECONDS = Object.freeze([0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30] as const);
|
|
19
|
+
|
|
20
|
+
export type RequestMetricsProtocol = typeof REQUEST_METRICS_PROTOCOLS[number];
|
|
21
|
+
export type RequestMetricsResult = typeof REQUEST_METRICS_RESULTS[number];
|
|
22
|
+
export type RequestMetricsRecoveryClass = typeof REQUEST_METRICS_RECOVERY_CLASSES[number];
|
|
23
|
+
|
|
24
|
+
export interface RequestMetricFinalFact {
|
|
25
|
+
protocol?: "responses" | "chat" | "messages";
|
|
26
|
+
status: number;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
firstOutputMs?: number;
|
|
29
|
+
terminalStatus?: ResponsesTerminalStatus;
|
|
30
|
+
closeReason?: "terminal" | "client_cancel" | "non_stream" | "body_stall" | "body_overflow";
|
|
31
|
+
attempts?: ReadonlyArray<{
|
|
32
|
+
sendCount: number;
|
|
33
|
+
recoveryKinds: readonly AttemptRecoveryKind[];
|
|
34
|
+
}>;
|
|
35
|
+
spendSends?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RequestMetricsRecorder {
|
|
39
|
+
recordFinalRequest(fact: RequestMetricFinalFact): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RequestMetricsSnapshotter {
|
|
43
|
+
snapshot(): string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RequestMetricsOwner extends RequestMetricsRecorder, RequestMetricsSnapshotter {
|
|
47
|
+
resetForTests(): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface HistogramCell {
|
|
51
|
+
buckets: number[];
|
|
52
|
+
count: number;
|
|
53
|
+
sum: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const protocolCell = (value: RequestMetricsProtocol): number => REQUEST_METRICS_PROTOCOLS.indexOf(value);
|
|
57
|
+
const resultCell = (value: RequestMetricsResult): number => REQUEST_METRICS_RESULTS.indexOf(value);
|
|
58
|
+
const recoveryCell = (value: RequestMetricsRecoveryClass): number => REQUEST_METRICS_RECOVERY_CLASSES.indexOf(value);
|
|
59
|
+
|
|
60
|
+
function matrix(rows: number, columns: number): number[][] {
|
|
61
|
+
return Array.from({ length: rows }, () => Array.from({ length: columns }, () => 0));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function histograms(bounds: readonly number[]): HistogramCell[][] {
|
|
65
|
+
return Array.from({ length: REQUEST_METRICS_PROTOCOLS.length }, () => (
|
|
66
|
+
Array.from({ length: REQUEST_METRICS_RESULTS.length }, () => ({
|
|
67
|
+
buckets: Array.from({ length: bounds.length + 1 }, () => 0),
|
|
68
|
+
count: 0,
|
|
69
|
+
sum: 0,
|
|
70
|
+
}))
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function classifyResult(fact: RequestMetricFinalFact): RequestMetricsResult {
|
|
75
|
+
if (fact.closeReason === "client_cancel" || fact.status === 499) return "aborted";
|
|
76
|
+
if (fact.terminalStatus === "failed") return "failed";
|
|
77
|
+
if (fact.terminalStatus === "incomplete"
|
|
78
|
+
|| fact.closeReason === "body_stall"
|
|
79
|
+
|| fact.closeReason === "body_overflow") return "incomplete";
|
|
80
|
+
if (fact.terminalStatus === "completed") return "completed";
|
|
81
|
+
if (fact.terminalStatus === undefined
|
|
82
|
+
&& (fact.status === 101 || (fact.status >= 200 && fact.status < 400))) return "completed";
|
|
83
|
+
return "failed";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function recoveryClass(kind: AttemptRecoveryKind): RequestMetricsRecoveryClass {
|
|
87
|
+
switch (kind) {
|
|
88
|
+
case "transient-5xx": return "transient";
|
|
89
|
+
case "connection-reset": return "connection";
|
|
90
|
+
case "oauth-401":
|
|
91
|
+
case "key-401": return "credential";
|
|
92
|
+
case "key-429":
|
|
93
|
+
case "rate-limit-429":
|
|
94
|
+
case "anthropic-oauth-429":
|
|
95
|
+
case "oauth-account-429": return "rate_limit";
|
|
96
|
+
case "image-413":
|
|
97
|
+
case "console-go-upload-retry":
|
|
98
|
+
case "opaque-blob-rejection": return "payload";
|
|
99
|
+
case "empty-completion": return "empty_completion";
|
|
100
|
+
case "reasoning-effort-downgrade": return "effort_downgrade";
|
|
101
|
+
default: return "other";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function observeHistogram(cell: HistogramCell, bounds: readonly number[], value: number): void {
|
|
106
|
+
if (!Number.isFinite(value) || value < 0) return;
|
|
107
|
+
cell.count += 1;
|
|
108
|
+
cell.sum += value;
|
|
109
|
+
for (let index = 0; index < bounds.length; index += 1) {
|
|
110
|
+
if (value <= bounds[index]!) cell.buckets[index]! += 1;
|
|
111
|
+
}
|
|
112
|
+
cell.buckets[bounds.length]! += 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function sampleLabels(protocol: RequestMetricsProtocol, result?: RequestMetricsResult): string {
|
|
116
|
+
return result === undefined
|
|
117
|
+
? `{protocol="${protocol}"}`
|
|
118
|
+
: `{protocol="${protocol}",result="${result}"}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function appendHistogram(
|
|
122
|
+
lines: string[],
|
|
123
|
+
name: string,
|
|
124
|
+
help: string,
|
|
125
|
+
values: HistogramCell[][],
|
|
126
|
+
bounds: readonly number[],
|
|
127
|
+
): void {
|
|
128
|
+
lines.push(`# HELP ${name} ${help}`, `# TYPE ${name} histogram`);
|
|
129
|
+
for (const protocol of REQUEST_METRICS_PROTOCOLS) {
|
|
130
|
+
for (const result of REQUEST_METRICS_RESULTS) {
|
|
131
|
+
const cell = values[protocolCell(protocol)]![resultCell(result)]!;
|
|
132
|
+
for (let index = 0; index < bounds.length; index += 1) {
|
|
133
|
+
lines.push(`${name}_bucket{protocol="${protocol}",result="${result}",le="${bounds[index]}"} ${cell.buckets[index]}`);
|
|
134
|
+
}
|
|
135
|
+
lines.push(`${name}_bucket{protocol="${protocol}",result="${result}",le="+Inf"} ${cell.buckets[bounds.length]}`);
|
|
136
|
+
lines.push(`${name}_sum${sampleLabels(protocol, result)} ${cell.sum}`);
|
|
137
|
+
lines.push(`${name}_count${sampleLabels(protocol, result)} ${cell.count}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function createRequestMetricsOwner(
|
|
143
|
+
processStartTimeSeconds = Date.now() / 1000,
|
|
144
|
+
): RequestMetricsOwner {
|
|
145
|
+
let logicalRequests = matrix(REQUEST_METRICS_PROTOCOLS.length, REQUEST_METRICS_RESULTS.length);
|
|
146
|
+
let physicalSends = Array.from({ length: REQUEST_METRICS_PROTOCOLS.length }, () => 0);
|
|
147
|
+
let recoveries = matrix(REQUEST_METRICS_PROTOCOLS.length, REQUEST_METRICS_RECOVERY_CLASSES.length);
|
|
148
|
+
let durations = histograms(REQUEST_DURATION_BUCKETS_SECONDS);
|
|
149
|
+
let ttft = histograms(REQUEST_TTFT_BUCKETS_SECONDS);
|
|
150
|
+
let missingTtft = matrix(REQUEST_METRICS_PROTOCOLS.length, REQUEST_METRICS_RESULTS.length);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
recordFinalRequest(fact): void {
|
|
154
|
+
const protocol: RequestMetricsProtocol = fact.protocol ?? "unknown";
|
|
155
|
+
const result = classifyResult(fact);
|
|
156
|
+
const protocolIndex = protocolCell(protocol);
|
|
157
|
+
const resultIndex = resultCell(result);
|
|
158
|
+
logicalRequests[protocolIndex]![resultIndex]! += 1;
|
|
159
|
+
|
|
160
|
+
const attempts = fact.attempts;
|
|
161
|
+
const sends = attempts === undefined
|
|
162
|
+
? (Number.isInteger(fact.spendSends) && fact.spendSends! >= 0 ? fact.spendSends! : 0)
|
|
163
|
+
: attempts.reduce((total, attempt) => (
|
|
164
|
+
Number.isInteger(attempt.sendCount) && attempt.sendCount >= 0 ? total + attempt.sendCount : total
|
|
165
|
+
), 0);
|
|
166
|
+
physicalSends[protocolIndex]! += sends;
|
|
167
|
+
|
|
168
|
+
for (const attempt of attempts ?? []) {
|
|
169
|
+
for (const kind of new Set(attempt.recoveryKinds)) {
|
|
170
|
+
recoveries[protocolIndex]![recoveryCell(recoveryClass(kind))]! += 1;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
observeHistogram(durations[protocolIndex]![resultIndex]!, REQUEST_DURATION_BUCKETS_SECONDS, fact.durationMs / 1000);
|
|
175
|
+
if (typeof fact.firstOutputMs === "number" && Number.isFinite(fact.firstOutputMs) && fact.firstOutputMs >= 0) {
|
|
176
|
+
observeHistogram(ttft[protocolIndex]![resultIndex]!, REQUEST_TTFT_BUCKETS_SECONDS, fact.firstOutputMs / 1000);
|
|
177
|
+
} else {
|
|
178
|
+
missingTtft[protocolIndex]![resultIndex]! += 1;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
snapshot(): string {
|
|
183
|
+
const lines: string[] = [
|
|
184
|
+
"# HELP opencodex_logical_requests_total Finalized logical requests in this process.",
|
|
185
|
+
"# TYPE opencodex_logical_requests_total counter",
|
|
186
|
+
];
|
|
187
|
+
for (const protocol of REQUEST_METRICS_PROTOCOLS) {
|
|
188
|
+
for (const result of REQUEST_METRICS_RESULTS) {
|
|
189
|
+
lines.push(`opencodex_logical_requests_total${sampleLabels(protocol, result)} ${logicalRequests[protocolCell(protocol)]![resultCell(result)]}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
lines.push(
|
|
193
|
+
"# HELP opencodex_physical_sends_total Upstream sends made by finalized logical requests in this process.",
|
|
194
|
+
"# TYPE opencodex_physical_sends_total counter",
|
|
195
|
+
);
|
|
196
|
+
for (const protocol of REQUEST_METRICS_PROTOCOLS) {
|
|
197
|
+
lines.push(`opencodex_physical_sends_total${sampleLabels(protocol)} ${physicalSends[protocolCell(protocol)]}`);
|
|
198
|
+
}
|
|
199
|
+
lines.push(
|
|
200
|
+
"# HELP opencodex_recoveries_total Distinct recovery kinds observed per physical attempt in this process.",
|
|
201
|
+
"# TYPE opencodex_recoveries_total counter",
|
|
202
|
+
);
|
|
203
|
+
for (const protocol of REQUEST_METRICS_PROTOCOLS) {
|
|
204
|
+
for (const recovery of REQUEST_METRICS_RECOVERY_CLASSES) {
|
|
205
|
+
lines.push(`opencodex_recoveries_total{protocol="${protocol}",recovery="${recovery}"} ${recoveries[protocolCell(protocol)]![recoveryCell(recovery)]}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
appendHistogram(lines, "opencodex_request_duration_seconds", "Finalized logical request duration in seconds.", durations, REQUEST_DURATION_BUCKETS_SECONDS);
|
|
209
|
+
appendHistogram(lines, "opencodex_ttft_seconds", "Observed time to first output in seconds.", ttft, REQUEST_TTFT_BUCKETS_SECONDS);
|
|
210
|
+
lines.push(
|
|
211
|
+
"# HELP opencodex_ttft_missing_total Finalized logical requests without an observed time to first output.",
|
|
212
|
+
"# TYPE opencodex_ttft_missing_total counter",
|
|
213
|
+
);
|
|
214
|
+
for (const protocol of REQUEST_METRICS_PROTOCOLS) {
|
|
215
|
+
for (const result of REQUEST_METRICS_RESULTS) {
|
|
216
|
+
lines.push(`opencodex_ttft_missing_total${sampleLabels(protocol, result)} ${missingTtft[protocolCell(protocol)]![resultCell(result)]}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
lines.push(
|
|
220
|
+
"# HELP opencodex_metrics_process_start_time_seconds Unix time when this process metrics owner started.",
|
|
221
|
+
"# TYPE opencodex_metrics_process_start_time_seconds gauge",
|
|
222
|
+
`opencodex_metrics_process_start_time_seconds ${processStartTimeSeconds}`,
|
|
223
|
+
);
|
|
224
|
+
return `${lines.join("\n")}\n`;
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
resetForTests(): void {
|
|
228
|
+
logicalRequests = matrix(REQUEST_METRICS_PROTOCOLS.length, REQUEST_METRICS_RESULTS.length);
|
|
229
|
+
physicalSends = Array.from({ length: REQUEST_METRICS_PROTOCOLS.length }, () => 0);
|
|
230
|
+
recoveries = matrix(REQUEST_METRICS_PROTOCOLS.length, REQUEST_METRICS_RECOVERY_CLASSES.length);
|
|
231
|
+
durations = histograms(REQUEST_DURATION_BUCKETS_SECONDS);
|
|
232
|
+
ttft = histograms(REQUEST_TTFT_BUCKETS_SECONDS);
|
|
233
|
+
missingTtft = matrix(REQUEST_METRICS_PROTOCOLS.length, REQUEST_METRICS_RESULTS.length);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
sealRequestAttemptIdentity,
|
|
14
14
|
recordAttemptCredentialSource,
|
|
15
15
|
} from "../request-log";
|
|
16
|
+
import { noteAttemptRecoveryWithheld } from "../request-log";
|
|
16
17
|
import { waitForProviderRequestSlot } from "../../providers/request-pacing";
|
|
17
18
|
import { providerFetch, fetchWithHeaderTimeout, safeHostLabel } from "./fetch-helpers";
|
|
18
19
|
import {
|
|
@@ -46,7 +47,12 @@ import {
|
|
|
46
47
|
import { shouldAttemptImageTierRetry } from "../image-retry";
|
|
47
48
|
import { readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
|
|
48
49
|
import { isCyberPolicyCode, CYBER_POLICY_FALLBACK_MESSAGE, CYBER_POLICY_ERROR_CODE } from "../../lib/errors";
|
|
49
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
readResponseBodyWithInactivity,
|
|
52
|
+
readResponseStreamWithInactivity,
|
|
53
|
+
ResponseBodyInactivityError,
|
|
54
|
+
} from "../../lib/response-body-inactivity";
|
|
55
|
+
import { resolveStallTimeoutSec } from "../../stall-timeout";
|
|
50
56
|
import { guardTerminalEventStream } from "./terminal-guard";
|
|
51
57
|
|
|
52
58
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
@@ -78,6 +84,7 @@ export function createAdapterContinuations(
|
|
|
78
84
|
| "genericFailoverAccountId"
|
|
79
85
|
| "genericFailovers"
|
|
80
86
|
| "applyFailoverSnapshot"
|
|
87
|
+
| "replayOAuthCredentialSnapshot"
|
|
81
88
|
| "noteRoutedAttemptSend"
|
|
82
89
|
>,
|
|
83
90
|
sidecarState: Pick<ResponsesSidecarAuth, "routedCompaction">,
|
|
@@ -85,6 +92,7 @@ export function createAdapterContinuations(
|
|
|
85
92
|
ResponsesSendBudget,
|
|
86
93
|
| "adapterDispatchBudget"
|
|
87
94
|
| "noteAdapterPhysicalSend"
|
|
95
|
+
| "noteAdapterRecoveryWithheld"
|
|
88
96
|
| "remainingTransientSendBudget"
|
|
89
97
|
| "noteTransientSends"
|
|
90
98
|
| "reserveCredentialHop"
|
|
@@ -112,9 +120,11 @@ export function createAdapterContinuations(
|
|
|
112
120
|
const { route, translatorBudget, inboundWire, parsed } = requestState;
|
|
113
121
|
const { routedCompaction } = sidecarState;
|
|
114
122
|
const { upstream, connectMs, rateLimitPolicy, stallTimeoutMs } = adapterExchange;
|
|
123
|
+
const bodyInactivityMs = resolveStallTimeoutSec(config.stallTimeoutSec) * 1000;
|
|
115
124
|
const {
|
|
116
125
|
adapterDispatchBudget,
|
|
117
126
|
noteAdapterPhysicalSend,
|
|
127
|
+
noteAdapterRecoveryWithheld,
|
|
118
128
|
remainingTransientSendBudget,
|
|
119
129
|
noteTransientSends,
|
|
120
130
|
reserveCredentialHop,
|
|
@@ -193,6 +203,7 @@ export function createAdapterContinuations(
|
|
|
193
203
|
timeoutMs: connectMs,
|
|
194
204
|
sendBudget: adapterDispatchBudget,
|
|
195
205
|
onPhysicalSend: send => noteAdapterPhysicalSend(continuationEstimate, send),
|
|
206
|
+
onRecoveryWithheld: noteAdapterRecoveryWithheld,
|
|
196
207
|
stream: nextParsed.stream,
|
|
197
208
|
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
198
209
|
pacingSlotAcquired: true,
|
|
@@ -263,12 +274,12 @@ export function createAdapterContinuations(
|
|
|
263
274
|
// Same-target 429 wait-and-retry (opt-in `retryOn429`) before key/account failover:
|
|
264
275
|
// a primary-key rate-limit blip replays on the SAME key, matching the main recovery
|
|
265
276
|
// loop; only after the attempts are exhausted does the continuation fail over.
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
while (
|
|
278
|
+
response.status === 429
|
|
268
279
|
// A synthesized replay refusal is not a rate limit; replaying the continuation on
|
|
269
280
|
// it would re-send a turn whose first send may already have been processed.
|
|
270
281
|
&& !isNonReplayableResponse(response)
|
|
271
|
-
|
|
282
|
+
&& rateLimitPolicy !== null
|
|
272
283
|
&& adapterExchange.rateLimitRetries < rateLimitPolicy.attempts
|
|
273
284
|
// The main recovery loop and the passthrough ladder both consult the shared remainder
|
|
274
285
|
// here; this loop did not, so a request whose budget was already spent could still
|
|
@@ -327,7 +338,7 @@ export function createAdapterContinuations(
|
|
|
327
338
|
route.provider = rotated;
|
|
328
339
|
invalidateSameTargetRequest();
|
|
329
340
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
330
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
341
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
331
342
|
config.cacheRetention,
|
|
332
343
|
);
|
|
333
344
|
bindRouteReasoningReplayScope({
|
|
@@ -348,11 +359,11 @@ export function createAdapterContinuations(
|
|
|
348
359
|
continue;
|
|
349
360
|
}
|
|
350
361
|
}
|
|
351
|
-
|
|
352
|
-
|
|
362
|
+
if (
|
|
363
|
+
response.status === 429
|
|
364
|
+
&& transportState.anthropicPoolAccountId
|
|
353
365
|
&& !isNonReplayableResponse(response)
|
|
354
|
-
|
|
355
|
-
&& transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST
|
|
366
|
+
&& transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST
|
|
356
367
|
) {
|
|
357
368
|
const nextAccountId = rotateAnthropicAccountOn429(
|
|
358
369
|
config,
|
|
@@ -373,7 +384,7 @@ export function createAdapterContinuations(
|
|
|
373
384
|
invalidateSameTargetRequest();
|
|
374
385
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", admitted.accountId, config);
|
|
375
386
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
376
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
387
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
377
388
|
config.cacheRetention,
|
|
378
389
|
);
|
|
379
390
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
|
|
@@ -390,11 +401,11 @@ export function createAdapterContinuations(
|
|
|
390
401
|
// 429 stayed terminal even with failover fully active -- the same class of divergence the
|
|
391
402
|
// two sidecars already produced once. Request-local state is shared with the other arms so
|
|
392
403
|
// the per-request bound cannot be silently re-armed by reaching a different loop.
|
|
393
|
-
|
|
394
|
-
|
|
404
|
+
if (
|
|
405
|
+
response.status === 429
|
|
406
|
+
&& transportState.genericFailoverAccountId
|
|
395
407
|
&& !isNonReplayableResponse(response)
|
|
396
|
-
|
|
397
|
-
&& transportState.genericFailovers < GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST
|
|
408
|
+
&& transportState.genericFailovers < GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST
|
|
398
409
|
&& isGenericOAuthFailoverEnabled(config, route.providerName)
|
|
399
410
|
) {
|
|
400
411
|
// Intersection with the shared request budget. The continuation loop re-sends the
|
|
@@ -416,8 +427,13 @@ export function createAdapterContinuations(
|
|
|
416
427
|
route.providerName,
|
|
417
428
|
transportState.genericFailoverAccountId,
|
|
418
429
|
response.headers.get("retry-after"),
|
|
430
|
+
Date.now(),
|
|
431
|
+
route.modelId,
|
|
419
432
|
)
|
|
420
433
|
: null;
|
|
434
|
+
// Eligible and refused by the shared budget, as opposed to eligible and finding no next
|
|
435
|
+
// account: the two produce the same response and need different follow-ups (#5044).
|
|
436
|
+
if (!hop.allowed) noteAttemptRecoveryWithheld(logCtx.activeAttempt, "rotation-send-budget");
|
|
421
437
|
if (!nextAccountId) hop.permit?.release();
|
|
422
438
|
if (nextAccountId) {
|
|
423
439
|
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
@@ -433,9 +449,25 @@ export function createAdapterContinuations(
|
|
|
433
449
|
if (applied) {
|
|
434
450
|
invalidateSameTargetRequest();
|
|
435
451
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
436
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
452
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
437
453
|
config.cacheRetention,
|
|
438
454
|
);
|
|
455
|
+
bindRouteReasoningReplayScope({
|
|
456
|
+
parsed: nextParsed,
|
|
457
|
+
providerName: route.providerName,
|
|
458
|
+
provider: route.provider,
|
|
459
|
+
adapterName: transportState.activeAdapter.name,
|
|
460
|
+
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
461
|
+
});
|
|
462
|
+
// Response persistence closes over the outer parsed request; keep its owner binding in
|
|
463
|
+
// sync with the terminal-guard clone that builds the rotated continuation request.
|
|
464
|
+
bindRouteReasoningReplayScope({
|
|
465
|
+
parsed,
|
|
466
|
+
providerName: route.providerName,
|
|
467
|
+
provider: route.provider,
|
|
468
|
+
adapterName: transportState.activeAdapter.name,
|
|
469
|
+
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
470
|
+
});
|
|
439
471
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
|
|
440
472
|
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
|
|
441
473
|
// The replay goes out on the next iteration. An adapter that owns its ladder
|
|
@@ -493,23 +525,35 @@ export function createAdapterContinuations(
|
|
|
493
525
|
}
|
|
494
526
|
|
|
495
527
|
try {
|
|
496
|
-
//
|
|
497
|
-
// reader
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
528
|
+
// Each successful continuation owns a fresh pending-read deadline and an abort
|
|
529
|
+
// listener that can cancel its reader even while the parser holds the body lock.
|
|
530
|
+
// The shared signal is not aborted on timeout: this generator must still emit
|
|
531
|
+
// the error that the already-live Responses bridge turns into a terminal.
|
|
532
|
+
if (nextParsed.stream) {
|
|
533
|
+
yield* readResponseStreamWithInactivity(
|
|
534
|
+
response,
|
|
535
|
+
upstream.signal,
|
|
536
|
+
bodyInactivityMs,
|
|
537
|
+
guarded => transportState.activeAdapter.parseStream(guarded, translatorBudget, logCtx.activeTierMetadata),
|
|
538
|
+
);
|
|
539
|
+
} else if (transportState.activeAdapter.parseResponse) {
|
|
540
|
+
yield* await readResponseBodyWithInactivity(
|
|
541
|
+
response,
|
|
542
|
+
upstream.signal,
|
|
543
|
+
bodyInactivityMs,
|
|
544
|
+
guarded => transportState.activeAdapter.parseResponse!(guarded, translatorBudget, logCtx.activeTierMetadata),
|
|
545
|
+
);
|
|
546
|
+
} else {
|
|
547
|
+
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
548
|
+
yield { type: "error", message: "Provider continuation does not support response parsing" };
|
|
510
549
|
}
|
|
511
550
|
} catch (error) {
|
|
512
|
-
|
|
551
|
+
// Classify the thrown error before reading signal state: cancelling the stalled
|
|
552
|
+
// source can synchronously abort the shared signal, which would otherwise report
|
|
553
|
+
// this timeout as a client cancellation.
|
|
554
|
+
if (error instanceof ResponseBodyInactivityError) {
|
|
555
|
+
yield { type: "error", message: "Provider continuation response body stalled before completing", status: 504 };
|
|
556
|
+
} else if (options.abortSignal?.aborted || upstream.signal.aborted) {
|
|
513
557
|
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
514
558
|
} else {
|
|
515
559
|
yield { type: "error", message: `Provider continuation parse failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
@@ -14,6 +14,12 @@ import { rememberResponseState } from "../../responses/state";
|
|
|
14
14
|
import { trackStreamLifetime } from "../lifecycle";
|
|
15
15
|
import { awaitThoughtSignatureDurability } from "../../responses/thought-signature-replay";
|
|
16
16
|
import { adapterResponseReachedServingTerminal } from "./core-replay";
|
|
17
|
+
import {
|
|
18
|
+
readResponseBodyWithInactivity,
|
|
19
|
+
readResponseStreamWithInactivity,
|
|
20
|
+
ResponseBodyInactivityError,
|
|
21
|
+
} from "../../lib/response-body-inactivity";
|
|
22
|
+
import { resolveStallTimeoutSec } from "../../stall-timeout";
|
|
17
23
|
|
|
18
24
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
19
25
|
export async function deliverAdapterResponse(
|
|
@@ -61,14 +67,33 @@ export async function deliverAdapterResponse(
|
|
|
61
67
|
notifyResponseComplete,
|
|
62
68
|
} = responseEffects;
|
|
63
69
|
const { routedCompaction } = sidecarState;
|
|
70
|
+
const bodyInactivityMs = resolveStallTimeoutSec(config.stallTimeoutSec) * 1000;
|
|
64
71
|
|
|
65
72
|
|
|
66
73
|
if (parsed.stream) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
// The continuation legs classify a stalled body themselves; the initial stream needs the
|
|
75
|
+
// same mapping or the bridge catch reports this upstream timeout as a 500 proxy_error.
|
|
76
|
+
const initialEventStream = (async function* (): AsyncGenerator<AdapterEvent> {
|
|
77
|
+
try {
|
|
78
|
+
yield* readResponseStreamWithInactivity(
|
|
79
|
+
upstreamResponse,
|
|
80
|
+
upstream.signal,
|
|
81
|
+
bodyInactivityMs,
|
|
82
|
+
response => transportState.activeAdapter.parseStream(response, translatorBudget, logCtx.activeTierMetadata),
|
|
83
|
+
);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof ResponseBodyInactivityError) {
|
|
86
|
+
yield {
|
|
87
|
+
type: "error",
|
|
88
|
+
message: "Upstream response body stalled before completing",
|
|
89
|
+
status: 504,
|
|
90
|
+
errorType: "upstream_error",
|
|
91
|
+
};
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
72
97
|
const eventStream = terminalGuardEnabled
|
|
73
98
|
? guardTerminalEventStream({
|
|
74
99
|
parsed,
|
|
@@ -137,10 +162,11 @@ export async function deliverAdapterResponse(
|
|
|
137
162
|
if (transportState.activeAdapter.parseResponse) {
|
|
138
163
|
let events: AdapterEvent[];
|
|
139
164
|
try {
|
|
140
|
-
const initialEvents = await
|
|
165
|
+
const initialEvents = await readResponseBodyWithInactivity(
|
|
141
166
|
upstreamResponse,
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
upstream.signal,
|
|
168
|
+
bodyInactivityMs,
|
|
169
|
+
response => transportState.activeAdapter.parseResponse!(response, translatorBudget, logCtx.activeTierMetadata),
|
|
144
170
|
);
|
|
145
171
|
let guardedEvents: AdapterEvent[];
|
|
146
172
|
if (terminalGuardEnabled) {
|
|
@@ -164,6 +190,11 @@ export async function deliverAdapterResponse(
|
|
|
164
190
|
} else {
|
|
165
191
|
events = guardedEvents;
|
|
166
192
|
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error instanceof ResponseBodyInactivityError) {
|
|
195
|
+
return formatErrorResponse(504, "upstream_error", "Upstream response body stalled before completing");
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
167
198
|
} finally {
|
|
168
199
|
cleanupUpstreamAbort();
|
|
169
200
|
}
|