Files
buzz/scripts/generate-model-capabilities.mjs
npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhwandWill Pfleger 4855d95252 feat(models): Phase 3 — pricing axis to D2' (authority, model) exact-lookup contract
Replaces lookup_pricing(raw_model_id) + normalize_for_pricing + 17 prefix rules
with an exact (billing-authority domain, billable-model-string) lookup. Billing
authority is a distinct namespace from the runtime transport Provider enum.
Match is exact: no case normalization, no prefix matching, no inference.

Manifest (scripts/model-capabilities.json):
- Removed pricing_rules (17 prefix-match rules), pricing_exact_records (3 records)
- Added flat pricing_records list of 20 exact records, each with authority + model +
  usd_per_mtok (input/output/cache_read/cache_write) + _source provenance
- Authorities: api.anthropic.com, api.openai.com — registered bare-host tokens per
  D2'/NIP-AM; not transport Provider enum values
- Semantics: current list prices (ccusage-aligned); cache_write scoped to ephemeral;
  other classes unknown

Generator (scripts/generate-model-capabilities.mjs):
- Removed all old pricing machinery (pricingNormalize, normalizeVersionSeparators,
  pricingRules, pricingExactRecords, resolvePricing, all TS rule-resolver emission)
- Strict pricing-record validation: registered authority (closed set: api.anthropic.com,
  api.openai.com, openrouter.ai); nonempty verbatim model; no control/NUL/quote/backslash
  in key fields; finite nonnegative input/output; both cache_read and cache_write required
  (absent member is hard error; use null for unpublished); _source provenance required;
  duplicate detection on exact emitted identities
- byAuthority grouping uses exact record values — no toLowerCase() anywhere in
  pricing generation path
- REGISTERED_AUTHORITIES set; hasUnsafeKeyChars() guard
- TS PRICING_TABLE: key is ${authority}\0${model} (exact strings, no transforms)
- lookupModelPricing(authority, model) performs exact Map lookup (no normalization)
- modelPricing.ts is standalone (no import from modelCapabilities)

Generated artifacts:
- generated_model_capabilities.rs: lookup_pricing(authority: &str, model: &str)
  performs exact match — no to_ascii_lowercase() calls, no fallback
- modelPricing.ts: exact PRICING_TABLE + lookupModelPricing(authority, model)

CI (.github/workflows/ci.yml):
- Replaced two-step "regenerate + git diff" check with a single
  node scripts/generate-model-capabilities.mjs --check step, which validates
  all three generated artifacts (Rust capabilities, TS capabilities, TS pricing)
  and exits 1 if any are stale. Self-maintains if a fourth artifact is added.

Tests (generated_model_capabilities_tests.rs, 23 pricing tests):
- Anthropic models with correct rates (claude-fable-5, claude-opus-5, claude-sonnet-5,
  claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5)
- OpenAI models incl. gpt-5.6-luna different tier, gpt-5.5 null cache_write, gpt-5-pro
  null cache fields
- Null guards: unknown authority, custom base URL, unknown model, empty authority,
  empty model, both empty, wrong authority for known model
- Exact-match guards: uppercase authority -> None, mixed-case authority -> None,
  mixed-case model -> None (no case folding)
- Databricks guards: workspace URL authority -> None, 'databricks' authority -> None

Manifest validator tests (scripts/test-manifest-validator.mjs, 45 tests total):
- 21 new pricing-record mutation tests covering: missing authority, unregistered/path-
  bearing/scheme authority, empty model, unsafe chars (double-quote, backslash, NUL,
  control), missing output, negative rates, invalid cache rate, deleted cache_read,
  deleted cache_write, missing/empty provenance, duplicate records, uppercase authority

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-04 10:58:29 -04:00

1746 lines
68 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Model-capability manifest generator.
*
* Reads `scripts/model-capabilities.json` and emits:
* - `crates/buzz-agent/src/generated_model_capabilities.rs`
* - `desktop/src/features/agents/ui/modelCapabilities.ts`
*
* The generator performs three ordered resolution steps (resolver contract, plan v4):
* 1. Provider-qualified raw exact lookup — key is (provider, raw_model_id), matched
* BEFORE any prefix stripping or normalization.
* 2. Provider-scoped ordered family rules — match kinds: exact | prefix | gpt5-token |
* gpt5-base | segment | segment-prefix, ordered by match_priority (desc).
* 3. Per-axis provider fallback — blank vs concrete-unknown states.
*
* Completeness validator: every emitted CapabilityResult must have every axis populated.
* A generation-time failure on any unresolvable axis is a hard error.
*
* Usage:
* node scripts/generate-model-capabilities.mjs [--check]
*
* --check: verify that already-generated files match what the generator would produce
* (used by CI regenerate-then-diff job). Exits 1 if any file differs.
*/
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..");
const CHECK_MODE = process.argv.includes("--check");
const CHECK_GOOSE_MODE = process.argv.includes("--check-goose");
// Run content through rustfmt (stdin → stdout) if available; fall back to raw.
// Uses hermit-pinned rustfmt from bin/ when present (same binary as pre-commit hook).
function rustfmt(content) {
const hermitBin = join(repoRoot, "bin", "rustfmt");
const hermitExists = existsSync(hermitBin);
const rustfmtBin = hermitExists ? hermitBin : "rustfmt";
const result = spawnSync(rustfmtBin, ["--edition", "2021", "--emit", "stdout"], {
input: content,
encoding: "utf8",
maxBuffer: 10 * 1024 * 1024,
});
if (result.status === 0 && result.stdout) return result.stdout;
if (hermitExists && result.status !== 0) {
// Hermit-pinned binary is present but failed — fail closed so CI catches drift.
const stderr = result.stderr ?? "";
throw new Error(`rustfmt (hermit-pinned) exited ${result.status}: ${stderr.trim()}`);
}
// rustfmt not available (PATH fallback also absent) — warn and return raw.
// CI's regen-diff gate will catch any drift.
process.stderr.write("warning: rustfmt not available; raw Rust output may not be formatter-stable\n");
return content;
}
// Support --manifest-path and --output-dir flags for testing
const manifestPathOverride = (() => {
const idx = process.argv.indexOf("--manifest-path");
return idx !== -1 ? process.argv[idx + 1] : null;
})();
const outputDirOverride = (() => {
const idx = process.argv.indexOf("--output-dir");
return idx !== -1 ? process.argv[idx + 1] : null;
})();
// ---------------------------------------------------------------------------
// Load manifest
// ---------------------------------------------------------------------------
const manifestPath = manifestPathOverride ?? join(repoRoot, "scripts", "model-capabilities.json");
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
// Validate registry_labels — must be an array of {id, label} objects with unique IDs.
// JSON.parse() silently overwrites duplicate object keys, so an array is required for
// structural duplicate detection.
const registryLabelsArr = manifest.registry_labels ?? [];
if (!Array.isArray(registryLabelsArr)) {
throw new Error("registry_labels: must be an array of {id, label} objects");
}
for (const entry of registryLabelsArr) {
if (!entry.id || typeof entry.id !== "string" || entry.id.trim() === "") {
throw new Error(`registry_labels: entry missing nonempty string "id": ${JSON.stringify(entry)}`);
}
if (!entry.label || typeof entry.label !== "string" || entry.label.trim() === "") {
throw new Error(`registry_labels: entry id="${entry.id}" missing nonempty string "label"`);
}
// Safe for code interpolation: reject double-quote, backslash, and control chars.
// (These characters would break emitted Rust/TS string literals.)
// Checked via requireSafeString() below (shared validator defined after knownModels block).
const unsafeId = entry.id.includes('"') || entry.id.includes("\\") ||
Array.from(entry.id).some((c) => c.charCodeAt(0) < 32);
if (unsafeId) {
throw new Error(`registry_labels: entry id="${entry.id}" contains unsafe characters`);
}
const unsafeLabel = entry.label.includes('"') || entry.label.includes("\\") ||
Array.from(entry.label).some((c) => c.charCodeAt(0) < 32);
if (unsafeLabel) {
throw new Error(`registry_labels: entry id="${entry.id}" label contains unsafe characters`);
}
}
const registryLabelIds = registryLabelsArr.map((e) => e.id);
const registryLabelSet = new Set(registryLabelIds);
if (registryLabelSet.size !== registryLabelIds.length) {
const dups = registryLabelIds.filter((id, i) => registryLabelIds.indexOf(id) !== i);
throw new Error(`registry_labels: duplicate endpoint IDs detected: ${dups.join(", ")}`);
}
// Validate databricks_v2_known_models uniqueness
const knownModels = manifest.databricks_v2_known_models ?? [];
const knownModelsSet = new Set(knownModels);
if (knownModelsSet.size !== knownModels.length) {
throw new Error(`databricks_v2_known_models: duplicate IDs detected: ${
knownModels.filter((id, i) => knownModels.indexOf(id) !== i).join(", ")
}`);
}
// ---------------------------------------------------------------------------
// Shared string-safety validator
// ---------------------------------------------------------------------------
//
// All manifest strings that end up inside generated Rust or TypeScript string literals
// must not contain double-quote, backslash, or control characters — any of these would
// break the emitted source or allow injection. One shared check prevents the same class
// of defect from appearing at scattered emission sites.
//
// Usage: requireSafeString(value, "context.path") — throws on violation.
// ---------------------------------------------------------------------------
function requireSafeString(value, context) {
if (typeof value !== "string" || value.length === 0) {
throw new Error(`${context}: must be a nonempty string, got ${JSON.stringify(value)}`);
}
const hasUnsafe = value.includes('"') || value.includes("\\") ||
Array.from(value).some((c) => c.charCodeAt(0) < 32);
if (hasUnsafe) {
throw new Error(`${context}: contains unsafe characters (double-quote, backslash, or control chars): ${JSON.stringify(value)}`);
}
}
// Validate all manifest strings that end up in generated Rust/TS string literals.
// family_tokens → Rust FAMILY_TOKENS array and TS FAMILY_TOKENS constant
for (const tok of manifest.family_tokens ?? []) {
requireSafeString(tok, "family_tokens[]");
}
// databricks_v2_known_models IDs → Rust/TS DATABRICKS_V2_KNOWN_MODELS array
for (const id of knownModels) {
requireSafeString(id, "databricks_v2_known_models[]");
}
// exact_records → Rust lookup_exact() match arms and TS EXACT_RECORDS map keys
for (const rec of manifest.exact_records ?? []) {
requireSafeString(rec.provider ?? "", `exact_records[${rec.raw_model_id}].provider`);
requireSafeString(rec.raw_model_id ?? "", `exact_records[${rec.raw_model_id}].raw_model_id`);
}
// family_rules → Rust/TS lookup_by_family_rules() match-expression strings
for (const rule of manifest.family_rules ?? []) {
requireSafeString(rule.id ?? "", `family_rules[${rule.id}].id`);
requireSafeString(rule.match_value ?? "", `family_rules[${rule.id}].match_value`);
for (const alias of rule.match_aliases ?? []) {
requireSafeString(alias, `family_rules[${rule.id}].match_aliases[]`);
}
for (const provider of rule.providers ?? []) {
requireSafeString(provider, `family_rules[${rule.id}].providers[]`);
}
// registry_label flows into Rust Some("...") and TS "..." string literals
if (rule.registry_label != null) {
requireSafeString(rule.registry_label, `family_rules[${rule.id}].registry_label`);
}
}
// exact_records: registry_label values flow into Rust rustString() and TS emitter
for (const rec of manifest.exact_records ?? []) {
if (rec.registry_label != null) {
requireSafeString(rec.registry_label, `exact_records[${rec.raw_model_id}].registry_label`);
}
}
// provider_fallbacks: object keys are interpolated as Rust match arms and TS object keys
for (const providerKey of Object.keys(manifest.provider_fallbacks ?? {})) {
if (providerKey !== "_default") {
requireSafeString(providerKey, `provider_fallbacks key`);
}
}
// ---------------------------------------------------------------------------
// --check-goose: optional drift check against pinned goose upstream
// ---------------------------------------------------------------------------
//
// Reads the goose source file at the pinned revision and verifies that
// manifest.databricks_v2_known_models exactly matches the IDs declared there.
//
// Usage (non-CI, opt-in):
// node scripts/generate-model-capabilities.mjs --check-goose
//
// Pin metadata lives in manifest._sources.goose_known_models:
// "goose revision <SHA> (<path>:<lines>) — <ids>"
//
// The check fetches the raw file from github.com/block/goose via the GitHub
// contents API and parses DATABRICKS_V2_KNOWN_MODELS from it.
// ---------------------------------------------------------------------------
if (CHECK_GOOSE_MODE) {
// Parse pin metadata from _sources
const pin = manifest._sources?.goose_known_models;
if (!pin) {
console.error("--check-goose: manifest._sources.goose_known_models is missing.");
process.exit(1);
}
const revMatch = pin.match(/revision\s+([0-9a-f]{7,40})/i);
const pathMatch = pin.match(/\(([^)]+\.rs):/);
if (!revMatch || !pathMatch) {
console.error(`--check-goose: could not parse revision/path from pin metadata: ${pin}`);
process.exit(1);
}
const pinnedRev = revMatch[1];
const goosePath = pathMatch[1]; // e.g. "crates/goose-providers/src/databricks_v2.rs"
// Fetch file content from GitHub API (no token required for public repos)
const url = `https://raw.githubusercontent.com/block/goose/${pinnedRev}/${goosePath}`;
let fileContent;
try {
const result = spawnSync("curl", ["-fsS", "--max-time", "15", url], {
encoding: "utf8",
maxBuffer: 2 * 1024 * 1024,
});
if (result.status !== 0) {
console.error(`--check-goose: curl failed (exit ${result.status}): ${result.stderr?.trim()}`);
console.error(` URL: ${url}`);
process.exit(1);
}
fileContent = result.stdout;
} catch (e) {
console.error(`--check-goose: fetch failed: ${e.message}`);
process.exit(1);
}
// Extract DATABRICKS_V2_KNOWN_MODELS from Rust source: parse &[&str] literal
// Pattern: pub const DATABRICKS_V2_KNOWN_MODELS: &[&str] = &[ "id1", "id2", ... ];
const constMatch = fileContent.match(
/DATABRICKS_V2_KNOWN_MODELS\s*:\s*&\[&str\]\s*=\s*&\[([\s\S]*?)\];/,
);
if (!constMatch) {
console.error(`--check-goose: could not find DATABRICKS_V2_KNOWN_MODELS in goose source at ${pinnedRev}:${goosePath}`);
process.exit(1);
}
const gooseIds = [...constMatch[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]).sort();
const manifestIds = [...(manifest.databricks_v2_known_models ?? [])].sort();
const onlyInGoose = gooseIds.filter((id) => !manifestIds.includes(id));
const onlyInManifest = manifestIds.filter((id) => !gooseIds.includes(id));
if (onlyInGoose.length === 0 && onlyInManifest.length === 0) {
console.log(`--check-goose: OK — manifest matches goose@${pinnedRev} (${gooseIds.length} IDs)`);
} else {
if (onlyInGoose.length > 0) {
console.error(`--check-goose: DRIFT — IDs in goose@${pinnedRev} not in manifest: ${onlyInGoose.join(", ")}`);
}
if (onlyInManifest.length > 0) {
console.error(`--check-goose: DRIFT — IDs in manifest not in goose@${pinnedRev}: ${onlyInManifest.join(", ")}`);
}
console.error(`Pin: ${pin}`);
process.exit(1);
}
process.exit(0);
}
// ---------------------------------------------------------------------------
// Validation helpers
// ---------------------------------------------------------------------------
const VALID_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
const VALID_THINKING_MODES = ["manual-budget", "adaptive", "omit-fields", "none", "not-applicable"];
const VALID_DBV2_ROUTES = [
"openai-responses",
"anthropic-messages",
"mlflow-chat",
"route-unknown",
"not-applicable",
];
const VALID_NORM_POLICIES = ["none", "openai-standard", "openai-clamp-max-to-xhigh"];
const VALID_MATCH_KINDS = [
"exact",
"prefix",
"gpt5-token",
"gpt5-base",
"segment",
"segment-prefix",
];
function assertEnum(value, valid, label) {
if (!valid.includes(value)) {
throw new Error(`${label}: invalid value "${value}" (must be one of: ${valid.join(", ")})`);
}
}
function assertNonEmpty(arr, label) {
if (!Array.isArray(arr) || arr.length === 0) {
throw new Error(`${label}: must be a non-empty array`);
}
}
function validateFallbackRecord(rec, label) {
assertEnum(rec.databricks_v2_wire_route, VALID_DBV2_ROUTES, `${label}.databricks_v2_wire_route`);
assertEnum(rec.thinking_mode, VALID_THINKING_MODES, `${label}.thinking_mode`);
assertNonEmpty(rec.supported_efforts, `${label}.supported_efforts`);
for (const e of rec.supported_efforts) {
assertEnum(e, VALID_EFFORTS, `${label}.supported_efforts[]`);
}
if (rec.default_effort !== null) {
assertEnum(rec.default_effort, VALID_EFFORTS, `${label}.default_effort`);
if (!rec.supported_efforts.includes(rec.default_effort)) {
throw new Error(
`${label}: default_effort "${rec.default_effort}" not in supported_efforts [${rec.supported_efforts.join(", ")}]`,
);
}
}
assertEnum(rec.normalization_policy, VALID_NORM_POLICIES, `${label}.normalization_policy`);
}
// ---------------------------------------------------------------------------
// Validate manifest structure
// ---------------------------------------------------------------------------
// Validate family_rules
const seenRuleIds = new Set();
for (const rule of manifest.family_rules) {
if (!rule.id) throw new Error("family_rule missing id");
if (seenRuleIds.has(rule.id)) throw new Error(`duplicate family_rule id: ${rule.id}`);
seenRuleIds.add(rule.id);
assertEnum(rule.match_kind, VALID_MATCH_KINDS, `rule ${rule.id} match_kind`);
assertEnum(rule.thinking_mode, VALID_THINKING_MODES, `rule ${rule.id} thinking_mode`);
assertNonEmpty(rule.supported_efforts, `rule ${rule.id} supported_efforts`);
for (const e of rule.supported_efforts) {
assertEnum(e, VALID_EFFORTS, `rule ${rule.id} supported_efforts[]`);
}
if (rule.default_effort !== null) {
assertEnum(rule.default_effort, VALID_EFFORTS, `rule ${rule.id} default_effort`);
if (!rule.supported_efforts.includes(rule.default_effort)) {
throw new Error(
`rule ${rule.id}: default_effort "${rule.default_effort}" not in supported_efforts`,
);
}
}
assertEnum(
rule.databricks_v2_wire_route,
VALID_DBV2_ROUTES,
`rule ${rule.id} databricks_v2_wire_route`,
);
assertEnum(
rule.normalization_policy,
VALID_NORM_POLICIES,
`rule ${rule.id} normalization_policy`,
);
}
// Validate provider_fallbacks
for (const [provider, fb] of Object.entries(manifest.provider_fallbacks)) {
for (const state of ["blank", "concrete_unknown"]) {
if (!fb[state])
throw new Error(`provider_fallbacks.${provider} missing "${state}" record`);
validateFallbackRecord(fb[state], `provider_fallbacks.${provider}.${state}`);
}
}
// Validate exact_records — check for duplicate (provider, raw_model_id) keys
const seenExactKeys = new Set();
for (const rec of manifest.exact_records ?? []) {
if (!rec.provider || !rec.raw_model_id)
throw new Error("exact_record missing provider or raw_model_id");
const key = `${rec.provider}::${rec.raw_model_id}`;
if (seenExactKeys.has(key)) throw new Error(`duplicate exact_record key: ${key}`);
seenExactKeys.add(key);
}
// ---------------------------------------------------------------------------
// Resolution engine (mirrors plan resolver contract)
// ---------------------------------------------------------------------------
/**
* Strip catalog prefix to get the normalized alias for family-rule matching.
* Finds the first occurrence of a known family token and returns from there.
* e.g. "goose-claude-fable-5" → "claude-fable-5"
* "databricks-gpt-5.5" → "gpt-5.5"
* "claude-opus-4-7" → "claude-opus-4-7" (no prefix)
*/
function stripCatalogPrefix(model) {
const lower = model.toLowerCase();
let firstIdx = Infinity;
for (const tok of manifest.family_tokens) {
const idx = lower.indexOf(tok);
if (idx !== -1 && idx < firstIdx) firstIdx = idx;
}
return firstIdx === Infinity ? model : model.slice(firstIdx);
}
/**
* gpt5-token match: model contains token at a word boundary (end-of-string or "-").
* Does NOT match if followed by a digit or letter.
*/
function gpt5TokenMatches(model, token) {
const lower = model.toLowerCase();
let start = 0;
while (true) {
const idx = lower.indexOf(token.toLowerCase(), start);
if (idx === -1) return false;
const afterIdx = idx + token.length;
const afterChar = afterIdx < lower.length ? lower[afterIdx] : "";
if (afterChar === "" || afterChar === "-") return true;
start = afterIdx;
}
}
/**
* gpt5-base match: like gpt5-token but also rejects short -<1-3 digit> suffixes.
*/
function gpt5BaseMatches(model, token) {
const lower = model.toLowerCase();
let start = 0;
while (true) {
const idx = lower.indexOf(token.toLowerCase(), start);
if (idx === -1) return false;
const afterIdx = idx + token.length;
const suffix = lower.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;
}
}
/**
* Test if a family rule matches the given (normalized) model string for a provider.
*/
function ruleMatchesModel(rule, normalizedModel, provider) {
if (!rule.providers.includes(provider)) return false;
const lower = normalizedModel.toLowerCase();
const allTokens = [rule.match_value, ...(rule.match_aliases ?? [])];
switch (rule.match_kind) {
case "exact":
return allTokens.some((t) => lower === t.toLowerCase());
case "prefix":
return allTokens.some((t) => lower.startsWith(t.toLowerCase()));
case "gpt5-token":
return allTokens.some((t) => gpt5TokenMatches(lower, t));
case "gpt5-base":
return allTokens.some((t) => gpt5BaseMatches(lower, t));
case "segment": {
const segs = lower.split(/[^a-z0-9]+/);
return allTokens.some((t) => segs.includes(t.toLowerCase()));
}
case "segment-prefix": {
const segs = lower.split(/[^a-z0-9]+/);
return allTokens.some((t) => segs.some((s) => s.startsWith(t.toLowerCase())));
}
default:
throw new Error(`unknown match_kind: ${rule.match_kind}`);
}
}
/**
* Resolve (provider, rawModelId) → CapabilityResult.
* Three steps: raw exact lookup → family rules → provider fallback.
*/
function resolve(provider, rawModelId) {
const isBlank = !rawModelId || rawModelId.trim() === "";
// Step 1: provider-qualified raw exact lookup (BEFORE any prefix stripping)
const exactRecord = (manifest.exact_records ?? []).find(
(r) => r.provider === provider && r.raw_model_id === rawModelId,
);
if (exactRecord) {
// Materialize the full record by evaluating family rules for any axis not overridden.
const normalizedAlias = stripCatalogPrefix(rawModelId);
const familyResult = resolveFamilyRules(provider, normalizedAlias, rawModelId);
const base = familyResult ?? getProviderFallback(provider, isBlank);
// Determine which axes came from family rule vs exact record for per-axis provenance.
const effortsFromExact = exactRecord.supported_efforts_override !== undefined;
const routeFromExact = exactRecord.databricks_v2_wire_route !== undefined;
const modeFromExact = exactRecord.thinking_mode !== undefined;
const normFromExact = exactRecord.normalization_policy !== undefined;
const effortDefaultFromExact = exactRecord.default_effort !== undefined;
const labelFromExact = exactRecord.registry_label !== undefined;
const familyProv = familyResult
? { rule_id: familyResult._provenance.rule_id, rule_priority: familyResult._provenance.rule_priority }
: null;
return {
registry_label: exactRecord.registry_label ?? null,
thinking_mode: exactRecord.thinking_mode ?? base.thinking_mode,
supported_efforts: exactRecord.supported_efforts_override ?? base.supported_efforts,
default_effort: exactRecord.default_effort !== undefined ? exactRecord.default_effort : base.default_effort,
databricks_v2_wire_route: exactRecord.databricks_v2_wire_route ?? base.databricks_v2_wire_route,
normalization_policy: exactRecord.normalization_policy ?? base.normalization_policy,
_provenance: {
source: "exact",
exact_key: `${provider}::${rawModelId}`,
registry_label: labelFromExact ? "exact_record" : (familyProv ? `family:${familyProv.rule_id}@${familyProv.rule_priority}` : "absent"),
supported_efforts: effortsFromExact ? "exact_record" : (familyProv ? `family:${familyProv.rule_id}@${familyProv.rule_priority}` : "fallback"),
databricks_v2_wire_route: routeFromExact ? "exact_record" : (familyProv ? `family:${familyProv.rule_id}@${familyProv.rule_priority}` : "fallback"),
thinking_mode: modeFromExact ? "exact_record" : (familyProv ? `family:${familyProv.rule_id}@${familyProv.rule_priority}` : "fallback"),
normalization_policy: normFromExact ? "exact_record" : (familyProv ? `family:${familyProv.rule_id}@${familyProv.rule_priority}` : "fallback"),
default_effort: effortDefaultFromExact ? "exact_record" : (familyProv ? `family:${familyProv.rule_id}@${familyProv.rule_priority}` : "fallback"),
},
};
}
// Step 2: provider-scoped family rules on normalized alias
const normalizedAlias = stripCatalogPrefix(rawModelId ?? "");
const familyResult = resolveFamilyRules(provider, normalizedAlias, rawModelId);
if (familyResult) return familyResult;
// Step 3: provider fallback
const fallback = getProviderFallback(provider, isBlank);
return { ...fallback, registry_label: null };
}
function resolveFamilyRules(provider, normalizedAlias, rawModelId) {
if (!normalizedAlias) return null;
// Sort rules by match_priority descending (higher priority wins)
const sorted = [...manifest.family_rules].sort((a, b) => b.match_priority - a.match_priority);
for (const rule of sorted) {
if (ruleMatchesModel(rule, normalizedAlias, provider)) {
// databricks_v2_wire_route is only meaningful for databricks_v2; all other providers get not-applicable
const wireRoute = provider === "databricks_v2"
? rule.databricks_v2_wire_route
: "not-applicable";
return {
registry_label: rule.registry_label ?? null,
thinking_mode: rule.thinking_mode,
supported_efforts: rule.supported_efforts,
default_effort: rule.default_effort,
databricks_v2_wire_route: wireRoute,
normalization_policy: rule.normalization_policy,
_provenance: {
source: "family",
rule_id: rule.id,
rule_priority: rule.match_priority,
normalized_alias: normalizedAlias,
raw_model_id: rawModelId,
},
};
}
}
return null;
}
function getProviderFallback(provider, isBlank) {
const fb =
manifest.provider_fallbacks[provider] ?? manifest.provider_fallbacks["_default"];
const state = isBlank ? "blank" : "concrete_unknown";
const rec = fb[state];
return {
...rec,
_provenance: { source: "fallback", provider, state },
};
}
// ---------------------------------------------------------------------------
// Rust code generation
// ---------------------------------------------------------------------------
function rustString(s) {
if (s === null || s === undefined) return "None";
return `Some("${s}")`;
}
function rustEffortList(efforts) {
const mapped = efforts.map((e) => `ThinkingEffort::${capitalize(e)}`);
return `&[${mapped.join(", ")}]`;
}
function rustEffortOption(e) {
if (e === null || e === undefined) return "None";
return `Some(ThinkingEffort::${capitalize(e)})`;
}
function capitalize(s) {
if (s === "xhigh") return "XHigh";
return s.charAt(0).toUpperCase() + s.slice(1);
}
function rustDbv2Route(route) {
const map = {
"openai-responses": "DatabricksV2Route::OpenAiResponses",
"anthropic-messages": "DatabricksV2Route::AnthropicMessages",
"mlflow-chat": "DatabricksV2Route::MlflowChatCompletions",
"route-unknown": "DatabricksV2Route::RouteUnknown",
"not-applicable": "DatabricksV2Route::NotApplicable",
};
if (!map[route]) throw new Error(`unknown dbv2 route: ${route}`);
return map[route];
}
function rustNormPolicy(policy) {
const map = {
none: "NormalizationPolicy::None",
"openai-standard": "NormalizationPolicy::OpenAiStandard",
"openai-clamp-max-to-xhigh": "NormalizationPolicy::OpenAiClampMaxToXHigh",
};
if (!map[policy]) throw new Error(`unknown norm policy: ${policy}`);
return map[policy];
}
function rustThinkingMode(mode) {
const map = {
"manual-budget": "ThinkingMode::ManualBudget",
adaptive: "ThinkingMode::Adaptive",
"omit-fields": "ThinkingMode::OmitFields",
none: "ThinkingMode::None",
"not-applicable": "ThinkingMode::NotApplicable",
};
if (!map[mode]) throw new Error(`unknown thinking mode: ${mode}`);
return map[mode];
}
function emitRustCapabilityResult(r, indent = " ") {
const i = indent;
const lines = [
`${i}CapabilityResult {`,
`${i} registry_label: ${rustString(r.registry_label)},`,
`${i} thinking_mode: ${rustThinkingMode(r.thinking_mode)},`,
`${i} supported_efforts: Cow::Borrowed(${rustEffortList(r.supported_efforts)}),`,
`${i} default_effort: ${rustEffortOption(r.default_effort)},`,
`${i} databricks_v2_wire_route: ${rustDbv2Route(r.databricks_v2_wire_route)},`,
`${i} normalization_policy: ${rustNormPolicy(r.normalization_policy)},`,
`${i}}`,
];
return lines.join("\n");
}
// Build the exact_records map entries for Rust
const exactMapEntries = [];
for (const rec of manifest.exact_records ?? []) {
const result = resolve(rec.provider, rec.raw_model_id);
// Strip provenance from emitted result
const clean = { ...result };
delete clean._provenance;
const provNote = result._provenance
? (() => {
const p = result._provenance;
if (p.source !== "exact") return `// source: ${p.source}`;
return [
`// provenance: exact(${p.exact_key})`,
`// registry_label: ${p.registry_label}`,
`// supported_efforts: ${p.supported_efforts}`,
`// databricks_v2_wire_route: ${p.databricks_v2_wire_route}`,
`// thinking_mode: ${p.thinking_mode}`,
`// normalization_policy: ${p.normalization_policy}`,
`// default_effort: ${p.default_effort}`,
].join("\n ");
})()
: "";
exactMapEntries.push({ rec, clean, provNote });
}
// Build provider fallback static arrays
function emitRustFallbackFn(provider, state, rec) {
const clean = { ...rec };
delete clean._provenance;
const stateId = state === "blank" ? "Blank" : "ConcreteUnknown";
return emitRustCapabilityResult(clean, " ");
}
// Collect all providers for fallback fns
const providerFallbackKeys = Object.keys(manifest.provider_fallbacks).filter(
(k) => k !== "_default",
);
const rustContent = `// @generated — do not edit by hand.
// Regenerate with: node scripts/generate-model-capabilities.mjs
// Source: scripts/model-capabilities.json
//! Generated model-capability lookup tables.
//!
//! Resolution is a total function \`resolve(provider, raw_model_id) → CapabilityResult\`.
//! Three ordered steps (plan v4 resolver contract):
//! 1. Provider-qualified raw exact lookup (before any prefix stripping).
//! 2. Provider-scoped ordered family rules on the normalized alias.
//! 3. Per-axis provider fallback (blank vs concrete-unknown).
//!
//! The manifest (scripts/model-capabilities.json) is the single source of truth.
//! This file is regenerated by scripts/generate-model-capabilities.mjs.
//! CI verifies that generated files match the manifest (regenerate-then-diff).
//!
//! Boundaries this file does NOT own (plan v4 §Boundaries):
//! - Transport/endpoint selection for pure OpenAI, legacy Databricks, OpenRouter:
//! OpenAiApi / openai_request() remain authoritative.
//! - Final display labels: resolveModelLabel() three-tier precedence is authoritative.
//! \`registry_label\` here feeds only the static registry tier.
//! - llm.rs replacement scope: ONLY \`databricks_v2_route_for_model\`.
use crate::config::ThinkingEffort;
use std::borrow::Cow;
/// Which Databricks v2 gateway wire path to use for a model.
/// Scoped to DBv2 only — other providers use \`NotApplicable\`.
/// Transport for pure OpenAI, legacy Databricks, and OpenRouter is selected
/// by OpenAiApi / openai_request() at runtime, not by this manifest.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DatabricksV2Route {
/// /ai-gateway/openai/v1/responses
OpenAiResponses,
/// /ai-gateway/anthropic/v1/messages
AnthropicMessages,
/// /ai-gateway/mlflow/v1/chat/completions
MlflowChatCompletions,
/// DBv2 blank model — route not yet determinable.
RouteUnknown,
/// Not a DBv2 provider — transport is selected by OpenAiApi/openai_request().
NotApplicable,
}
/// Anthropic thinking API shape for this model.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThinkingMode {
/// thinking:{type:"enabled", budget_tokens} — claude-3*, claude-opus-4-5
ManualBudget,
/// thinking:{type:"adaptive"} + output_config:{effort} — opus-4-6+, sonnet-4-6+, etc.
Adaptive,
/// Unknown Anthropic model — omit thinking fields rather than guess request shape.
OmitFields,
/// Non-Anthropic-routed model — thinking fields are not applicable.
None,
/// Provider does not use Anthropic thinking API at all.
NotApplicable,
}
/// How to normalize effort values before sending to the provider.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormalizationPolicy {
/// Pass effort through unchanged.
None,
/// Apply per-family effort table; none↔minimal peer fallback + upward tie preference.
OpenAiStandard,
/// Unknown OpenAI model: clamp max → xhigh, pass all others unchanged.
OpenAiClampMaxToXHigh,
}
/// Complete resolved capability record for a (provider, raw_model_id) pair.
/// Every axis is populated — runtime consumers do not compose fields.
#[derive(Debug, Clone, PartialEq)]
pub struct CapabilityResult {
/// Optional static display label. Feeds resolveModelLabel()'s registry tier only.
/// The dynamic three-tier precedence (discovered_name > registry_label > raw_id)
/// lives in formatAgentModelLabel / resolveModelLabel — NOT in this struct.
pub registry_label: Option<&'static str>,
/// Anthropic thinking API shape for this model.
pub thinking_mode: ThinkingMode,
/// Valid effort values for the model's effort dropdown (UI).
pub supported_efforts: Cow<'static, [ThinkingEffort]>,
/// Semantic default, or None when "Inherit" is the natural default (manual-budget Anthropic).
pub default_effort: Option<ThinkingEffort>,
/// DBv2 wire route; NotApplicable for non-DBv2 providers.
pub databricks_v2_wire_route: DatabricksV2Route,
/// Effort normalization policy before sending to provider.
pub normalization_policy: NormalizationPolicy,
}
// ---------------------------------------------------------------------------
// Exact records — provider-qualified (provider, raw_model_id), pre-prefix-stripping
// ---------------------------------------------------------------------------
/// Returns the exact capability record for a provider-qualified raw model ID,
/// if one exists in the manifest. This is checked BEFORE any prefix stripping.
pub fn lookup_exact(provider: &str, raw_model_id: &str) -> Option<CapabilityResult> {
match (provider, raw_model_id) {
${exactMapEntries
.map(({ rec, clean, provNote }) => {
return ` ("${rec.provider}", "${rec.raw_model_id}") => {
${provNote}
Some(
${emitRustCapabilityResult(clean, " ")}
)
}`;
})
.join("\n")}
_ => None,
}
}
// ---------------------------------------------------------------------------
// Family rule resolution — normalized alias after prefix stripping
// ---------------------------------------------------------------------------
/// Strip any catalog-naming prefix to get the normalized model alias for family matching.
/// Finds the first occurrence of a known family token (claude-, gpt-) and returns from there.
///
/// Examples:
/// "goose-claude-fable-5" → "claude-fable-5"
/// "databricks-gpt-5.5" → "gpt-5.5"
/// "team-x-claude-opus-4-7" → "claude-opus-4-7"
/// "claude-opus-4-7" → "claude-opus-4-7" (no prefix)
/// "llama-3" → "llama-3" (no family token)
pub fn strip_catalog_prefix(model: &str) -> &str {
const FAMILY_TOKENS: &[&str] = &[${manifest.family_tokens.map((t) => `"${t}"`).join(", ")}];
let lower = model.to_ascii_lowercase();
let first_idx = FAMILY_TOKENS
.iter()
.filter_map(|tok| lower.find(tok))
.min();
match first_idx {
Some(idx) => &model[idx..],
None => model,
}
}
${emitRustFamilyResolverFn()}
// ---------------------------------------------------------------------------
// Provider fallbacks — blank vs concrete-unknown, per provider
// ---------------------------------------------------------------------------
/// Returns the fallback capability record for the given provider and model state.
/// \`is_blank\` is true when the model string is empty/whitespace; false when it is
/// a nonblank but unmatched concrete ID.
pub fn provider_fallback(provider: &str, is_blank: bool) -> CapabilityResult {
match (provider, is_blank) {
${providerFallbackKeys
.map((provider) => {
const fb = manifest.provider_fallbacks[provider];
const blankClean = { ...fb.blank };
delete blankClean._provenance;
const concClean = { ...fb.concrete_unknown };
delete concClean._provenance;
return ` ("${provider}", true) => {
${emitRustCapabilityResult({ ...blankClean, registry_label: null }, " ")}
}
("${provider}", false) => {
${emitRustCapabilityResult({ ...concClean, registry_label: null }, " ")}
}`;
})
.join("\n")}
// Default fallback for unknown/empty providers
(_, true) => {
${emitRustCapabilityResult(
{
...manifest.provider_fallbacks["_default"].blank,
registry_label: null,
},
" ",
)}
}
(_, false) => {
${emitRustCapabilityResult(
{
...manifest.provider_fallbacks["_default"].concrete_unknown,
registry_label: null,
},
" ",
)}
}
}
}
// ---------------------------------------------------------------------------
// Top-level resolve — total function, always returns a complete result
// ---------------------------------------------------------------------------
/// Resolve (provider, raw_model_id) → CapabilityResult.
///
/// This is the single entry point. Result is complete — every axis is populated.
/// Consumers never compose fields from multiple tiers at runtime.
///
/// Resolution order (plan v4 resolver contract):
/// 1. provider-qualified raw exact lookup (before any prefix stripping)
/// 2. provider-scoped family rules on normalized alias
/// 3. per-axis provider fallback (blank vs concrete-unknown)
pub fn resolve_model_capabilities(provider: &str, raw_model_id: &str) -> CapabilityResult {
// Step 1: raw exact lookup
if let Some(exact) = lookup_exact(provider, raw_model_id) {
return exact;
}
// Step 2: family rules on normalized alias
let normalized = strip_catalog_prefix(raw_model_id);
if let Some(family) = lookup_by_family_rules(provider, normalized) {
return family;
}
// Step 3: provider fallback
let is_blank = raw_model_id.trim().is_empty();
let mut fallback = provider_fallback(provider, is_blank);
fallback.registry_label = None;
fallback
}
// ---------------------------------------------------------------------------
// Generated constants (fold-in from #3603 branch)
// ---------------------------------------------------------------------------
/// Valid thinking-effort values accepted by buzz-agent.
/// Mirrors parse_thinking_effort in config.rs.
pub const THINKING_EFFORT_VALUES: &[&str] = &[${VALID_EFFORTS.map((e) => `"${e}"`).join(", ")}];
/// Databricks v2 known model IDs. Mirrors goose DATABRICKS_V2_KNOWN_MODELS.
/// Single source of truth inside buzz; generated from manifest databricks_v2_known_models section.
pub const DATABRICKS_V2_KNOWN_MODELS: &[&str] = &[
${manifest.databricks_v2_known_models
.map((id) => ` "${id}",`)
.join("\n")}
];
/// Databricks endpoint-ID to display-name registry. Generated from manifest registry_labels section.
/// Feeds the static registry tier of resolveModelLabel(). Final display label is determined
/// by the three-tier precedence in resolveModelLabel() (discovered_name > registry_label > raw_id).
pub const DATABRICKS_MODEL_NAMES: &[(&str, &str)] = &[
${registryLabelsArr
.map(({ id, label }) => ` ("${id}", "${label}"),`)
.join("\n")}
];
`;
function emitRustFamilyResolverFn() {
// Generate match arms for each family rule, sorted by priority desc
const sorted = [...manifest.family_rules].sort((a, b) => b.match_priority - a.match_priority);
// Group by provider for the generated fn
const allProviders = [
...new Set(sorted.flatMap((r) => r.providers)),
];
// We emit a single fn that takes (provider: &str, normalized: &str)
// and returns Option<CapabilityResult>. We use if-else chains.
const arms = [];
for (const rule of sorted) {
for (const provider of rule.providers) {
const clean = { ...rule };
delete clean._provenance;
// databricks_v2_wire_route is only meaningful for databricks_v2; all other providers get not-applicable
const wireRoute = provider === "databricks_v2"
? rule.databricks_v2_wire_route
: "not-applicable";
const matchExpr = buildRustMatchExpr(rule, provider);
arms.push(
` // rule: ${rule.id}, provider: ${provider}, priority: ${rule.match_priority}\n if provider == "${provider}" && (${matchExpr}) {\n return Some(\n${emitRustCapabilityResult(
{
registry_label: rule.registry_label ?? null,
thinking_mode: rule.thinking_mode,
supported_efforts: rule.supported_efforts,
default_effort: rule.default_effort,
databricks_v2_wire_route: wireRoute,
normalization_policy: rule.normalization_policy,
},
" ",
)}\n );\n }`,
);
}
}
return `/// Resolve capability by family rules on the normalized (prefix-stripped) alias.
/// Returns None if no rule matches (caller falls through to provider_fallback).
///
/// Rules are ordered by match_priority descending.
/// Generated from manifest family_rules.
pub fn lookup_by_family_rules(provider: &str, normalized: &str) -> Option<CapabilityResult> {
let lower = normalized.to_ascii_lowercase();
let lower = lower.as_str();
#[allow(clippy::nonminimal_bool)]
${arms.join("\n")}
None
}`;
}
function buildRustMatchExpr(rule, provider) {
const allTokens = [rule.match_value, ...(rule.match_aliases ?? [])];
switch (rule.match_kind) {
case "exact":
return allTokens.map((t) => `lower == "${t.toLowerCase()}"`).join(" || ");
case "prefix":
return allTokens.map((t) => `lower.starts_with("${t.toLowerCase()}")`).join(" || ");
case "gpt5-token":
return allTokens
.map((t) => `gpt5_token_matches_rs(lower, "${t.toLowerCase()}")`)
.join(" || ");
case "gpt5-base":
return allTokens.map((t) => `gpt5_base_matches_rs(lower, "${t.toLowerCase()}")`).join(" || ");
case "segment": {
// Use a contains approach: split on non-alphanumeric, check any segment equals token
return allTokens
.map((t) => `lower.split(|c: char| !c.is_ascii_alphanumeric()).any(|s| s == "${t.toLowerCase()}")`)
.join(" || ");
}
case "segment-prefix": {
return allTokens
.map(
(t) =>
`lower.split(|c: char| !c.is_ascii_alphanumeric()).any(|s| s.starts_with("${t.toLowerCase()}"))`,
)
.join(" || ");
}
default:
throw new Error(`unknown match_kind: ${rule.match_kind}`);
}
}
// Append gpt5 helper fns that the generated code calls
const rustGpt5Helpers = `
// ---------------------------------------------------------------------------
// gpt5 boundary-aware token helpers (used by generated family resolver)
// ---------------------------------------------------------------------------
/// Returns true if \`model\` contains \`token\` at a word boundary (end-of-string or "-").
/// Does not match if followed immediately by a digit or letter.
fn gpt5_token_matches_rs(model: &str, token: &str) -> bool {
let lower = model;
let tok_lower = token;
let mut start = 0;
loop {
match lower[start..].find(tok_lower) {
None => return false,
Some(rel_idx) => {
let abs_idx = start + rel_idx;
let after_idx = abs_idx + tok_lower.len();
let after_char = lower[after_idx..].chars().next();
match after_char {
None | Some('-') => return true,
_ => start = after_idx,
}
}
}
}
}
/// Like gpt5_token_matches_rs but also rejects short -<1-3 digit> suffixes.
fn gpt5_base_matches_rs(model: &str, token: &str) -> bool {
let lower = model;
let tok_lower = token;
let mut start = 0;
loop {
match lower[start..].find(tok_lower) {
None => return false,
Some(rel_idx) => {
let abs_idx = start + rel_idx;
let after_idx = abs_idx + tok_lower.len();
let suffix = &lower[after_idx..];
if suffix.is_empty() {
return true;
}
if !suffix.starts_with('-') {
start = after_idx;
continue;
}
let dash_rest = &suffix[1..];
// Reject -<1-3 digits> that look like version numbers.
let is_short_version = dash_rest
.chars()
.take(4)
.enumerate()
.all(|(i, c)| {
if i < 3 { c.is_ascii_digit() }
else { !c.is_ascii_alphanumeric() }
})
&& dash_rest.chars().next().is_some_and(|c| c.is_ascii_digit());
if is_short_version {
start = after_idx;
continue;
}
return true;
}
}
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
#[path = "generated_model_capabilities_tests.rs"]
mod tests;
`;
const finalRustContent = rustfmt(rustContent + rustGpt5Helpers);
// ---------------------------------------------------------------------------
// TypeScript code generation
// ---------------------------------------------------------------------------
function tsEffortList(efforts) {
return `[${efforts.map((e) => `"${e}"`).join(", ")}] as const`;
}
function tsEffortOrNull(e) {
if (e === null || e === undefined) return "null";
return `"${e}"`;
}
function tsDbv2Route(route) {
return `"${route}"`;
}
function tsThinkingMode(mode) {
return `"${mode}"`;
}
function tsNormPolicy(policy) {
return `"${policy}"`;
}
function emitTsCapabilityResult(r, indent = " ") {
const i = indent;
return [
`{`,
`${i} registryLabel: ${r.registry_label === null ? "null" : `"${r.registry_label}"`},`,
`${i} thinkingMode: ${tsThinkingMode(r.thinking_mode)},`,
`${i} supportedEfforts: ${tsEffortList(r.supported_efforts)},`,
`${i} defaultEffort: ${tsEffortOrNull(r.default_effort)},`,
`${i} databricksV2WireRoute: ${tsDbv2Route(r.databricks_v2_wire_route)},`,
`${i} normalizationPolicy: ${tsNormPolicy(r.normalization_policy)},`,
`${i}}`,
].join(`\n${i}`);
}
// Build TS exact records map
const tsExactEntries = [];
for (const rec of manifest.exact_records ?? []) {
const result = resolve(rec.provider, rec.raw_model_id);
const clean = { ...result };
delete clean._provenance;
tsExactEntries.push({ rec, clean });
}
// Build TS family rule resolution (inlined ordered if-chain for readability)
function emitTsFamilyResolver() {
const sorted = [...manifest.family_rules].sort((a, b) => b.match_priority - a.match_priority);
const arms = [];
for (const rule of sorted) {
for (const provider of rule.providers) {
// databricks_v2_wire_route is only meaningful for databricks_v2; all other providers get not-applicable
const wireRoute = provider === "databricks_v2"
? rule.databricks_v2_wire_route
: "not-applicable";
const clean = {
registry_label: rule.registry_label ?? null,
thinking_mode: rule.thinking_mode,
supported_efforts: rule.supported_efforts,
default_effort: rule.default_effort,
databricks_v2_wire_route: wireRoute,
normalization_policy: rule.normalization_policy,
};
const matchExpr = buildTsMatchExpr(rule, provider);
arms.push(
` // rule: ${rule.id}, provider: ${provider}, priority: ${rule.match_priority}\n if (provider === "${provider}" && (${matchExpr})) {\n return ${emitTsCapabilityResult(clean, " ")};\n }`,
);
}
}
return arms.join("\n");
}
function buildTsMatchExpr(rule, provider) {
const allTokens = [rule.match_value, ...(rule.match_aliases ?? [])];
switch (rule.match_kind) {
case "exact":
return allTokens.map((t) => `lower === "${t.toLowerCase()}"`).join(" || ");
case "prefix":
return allTokens.map((t) => `lower.startsWith("${t.toLowerCase()}")`).join(" || ");
case "gpt5-token":
return allTokens
.map((t) => `gpt5TokenMatchesGenerated(lower, "${t.toLowerCase()}")`)
.join(" || ");
case "gpt5-base":
return allTokens
.map((t) => `gpt5BaseMatchesGenerated(lower, "${t.toLowerCase()}")`)
.join(" || ");
case "segment":
return allTokens
.map(
(t) =>
`lower.split(/[^a-z0-9]+/).includes("${t.toLowerCase()}")`,
)
.join(" || ");
case "segment-prefix":
return allTokens
.map(
(t) =>
`lower.split(/[^a-z0-9]+/).some(s => s.startsWith("${t.toLowerCase()}"))`,
)
.join(" || ");
default:
throw new Error(`unknown match_kind: ${rule.match_kind}`);
}
}
const tsProviderFallbacks = providerFallbackKeys
.map((provider) => {
const fb = manifest.provider_fallbacks[provider];
const blankClean = { ...fb.blank, registry_label: null };
const concClean = { ...fb.concrete_unknown, registry_label: null };
delete blankClean._provenance;
delete concClean._provenance;
return ` "${provider}": {
blank: ${emitTsCapabilityResult(blankClean, " ")},
concreteUnknown: ${emitTsCapabilityResult(concClean, " ")},
},`;
})
.join("\n");
const defaultFbBlank = { ...manifest.provider_fallbacks["_default"].blank, registry_label: null };
const defaultFbConc = {
...manifest.provider_fallbacks["_default"].concrete_unknown,
registry_label: null,
};
delete defaultFbBlank._provenance;
delete defaultFbConc._provenance;
const tsContent = `// 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 = [${VALID_EFFORTS.map((e) => `"${e}"`).join(", ")}] 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 = [
${manifest.databricks_v2_known_models
.map((id) => ` "${id}",`)
.join("\n")}
] 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([
${registryLabelsArr
.map(({ id, label }) => ` ["${id}", "${label}"],`)
.join("\n")}
]);
// ---------------------------------------------------------------------------
// 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>([
${tsExactEntries
.map(({ rec, clean }) => {
return ` ["${rec.provider}::${rec.raw_model_id}", ${emitTsCapabilityResult(clean, " ")}],`;
})
.join("\n")}
]);
// ---------------------------------------------------------------------------
// Provider fallbacks
// ---------------------------------------------------------------------------
const PROVIDER_FALLBACKS: Record<string, { blank: CapabilityResult; concreteUnknown: CapabilityResult }> = {
${tsProviderFallbacks}
};
const DEFAULT_FALLBACK = {
blank: ${emitTsCapabilityResult(defaultFbBlank, " ")},
concreteUnknown: ${emitTsCapabilityResult(defaultFbConc, " ")},
};
// ---------------------------------------------------------------------------
// Strip catalog prefix — finds first family token occurrence
// ---------------------------------------------------------------------------
export function stripCatalogPrefix(model: string): string {
const FAMILY_TOKENS = [${manifest.family_tokens.map((t) => `"${t}"`).join(", ")}] 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();
${emitTsFamilyResolver()}
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 };
}
`;
// ---------------------------------------------------------------------------
// Write or check files
// ---------------------------------------------------------------------------
// Outputs are written after the pricing axis is built below — see outputsWithPricing.
// (The outputs array is kept here as a reference; actual writing uses outputsWithPricing.)
const _capabilityOnlyOutputs = [finalRustContent, tsContent]; // referenced below
let checkFailed = false; // used by the pricing write loop below
// ---------------------------------------------------------------------------
// Pricing axis — exact (authority, model) lookup, no inference
// ---------------------------------------------------------------------------
//
// Pricing is keyed on (billing-authority domain, exact model string) — both
// as returned by the provider API. No prefix matching, no normalization,
// no catalog-prefix stripping. "No applicable price" is a complete result.
//
// Databricks routes are corporate-internal and are not represented here.
// The publisher (P2 scope) must omit PricingIdentity for those routes.
// ---------------------------------------------------------------------------
const pricingRecords = manifest.pricing_records ?? [];
// ---------------------------------------------------------------------------
// Pricing-record validation — every entry must be structurally complete.
// No silent skips; any non-conforming entry is a hard error.
// ---------------------------------------------------------------------------
/** Registered billing-namespace identifiers (closed set; extends only by NIP amendment). */
const REGISTERED_AUTHORITIES = new Set([
"api.anthropic.com",
"api.openai.com",
"openrouter.ai",
]);
/**
* Return true if the string contains any character that is unsafe in a
* generated Rust string literal or a TS composite key: control characters
* (U+0000–U+001F, U+007F), double-quotes, backslashes, or NUL.
*/
function hasUnsafeKeyChars(s) {
return /[\x00-\x1f\x7f"\\]/.test(s);
}
for (let i = 0; i < pricingRecords.length; i++) {
const rec = pricingRecords[i];
const loc = `pricing_records[${i}]`;
// Every entry must have authority and model — no comment-only entries allowed
// in the records array (top-level _comment_pricing is a separate key).
if (typeof rec.authority !== "string" || rec.authority.length === 0) {
throw new Error(`${loc}: missing or empty "authority" — every pricing record must have a registered authority`);
}
if (!REGISTERED_AUTHORITIES.has(rec.authority)) {
throw new Error(
`${loc}: unregistered authority "${rec.authority}". ` +
`Allowed values: ${[...REGISTERED_AUTHORITIES].join(", ")}. ` +
`The set extends only by NIP amendment.`,
);
}
if (typeof rec.model !== "string" || rec.model.length === 0) {
throw new Error(`${loc} (${rec.authority}): missing or empty "model"`);
}
if (hasUnsafeKeyChars(rec.authority)) {
throw new Error(`${loc}: authority "${rec.authority}" contains unsafe characters (control chars, quotes, backslashes)`);
}
if (hasUnsafeKeyChars(rec.model)) {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): model contains unsafe characters (control chars, quotes, backslashes)`);
}
// usd_per_mtok must be present with finite nonnegative input and output
const p = rec.usd_per_mtok;
if (!p || typeof p !== "object") {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): missing usd_per_mtok`);
}
if (typeof p.input !== "number" || !isFinite(p.input) || p.input < 0) {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): usd_per_mtok.input must be a finite nonnegative number`);
}
if (typeof p.output !== "number" || !isFinite(p.output) || p.output < 0) {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): usd_per_mtok.output must be a finite nonnegative number`);
}
for (const cacheField of ["cache_read", "cache_write"]) {
if (!Object.hasOwn(p, cacheField)) {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): usd_per_mtok.${cacheField} is required (use null for unknown)`);
}
const v = p[cacheField];
if (v !== null) {
if (typeof v !== "number" || !isFinite(v) || v < 0) {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): usd_per_mtok.${cacheField} must be null or a finite nonnegative number`);
}
}
}
// Provenance: _source must be present and non-empty
if (typeof rec._source !== "string" || rec._source.length === 0) {
throw new Error(`${loc} (${rec.authority}, ${rec.model}): missing "_source" provenance field`);
}
}
// Duplicate detection on the exact emitted (authority, model) identity — no transforms applied.
const pricingKeysSeen = new Set();
for (const rec of pricingRecords) {
const key = `${rec.authority}\0${rec.model}`;
if (pricingKeysSeen.has(key)) {
throw new Error(`duplicate pricing record for (${rec.authority}, ${rec.model})`);
}
pricingKeysSeen.add(key);
}
// ---------------------------------------------------------------------------
// Rust pricing generation
// ---------------------------------------------------------------------------
function rustOptionFloat(v) {
if (v === null || v === undefined) return "None";
const f = Number(v);
const s = Number.isInteger(f) ? `${f}.0_f64` : `${f}_f64`;
return `Some(${s})`;
}
function rustFloat(v) {
const f = Number(v);
return Number.isInteger(f) ? `${f}.0` : `${f}`;
}
function emitRustPricingResult(p, indent = " ") {
const i = indent;
return [
`${i}ModelPricing {`,
`${i} input_usd_per_mtok: ${rustFloat(p.input)},`,
`${i} output_usd_per_mtok: ${rustFloat(p.output)},`,
`${i} cache_read_usd_per_mtok: ${rustOptionFloat(p.cache_read)},`,
`${i} cache_write_usd_per_mtok: ${rustOptionFloat(p.cache_write)},`,
`${i}}`,
].join("\n");
}
// Each record becomes one arm in a nested match: authority → model → prices
// We group records by authority first for a readable nested match.
/** @type {Map<string, Array>} */
const byAuthority = new Map();
for (const rec of pricingRecords) {
const auth = rec.authority;
if (!byAuthority.has(auth)) byAuthority.set(auth, []);
byAuthority.get(auth).push(rec);
}
function buildRustPricingBody() {
const authArms = [];
for (const [auth, recs] of byAuthority) {
const modelArms = recs.map((rec) => {
const m = rec.model;
return (
` // (${auth}, ${m})\n` +
` "${m}" => return Some(\n${emitRustPricingResult(rec.usd_per_mtok, " ")}\n ),`
);
});
authArms.push(
` // authority: ${auth}\n` +
` "${auth}" => match model {\n${modelArms.join("\n")}\n _ => {}\n },`
);
}
return authArms.join("\n");
}
const rustPricingContent = `
// ---------------------------------------------------------------------------
// Pricing axis — exact (authority, model) lookup
// ---------------------------------------------------------------------------
/// Per-model token pricing in USD per million tokens.
/// All fields are USD / 1,000,000 tokens.
///
/// \`None\` fields mean the provider has not published a price for that token
/// category — never treat them as zero.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModelPricing {
pub input_usd_per_mtok: f64,
pub output_usd_per_mtok: f64,
/// Cache-read price, if published.
pub cache_read_usd_per_mtok: Option<f64>,
/// Cache-write (cache creation) price, if published.
/// Scoped to the default ephemeral cache class.
pub cache_write_usd_per_mtok: Option<f64>,
}
/// Look up pricing for an exact (billing authority, model) pair.
///
/// * \`authority\` — the registered billing-authority token, e.g.
/// \`"api.anthropic.com"\`, \`"api.openai.com"\`. This is NOT the
/// runtime transport \`Provider\` enum; it is the proven billing namespace.
/// Match is exact — the caller must supply the exact registered token.
/// * \`model\` — the exact model string returned by the provider API
/// in its response body. Match is exact — no normalization applied.
///
/// Returns \`None\` when the (authority, model) pair has no pricing record
/// — callers MUST treat \`None\` as "price unknown", never as "price zero".
/// Unknown authorities and unknown models both return \`None\`; there is no
/// family, prefix, or case-folding fallback.
pub fn lookup_pricing(authority: &str, model: &str) -> Option<ModelPricing> {
match authority {
${buildRustPricingBody()}
_ => {}
}
None
}
`;
const finalRustPricingContent = rustfmt(rustContent + rustGpt5Helpers + rustPricingContent);
// ---------------------------------------------------------------------------
// TypeScript pricing generation
// ---------------------------------------------------------------------------
function tsPricingField(v) {
if (v === null || v === undefined) return "null";
return `${v}`;
}
function emitTsPricingResult(p, indent = " ") {
const i = indent;
return [
`{`,
`${i} inputUsdPerMtok: ${p.input},`,
`${i} outputUsdPerMtok: ${p.output},`,
`${i} cacheReadUsdPerMtok: ${tsPricingField(p.cache_read)},`,
`${i} cacheWriteUsdPerMtok: ${tsPricingField(p.cache_write)},`,
`${i}}`,
].join(`\n${i}`);
}
// Build TS pricing map entries: key is `${authority}\0${model}` (exact, no transforms)
const tsPricingMapEntries = pricingRecords
.map((rec) => {
const key = `${rec.authority}\0${rec.model}`;
return ` [${JSON.stringify(key)}, ${emitTsPricingResult(rec.usd_per_mtok, " ")}],`;
})
.join("\n");
const tsPricingContent = `
// ---------------------------------------------------------------------------
// Pricing axis — exact (authority, model) lookup
// ---------------------------------------------------------------------------
/** Per-model token pricing, USD per million tokens. null = price not published. */
export type ModelPricing = {
/** Input (fresh, non-cache) token price, USD/MTok. */
readonly inputUsdPerMtok: number;
/** Output token price, USD/MTok. */
readonly outputUsdPerMtok: number;
/** Cache-read token price, USD/MTok. null when provider has not published it. */
readonly cacheReadUsdPerMtok: number | null;
/**
* Cache-write (cache creation) token price, USD/MTok. null when not published.
* Scoped to the default ephemeral cache class.
*/
readonly cacheWriteUsdPerMtok: number | null;
};
/**
* Composite key for pricing lookup: \`\${authority}\\0\${model}\` (exact strings, no transforms).
*
* authority — registered billing-authority token (e.g. "api.anthropic.com").
* model — exact model string from the provider API response body.
*/
const PRICING_TABLE: Map<string, ModelPricing> = new Map([
${tsPricingMapEntries}
]);
/**
* Look up pricing for an exact (billing authority, model) pair.
*
* Both arguments are matched exactly as supplied — no case normalization.
* The caller must supply the exact registered authority token and the exact
* provider-API model string. Returns \`null\` for unrecognised pairs — callers
* MUST treat null as "price unknown", never as zero cost. There is no family,
* prefix, or case-folding fallback.
*
* @param authority - Registered billing-authority token, e.g. "api.anthropic.com".
* This is NOT the runtime provider name; it is the proven billing namespace.
* @param model - Exact model string returned by the provider API response body.
*/
export function lookupModelPricing(authority: string, model: string): ModelPricing | null {
const key = \`\${authority}\\0\${model}\`;
return PRICING_TABLE.get(key) ?? null;
}
`;
// Standalone pricing TS file that does NOT import from modelCapabilities
// (no normalization or catalog-prefix logic needed)
const tsPricingFileContent = `// biome-ignore-all format: generated — do not edit by hand.
// Regenerate with: node scripts/generate-model-capabilities.mjs
// Source: scripts/model-capabilities.json
//
// Pricing axis: exact (authority, model) lookup for cost estimation.
// Consumers: agent-usage aggregation (Phase 4 of Usage v2 plan).
${tsPricingContent}`;
const outputsWithPricing = [
{
path: outputDirOverride
? join(outputDirOverride, "generated_model_capabilities.rs")
: join(repoRoot, "crates", "buzz-agent", "src", "generated_model_capabilities.rs"),
content: finalRustPricingContent,
label: "Rust (with pricing)",
},
{
path: outputDirOverride
? join(outputDirOverride, "modelCapabilities.ts")
: join(repoRoot, "desktop", "src", "features", "agents", "ui", "modelCapabilities.ts"),
content: tsContent,
label: "TypeScript",
},
{
path: outputDirOverride
? join(outputDirOverride, "modelPricing.ts")
: join(repoRoot, "desktop", "src", "features", "agents", "ui", "modelPricing.ts"),
content: tsPricingFileContent,
label: "TypeScript Pricing",
},
];
let checkFailedPricing = false;
for (const { path, content, label } of outputsWithPricing) {
if (CHECK_MODE) {
if (!existsSync(path)) {
console.error(`CHECK FAILED: ${label} file does not exist: ${path}`);
checkFailedPricing = true;
continue;
}
const existing = readFileSync(path, "utf8");
if (existing !== content) {
console.error(`CHECK FAILED: ${label} file is stale: ${path}`);
console.error("Run: node scripts/generate-model-capabilities.mjs to regenerate.");
checkFailedPricing = true;
} else {
console.log(`OK: ${label}`);
}
} else {
writeFileSync(path, content, "utf8");
console.log(`Wrote ${label}: ${path}`);
}
}
if ((CHECK_MODE && checkFailed) || (CHECK_MODE && checkFailedPricing)) {
process.exit(1);
}
if (!CHECK_MODE) {
console.log("Done. Generated 3 files (Rust capabilities + TS capabilities + TS pricing).");
}