-
Notifications
You must be signed in to change notification settings - Fork 750
fix(providers): keep hand-edited context windows through a POST overwrite #1535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -427,6 +427,120 @@ describe("provider management validation", () => { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // #1409: the add/edit form's payload type has no member for contextWindow or | ||||||||||||||||||||||||||||||||||||
| // modelContextWindows, so an overwrite arrives without them. Registry enrichment then fills | ||||||||||||||||||||||||||||||||||||
| // the absent fields from the seed and the stored row loses the user's values — for | ||||||||||||||||||||||||||||||||||||
| // opencode-go the seed is exactly {"kimi-k3": 262144}, which is what the reporter found in | ||||||||||||||||||||||||||||||||||||
| // place of their deepseek-v4-flash override. | ||||||||||||||||||||||||||||||||||||
| describe("provider POST overwrite preserves hand-edited context windows (#1409)", () => { | ||||||||||||||||||||||||||||||||||||
| async function seedProvider(url: URL, extra: Record<string, unknown>): Promise<Response> { | ||||||||||||||||||||||||||||||||||||
| return fetch(new URL("/api/providers", url), { | ||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||
| headers: { "content-type": "application/json" }, | ||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ | ||||||||||||||||||||||||||||||||||||
| name: "opencode-go", | ||||||||||||||||||||||||||||||||||||
| provider: { adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1", apiKey: "k", ...extra }, | ||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| function freshHome(): void { | ||||||||||||||||||||||||||||||||||||
| if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true }); | ||||||||||||||||||||||||||||||||||||
| mkdirSync(TEST_DIR, { recursive: true }); | ||||||||||||||||||||||||||||||||||||
| process.env.OPENCODEX_HOME = TEST_DIR; | ||||||||||||||||||||||||||||||||||||
| saveConfig(config("127.0.0.1")); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("an omitted modelContextWindows keeps the user's map, without registry seed keys", async () => { | ||||||||||||||||||||||||||||||||||||
| freshHome(); | ||||||||||||||||||||||||||||||||||||
| const server = startServer(0); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { modelContextWindows: { "deepseek-v4-flash": 900000 } })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, {})).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // The user's key survives, and the registry seed is NOT persisted into user config: | ||||||||||||||||||||||||||||||||||||
| // router.ts fills registry values beneath user entries at resolve time, so writing | ||||||||||||||||||||||||||||||||||||
| // them here would be a side effect of an unrelated save. | ||||||||||||||||||||||||||||||||||||
| expect(loadConfig().providers["opencode-go"]?.modelContextWindows).toEqual({ "deepseek-v4-flash": 900000 }); | ||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||
| await server.stop(true); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("a submitted modelContextWindows updates that key and keeps the others", async () => { | ||||||||||||||||||||||||||||||||||||
| freshHome(); | ||||||||||||||||||||||||||||||||||||
| const server = startServer(0); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { modelContextWindows: { "deepseek-v4-flash": 900000 } })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { modelContextWindows: { "kimi-k3": 300000 } })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(loadConfig().providers["opencode-go"]?.modelContextWindows) | ||||||||||||||||||||||||||||||||||||
| .toEqual({ "deepseek-v4-flash": 900000, "kimi-k3": 300000 }); | ||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||
| await server.stop(true); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("an omitted contextWindow keeps the user's scalar", async () => { | ||||||||||||||||||||||||||||||||||||
| freshHome(); | ||||||||||||||||||||||||||||||||||||
| const server = startServer(0); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { contextWindow: 777000 })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, {})).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(loadConfig().providers["opencode-go"]?.contextWindow).toBe(777000); | ||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||
| await server.stop(true); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("a submitted contextWindow still wins", async () => { | ||||||||||||||||||||||||||||||||||||
| freshHome(); | ||||||||||||||||||||||||||||||||||||
| const server = startServer(0); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { contextWindow: 777000 })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { contextWindow: 512000 })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(loadConfig().providers["opencode-go"]?.contextWindow).toBe(512000); | ||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||
| await server.stop(true); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("a brand-new provider still receives the registry seed", async () => { | ||||||||||||||||||||||||||||||||||||
| freshHome(); | ||||||||||||||||||||||||||||||||||||
| const server = startServer(0); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, {})).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // No prior row exists, so enrichment is authoritative and the seed must land. | ||||||||||||||||||||||||||||||||||||
| expect(loadConfig().providers["opencode-go"]?.modelContextWindows).toBeDefined(); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+510
to
+517
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Assert the expected registry seed. Line 517 accepts any defined map. The test passes if registry enrichment writes the wrong model ID or window. Assert the documented Proposed fix- expect(loadConfig().providers["opencode-go"]?.modelContextWindows).toBeDefined();
+ expect(loadConfig().providers["opencode-go"]?.modelContextWindows)
+ .toEqual({ "kimi-k3": 262144 });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||
| await server.stop(true); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("PATCH can still delete a key with an explicit null", async () => { | ||||||||||||||||||||||||||||||||||||
| freshHome(); | ||||||||||||||||||||||||||||||||||||
| const server = startServer(0); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| expect((await seedProvider(server.url, { modelContextWindows: { "deepseek-v4-flash": 900000 } })).status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const patch = await fetch(new URL("/api/providers?name=opencode-go", server.url), { | ||||||||||||||||||||||||||||||||||||
| method: "PATCH", | ||||||||||||||||||||||||||||||||||||
| headers: { "content-type": "application/json" }, | ||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ modelContextWindows: { "deepseek-v4-flash": null } }), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| expect(patch.status).toBe(200); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Deletion is an explicit null through PATCH, which the POST carry-over must not undo. | ||||||||||||||||||||||||||||||||||||
| expect(loadConfig().providers["opencode-go"]?.modelContextWindows?.["deepseek-v4-flash"]).toBeUndefined(); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+523
to
+537
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win Test deletion across a later POST overwrite. The test comment states that POST carry-over must not undo deletion, but the test only checks the immediate PATCH result. Use a registry-seeded key such as 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||
| await server.stop(true); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| test("provider management accepts modelCosts on the canonical openai provider", async () => { | ||||||||||||||||||||||||||||||||||||
| if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true }); | ||||||||||||||||||||||||||||||||||||
| mkdirSync(TEST_DIR, { recursive: true }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a registry provider was originally created, enrichment persisted that release's
modelContextWindowsseed into the provider row even if the user never edited it. If a later release corrects the canonical seed and the dashboard performs a duplicate-name POST (which omits this field), this assignment replaces the freshly enriched map with the old persisted seed;routedProviderConfigthen gives that provider map precedence, so the registry correction never takes effect. Preserve only identifiable user overrides—such as by tracking provenance or normalizing stored seed values—instead of treating the entire existing map as user-owned.AGENTS.md reference: src/AGENTS.md:L18-L18
Useful? React with 👍 / 👎.