mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-19 13:23:50 +03:00
fix(ci): repair the API Route Typecheck base-red blocking every PR (#14079)
The gate fails on the pure release/v3.8.51 tip (3 files above the frozen baseline), so every PR against the release is born red on it. None of the three is a PR defect — they are drift from merged work: - src/lib/usage/glmResetCards.ts: entered the gate's scope when #12754 added the route that imports it. runWithProxyContext is an untyped async helper (Promise<any>), so runWithConnectionFetch<T> could not return T. Every call site passes an async callback and awaits it — declare that contract: fn: () => Promise<T> → Promise<T>. - src/sse/handlers/chat.ts: handleSingleModelChat had no return annotation, so runWithTransientBackendRetry<T extends ResponseLike> fell back to the constraint and the value could no longer feed withSessionHeader(Response). Annotated Promise<Response> (every return path builds a Response). - src/app/api/internal/codex-responses-ws/route.ts: the bridge helpers return either { error: Response } or a payload, but as unannotated object-literal unions TypeScript synthesised error?: undefined on the success member, and every "error" in x guard stopped narrowing (TS2339 x5 on the destructure). Explicit return types keep the discriminant real; the ApiKeyMetadata alias now points at the policy shape (the wider one both sources are assignable to), which clears the TS2740 self-mismatch; logger.warn → log.warn (the module logger factory has no .warn). check-api-typecheck.mjs: OK — 283 errors, baseline ratcheted DOWN from 294 (codex-responses-ws 7→4 TS2339, TS2740 1→0; combos/test and keys/[id] 1→0). typecheck:core clean; 43/43 unit tests in the touched areas green.
This commit is contained in:
committed by
GitHub
parent
1b484c57a0
commit
24fc202d9f
@@ -0,0 +1 @@
|
||||
- **ci:** repair the `API Route Typecheck` base-red on `release/v3.8.51` — type the awaited-callback contract of `runWithConnectionFetch` (glmResetCards), annotate `handleSingleModelChat(): Promise<Response>`, and give the codex-responses-ws bridge helpers real `{ error } | payload` discriminants so `"error" in x` narrows again; baseline ratcheted 294 → 283 (no widening).
|
||||
@@ -48,8 +48,7 @@
|
||||
"TS2322": 1
|
||||
},
|
||||
"src/app/api/combos/test/route.ts": {
|
||||
"TS2345": 1,
|
||||
"TS2339": 1
|
||||
"TS2345": 1
|
||||
},
|
||||
"src/app/api/compression/compare/route.ts": {
|
||||
"TS2345": 1
|
||||
@@ -70,11 +69,7 @@
|
||||
"TS2554": 1
|
||||
},
|
||||
"src/app/api/internal/codex-responses-ws/route.ts": {
|
||||
"TS2740": 1,
|
||||
"TS2339": 7
|
||||
},
|
||||
"src/app/api/keys/[id]/route.ts": {
|
||||
"TS2339": 1
|
||||
"TS2339": 4
|
||||
},
|
||||
"src/app/api/local/redis/start/route.ts": {
|
||||
"TS2339": 1
|
||||
|
||||
@@ -34,7 +34,10 @@ import {
|
||||
validateCodexWsDecision,
|
||||
} from "@/lib/reasoningRouting/policy";
|
||||
import { resolveRequestRoutingTags } from "@/domain/tagRouter";
|
||||
import { validateApiKeyRoutingTarget } from "@/shared/utils/apiKeyPolicy";
|
||||
import {
|
||||
validateApiKeyRoutingTarget,
|
||||
type ApiKeyMetadata as PolicyApiKeyMetadata,
|
||||
} from "@/shared/utils/apiKeyPolicy";
|
||||
import { persistResponsesWsCallHistory } from "./history";
|
||||
import { applyResponsesWsCompression } from "./compression";
|
||||
import { getComboByName } from "@/lib/db/combos";
|
||||
@@ -50,7 +53,52 @@ const executor = new CodexExecutor();
|
||||
const log = logger("RESPONSES_WS");
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
type ApiKeyMetadata = Awaited<ReturnType<typeof getApiKeyMetadata>>;
|
||||
// Key metadata reaches this bridge from two sources that each declare their own
|
||||
// shape: `getApiKeyMetadata()` (every field required) and `enforceApiKeyPolicy()`
|
||||
// (every field optional). The policy shape is the wider of the two and the db
|
||||
// shape is assignable to it, so it is the only one that can hold both — pinning
|
||||
// the alias to the db shape is what produced the "Type 'ApiKeyMetadata' is
|
||||
// missing … from type 'ApiKeyMetadata'" mismatch at the policy boundary.
|
||||
type ApiKeyMetadata = PolicyApiKeyMetadata | null;
|
||||
|
||||
/**
|
||||
* Bridge helpers below either fail with a ready-made HTTP response or return
|
||||
* their success payload. `error` must exist on exactly ONE member of each union:
|
||||
* for an unannotated object-literal union TypeScript synthesises `error?:
|
||||
* undefined` on the success member, and `"error" in x` then keeps that member
|
||||
* too — which is how every `if ("error" in context)` guard in this file silently
|
||||
* stopped narrowing. Annotating the returns keeps the discriminant real.
|
||||
*/
|
||||
type CodexWsFailure = { error: Response };
|
||||
|
||||
type CodexWsReasoningRoute = {
|
||||
decision: Awaited<ReturnType<typeof resolveReasoningRoutingRule>>;
|
||||
intent: ReturnType<typeof extractReasoningIntent>;
|
||||
sourceModels: Awaited<ReturnType<typeof resolveReasoningSourceModels>>;
|
||||
routingTags: ReturnType<typeof resolveRequestRoutingTags>;
|
||||
};
|
||||
|
||||
type CodexWsCredentials = {
|
||||
credentials: NonNullable<Awaited<ReturnType<typeof checkAndRefreshToken>>>;
|
||||
leaseId: string;
|
||||
};
|
||||
|
||||
type CodexWsRequestContext = CodexWsReasoningRoute & {
|
||||
authRequest: Request;
|
||||
apiKey: string | null;
|
||||
responseBody: JsonRecord;
|
||||
requestedModel: string;
|
||||
clientHeaders: Record<string, string>;
|
||||
metadata: ApiKeyMetadata;
|
||||
allowedConnections: string[] | null;
|
||||
};
|
||||
|
||||
type CodexWsUpstreamContext = CodexWsRequestContext &
|
||||
CodexWsCredentials & {
|
||||
provider: string;
|
||||
model: string;
|
||||
reasoningDecision: Awaited<ReturnType<typeof resolveReasoningRoutingRule>>;
|
||||
};
|
||||
|
||||
const bridgePayloadSchema = z
|
||||
.object({
|
||||
@@ -325,10 +373,10 @@ async function enforceCodexWsApiKeyPolicy(
|
||||
async function prepareReasoningRoute(
|
||||
authRequest: Request,
|
||||
apiKey: string | null,
|
||||
metadata: ApiKeyMetadata | null,
|
||||
metadata: ApiKeyMetadata,
|
||||
requestedModel: string,
|
||||
responseBody: JsonRecord
|
||||
) {
|
||||
): Promise<CodexWsFailure | CodexWsReasoningRoute> {
|
||||
const reasoningIntent = extractReasoningIntent(requestedModel, responseBody);
|
||||
const sourceModels = await resolveReasoningSourceModels(reasoningIntent.model, (model) =>
|
||||
resolveCodexWsModelInfo(model, getModelInfo)
|
||||
@@ -373,7 +421,7 @@ async function resolveCodexCredentials(
|
||||
provider: string,
|
||||
model: string,
|
||||
allowedConnections: string[] | null
|
||||
) {
|
||||
): Promise<CodexWsFailure | CodexWsCredentials> {
|
||||
const excludedConnectionIds: string[] = [];
|
||||
let credentials: Awaited<ReturnType<typeof getProviderCredentialsWithQuotaPreflight>> = null;
|
||||
|
||||
@@ -428,7 +476,9 @@ async function resolveCodexCredentials(
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveCodexRequestContext(body: JsonRecord) {
|
||||
async function resolveCodexRequestContext(
|
||||
body: JsonRecord
|
||||
): Promise<CodexWsFailure | CodexWsRequestContext> {
|
||||
if (!isFeatureFlagEnabled("OMNIROUTE_CODEX_WS_ENABLED")) {
|
||||
return {
|
||||
error: jsonError(503, "codex_ws_disabled", "Codex Responses WebSocket transport is disabled"),
|
||||
@@ -477,7 +527,7 @@ async function resolveCodexRequestContext(body: JsonRecord) {
|
||||
requestedModel,
|
||||
responseBody
|
||||
);
|
||||
if (reasoningRoute.error) return { error: reasoningRoute.error };
|
||||
if ("error" in reasoningRoute) return reasoningRoute;
|
||||
return {
|
||||
authRequest,
|
||||
apiKey,
|
||||
@@ -491,8 +541,8 @@ async function resolveCodexRequestContext(body: JsonRecord) {
|
||||
}
|
||||
|
||||
async function resolveCodexUpstreamContext(
|
||||
context: Awaited<ReturnType<typeof resolveCodexRequestContext>>
|
||||
) {
|
||||
context: CodexWsFailure | CodexWsRequestContext
|
||||
): Promise<CodexWsFailure | CodexWsUpstreamContext> {
|
||||
if ("error" in context) return context;
|
||||
const routedModel = context.decision?.targetModel ?? context.requestedModel;
|
||||
const modelInfo = await resolveCodexWsModelInfo(routedModel, getModelInfo);
|
||||
@@ -512,7 +562,7 @@ async function resolveCodexUpstreamContext(
|
||||
model,
|
||||
context.allowedConnections
|
||||
);
|
||||
if (credentialResult.error) return credentialResult;
|
||||
if ("error" in credentialResult) return credentialResult;
|
||||
let reasoningDecision = context.decision;
|
||||
if (!reasoningDecision) {
|
||||
try {
|
||||
@@ -563,7 +613,7 @@ async function resolveCodexProxy(provider: string): Promise<string | undefined>
|
||||
try {
|
||||
return proxyConfigToUrl(await resolveProxy(provider)) || undefined;
|
||||
} catch (err) {
|
||||
logger.warn(`[codex-responses-ws] proxy resolution failed: ${sanitizeErrorMessage(err)}`);
|
||||
log.warn(`[codex-responses-ws] proxy resolution failed: ${sanitizeErrorMessage(err)}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,13 @@ const OPERATION_LEASE_OWNER_PREFIX = "vlo_";
|
||||
type ProxyLike = Parameters<typeof runWithProxyContext>[0];
|
||||
|
||||
/** A connection with no proxy must go explicitly direct — passing null to
|
||||
* runWithProxyContext would inherit an ambient context instead. */
|
||||
function runWithConnectionFetch<T>(proxy: ProxyLike, fn: () => T): T {
|
||||
* runWithProxyContext would inherit an ambient context instead.
|
||||
*
|
||||
* `runWithProxyContext` is an untyped `async` JS helper (`Promise<any>`), so the
|
||||
* awaited-callback contract has to be stated here: every call site passes an
|
||||
* async callback and awaits the result. Declaring `fn: () => Promise<T>` keeps
|
||||
* `runWithDirectFetchContext<Promise<T>>` returning `Promise<T>` too. */
|
||||
function runWithConnectionFetch<T>(proxy: ProxyLike, fn: () => Promise<T>): Promise<T> {
|
||||
return proxy ? runWithProxyContext(proxy, fn) : runWithDirectFetchContext(fn);
|
||||
}
|
||||
|
||||
|
||||
@@ -1430,7 +1430,7 @@ async function handleSingleModelChat(
|
||||
} = {},
|
||||
comboStrategy: string | null = null,
|
||||
isCombo: boolean = false
|
||||
) {
|
||||
): Promise<Response> {
|
||||
// 1. Resolve model → provider/model
|
||||
const resolved = await resolveModelOrError(
|
||||
modelStr,
|
||||
|
||||
Reference in New Issue
Block a user