Skip to content

Commit 0f8c332

Browse files
committed
fix(responses): tri-state service_tier gate and authoritative-only [1m] picker rows
The service-tier gate stripped the field for UNCLASSIFIED custom providers too, silently rewriting caller requests against unknown gateways; only an explicit supportsServiceTier: false strips now (true supports, false strips, undefined preserves-without-injection). The Claude picker emitted [1m] rows for sub-1M models under auto-context (the #854 defect at the picker level — a 372K route marked [1m] makes Claude Code account 1e6); variants now require an authoritative >=1M window. Adds the real DeepSeek tool-call continuation shape (reasoning → call → output) to the replay tests.
1 parent 622d60a commit 0f8c332

5 files changed

Lines changed: 58 additions & 21 deletions

File tree

src/claude/model-info.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,17 @@ export function buildAnthropicModelInfos(
118118
// host the compact window — display stays honest (real window, not "1M"). Guards
119119
// (audit R1#11): same dedupe set, never double-suffix.
120120
const push1mVariant = (base: AnthropicModelInfo, contextWindow: number | undefined, mode: AutoContextMode = auto) => {
121-
if (!shouldMarkOneMillion(contextWindow, mode)) return;
121+
// The [1m] marker makes Claude Code account 1e6 tokens for the row, so it
122+
// may only name models whose AUTHORITATIVE effective window is >= 1M —
123+
// never the auto-context widening, which would mark a 372K route and have
124+
// Claude Code over-fill it (the #854 defect).
125+
if (contextWindow === undefined || contextWindow < ONE_MILLION) return;
122126
if (base.id.includes("[1m]")) return;
123127
const id = `${base.id}[1m]`;
124128
if (seen.has(id)) return;
125129
seen.add(id);
126130
const window = contextWindow as number;
127-
const label = window >= ONE_MILLION ? "1M" : `${Math.round(window / 1_000)}k`;
128-
out.push({ ...base, id, display_name: `${base.display_name} · ${label}`, max_input_tokens: Math.min(window, ONE_MILLION) });
131+
out.push({ ...base, id, display_name: `${base.display_name} · 1M`, max_input_tokens: ONE_MILLION });
129132
};
130133
for (const slug of nativeSlugs) {
131134
const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug);

src/server/responses/core.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,17 +1147,20 @@ function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBud
11471147

11481148
/**
11491149
* Service-tier capability gate, applied after the final route/wire is settled. A
1150-
* provider that does not document `service_tier` must never receive it: strip the
1151-
* field and clear the logging value even when the caller supplied one (fail
1152-
* closed). An explicit `supportsServiceTier: true` on the provider config is the
1153-
* escape hatch for gateways that genuinely honour tiers.
1150+
* provider explicitly documented as NOT supporting `service_tier` must never
1151+
* receive it: strip the field and clear the logging value even when the caller
1152+
* supplied one (fail closed). Tri-state contract: `true` supports (injection
1153+
* allowed, caller values preserved), `false` strips, and an UNCLASSIFIED custom
1154+
* provider (`undefined`) preserves caller-supplied values but never gets an
1155+
* injection — deleting the caller's field there would silently change their
1156+
* request against a gateway we know nothing about.
11541157
*/
11551158
export function applyServiceTierGate(
11561159
provider: OcxProviderConfig,
11571160
rawBody: unknown,
11581161
options: { serviceTier?: string },
11591162
): void {
1160-
if (provider.adapter !== "openai-responses" || provider.supportsServiceTier === true) return;
1163+
if (provider.adapter !== "openai-responses" || provider.supportsServiceTier !== false) return;
11611164
if (rawBody && typeof rawBody === "object") {
11621165
delete (rawBody as Record<string, unknown>).service_tier;
11631166
}

tests/claude-model-info.test.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,19 @@ describe("anthropic-flavor ModelInfo discovery entries (devlog 130 B4b)", () =>
9696
expect(infos).toHaveLength(1);
9797
});
9898

99-
test("auto-context widens variants to safe sub-1M rows with honest labels (devlog 020)", () => {
99+
test("no [1m] rows for sub-1M models, even with auto-context enabled (#854 contract)", () => {
100100
const auto = { enabled: true, compactWindow: 350_000 };
101101
const infos = buildAnthropicModelInfos(["gpt-5.4", "gpt-5.6-sol"], [
102102
{ provider: "mock", id: "small-model", contextWindow: 128_000 },
103103
{ provider: "mock", id: "mid-model", contextWindow: 300_000 }, // < compact window: unsafe, no row
104104
], auto);
105105
const variants = infos.filter(i => i.id.endsWith("[1m]"));
106-
expect(variants).toHaveLength(2); // gpt-5.4 (1M) + gpt-5.6-sol (372k)
107-
const sol = variants.find(v => v.display_name.includes("gpt-5.6-sol"))!;
108-
expect(sol.display_name.endsWith("· 372k")).toBe(true); // honest real window, not "1M"
109-
expect(sol.max_input_tokens).toBe(372_000);
110-
const five4 = variants.find(v => v.display_name.includes("gpt-5.4"))!;
111-
expect(five4.display_name.endsWith("· 1M")).toBe(true);
106+
// The [1m] marker makes Claude Code account 1e6 tokens: only the
107+
// authoritative 1M model may carry it — never the 372K route.
108+
expect(variants).toHaveLength(1);
109+
expect(variants[0]!.display_name.includes("gpt-5.4")).toBe(true);
110+
expect(variants[0]!.display_name.endsWith("· 1M")).toBe(true);
111+
expect(variants[0]!.max_input_tokens).toBe(1_000_000);
112112
});
113113

114114
test("auto-context never widens anthropic passthrough rows (audit 021 #3)", () => {
@@ -131,7 +131,8 @@ describe("anthropic-flavor ModelInfo discovery entries (devlog 130 B4b)", () =>
131131
], auto, "readable");
132132
const ids = infos.map(i => i.id);
133133
expect(ids).toContain("claude-ocx-native--gpt-5.6-sol");
134-
expect(ids).toContain("claude-ocx-native--gpt-5.6-sol[1m]"); // 372k native, auto-marked
134+
// 372k native: NO [1m] variant under the authoritative-window contract.
135+
expect(ids).not.toContain("claude-ocx-native--gpt-5.6-sol[1m]");
135136
expect(ids).toContain("claude-ocx-cursor--gpt-5.6-luna");
136137
expect(ids).toContain("claude-ocx-cursor--gpt-5.6-luna[1m]");
137138
expect(ids).toContain("claude-opus-4-8"); // anthropic canonical passthrough

tests/deepseek-reasoning-replay.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,32 @@ describe("DeepSeek Responses replay keeps reasoning on the wire", () => {
7676
expect(item.content).toEqual([{ type: "reasoning_text", text: "think step by step" }]);
7777
});
7878

79+
test("a real tool-call continuation (reasoning → call → output) keeps all three for DeepSeek", () => {
80+
// The documented DeepSeek failure shape: the turn AFTER a tool call must
81+
// carry reasoning_content, or the upstream answers HTTP 400.
82+
const provider = { ...providerConfigSeed(getProviderRegistryEntry("deepseek")!), apiKey: "sk-test" };
83+
enrichProviderFromRegistry("deepseek", provider);
84+
const built = createResponsesPassthroughAdapter(provider).buildRequest({
85+
modelId: "deepseek-v4-flash",
86+
context: { messages: [] },
87+
stream: true,
88+
options: {},
89+
_rawBody: {
90+
model: "deepseek-v4-flash",
91+
input: [
92+
reasoningItem(),
93+
{ type: "function_call", id: "fc_1", call_id: "call_1", name: "get_weather", arguments: "{\"city\":\"Seoul\"}" },
94+
{ type: "function_call_output", call_id: "call_1", output: "rain" },
95+
],
96+
},
97+
} as Parameters<ReturnType<typeof createResponsesPassthroughAdapter>["buildRequest"]>[0], { headers: new Headers() });
98+
const body = JSON.parse(String(built.body)) as { input: Record<string, unknown>[] };
99+
expect(body.input).toHaveLength(3);
100+
expect(body.input[0]!.content).toEqual([{ type: "reasoning_text", text: "think step by step" }]);
101+
expect(body.input[1]).toMatchObject({ type: "function_call", call_id: "call_1", name: "get_weather" });
102+
expect(body.input[2]).toMatchObject({ type: "function_call_output", call_id: "call_1", output: "rain" });
103+
});
104+
79105
test("a canonical OpenAI provider still blanks reasoning content", () => {
80106
const provider = { ...providerConfigSeed(getProviderRegistryEntry("openai-apikey")!), apiKey: "sk-test" };
81107
const body = buildBody(provider);

tests/service-tier-capability.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ describe("applyServiceTierGate fails closed", () => {
5858
expect(options.serviceTier).toBeUndefined();
5959
});
6060

61-
test("an unclassified provider (undefined capability) also fails closed", () => {
61+
test("an unclassified provider (undefined capability) preserves the caller value", () => {
6262
const body = { model: "m", service_tier: "priority" };
6363
const options: { serviceTier?: string } = { serviceTier: "priority" };
6464
applyServiceTierGate({ adapter: "openai-responses", baseUrl: "https://example.com/v1" }, body, options);
65-
expect("service_tier" in body).toBe(false);
66-
expect(options.serviceTier).toBeUndefined();
65+
// Tri-state: only an explicit `false` strips; unknown gateways keep the
66+
// caller's field (we know nothing about them), and never get an injection.
67+
expect(body.service_tier).toBe("priority");
68+
expect(options.serviceTier).toBe("priority");
6769
});
6870

6971
test("a non-Responses adapter is out of scope", () => {
@@ -136,9 +138,11 @@ describe("the gate fires on the live handleResponses path", () => {
136138
expect(body.service_tier).toBe("flex");
137139
});
138140

139-
test("an unclassified custom Responses provider fails closed unless explicitly opted in", async () => {
141+
test("an unclassified custom Responses provider keeps caller values; only explicit false strips", async () => {
140142
const custom = (): OcxProviderConfig => ({ adapter: "openai-responses", baseUrl: "https://gateway.example.com/v1", apiKey: "sk-test" });
141-
const stripped = await drive("custom-gw", custom(), "some-model", { service_tier: "priority" });
143+
const preserved = await drive("custom-gw", custom(), "some-model", { service_tier: "priority" });
144+
expect(preserved.service_tier).toBe("priority");
145+
const stripped = await drive("custom-gw", { ...custom(), supportsServiceTier: false }, "some-model", { service_tier: "priority" });
142146
expect("service_tier" in stripped).toBe(false);
143147
const optedIn = await drive("custom-gw", { ...custom(), supportsServiceTier: true }, "some-model", { service_tier: "priority" });
144148
expect(optedIn.service_tier).toBe("priority");

0 commit comments

Comments
 (0)