Fix OpenCode model discovery when npm is omitted (#65872)

Fixes #65710.

OpenCode's catalog includes per-model provider overrides containing only
an API URL. Make the per-model npm transport optional and inherit the
parent provider's transport when it is absent, while preserving explicit
per-model overrides.

Add regression coverage for transport inheritance, API-only provider
overrides, and explicit transport overrides for both Zen and Go.

Release Notes:

- Fixed OpenCode models failing to load with a missing field `npm`
error.
This commit is contained in:
Bennet Bo Fenner 2026-10-20 08:53:34 +00:00 • committed by GitHub
parent c4a82ab1ab
commit 294169aeed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1063,7 +1063,7 @@ struct ModelLimits {
#[derive(Deserialize)]
struct ModelTransport {
npm: String,
npm: Option<String>,
}
#[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(