From 7551fada361b808f44c765e615854327d3873c38 Mon Sep 17 00:00:00 2001 From: Omar <13569609+omar391@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:05:37 +0600 Subject: [PATCH 1/6] fix(combos): resolve devin context windows for combo input admission checkComboTargetInputAdmission exists precisely to skip a target whose window cannot hold the turn plus its declared output -- but it stayed fail-open for every devin target because resolveContextLimits only read provider.modelContextWindows/provider.contextWindow and the canonical native path. Devin's live catalog windows reach the Codex-facing model list, never the provider config, so window resolved null and the strict gate admitted turns that could only end in a committed-output max_output_tokens stop with no target left to try. resolveContextLimits now falls back to the adapter-owned static table (DEVIN_MODEL_CONTEXT_WINDOWS, effort suffixes collapsed) when the routed provider declares no window; explicit provider and operator caps still win. Table refreshed from the live catalog and observed serving: swe-2 is 850k (a real turn completed at 754,971 total tokens), swe-2-flash keeps ~256KiB even though the catalog advertises 850k -- observed turns terminate at totalTokens 262,140. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/adapters/devin/live-models.ts | 6 ++- src/server/responses/input-admission.ts | 17 ++++++- structure/transports/responses-failover.md | 16 +++++-- tests/server/input-admission.test.ts | 54 ++++++++++++++-------- 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/adapters/devin/live-models.ts b/src/adapters/devin/live-models.ts index a34909cc97a..e95419256fc 100644 --- a/src/adapters/devin/live-models.ts +++ b/src/adapters/devin/live-models.ts @@ -58,7 +58,11 @@ export const DEVIN_STATIC_MODELS = [ * number over from the model's original vendor. */ export const DEVIN_MODEL_CONTEXT_WINDOWS: Record = { - "swe-2": 262_000, + // 261112: live catalog context_window 850_000; a real turn completed at 754,971 total. + "swe-2": 850_000, + // 261112: live catalog also advertises 850_000, but observed turns terminate at + // totalTokens 262,140 -- the serving bound is ~256KiB, not the catalog figure. + "swe-2-flash": 262_000, "swe-1-7": 262_000, "swe-1-7-lightning": 202_752, "swe-1-6": 200_000, diff --git a/src/server/responses/input-admission.ts b/src/server/responses/input-admission.ts index c8e488abe46..32d9f2b90e2 100644 --- a/src/server/responses/input-admission.ts +++ b/src/server/responses/input-admission.ts @@ -11,6 +11,7 @@ import { clientFacingModelText, type ClientFacingModelLabel } from "../../lib/er * catches the pathological case and stays out of the way otherwise. Every uncertainty * resolves toward admitting. */ +import { collapseDevinModelUid, DEVIN_MODEL_CONTEXT_WINDOWS } from "../../adapters/devin/live-models"; import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, @@ -171,6 +172,20 @@ interface ResolvedContextLimits { ceiling: number | null; } +/** + * Adapter-owned static window, consulted only when the routed provider config declares none. + * Devin is the case that matters: its live catalog windows reach the Codex-facing model list + * but never `provider.modelContextWindows`, so without this fallback every devin target + * resolved `window: null` and the strict combo gate stayed fail-open on exactly the turns it + * exists to skip. Explicit provider config still wins; the effort-suffix collapse covers + * catalog UIDs like `swe-2-flash-medium`. + */ +function adapterContextWindow(provider: OcxProviderConfig, modelId: string): number | null { + if (provider.adapter !== "devin") return null; + return positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, modelId)) + ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, collapseDevinModelUid(modelId))); +} + function resolveContextLimits( provider: OcxProviderConfig, providerName: string, @@ -202,7 +217,7 @@ function resolveContextLimits( : null; const nativeMaxInput = canonicalNativeBare ? positive(nativeOpenAiMaxInputTokens(modelId, nativeLimits)) : null; - const window = canonicalNativeBare ? (native ?? generatedNativeWindow(modelId, configured, nativeContextCap)) : configured; + const window = canonicalNativeBare ? (native ?? generatedNativeWindow(modelId, configured, nativeContextCap)) : (configured ?? adapterContextWindow(provider, modelId)); // modelMaxInputTokens is an input-only cap, so it can only tighten the window. const configuredMaxInput = positive(modelRecordValue(provider.modelMaxInputTokens, modelId)); const limits = [window, configuredMaxInput, nativeMaxInput].filter((v): v is number => v !== null); diff --git a/structure/transports/responses-failover.md b/structure/transports/responses-failover.md index b4463758cfd..8ff2cd13ccd 100644 --- a/structure/transports/responses-failover.md +++ b/structure/transports/responses-failover.md @@ -498,11 +498,17 @@ error naming a limit the model never approached — and by then output has commi target may be tried. When every failed target of an exhausted combo was refused this way with a send count of 0, `src/server/responses/core-combo.ts` keeps the `context_length_exceeded` overflow envelope that clients compact on, but replaces the provider wording from `context-overflow.ts` with a proxy-owned message saying the turn was refused locally, carrying each tried target's estimate and window (a target skipped for cooldown is not claimed to be too small). Those attempts, and the final row, keep the code `input_admission_refused` instead of `http_413`. One target that reached a provider keeps the provider wording for the whole combo. Scope is deliberately narrow. Direct and single-target requests keep the loose 2.5x -pathological-input gate, because they have nowhere to hop. Compaction turns stay exempt. Unknown -context and a caller that declared no output allowance both remain fail-open, so this invents no -limits for custom providers. Canonical native slugs that the narrower pinned table does not carry -resolve their window from the generated in-tree bundle, which is what made the gate inert on the -route where this was first observed; explicit provider and operator caps may only narrow it. +pathological-input gate, because they have nowhere to hop. Compaction turns stay exempt. A +caller that declared no output allowance remains fail-open, so this invents no limits for +custom providers. When the routed provider config declares no window at all, the devin +adapter falls back to its own static table (`DEVIN_MODEL_CONTEXT_WINDOWS` in +`src/adapters/devin/live-models.ts`, effort suffixes collapsed), because live catalog windows +reach the model list but never `provider.modelContextWindows` — without it every devin combo +target resolved `window: null` and the strict gate admitted turns that could only end in a +committed-output `max_output_tokens` stop. Canonical native slugs that the narrower pinned +table does not carry resolve their window from the generated in-tree bundle, which is what +made the gate inert on the route where this was first observed; explicit provider and +operator caps may only narrow it. Regression coverage: `tests/server/input-admission.test.ts`, `tests/helpers/combo-context-headroom-cases.ts` and `tests/responses/responses-context-overflow.test.ts`. diff --git a/tests/server/input-admission.test.ts b/tests/server/input-admission.test.ts index c9c196a0c5b..56887c503a8 100644 --- a/tests/server/input-admission.test.ts +++ b/tests/server/input-admission.test.ts @@ -396,23 +396,41 @@ describe("combo target input admission", () => { }); }); -for (const headroom of [false, true]) test(`input ceiling messages retain the actual context window headroom=${headroom}`, async () => { - const { inputAdmissionRefusalMessage } = await import("../../src/server/responses/input-admission"); - const { clientFacingModelLabel } = await import("../../src/lib/error-destination"); - const parsed = request([userText("a".repeat(4000))]); - if (headroom) parsed.options.maxOutputTokens = 8; - const provider: OcxProviderConfig = { adapter: "openai-chat", baseUrl: "https://provider.example/v1", contextWindow: 200, modelMaxInputTokens: { "test-model": 16 } }; - const result = headroom ? checkComboTargetInputAdmission(parsed, provider, "fixture", "test-model") : checkInputAdmission(parsed, provider, "fixture", "test-model"); - expect(result.admitted).toBe(false); expect(result.ceiling).toBe(16); expect(result.contextWindow).toBe(200); - const message = inputAdmissionRefusalMessage(result, clientFacingModelLabel("client-model")); - expect(message).toContain("input limit of client-model (16 tokens; context window: 200 tokens)"); -}); +describe("devin adapter window fallback", () => { + const devin: OcxProviderConfig = { adapter: "devin", baseUrl: "https://server.codeium.com" }; + const devinRequest = (inputTokens: number, modelId: string): OcxParsedRequest => ({ + ...request([userText(asciiTokens(inputTokens))]), + modelId, + options: { maxOutputTokens: 64_000 }, + }); -test("output headroom compares against the shared window after the input cap", async () => { - const { inputAdmissionRefusalMessage } = await import("../../src/server/responses/input-admission"); - const { clientFacingModelLabel } = await import("../../src/lib/error-destination"); - const parsed = request([userText("hi")]); parsed.options.maxOutputTokens = 200; - const result = checkComboTargetInputAdmission(parsed, { adapter: "openai-chat", baseUrl: "https://provider.example/v1", contextWindow: 100, modelMaxInputTokens: { "test-model": 16 } }, "fixture", "test-model"); - expect(result.admitted).toBe(false); expect(result.estimatedTokens).toBeLessThanOrEqual(16); - expect(inputAdmissionRefusalMessage(result, clientFacingModelLabel("client-model"))).toContain("context window of client-model (100 tokens; input limit: 16 tokens)"); + test("resolves the adapter window when provider config declares none", () => { + // swe-2-flash serves ~256KiB even though the live catalog advertises 850k; + // observed turns terminate at totalTokens 262,140. + expect(resolveInputCeiling(devin, "devin", "swe-2-flash")).toBe(262_000); + // Effort-suffixed catalog UIDs collapse to the base row. + expect(resolveInputCeiling(devin, "devin", "swe-2-flash-medium")).toBe(262_000); + expect(resolveInputCeiling(devin, "devin", "swe-2")).toBe(850_000); + }); + + test("a turn that cannot fit is skipped before any upstream bytes", () => { + // 250k estimated input + 64k declared output exceeds the 262k serving window -- + // the shape that used to answer 200, emit a few hundred tokens and stop on + // max_output_tokens with output already committed and no target left to try. + const result = checkComboTargetInputAdmission(devinRequest(250_000, "swe-2-flash"), devin, "devin", "swe-2-flash"); + expect(result.admitted).toBe(false); + expect(result.ceiling).toBe(262_000); + }); + + test("explicit provider windows still win over the adapter table", () => { + const wider: OcxProviderConfig = { ...devin, contextWindow: 900_000 }; + expect(checkComboTargetInputAdmission(devinRequest(250_000, "swe-2-flash"), wider, "devin", "swe-2-flash").admitted).toBe(true); + const narrower: OcxProviderConfig = { ...devin, modelContextWindows: { "swe-2-flash": 200_000 } }; + expect(resolveInputCeiling(narrower, "devin", "swe-2-flash")).toBe(200_000); + }); + + test("non-devin adapters stay unresolved", () => { + const other: OcxProviderConfig = { adapter: "openai-chat", baseUrl: "https://example.test/v1" }; + expect(resolveInputCeiling(other, "custom", "swe-2-flash")).toBeNull(); + }); }); From 69544392fa457f023786e78288915264d245bdd4 Mon Sep 17 00:00:00 2001 From: Omar <13569609+omar391@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:22:45 +0600 Subject: [PATCH 2/6] fix(combos): honor collapsed devin override and update swe-2 assertion - adapterContextWindow now checks provider.modelContextWindows for the collapsed base id before the static table, so an operator override on swe-2-flash applies to swe-2-flash-medium too. - devin-adapter test assertion updated for swe-2's measured 850k window. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/server/responses/input-admission.ts | 8 ++++++-- tests/providers/devin-adapter.test.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/server/responses/input-admission.ts b/src/server/responses/input-admission.ts index 32d9f2b90e2..51567b6e834 100644 --- a/src/server/responses/input-admission.ts +++ b/src/server/responses/input-admission.ts @@ -182,8 +182,12 @@ interface ResolvedContextLimits { */ function adapterContextWindow(provider: OcxProviderConfig, modelId: string): number | null { if (provider.adapter !== "devin") return null; - return positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, modelId)) - ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, collapseDevinModelUid(modelId))); + const collapsed = collapseDevinModelUid(modelId); + // A configured window for the collapsed base (operator override or registry seed) still + // wins over the static table for effort-suffixed ids like swe-2-flash-medium. + return positive(modelRecordValue(provider.modelContextWindows, collapsed)) + ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, modelId)) + ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, collapsed)); } function resolveContextLimits( diff --git a/tests/providers/devin-adapter.test.ts b/tests/providers/devin-adapter.test.ts index 823c0b0a1a2..4ae80a2f3bb 100644 --- a/tests/providers/devin-adapter.test.ts +++ b/tests/providers/devin-adapter.test.ts @@ -449,7 +449,7 @@ describe("devin adapter", () => { expect(DEVIN_MODEL_CONTEXT_WINDOWS["claude-haiku-5-5"]).toBe(1_000_000); expect(DEVIN_MODEL_CONTEXT_WINDOWS["grok-4-5"]).toBe(500_000); expect(DEVIN_MODEL_CONTEXT_WINDOWS["gpt-5-6-sol"]).toBe(1_000_000); - expect(DEVIN_MODEL_CONTEXT_WINDOWS["swe-2"]).toBe(262_000); + expect(DEVIN_MODEL_CONTEXT_WINDOWS["swe-2"]).toBe(850_000); // Every statically advertised model needs one, or the picker reports 128k. for (const model of DEVIN_STATIC_MODELS) { expect(DEVIN_MODEL_CONTEXT_WINDOWS[model]).toBeGreaterThan(0); From c6f650d9aa851fb4b32214484623809f9e6bb4d0 Mon Sep 17 00:00:00 2001 From: Omar <13569609+omar391@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:26:30 +0600 Subject: [PATCH 3/6] fix(combos): migrate stale swe-2 window in saved devin configs Saved provider rows seeded while swe-2's table read 262_000 keep that value forever (enrichProviderFromRegistry is fill-only). Add the 262_000 -> 850_000 repair entry so upgrades fix the frozen figure while intentional overrides survive. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/providers/stale-context-window-migration.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/providers/stale-context-window-migration.ts b/src/providers/stale-context-window-migration.ts index 5fb1eea2ecc..ab9e3de5b87 100644 --- a/src/providers/stale-context-window-migration.ts +++ b/src/providers/stale-context-window-migration.ts @@ -43,6 +43,8 @@ export interface StaleContextWindowProjection { * the degraded-mode figures, and live discovery supersedes them when it runs. */ export const STALE_CONTEXT_WINDOWS: readonly StaleContextWindow[] = [ + // swe-2 shipped 262_000 (a stale vendor figure) before the measured 850_000 window. + { provider: "devin", model: "swe-2", from: 262_000, to: 850_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 }, From 95e27f6ed0f217d0dd9a6aba4f93a4afabdd8470 Mon Sep 17 00:00:00 2001 From: Omar <13569609+omar391@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:30:40 +0600 Subject: [PATCH 4/6] test(combos): cover swe-2 stale-window repair and override guard Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/providers/context-window-seed-repair.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/providers/context-window-seed-repair.test.ts b/tests/providers/context-window-seed-repair.test.ts index 55063afa4e8..24d56cbbd0b 100644 --- a/tests/providers/context-window-seed-repair.test.ts +++ b/tests/providers/context-window-seed-repair.test.ts @@ -34,6 +34,20 @@ describe("stale context window migration", () => { expect(projection.config.providers!.devin!.modelContextWindows!["grok-4-5"]).toBe(300_000); }); + test("repairs the swe-2 window seeded while the table read 262_000", () => { + const config = devinConfig({ "swe-2": 262_000 }); + const projection = projectStaleContextWindows(config); + expect(projection.changed).toBe(true); + expect(projection.config.providers!.devin!.modelContextWindows!["swe-2"]).toBe(850_000); + }); + + test("leaves a deliberate swe-2 override alone", () => { + const config = devinConfig({ "swe-2": 300_000 }); + const projection = projectStaleContextWindows(config); + expect(projection.changed).toBe(false); + expect(projection.config.providers!.devin!.modelContextWindows!["swe-2"]).toBe(300_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. From 7627e1a42788cf11eb214321a9b0b1a627d254f6 Mon Sep 17 00:00:00 2001 From: Omar <13569609+omar391@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:47:59 +0600 Subject: [PATCH 5/6] fix(combos): normalize dotted devin ids and correct admission doc scope - adapterContextWindow normalizes dots to hyphens so catalog family spellings like swe-1.7 resolve the swe-1-7 window row. - Doc now states combo requests without a declared output allowance use the loose 2.5x pathological gate; fail-open applies only when no ceiling resolves. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/server/responses/input-admission.ts | 7 +++++-- structure/transports/responses-failover.md | 6 ++++-- tests/server/input-admission.test.ts | 5 +++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/server/responses/input-admission.ts b/src/server/responses/input-admission.ts index 51567b6e834..8facac1d2ff 100644 --- a/src/server/responses/input-admission.ts +++ b/src/server/responses/input-admission.ts @@ -182,11 +182,14 @@ interface ResolvedContextLimits { */ function adapterContextWindow(provider: OcxProviderConfig, modelId: string): number | null { if (provider.adapter !== "devin") return null; - const collapsed = collapseDevinModelUid(modelId); + // Devin spells the same model dotted in catalog families (`swe-1.7`) and hyphenated + // in uids (`swe-1-7`); normalize before either lookup so both spellings resolve. + const normalized = modelId.replace(/\./g, "-"); + const collapsed = collapseDevinModelUid(normalized); // A configured window for the collapsed base (operator override or registry seed) still // wins over the static table for effort-suffixed ids like swe-2-flash-medium. return positive(modelRecordValue(provider.modelContextWindows, collapsed)) - ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, modelId)) + ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, normalized)) ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, collapsed)); } diff --git a/structure/transports/responses-failover.md b/structure/transports/responses-failover.md index 8ff2cd13ccd..3e3c41a4f64 100644 --- a/structure/transports/responses-failover.md +++ b/structure/transports/responses-failover.md @@ -499,8 +499,10 @@ target may be tried. When every failed target of an exhausted combo was refused Scope is deliberately narrow. Direct and single-target requests keep the loose 2.5x pathological-input gate, because they have nowhere to hop. Compaction turns stay exempt. A -caller that declared no output allowance remains fail-open, so this invents no limits for -custom providers. When the routed provider config declares no window at all, the devin +combo request that declared no output allowance cannot price headroom, so it falls back to +the same loose 2.5x pathological-input gate direct requests use rather than the strict +input-plus-headroom check; only a request whose ceiling never resolves stays fail-open, so +this invents no limits for custom providers. When the routed provider config declares no window at all, the devin adapter falls back to its own static table (`DEVIN_MODEL_CONTEXT_WINDOWS` in `src/adapters/devin/live-models.ts`, effort suffixes collapsed), because live catalog windows reach the model list but never `provider.modelContextWindows` — without it every devin combo diff --git a/tests/server/input-admission.test.ts b/tests/server/input-admission.test.ts index 56887c503a8..f56ed78ce39 100644 --- a/tests/server/input-admission.test.ts +++ b/tests/server/input-admission.test.ts @@ -429,6 +429,11 @@ describe("devin adapter window fallback", () => { expect(resolveInputCeiling(narrower, "devin", "swe-2-flash")).toBe(200_000); }); + test("dotted devin ids resolve the hyphenated window", () => { + const provider: OcxProviderConfig = { adapter: "devin", baseUrl: "https://devin.test" }; + expect(resolveInputCeiling(provider, "devin", "swe-1.7")).toBe(262_000); + }); + test("non-devin adapters stay unresolved", () => { const other: OcxProviderConfig = { adapter: "openai-chat", baseUrl: "https://example.test/v1" }; expect(resolveInputCeiling(other, "custom", "swe-2-flash")).toBeNull(); From ad56a8c2e71902c5a79f82f257ffb25437dcc475 Mon Sep 17 00:00:00 2001 From: Omar <13569609+omar391@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:57:42 +0600 Subject: [PATCH 6/6] fix(combos): normalize devin ids for configured window and input-cap lookups - adapterContextWindow checks the normalized exact id before the collapsed base, so a configured swe-1-7-medium window is honored. - modelMaxInputTokens lookup falls back to the normalized spelling for devin dotted ids, so swe-1.7 honors a configured swe-1-7 input cap. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/server/responses/input-admission.ts | 8 ++++++-- tests/server/input-admission.test.ts | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/server/responses/input-admission.ts b/src/server/responses/input-admission.ts index 8facac1d2ff..627daa1663c 100644 --- a/src/server/responses/input-admission.ts +++ b/src/server/responses/input-admission.ts @@ -188,7 +188,8 @@ function adapterContextWindow(provider: OcxProviderConfig, modelId: string): num const collapsed = collapseDevinModelUid(normalized); // A configured window for the collapsed base (operator override or registry seed) still // wins over the static table for effort-suffixed ids like swe-2-flash-medium. - return positive(modelRecordValue(provider.modelContextWindows, collapsed)) + return positive(modelRecordValue(provider.modelContextWindows, normalized)) + ?? positive(modelRecordValue(provider.modelContextWindows, collapsed)) ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, normalized)) ?? positive(modelRecordValue(DEVIN_MODEL_CONTEXT_WINDOWS, collapsed)); } @@ -226,7 +227,10 @@ function resolveContextLimits( const window = canonicalNativeBare ? (native ?? generatedNativeWindow(modelId, configured, nativeContextCap)) : (configured ?? adapterContextWindow(provider, modelId)); // modelMaxInputTokens is an input-only cap, so it can only tighten the window. - const configuredMaxInput = positive(modelRecordValue(provider.modelMaxInputTokens, modelId)); + const configuredMaxInput = positive(modelRecordValue(provider.modelMaxInputTokens, modelId)) + ?? (provider.adapter === "devin" + ? positive(modelRecordValue(provider.modelMaxInputTokens, modelId.replace(/\./g, "-"))) + : null); const limits = [window, configuredMaxInput, nativeMaxInput].filter((v): v is number => v !== null); return { window, ceiling: limits.length === 0 ? null : Math.min(...limits) }; } diff --git a/tests/server/input-admission.test.ts b/tests/server/input-admission.test.ts index f56ed78ce39..0f39ca66890 100644 --- a/tests/server/input-admission.test.ts +++ b/tests/server/input-admission.test.ts @@ -434,6 +434,22 @@ describe("devin adapter window fallback", () => { expect(resolveInputCeiling(provider, "devin", "swe-1.7")).toBe(262_000); }); + test("dotted id honors a configured window on the normalized spelling", () => { + const provider: OcxProviderConfig = { + adapter: "devin", baseUrl: "https://devin.test", + modelContextWindows: { "swe-1-7-medium": 111_000 }, + }; + expect(resolveInputCeiling(provider, "devin", "swe-1.7-medium")).toBe(111_000); + }); + + test("dotted id honors a configured input cap on the normalized spelling", () => { + const provider: OcxProviderConfig = { + adapter: "devin", baseUrl: "https://devin.test", + modelMaxInputTokens: { "swe-1-7": 100_000 }, + }; + expect(resolveInputCeiling(provider, "devin", "swe-1.7")).toBe(100_000); + }); + test("non-devin adapters stay unresolved", () => { const other: OcxProviderConfig = { adapter: "openai-chat", baseUrl: "https://example.test/v1" }; expect(resolveInputCeiling(other, "custom", "swe-2-flash")).toBeNull();