feat(agent): Phase 1 — model-capability manifest, generator, and test oracle (#3821)

## What this does

Introduces the model-capability manifest infrastructure (Phase 1 of the
Model-Capability Manifest plan v4, Thufir-approved 9/9/9). No consumer
cutover — `config.rs`, `llm.rs`, `catalog.rs`, and `buzzAgentConfig.ts`
are unchanged. Phase 2 wires them.

**Single source of truth** replaces hand-mirrored metadata across four
files:

```
scripts/model-capabilities.json         → hand-curated manifest
scripts/generate-model-capabilities.mjs → emits Rust + TS artifacts
crates/buzz-agent/src/generated_model_capabilities.rs
desktop/src/features/agents/ui/modelCapabilities.ts
```

## Resolver contract (plan v4 §Resolver contract)

Total function `resolve(provider, raw_model_id) → CapabilityResult`.
Three ordered steps:

1. Provider-qualified raw exact lookup — key is `(provider,
raw_model_id)`, matched before any prefix stripping. A prefixed alias
never inherits an exact record.
2. Provider-scoped ordered family rules — on normalized
(prefix-stripped) alias, by `match_priority` desc.
3. Per-axis provider fallback — `blank` vs `concrete_unknown`, per
provider.

Result is complete — every axis populated, runtime consumers never
compose fields.

## Boundaries the manifest does NOT own

- Transport for pure OpenAI, legacy Databricks, OpenRouter:
`OpenAiApi`/`openai_request()` remain authoritative.
`databricks_v2_wire_route` is DBv2-only (all other providers emit
`not-applicable`).
- Final display labels: `resolveModelLabel()` three-tier precedence
unchanged. `registry_label` feeds only the static registry tier.
- `llm.rs` scope: only `databricks_v2_route_for_model` (Phase 2).

## Test oracle (three independent layers)

1. Generated full-table coverage —
`scripts/generated-model-capabilities-coverage.json`: every manifest
entry + provider fallbacks.
2. Hand-authored normative corpus — `scripts/normative-corpus.json` (44
vectors): Anthropic manual-budget/adaptive families, OpenAI gpt-5
adversarial boundary cases, DBv2 segment-routing collision tests, P2-A
resolver-contract vectors, P2-B blank/concrete-unknown per provider.
Runs against JS resolver (`run-corpus.mjs`) and mirrored in Rust
(`generated_model_capabilities_tests.rs`).
3. Schema-negative tests — `scripts/test-manifest-validator.mjs` (17
tests): every validator rule has a failing-input test.

## Reconciliation table

`scripts/MODELS_DEV_RECONCILIATION.md` — all models.dev divergences
dispositioned. `databricks-gpt-5-4-mini` and `databricks-gpt-5-4-nano`
adopt models.dev `[low,medium,high]` (family rule adds `none+xhigh` the
endpoint doesn't advertise).

## CI

`.github/workflows/model-capability-regen-diff.yml`: triggers on
manifest/generator/artifact changes; regenerates and fails if stale;
runs JS corpus + schema-negative tests.

## Acceptance criteria (plan v4 Phase 1)

- Byte-clean regen: `node scripts/generate-model-capabilities.mjs
--check` passes
- Rust compiles: `cargo check -p buzz-agent`
- TS typechecks: `pnpm tsc --noEmit --strict`
- 44/44 normative corpus vectors pass (JS interpreter)
- 41/41 Rust corpus tests pass
- 17/17 schema-negative tests pass (every validator rule)
- Reconciliation table complete with doc citations
- No consumer changes (config.rs, llm.rs, catalog.rs, buzzAgentConfig.ts
untouched)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
This commit is contained in:
Will Pfleger
2026-07-31 12:39:37 -04:00
committed by GitHub
parent 485b36280a
commit 4d47f48143
16 changed files with 9079 additions and 0 deletions

View File

@@ -0,0 +1,743 @@
// biome-ignore-all format: generated — do not edit by hand.
// Regenerate with: node scripts/generate-model-capabilities.mjs
// Source: scripts/model-capabilities.json
//
// Resolver: provider+rawModelId → exact lookup → family rules → provider fallback (plan v4).
// Not owned here: OpenAI/legacy Databricks/OpenRouter transport; final labels (resolveModelLabel() authoritative).
/** Valid thinking-effort values accepted by buzz-agent (mirrors parse_thinking_effort in config.rs). */
export const THINKING_EFFORT_VALUES = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
export type ThinkingEffortValue = (typeof THINKING_EFFORT_VALUES)[number];
/** Databricks v2 wire route. NotApplicable for non-DBv2 providers. */
export type DatabricksV2WireRoute =
| "openai-responses"
| "anthropic-messages"
| "mlflow-chat"
| "route-unknown"
| "not-applicable";
/** Anthropic thinking API shape for this model. */
export type ThinkingMode =
| "manual-budget"
| "adaptive"
| "omit-fields"
| "none"
| "not-applicable";
/** How to normalize effort values before sending to the provider. */
export type NormalizationPolicy = "none" | "openai-standard" | "openai-clamp-max-to-xhigh";
/** Complete resolved capability record for a (provider, rawModelId) pair. Every axis populated. */
export type CapabilityResult = {
/** Optional static display label. Feeds resolveModelLabel()'s registry tier only. */
readonly registryLabel: string | null;
readonly thinkingMode: ThinkingMode;
readonly supportedEfforts: ReadonlyArray<ThinkingEffortValue>;
readonly defaultEffort: ThinkingEffortValue | null;
readonly databricksV2WireRoute: DatabricksV2WireRoute;
readonly normalizationPolicy: NormalizationPolicy;
};
/** Valid thinking-effort values accepted by buzz-agent. */
export const BUZZ_AGENT_THINKING_EFFORT_VALUES = THINKING_EFFORT_VALUES;
/** Databricks v2 known model IDs. Mirrors goose DATABRICKS_V2_KNOWN_MODELS. */
export const DATABRICKS_V2_KNOWN_MODELS = [
"databricks-gpt-5-5",
"databricks-claude-opus-4-7",
] as const;
/** Databricks endpoint-ID to display-name registry. Generated from manifest registry_labels section.
* Feeds the static registry tier of resolveModelLabel(). */
export const DATABRICKS_MODEL_NAMES: Map<string, string> = new Map([
["databricks-claude-haiku-4-5", "Claude Haiku 4.5 (latest)"],
["databricks-claude-opus-4-1", "Claude Opus 4.1 (latest)"],
["databricks-claude-opus-4-5", "Claude Opus 4.5 (latest)"],
["databricks-claude-opus-4-6", "Claude Opus 4.6"],
["databricks-claude-opus-4-7", "Claude Opus 4.7"],
["databricks-claude-sonnet-4", "Claude Sonnet 4.5"],
["databricks-claude-sonnet-4-5", "Claude Sonnet 4.5 (latest)"],
["databricks-claude-sonnet-4-6", "Claude Sonnet 4.6"],
["databricks-gemini-2-5-flash", "Gemini 2.5 Flash"],
["databricks-gemini-2-5-pro", "Gemini 2.5 Pro"],
["databricks-gemini-3-1-flash-lite", "Gemini 3.1 Flash Lite Preview"],
["databricks-gemini-3-1-pro", "Gemini 3.1 Pro Preview Custom Tools"],
["databricks-gemini-3-flash", "Gemini 3 Flash Preview"],
["databricks-gemini-3-pro", "Gemini 3 Pro Preview"],
["databricks-glm-5-2", "GLM-5.2"],
["databricks-gpt-5", "GPT-5"],
["databricks-gpt-5-1", "GPT-5.1"],
["databricks-gpt-5-2", "GPT-5.2"],
["databricks-gpt-5-4", "GPT-5.4"],
["databricks-gpt-5-4-mini", "GPT-5.4 mini"],
["databricks-gpt-5-4-nano", "GPT-5.4 nano"],
["databricks-gpt-5-5", "GPT-5.5"],
["databricks-gpt-5-6-luna", "GPT-5.6 Luna"],
["databricks-gpt-5-6-sol", "GPT-5.6 Sol"],
["databricks-gpt-5-6-terra", "GPT-5.6 Terra"],
["databricks-gpt-5-mini", "GPT-5 Mini"],
["databricks-gpt-5-nano", "GPT-5 Nano"],
["databricks-gpt-oss-120b", "GPT OSS 120B"],
["databricks-gpt-oss-20b", "GPT OSS 20B"],
["databricks-kimi-k2-7-code", "Kimi K2.7 Code"],
]);
// ---------------------------------------------------------------------------
// gpt5 boundary-aware token helpers
// ---------------------------------------------------------------------------
function gpt5TokenMatchesGenerated(m: string, token: string): boolean {
let start = 0;
while (true) {
const idx = m.indexOf(token, start);
if (idx === -1) return false;
const afterIdx = idx + token.length;
const afterChar = afterIdx < m.length ? m[afterIdx] : "";
if (afterChar === "" || afterChar === "-") return true;
start = afterIdx;
}
}
function gpt5BaseMatchesGenerated(m: string, token: string): boolean {
let start = 0;
while (true) {
const idx = m.indexOf(token, start);
if (idx === -1) return false;
const afterIdx = idx + token.length;
const suffix = m.slice(afterIdx);
if (suffix === "") return true;
if (!suffix.startsWith("-")) { start = afterIdx; continue; }
const dashRest = suffix.slice(1);
if (/^\d{1,3}(?:[^a-z\d]|$)/i.test(dashRest)) { start = afterIdx; continue; }
return true;
}
}
// ---------------------------------------------------------------------------
// Exact records — provider-qualified, pre-prefix-stripping
// ---------------------------------------------------------------------------
const EXACT_RECORDS = new Map<string, CapabilityResult>([
["databricks_v2::databricks-gpt-5-4-mini", {
registryLabel: "GPT-5.4 Mini",
thinkingMode: "none",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
}],
["databricks_v2::databricks-gpt-5-4-nano", {
registryLabel: "GPT-5.4 Nano",
thinkingMode: "none",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
}],
["databricks_v2::databricks-gpt-5-6-sol", {
registryLabel: "GPT-5.6 Sol",
thinkingMode: "none",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
}],
["databricks_v2::databricks-gpt-5-5", {
registryLabel: "GPT-5.5",
thinkingMode: "none",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
}],
["databricks_v2::databricks-claude-opus-4-7", {
registryLabel: "Claude Opus 4.7",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
}],
]);
// ---------------------------------------------------------------------------
// Provider fallbacks
// ---------------------------------------------------------------------------
const PROVIDER_FALLBACKS: Record<string, { blank: CapabilityResult; concreteUnknown: CapabilityResult }> = {
"anthropic": {
blank: {
registryLabel: null,
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
},
concreteUnknown: {
registryLabel: null,
thinkingMode: "omit-fields",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
},
},
"openai": {
blank: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-clamp-max-to-xhigh",
},
concreteUnknown: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-clamp-max-to-xhigh",
},
},
"databricks_v2": {
blank: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "route-unknown",
normalizationPolicy: "openai-clamp-max-to-xhigh",
},
concreteUnknown: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "mlflow-chat",
normalizationPolicy: "openai-clamp-max-to-xhigh",
},
},
"databricks": {
blank: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-clamp-max-to-xhigh",
},
concreteUnknown: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-clamp-max-to-xhigh",
},
},
"openrouter": {
blank: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
},
concreteUnknown: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
},
},
};
const DEFAULT_FALLBACK = {
blank: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
},
concreteUnknown: {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
},
};
// ---------------------------------------------------------------------------
// Strip catalog prefix — finds first family token occurrence
// ---------------------------------------------------------------------------
export function stripCatalogPrefix(model: string): string {
const FAMILY_TOKENS = ["claude-", "gpt-"] as const;
let firstIdx = Infinity;
for (const tok of FAMILY_TOKENS) {
const idx = model.toLowerCase().indexOf(tok);
if (idx !== -1 && idx < firstIdx) firstIdx = idx;
}
return firstIdx === Infinity ? model : model.slice(firstIdx);
}
// ---------------------------------------------------------------------------
// Family rule resolver (generated ordered if-chain)
// ---------------------------------------------------------------------------
function lookupByFamilyRules(provider: string, normalized: string): CapabilityResult | null {
const lower = normalized.toLowerCase();
// rule: openai-gpt5-pro, provider: openai, priority: 20
if (provider === "openai" && (gpt5TokenMatchesGenerated(lower, "gpt-5-pro") || gpt5TokenMatchesGenerated(lower, "gpt5-pro"))) {
return {
registryLabel: "GPT-5 Pro",
thinkingMode: "none",
supportedEfforts: ["high"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-pro, provider: databricks_v2, priority: 20
if (provider === "databricks_v2" && (gpt5TokenMatchesGenerated(lower, "gpt-5-pro") || gpt5TokenMatchesGenerated(lower, "gpt5-pro"))) {
return {
registryLabel: "GPT-5 Pro",
thinkingMode: "none",
supportedEfforts: ["high"] as const,
defaultEffort: "high",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-6, provider: openai, priority: 15
if (provider === "openai" && (gpt5TokenMatchesGenerated(lower, "gpt-5.6") || gpt5TokenMatchesGenerated(lower, "gpt5.6") || gpt5TokenMatchesGenerated(lower, "gpt-5-6") || gpt5TokenMatchesGenerated(lower, "gpt5-6"))) {
return {
registryLabel: "GPT-5.6",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-6, provider: databricks_v2, priority: 15
if (provider === "databricks_v2" && (gpt5TokenMatchesGenerated(lower, "gpt-5.6") || gpt5TokenMatchesGenerated(lower, "gpt5.6") || gpt5TokenMatchesGenerated(lower, "gpt-5-6") || gpt5TokenMatchesGenerated(lower, "gpt5-6"))) {
return {
registryLabel: "GPT-5.6",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-5, provider: openai, priority: 15
if (provider === "openai" && (gpt5TokenMatchesGenerated(lower, "gpt-5.5") || gpt5TokenMatchesGenerated(lower, "gpt5.5") || gpt5TokenMatchesGenerated(lower, "gpt-5-5") || gpt5TokenMatchesGenerated(lower, "gpt5-5"))) {
return {
registryLabel: "GPT-5.5",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-5, provider: databricks_v2, priority: 15
if (provider === "databricks_v2" && (gpt5TokenMatchesGenerated(lower, "gpt-5.5") || gpt5TokenMatchesGenerated(lower, "gpt5.5") || gpt5TokenMatchesGenerated(lower, "gpt-5-5") || gpt5TokenMatchesGenerated(lower, "gpt5-5"))) {
return {
registryLabel: "GPT-5.5",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-4, provider: openai, priority: 15
if (provider === "openai" && (gpt5TokenMatchesGenerated(lower, "gpt-5.4") || gpt5TokenMatchesGenerated(lower, "gpt5.4") || gpt5TokenMatchesGenerated(lower, "gpt-5-4") || gpt5TokenMatchesGenerated(lower, "gpt5-4"))) {
return {
registryLabel: "GPT-5.4",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-4, provider: databricks_v2, priority: 15
if (provider === "databricks_v2" && (gpt5TokenMatchesGenerated(lower, "gpt-5.4") || gpt5TokenMatchesGenerated(lower, "gpt5.4") || gpt5TokenMatchesGenerated(lower, "gpt-5-4") || gpt5TokenMatchesGenerated(lower, "gpt5-4"))) {
return {
registryLabel: "GPT-5.4",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-1, provider: openai, priority: 15
if (provider === "openai" && (gpt5TokenMatchesGenerated(lower, "gpt-5.1") || gpt5TokenMatchesGenerated(lower, "gpt5.1") || gpt5TokenMatchesGenerated(lower, "gpt-5-1") || gpt5TokenMatchesGenerated(lower, "gpt5-1"))) {
return {
registryLabel: "GPT-5.1",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high"] as const,
defaultEffort: "none",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-1, provider: databricks_v2, priority: 15
if (provider === "databricks_v2" && (gpt5TokenMatchesGenerated(lower, "gpt-5.1") || gpt5TokenMatchesGenerated(lower, "gpt5.1") || gpt5TokenMatchesGenerated(lower, "gpt-5-1") || gpt5TokenMatchesGenerated(lower, "gpt5-1"))) {
return {
registryLabel: "GPT-5.1",
thinkingMode: "none",
supportedEfforts: ["none", "low", "medium", "high"] as const,
defaultEffort: "none",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
};
}
// rule: anthropic-manual-budget-claude3, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-3"))) {
return {
registryLabel: null,
thinkingMode: "manual-budget",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: null,
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-manual-budget-claude3, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-3"))) {
return {
registryLabel: null,
thinkingMode: "manual-budget",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: null,
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-manual-budget-opus-4-5, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower === "claude-opus-4-5")) {
return {
registryLabel: "Claude Opus 4.5",
thinkingMode: "manual-budget",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: null,
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-manual-budget-opus-4-5, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower === "claude-opus-4-5")) {
return {
registryLabel: "Claude Opus 4.5",
thinkingMode: "manual-budget",
supportedEfforts: ["low", "medium", "high"] as const,
defaultEffort: null,
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-opus-4-7, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-opus-4-7"))) {
return {
registryLabel: "Claude Opus 4.7",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-opus-4-7, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-opus-4-7"))) {
return {
registryLabel: "Claude Opus 4.7",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-opus-4-8, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-opus-4-8"))) {
return {
registryLabel: "Claude Opus 4.8",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-opus-4-8, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-opus-4-8"))) {
return {
registryLabel: "Claude Opus 4.8",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-opus-5, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-opus-5"))) {
return {
registryLabel: "Claude Opus 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-opus-5, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-opus-5"))) {
return {
registryLabel: "Claude Opus 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-sonnet-5, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-sonnet-5"))) {
return {
registryLabel: "Claude Sonnet 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-sonnet-5, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-sonnet-5"))) {
return {
registryLabel: "Claude Sonnet 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-fable-5, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-fable-5"))) {
return {
registryLabel: "Claude Fable 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-fable-5, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-fable-5"))) {
return {
registryLabel: "Claude Fable 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-mythos-5, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-mythos-5"))) {
return {
registryLabel: "Claude Mythos 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-xhigh-mythos-5, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-mythos-5"))) {
return {
registryLabel: "Claude Mythos 5",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-no-xhigh-opus-4-6, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-opus-4-6"))) {
return {
registryLabel: "Claude Opus 4.6",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-no-xhigh-opus-4-6, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-opus-4-6"))) {
return {
registryLabel: "Claude Opus 4.6",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-no-xhigh-sonnet-4-6, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-sonnet-4-6"))) {
return {
registryLabel: "Claude Sonnet 4.6",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-no-xhigh-sonnet-4-6, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-sonnet-4-6"))) {
return {
registryLabel: "Claude Sonnet 4.6",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-no-xhigh-mythos-preview, provider: anthropic, priority: 10
if (provider === "anthropic" && (lower.startsWith("claude-mythos-preview"))) {
return {
registryLabel: "Claude Mythos Preview",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "none",
};
}
// rule: anthropic-adaptive-no-xhigh-mythos-preview, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (lower.startsWith("claude-mythos-preview"))) {
return {
registryLabel: "Claude Mythos Preview",
thinkingMode: "adaptive",
supportedEfforts: ["low", "medium", "high", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: openai-gpt5-base, provider: openai, priority: 10
if (provider === "openai" && (gpt5BaseMatchesGenerated(lower, "gpt-5") || gpt5BaseMatchesGenerated(lower, "gpt5"))) {
return {
registryLabel: "GPT-5",
thinkingMode: "none",
supportedEfforts: ["minimal", "low", "medium", "high"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "not-applicable",
normalizationPolicy: "openai-standard",
};
}
// rule: openai-gpt5-base, provider: databricks_v2, priority: 10
if (provider === "databricks_v2" && (gpt5BaseMatchesGenerated(lower, "gpt-5") || gpt5BaseMatchesGenerated(lower, "gpt5"))) {
return {
registryLabel: "GPT-5",
thinkingMode: "none",
supportedEfforts: ["minimal", "low", "medium", "high"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-standard",
};
}
// rule: dbv2-claude-code-names-segment, provider: databricks_v2, priority: 5
if (provider === "databricks_v2" && (lower.split(/[^a-z0-9]+/).includes("claude") || lower.split(/[^a-z0-9]+/).includes("opus") || lower.split(/[^a-z0-9]+/).includes("sonnet") || lower.split(/[^a-z0-9]+/).includes("haiku") || lower.split(/[^a-z0-9]+/).includes("mythos") || lower.split(/[^a-z0-9]+/).includes("fable"))) {
return {
registryLabel: null,
thinkingMode: "omit-fields",
supportedEfforts: ["low", "medium", "high", "xhigh", "max"] as const,
defaultEffort: "high",
databricksV2WireRoute: "anthropic-messages",
normalizationPolicy: "none",
};
}
// rule: dbv2-gpt-code-names-segment, provider: databricks_v2, priority: 5
if (provider === "databricks_v2" && (lower.split(/[^a-z0-9]+/).some(s => s.startsWith("gpt")))) {
return {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-clamp-max-to-xhigh",
};
}
// rule: dbv2-sol-luna-terra-segment, provider: databricks_v2, priority: 5
if (provider === "databricks_v2" && (lower.split(/[^a-z0-9]+/).includes("sol") || lower.split(/[^a-z0-9]+/).includes("luna") || lower.split(/[^a-z0-9]+/).includes("terra"))) {
return {
registryLabel: null,
thinkingMode: "none",
supportedEfforts: ["none", "minimal", "low", "medium", "high", "xhigh"] as const,
defaultEffort: "medium",
databricksV2WireRoute: "openai-responses",
normalizationPolicy: "openai-clamp-max-to-xhigh",
};
}
return null;
}
// ---------------------------------------------------------------------------
// Top-level resolve — total function, always returns a complete result
// ---------------------------------------------------------------------------
/**
* Resolve (provider, rawModelId) → CapabilityResult.
*
* Total function — always returns a complete result. Consumers never compose
* fields from multiple tiers at runtime.
*
* Resolution order (plan v4):
* 1. Provider-qualified raw exact lookup (before prefix stripping)
* 2. Provider-scoped family rules on normalized alias
* 3. Per-axis provider fallback (blank vs concrete-unknown)
*/
export function resolveModelCapabilities(
provider: string,
rawModelId: string,
): CapabilityResult {
// Step 1: raw exact lookup
const exactKey = `${provider}::${rawModelId}`;
const exact = EXACT_RECORDS.get(exactKey);
if (exact) return exact;
// Step 2: family rules on normalized alias
const normalized = stripCatalogPrefix(rawModelId);
const family = lookupByFamilyRules(provider, normalized);
if (family) return family;
// Step 3: provider fallback
const isBlank = rawModelId.trim() === "";
const fb = PROVIDER_FALLBACKS[provider] ?? DEFAULT_FALLBACK;
return isBlank ? { ...fb.blank, registryLabel: null } : { ...fb.concreteUnknown, registryLabel: null };
}