migrate openrouter provider to goose-providers (#11757)
This commit is contained in:
@@ -16,6 +16,8 @@ pub mod local_inference;
|
|||||||
pub mod ollama;
|
pub mod ollama;
|
||||||
pub mod openai;
|
pub mod openai;
|
||||||
pub mod openai_compatible;
|
pub mod openai_compatible;
|
||||||
|
pub mod openrouter;
|
||||||
|
pub mod openrouter_format;
|
||||||
|
|
||||||
pub use declarative::declarative_providers::*;
|
pub use declarative::declarative_providers::*;
|
||||||
|
|
||||||
|
|||||||
+41
-123
@@ -1,22 +1,23 @@
|
|||||||
use anyhow::{bail, Result};
|
use crate::images::ImageFormat;
|
||||||
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::future::BoxFuture;
|
|
||||||
use goose_providers::images::ImageFormat;
|
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use super::api_client::{ApiClient, AuthMethod};
|
use crate::api_client::ApiClient;
|
||||||
use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata};
|
|
||||||
use super::openai_compatible::{handle_status, stream_openai_compat};
|
pub type OpenRouterSessionIdProvider = Box<dyn Fn() -> Option<String> + Send + Sync>;
|
||||||
use super::retry::ProviderRetry;
|
use crate::base::{ConfigKey, MessageStream, Provider, ProviderMetadata};
|
||||||
|
use crate::cache_semantics::{apply_chat_payload_breakpoints, CacheSemantics};
|
||||||
use crate::conversation::message::Message;
|
use crate::conversation::message::Message;
|
||||||
use crate::providers::formats::openrouter as openrouter_format;
|
use crate::errors::ProviderError;
|
||||||
use goose_providers::cache_semantics::{apply_chat_payload_breakpoints, CacheSemantics};
|
use crate::formats::openai::create_request;
|
||||||
use goose_providers::errors::ProviderError;
|
use crate::model::ModelConfig;
|
||||||
use goose_providers::formats::openai::create_request;
|
use crate::openai_compatible::{handle_status, stream_openai_compat};
|
||||||
use goose_providers::model::ModelConfig;
|
use crate::openrouter_format;
|
||||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
use crate::request_log::{start_log, LoggerHandleExt};
|
||||||
|
use crate::retry::ProviderRetry;
|
||||||
use rmcp::model::Tool;
|
use rmcp::model::Tool;
|
||||||
|
|
||||||
pub const OPENROUTER_PROVIDER_NAME: &str = "openrouter";
|
pub const OPENROUTER_PROVIDER_NAME: &str = "openrouter";
|
||||||
@@ -51,33 +52,23 @@ pub struct OpenRouterProvider {
|
|||||||
name: String,
|
name: String,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
configured_parameters: Option<HashMap<String, Value>>,
|
configured_parameters: Option<HashMap<String, Value>>,
|
||||||
|
#[serde(skip)]
|
||||||
|
session_id_provider: Option<OpenRouterSessionIdProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenRouterProvider {
|
impl OpenRouterProvider {
|
||||||
pub async fn from_env(
|
pub fn new(
|
||||||
tls_config: Option<crate::providers::api_client::TlsConfig>,
|
api_client: ApiClient,
|
||||||
) -> Result<Self> {
|
configured_parameters: Option<HashMap<String, Value>>,
|
||||||
let config = crate::config::Config::global();
|
session_id_provider: Option<OpenRouterSessionIdProvider>,
|
||||||
let api_key: String = config.get_secret("OPENROUTER_API_KEY")?;
|
) -> Self {
|
||||||
let host: String = config
|
Self {
|
||||||
.get_param("OPENROUTER_HOST")
|
|
||||||
.unwrap_or_else(|_| "https://openrouter.ai".to_string());
|
|
||||||
|
|
||||||
let configured_parameters = configured_openrouter_parameters()?;
|
|
||||||
|
|
||||||
let auth = AuthMethod::BearerToken(api_key);
|
|
||||||
let api_client = ApiClient::new_with_tls(host, auth, tls_config)?
|
|
||||||
.with_request_builder(crate::session_context::session_id_request_builder())
|
|
||||||
.with_header("HTTP-Referer", "https://goose-docs.ai")?
|
|
||||||
.with_header("X-Title", "goose")?
|
|
||||||
.with_header("X-OpenRouter-Categories", "cli-agent,productivity")?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
api_client,
|
api_client,
|
||||||
supports_streaming: true,
|
supports_streaming: true,
|
||||||
name: OPENROUTER_PROVIDER_NAME.to_string(),
|
name: OPENROUTER_PROVIDER_NAME.to_string(),
|
||||||
configured_parameters,
|
configured_parameters,
|
||||||
})
|
session_id_provider,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post_chat_completions(
|
async fn post_chat_completions(
|
||||||
@@ -368,26 +359,6 @@ fn escape_gemini_schema_ref_keys_in_tool_responses(payload: &mut Value) -> usize
|
|||||||
escaped
|
escaped
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_openrouter_parameters(raw: Value) -> Result<HashMap<String, Value>> {
|
|
||||||
match raw {
|
|
||||||
Value::Object(params) => Ok(params.into_iter().collect()),
|
|
||||||
Value::String(raw_json) => match serde_json::from_str::<Value>(&raw_json)? {
|
|
||||||
Value::Object(params) => Ok(params.into_iter().collect()),
|
|
||||||
_ => bail!("{OPENROUTER_PARAMETERS_CONFIG_KEY} must be a JSON object"),
|
|
||||||
},
|
|
||||||
_ => bail!("{OPENROUTER_PARAMETERS_CONFIG_KEY} must be a JSON object"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configured_openrouter_parameters() -> Result<Option<HashMap<String, Value>>> {
|
|
||||||
let config = crate::config::Config::global();
|
|
||||||
match config.get_param::<Value>(OPENROUTER_PARAMETERS_CONFIG_KEY) {
|
|
||||||
Ok(raw) => parse_openrouter_parameters(raw).map(Some),
|
|
||||||
Err(crate::config::ConfigError::NotFound(_)) => Ok(None),
|
|
||||||
Err(err) => Err(err.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_request_params(
|
fn merge_request_params(
|
||||||
request_params: &mut Option<HashMap<String, Value>>,
|
request_params: &mut Option<HashMap<String, Value>>,
|
||||||
params: HashMap<String, Value>,
|
params: HashMap<String, Value>,
|
||||||
@@ -401,7 +372,7 @@ fn merge_openrouter_parameters(model: &mut ModelConfig, params: HashMap<String,
|
|||||||
merge_request_params(&mut model.request_params, params);
|
merge_request_params(&mut model.request_params, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl goose_providers::base::ProviderDescriptor for OpenRouterProvider {
|
impl crate::base::ProviderDescriptor for OpenRouterProvider {
|
||||||
fn metadata() -> ProviderMetadata {
|
fn metadata() -> ProviderMetadata {
|
||||||
ProviderMetadata::new(
|
ProviderMetadata::new(
|
||||||
OPENROUTER_PROVIDER_NAME,
|
OPENROUTER_PROVIDER_NAME,
|
||||||
@@ -422,32 +393,10 @@ impl goose_providers::base::ProviderDescriptor for OpenRouterProvider {
|
|||||||
ConfigKey::new(OPENROUTER_PARAMETERS_CONFIG_KEY, false, false, None, false),
|
ConfigKey::new(OPENROUTER_PARAMETERS_CONFIG_KEY, false, false, None, false),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.with_setup(
|
|
||||||
crate::providers::catalog::ProviderSetupMetadata::api_key(
|
|
||||||
crate::providers::catalog::ProviderSetupGroup::Default,
|
|
||||||
)
|
|
||||||
.with_docs_url("https://openrouter.ai/keys"),
|
|
||||||
)
|
|
||||||
.with_setup_steps(vec![
|
|
||||||
"Go to https://openrouter.ai/settings/keys",
|
|
||||||
"Click 'Create' or use an existing API key",
|
|
||||||
"Copy the key and paste it above",
|
|
||||||
])
|
|
||||||
.with_fast_model(OPENROUTER_DEFAULT_FAST_MODEL)
|
.with_fast_model(OPENROUTER_DEFAULT_FAST_MODEL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProviderDef for OpenRouterProvider {
|
|
||||||
type Provider = Self;
|
|
||||||
|
|
||||||
fn from_env(
|
|
||||||
_extensions: Vec<crate::config::ExtensionConfig>,
|
|
||||||
tls_config: Option<crate::providers::api_client::TlsConfig>,
|
|
||||||
) -> BoxFuture<'static, Result<Self::Provider>> {
|
|
||||||
Box::pin(Self::from_env(tls_config))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Provider for OpenRouterProvider {
|
impl Provider for OpenRouterProvider {
|
||||||
fn get_name(&self) -> &str {
|
fn get_name(&self) -> &str {
|
||||||
@@ -522,7 +471,11 @@ impl Provider for OpenRouterProvider {
|
|||||||
messages: &[Message],
|
messages: &[Message],
|
||||||
tools: &[Tool],
|
tools: &[Tool],
|
||||||
) -> Result<MessageStream, ProviderError> {
|
) -> Result<MessageStream, ProviderError> {
|
||||||
let session_id = crate::session_context::current_session_id().unwrap_or_default();
|
let session_id = self
|
||||||
|
.session_id_provider
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|provider| provider())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut merged_model;
|
let mut merged_model;
|
||||||
let model_config = if let Some(params) = &self.configured_parameters {
|
let model_config = if let Some(params) = &self.configured_parameters {
|
||||||
@@ -592,7 +545,7 @@ impl Provider for OpenRouterProvider {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use goose_providers::base::ProviderDescriptor;
|
use crate::base::ProviderDescriptor;
|
||||||
|
|
||||||
fn model_config(model_name: &str) -> ModelConfig {
|
fn model_config(model_name: &str) -> ModelConfig {
|
||||||
ModelConfig {
|
ModelConfig {
|
||||||
@@ -619,48 +572,14 @@ mod tests {
|
|||||||
.any(|key| key.name == OPENROUTER_PARAMETERS_CONFIG_KEY));
|
.any(|key| key.name == OPENROUTER_PARAMETERS_CONFIG_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_openrouter_parameters_accepts_object_value() {
|
|
||||||
let params = parse_openrouter_parameters(json!({
|
|
||||||
"verbosity": "xhigh",
|
|
||||||
"reasoning": { "effort": "high" }
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(params["verbosity"], json!("xhigh"));
|
|
||||||
assert_eq!(params["reasoning"], json!({ "effort": "high" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_openrouter_parameters_accepts_json_string_value() {
|
|
||||||
let params = parse_openrouter_parameters(json!(
|
|
||||||
r#"{"plugins":[{"id":"web"}],"reasoning":{"max_tokens":2000}}"#
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(params["plugins"], json!([{ "id": "web" }]));
|
|
||||||
assert_eq!(params["reasoning"], json!({ "max_tokens": 2000 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_openrouter_parameters_rejects_non_object_json_string() {
|
|
||||||
let err = parse_openrouter_parameters(json!(r#"["web"]"#)).unwrap_err();
|
|
||||||
|
|
||||||
assert!(err
|
|
||||||
.to_string()
|
|
||||||
.contains("OPENROUTER_PARAMETERS must be a JSON object"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_openrouter_parameters_updates_model_request_params() {
|
fn merge_openrouter_parameters_updates_model_request_params() {
|
||||||
let mut model = model_config("anthropic/claude-sonnet-4");
|
let mut model = model_config("anthropic/claude-sonnet-4");
|
||||||
model.request_params = Some(HashMap::from([("verbosity".to_string(), json!("low"))]));
|
model.request_params = Some(HashMap::from([("verbosity".to_string(), json!("low"))]));
|
||||||
|
let params = HashMap::from([
|
||||||
let params = parse_openrouter_parameters(json!({
|
("plugins".to_string(), json!([{ "id": "web" }])),
|
||||||
"plugins": [{ "id": "web" }],
|
("verbosity".to_string(), json!("xhigh")),
|
||||||
"verbosity": "xhigh"
|
]);
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
merge_openrouter_parameters(&mut model, params);
|
merge_openrouter_parameters(&mut model, params);
|
||||||
|
|
||||||
@@ -700,17 +619,16 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let provider = OpenRouterProvider {
|
let provider = OpenRouterProvider::new(
|
||||||
api_client: ApiClient::new_with_tls(
|
ApiClient::new_with_tls(
|
||||||
server.uri(),
|
server.uri(),
|
||||||
AuthMethod::BearerToken("test-key".to_string()),
|
crate::api_client::AuthMethod::BearerToken("test-key".to_string()),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
supports_streaming: true,
|
None,
|
||||||
name: OPENROUTER_PROVIDER_NAME.to_string(),
|
None,
|
||||||
configured_parameters: None,
|
);
|
||||||
};
|
|
||||||
|
|
||||||
let mut config = model_config("google/gemini-3.5-flash");
|
let mut config = model_config("google/gemini-3.5-flash");
|
||||||
config.reasoning = Some(true);
|
config.reasoning = Some(true);
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
use crate::conversation::message::{Message, MessageContent, ProviderMetadata};
|
use crate::conversation::message::{Message, MessageContent, ProviderMetadata};
|
||||||
use goose_providers::formats::openai;
|
use crate::formats::openai;
|
||||||
use goose_providers::model::ModelConfig;
|
use crate::model::ModelConfig;
|
||||||
use goose_providers::thinking::ThinkingEffort;
|
use crate::thinking::ThinkingEffort;
|
||||||
use rmcp::model::Role;
|
use rmcp::model::Role;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ pub fn configure_openrouter(config: &Config, api_key: String) -> Result<()> {
|
|||||||
config.set_secret("OPENROUTER_API_KEY", &api_key)?;
|
config.set_secret("OPENROUTER_API_KEY", &api_key)?;
|
||||||
crate::config::set_active_provider(
|
crate::config::set_active_provider(
|
||||||
config,
|
config,
|
||||||
crate::providers::openrouter::OPENROUTER_PROVIDER_NAME,
|
goose_providers::openrouter::OPENROUTER_PROVIDER_NAME,
|
||||||
OPENROUTER_DEFAULT_MODEL,
|
OPENROUTER_DEFAULT_MODEL,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ pub mod google {
|
|||||||
create_request_with_thinking_budget(model_config, system, messages, tools, thinking_budget)
|
create_request_with_thinking_budget(model_config, system, messages, tools, thinking_budget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub mod openrouter;
|
pub mod openrouter {
|
||||||
|
pub use goose_providers::openrouter_format::*;
|
||||||
|
}
|
||||||
pub mod snowflake {
|
pub mod snowflake {
|
||||||
pub use goose_providers::formats::snowflake::*;
|
pub use goose_providers::formats::snowflake::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use super::{
|
|||||||
kimicode::KimiCodeProvider,
|
kimicode::KimiCodeProvider,
|
||||||
litellm::LiteLLMProvider,
|
litellm::LiteLLMProvider,
|
||||||
nanogpt::NanoGptProvider,
|
nanogpt::NanoGptProvider,
|
||||||
openrouter::OpenRouterProvider,
|
|
||||||
pi_acp::PiAcpProvider,
|
pi_acp::PiAcpProvider,
|
||||||
provider_registry::ProviderRegistry,
|
provider_registry::ProviderRegistry,
|
||||||
snowflake_def::SnowflakeProviderDef,
|
snowflake_def::SnowflakeProviderDef,
|
||||||
@@ -45,6 +44,7 @@ use crate::providers::databricks_v2_def::{self, DatabricksV2ProviderDef};
|
|||||||
use crate::providers::google_def::GoogleProviderDef;
|
use crate::providers::google_def::GoogleProviderDef;
|
||||||
use crate::providers::ollama_def::OllamaProviderDef;
|
use crate::providers::ollama_def::OllamaProviderDef;
|
||||||
use crate::providers::openai_def::OpenAiProviderDef;
|
use crate::providers::openai_def::OpenAiProviderDef;
|
||||||
|
use crate::providers::openrouter_def::OpenRouterProviderDef;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::declarative_providers::register_declarative_providers,
|
config::declarative_providers::register_declarative_providers,
|
||||||
providers::provider_registry::ProviderEntry,
|
providers::provider_registry::ProviderEntry,
|
||||||
@@ -157,7 +157,7 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
|
|||||||
true,
|
true,
|
||||||
Some(registrations::openai_inventory()),
|
Some(registrations::openai_inventory()),
|
||||||
);
|
);
|
||||||
registry.register_with_inventory::<OpenRouterProvider>(
|
registry.register_with_inventory::<OpenRouterProviderDef>(
|
||||||
true,
|
true,
|
||||||
Some(registrations::refresh_only().with_configured(|| {
|
Some(registrations::refresh_only().with_configured(|| {
|
||||||
let config = crate::config::Config::global();
|
let config = crate::config::Config::global();
|
||||||
|
|||||||
@@ -70,7 +70,10 @@ pub mod openai {
|
|||||||
pub mod openai_compatible {
|
pub mod openai_compatible {
|
||||||
pub use goose_providers::openai_compatible::*;
|
pub use goose_providers::openai_compatible::*;
|
||||||
}
|
}
|
||||||
pub mod openrouter;
|
pub mod openrouter {
|
||||||
|
pub use goose_providers::openrouter::*;
|
||||||
|
}
|
||||||
|
pub mod openrouter_def;
|
||||||
pub mod pi_acp;
|
pub mod pi_acp;
|
||||||
pub(crate) mod private_file;
|
pub(crate) mod private_file;
|
||||||
pub mod provider_registry;
|
pub mod provider_registry;
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
use anyhow::{bail, Result};
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
use goose_providers::{
|
||||||
|
api_client::{ApiClient, AuthMethod, TlsConfig},
|
||||||
|
base::{ProviderDescriptor, ProviderMetadata},
|
||||||
|
openrouter::OpenRouterProvider,
|
||||||
|
};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{Config, ConfigError, ExtensionConfig},
|
||||||
|
providers::base::ProviderDef,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPENROUTER_PARAMETERS_CONFIG_KEY: &str = "OPENROUTER_PARAMETERS";
|
||||||
|
|
||||||
|
pub struct OpenRouterProviderDef;
|
||||||
|
|
||||||
|
impl ProviderDescriptor for OpenRouterProviderDef {
|
||||||
|
fn metadata() -> ProviderMetadata {
|
||||||
|
OpenRouterProvider::metadata()
|
||||||
|
.with_setup(
|
||||||
|
crate::providers::catalog::ProviderSetupMetadata::api_key(
|
||||||
|
crate::providers::catalog::ProviderSetupGroup::Default,
|
||||||
|
)
|
||||||
|
.with_docs_url("https://openrouter.ai/keys"),
|
||||||
|
)
|
||||||
|
.with_setup_steps(vec![
|
||||||
|
"Go to https://openrouter.ai/settings/keys",
|
||||||
|
"Click 'Create' or use an existing API key",
|
||||||
|
"Copy the key and paste it above",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProviderDef for OpenRouterProviderDef {
|
||||||
|
type Provider = OpenRouterProvider;
|
||||||
|
|
||||||
|
fn from_env(
|
||||||
|
_extensions: Vec<ExtensionConfig>,
|
||||||
|
tls_config: Option<TlsConfig>,
|
||||||
|
) -> BoxFuture<'static, Result<Self::Provider>> {
|
||||||
|
Box::pin(from_env(tls_config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_env(tls_config: Option<TlsConfig>) -> Result<OpenRouterProvider> {
|
||||||
|
let config = Config::global();
|
||||||
|
let api_key: String = config.get_secret("OPENROUTER_API_KEY")?;
|
||||||
|
let host: String = config
|
||||||
|
.get_param("OPENROUTER_HOST")
|
||||||
|
.unwrap_or_else(|_| "https://openrouter.ai".to_string());
|
||||||
|
let configured_parameters = configured_openrouter_parameters(config)?;
|
||||||
|
|
||||||
|
let api_client = ApiClient::new_with_tls(host, AuthMethod::BearerToken(api_key), tls_config)?
|
||||||
|
.with_request_builder(crate::session_context::session_id_request_builder())
|
||||||
|
.with_header("HTTP-Referer", "https://goose-docs.ai")?
|
||||||
|
.with_header("X-Title", "goose")?
|
||||||
|
.with_header("X-OpenRouter-Categories", "cli-agent,productivity")?;
|
||||||
|
|
||||||
|
Ok(OpenRouterProvider::new(
|
||||||
|
api_client,
|
||||||
|
configured_parameters,
|
||||||
|
Some(Box::new(crate::session_context::current_session_id)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configured_openrouter_parameters(config: &Config) -> Result<Option<HashMap<String, Value>>> {
|
||||||
|
match config.get_param::<Value>(OPENROUTER_PARAMETERS_CONFIG_KEY) {
|
||||||
|
Ok(raw) => parse_openrouter_parameters(raw).map(Some),
|
||||||
|
Err(ConfigError::NotFound(_)) => Ok(None),
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_openrouter_parameters(raw: Value) -> Result<HashMap<String, Value>> {
|
||||||
|
match raw {
|
||||||
|
Value::Object(params) => Ok(params.into_iter().collect()),
|
||||||
|
Value::String(raw_json) => match serde_json::from_str::<Value>(&raw_json)? {
|
||||||
|
Value::Object(params) => Ok(params.into_iter().collect()),
|
||||||
|
_ => bail!("{OPENROUTER_PARAMETERS_CONFIG_KEY} must be a JSON object"),
|
||||||
|
},
|
||||||
|
_ => bail!("{OPENROUTER_PARAMETERS_CONFIG_KEY} must be a JSON object"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn metadata_includes_parameters_config_key() {
|
||||||
|
assert!(OpenRouterProviderDef::metadata()
|
||||||
|
.config_keys
|
||||||
|
.iter()
|
||||||
|
.any(|key| key.name == OPENROUTER_PARAMETERS_CONFIG_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_object_and_json_string_parameters() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_openrouter_parameters(json!({ "verbosity": "high" })).unwrap()["verbosity"],
|
||||||
|
json!("high")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_openrouter_parameters(json!(r#"{"plugins":[{"id":"web"}]}"#)).unwrap()["plugins"],
|
||||||
|
json!([{ "id": "web" }])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_non_object_parameters() {
|
||||||
|
assert!(parse_openrouter_parameters(json!(r#"["web"]"#))
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("OPENROUTER_PARAMETERS must be a JSON object"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user