





























@@ -0,0 +1,203 @@
1+/**
2+ * E2E regression test for #76482: verifies the full pipeline from gateway
3+ * sessions.list (lightweight rows with empty thinkingOptions) through
4+ * consumer-side resolution, ensuring:
5+ * 1. DeepSeek V4 Pro sessions resolve all 7 thinking levels
6+ * 2. Anthropic sessions don't leak DeepSeek levels from defaults
7+ * 3. Sessions matching the default model correctly inherit defaults
8+ */
9+import { expect, test, vi } from "vitest";
10+import { formatThinkingLevels } from "../auto-reply/thinking.js";
11+import { testState, writeSessionStore } from "./test-helpers.js";
12+import {
13+setupGatewaySessionsTestHarness,
14+getGatewayConfigModule,
15+getSessionsHandlers,
16+sessionStoreEntry,
17+} from "./test/server-sessions.test-helpers.js";
18+19+const { createSessionStoreDir } = setupGatewaySessionsTestHarness();
20+21+/**
22+ * Simulates the consumer-side resolution from session-controls.ts and
23+ * slash-command-executor.ts — the code path that the PR fixes.
24+ */
25+function resolveThinkingLevelsConsumerSide(
26+session:
27+| {
28+modelProvider?: string;
29+model?: string;
30+thinkingLevels?: Array<{ label: string }>;
31+thinkingOptions?: string[];
32+}
33+| undefined,
34+defaults:
35+| {
36+modelProvider?: string;
37+model?: string;
38+thinkingLevels?: Array<{ label: string }>;
39+thinkingOptions?: string[];
40+}
41+| undefined,
42+): string[] {
43+if (session?.thinkingLevels?.length) {
44+return session.thinkingLevels.map((l) => l.label);
45+}
46+const sessionModelMatchesDefaults =
47+(!session?.modelProvider || session.modelProvider === defaults?.modelProvider) &&
48+(!session?.model || session.model === defaults?.model);
49+if (sessionModelMatchesDefaults && defaults?.thinkingLevels?.length) {
50+return defaults.thinkingLevels.map((l) => l.label);
51+}
52+const labels =
53+(session?.thinkingOptions?.length ? session.thinkingOptions : null) ??
54+(sessionModelMatchesDefaults && defaults?.thinkingOptions?.length
55+ ? defaults.thinkingOptions
56+ : null) ??
57+formatThinkingLevels(
58+session?.modelProvider ?? defaults?.modelProvider,
59+session?.model ?? defaults?.model,
60+).split(/\s*,\s*/);
61+return labels.filter(Boolean);
62+}
63+64+test("e2e #76482: session with different model gets its own thinking levels through gateway row + consumer fallback", async () => {
65+await createSessionStoreDir();
66+testState.agentConfig = {
67+model: { primary: "openai/gpt-5.5" },
68+};
69+await writeSessionStore({
70+entries: {
71+main: sessionStoreEntry("sess-main", {
72+modelProvider: "test-extended",
73+model: "extended-reasoner",
74+}),
75+},
76+});
77+78+const respond = vi.fn();
79+const sessionsHandlers = await getSessionsHandlers();
80+const { getRuntimeConfig } = await getGatewayConfigModule();
81+await sessionsHandlers["sessions.list"]({
82+req: { type: "req", id: "req-e2e-extended", method: "sessions.list", params: {} },
83+params: {},
84+ respond,
85+client: null,
86+isWebchatConnect: () => false,
87+context: {
88+ getRuntimeConfig,
89+// Provide a catalog with xhigh support — simulates what a real gateway
90+// resolves for models like DeepSeek V4 Pro
91+loadGatewayModelCatalog: async () => [
92+{
93+provider: "test-extended",
94+id: "extended-reasoner",
95+name: "Extended Reasoner",
96+reasoning: true,
97+compat: { supportedReasoningEfforts: ["xhigh"] },
98+},
99+],
100+} as never,
101+});
102+103+const result = respond.mock.calls[0]?.[1];
104+const session = result?.sessions?.find((s: { key: string }) => s.key === "agent:main:main");
105+const defaults = result?.defaults;
106+107+// Gateway includes thinkingOptions for lightweight rows (needed by Control UI)
108+expect(session?.thinkingOptions?.length).toBeGreaterThan(0);
109+expect(session?.thinkingOptions).toContain("xhigh");
110+111+// Session model differs from default
112+expect(session?.modelProvider).toBe("test-extended");
113+expect(defaults?.modelProvider).toBe("openai");
114+115+// Consumer-side resolution uses session's own thinkingOptions (not defaults)
116+const resolved = resolveThinkingLevelsConsumerSide(session, defaults);
117+expect(resolved).toContain("xhigh");
118+expect(resolved).toContain("off");
119+expect(resolved).toContain("high");
120+});
121+122+test("e2e #76482: Anthropic session does not leak DeepSeek thinking levels from defaults", async () => {
123+await createSessionStoreDir();
124+testState.agentConfig = {
125+model: { primary: "deepseek/deepseek-v4-pro" },
126+};
127+await writeSessionStore({
128+entries: {
129+main: sessionStoreEntry("sess-main", {
130+modelProvider: "anthropic",
131+model: "claude-sonnet-4-6",
132+}),
133+},
134+});
135+136+const respond = vi.fn();
137+const sessionsHandlers = await getSessionsHandlers();
138+const { getRuntimeConfig } = await getGatewayConfigModule();
139+await sessionsHandlers["sessions.list"]({
140+req: { type: "req", id: "req-e2e-anthropic", method: "sessions.list", params: {} },
141+params: {},
142+ respond,
143+client: null,
144+isWebchatConnect: () => false,
145+context: { getRuntimeConfig, loadGatewayModelCatalog: async () => [] } as never,
146+});
147+148+const result = respond.mock.calls[0]?.[1];
149+const session = result?.sessions?.find((s: { key: string }) => s.key === "agent:main:main");
150+const defaults = result?.defaults;
151+152+// Session model differs from default
153+expect(session?.modelProvider).toBe("anthropic");
154+expect(defaults?.modelProvider).toBe("deepseek");
155+156+// Consumer-side resolution should NOT include DeepSeek-specific levels
157+const resolved = resolveThinkingLevelsConsumerSide(session, defaults);
158+expect(resolved).not.toContain("xhigh");
159+expect(resolved).not.toContain("max");
160+// Should have base Anthropic levels
161+expect(resolved).toContain("off");
162+expect(resolved).toContain("high");
163+});
164+165+test("e2e #76482: session matching default model inherits default thinking levels", async () => {
166+await createSessionStoreDir();
167+testState.agentConfig = {
168+model: { primary: "openai/gpt-5.5" },
169+};
170+await writeSessionStore({
171+entries: {
172+main: sessionStoreEntry("sess-main", {
173+modelProvider: "openai",
174+model: "gpt-5.5",
175+}),
176+},
177+});
178+179+const respond = vi.fn();
180+const sessionsHandlers = await getSessionsHandlers();
181+const { getRuntimeConfig } = await getGatewayConfigModule();
182+await sessionsHandlers["sessions.list"]({
183+req: { type: "req", id: "req-e2e-same", method: "sessions.list", params: {} },
184+params: {},
185+ respond,
186+client: null,
187+isWebchatConnect: () => false,
188+context: { getRuntimeConfig, loadGatewayModelCatalog: async () => [] } as never,
189+});
190+191+const result = respond.mock.calls[0]?.[1];
192+const session = result?.sessions?.find((s: { key: string }) => s.key === "agent:main:main");
193+const defaults = result?.defaults;
194+195+// Session matches default → consumer should use defaults
196+expect(session?.modelProvider).toBe(defaults?.modelProvider);
197+198+const resolved = resolveThinkingLevelsConsumerSide(session, defaults);
199+expect(resolved.length).toBeGreaterThan(0);
200+// Should match what defaults provide
201+expect(resolved).toContain("off");
202+expect(resolved).toContain("high");
203+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。