diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 1f480bc109..63e56d4231 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -439,7 +439,10 @@ configuration that names the old id is rewritten at startup. with manual protobuf framing in `devin/cloud-direct/wire.ts`; the ordinary `buildRequest` / `parseStream` path is disabled. - Live model discovery via `GetCascadeModelConfigs`; the static seed is filtered against the - account's live roster so models not on the plan drop out instead of failing at request time. + account's live roster so models not on the plan drop out instead of failing at request time. Each + model's effort control uses the variants exposed by that account; the static fallback ladder is + used only before discovery or when discovery fails. An explicit per-model ladder remains an + operator override. - Tool definitions are encoded in the request and tool-call events are decoded from the response stream. Cognition enforces a per-tool-description length limit (6,998 chars) and an exact-phrase blocklist; the adapter sanitizes known triggers and truncates over-long descriptions before diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index f76e0b98fb..b2dd5e030a 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -876,6 +876,37 @@ export function applyProviderConfigHints( }; } +/** Build a Devin row without letting its degraded provider fallback replace live account evidence. */ +export function buildDevinLiveCatalogEntry( + name: string, + prov: OcxProviderConfig, + id: string, + liveWindow: number | undefined, + liveEfforts: string[] | undefined, + providerCap?: number, + metadataModelIdCaseFold?: boolean, + effectiveAlias?: string | null, +): CatalogModel { + const hinted = catalogHintsFromProviderConfig( + name, + prov, + id, + providerCap, + metadataModelIdCaseFold, + effectiveAlias, + ); + const modelEfforts = modelRecordValue(prov.modelReasoningEfforts, id); + return { + id, + provider: name, + ...(liveWindow ? { contextWindow: liveWindow } : {}), + ...hinted, + // A per-model setting is an operator override. The provider-wide value is + // only a degraded-mode fallback, so live account evidence supersedes it. + ...(liveEfforts?.length && modelEfforts === undefined ? { reasoningEfforts: liveEfforts } : {}), + }; +} + export function catalogHintsFromProviderConfig( name: string, prov: OcxProviderConfig, @@ -1735,18 +1766,16 @@ async function fetchProviderModelsWithAuth( // chose. const result = liveResult.models.map((id) => { const liveWindow = liveResult.contextWindows[id]; - return { + return buildDevinLiveCatalogEntry( + name, + prov, id, - provider: name, - ...(liveWindow ? { contextWindow: liveWindow } : {}), - // The account catalog names the effort variants each base model has, so - // its ladder is measured rather than assumed. Without this the entry - // inherits the generic routed ladder and offers rungs the model rounds - // away, and every client that keys an effort control off this field — - // the Pi-shaped exports — renders no control at all. - ...(liveResult.efforts[id]?.length ? { reasoningEfforts: liveResult.efforts[id] } : {}), - ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias), - } as CatalogModel; + liveWindow, + liveResult.efforts[id], + contextCap, + metadataModelIdCaseFold, + captured.effectiveAlias, + ); }); const forCache = withConfiguredRetention(result, { retainComboTargets: false }); if (!setCached(name, forCache, Date.now(), cacheGeneration)) { diff --git a/structure/catalog.md b/structure/catalog.md index 8cce6268b0..328f0c514b 100644 --- a/structure/catalog.md +++ b/structure/catalog.md @@ -336,3 +336,7 @@ Exact [model input declarations](config.md#explicit-per-model-capability-declara ## Renamed destination reasoning metadata `src/providers/derive.ts` fills missing reasoning tables for renamed providers accepted by the existing fixed-key destination matcher. Model entries are cloned and explicit user entries (including empty arrays) win. Provider-wide effort defaults fill only when undefined; Command Code unknown models therefore keep the registry's empty picker policy unless overridden. Identity, transport and other capability axes are unchanged. The gathered row drives client exports; this metadata contract does not prove arbitrary gateway routing. + +## Devin live effort authority + +`src/codex/catalog/provider-fetch.ts` treats the reasoning ladder measured from an authenticated Devin account catalog as authoritative over the registry's provider-wide degraded-mode fallback. An explicit `modelReasoningEfforts` entry remains authoritative over discovery. Static model-specific and provider-wide ladders continue to supply signed-out and failed-discovery rows only. diff --git a/structure/codex-home.md b/structure/codex-home.md index 1f1edfd7da..360cb68526 100644 --- a/structure/codex-home.md +++ b/structure/codex-home.md @@ -273,3 +273,5 @@ Pool quota producers and account commands follow the [bounded raw-observation co The account history response can include a [low-confidence effective capacity estimate](providers/openai-tiers.md#observed-effective-token-capacity); usage normalization retains local-answer provenance so local responses cannot supply samples. Codex pool settings and their consumers follow the [reset-first ordering contract](providers/openai-tiers.md#reset-first-account-ordering), including independent-quota fallback and preserved affinity. + +Materialized Devin rows preserve the [live account effort ladder](catalog.md#devin-live-effort-authority) rather than replacing it with degraded-mode catalog defaults. diff --git a/structure/config.md b/structure/config.md index f799ebd2c5..b407b3bb9c 100644 --- a/structure/config.md +++ b/structure/config.md @@ -297,3 +297,5 @@ Display-name validation retains prototype-shaped model IDs as data; reviewer-tar `modelCapabilities` on `src/types/provider.ts` stores exact model-ID entries with optional inputModalities, contextTier and video.processing axes. `src/config/provider-validation.ts` strictly validates writes and merges PATCH axes without sharing live objects; null map/model/axis/processing tombstones delete, while empty PATCH objects do nothing. Complete POST/PUT replacements reject tombstones. File reads retain valid axes; malformed explicit modalities restrict to text with a diagnostic. The two catalog writers receive explicit config and gather fingerprints include the map. This storage contract alone does not activate a context tier, advertise a larger window or enable video processing. The text-only consumer reads exact inputModalities declarations before legacy hints. CLI add/edit `--text-only` targets one model and preserves sibling declarations; `src/vision/eligibility.ts` routes declared text-only models into existing image-description or explicit-omission handling. Positive routed image declarations override stale candidate metadata, while native catalog authority retains its existing legacy policy. + +For Devin, explicit per-model effort configuration retains precedence while provider-wide defaults yield to [live account evidence](catalog.md#devin-live-effort-authority). diff --git a/structure/gui-and-management-api.md b/structure/gui-and-management-api.md index c445858e6b..61c8fb015f 100644 --- a/structure/gui-and-management-api.md +++ b/structure/gui-and-management-api.md @@ -594,7 +594,7 @@ Live sideband admission and its bounded upstream handshake follow the [runtime c Dashboard overview polling observes authorization failures independently of stalled or rejected peer requests, cancels remaining child requests after a decisive result, and exposes resource-level deadline failures without rewriting them as authentication failures. The [explicit model-capability contract](config.md#explicit-per-model-capability-declarations) preserves operator declarations through provider storage and catalog capture; it does not infer upstream capability or change this surface's routing behavior. - Exact [model input declarations](config.md#explicit-per-model-capability-declarations) now feed text-only eligibility and catalog hints; existing image-description/omission handling consumes them before the main upstream send. The raw provider editor round-trips `autoReviewModel` and `autoReviewModelOverrides` through editor-owned DTO fields. POST/PATCH/PUT share validation; PUT copies schema-normalized values into the persisted and live candidate before adoption. Canonical `openai` rejects these fields, including clear forms. Field-masked writes (PATCH, editor PUT, reload) pin every registry-seed key and ignore operator overlays the seed never defines, most commonly `selectedModels`; POST keeps the exact-key comparison. Canonical `openai` still rejects `allowPrivateNetwork`, which must not short-circuit destination DNS checks on the ChatGPT forward row. Existing authentication, origin checks and stale-baseline protection still govern the writes. See [reviewer projection](catalog.md#provider-scoped-approval-reviewer). +Management and exported client model rows display Devin's [live account effort ladder](catalog.md#devin-live-effort-authority) when discovery succeeds. diff --git a/structure/ops/docs-and-release.md b/structure/ops/docs-and-release.md index 17e54e3664..9aaffe68e1 100644 --- a/structure/ops/docs-and-release.md +++ b/structure/ops/docs-and-release.md @@ -375,3 +375,5 @@ Exact [model input declarations](../config.md#explicit-per-model-capability-decl Provider-scoped approval reviewer settings are projected by the [catalog owner](../catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior. Renamed fixed-key providers receive [missing reasoning metadata](../catalog.md#renamed-destination-reasoning-metadata) during derivation; explicit per-model entries and provider defaults retain precedence. + +Catalog publication carries Devin's [live account effort ladder](../catalog.md#devin-live-effort-authority) through generated client artifacts. diff --git a/structure/providers/openai-tiers.md b/structure/providers/openai-tiers.md index 527aacba6b..2902c865ce 100644 --- a/structure/providers/openai-tiers.md +++ b/structure/providers/openai-tiers.md @@ -518,3 +518,5 @@ The history read API reports a median effective token estimate and interval samp Live bindings obey the existing cache-affinity release policy: with `pool.cacheAffinity`, threshold crossing alone retains a healthy account. Manual preference, scoped health and shared-cursor guards remain authoritative. Independent `spark`/`reserve` quota scopes resolve reset-first to existing quota selection because shared reset timestamps do not describe those windows. The configured value stays unchanged. The Codex parser in `src/oauth/pool-kernel.ts` is reexported by the compatibility facade and used by both `/api/pool/settings` and the legacy Codex settings route. Generic and Anthropic parsers reject reset-first. The dashboard offers it only for Codex; API, CLI and translated guides preserve the same contract. + +Non-OpenAI Devin catalog rows use the shared [live effort authority](../catalog.md#devin-live-effort-authority); this does not change OpenAI tier or pool selection. diff --git a/structure/runtime.md b/structure/runtime.md index a09d007d7b..d09fddf610 100644 --- a/structure/runtime.md +++ b/structure/runtime.md @@ -350,3 +350,5 @@ Exact [model input declarations](config.md#explicit-per-model-capability-declara Provider-scoped approval reviewer settings are projected by the [catalog owner](catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior. Renamed fixed-key providers receive [missing reasoning metadata](catalog.md#renamed-destination-reasoning-metadata) during derivation; explicit per-model entries and provider defaults retain precedence. + +Devin catalog routing follows the [live effort authority](catalog.md#devin-live-effort-authority); request transport behavior is unchanged. diff --git a/structure/subagents.md b/structure/subagents.md index 69047b076b..aa3af9f741 100644 --- a/structure/subagents.md +++ b/structure/subagents.md @@ -371,3 +371,5 @@ Exact [model input declarations](config.md#explicit-per-model-capability-declara Provider-scoped approval reviewer settings are projected by the [catalog owner](catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior. Renamed fixed-key providers receive [missing reasoning metadata](catalog.md#renamed-destination-reasoning-metadata) during derivation; explicit per-model entries and provider defaults retain precedence. + +Devin subagent choices consume the final [live account effort ladder](catalog.md#devin-live-effort-authority), subject to explicit per-model configuration. diff --git a/tests/providers/devin-effort-ladder.test.ts b/tests/providers/devin-effort-ladder.test.ts index 0b800c11a8..2099412e39 100644 --- a/tests/providers/devin-effort-ladder.test.ts +++ b/tests/providers/devin-effort-ladder.test.ts @@ -7,6 +7,8 @@ import { sortDevinRungs, } from "../../src/adapters/devin/live-models"; import { PROVIDER_REGISTRY } from "../../src/providers/registry"; +import { providerConfigSeed } from "../../src/providers/derive"; +import { buildDevinLiveCatalogEntry } from "../../src/codex/catalog/provider-fetch"; const devinRow = () => PROVIDER_REGISTRY.find(row => row.id === "devin")!; @@ -39,6 +41,35 @@ describe("devin reasoning rungs come from the catalog suffixes", () => { }); describe("devin advertises a ladder instead of inheriting the generic one", () => { + test("live account rungs supersede the degraded provider fallback", () => { + const provider = providerConfigSeed(devinRow()); + const model = buildDevinLiveCatalogEntry( + "devin", + provider, + "account-model", + 200_000, + ["low", "high"], + ); + + expect(model.reasoningEfforts).toEqual(["low", "high"]); + }); + + test("an explicit per-model ladder still overrides live account rungs", () => { + const provider = { + ...providerConfigSeed(devinRow()), + modelReasoningEfforts: { "account-model": ["medium", "max"] }, + }; + const model = buildDevinLiveCatalogEntry( + "devin", + provider, + "account-model", + undefined, + ["low", "high"], + ); + + expect(model.reasoningEfforts).toEqual(["medium", "max"]); + }); + test("the provider row carries both fields", () => { // modelReasoningEfforts drives the Codex picker; reasoningEfforts is what the // Pi-shaped client exports read. Without them the row inherited the routed