feat(inference): register burst-v1 tier; SuperContext scout uses it (#4260)

This commit is contained in:
Steven Enamakel
2026-06-29 10:18:39 -07:00
committed by GitHub
parent 59502c21e2
commit 300d70aa42
11 changed files with 113 additions and 18 deletions
@@ -39,6 +39,7 @@ The router wraps several pre-created providers (Anthropic, OpenAI, Google, Groq,
| `hint:vision` | A vision-capable model | Screenshots, image attachments, OCR | | `hint:vision` | A vision-capable model | Screenshots, image attachments, OCR |
| `hint:summarize` | A model good at compression | Memory tree summary builders | | `hint:summarize` | A model good at compression | Memory tree summary builders |
| `hint:code` | A code-tuned model | Native coder turns | | `hint:code` | A code-tuned model | Native coder turns |
| `hint:burst` | A high-throughput, low-cost model | Cheap, latency-tolerant pre-flight sweeps — e.g. the SuperContext scout |
The exact mappings are configurable; the defaults ship sensible per-provider routes. The exact mappings are configurable; the defaults ship sensible per-provider routes.
+2
View File
@@ -44,6 +44,8 @@ New thread, first message
Because the scout is **read-only**, it can never take an action on a fresh thread — it only reads and summarizes. And because it runs in the harness rather than as an optional tool, the redundant `agent_prepare_context` tool is suppressed for that turn, so the agent doesn't do the work twice. Because the scout is **read-only**, it can never take an action on a fresh thread — it only reads and summarizes. And because it runs in the harness rather than as an optional tool, the redundant `agent_prepare_context` tool is suppressed for that turn, so the agent doesn't do the work twice.
The scout runs on the **`burst` tier** (`hint = "burst"``burst-v1` on the managed backend) — a cheap, high-throughput, non-reasoning model. The sweep is a latency-tolerant pre-flight pass, so raw throughput on a fast model is a better fit than the pricier agentic/reasoning tiers. See [Automatic Model Routing](model-routing/README.md).
*** ***
## Safety and robustness ## Safety and robustness
+20
View File
@@ -101,6 +101,15 @@ pub const PRICING_TABLE: &[ModelPricing] = &[
cached_input_per_mtok_usd: 0.003625, cached_input_per_mtok_usd: 0.003625,
output_per_mtok_usd: 0.87, output_per_mtok_usd: 0.87,
}, },
// Burst tier — high-throughput, low-cost model; flat rate both directions,
// no prompt cache (so cached rate mirrors the input rate). Used by the
// SuperContext scout.
ModelPricing {
model: "burst-v1",
input_per_mtok_usd: 0.208,
cached_input_per_mtok_usd: 0.208,
output_per_mtok_usd: 0.208,
},
// Vision tier — multimodal; estimate only. The backend's echoed // Vision tier — multimodal; estimate only. The backend's echoed
// `charged_amount_usd` is authoritative when present. // `charged_amount_usd` is authoritative when present.
ModelPricing { ModelPricing {
@@ -252,6 +261,17 @@ mod tests {
assert_eq!(p.output_per_mtok_usd, 15.0); assert_eq!(p.output_per_mtok_usd, 15.0);
} }
#[test]
fn lookup_pricing_has_a_burst_row() {
// The burst tier (SuperContext scout) must price from its own row —
// NOT via the $3/$15 fallback, which would inflate first-turn scout cost
// and could trip budget gates.
let p = lookup_pricing("burst-v1");
assert_eq!(p.model, "burst-v1");
assert_eq!(p.input_per_mtok_usd, 0.208);
assert_eq!(p.output_per_mtok_usd, 0.208);
}
#[test] #[test]
fn lookup_pricing_falls_back_for_unknown_model() { fn lookup_pricing_falls_back_for_unknown_model() {
let p = lookup_pricing("totally-unknown-model"); let p = lookup_pricing("totally-unknown-model");
@@ -32,9 +32,11 @@ omit_profile = false
omit_memory_md = false omit_memory_md = false
[model] [model]
# Multi-step gathering loop (recall → maybe fetch → assess), so the agentic # Multi-step gathering loop (recall → maybe fetch → assess). Rides the
# tier fits — not the slow reasoning tier (the scout is meant to be cheap). # high-throughput `burst` tier: the scout is a cheap, latency-tolerant,
hint = "agentic" # non-reasoning pre-flight pass, so raw throughput on a fast model beats the
# pricier agentic/reasoning tiers. Resolves to `burst-v1` on the managed backend.
hint = "burst"
[tools] [tools]
# Curated read-only context-gathering surface. No writes, no shell, no # Curated read-only context-gathering surface. No writes, no shell, no
@@ -988,6 +988,14 @@ mod tests {
let def = find("context_scout"); let def = find("context_scout");
assert_eq!(def.agent_tier, AgentTier::Worker); assert_eq!(def.agent_tier, AgentTier::Worker);
assert_eq!(def.sandbox_mode, SandboxMode::ReadOnly); assert_eq!(def.sandbox_mode, SandboxMode::ReadOnly);
// Super-context scout rides the cheap, high-throughput `burst` tier
// (resolves to `burst-v1` on the managed backend) — not the pricier
// agentic/reasoning tiers.
assert!(
matches!(&def.model, ModelSpec::Hint(h) if h == "burst"),
"context_scout must spawn on the burst tier, got {:?}",
def.model
);
// Bundle cap — load-bearing for the parent's context budget. Leaves // Bundle cap — load-bearing for the parent's context budget. Leaves
// room for the `recommended_skills` block alongside summary + plan. // room for the `recommended_skills` block alongside summary + plan.
assert_eq!(def.max_result_chars, Some(5000)); assert_eq!(def.max_result_chars, Some(5000));
+4 -4
View File
@@ -48,10 +48,10 @@ pub use schema::{
StorageProviderSection, StreamMode, TeamModelConfig, TelegramConfig, TokenjuiceConfig, StorageProviderSection, StreamMode, TeamModelConfig, TelegramConfig, TokenjuiceConfig,
UpdateConfig, UpdateRestartStrategy, VoiceActivationMode, VoiceServerConfig, WebSearchConfig, UpdateConfig, UpdateRestartStrategy, VoiceActivationMode, VoiceServerConfig, WebSearchConfig,
WebhookConfig, DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MEMORY_SYNC_INTERVAL_SECS, DEFAULT_MODEL, WebhookConfig, DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MEMORY_SYNC_INTERVAL_SECS, DEFAULT_MODEL,
MEMORY_SYNC_INTERVAL_PRESETS_SECS, MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MEMORY_SYNC_INTERVAL_PRESETS_SECS, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
SEARCH_ENGINE_BRAVE, SEARCH_ENGINE_DISABLED, SEARCH_ENGINE_MANAGED, SEARCH_ENGINE_PARALLEL, MODEL_VISION_V1, SEARCH_ENGINE_BRAVE, SEARCH_ENGINE_DISABLED, SEARCH_ENGINE_MANAGED,
SEARCH_ENGINE_QUERIT, SEARCH_ENGINE_PARALLEL, SEARCH_ENGINE_QUERIT,
}; };
pub use schemas::{ pub use schemas::{
all_controller_schemas as all_config_controller_schemas, all_controller_schemas as all_config_controller_schemas,
+9 -1
View File
@@ -150,7 +150,7 @@ impl Default for CostConfig {
/// Default pricing for popular models (USD per 1M tokens) /// Default pricing for popular models (USD per 1M tokens)
fn get_default_pricing() -> HashMap<String, ModelPricing> { fn get_default_pricing() -> HashMap<String, ModelPricing> {
use super::types::{ use super::types::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1, MODEL_REASONING_V1,
}; };
@@ -192,6 +192,14 @@ fn get_default_pricing() -> HashMap<String, ModelPricing> {
output: 3.30, output: 3.30,
}, },
); );
// Burst tier — high-throughput, low-cost model; flat rate both directions.
prices.insert(
MODEL_BURST_V1.into(),
ModelPricing {
input: 0.208,
output: 0.208,
},
);
prices prices
} }
+4
View File
@@ -14,6 +14,10 @@ pub const MODEL_CHAT_V1: &str = "chat-v1";
/// Legacy low-latency chat tier slug retained for older persisted configs. /// Legacy low-latency chat tier slug retained for older persisted configs.
pub const MODEL_REASONING_QUICK_V1: &str = "reasoning-quick-v1"; pub const MODEL_REASONING_QUICK_V1: &str = "reasoning-quick-v1";
pub const MODEL_CODING_V1: &str = "coding-v1"; pub const MODEL_CODING_V1: &str = "coding-v1";
/// High-throughput "burst" tier served by the managed backend. Cheap, fast,
/// non-reasoning, text-only, 128k context, no prompt cache; used by the
/// super-context scout. Managed-backend only (no BYOK knob).
pub const MODEL_BURST_V1: &str = "burst-v1";
pub const MODEL_SUMMARIZATION_V1: &str = "summarization-v1"; pub const MODEL_SUMMARIZATION_V1: &str = "summarization-v1";
/// Multimodal (image-input) tier. Managed backend serves this with the vision /// Multimodal (image-input) tier. Managed backend serves this with the vision
/// flag enabled; the vision sub-agent rides this tier via `hint:vision`. /// flag enabled; the vision sub-agent rides this tier via `hint:vision`.
+8 -1
View File
@@ -6,7 +6,8 @@
//! metadata is not yet available. //! metadata is not yet available.
use crate::openhuman::config::{ use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1,
}; };
/// Conservative default for OpenHuman abstract tier models (tokens). /// Conservative default for OpenHuman abstract tier models (tokens).
@@ -126,6 +127,9 @@ fn tier_context_window(model: &str) -> Option<u64> {
MODEL_REASONING_V1 => Some(TIER_REASONING_CONTEXT), MODEL_REASONING_V1 => Some(TIER_REASONING_CONTEXT),
MODEL_AGENTIC_V1 | MODEL_CODING_V1 => Some(TIER_LARGE_CONTEXT), MODEL_AGENTIC_V1 | MODEL_CODING_V1 => Some(TIER_LARGE_CONTEXT),
"summarization-v1" => Some(TIER_SUMMARIZATION_CONTEXT), "summarization-v1" => Some(TIER_SUMMARIZATION_CONTEXT),
// Burst tier advertises a 128k window on the managed backend. Matched on
// the `burst-v1` alias before any substring fallbacks below.
MODEL_BURST_V1 => Some(TIER_STANDARD_CONTEXT),
MODEL_CHAT_V1 | MODEL_REASONING_QUICK_V1 | "chat" => Some(TIER_STANDARD_CONTEXT), MODEL_CHAT_V1 | MODEL_REASONING_QUICK_V1 | "chat" => Some(TIER_STANDARD_CONTEXT),
m if m.starts_with("gemma") || m.contains(":1b") || m.contains("270m") => { m if m.starts_with("gemma") || m.contains(":1b") || m.contains("270m") => {
Some(TIER_LOCAL_CONTEXT) Some(TIER_LOCAL_CONTEXT)
@@ -277,6 +281,9 @@ mod tests {
assert_eq!(context_window_for_model("reasoning-v1"), Some(1_000_000)); assert_eq!(context_window_for_model("reasoning-v1"), Some(1_000_000));
assert_eq!(context_window_for_model("agentic-v1"), Some(200_000)); assert_eq!(context_window_for_model("agentic-v1"), Some(200_000));
assert_eq!(context_window_for_model("chat-v1"), Some(128_000)); assert_eq!(context_window_for_model("chat-v1"), Some(128_000));
// Burst tier — 128k on the managed backend. Matched on the alias, not
// the local-gemma 8k substring arm.
assert_eq!(context_window_for_model("burst-v1"), Some(128_000));
assert_eq!( assert_eq!(
context_window_for_model("reasoning-quick-v1"), context_window_for_model("reasoning-quick-v1"),
Some(128_000) Some(128_000)
+32 -7
View File
@@ -77,7 +77,7 @@ pub(crate) const NO_MODEL_CONFIGURED_ANCHOR: &str = "resolved to an empty model
fn is_abstract_tier_model(model: &str) -> bool { fn is_abstract_tier_model(model: &str) -> bool {
use crate::openhuman::config::{ use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
}; };
let trimmed = model.trim(); let trimmed = model.trim();
@@ -86,6 +86,7 @@ fn is_abstract_tier_model(model: &str) -> bool {
|| trimmed == MODEL_CHAT_V1 || trimmed == MODEL_CHAT_V1
|| trimmed == MODEL_AGENTIC_V1 || trimmed == MODEL_AGENTIC_V1
|| trimmed == MODEL_CODING_V1 || trimmed == MODEL_CODING_V1
|| trimmed == MODEL_BURST_V1
|| trimmed == MODEL_VISION_V1 || trimmed == MODEL_VISION_V1
|| trimmed == MODEL_SUMMARIZATION_V1 || trimmed == MODEL_SUMMARIZATION_V1
} }
@@ -109,6 +110,7 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
("chat", crate::openhuman::config::MODEL_CHAT_V1), ("chat", crate::openhuman::config::MODEL_CHAT_V1),
("agentic", crate::openhuman::config::MODEL_AGENTIC_V1), ("agentic", crate::openhuman::config::MODEL_AGENTIC_V1),
("coding", crate::openhuman::config::MODEL_CODING_V1), ("coding", crate::openhuman::config::MODEL_CODING_V1),
("burst", crate::openhuman::config::MODEL_BURST_V1),
("vision", crate::openhuman::config::MODEL_VISION_V1), ("vision", crate::openhuman::config::MODEL_VISION_V1),
( (
"summarization", "summarization",
@@ -125,6 +127,7 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
(crate::openhuman::config::MODEL_REASONING_QUICK_V1, "chat"), (crate::openhuman::config::MODEL_REASONING_QUICK_V1, "chat"),
(crate::openhuman::config::MODEL_AGENTIC_V1, "agentic"), (crate::openhuman::config::MODEL_AGENTIC_V1, "agentic"),
(crate::openhuman::config::MODEL_CODING_V1, "coding"), (crate::openhuman::config::MODEL_CODING_V1, "coding"),
(crate::openhuman::config::MODEL_BURST_V1, "burst"),
(crate::openhuman::config::MODEL_VISION_V1, "vision"), (crate::openhuman::config::MODEL_VISION_V1, "vision"),
( (
crate::openhuman::config::MODEL_SUMMARIZATION_V1, crate::openhuman::config::MODEL_SUMMARIZATION_V1,
@@ -184,7 +187,7 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
/// to the backend. /// to the backend.
pub(crate) fn is_known_openhuman_tier(model: &str) -> bool { pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
use crate::openhuman::config::{ use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
}; };
matches!( matches!(
@@ -193,6 +196,7 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
| MODEL_CHAT_V1 | MODEL_CHAT_V1
| MODEL_AGENTIC_V1 | MODEL_AGENTIC_V1
| MODEL_CODING_V1 | MODEL_CODING_V1
| MODEL_BURST_V1
| MODEL_REASONING_QUICK_V1 | MODEL_REASONING_QUICK_V1
| MODEL_SUMMARIZATION_V1 | MODEL_SUMMARIZATION_V1
| MODEL_VISION_V1 | MODEL_VISION_V1
@@ -200,6 +204,7 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
| "hint:chat" | "hint:chat"
| "hint:agentic" | "hint:agentic"
| "hint:coding" | "hint:coding"
| "hint:burst"
| "hint:summarization" | "hint:summarization"
| "hint:vision" | "hint:vision"
) )
@@ -219,7 +224,7 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
/// ([`crate::openhuman::inference::model_context::model_vision_enabled`]). /// ([`crate::openhuman::inference::model_context::model_vision_enabled`]).
pub(crate) fn oh_tier_supports_vision(model: &str) -> bool { pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
use crate::openhuman::config::{ use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, MODEL_VISION_V1,
}; };
match model { match model {
@@ -231,6 +236,8 @@ pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
MODEL_REASONING_QUICK_V1 => false, MODEL_REASONING_QUICK_V1 => false,
MODEL_AGENTIC_V1 | "hint:agentic" => false, MODEL_AGENTIC_V1 | "hint:agentic" => false,
MODEL_CODING_V1 | "hint:coding" => false, MODEL_CODING_V1 | "hint:coding" => false,
// Burst is a text-only tier.
MODEL_BURST_V1 | "hint:burst" => false,
MODEL_SUMMARIZATION_V1 | "hint:summarization" => false, MODEL_SUMMARIZATION_V1 | "hint:summarization" => false,
_ => false, _ => false,
} }
@@ -259,6 +266,11 @@ pub fn provider_for_role(role: &str, config: &Config) -> String {
"reasoning" => config.reasoning_provider.as_deref(), "reasoning" => config.reasoning_provider.as_deref(),
"agentic" => config.agentic_provider.as_deref(), "agentic" => config.agentic_provider.as_deref(),
"coding" => config.coding_provider.as_deref(), "coding" => config.coding_provider.as_deref(),
// Burst is managed-backend only (no per-workload provider knob): it rides
// the hosted high-throughput tier. Unset → falls through to
// `primary_cloud` (→ managed `burst-v1`) like the other tier-specific
// background workloads.
"burst" => None,
// Tier-specific multimodal model; like `agentic` it is NOT part of the // Tier-specific multimodal model; like `agentic` it is NOT part of the
// chat-tier BYOK inheritance below — when unset it falls through to // chat-tier BYOK inheritance below — when unset it falls through to
// `primary_cloud` (→ managed `vision-v1`). // `primary_cloud` (→ managed `vision-v1`).
@@ -299,13 +311,19 @@ pub fn provider_for_role(role: &str, config: &Config) -> String {
if !matches!(role, "chat" | "reasoning" | "coding") { if !matches!(role, "chat" | "reasoning" | "coding") {
if let Some(chat) = config.chat_provider.as_deref() { if let Some(chat) = config.chat_provider.as_deref() {
if crate::openhuman::inference::local::profile::is_local_provider_string(chat) { if crate::openhuman::inference::local::profile::is_local_provider_string(chat) {
// burst is managed-backend only — there is no `burst_provider`
// knob, so don't suggest setting one.
let override_hint = if role == "burst" {
"managed-backend only; no per-workload override".to_string()
} else {
format!("set {role}_provider explicitly to override")
};
log::info!( log::info!(
"[providers][local-fallback] role={} using managed backend (chat is \ "[providers][local-fallback] role={} using managed backend (chat is \
local '{}' but background workloads require cloud — set \ local '{}' but background workloads require cloud — {})",
{}_provider explicitly to override)",
role, role,
chat, chat,
role override_hint
); );
} }
} }
@@ -871,12 +889,18 @@ pub(crate) fn create_local_chat_provider_from_string(
/// every attached image — leaving the managed vision sub-agent blind. /// every attached image — leaving the managed vision sub-agent blind.
fn managed_tier_for_role(role: &str) -> Option<&'static str> { fn managed_tier_for_role(role: &str) -> Option<&'static str> {
use crate::openhuman::config::{ use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_V1, MODEL_VISION_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_V1,
MODEL_VISION_V1,
}; };
match role { match role {
"reasoning" => Some(MODEL_REASONING_V1), "reasoning" => Some(MODEL_REASONING_V1),
"agentic" => Some(MODEL_AGENTIC_V1), "agentic" => Some(MODEL_AGENTIC_V1),
"coding" => Some(MODEL_CODING_V1), "coding" => Some(MODEL_CODING_V1),
// Burst rides the managed backend's high-throughput tier. Pinned here
// (rather than collapsing to `default_model`) so the `hint = "burst"`
// sub-agent — the super-context scout — actually reaches `burst-v1`.
// There is no `burst_provider` knob: burst is managed-only.
"burst" => Some(MODEL_BURST_V1),
"vision" => Some(MODEL_VISION_V1), "vision" => Some(MODEL_VISION_V1),
// Background subconscious tick/triage: pinned to the lightweight chat // Background subconscious tick/triage: pinned to the lightweight chat
// tier (see the doc above for why it is pinned despite being background). // tier (see the doc above for why it is pinned despite being background).
@@ -977,6 +1001,7 @@ fn make_openhuman_backend(
Some("chat") => crate::openhuman::config::MODEL_CHAT_V1.to_string(), Some("chat") => crate::openhuman::config::MODEL_CHAT_V1.to_string(),
Some("agentic") => crate::openhuman::config::MODEL_AGENTIC_V1.to_string(), Some("agentic") => crate::openhuman::config::MODEL_AGENTIC_V1.to_string(),
Some("coding") => crate::openhuman::config::MODEL_CODING_V1.to_string(), Some("coding") => crate::openhuman::config::MODEL_CODING_V1.to_string(),
Some("burst") => crate::openhuman::config::MODEL_BURST_V1.to_string(),
Some("summarization") => crate::openhuman::config::MODEL_SUMMARIZATION_V1.to_string(), Some("summarization") => crate::openhuman::config::MODEL_SUMMARIZATION_V1.to_string(),
Some("vision") => crate::openhuman::config::MODEL_VISION_V1.to_string(), Some("vision") => crate::openhuman::config::MODEL_VISION_V1.to_string(),
Some(_) => { Some(_) => {
@@ -404,7 +404,7 @@ fn create_chat_provider_uses_role() {
#[test] #[test]
fn managed_backend_pins_specialised_role_to_tier() { fn managed_backend_pins_specialised_role_to_tier() {
use crate::openhuman::config::{ use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_V1, MODEL_VISION_V1, MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CODING_V1, MODEL_REASONING_V1, MODEL_VISION_V1,
}; };
// default_model is chat-v1 — the value the buggy path would have leaked. // default_model is chat-v1 — the value the buggy path would have leaked.
let config = Config::default(); let config = Config::default();
@@ -414,6 +414,7 @@ fn managed_backend_pins_specialised_role_to_tier() {
("reasoning", MODEL_REASONING_V1), ("reasoning", MODEL_REASONING_V1),
("agentic", MODEL_AGENTIC_V1), ("agentic", MODEL_AGENTIC_V1),
("coding", MODEL_CODING_V1), ("coding", MODEL_CODING_V1),
("burst", MODEL_BURST_V1),
("vision", MODEL_VISION_V1), ("vision", MODEL_VISION_V1),
] { ] {
let (_, model) = create_chat_provider_from_string(role, "openhuman", &config) let (_, model) = create_chat_provider_from_string(role, "openhuman", &config)
@@ -481,12 +482,16 @@ fn managed_backend_summarization_ignores_cloud_llm_model_override() {
// `code_executor` agent (`hint = "coding"`) makes when it spawns. // `code_executor` agent (`hint = "coding"`) makes when it spawns.
#[test] #[test]
fn subagent_hint_resolves_to_tier_on_managed_backend() { fn subagent_hint_resolves_to_tier_on_managed_backend() {
use crate::openhuman::config::{MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_V1}; use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_BURST_V1, MODEL_CODING_V1, MODEL_REASONING_V1,
};
let config = Config::default(); let config = Config::default();
for (hint, expected_tier) in &[ for (hint, expected_tier) in &[
("coding", MODEL_CODING_V1), ("coding", MODEL_CODING_V1),
("agentic", MODEL_AGENTIC_V1), ("agentic", MODEL_AGENTIC_V1),
("reasoning", MODEL_REASONING_V1), ("reasoning", MODEL_REASONING_V1),
// The super-context scout (`context_scout`) spawns with `hint = "burst"`.
("burst", MODEL_BURST_V1),
] { ] {
let (_, model) = let (_, model) =
create_chat_provider(hint, &config).expect("create_chat_provider must succeed"); create_chat_provider(hint, &config).expect("create_chat_provider must succeed");
@@ -1010,10 +1015,23 @@ fn known_hints_pass() {
assert!(is_known_openhuman_tier("hint:chat")); assert!(is_known_openhuman_tier("hint:chat"));
assert!(is_known_openhuman_tier("hint:agentic")); assert!(is_known_openhuman_tier("hint:agentic"));
assert!(is_known_openhuman_tier("hint:coding")); assert!(is_known_openhuman_tier("hint:coding"));
assert!(is_known_openhuman_tier("hint:burst"));
assert!(is_known_openhuman_tier("hint:summarization")); assert!(is_known_openhuman_tier("hint:summarization"));
assert!(is_known_openhuman_tier("hint:vision")); assert!(is_known_openhuman_tier("hint:vision"));
} }
// `hint:burst` is accepted by `is_known_openhuman_tier`, so it must also be
// translated to `burst-v1` by the managed backend — otherwise a saved
// `default_model = "hint:burst"` would be forwarded literally and 400.
#[test]
fn managed_backend_translates_hint_burst_to_burst_tier() {
let mut config = Config::default();
config.default_model = Some("hint:burst".to_string());
let (_, model) = create_chat_provider_from_string("chat", "openhuman", &config)
.expect("managed backend must build");
assert_eq!(model, crate::openhuman::config::MODEL_BURST_V1);
}
#[test] #[test]
fn invalid_models_fail() { fn invalid_models_fail() {
assert!(!is_known_openhuman_tier("deepseek-v4-pro")); assert!(!is_known_openhuman_tier("deepseek-v4-pro"));