feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)

Upgrades Buzz's mesh-llm dependencies through the released `v0.76.0-rc8`
tag (`2040765d`), including the Qwen3.8 curated recommendation and rc8's
scheduler/runtime improvements.

**Scope note:** the earlier open-relay/unenforced-admission mode has
been removed from this PR at Mic's direction — it is not a product mode
we want. Mesh admission remains roster/allowlist driven, exactly as on
`main`: on a relay with no NIP-43 membership snapshot the mesh runs
self-only. No NIP-11 mode probing, no mode-transition restarts. A future
perimeter/admission strategy for open relays will be designed
separately.

This PR also:
- seeds `BUZZ_AGENT_LLM_TIMEOUT_SECS=660` for mesh agents, above
MeshLLM's 600-second backend timeout;
- makes `desktop-tauri-clippy` lint both default and `mesh-llm` cfg
graphs;
- runs the feature-enabled desktop test suite in CI;
- recommends Qwen3.8 27B Q4_K_M for 64 GB-and-larger machines, then
ladders down through Gemma 4 E4B and Qwen 9B for smaller machines;
- keeps stored shared-compute `auto` translated to MeshLLM's supported
wire model `mesh`.

RC8 verification:
- `just ci` passed locally at
`92ecc7ec933bdd4df804cc9f28a2b51efa5313c5`.
- Pre-push differential gates passed, including both desktop Tauri cfg
graphs and package tests.
- A prior isolated runtime smoke used the RC8 binary's OpenAI endpoint
for a Buzz-shaped system/user/tool/tool-result/final-response loop; all
assertions passed and the isolated process was shut down.

Perf previously measured on M5 Metal, Qwen3.8-27B-Q4_K_M: TTFT 0.22–0.32
s, ~25 tok/s streaming; agent-shaped turns ~1 s to first token after the
first (prefix cache).

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz>
Signed-off-by: Alessandro Joabar <sandro@squareup.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz>
Co-authored-by: Mic Neale <305999590+micspiral@users.noreply.github.com>
Co-authored-by: Alessandro Joabar <sandro@squareup.com>
This commit is contained in:
Mic Neale
2026-09-09 00:30:45 +10:00
committed by GitHub
parent cd54e26823
commit 44316ff72f
14 changed files with 690 additions and 558 deletions

View File

@@ -92,6 +92,10 @@ jobs:
run: just desktop-tauri-test
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
- name: Desktop Mesh feature tests
run: cargo test --manifest-path desktop/src-tauri/Cargo.toml --features mesh-llm --lib
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
- name: Desktop Tauri compiled-flag verification
run: just desktop-tauri-test-compiled-flags
env:

479
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -211,8 +211,13 @@ _ensure-migrations: _ensure-services
./scripts/seed-local-community.sh
# Run clippy on the desktop Tauri Rust crate
# Features are additive, so a single invocation lints only one cfg graph.
# Both graphs ship (release-windows builds without mesh-llm), so lint both:
# the default graph covers the `#[cfg(not(feature = "mesh-llm"))]` arms and
# the feature-enabled graph covers the mesh code.
desktop-tauri-clippy: _ensure-sidecar-stubs
cargo clippy --manifest-path {{desktop_tauri_manifest}} --workspace --all-targets -- -D warnings
cargo clippy --manifest-path {{desktop_tauri_manifest}} --workspace --all-targets --features mesh-llm -- -D warnings
# Check the desktop Tauri Rust crate compiles
desktop-tauri-check: _ensure-sidecar-stubs
@@ -682,7 +687,11 @@ desktop-standalone *ARGS: _ensure-sidecar-stubs
fi
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
echo "Starting standalone desktop on Vite port ${BUZZ_VITE_PORT}; no relay services were started"
pnpm exec tauri dev --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
FEATURES=()
if [[ -n "{{mesh}}" ]]; then
FEATURES=(--features mesh-llm)
fi
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
# Run the desktop app against the internal staging relay (installs deps + builds agent tools automatically)
staging *ARGS: bootstrap _ensure-sidecar-stubs

View File

@@ -86,8 +86,8 @@ async-compression = { version = "0.4.42", features = ["tokio", "gzip"] }
dev = ["buzz-auth/dev"]
[dev-dependencies]
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"] }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"] }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"] }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"] }
# Relay-driven mesh lifecycle smoke (examples/mesh_relay_lifecycle_smoke.rs):
# the relay client for discovery notes and the exact ed25519 the mesh owner
# keys use for binding verification.

File diff suppressed because it is too large Load Diff

View File

@@ -110,15 +110,15 @@ buzz_voice_pkg = { package = "buzz-voice", path = "../../crates/buzz-voice" }
buzz_terminal = { package = "buzz-terminal", path = "crates/buzz-terminal" }
buzz_ws_client_pkg = { package = "buzz-ws-client", path = "../../crates/buzz-ws-client" }
portable-pty = "0.9"
iroh = { version = "1.0.2", optional = true }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
iroh = { version = "1.0.3", optional = true }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
# Model catalog + hardware survey for the Share-compute model picker (same
# diagnose pattern as mesh-console). Lib name of mesh-llm-client is mesh_client.
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-client", optional = true }
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-node", optional = true }
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-system", optional = true }
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.75.1", package = "mesh-llm-events", optional = true }
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-client", optional = true }
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-node", optional = true }
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-system", optional = true }
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", package = "mesh-llm-events", optional = true }
base64 = "0.22"
sha2 = "0.11"
tar = "0.4"

View File

@@ -353,7 +353,7 @@ pub async fn apply_workspace(
}
}
});
return Ok(());
Ok(())
}
#[cfg(not(feature = "mesh-llm"))]
@@ -372,9 +372,11 @@ pub async fn apply_workspace(
return Ok(());
}
assert_current_apply_generation(&state.workspace_apply_generation, apply_generation)?;
Ok(())
#[cfg(not(feature = "mesh-llm"))]
{
assert_current_apply_generation(&state.workspace_apply_generation, apply_generation)?;
Ok(())
}
}
#[cfg(test)]

View File

@@ -509,7 +509,7 @@ fn inherited_shared_compute_translates_to_supported_agent_transport() {
);
assert_eq!(
effective.env.get("BUZZ_AGENT_MODEL").map(String::as_str),
Some("auto")
Some(super::super::RELAY_MESH_VIRTUAL_MODEL_ID)
);
assert_eq!(
effective

View File

@@ -66,6 +66,21 @@ pub fn apply_relay_mesh_env(
// survives (see `insert_default_if_unset`, and the copy-forward list in
// `relay_mesh_process_env` that preserves it through the spawn path).
insert_default_if_unset(env, "BUZZ_AGENT_REQUIRE_REPLY", "1");
// A local mesh prefills a cold multi-ten-thousand-token prompt at a few
// hundred tokens/second, so the first turn of a large session legitimately
// runs for minutes with no bytes on the wire (`stream: false`). MeshLLM's
// own frontend gives such a request 600 s before it aborts
// (`OpenAiFrontendConfig::DEFAULT_BACKEND_TIMEOUT`); buzz-agent's 240 s
// default abandons it at four minutes, so the client gives up on work the
// server is still doing and retries it — adding load to a box that is
// already prefilling. Seat the client just above the server's budget so the
// mesh's own error is what surfaces, rather than a client-side abort racing
// it. Measured: an 88,318-token cold prompt on an M5 Max returns 200 at
// 503 s. Remote providers keep the 240 s default; only mesh moves.
// A default, not policy — an explicit user value survives via
// `insert_default_if_unset` and the copy-forward list in
// `relay_mesh_process_env`.
insert_default_if_unset(env, "BUZZ_AGENT_LLM_TIMEOUT_SECS", "660");
// Deliberately no BUZZ_AGENT_THINKING_EFFORT default: mesh translates
// `reasoning_effort` into the chat template's `enable_thinking` flag, so any
// value we pick overrides each model's own template default — and the right
@@ -102,6 +117,10 @@ pub fn relay_mesh_process_env(
for key in [
"BUZZ_AGENT_MAX_OUTPUT_TOKENS",
"BUZZ_AGENT_THINKING_EFFORT",
// Same reason as `BUZZ_AGENT_REQUIRE_REPLY` below: without the
// copy-forward, an explicit user timeout is re-defaulted to 660 by
// `apply_relay_mesh_env`.
"BUZZ_AGENT_LLM_TIMEOUT_SECS",
// Must be copied forward for the user's value to survive: this map is
// written onto the command *after* the layered user env, so a key absent
// here is re-defaulted by `apply_relay_mesh_env` below and an explicit
@@ -270,6 +289,74 @@ mod tests {
);
}
/// MeshLLM's frontend allows a backend call 600 s
/// (`OpenAiFrontendConfig::DEFAULT_BACKEND_TIMEOUT`). buzz-agent's 240 s
/// default abandons a healthy cold prefill long before the server does and
/// retries it, so the client budget must sit above the server's.
#[test]
fn native_provider_outlasts_the_mesh_backend_timeout() {
let mut env = BTreeMap::new();
apply_relay_mesh_env(
&mut env,
Some(RELAY_MESH_PROVIDER_ID),
Some(RELAY_MESH_AUTO_MODEL_ID),
);
let seconds: u64 = env
.get("BUZZ_AGENT_LLM_TIMEOUT_SECS")
.expect("mesh seeds a client timeout")
.parse()
.expect("timeout is whole seconds");
assert!(
seconds > 600,
"client budget ({seconds}s) must outlast the mesh frontend's 600s backend timeout"
);
}
#[test]
fn native_provider_preserves_explicit_llm_timeout() {
let mut env =
BTreeMap::from([("BUZZ_AGENT_LLM_TIMEOUT_SECS".to_string(), "90".to_string())]);
apply_relay_mesh_env(
&mut env,
Some(RELAY_MESH_PROVIDER_ID),
Some(RELAY_MESH_AUTO_MODEL_ID),
);
assert_eq!(
env.get("BUZZ_AGENT_LLM_TIMEOUT_SECS").map(String::as_str),
Some("90"),
"an explicit timeout is a user decision, not a value to re-default"
);
}
/// Same spawn-ordering hazard as the reply guard: without the copy-forward
/// in `relay_mesh_process_env`, the user's value is re-defaulted here.
#[test]
fn process_env_preserves_explicit_llm_timeout() {
let effective_env =
BTreeMap::from([("BUZZ_AGENT_LLM_TIMEOUT_SECS".to_string(), "90".to_string())]);
let env = relay_mesh_process_env(&effective_env, "Gemma-4");
assert_eq!(
env.get("BUZZ_AGENT_LLM_TIMEOUT_SECS").map(String::as_str),
Some("90")
);
}
#[test]
fn non_mesh_provider_leaves_llm_timeout_unset() {
let mut env = BTreeMap::new();
apply_relay_mesh_env(&mut env, Some("anthropic"), Some("claude-haiku-4.5"));
assert_eq!(
env.get("BUZZ_AGENT_LLM_TIMEOUT_SECS"),
None,
"remote providers keep buzz-agent's own default"
);
}
#[test]
fn non_mesh_provider_leaves_reply_guard_unset() {
let mut env = BTreeMap::new();

View File

@@ -545,22 +545,6 @@ pub(crate) fn spawn_pending_profile_reconciliations(app: &tauri::AppHandle, work
}
}
#[cfg(test)]
mod profile_reconcile_tests {
use super::profile_reconcile_completed;
use crate::commands::ProfileReconcileOutcome;
#[test]
fn skipped_reconciliation_never_retires_pending_work() {
assert!(profile_reconcile_completed(
ProfileReconcileOutcome::Reconciled
));
assert!(!profile_reconcile_completed(
ProfileReconcileOutcome::SkippedDisabled
));
}
}
#[cfg(feature = "mesh-llm")]
fn persist_restore_error(
app: &tauri::AppHandle,
@@ -578,3 +562,19 @@ fn persist_restore_error(
record.last_error = Some(error);
save_managed_agents(app, &records)
}
#[cfg(test)]
mod profile_reconcile_tests {
use super::profile_reconcile_completed;
use crate::commands::ProfileReconcileOutcome;
#[test]
fn skipped_reconciliation_never_retires_pending_work() {
assert!(profile_reconcile_completed(
ProfileReconcileOutcome::Reconciled
));
assert!(!profile_reconcile_completed(
ProfileReconcileOutcome::SkippedDisabled
));
}
}

View File

@@ -13,38 +13,50 @@ use mesh_llm_system::hardware;
use mesh_llm_system::vram::{format_rated_capacity, rated_capacity_gb};
/// Buzz-curated tier picks. These are the models we know survive the agent
/// harness on shared compute — deliberately non-reasoning instruction models,
/// so agents stay snappy instead of burning hidden reasoning tokens.
/// harness on shared compute.
///
/// The large pick is resolved through mesh-llm's remote catalog
/// (huggingface.co/datasets/meshllm/catalog), so it does not need to exist in
/// the compiled `MODEL_CATALOG`; the entry is synthesized below.
const CURATED_LARGE: &str = "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M";
const CURATED_LARGE_ALIAS: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M";
const CURATED_LARGE_SIZE: &str = "17GB";
const CURATED_LARGE_FILE: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf";
const CURATED_LARGE_DESCRIPTION: &str =
"Gemma 4 26B MoE (4B active) — Buzz default for 64GB+ machines";
/// The recommended ladder follows rated unified memory:
/// - below 32 GB: Gemma 4 E4B;
/// - 32 GB through the rated classes below 64 GB: Qwen3.5 9B;
/// - 64 GB and above: Qwen3.8 27B.
///
/// The Qwen entries are canonicalized from mesh-llm's compiled
/// `MODEL_CATALOG` rather than synthesized.
const CURATED_LARGE: &str = "unsloth/Qwen3.8-27B-GGUF:Q4_K_M";
const CURATED_LARGE_ALIAS: &str = "Qwen3.8-27B-Q4_K_M";
const CURATED_MEDIUM: &str = "unsloth/Qwen3.5-9B-GGUF:Q4_K_M";
const CURATED_MEDIUM_ALIAS: &str = "Qwen3.5-9B-Vision-Q4_K_M";
const CURATED_SMALL: &str = "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M";
const CURATED_SMALL_ALIAS: &str = "Gemma-4-E4B-it-Q4_K_M";
/// Rated-capacity boundary between the two curated tiers, in GB (marketing
/// capacity — a "64GB" Mac rates as 64 even though usable AI memory is less).
/// Superseded large alias retained only to preserve the historical string
/// canonicalization contract. Availability in a particular Mesh runtime is
/// determined by that runtime's compiled catalog.
const LEGACY_LARGE: &str = "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M";
const LEGACY_LARGE_ALIAS: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M";
/// Rated-capacity boundary for the balanced Qwen3.5 9B tier.
const CURATED_MEDIUM_MIN_RATED_GB: u64 = 32;
/// Qwen3.8 27B is recommended for 64 GB-and-larger rated capacity classes,
/// leaving substantial headroom beyond its observed working footprint for KV
/// cache and runtime use.
const CURATED_LARGE_MIN_RATED_GB: u64 = 64;
/// The Buzz-curated recommendation for a machine's rated memory capacity.
fn buzz_recommended_model(rated_gb: Option<u64>) -> &'static str {
match rated_gb {
Some(gb) if gb >= CURATED_LARGE_MIN_RATED_GB => CURATED_LARGE,
_ => CURATED_SMALL,
Some(gb) if gb >= CURATED_MEDIUM_MIN_RATED_GB => CURATED_MEDIUM,
Some(_) | None => CURATED_SMALL,
}
}
/// Convert Buzz's pre-0.74 curated package aliases into the canonical model
/// Convert Buzz's historical curated package aliases into the canonical model
/// ids advertised and accepted by Mesh's OpenAI ingress.
pub(crate) fn canonical_curated_model_id(model_id: &str) -> &str {
match model_id.trim() {
CURATED_SMALL_ALIAS => CURATED_SMALL,
CURATED_MEDIUM_ALIAS => CURATED_MEDIUM,
CURATED_LARGE_ALIAS => CURATED_LARGE,
LEGACY_LARGE_ALIAS => LEGACY_LARGE,
other => other,
}
}
@@ -172,31 +184,14 @@ fn build_catalog(
})
.collect();
// The compiled MODEL_CATALOG does not know the Buzz large pick; it
// resolves through mesh-llm's remote catalog at download time. Synthesize
// its entry so the picker can offer it.
if !entries.iter().any(|e| e.name == CURATED_LARGE) {
let size_gb = parse_size_gb(CURATED_LARGE_SIZE);
entries.push(MeshCatalogEntry {
fit: fit_code(size_gb, vram_gb),
installed: is_installed(CURATED_LARGE_FILE, CURATED_LARGE)
|| is_installed(CURATED_LARGE_FILE, CURATED_LARGE_ALIAS),
recommended: false,
curated: false,
name: CURATED_LARGE.to_string(),
size: CURATED_LARGE_SIZE.to_string(),
size_gb,
description: CURATED_LARGE_DESCRIPTION.to_string(),
});
}
let recommended = Some(buzz_recommended_model(rated_capacity_gb(vram_bytes)).to_string());
for entry in &mut entries {
entry.recommended = recommended.as_deref() == Some(entry.name.as_str());
// Both curated tiers are always offered: the recommended one for this
// machine plus the other pick (e.g. the small one as an explicit
// lighter choice on big machines).
entry.curated = entry.name == CURATED_LARGE || entry.name == CURATED_SMALL;
// All curated tiers are always offered: the recommendation plus
// lighter and heavier alternatives appropriate to other machine tiers.
entry.curated = entry.name == CURATED_LARGE
|| entry.name == CURATED_MEDIUM
|| entry.name == CURATED_SMALL;
}
entries.sort_by(|a, b| {
@@ -269,20 +264,31 @@ mod tests {
}
#[test]
fn recommendation_follows_buzz_curated_tiers() {
fn recommendation_follows_buzz_curated_ladder() {
assert_eq!(CURATED_SMALL, "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M");
assert_eq!(CURATED_LARGE, "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M");
// 64GB+ rated machines get the large curated pick.
assert_eq!(CURATED_MEDIUM, "unsloth/Qwen3.5-9B-GGUF:Q4_K_M");
assert_eq!(CURATED_LARGE, "unsloth/Qwen3.8-27B-GGUF:Q4_K_M");
// Qwen3.8 is recommended for 64 GB-and-larger rated capacity classes.
let large = build_catalog(None, 64_000_000_000, 64.0, &[]);
assert_eq!(large.recommended.as_deref(), Some(CURATED_LARGE));
let large_80 = build_catalog(None, 80_000_000_000, 80.0, &[]);
assert_eq!(large_80.recommended.as_deref(), Some(CURATED_LARGE));
let big = build_catalog(None, 128_000_000_000, 128.0, &[]);
assert_eq!(big.recommended.as_deref(), Some(CURATED_LARGE));
// Below the boundary: the small curated pick — never a reasoning
// model, never sub-4B guesswork.
let small = build_catalog(None, 32_000_000_000, 32.0, &[]);
// The balanced Qwen3.5 tier covers every rated class from 32 GB up
// to (but not including) 64 GB.
let medium = build_catalog(None, 32_000_000_000, 32.0, &[]);
assert_eq!(medium.recommended.as_deref(), Some(CURATED_MEDIUM));
let medium_max = build_catalog(None, 48_000_000_000, 48.0, &[]);
assert_eq!(medium_max.recommended.as_deref(), Some(CURATED_MEDIUM));
// Smaller and unknown machines use the light Gemma tier.
let small = build_catalog(None, 24_000_000_000, 24.0, &[]);
assert_eq!(small.recommended.as_deref(), Some(CURATED_SMALL));
let tiny = build_catalog(None, 16_000_000_000, 16.0, &[]);
assert_eq!(tiny.recommended.as_deref(), Some(CURATED_SMALL));
let unknown = build_catalog(None, 0, 0.0, &[]);
assert_eq!(unknown.recommended.as_deref(), Some(CURATED_SMALL));
}
#[test]
@@ -291,10 +297,15 @@ mod tests {
canonical_curated_model_id(CURATED_SMALL_ALIAS),
CURATED_SMALL
);
assert_eq!(
canonical_curated_model_id(CURATED_MEDIUM_ALIAS),
CURATED_MEDIUM
);
assert_eq!(
canonical_curated_model_id(CURATED_LARGE_ALIAS),
CURATED_LARGE
);
assert_eq!(canonical_curated_model_id(LEGACY_LARGE_ALIAS), LEGACY_LARGE);
assert_eq!(
canonical_curated_model_id("other/model:Q4"),
"other/model:Q4"
@@ -304,14 +315,17 @@ mod tests {
#[test]
fn curated_picks_lead_the_catalog() {
let catalog = build_catalog(None, 96_000_000_000, 96.0, &[]);
// Recommended curated entry first, the other curated pick second,
// advanced entries after.
// Recommended curated entry first, then the other two curated tiers,
// with advanced entries after them.
assert_eq!(catalog.entries[0].name, CURATED_LARGE);
assert!(catalog.entries[0].recommended && catalog.entries[0].curated);
assert_eq!(catalog.entries[1].name, CURATED_SMALL);
assert_eq!(catalog.entries[1].name, CURATED_MEDIUM);
assert!(catalog.entries[1].curated && !catalog.entries[1].recommended);
assert!(catalog.entries[2..].iter().all(|e| !e.curated));
// The synthesized large pick carries a real size for fit ranking.
assert_eq!(catalog.entries[2].name, CURATED_SMALL);
assert!(catalog.entries[2].curated && !catalog.entries[2].recommended);
assert!(catalog.entries[3..].iter().all(|e| !e.curated));
// The large pick comes from the compiled catalog with a real size, so
// fit ranking is meaningful rather than a placeholder.
assert!(catalog.entries[0].size_gb > 10.0);
}

View File

@@ -34,9 +34,9 @@ mod usage;
pub use usage::{serving_usage_from_payload, MeshServingUsage};
mod transport_policy;
use transport_policy::{iroh_relay_mode, sdk_iroh_relay_config, validate_advertised_endpoint};
#[cfg(test)]
use transport_policy::iroh_relay_mode_from;
use transport_policy::{iroh_relay_mode, validate_advertised_endpoint, IrohRelayMode};
use transport_policy::{iroh_relay_mode_from, IrohRelayMode};
use mesh_llm_sdk::{client, serve, EmbeddedNodeHandle, MeshDiscoveryMode, TrustPolicy};
use serde::{Deserialize, Serialize};
@@ -376,13 +376,10 @@ impl DesktopMeshRuntime {
if let Some(mesh_name) = request.mesh_name.as_deref() {
builder = builder.mesh_name(mesh_name);
}
builder = match iroh_relay_mode()? {
IrohRelayMode::Disabled => builder.disable_iroh_relays(true),
IrohRelayMode::Default => builder.disable_iroh_relays(false),
IrohRelayMode::Custom(urls) => builder
.disable_iroh_relays(false)
.iroh_relays(urls.into_iter().map(|url| url.to_string())),
};
let (disable_iroh_relays, iroh_relays) = sdk_iroh_relay_config(iroh_relay_mode()?);
builder = builder
.disable_iroh_relays(disable_iroh_relays)
.iroh_relays(iroh_relays);
if let Some(max_vram_gb) = request.max_vram_gb {
builder = builder.max_vram_gb(max_vram_gb as f64);
}
@@ -416,13 +413,10 @@ impl DesktopMeshRuntime {
if let Some(mesh_name) = request.mesh_name.as_deref() {
builder = builder.mesh_name(mesh_name);
}
builder = match iroh_relay_mode()? {
IrohRelayMode::Disabled => builder.disable_iroh_relays(true),
IrohRelayMode::Default => builder.disable_iroh_relays(false),
IrohRelayMode::Custom(urls) => builder
.disable_iroh_relays(false)
.iroh_relays(urls.into_iter().map(|url| url.to_string())),
};
let (disable_iroh_relays, iroh_relays) = sdk_iroh_relay_config(iroh_relay_mode()?);
builder = builder
.disable_iroh_relays(disable_iroh_relays)
.iroh_relays(iroh_relays);
if let Some(join_token) = request.join_token.as_deref() {
builder = builder.join_token(join_token);
}

View File

@@ -443,6 +443,7 @@ mod tests {
id: pubkey.to_string(),
display_name: pubkey.to_string(),
avatar_url: None,
description: None,
system_prompt: String::new(),
runtime: None,
model: None,

View File

@@ -43,6 +43,22 @@ pub(super) fn iroh_relay_mode_from(raw: Option<&str>) -> anyhow::Result<IrohRela
}
}
pub(super) fn sdk_iroh_relay_config(mode: IrohRelayMode) -> (bool, Vec<String>) {
match mode {
IrohRelayMode::Disabled => (true, Vec::new()),
IrohRelayMode::Default => (
false,
MESH_LLM_DEFAULT_RELAYS
.iter()
.map(|url| (*url).to_string())
.collect(),
),
IrohRelayMode::Custom(urls) => {
(false, urls.into_iter().map(|url| url.to_string()).collect())
}
}
}
fn parse_configured_relay_url(raw: &str) -> anyhow::Result<RelayUrl> {
let parsed = url::Url::parse(raw)
.map_err(|error| anyhow::anyhow!("invalid relay URL {raw:?}: {error}"))?;
@@ -205,9 +221,11 @@ fn validate_transport(transport: &TransportAddr, mode: &IrohRelayMode) -> anyhow
/// mesh-llm serving node (they are not in iroh's own prod relay map).
///
/// Kept in sync with `effective_relay_urls(RelayPolicy::DefaultPublic, &[])`.
const MESH_LLM_DEFAULT_RELAYS: &[&str] = &[
pub(super) const MESH_LLM_DEFAULT_RELAYS: &[&str] = &[
"https://usw1-2.relay.michaelneale.mesh-llm.iroh.link./",
"https://aps1-1.relay.michaelneale.mesh-llm.iroh.link./",
"https://euc1-1.relay.michaelneale.mesh-llm.iroh.link./",
"https://use1-1.relay.michaelneale.mesh-llm.iroh.link./",
];
/// Whether `relay` is one of mesh-llm's baked-in default public relays.
@@ -327,6 +345,27 @@ mod tests {
}
}
#[test]
fn sdk_default_relays_are_exactly_the_trusted_default_relays() {
let (disabled, configured) = sdk_iroh_relay_config(IrohRelayMode::Default);
assert!(!disabled);
let configured = configured
.into_iter()
.map(|url| url.parse::<RelayUrl>().expect("SDK relay URL must parse"))
.collect::<Vec<_>>();
let trusted = MESH_LLM_DEFAULT_RELAYS
.iter()
.map(|url| {
url.parse::<RelayUrl>()
.expect("trusted relay URL must parse")
})
.collect::<Vec<_>>();
assert_eq!(configured, trusted);
assert!(configured
.iter()
.all(|relay| relay_allowed(relay, &IrohRelayMode::Default)));
}
#[test]
fn endpoint_with_one_good_and_one_junk_candidate_is_sanitized() {
// A mesh-llm endpoint can advertise a usable relay alongside an