diff --git a/README.md b/README.md index bad2083c5a..1e7e66626d 100644 --- a/README.md +++ b/README.md @@ -590,6 +590,21 @@ ORCAROUTER_API_KEY= +
+Meta + +```bash +# .env +META_API_KEY= +``` + +```yaml +# forge.yaml +model: muse-spark-1.1 +``` + +
+
IO Intelligence diff --git a/crates/forge_domain/src/provider.rs b/crates/forge_domain/src/provider.rs index 6e3d33c7f4..c2fe89a8ab 100644 --- a/crates/forge_domain/src/provider.rs +++ b/crates/forge_domain/src/provider.rs @@ -84,6 +84,7 @@ impl ProviderId { pub const AMBIENT: ProviderId = ProviderId(Cow::Borrowed("ambient")); pub const NEURALWATT: ProviderId = ProviderId(Cow::Borrowed("neuralwatt")); pub const ORCA_ROUTER: ProviderId = ProviderId(Cow::Borrowed("orca_router")); + pub const META: ProviderId = ProviderId(Cow::Borrowed("meta")); /// Returns all built-in provider IDs /// @@ -127,6 +128,7 @@ impl ProviderId { ProviderId::AMBIENT, ProviderId::NEURALWATT, ProviderId::ORCA_ROUTER, + ProviderId::META, ] } @@ -164,6 +166,7 @@ impl ProviderId { "ambient" => "Ambient".to_string(), "neuralwatt" => "Neuralwatt".to_string(), "orca_router" => "OrcaRouter".to_string(), + "meta" => "Meta".to_string(), _ => { // For other providers, use UpperCamelCase conversion use convert_case::{Case, Casing}; @@ -222,6 +225,7 @@ impl std::str::FromStr for ProviderId { "ambient" => ProviderId::AMBIENT, "neuralwatt" => ProviderId::NEURALWATT, "orca_router" => ProviderId::ORCA_ROUTER, + "meta" => ProviderId::META, // For custom providers, use Cow::Owned to avoid memory leaks custom => ProviderId(Cow::Owned(custom.to_string())), }; @@ -600,6 +604,7 @@ mod tests { assert_eq!(ProviderId::NVIDIA.to_string(), "NVIDIA"); assert_eq!(ProviderId::AMBIENT.to_string(), "Ambient"); assert_eq!(ProviderId::ORCA_ROUTER.to_string(), "OrcaRouter"); + assert_eq!(ProviderId::META.to_string(), "Meta"); } #[test] @@ -642,6 +647,7 @@ mod tests { assert!(built_in.contains(&ProviderId::NVIDIA)); assert!(built_in.contains(&ProviderId::AMBIENT)); assert!(built_in.contains(&ProviderId::ORCA_ROUTER)); + assert!(built_in.contains(&ProviderId::META)); } #[test] @@ -759,6 +765,24 @@ mod tests { assert!(built_in.contains(&ProviderId::ORCA_ROUTER)); } + #[test] + fn test_meta_from_str() { + let actual = ProviderId::from_str("meta").unwrap(); + let expected = ProviderId::META; + assert_eq!(actual, expected); + } + + #[test] + fn test_meta_display_name() { + assert_eq!(ProviderId::META.to_string(), "Meta"); + } + + #[test] + fn test_meta_in_built_in_providers() { + let built_in = ProviderId::built_in_providers(); + assert!(built_in.contains(&ProviderId::META)); + } + #[test] fn test_io_intelligence() { let fixture = "test_key"; diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index e52f66a27d..183737f5d5 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -4097,5 +4097,25 @@ "url": "https://api.orcarouter.ai/v1/chat/completions", "models": "https://api.orcarouter.ai/v1/models", "auth_methods": ["api_key"] + }, + { + "id": "meta", + "api_key_vars": "META_API_KEY", + "url_param_vars": [], + "response_type": "OpenAIResponses", + "url": "https://api.meta.ai/v1/responses", + "models": [ + { + "id": "muse-spark-1.1", + "name": "Muse Spark 1.1", + "description": "Meta's multimodal model for agentic tool calling, coding, structured output, image and video understanding, and long-context reasoning", + "context_length": 1048576, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + } + ], + "auth_methods": ["api_key"] } ] diff --git a/crates/forge_repo/src/provider/provider_repo.rs b/crates/forge_repo/src/provider/provider_repo.rs index e97cef4890..380d028a3f 100644 --- a/crates/forge_repo/src/provider/provider_repo.rs +++ b/crates/forge_repo/src/provider/provider_repo.rs @@ -957,6 +957,56 @@ mod tests { } } + #[test] + fn test_meta_config() { + let configs = get_provider_configs(); + let config = configs.iter().find(|c| c.id == ProviderId::META).unwrap(); + assert_eq!(config.id, ProviderId::META); + assert_eq!(config.api_key_vars, Some("META_API_KEY".to_string())); + assert!(config.url_param_vars.is_empty()); + assert_eq!( + config.response_type, + Some(ProviderResponse::OpenAIResponses) + ); + assert_eq!(config.url.as_str(), "https://api.meta.ai/v1/responses"); + + match config.models.as_ref().expect("models should be present") { + Models::Hardcoded(models) => { + let model = models + .iter() + .find(|m| m.id.as_str() == "muse-spark-1.1") + .expect("muse-spark-1.1 should be present in hardcoded models"); + assert_eq!( + model.context_length, + Some(1048576), + "muse-spark-1.1 should have 1048576 context length" + ); + assert_eq!( + model.tools_supported, + Some(true), + "muse-spark-1.1 should support tools" + ); + assert_eq!( + model.supports_parallel_tool_calls, + Some(true), + "muse-spark-1.1 should support parallel tool calls" + ); + assert_eq!( + model.supports_reasoning, + Some(true), + "muse-spark-1.1 should support reasoning" + ); + assert!( + model + .input_modalities + .contains(&forge_app::domain::InputModality::Image), + "muse-spark-1.1 should support image input" + ); + } + other => panic!("expected hardcoded models, got {other:?}"), + } + } + #[test] fn test_provider_entry_with_static_models_converts_to_hardcoded() { let model = forge_domain::Model::new("Qwen3.6-35B-A3b-q3-mlx")