feat(catalog): replace Databricks model-label tokenizer with models.dev registry lookup

The frontend heuristic in #3586 title-cased every word and converted adjacent
digit pairs to decimals for any string starting with 'databricks-'. This
invented false labels for custom workspace endpoints: 'databricks-team-2025-01'
→ 'Team 2025.01', 'databricks-finance-2025-01-30' → 'Finance 2025.01 30'.

Replace with a lookup-and-pass-through approach drawn from the same design
goose uses in production:

- Generator script (scripts/generate-databricks-model-names.py) fetches
  https://models.dev/api.json and emits a sorted Rust static slice of
  (id, name) pairs under crates/buzz-agent/src/databricks_model_names.rs.
  Refresh by rerunning the script and committing the diff.

- catalog.rs consults the table via databricks_model_name(id): known managed
  endpoints get curated names (e.g. 'databricks-gpt-5-5' → 'GPT-5.5'),
  unknown/custom endpoints return their raw ID unchanged. Applied to all
  ModelEntry construction paths: v1/v2 discovery, the empty-list fallback,
  and discovery_failure_fallback.

- Frontend: a matching TS registry (desktop/src/features/agents/lib/
  databricksModelNames.ts) covers persisted raw IDs on cards, rows, and
  popovers that render before discovery data is available. formatAgentModelLabel
  and formatDefaultModelLabel use the registry; no heuristic string mangling
  exists anywhere in the TS layer.

- AGENTS.md documents the three-tier label precedence: API/runtime name first,
  table-backed fallback second, raw ID last.

Supersedes #3586 (kennylopez-model-display-labels).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
2026-07-29 15:03:47 -04:00
parent 324bd6b464
commit 144ef4d9d0
12 changed files with 385 additions and 10 deletions

View File

@@ -143,3 +143,10 @@ matches the code is worse than no rule; a new pattern that isn't written down
here will be broken by the next agent that never learns it existed. Reviewers:
treat a config-behavior diff without a matching AGENTS.md diff (or an explicit
"no rules changed" note) as incomplete.
## Model display-name precedence
Labels shown in cards, pickers, and popovers follow a three-tier cascade:
1. **API/runtime `name`** — `AgentModelInfo.name` from discovery (`AgentModelsResponse`). This is the authoritative source; all providers populate it at discover time (`openai_model_display_name`, Anthropic `display_name`, ACP runtime name, Databricks registry lookup).
2. **Table-backed fallback** — for persisted raw Databricks endpoint IDs that render before discovery data is available, `databricksModelName(id)` in `desktop/src/features/agents/lib/databricksModelNames.ts` does a static lookup against the models.dev-seeded registry. Refresh by rerunning `scripts/generate-databricks-model-names.py`.
3. **Raw ID** — any ID not covered by tiers 1 or 2 renders unchanged. No heuristic string mangling.

View File

@@ -56,3 +56,59 @@ test("resolveAgentCardModelLabel — non-inherited agent with a blank resolved m
});
assert.equal(label, "Default model (claude-sonnet)");
});
// Databricks registry integration
import { formatAgentModelLabel } from "./formatAgentModelLabel.ts";
test("formatAgentModelLabel — known Databricks managed ID returns curated name", () => {
assert.equal(formatAgentModelLabel("databricks-gpt-5-5"), "GPT-5.5");
assert.equal(
formatAgentModelLabel("databricks-claude-opus-4-7"),
"Claude Opus 4.7",
);
});
test("formatAgentModelLabel — unknown custom Databricks ID returns raw ID unchanged", () => {
assert.equal(
formatAgentModelLabel("databricks-team-2025-01"),
"databricks-team-2025-01",
);
});
test("formatAgentModelLabel — non-Databricks ID returns raw ID unchanged", () => {
assert.equal(formatAgentModelLabel("claude-sonnet-4-7"), "claude-sonnet-4-7");
assert.equal(formatAgentModelLabel("gpt-4o"), "gpt-4o");
});
test("formatAgentModelLabel — null or empty returns Auto", () => {
assert.equal(formatAgentModelLabel(null), "Auto");
assert.equal(formatAgentModelLabel(""), "Auto");
assert.equal(formatAgentModelLabel(" "), "Auto");
});
test("resolveAgentCardModelLabel — known Databricks defaultModel renders curated name in default label", () => {
const label = resolveAgentCardModelLabel({
agent: undefined,
personaModel: null,
defaultModel: "databricks-gpt-5-5",
});
assert.equal(label, "Default model (GPT-5.5)");
});
test("resolveAgentCardModelLabel — unknown custom Databricks defaultModel renders raw ID in default label", () => {
const label = resolveAgentCardModelLabel({
agent: undefined,
personaModel: null,
defaultModel: "databricks-team-2025-01",
});
assert.equal(label, "Default model (databricks-team-2025-01)");
});
test("resolveAgentCardModelLabel — known Databricks agent model renders curated name", () => {
const label = resolveAgentCardModelLabel({
agent: { modelSource: "definition", model: "databricks-gpt-oss-120b" },
personaModel: null,
defaultModel: "something-else",
});
assert.equal(label, "GPT OSS 120B");
});

View File

@@ -1,4 +1,5 @@
import { formatAgentModelLabel } from "./formatAgentModelLabel";
import { databricksModelName } from "./databricksModelNames";
import type { ManagedAgent } from "@/shared/api/types";
/**
@@ -40,5 +41,7 @@ export function resolveAgentCardModelLabel(input: {
export function formatDefaultModelLabel(defaultModel: string) {
const model = defaultModel.trim();
return model ? `Default model (${model})` : "Default model";
return model
? `Default model (${databricksModelName(model)})`
: "Default model";
}

View File

@@ -0,0 +1,50 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
databricksModelName,
DATABRICKS_MODEL_NAMES,
} from "./databricksModelNames.ts";
test("databricksModelName — known managed endpoint returns curated name", () => {
assert.equal(databricksModelName("databricks-gpt-5-5"), "GPT-5.5");
assert.equal(
databricksModelName("databricks-claude-opus-4-7"),
"Claude Opus 4.7",
);
assert.equal(databricksModelName("databricks-gpt-oss-120b"), "GPT OSS 120B");
});
test("databricksModelName — unknown custom endpoint returns raw ID unchanged", () => {
// A workspace-specific or date-stamped endpoint that isn't in the registry
// must never be guessed — return it verbatim.
assert.equal(
databricksModelName("databricks-team-2025-01"),
"databricks-team-2025-01",
);
assert.equal(
databricksModelName("databricks-finance-2025-01-30"),
"databricks-finance-2025-01-30",
);
assert.equal(
databricksModelName("some-custom-workspace-model"),
"some-custom-workspace-model",
);
});
test("databricksModelName — empty string returns empty string", () => {
assert.equal(databricksModelName(""), "");
});
test("DATABRICKS_MODEL_NAMES — registry is non-empty and all values are non-empty strings", () => {
const entries = Object.entries(DATABRICKS_MODEL_NAMES);
assert.ok(entries.length > 0, "registry must not be empty");
for (const [id, name] of entries) {
assert.ok(
id.startsWith("databricks-"),
`ID ${id} must start with 'databricks-'`,
);
assert.ok(name.length > 0, `name for ${id} must be non-empty`);
assert.notEqual(name, id, `curated name for ${id} must differ from raw ID`);
}
});

View File

@@ -0,0 +1,54 @@
// GENERATED by scripts/generate-databricks-model-names.py
// Source: https://models.dev/api.json -- providers.databricks.models
// Refresh: python3 scripts/generate-databricks-model-names.py (then port to TS)
//
// Do not hand-edit -- rerun the script to update.
/**
* Curated display names for known Databricks AI Gateway endpoints.
*
* Keys are endpoint IDs returned verbatim by the discovery APIs.
* Values are human-readable display names sourced from models.dev.
*
* Unknown endpoint IDs are displayed as their raw ID -- no guessing.
*/
export const DATABRICKS_MODEL_NAMES: Record<string, string> = {
"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",
};
/**
* Returns the curated display name for a Databricks endpoint ID, or the raw
* ID when no entry exists in the registry. No heuristic guessing.
*/
export function databricksModelName(id: string): string {
return DATABRICKS_MODEL_NAMES[id] ?? id;
}

View File

@@ -1,8 +1,15 @@
import { databricksModelName } from "./databricksModelNames";
/**
* Returns a human-readable model label for an agent or persona, falling back to
* "Auto" when no model is set (empty or whitespace-only).
*
* For known Databricks managed endpoints the registry-curated name is returned
* (e.g. "databricks-gpt-5-5" → "GPT-5.5"). Unknown or custom endpoint IDs are
* returned unchanged — no heuristic string mangling.
*/
export function formatAgentModelLabel(model: string | null | undefined) {
const trimmed = model?.trim();
return trimmed && trimmed.length > 0 ? trimmed : "Auto";
if (!trimmed) return "Auto";
return databricksModelName(trimmed);
}

View File

@@ -27,6 +27,7 @@ import { friendlyAgentLastError } from "@/features/agents/lib/friendlyAgentLastE
import { ManagedAgentLogPanel } from "./ManagedAgentLogPanel";
import { PubKey } from "@/shared/ui/PubKey";
import { SubsectionLabel } from "@/shared/ui/PageHeader";
import { databricksModelName } from "@/features/agents/lib/databricksModelNames";
export function ManagedAgentRow({
agent,
@@ -410,7 +411,7 @@ function RuntimeBlock({
{runtimeSource || agent.model ? (
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{runtimeSource ? <span>{runtimeSource}</span> : null}
{agent.model ? <span>{agent.model}</span> : null}
{agent.model ? <span>{databricksModelName(agent.model)}</span> : null}
</div>
) : null}
</div>

View File

@@ -51,6 +51,7 @@ import { BotIdenticon } from "@/features/messages/ui/BotIdenticon";
import { useNow } from "@/shared/lib/useNow";
import { Button } from "@/shared/ui/button";
import { Spinner } from "@/shared/ui/spinner";
import { databricksModelName } from "@/features/agents/lib/databricksModelNames";
type UserProfilePopoverProps = {
children: React.ReactNode;
@@ -611,7 +612,7 @@ export function UserProfilePopover({
<InfoBadge>{runtimeLabel(relayAgent.agentType)}</InfoBadge>
) : null}
{managedAgent?.model ? (
<InfoBadge>{managedAgent.model}</InfoBadge>
<InfoBadge>{databricksModelName(managedAgent.model)}</InfoBadge>
) : null}
{managedAgent?.acpCommand ? (
<InfoBadge>ACP: {managedAgent.acpCommand}</InfoBadge>