From f700c56172d79f1222690d7a1ccfb9d6cc4c53bf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 20 Sep 2026 13:22:17 +0000 Subject: [PATCH 1/3] test(openai-chat): declare role acceptance in suites that assert the forwarded role (#5334 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5334 made the developer wire role tri-state: an undeclared destination folds it to system. Two suites asserting role:"developer" on the Chat wire were missed because they are about tool-result repair ordering and document parts, not role selection — declare the destination, per the convention the change established. Verified: both files fail on dev@600075d2 with system-for-developer wire roles and pass with the declaration. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/adapters/openai/openai-chat-dangling-toolcalls.test.ts | 4 ++++ tests/responses/chat-inline-document-bytes.test.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/adapters/openai/openai-chat-dangling-toolcalls.test.ts b/tests/adapters/openai/openai-chat-dangling-toolcalls.test.ts index 41a61c21023..075e3c33186 100644 --- a/tests/adapters/openai/openai-chat-dangling-toolcalls.test.ts +++ b/tests/adapters/openai/openai-chat-dangling-toolcalls.test.ts @@ -12,6 +12,10 @@ const provider: OcxProviderConfig = { baseUrl: "https://example.test/v1", apiKey: "sk-test", authMode: "key", + // The wire role folds to `system` unless a destination is recorded as accepting + // `developer`; this suite is about tool-result repair ordering, so it declares the + // destination rather than asserting the default. + foldDeveloperRoleToSystem: false, }; interface ChatMsg { diff --git a/tests/responses/chat-inline-document-bytes.test.ts b/tests/responses/chat-inline-document-bytes.test.ts index dd88fa05788..a718c1c376a 100644 --- a/tests/responses/chat-inline-document-bytes.test.ts +++ b/tests/responses/chat-inline-document-bytes.test.ts @@ -28,6 +28,10 @@ const chatProvider: OcxProviderConfig = { adapter: "openai-chat", baseUrl: "https://gateway.example.internal/v1", apiKey: "k", + // The wire role folds to `system` unless a destination is recorded as accepting + // `developer`; the document test asserts the role a turn keeps, so it declares the + // destination rather than asserting the default. + foldDeveloperRoleToSystem: false, }; const anthropicProvider = { adapter: "anthropic", From bd5a0e8c1bc71c566f3378c2a020069b1c5c5c84 Mon Sep 17 00:00:00 2001 From: Epinephrine Date: Mon, 21 Sep 2026 09:50:19 +0900 Subject: [PATCH 2/3] fix(providers): migrate stale Alibaba context windows --- src/providers/stale-context-window-migration.ts | 8 +++++--- structure/providers-and-adapters.md | 3 +++ .../providers/context-window-seed-repair.test.ts | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/providers/stale-context-window-migration.ts b/src/providers/stale-context-window-migration.ts index 5fb1eea2ecc..2cc764eebfd 100644 --- a/src/providers/stale-context-window-migration.ts +++ b/src/providers/stale-context-window-migration.ts @@ -34,15 +34,18 @@ export interface StaleContextWindowProjection { } /** - * Cognition windows corrected against a live `GetCascadeModelConfigs` response. + * Known-bad registry context windows corrected against provider evidence. * - * The shipped table had been assembled from each model's ORIGINAL vendor window + * The Alibaba Token Plan correction comes from gateway boundary probes. The + * shipped Cognition table had been assembled from each model's ORIGINAL vendor window * rather than from what Cognition serves, so the Claude rows claimed 200k against * an actual 1M and Grok claimed 256k against 500k. Cognition documents no window * anywhere, so the per-account catalog is the only first-party source; these are * the degraded-mode figures, and live discovery supersedes them when it runs. */ export const STALE_CONTEXT_WINDOWS: readonly StaleContextWindow[] = [ + { provider: "alibaba-token-plan", model: "qwen3.8-max", from: 983_616, to: 1_000_000 }, + { provider: "alibaba-token-plan-intl", model: "qwen3.8-max", from: 983_616, to: 1_000_000 }, { provider: "devin", model: "swe-1-7", from: 256_000, to: 262_000 }, { provider: "devin", model: "swe-1-7-lightning", from: 256_000, to: 202_752 }, { provider: "devin", model: "gpt-5-6-sol", from: 1_050_000, to: 1_000_000 }, @@ -89,4 +92,3 @@ export function projectStaleContextWindows( return { config, changed: repaired.size > 0, warnings }; } - diff --git a/structure/providers-and-adapters.md b/structure/providers-and-adapters.md index de68ef9f7ae..039a8137a65 100644 --- a/structure/providers-and-adapters.md +++ b/structure/providers-and-adapters.md @@ -83,6 +83,9 @@ Because `enrichProviderFromRegistry` fills `noVisionModels` all-or-nothing and f frozen into any config saved while it was current. `src/providers/stale-vision-classification-migration.ts` repairs exactly those two saved values and runs inside the shared startup repair pass in `src/providers/model-rename-startup.ts`. Correcting the registry alone fixes new installs only. +The same startup pass uses `src/providers/stale-context-window-migration.ts` to replace only exact +known-bad saved context-window seeds; this includes both Alibaba Token Plan variants' former +983,616-token `qwen3.8-max` value, while any operator-selected value remains authoritative. It covers both states that reach a running process, because the sidecar predicate reads `noVisionModels` before `modelInputModalities`: the full stale pair (modalities still the stale diff --git a/tests/providers/context-window-seed-repair.test.ts b/tests/providers/context-window-seed-repair.test.ts index 55063afa4e8..24cba8e1dfe 100644 --- a/tests/providers/context-window-seed-repair.test.ts +++ b/tests/providers/context-window-seed-repair.test.ts @@ -10,6 +10,10 @@ function devinConfig(windows: Record, adapter = "devin"): OcxCon } as unknown as OcxConfig; } +function alibabaConfig(provider: "alibaba-token-plan" | "alibaba-token-plan-intl", value: number): OcxConfig { + return { providers: { [provider]: { adapter: "openai-chat", modelContextWindows: { "qwen3.8-max": value } } } } as OcxConfig; +} + describe("stale context window migration", () => { test("repairs a window the config inherited from the wrong registry seed", () => { // `enrichProviderFromRegistry` is fill-only, so a config saved while the @@ -34,6 +38,15 @@ describe("stale context window migration", () => { expect(projection.config.providers!.devin!.modelContextWindows!["grok-4-5"]).toBe(300_000); }); + test.each(["alibaba-token-plan", "alibaba-token-plan-intl"] as const)( + "repairs the old qwen3.8-max seed for %s", + provider => { + const projection = projectStaleContextWindows(alibabaConfig(provider, 983_616)); + expect(projection.changed).toBe(true); + expect(projection.config.providers![provider]!.modelContextWindows!["qwen3.8-max"]).toBe(1_000_000); + }, + ); + test("skips a row that no longer carries the registry adapter", () => { // A `devin` row retargeted at another transport is not the provider these // numbers describe, so rewriting its windows would be a guess. @@ -52,8 +65,7 @@ describe("stale context window migration", () => { // never performs, and an entry for another provider would silently do nothing. for (const entry of STALE_CONTEXT_WINDOWS) { expect(entry.from).not.toBe(entry.to); - expect(entry.provider).toBe("devin"); + expect(["alibaba-token-plan", "alibaba-token-plan-intl", "devin"]).toContain(entry.provider); } }); }); - From f9ef2afb558afc3f850999d94bf0de14d77fa550 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:11:52 +0000 Subject: [PATCH 3/3] fix(providers): skip stale-window repair on repointed provider rows The migration's provider guard checked only the adapter, so a saved row repointed at a custom gateway but still carrying the generic openai-chat adapter had its context window overwritten with the registry's corrected value. Require the configured baseUrl to match the registry endpoint or a declared baseUrlChoices entry, the same ownership rule model-rename-migration applies. Co-Authored-By: Epinephrine --- .../stale-context-window-migration.ts | 31 ++++++++++++----- .../context-window-seed-repair.test.ts | 33 +++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/providers/stale-context-window-migration.ts b/src/providers/stale-context-window-migration.ts index 2cc764eebfd..533cb3e50fb 100644 --- a/src/providers/stale-context-window-migration.ts +++ b/src/providers/stale-context-window-migration.ts @@ -10,13 +10,13 @@ * * This rewrites one thing: a window whose saved value is still byte-for-byte the * wrong number this file names, on a provider that still carries the registry's - * adapter. A value the user changed does not match `from` and is left alone, and - * nothing else in the row is touched. Same shape and the same restraint as - * `model-rename-migration`, for the case where the id was right and the number - * was not. + * adapter and still points at the registry's own endpoint. A value the user + * changed does not match `from` and is left alone, and nothing else in the row + * is touched. Same shape and the same restraint as `model-rename-migration`, + * for the case where the id was right and the number was not. */ import { PROVIDER_REGISTRY } from "./registry"; -import type { OcxConfig } from "../types"; +import type { OcxConfig, OcxProviderConfig } from "../types"; export interface StaleContextWindow { /** Registry provider id whose saved rows may carry the wrong window. */ @@ -58,9 +58,22 @@ export const STALE_CONTEXT_WINDOWS: readonly StaleContextWindow[] = [ { provider: "devin", model: "grok-4-5", from: 256_000, to: 500_000 }, ]; -function providerStillMatchesRegistry(id: string, adapter: unknown): boolean { - const entry = PROVIDER_REGISTRY.find(row => row.id === id); - return entry !== undefined && entry.adapter === adapter; +/** + * Only repair a row that still points at the registry's own endpoint. The + * adapter alone cannot tell the registry provider from another destination — + * a repointed `alibaba-token-plan-intl` row keeps the generic `openai-chat` + * adapter, but the windows on its custom gateway are the user's own figures. + * Same ownership rule as `model-rename-migration`. + */ +function providerStillMatchesRegistry(name: string, prov: OcxProviderConfig): boolean { + const entry = PROVIDER_REGISTRY.find(row => row.id === name); + if (!entry || entry.adapter !== prov.adapter) return false; + if (!prov.baseUrl || !entry.baseUrl) return true; + const choices = entry.baseUrlChoices?.map(choice => choice.baseUrl) ?? []; + const known = [entry.baseUrl, ...choices] + .filter((url): url is string => typeof url === "string") + .map(url => url.replace(/\/+$/, "")); + return known.includes(prov.baseUrl.replace(/\/+$/, "")); } /** Pure projection. The caller decides whether to persist. */ @@ -74,7 +87,7 @@ export function projectStaleContextWindows( for (const entry of entries) { const prov = config.providers?.[entry.provider]; if (!prov) continue; - if (!providerStillMatchesRegistry(entry.provider, prov.adapter)) continue; + if (!providerStillMatchesRegistry(entry.provider, prov)) continue; const windows = prov.modelContextWindows; if (!windows || windows[entry.model] !== entry.from) continue; windows[entry.model] = entry.to; diff --git a/tests/providers/context-window-seed-repair.test.ts b/tests/providers/context-window-seed-repair.test.ts index 24cba8e1dfe..5428382e4cd 100644 --- a/tests/providers/context-window-seed-repair.test.ts +++ b/tests/providers/context-window-seed-repair.test.ts @@ -47,6 +47,39 @@ describe("stale context window migration", () => { }, ); + test.each(["alibaba-token-plan", "alibaba-token-plan-intl"] as const)( + "skips %s when the row was repointed to a custom gateway", + provider => { + // A repointed row keeps the provider id and the generic openai-chat + // adapter, so the adapter alone cannot tell Alibaba from another + // OpenAI-compatible destination — the 983,616 there may be that + // gateway's real limit rather than the stale registry seed. + const config = alibabaConfig(provider, 983_616); + config.providers![provider]!.baseUrl = "https://gateway.example/v1"; + const projection = projectStaleContextWindows(config); + expect(projection.changed).toBe(false); + expect(projection.config.providers![provider]!.modelContextWindows!["qwen3.8-max"]).toBe(983_616); + }, + ); + + test("repairs a row pointing at the registry endpoint with a trailing slash", () => { + const config = alibabaConfig("alibaba-token-plan", 983_616); + config.providers!["alibaba-token-plan"]!.baseUrl = + "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/"; + const projection = projectStaleContextWindows(config); + expect(projection.changed).toBe(true); + expect(projection.config.providers!["alibaba-token-plan"]!.modelContextWindows!["qwen3.8-max"]).toBe(1_000_000); + }); + + test("repairs an intl row pointed at a declared baseUrlChoices endpoint", () => { + const config = alibabaConfig("alibaba-token-plan-intl", 983_616); + config.providers!["alibaba-token-plan-intl"]!.baseUrl = + "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"; + const projection = projectStaleContextWindows(config); + expect(projection.changed).toBe(true); + expect(projection.config.providers!["alibaba-token-plan-intl"]!.modelContextWindows!["qwen3.8-max"]).toBe(1_000_000); + }); + test("skips a row that no longer carries the registry adapter", () => { // A `devin` row retargeted at another transport is not the provider these // numbers describe, so rewriting its windows would be a guess.