fix(timeout): align cc-compatible timeout header with runtime config

This commit is contained in:
R.D.
2026-04-12 23:21:22 -04:00
parent 531d49fa00
commit a58db791e1
6 changed files with 36 additions and 1 deletions

View File

@@ -449,6 +449,7 @@ GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1
# ── Upstream fetch (provider calls) ──
# FETCH_TIMEOUT_MS=600000 # Total request timeout (default: 600000 = 10 min)
# # Also drives anthropic-compatible-cc-* X-Stainless-Timeout.
# FETCH_HEADERS_TIMEOUT_MS=600000 # Time to receive response headers
# FETCH_BODY_TIMEOUT_MS=600000 # Time to receive full response body
# FETCH_CONNECT_TIMEOUT_MS=30000 # TCP connection establishment (default: 30s)

View File

@@ -799,6 +799,8 @@ For most deployments, you only need:
Backward compatibility is preserved: existing `FETCH_TIMEOUT_MS`, `API_BRIDGE_PROXY_TIMEOUT_MS`, and other per-layer timeout vars still work and override the shared baseline.
For Claude Code-compatible upstreams (`anthropic-compatible-cc-*`), OmniRoute also derives the outbound `X-Stainless-Timeout` header from the resolved fetch timeout so provider-side read timeouts stay aligned with your env configuration.
Advanced overrides are available if you need finer control:
| Variable | Default | Purpose |

View File

@@ -1,5 +1,6 @@
import { createHash, randomUUID } from "node:crypto";
import { getStainlessTimeoutSeconds } from "@/shared/utils/runtimeTimeouts";
import { prepareClaudeRequest } from "../translator/helpers/claudeHelper.ts";
export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-";
@@ -12,6 +13,9 @@ export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA =
export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = "claude-cli/2.1.89 (external, sdk-cli)";
export const CLAUDE_CODE_COMPATIBLE_BILLING_HEADER =
"x-anthropic-billing-header: cc_version=2.1.89.728; cc_entrypoint=sdk-cli; cch=00000;";
export const CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS = getStainlessTimeoutSeconds(
process.env
);
type HeaderLike =
| Headers
@@ -97,7 +101,7 @@ export function buildClaudeCodeCompatibleHeaders(
"x-app": "cli",
"User-Agent": CLAUDE_CODE_COMPATIBLE_USER_AGENT,
"X-Stainless-Retry-Count": "0",
"X-Stainless-Timeout": "300",
"X-Stainless-Timeout": String(CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS),
"X-Stainless-Lang": "js",
"X-Stainless-Package-Version": "0.74.0",
"X-Stainless-OS": "MacOS",

View File

@@ -121,6 +121,14 @@ export function getUpstreamTimeoutConfig(
};
}
export function getStainlessTimeoutSeconds(
env: EnvSource = process.env,
logger?: TimeoutLogger
): number {
const { fetchTimeoutMs } = getUpstreamTimeoutConfig(env, logger);
return Math.max(1, Math.ceil(fetchTimeoutMs / 1_000));
}
export function getTlsClientTimeoutConfig(
env: EnvSource = process.env,
logger?: TimeoutLogger

View File

@@ -5,6 +5,7 @@ const {
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH,
CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS,
CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS,
isClaudeCodeCompatibleProvider,
stripAnthropicMessagesSuffix,
stripClaudeCodeCompatibleEndpointSuffix,
@@ -46,6 +47,10 @@ test("buildClaudeCodeCompatibleHeaders emits stream-aware auth headers and sessi
assert.equal(streamHeaders.Accept, "text/event-stream");
assert.equal(streamHeaders["x-api-key"], "sk-demo");
assert.equal(streamHeaders["X-Claude-Code-Session-Id"], "session-123");
assert.equal(
streamHeaders["X-Stainless-Timeout"],
String(CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS)
);
assert.equal(jsonHeaders.Accept, "application/json");
assert.equal(jsonHeaders["X-Claude-Code-Session-Id"], undefined);
});

View File

@@ -65,6 +65,21 @@ test("TLS client timeout defaults to FETCH_TIMEOUT_MS and can be overridden", ()
assert.equal(overriddenConfig.timeoutMs, 720000);
});
test("stainless timeout derives from fetch timeout and rounds up to whole seconds", () => {
assert.equal(
runtimeTimeouts.getStainlessTimeoutSeconds({
REQUEST_TIMEOUT_MS: "1200000",
}),
1200
);
assert.equal(
runtimeTimeouts.getStainlessTimeoutSeconds({
FETCH_TIMEOUT_MS: "600001",
}),
601
);
});
test("API bridge timeouts align request timeout with long proxy timeout by default", () => {
const config = runtimeTimeouts.getApiBridgeTimeoutConfig({
API_BRIDGE_PROXY_TIMEOUT_MS: "600000",