Add Inception Labs (Mercury) provider

Register Inception Labs' Mercury diffusion LLM as a new provider using
the OpenAI-compatible adapter at api.inceptionlabs.ai. Adds mercury and
mercury-coder to the model catalog and wires up all exhaustive match
arms across arc-llm, arc-agent, and arc-attractor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-28 17:25:37 -05:00
parent d37d2c8f49
commit cbb94ae9b2
10 changed files with 84 additions and 13 deletions

View file

@ -2,6 +2,7 @@ export ANTHROPIC_API_KEY=
export BRAVE_SEARCH_API_KEY=
export DAYTONA_API_KEY=
export GEMINI_API_KEY=
export INCEPTION_API_KEY=
export KIMI_API_KEY=
export MINIMAX_API_KEY=
export OPENAI_API_KEY=

View file

@ -17,7 +17,7 @@ pub struct AgentArgs {
/// Task prompt
pub prompt: String,
/// LLM provider (anthropic, openai, gemini, kimi, zai, minimax)
/// LLM provider (anthropic, openai, gemini, kimi, zai, minimax, inception)
#[arg(long, default_value = "anthropic")]
pub provider: String,
@ -78,6 +78,7 @@ fn default_model(provider: Provider) -> &'static str {
Provider::Kimi => "kimi-k2.5",
Provider::Zai => "glm-4.7",
Provider::Minimax => "minimax-m2.5",
Provider::Inception => "mercury",
}
}
@ -159,6 +160,7 @@ fn summarizer_model_id(provider: Provider) -> ModelId {
Provider::Kimi => ModelId::new(Provider::Kimi, "kimi-k2.5"),
Provider::Zai => ModelId::new(Provider::Zai, "glm-4.7"),
Provider::Minimax => ModelId::new(Provider::Minimax, "minimax-m2.5"),
Provider::Inception => ModelId::new(Provider::Inception, "mercury"),
}
}
@ -174,7 +176,7 @@ fn build_profile(provider: Provider, model: &str, llm_client: Option<Client>) ->
let summarizer = build_summarizer(provider, llm_client);
match provider {
Provider::OpenAi => Box::new(OpenAiProfile::with_summarizer(model, summarizer)),
Provider::Kimi | Provider::Zai | Provider::Minimax => Box::new(
Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => Box::new(
OpenAiProfile::with_summarizer(model, summarizer).with_provider(provider),
),
Provider::Gemini => Box::new(GeminiProfile::with_summarizer(model, summarizer)),
@ -192,6 +194,7 @@ fn validate_api_key(provider: Provider) -> bool {
Provider::Kimi => std::env::var("KIMI_API_KEY").is_ok(),
Provider::Zai => std::env::var("ZAI_API_KEY").is_ok(),
Provider::Minimax => std::env::var("MINIMAX_API_KEY").is_ok(),
Provider::Inception => std::env::var("INCEPTION_API_KEY").is_ok(),
}
}
@ -400,7 +403,7 @@ pub async fn run_with_args(args: AgentArgs) -> anyhow::Result<()> {
Provider::OpenAi => {
Arc::new(OpenAiProfile::with_summarizer(&factory_model, child_summarizer))
}
Provider::Kimi | Provider::Zai | Provider::Minimax => Arc::new(
Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => Arc::new(
OpenAiProfile::with_summarizer(&factory_model, child_summarizer)
.with_provider(provider),
),
@ -650,6 +653,11 @@ mod tests {
assert_eq!(default_model(Provider::Minimax), "minimax-m2.5");
}
#[test]
fn default_model_inception() {
assert_eq!(default_model(Provider::Inception), "mercury");
}
// build_tool_approval non-interactive tests
#[test]

View file

@ -13,7 +13,7 @@ pub async fn discover_project_docs(
let candidate_filenames: Vec<&str> = match provider {
Provider::Anthropic => vec!["AGENTS.md", "CLAUDE.md"],
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax => {
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {
vec!["AGENTS.md", ".codex/instructions.md"]
}
Provider::Gemini => vec!["AGENTS.md", "GEMINI.md"],

View file

@ -10,7 +10,7 @@ use arc_llm::provider::{ModelId, Provider};
fn summarizer_model_id(provider: Provider) -> ModelId {
match provider {
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax => {
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {
ModelId::new(Provider::OpenAi, "gpt-4o-mini")
}
Provider::Gemini => ModelId::new(Provider::Gemini, "gemini-2.0-flash"),
@ -32,7 +32,7 @@ fn build_profile(provider: Provider, model: &str, client: &Client) -> Box<dyn Pr
match provider {
Provider::Anthropic => Box::new(AnthropicProfile::with_summarizer(model, summarizer)),
Provider::OpenAi => Box::new(OpenAiProfile::with_summarizer(model, summarizer)),
Provider::Kimi | Provider::Zai | Provider::Minimax => Box::new(
Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => Box::new(
OpenAiProfile::with_summarizer(model, summarizer).with_provider(provider),
),
Provider::Gemini => Box::new(GeminiProfile::with_summarizer(model, summarizer)),
@ -60,7 +60,7 @@ async fn make_session(provider: Provider, model: &str, cwd: &Path) -> Session {
Provider::OpenAi => {
Arc::new(OpenAiProfile::with_summarizer(&factory_model, summarizer))
}
Provider::Kimi | Provider::Zai | Provider::Minimax => Arc::new(
Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => Arc::new(
OpenAiProfile::with_summarizer(&factory_model, summarizer)
.with_provider(provider),
),

View file

@ -76,7 +76,7 @@ impl AgentBackend {
let factory: SessionFactory = Arc::new(move || {
let child_profile: Arc<dyn ProviderProfile> = match factory_provider {
Provider::OpenAi => Arc::new(OpenAiProfile::new(&factory_model)),
Provider::Kimi | Provider::Zai | Provider::Minimax => Arc::new(
Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => Arc::new(
OpenAiProfile::new(&factory_model).with_provider(factory_provider),
),
Provider::Gemini => Arc::new(GeminiProfile::new(&factory_model)),
@ -104,7 +104,7 @@ impl AgentBackend {
fn build_profile(&self) -> Box<dyn ProviderProfile> {
match self.provider {
Provider::OpenAi => Box::new(OpenAiProfile::new(&self.model)),
Provider::Kimi | Provider::Zai | Provider::Minimax => {
Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {
Box::new(OpenAiProfile::new(&self.model).with_provider(self.provider))
}
Provider::Gemini => Box::new(GeminiProfile::new(&self.model)),

View file

@ -31,7 +31,7 @@ pub fn cli_command_for_provider(provider: Provider, model: &str, prompt_file: &s
String::new()
} else {
match provider {
Provider::OpenAi | Provider::Gemini | Provider::Kimi | Provider::Zai | Provider::Minimax => {
Provider::OpenAi | Provider::Gemini | Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {
format!(" -m {model}")
}
Provider::Anthropic => format!(" --model {model}"),
@ -39,7 +39,7 @@ pub fn cli_command_for_provider(provider: Provider, model: &str, prompt_file: &s
};
match provider {
// --full-auto: sandboxed auto-execution, escalates on request
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax => {
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {
format!("codex exec --json --full-auto{model_flag} < {prompt_file}")
}
// --yolo: auto-approve all tool calls
@ -176,7 +176,7 @@ fn parse_gemini_json(output: &str) -> Option<CliResponse> {
/// Parse CLI output, choosing the right parser based on provider.
pub fn parse_cli_response(provider: Provider, output: &str) -> Option<CliResponse> {
match provider {
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax => {
Provider::OpenAi | Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {
parse_codex_ndjson(output)
}
Provider::Gemini => parse_gemini_json(output),

View file

@ -154,5 +154,31 @@
"input_cost_per_million": null,
"output_cost_per_million": null,
"aliases": ["minimax"]
},
{
"id": "mercury",
"provider": "inception",
"display_name": "Mercury",
"context_window": 32768,
"max_output": null,
"supports_tools": true,
"supports_vision": false,
"supports_reasoning": false,
"input_cost_per_million": null,
"output_cost_per_million": null,
"aliases": []
},
{
"id": "mercury-coder",
"provider": "inception",
"display_name": "Mercury Coder",
"context_window": 32768,
"max_output": null,
"supports_tools": true,
"supports_vision": false,
"supports_reasoning": false,
"input_cost_per_million": null,
"output_cost_per_million": null,
"aliases": []
}
]

View file

@ -62,7 +62,7 @@ mod tests {
#[test]
fn list_models_all() {
let models = list_models(None);
assert_eq!(models.len(), 12);
assert_eq!(models.len(), 14);
}
#[test]
@ -106,6 +106,22 @@ mod tests {
assert_eq!(m.provider, "minimax");
}
#[test]
fn mercury_in_catalog() {
let m = get_model_info("mercury").unwrap();
assert_eq!(m.provider, "inception");
assert_eq!(m.context_window, 32768);
assert!(m.supports_tools);
assert!(!m.supports_vision);
assert!(!m.supports_reasoning);
}
#[test]
fn mercury_coder_in_catalog() {
let m = get_model_info("mercury-coder").unwrap();
assert_eq!(m.provider, "inception");
}
#[test]
fn model_info_costs() {
let claude = get_model_info("claude-opus-4-6").unwrap();

View file

@ -93,6 +93,12 @@ impl Client {
.with_name("minimax");
client.register_provider(Arc::new(adapter)).await?;
}
if let Ok(key) = std::env::var("INCEPTION_API_KEY") {
let adapter =
providers::OpenAiCompatibleAdapter::new(key, "https://api.inceptionlabs.ai/v1")
.with_name("inception");
client.register_provider(Arc::new(adapter)).await?;
}
Ok(client)
}

View file

@ -20,6 +20,7 @@ pub enum Provider {
Kimi,
Zai,
Minimax,
Inception,
}
impl Provider {
@ -34,6 +35,7 @@ impl Provider {
Self::Kimi => "kimi",
Self::Zai => "zai",
Self::Minimax => "minimax",
Self::Inception => "inception",
}
}
}
@ -55,6 +57,7 @@ impl FromStr for Provider {
"kimi" => Ok(Self::Kimi),
"zai" => Ok(Self::Zai),
"minimax" => Ok(Self::Minimax),
"inception" | "inception_labs" => Ok(Self::Inception),
other => Err(format!("unknown provider: {other}")),
}
}
@ -182,4 +185,15 @@ mod tests {
fn minimax_as_str() {
assert_eq!(Provider::Minimax.as_str(), "minimax");
}
#[test]
fn parse_inception() {
assert_eq!("inception".parse::<Provider>().unwrap(), Provider::Inception);
assert_eq!("inception_labs".parse::<Provider>().unwrap(), Provider::Inception);
}
#[test]
fn inception_as_str() {
assert_eq!(Provider::Inception.as_str(), "inception");
}
}