From 647b98eb8ae4d4ca3c16e1d515dc17a97e5993e4 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Thu, 20 Aug 2026 06:00:42 +0900 Subject: [PATCH 1/2] fix(chat): apply the Fast capability gate to the native passthrough Native /v1/chat/completions decided service_tier from chatServiceTier alone, so a provider declaring supportsServiceTier: false - or an exact-model false - still had the field forwarded. That is fail-open onto upstreams that reject it or bill differently for it. Route the native passthrough through the same resolved Fast policy the rest of the chat path uses, so a fail-closed or per-model declaration is honored on every surface rather than only the translated ones. Carries @olddonkey's #2075, rebased onto current dev. The only manual resolution was an import list in openai-chat.ts where dev had since added AdapterTierMetadata alongside this PR's decideTier and ResolvedFastPolicy; both sides are kept. Closes #1886 --- src/adapters/openai-chat.ts | 46 ++- src/server/chat-native.ts | 13 +- tests/fastwire-characterization-wire.test.ts | 26 +- tests/openai-chat-native-policy.test.ts | 341 +++++++++++++++++++ tests/openrouter-provider-routing.test.ts | 3 +- 5 files changed, 407 insertions(+), 22 deletions(-) create mode 100644 tests/openai-chat-native-policy.test.ts diff --git a/src/adapters/openai-chat.ts b/src/adapters/openai-chat.ts index c5338d5eaa..f6fce1a82c 100644 --- a/src/adapters/openai-chat.ts +++ b/src/adapters/openai-chat.ts @@ -14,12 +14,15 @@ import { buildNonOpenAIToolCatalogNudgeForTools, shouldInjectNonOpenAIToolCatalo import { openRouterProviderPayload, resolveOpenRouterRouting } from "../providers/openrouter-routing"; import { canForwardForeignServiceTierForChatModel, + fastPolicyForModel, supportsServiceTierForModel, } from "../providers/service-tier"; import { canonicalFastTierMarker, createAdapterTierMetadata, + decideTier, type AdapterTierMetadata, + type ResolvedFastPolicy, } from "../providers/fastwire"; import { openaiChatCompletionsUrl } from "./openai-chat-url"; import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema"; @@ -96,6 +99,8 @@ export function buildOpenAIChatPassthroughRequest( rawBody: Record, modelId: string, stream: boolean, + fastPolicy: ResolvedFastPolicy = fastPolicyForModel(provider, modelId, undefined, "chat"), + fastMode?: boolean, ): AdapterRequest { const { url, headers, hasCredential } = openAIChatTransport(provider); @@ -123,7 +128,16 @@ export function buildOpenAIChatPassthroughRequest( // `:` siblings the operator never opted out, silently returning prose. if (provider.noStructuredOutputModels?.includes(modelId)) delete body.response_format; - if (provider.chatServiceTier && rawBody.service_tier !== undefined) { + // Run the same complete Fast policy as the translated Chat path, including explicit + // fastMode and foreign-tier handling. On inherited canonical Fast, the passthrough still + // retains the caller's exact spelling; forced Fast uses the policy-owned wire value. + const callerTier = typeof rawBody.service_tier === "string" ? rawBody.service_tier : undefined; + const tierDecision = decideTier(fastPolicy, fastMode, callerTier); + if (tierDecision.kind === "set") { + body.service_tier = fastMode === undefined && canonicalFastTierMarker(callerTier) !== undefined + ? callerTier + : tierDecision.value; + } else if (tierDecision.kind === "forward-caller" && rawBody.service_tier !== undefined) { body.service_tier = rawBody.service_tier; } if (provider.promptCacheKey && rawBody.prompt_cache_key !== undefined) { @@ -1290,6 +1304,23 @@ function thinkingBudgetForEffort(parsed: OcxParsedRequest, reasoningEffort: stri return fraction === undefined ? undefined : Math.max(1, Math.floor(maxBudget * fraction)); } +function canSerializeOpenAIChatServiceTier( + provider: OcxProviderConfig, + modelId: string, + serviceTier: unknown, + tierDecision?: OcxParsedRequest["options"]["tierDecision"], +): boolean { + if (serviceTier === undefined) return false; + if (tierDecision !== undefined) { + return tierDecision.kind === "set" || tierDecision.kind === "forward-caller"; + } + const callerTier = typeof serviceTier === "string" ? serviceTier : undefined; + const callerCanonicalFast = canonicalFastTierMarker(callerTier) !== undefined; + const capability = supportsServiceTierForModel(provider, modelId); + const callerTierForwardAllowed = canForwardForeignServiceTierForChatModel(provider, modelId); + return callerTierForwardAllowed || (callerCanonicalFast && capability === true); +} + export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAdapter { return { name: "openai-chat", @@ -1312,13 +1343,12 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd // unclassified Chat routes remain behind the caller-forwarding opt-in. const serviceTier = parsed.options.serviceTier; const tierDecision = parsed.options.tierDecision; - const callerCanonicalFast = canonicalFastTierMarker(serviceTier) !== undefined; - const callerTierForwardAllowed = canForwardForeignServiceTierForChatModel(provider, parsed.modelId); - const canonicalFastCapability = callerCanonicalFast - && supportsServiceTierForModel(provider, parsed.modelId) === true; - const canSerializeServiceTier = tierDecision?.kind === "set" - || tierDecision?.kind === "forward-caller" - || (tierDecision === undefined && (callerTierForwardAllowed || canonicalFastCapability)); + const canSerializeServiceTier = canSerializeOpenAIChatServiceTier( + provider, + parsed.modelId, + serviceTier, + tierDecision, + ); if (canSerializeServiceTier && serviceTier !== undefined) { body.service_tier = serviceTier; } diff --git a/src/server/chat-native.ts b/src/server/chat-native.ts index cc6738b313..b27f29c962 100644 --- a/src/server/chat-native.ts +++ b/src/server/chat-native.ts @@ -27,6 +27,7 @@ import { rateLimitRetryPolicyFor, rotateProviderTransportOn429, } from "../providers/key-failover"; +import { fastPolicyForModel } from "../providers/service-tier"; import type { RouteResult } from "../router"; import type { OcxConfig, OcxProviderConfig } from "../types"; import { fetchWithHeaderTimeout, providerFetch, safeHostLabel } from "./responses/fetch-helpers"; @@ -154,8 +155,16 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio translatorBudget.chargeRetained(bytes, { kind: "request_copies" }); retainedRequestBytes = bytes; }; + const buildActiveRequest = () => buildOpenAIChatPassthroughRequest( + activeProvider, + options.chatBody, + route.modelId, + requestedStream, + fastPolicyForModel(activeProvider, route.modelId, route.providerName, "chat"), + config.fastMode, + ); try { - activeRequest = buildOpenAIChatPassthroughRequest(activeProvider, options.chatBody, route.modelId, requestedStream); + activeRequest = buildActiveRequest(); retainRequest(activeRequest); } catch (error) { releaseRetainedRequest(); @@ -222,7 +231,7 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio activeProvider = rotated; activeAdapter = createOpenAIChatAdapter(activeProvider); releaseRetainedRequest(); - activeRequest = buildOpenAIChatPassthroughRequest(activeProvider, options.chatBody, route.modelId, requestedStream); + activeRequest = buildActiveRequest(); retainRequest(activeRequest); response = await send(activeRequest, "key-429"); } diff --git a/tests/fastwire-characterization-wire.test.ts b/tests/fastwire-characterization-wire.test.ts index 0b4e89fbf3..4ae9e38242 100644 --- a/tests/fastwire-characterization-wire.test.ts +++ b/tests/fastwire-characterization-wire.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, spyOn, test } from "bun:test"; import { buildOpenAIChatPassthroughRequest } from "../src/adapters/openai-chat"; import { chatCompletionsToResponsesBody } from "../src/chat/inbound"; +import { fastPolicyForModel } from "../src/providers/service-tier"; import * as adapterResolveModule from "../src/server/adapter-resolve"; import type { RequestLogContext } from "../src/server/request-log"; import { handleResponses } from "../src/server/responses/core"; @@ -364,17 +365,19 @@ describe("FastWire characterization: rawBody observation point", () => { }); describe("FastWire characterization: known bugs", () => { - test("characterization (known bug): native chat passthrough ignores exact-model false", () => { + test("characterization: native chat passthrough honors exact-model false", () => { + // FastWire #1886 native-chat policy fix: exact-model false now strips the caller tier. + const provider: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://native-chat.example.test/v1", + authMode: "key", + apiKey: "sk-test", + supportsServiceTier: true, + chatServiceTier: true, + modelSupportsServiceTier: { model: false }, + }; const request = buildOpenAIChatPassthroughRequest( - { - adapter: "openai-chat", - baseUrl: "https://native-chat.example.test/v1", - authMode: "key", - apiKey: "sk-test", - supportsServiceTier: true, - chatServiceTier: true, - modelSupportsServiceTier: { model: false }, - }, + provider, { model: "model", messages: [{ role: "user", content: "ping" }], @@ -382,9 +385,10 @@ describe("FastWire characterization: known bugs", () => { }, "model", false, + fastPolicyForModel(provider, "model", "native-chat", "chat"), ); const body = JSON.parse(request.body) as Record; - expect(body.service_tier).toBe("flex"); + expect(body).not.toHaveProperty("service_tier"); }); test("characterization: chat-to-responses conversion preserves service_tier", () => { diff --git a/tests/openai-chat-native-policy.test.ts b/tests/openai-chat-native-policy.test.ts new file mode 100644 index 0000000000..f9657551f5 --- /dev/null +++ b/tests/openai-chat-native-policy.test.ts @@ -0,0 +1,341 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + buildOpenAIChatPassthroughRequest, + createOpenAIChatAdapter, +} from "../src/adapters/openai-chat"; +import { + decideTier, + tierValueAfterDecision, +} from "../src/providers/fastwire"; +import { clearKeyCooldowns } from "../src/providers/key-failover"; +import { fastPolicyForModel } from "../src/providers/service-tier"; +import { handleChatCompletions } from "../src/server/chat-completions"; +import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../src/types"; + +const PROVIDER_NAME = "native-tier-fixture"; +const MODEL_ID = "model"; +const originalFetch = globalThis.fetch; + +afterEach(() => { + globalThis.fetch = originalFetch; + clearKeyCooldowns(PROVIDER_NAME); +}); + +function provider(overrides: Partial = {}): OcxProviderConfig { + return { + adapter: "openai-chat", + baseUrl: "https://native-tier.example.test/v1", + authMode: "key", + apiKey: "sk-test", + ...overrides, + }; +} + +function nativeBody( + target: OcxProviderConfig, + callerTier: string | undefined, + modelId = MODEL_ID, + fastMode?: boolean, +): Record { + const policy = fastPolicyForModel(target, modelId, PROVIDER_NAME, "chat"); + const request = buildOpenAIChatPassthroughRequest( + target, + { + model: modelId, + messages: [{ role: "user", content: "ping" }], + ...(callerTier === undefined ? {} : { service_tier: callerTier }), + }, + modelId, + false, + policy, + fastMode, + ); + return JSON.parse(request.body) as Record; +} + +function mainPathBody( + target: OcxProviderConfig, + callerTier: string | undefined, + modelId = MODEL_ID, + fastMode?: boolean, +): Record { + const policy = fastPolicyForModel(target, modelId, PROVIDER_NAME, "chat"); + const tierDecision = decideTier(policy, fastMode, callerTier); + const serviceTier = tierValueAfterDecision(tierDecision, callerTier); + const parsed: OcxParsedRequest = { + modelId, + stream: false, + context: { messages: [{ role: "user", content: "ping" }], tools: [] }, + options: { + ...(serviceTier === undefined ? {} : { serviceTier }), + tierDecision, + }, + }; + const request = createOpenAIChatAdapter(target).buildRequest(parsed); + return JSON.parse(request.body) as Record; +} + +function forwardsTier(body: Record): boolean { + return Object.hasOwn(body, "service_tier"); +} + +describe("native Chat passthrough service-tier policy", () => { + test.each([ + { + name: "provider false stays fail-closed even with CallerTierForward", + config: { supportsServiceTier: false, chatServiceTier: true }, + callerTier: "priority", + expectedTier: undefined, + }, + { + name: "exact-model false narrows provider support", + config: { + supportsServiceTier: true, + chatServiceTier: true, + modelSupportsServiceTier: { [MODEL_ID]: false }, + }, + callerTier: "priority", + expectedTier: undefined, + }, + { + name: "exact-model true authorizes canonical Fast without CallerTierForward", + config: { modelSupportsServiceTier: { [MODEL_ID]: true } }, + callerTier: "FAST", + expectedTier: "FAST", + }, + { + name: "exact-model true does not authorize a foreign tier", + config: { modelSupportsServiceTier: { [MODEL_ID]: true } }, + callerTier: "flex", + expectedTier: undefined, + }, + { + name: "unclassified support drops a caller tier without CallerTierForward", + config: {}, + callerTier: "flex", + expectedTier: undefined, + }, + { + name: "unclassified support forwards a caller tier with CallerTierForward", + config: { chatServiceTier: true }, + callerTier: "flex", + expectedTier: "flex", + }, + { + name: "classified foreign-tier drop overrides CallerTierForward", + config: { + supportsServiceTier: true, + chatServiceTier: true, + fastWire: { + kind: "service-tier", + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "drop", + }, + }, + callerTier: "flex", + expectedTier: undefined, + }, + ] as const)("$name", ({ config, callerTier, expectedTier }) => { + const body = nativeBody(provider(config), callerTier); + if (expectedTier === undefined) expect(body).not.toHaveProperty("service_tier"); + else expect(body.service_tier).toBe(expectedTier); + }); + + test("the native handler passes its resolved fail-closed policy to the builder", async () => { + const captured: Record[] = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + captured.push(JSON.parse(String(init?.body ?? "{}")) as Record); + return Response.json({ + id: "chatcmpl_native_tier", + object: "chat.completion", + choices: [{ index: 0, message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + }); + }) as typeof fetch; + const target = provider({ supportsServiceTier: false, chatServiceTier: true }); + const config = { + port: 0, + defaultProvider: PROVIDER_NAME, + providers: { [PROVIDER_NAME]: target }, + } as OcxConfig; + + const response = await handleChatCompletions( + new Request("http://localhost/v1/chat/completions", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + model: `${PROVIDER_NAME}/${MODEL_ID}`, + messages: [{ role: "user", content: "ping" }], + service_tier: "priority", + }), + }), + config, + { model: "", provider: "" }, + ); + + expect(response.status).toBe(200); + expect(captured).toHaveLength(1); + expect(captured[0]).not.toHaveProperty("service_tier"); + }); + + test("forced Fast injects the policy wire value and forced default drops the caller tier", () => { + const target = provider({ supportsServiceTier: true, chatServiceTier: true }); + + expect(nativeBody(target, "flex", MODEL_ID, true).service_tier).toBe("priority"); + expect(nativeBody(target, undefined, MODEL_ID, true).service_tier).toBe("priority"); + expect(nativeBody(target, "priority", MODEL_ID, false)).not.toHaveProperty("service_tier"); + }); + + test("key failover rebuilds the request without reintroducing a dropped foreign tier", async () => { + const previousHome = process.env.OPENCODEX_HOME; + const home = mkdtempSync(join(tmpdir(), "ocx-native-tier-failover-")); + process.env.OPENCODEX_HOME = home; + const captured: Array<{ authorization: string | null; body: Record }> = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + captured.push({ + authorization: new Headers(init?.headers).get("authorization"), + body: JSON.parse(String(init?.body ?? "{}")) as Record, + }); + if (captured.length === 1) { + return Response.json({ error: { message: "rate limited" } }, { + status: 429, + headers: { "retry-after": "0" }, + }); + } + return Response.json({ + id: "chatcmpl_native_tier_failover", + object: "chat.completion", + choices: [{ index: 0, message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + }); + }) as typeof fetch; + const target = provider({ + supportsServiceTier: true, + chatServiceTier: true, + fastWire: { + kind: "service-tier", + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "drop", + }, + apiKey: "key-one", + apiKeyPool: [{ id: "one", key: "key-one" }, { id: "two", key: "key-two" }], + }); + const config = { + port: 0, + defaultProvider: PROVIDER_NAME, + providers: { [PROVIDER_NAME]: target }, + } as OcxConfig; + + try { + const response = await handleChatCompletions( + new Request("http://localhost/v1/chat/completions", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + model: `${PROVIDER_NAME}/${MODEL_ID}`, + messages: [{ role: "user", content: "ping" }], + service_tier: "flex", + }), + }), + config, + { model: "", provider: "" }, + ); + + expect(response.status).toBe(200); + expect(captured.map(entry => entry.authorization)).toEqual(["Bearer key-one", "Bearer key-two"]); + expect(captured).toHaveLength(2); + for (const entry of captured) expect(entry.body).not.toHaveProperty("service_tier"); + } finally { + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + rmSync(home, { recursive: true, force: true }); + } + }); +}); + +describe("main and native Chat tier authorization parity", () => { + test.each([ + { + name: "provider fail-closed", + config: { supportsServiceTier: false, chatServiceTier: true }, + callerTier: "priority", + forwarded: false, + }, + { + name: "exact-model fail-closed", + config: { + supportsServiceTier: true, + chatServiceTier: true, + modelSupportsServiceTier: { [MODEL_ID]: false }, + }, + callerTier: "priority", + forwarded: false, + }, + { + name: "exact-model canonical Fast", + config: { modelSupportsServiceTier: { [MODEL_ID]: true } }, + callerTier: "fast", + forwarded: true, + mainTier: "priority", + nativeTier: "fast", + }, + { + name: "exact-model foreign tier", + config: { modelSupportsServiceTier: { [MODEL_ID]: true } }, + callerTier: "flex", + forwarded: false, + }, + { + name: "unclassified without CallerTierForward", + config: {}, + callerTier: "priority", + forwarded: false, + }, + { + name: "unclassified with CallerTierForward", + config: { chatServiceTier: true }, + callerTier: "flex", + forwarded: true, + mainTier: "flex", + nativeTier: "flex", + }, + { + name: "classified foreign-tier drop with CallerTierForward", + config: { + supportsServiceTier: true, + chatServiceTier: true, + fastWire: { + kind: "service-tier", + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "drop", + }, + }, + callerTier: "flex", + forwarded: false, + }, + ] as const)("$name makes the same forward/drop decision", row => { + const target = provider(row.config); + const main = mainPathBody(target, row.callerTier); + const native = nativeBody(target, row.callerTier); + + expect(forwardsTier(main)).toBe(row.forwarded); + expect(forwardsTier(native)).toBe(row.forwarded); + expect(forwardsTier(native)).toBe(forwardsTier(main)); + if (row.forwarded) { + expect(main.service_tier).toBe(row.mainTier); + expect(native.service_tier).toBe(row.nativeTier); + } + }); + + test("forced Fast and forced default make the same decision on both Chat paths", () => { + const target = provider({ supportsServiceTier: true, chatServiceTier: true }); + + for (const fastMode of [true, false] as const) { + const main = mainPathBody(target, "flex", MODEL_ID, fastMode); + const native = nativeBody(target, "flex", MODEL_ID, fastMode); + expect(forwardsTier(native)).toBe(forwardsTier(main)); + expect(native.service_tier).toBe(main.service_tier); + } + }); +}); diff --git a/tests/openrouter-provider-routing.test.ts b/tests/openrouter-provider-routing.test.ts index b6ee368f3f..7a833a5b7a 100644 --- a/tests/openrouter-provider-routing.test.ts +++ b/tests/openrouter-provider-routing.test.ts @@ -7,6 +7,7 @@ import { openRouterRoutingConfigError, openRouterProviderPayload, } from "../src/providers/openrouter-routing"; +import { fastPolicyForModel } from "../src/providers/service-tier"; import { clearKeyCooldowns, rotateProviderTransportOn429 } from "../src/providers/key-failover"; import { routeModel } from "../src/router"; import { providerManagementConfigError, safeConfigDTO } from "../src/server/auth-cors"; @@ -39,7 +40,7 @@ function passthroughBody( const request = buildOpenAIChatPassthroughRequest(providerConfig, { messages: [{ role: "user", content: "hello" }], ...rawBody, - }, modelId, false); + }, modelId, false, fastPolicyForModel(providerConfig, modelId, undefined, "chat")); return JSON.parse(request.body as string) as Record; } From ef7efac6f01029b3890161a8550a1d4ffb225502 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Thu, 20 Aug 2026 11:21:38 +0900 Subject: [PATCH 2/2] fix(chat): let decideTier own the absent-decision fallback too canSerializeOpenAIChatServiceTier re-derived its own answer when the router supplied no tierDecision, returning true whenever foreign caller tiers could be forwarded at all. decideTier consults the resolved policy for the specific tier, so the two disagreed: a provider whose fastWire sets foreignCallerTiers: "drop" would still serialize a caller's flex. The fallback now calls decideTier and serializes only set/forward-caller, which makes the state machine the single authority on both paths. Found by CodeRabbit on #2151. --- src/adapters/openai-chat.ts | 11 +++-- tests/openai-chat-native-policy.test.ts | 56 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/adapters/openai-chat.ts b/src/adapters/openai-chat.ts index f6fce1a82c..61557b1407 100644 --- a/src/adapters/openai-chat.ts +++ b/src/adapters/openai-chat.ts @@ -1314,11 +1314,14 @@ function canSerializeOpenAIChatServiceTier( if (tierDecision !== undefined) { return tierDecision.kind === "set" || tierDecision.kind === "forward-caller"; } + // No decision from the router means this call did not go through the tier state machine, so + // ask that machine rather than re-deriving a looser answer beside it. The previous fallback + // returned true whenever foreign forwarding was allowed at all, which let a caller tier + // reach the wire in cases `decideTier` would have dropped — the two paths disagreeing is + // precisely the bug, so there is now only one authority. const callerTier = typeof serviceTier === "string" ? serviceTier : undefined; - const callerCanonicalFast = canonicalFastTierMarker(callerTier) !== undefined; - const capability = supportsServiceTierForModel(provider, modelId); - const callerTierForwardAllowed = canForwardForeignServiceTierForChatModel(provider, modelId); - return callerTierForwardAllowed || (callerCanonicalFast && capability === true); + const decision = decideTier(fastPolicyForModel(provider, modelId, undefined, "chat"), undefined, callerTier); + return decision.kind === "set" || decision.kind === "forward-caller"; } export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAdapter { diff --git a/tests/openai-chat-native-policy.test.ts b/tests/openai-chat-native-policy.test.ts index f9657551f5..01a15d7497 100644 --- a/tests/openai-chat-native-policy.test.ts +++ b/tests/openai-chat-native-policy.test.ts @@ -82,6 +82,29 @@ function forwardsTier(body: Record): boolean { return Object.hasOwn(body, "service_tier"); } +/** + * The translated path WITHOUT a router-supplied `tierDecision`. + * + * `mainPathBody` always computes and passes a decision, so it never exercises the adapter's + * absent-decision fallback. That fallback used to re-derive its own looser answer beside + * `decideTier` instead of asking it, which let a foreign caller tier reach the wire on a + * provider whose policy drops foreign tiers. + */ +function undecidedPathBody( + target: OcxProviderConfig, + callerTier: string | undefined, + modelId = MODEL_ID, +): Record { + const parsed: OcxParsedRequest = { + modelId, + stream: false, + context: { messages: [{ role: "user", content: "ping" }], tools: [] }, + options: { ...(callerTier === undefined ? {} : { serviceTier: callerTier }) }, + }; + const request = createOpenAIChatAdapter(target).buildRequest(parsed); + return JSON.parse(request.body) as Record; +} + describe("native Chat passthrough service-tier policy", () => { test.each([ { @@ -144,6 +167,39 @@ describe("native Chat passthrough service-tier policy", () => { else expect(body.service_tier).toBe(expectedTier); }); + describe("the translated path with no router tier decision defers to decideTier", () => { + const dropsForeign = { + supportsServiceTier: true, + chatServiceTier: true, + fastWire: { + kind: "service-tier" as const, + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "drop" as const, + }, + }; + + test("a foreign caller tier is dropped when the policy drops foreign tiers", () => { + // Before the fix this serialized `flex` because the fallback only asked whether foreign + // forwarding was allowed anywhere, not what the policy decided for this tier. + expect(undecidedPathBody(provider(dropsForeign), "flex")).not.toHaveProperty("service_tier"); + }); + + test("a canonical Fast tier still serializes through the same path", () => { + expect(undecidedPathBody(provider(dropsForeign), "priority").service_tier).toBe("priority"); + }); + + test("the fallback and decideTier cannot disagree", () => { + // The invariant, stated directly: whatever the state machine decides for this caller + // tier is what the wire carries, with or without a router-supplied decision. + for (const callerTier of ["flex", "priority", "auto", "default"]) { + const target = provider(dropsForeign); + const decision = decideTier(fastPolicyForModel(target, MODEL_ID, undefined, "chat"), undefined, callerTier); + const serializes = decision.kind === "set" || decision.kind === "forward-caller"; + expect(forwardsTier(undecidedPathBody(target, callerTier))).toBe(serializes); + } + }); + }); + test("the native handler passes its resolved fail-closed policy to the builder", async () => { const captured: Record[] = []; globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {