Files
OmniRoute/open-sse/utils/devinLiteralModelIds.ts
Mr White 5ba3eee2b0 fix(devin): treat Devin CLI model ids as literal — never strip or synthesize effort suffixes (#12492)
The Devin CLI providers (devin-cli, devin-cli-agentic, devin-desktop; aliases
dv/dva) serve a catalog whose model ids EMBED the reasoning tier:
claude-opus-5-low, claude-opus-5-medium, … and gpt-5-6-sol-max/-low are
distinct upstream models (see registry/devin/catalog.ts).

applyClaudeEffortVariant stripped the trailing -{low,medium,high,xhigh,max}
from any id whose base is a known Claude model, regardless of provider. For
Devin lanes this dispatched a base id that does not exist upstream, e.g.

  dva/claude-opus-5-low  ->  claude-opus-5  ->  400
  'Model is not present in the current Devin catalog: claude-opus-5'

Only accidental double-suffixed ids (dva/claude-opus-5-max-low) survived,
because stripping the outer -low left the real claude-opus-5-max. Symmetrically,
the catalog synthesized -<level> variants on top of tier-embedded ids,
advertising phantom ids (dva/gpt-5-6-sol-max-low, dva/kimi-k3-*) that 400 when
called.

Three gates now treat Devin ids as literal:
- applyClaudeEffortVariant: early return for Devin providers (ids/aliases)
- appendClaudeEffortVariants: no -<level> variants for devin-prefixed ids
- appendSyncedEffortVariants: isSkippedEffortProvider now covers Devin
  providers (they own their suffix mechanism — the tier IS the id)

Validated live on a self-hosted v3.8.51 deployment: dva/claude-opus-5-low,
dva/claude-5-fable-low and the whole tier-embedded catalog now dispatch; the
phantom variant ids disappear from /v1/models. Claude-lane stripping
(claude/cc, e.g. cc/claude-opus-5-high -> claude-opus-5 + reasoning_effort) is
unchanged and covered by existing + new characterization tests.

Co-authored-by: Neuron Mr White <whiteneuron@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-18 12:21:36 -03:00

41 lines
1.7 KiB
TypeScript

/**
* Devin CLI providers whose upstream catalog embeds the reasoning tier IN the
* model id itself: `claude-opus-5-low`, `claude-opus-5-medium`, … and
* `gpt-5-6-sol-max` / `gpt-5-6-sol-low` are distinct upstream models
* (see `config/providers/registry/devin/catalog.ts`). For these providers a
* trailing `-{effort}` suffix is NOT a client-side effort variant:
*
* - stripping it (`applyClaudeEffortVariant`) would dispatch a base id that
* does not exist upstream — e.g. `dva/claude-opus-5-low` became
* `claude-opus-5` and the executor rejected it with
* "Model is not present in the current Devin catalog";
* - synthesizing variants on top of tier-embedded ids produces phantom ids
* (`claude-opus-5-max-low`) that cannot route once the strip is fixed.
*
* Ids here cover the provider id and its routing alias, so both canonical and
* alias-prefixed qualified model ids are recognized.
*/
const DEVIN_LITERAL_MODEL_ID_PROVIDERS = new Set([
"devin-cli",
"devin-cli-agentic",
"devin-desktop",
]);
const DEVIN_LITERAL_MODEL_ID_ALIASES = new Set(["dv", "dva"]);
function bareProviderToken(value: string): string {
const slash = value.indexOf("/");
return slash >= 0 ? value.slice(0, slash) : value;
}
/**
* True when `provider` (a provider id or alias, optionally `provider/model`
* qualified) serves a Devin catalog whose model ids embed the effort tier and
* must therefore be treated as literal ids.
*/
export function isDevinLiteralModelIdProvider(provider: string | null | undefined): boolean {
if (typeof provider !== "string" || provider.length === 0) return false;
const token = bareProviderToken(provider);
return DEVIN_LITERAL_MODEL_ID_PROVIDERS.has(token) || DEVIN_LITERAL_MODEL_ID_ALIASES.has(token);
}