Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
web: ${{ steps.filter.outputs.web }}
mobile: ${{ steps.filter.outputs.mobile }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

Check warning

Code scanning / zizmor

detects commit SHAs that don't match their version comment tags Warning

detects commit SHAs that don't match their version comment tags
with:
fetch-depth: 2
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
Expand Down Expand Up @@ -130,6 +130,50 @@
- name: Unit tests
run: just test-unit

model-capabilities:
name: Model Capabilities (regen + corpus + schema + buzz-agent tests)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
save-if: ${{ github.event_name != 'pull_request' }}

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0

Check warning

Code scanning / zizmor

detects commit SHAs that don't match their version comment tags Warning

detects commit SHAs that don't match their version comment tags
with:
node-version: '24'
package-manager-cache: false

- name: Regenerate artifacts
run: node scripts/generate-model-capabilities.mjs

- name: Diff check — fail if generated files are stale
run: |
if ! git diff --exit-code \
crates/buzz-agent/src/generated_model_capabilities.rs \
desktop/src/features/agents/ui/modelCapabilities.ts; then
echo ""
echo "ERROR: Generated model-capability files are stale."
echo "Run: node scripts/generate-model-capabilities.mjs"
echo "Then commit the regenerated files."
exit 1
fi
echo "✓ All generated files are up to date."

- name: Run corpus (TS interpreter via --experimental-strip-types)
run: node --experimental-strip-types scripts/run-corpus.mjs

- name: Validate manifest (schema-negative tests)
run: node --test scripts/test-manifest-validator.mjs

- name: Run buzz-agent unit tests (normative corpus + generated interpreter)
run: cargo test -p buzz-agent --lib

desktop-core:
name: Desktop Core
runs-on: ubuntu-latest
Expand Down
88 changes: 81 additions & 7 deletions crates/buzz-agent/src/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

use std::sync::Arc;

use crate::generated_model_capabilities::DATABRICKS_MODEL_NAMES;

use reqwest::Client;

use crate::{
Expand All @@ -23,8 +25,25 @@ use crate::{
types::AgentError,
};

/// Returns the curated display name for a Databricks endpoint ID, or the raw
/// ID when no entry exists in the registry.
///
/// The registry (`DATABRICKS_MODEL_NAMES`) is generated from the manifest
/// (`scripts/model-capabilities.json`) exact records for the `databricks_v2`
/// provider and covers the ~30 managed Databricks endpoints. Any custom or
/// workspace endpoint not in the table is returned untouched — no heuristic
/// guessing.
pub(crate) fn databricks_model_name(id: &str) -> &str {
DATABRICKS_MODEL_NAMES
.iter()
.find(|(k, _)| *k == id)
.map(|(_, v)| *v)
.unwrap_or(id)
}

/// A discovered model entry: `id` is the picker value, `name` is the display
/// label (same as `id` for Databricks — the API has no separate display name).
/// label (curated from models.dev for known managed endpoints; raw ID for
/// custom/unknown endpoints).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelEntry {
pub id: String,
Expand All @@ -34,8 +53,10 @@ pub struct ModelEntry {
/// Known Databricks AI Gateway v2 models — used only when an authenticated
/// `api/ai-gateway/v2/endpoints` call succeeds with an empty list.
/// Mirrors goose's `DATABRICKS_V2_KNOWN_MODELS`.
pub const DATABRICKS_V2_KNOWN_MODELS: &[&str] =
&["databricks-gpt-5-5", "databricks-claude-opus-4-7"];
///
/// Phase 2 cutover: this is now a re-export of the generated constant in
/// `generated_model_capabilities`. Phase 3 removes the old hand-maintained list.
pub use crate::generated_model_capabilities::DATABRICKS_V2_KNOWN_MODELS;

const AUTHENTICATED_EMPTY_CATALOG_SUFFIX: &str = " (default catalog)";

Expand All @@ -44,7 +65,10 @@ fn authenticated_empty_v2_catalog() -> Vec<ModelEntry> {
.iter()
.map(|id| ModelEntry {
id: id.to_string(),
name: format!("{id}{AUTHENTICATED_EMPTY_CATALOG_SUFFIX}"),
name: format!(
"{}{AUTHENTICATED_EMPTY_CATALOG_SUFFIX}",
databricks_model_name(id)
),
})
.collect()
}
Expand Down Expand Up @@ -206,7 +230,7 @@ pub(crate) fn parse_v1_endpoints(json: &serde_json::Value) -> Result<Vec<ModelEn

Some(ModelEntry {
id: name.clone(),
name,
name: databricks_model_name(&name).to_string(),
})
})
.collect();
Expand Down Expand Up @@ -371,7 +395,7 @@ pub(crate) fn parse_v2_endpoints_page(
Some(V2Endpoint {
entry: ModelEntry {
id: name.clone(),
name,
name: databricks_model_name(&name).to_string(),
},
created_ms: endpoint_created_ms(endpoint),
})
Expand Down Expand Up @@ -649,8 +673,12 @@ mod tests {
let ids: Vec<&str> = models.iter().map(|model| model.id.as_str()).collect();

assert_eq!(ids, DATABRICKS_V2_KNOWN_MODELS);
// Each entry carries the curated display name (not the raw ID) plus the
// " (default catalog)" suffix to distinguish this authenticated-but-empty
// slate from a live discovery result.
assert!(models.iter().all(|model| {
model.name == format!("{}{AUTHENTICATED_EMPTY_CATALOG_SUFFIX}", model.id)
let curated = databricks_model_name(&model.id);
model.name == format!("{curated}{AUTHENTICATED_EMPTY_CATALOG_SUFFIX}")
}));
}

Expand All @@ -665,4 +693,50 @@ mod tests {
assert!(!is_chat_capable_endpoint("databricks-gte-large-en"));
assert!(!is_chat_capable_endpoint("databricks-qwen3-embedding-0-6b"));
}
// ---------------------------------------------------------------------------
// Databricks model name registry
// ---------------------------------------------------------------------------

#[test]
fn databricks_model_name_known_id_returns_curated_name() {
assert_eq!(databricks_model_name("databricks-gpt-5-5"), "GPT-5.5");
assert_eq!(
databricks_model_name("databricks-claude-opus-4-7"),
"Claude Opus 4.7"
);
assert_eq!(
databricks_model_name("databricks-gpt-oss-120b"),
"GPT OSS 120B"
);
}

#[test]
fn databricks_model_name_unknown_custom_endpoint_returns_raw_id() {
// Custom workspace endpoints must never be guessed — pass through unchanged.
assert_eq!(
databricks_model_name("databricks-team-2025-01"),
"databricks-team-2025-01"
);
assert_eq!(
databricks_model_name("databricks-finance-2025-01-30"),
"databricks-finance-2025-01-30"
);
assert_eq!(
databricks_model_name("some-unknown-endpoint"),
"some-unknown-endpoint"
);
}

#[test]
fn v2_known_models_fallback_entries_get_curated_names() {
// The DATABRICKS_V2_KNOWN_MODELS constant lists IDs that are in the
// registry, so their fallback entries must carry curated names.
for id in DATABRICKS_V2_KNOWN_MODELS {
let name = databricks_model_name(id);
assert_ne!(
name, *id,
"known model {id} should have a curated name, not raw ID"
);
}
}
}
Loading
Loading