mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
This commit is contained in:
committed by
GitHub
parent
9b3ad09b38
commit
c71eeae4ad
1
changelog.d/features/6354-per-model-timeout.md
Normal file
1
changelog.d/features/6354-per-model-timeout.md
Normal file
@@ -0,0 +1 @@
|
||||
- feat(sse): configurable per-model upstream header-response timeout override, precedence model > provider > global; applied to codex reasoning-heavy tiers (gpt-5.5-high/xhigh, gpt-5.6-\*-high/xhigh) (#6354)
|
||||
@@ -73,6 +73,21 @@ export function getModelsByProviderId(providerId: string): RegistryModel[] {
|
||||
return PROVIDER_MODELS[alias] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model-level upstream header-response timeout override, when the registry
|
||||
* entry for `modelId` sets one (#6354). Returns `undefined` when the model
|
||||
* isn't found or has no override, so callers can fall through to the
|
||||
* provider-level/global defaults unchanged.
|
||||
*/
|
||||
export function getModelTimeoutMs(aliasOrId: string, modelId: string): number | undefined {
|
||||
// Callers (e.g. chatCore's timeout resolution) pass the raw provider id
|
||||
// ("codex"), not the public alias ("cx") that PROVIDER_MODELS is keyed by
|
||||
// — resolve id→alias the same way getProviderModels()/getModelsByProviderId()
|
||||
// do, so the override actually resolves (#6354).
|
||||
const alias = PROVIDER_ID_TO_ALIAS[aliasOrId] || aliasOrId;
|
||||
return getProviderModel(alias, modelId)?.timeoutMs;
|
||||
}
|
||||
|
||||
const CLAUDE_MODEL_PATTERN = /(?:^|[\/._-])claude(?:[._-]|$)/;
|
||||
const CLAUDE_MAX_EFFORT_UNSUPPORTED_FAMILY_PATTERNS = [/(?:^|[\/._-])haiku(?:[._-]|$)/] as const;
|
||||
const ANTHROPIC_COMPATIBLE_PREFIX = "anthropic-compatible-";
|
||||
|
||||
@@ -43,11 +43,15 @@ export const codexProvider: RegistryEntry = {
|
||||
id: "gpt-5.6-sol-xhigh",
|
||||
name: "GPT 5.6 Sol (xHigh)",
|
||||
...GPT_5_6_CODEX_CAPABILITIES,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-sol-high",
|
||||
name: "GPT 5.6 Sol (High)",
|
||||
...GPT_5_6_CODEX_CAPABILITIES,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-sol-medium",
|
||||
@@ -78,11 +82,15 @@ export const codexProvider: RegistryEntry = {
|
||||
id: "gpt-5.6-terra-xhigh",
|
||||
name: "GPT 5.6 Terra (xHigh)",
|
||||
...GPT_5_6_CODEX_CAPABILITIES,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-terra-high",
|
||||
name: "GPT 5.6 Terra (High)",
|
||||
...GPT_5_6_CODEX_CAPABILITIES,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-terra-medium",
|
||||
@@ -108,11 +116,15 @@ export const codexProvider: RegistryEntry = {
|
||||
id: "gpt-5.6-luna-xhigh",
|
||||
name: "GPT 5.6 Luna (xHigh)",
|
||||
...GPT_5_6_CODEX_CAPABILITIES,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-luna-high",
|
||||
name: "GPT 5.6 Luna (High)",
|
||||
...GPT_5_6_CODEX_CAPABILITIES,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.6-luna-medium",
|
||||
@@ -149,6 +161,8 @@ export const codexProvider: RegistryEntry = {
|
||||
// #6191: input cap per reporter; TODO confirm exact value
|
||||
maxInputTokens: 272000,
|
||||
maxOutputTokens: 128000,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.5-high",
|
||||
@@ -158,6 +172,8 @@ export const codexProvider: RegistryEntry = {
|
||||
// #6191: input cap per reporter; TODO confirm exact value
|
||||
maxInputTokens: 272000,
|
||||
maxOutputTokens: 128000,
|
||||
// #6354: reasoning-heavy tier — more header-wait room than the global default.
|
||||
timeoutMs: 1200000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.5-medium",
|
||||
|
||||
@@ -71,6 +71,9 @@ export interface RegistryModel {
|
||||
* reasoning_content instead of failing with a DeepSeek 400 (#2900).
|
||||
*/
|
||||
interleavedField?: string;
|
||||
/** Per-model upstream header-response timeout override — precedes
|
||||
* `RegistryEntry.timeoutMs` and the global `FETCH_TIMEOUT_MS` (#6354). */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
// Reasoning models reject temperature, top_p, penalties, logprobs, n.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FETCH_TIMEOUT_MS } from "../../config/constants.ts";
|
||||
import { getModelTimeoutMs } from "../../config/providerModels.ts";
|
||||
import {
|
||||
getLoggedInputTokens,
|
||||
getLoggedOutputTokens,
|
||||
@@ -62,7 +63,16 @@ export function computeBillableTokens(usage: unknown): number {
|
||||
return getLoggedInputTokens(usage) + getLoggedOutputTokens(usage) + getReasoningTokens(usage);
|
||||
}
|
||||
|
||||
export function getExecutorTimeoutMs(executor: unknown): number {
|
||||
/** Resolves the model-level `timeoutMs` registry override, when both
|
||||
* `provider` and `model` are known and the model registers one (#6354). */
|
||||
function resolveModelTimeoutOverride(provider?: string, model?: string): number | undefined {
|
||||
if (!provider || !model) return undefined;
|
||||
const override = getModelTimeoutMs(provider, model);
|
||||
if (typeof override !== "number" || !Number.isFinite(override)) return undefined;
|
||||
return Math.max(0, Math.floor(override));
|
||||
}
|
||||
|
||||
function resolveProviderTimeoutMs(executor: unknown): number {
|
||||
const getTimeoutMs = (executor as { getTimeoutMs?: () => unknown } | null)?.getTimeoutMs;
|
||||
if (typeof getTimeoutMs !== "function") return FETCH_TIMEOUT_MS;
|
||||
|
||||
@@ -75,6 +85,19 @@ export function getExecutorTimeoutMs(executor: unknown): number {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the upstream header-response timeout in precedence order:
|
||||
* model-level override (registry `RegistryModel.timeoutMs`) → provider-level
|
||||
* override (`executor.getTimeoutMs()`) → global `FETCH_TIMEOUT_MS` default.
|
||||
* `provider`/`model` are optional so existing single-argument call sites
|
||||
* keep resolving to the provider/global chain unchanged (#6354).
|
||||
*/
|
||||
export function getExecutorTimeoutMs(executor: unknown, provider?: string, model?: string): number {
|
||||
const modelOverride = resolveModelTimeoutOverride(provider, model);
|
||||
if (modelOverride !== undefined) return modelOverride;
|
||||
return resolveProviderTimeoutMs(executor);
|
||||
}
|
||||
|
||||
export function normalizeExecutorResult(
|
||||
result:
|
||||
| Response
|
||||
@@ -111,7 +134,7 @@ export async function executeWithUpstreamStartTimeout<T>({
|
||||
log?: { warn?: (tag: string, message: string) => void } | null;
|
||||
execute: (signal: AbortSignal) => Promise<T>;
|
||||
}): Promise<T> {
|
||||
const timeoutMs = getExecutorTimeoutMs(executor);
|
||||
const timeoutMs = getExecutorTimeoutMs(executor, provider, model);
|
||||
if (timeoutMs <= 0) return execute(signal);
|
||||
if (signal.aborted) throw createAbortError(signal);
|
||||
|
||||
|
||||
49
tests/unit/upstream-timeout-model-override.test.ts
Normal file
49
tests/unit/upstream-timeout-model-override.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { getExecutorTimeoutMs } from "../../open-sse/handlers/chatCore/upstreamTimeouts.ts";
|
||||
import { FETCH_TIMEOUT_MS } from "../../open-sse/config/constants.ts";
|
||||
import { getModelTimeoutMs } from "../../open-sse/config/providerModels.ts";
|
||||
|
||||
test("model-level timeoutMs wins over provider-level and global", () => {
|
||||
const executor = { getTimeoutMs: () => 30000 };
|
||||
const result = getExecutorTimeoutMs(executor, "codex", "gpt-5.5-high");
|
||||
assert.equal(result, 1200000);
|
||||
assert.notEqual(result, 30000);
|
||||
assert.notEqual(result, FETCH_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
test("provider-level timeoutMs still wins over global when no model override exists (regression guard)", () => {
|
||||
const executor = { getTimeoutMs: () => 45000 };
|
||||
// No model registered for this provider/model combo -> no model override,
|
||||
// must still fall back to the provider-level executor.getTimeoutMs().
|
||||
const result = getExecutorTimeoutMs(executor, "codex", "gpt-5.5-medium");
|
||||
assert.equal(result, 45000);
|
||||
});
|
||||
|
||||
test("global FETCH_TIMEOUT_MS remains the fallback with neither override", () => {
|
||||
const executor = {};
|
||||
const result = getExecutorTimeoutMs(executor, "codex", "gpt-5.5-medium");
|
||||
assert.equal(result, FETCH_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
test("getExecutorTimeoutMs is backward compatible when called with only an executor", () => {
|
||||
const executor = { getTimeoutMs: () => 5000 };
|
||||
assert.equal(getExecutorTimeoutMs(executor), 5000);
|
||||
});
|
||||
|
||||
test("gpt-5.5-high resolves to its override; gpt-5.5-medium resolves to the provider/global default (reported-scenario guard)", () => {
|
||||
const executor = {};
|
||||
const highResult = getExecutorTimeoutMs(executor, "codex", "gpt-5.5-high");
|
||||
const mediumResult = getExecutorTimeoutMs(executor, "codex", "gpt-5.5-medium");
|
||||
assert.equal(highResult, 1200000);
|
||||
assert.equal(mediumResult, FETCH_TIMEOUT_MS);
|
||||
assert.notEqual(highResult, mediumResult);
|
||||
});
|
||||
|
||||
test("getModelTimeoutMs returns the registered override for reasoning-heavy codex tiers", () => {
|
||||
assert.equal(getModelTimeoutMs("codex", "gpt-5.5-high"), 1200000);
|
||||
assert.equal(getModelTimeoutMs("codex", "gpt-5.5-xhigh"), 1200000);
|
||||
assert.equal(getModelTimeoutMs("codex", "gpt-5.5-medium"), undefined);
|
||||
assert.equal(getModelTimeoutMs("codex", "nonexistent-model"), undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user