Skip to content

Commit 5db89aa

Browse files
feat: fetch openrouter supported models in goose configure (aaif-goose#3347)
1 parent ad1e322 commit 5db89aa

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

crates/goose/src/providers/openrouter.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,95 @@ impl Provider for OpenRouterProvider {
278278
emit_debug_trace(&self.model, &payload, &response, &usage);
279279
Ok((message, ProviderUsage::new(model, usage)))
280280
}
281+
282+
/// Fetch supported models from OpenRouter API (only models with tool support)
283+
async fn fetch_supported_models_async(&self) -> Result<Option<Vec<String>>, ProviderError> {
284+
let base_url = Url::parse(&self.host)
285+
.map_err(|e| ProviderError::RequestFailed(format!("Invalid base URL: {e}")))?;
286+
let url = base_url.join("api/v1/models").map_err(|e| {
287+
ProviderError::RequestFailed(format!("Failed to construct models URL: {e}"))
288+
})?;
289+
290+
// Handle request failures gracefully
291+
// If the request fails, fall back to manual entry
292+
let response = match self
293+
.client
294+
.get(url)
295+
.header("Authorization", format!("Bearer {}", self.api_key))
296+
.header("HTTP-Referer", "https://block.github.io/goose")
297+
.header("X-Title", "Goose")
298+
.send()
299+
.await
300+
{
301+
Ok(response) => response,
302+
Err(e) => {
303+
tracing::warn!("Failed to fetch models from OpenRouter API: {}, falling back to manual model entry", e);
304+
return Ok(None);
305+
}
306+
};
307+
308+
// Handle JSON parsing failures gracefully
309+
let json: serde_json::Value = match response.json().await {
310+
Ok(json) => json,
311+
Err(e) => {
312+
tracing::warn!("Failed to parse OpenRouter API response as JSON: {}, falling back to manual model entry", e);
313+
return Ok(None);
314+
}
315+
};
316+
317+
// Check for error in response
318+
if let Some(err_obj) = json.get("error") {
319+
let msg = err_obj
320+
.get("message")
321+
.and_then(|v| v.as_str())
322+
.unwrap_or("unknown error");
323+
tracing::warn!("OpenRouter API returned an error: {}", msg);
324+
return Ok(None);
325+
}
326+
327+
let data = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
328+
ProviderError::UsageError("Missing data field in JSON response".into())
329+
})?;
330+
331+
let mut models: Vec<String> = data
332+
.iter()
333+
.filter_map(|model| {
334+
// Get the model ID
335+
let id = model.get("id").and_then(|v| v.as_str())?;
336+
337+
// Check if the model supports tools
338+
let supported_params =
339+
match model.get("supported_parameters").and_then(|v| v.as_array()) {
340+
Some(params) => params,
341+
None => {
342+
// If supported_parameters is missing, skip this model (assume no tool support)
343+
tracing::debug!(
344+
"Model '{}' missing supported_parameters field, skipping",
345+
id
346+
);
347+
return None;
348+
}
349+
};
350+
351+
let has_tool_support = supported_params
352+
.iter()
353+
.any(|param| param.as_str() == Some("tools"));
354+
355+
if has_tool_support {
356+
Some(id.to_string())
357+
} else {
358+
None
359+
}
360+
})
361+
.collect();
362+
363+
// If no models with tool support were found, fall back to manual entry
364+
if models.is_empty() {
365+
tracing::warn!("No models with tool support found in OpenRouter API response, falling back to manual model entry");
366+
return Ok(None);
367+
}
368+
369+
models.sort();
370+
Ok(Some(models))
371+
}
281372
}

0 commit comments

Comments
 (0)