diff --git a/crates/language_models/src/provider/opencode.rs b/crates/language_models/src/provider/opencode.rs index 1db81bb6ae2..3f2e954d722 100644 --- a/crates/language_models/src/provider/opencode.rs +++ b/crates/language_models/src/provider/opencode.rs @@ -1063,7 +1063,7 @@ struct ModelLimits { #[derive(Deserialize)] struct ModelTransport { - npm: String, + npm: Option, } #[derive(Deserialize)] @@ -1117,7 +1117,7 @@ fn parse_discovered_models( let transport = metadata .provider .as_ref() - .map(|provider| provider.npm.as_str()) + .and_then(|provider| provider.npm.as_deref()) .or(provider.npm.as_deref()); let protocol = transport.and_then(protocol_for_transport)?; let reasoning_effort_levels = metadata @@ -1833,6 +1833,50 @@ mod tests { Ok(()) } + #[test] + fn test_discovery_inherits_transport_unless_model_overrides_it() -> Result<()> { + for (model_provider, expected_protocol) in [ + (serde_json::Value::Null, ApiProtocol::OpenAiChat), + ( + serde_json::json!({"api": "https://opencode.ai/inference/openai/v1"}), + ApiProtocol::OpenAiChat, + ), + ( + serde_json::json!({"npm": "@ai-sdk/anthropic"}), + ApiProtocol::Anthropic, + ), + ] { + let provider = serde_json::json!({ + "npm": "@ai-sdk/openai-compatible", + "models": { + "model": { + "id": "model", + "name": "Model", + "tool_call": true, + "modalities": {"output": ["text"]}, + "limit": {"context": 1000}, + "provider": model_provider, + }, + }, + }); + let catalog = serde_json::json!({ + "opencode": provider, + "opencode-go": provider, + }) + .to_string(); + for subscription in [OpenCodeSubscription::Zen, OpenCodeSubscription::Go] { + let models = parse_discovered_models(&catalog, subscription)?; + assert_eq!(models.len(), 1, "{subscription:?}: {model_provider}"); + assert_eq!( + models[0].model.protocol(), + expected_protocol, + "{subscription:?}: {model_provider}", + ); + } + } + Ok(()) + } + #[test] fn test_parse_discovered_models_uses_catalog_entries_directly() { let models = parse_discovered_models(