Skip to content
Merged
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
91 changes: 91 additions & 0 deletions crates/goose/src/providers/openrouter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,95 @@ impl Provider for OpenRouterProvider {
emit_debug_trace(&self.model, &payload, &response, &usage);
Ok((message, ProviderUsage::new(model, usage)))
}

/// Fetch supported models from OpenRouter API (only models with tool support)
async fn fetch_supported_models_async(&self) -> Result<Option<Vec<String>>, ProviderError> {
let base_url = Url::parse(&self.host)
.map_err(|e| ProviderError::RequestFailed(format!("Invalid base URL: {e}")))?;
let url = base_url.join("api/v1/models").map_err(|e| {
ProviderError::RequestFailed(format!("Failed to construct models URL: {e}"))
})?;

// Handle request failures gracefully
// If the request fails, fall back to manual entry
let response = match self
.client
.get(url)
.header("Authorization", format!("Bearer {}", self.api_key))
.header("HTTP-Referer", "https://block.github.io/goose")
.header("X-Title", "Goose")
.send()
.await
{
Ok(response) => response,
Err(e) => {
tracing::warn!("Failed to fetch models from OpenRouter API: {}, falling back to manual model entry", e);
return Ok(None);
}
};

// Handle JSON parsing failures gracefully
let json: serde_json::Value = match response.json().await {
Ok(json) => json,
Err(e) => {
tracing::warn!("Failed to parse OpenRouter API response as JSON: {}, falling back to manual model entry", e);
return Ok(None);
}
};

// Check for error in response
if let Some(err_obj) = json.get("error") {
let msg = err_obj
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
tracing::warn!("OpenRouter API returned an error: {}", msg);
return Ok(None);
}

let data = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
ProviderError::UsageError("Missing data field in JSON response".into())
})?;

let mut models: Vec<String> = data
.iter()
.filter_map(|model| {
// Get the model ID
let id = model.get("id").and_then(|v| v.as_str())?;

// Check if the model supports tools
let supported_params =
match model.get("supported_parameters").and_then(|v| v.as_array()) {
Some(params) => params,
None => {
// If supported_parameters is missing, skip this model (assume no tool support)
tracing::debug!(
"Model '{}' missing supported_parameters field, skipping",
id
);
return None;
}
};

let has_tool_support = supported_params
.iter()
.any(|param| param.as_str() == Some("tools"));

if has_tool_support {
Some(id.to_string())
} else {
None
}
})
.collect();

// If no models with tool support were found, fall back to manual entry
if models.is_empty() {
tracing::warn!("No models with tool support found in OpenRouter API response, falling back to manual model entry");
return Ok(None);
}

models.sort();
Ok(Some(models))
}
}
Loading