blob: aae1accfbe18586eee1710bd6c544d5af3c32bee [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::*;
use maka_agent_facts::configuration::ModelOverrideCapabilities;
#[tokio::test]
async fn kimi_catalog_thinking_levels_prepare_without_losing_the_selected_route() {
let binding = binding("kimi-coding-plan");
for (level, options) in [
(
None,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"}}}),
),
(
Some(Low),
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"effort":"low"}}),
),
(
Some(High),
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"effort":"high"}}),
),
(
Some(Max),
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"effort":"max"}}),
),
] {
let mut input = request(&binding, "k3", ModelOverride::default());
input.thinking_level = level;
let model = binding.prepare(input).await.unwrap();
assert!(matches!(model.protocol, ProviderKind::Anthropic));
assert_eq!(model.adapter, "anthropic-messages");
assert_eq!(model.info.id, "k3");
assert_eq!(model.base_url, "https://relay.test/v1");
assert_eq!(model.thinking_levels, [Low, High, Max]);
assert_eq!(model.provider_options, options);
assert_eq!(model.main_output_limit, Some(131072));
}
}
#[tokio::test]
async fn kimi_catalog_choices_and_supported_default_remain_selectable() {
let binding = binding("kimi-coding-plan");
for default in [None, Some(High)] {
let input = request(
&binding,
"k3",
ModelOverride {
default_thinking_level: default,
..Default::default()
},
);
let metadata = ApiProviders::model_metadata(
"kimi-coding-plan",
snapshot(),
input.connection.clone(),
input.model.clone(),
input.overrides.clone(),
)
.unwrap();
assert_eq!(metadata.thinking_levels, [Low, High, Max]);
assert_eq!(metadata.default_thinking_level, default);
let model = binding.prepare(input).await.unwrap();
assert_eq!(
model.provider_options,
if default.is_some() {
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"effort":"high"}})
} else {
json!({"anthropic":{"cacheControl":{"type":"ephemeral"}}})
}
);
}
}
#[tokio::test]
async fn anthropic_alias_override_and_alternate_use_the_selected_protocol_policy() {
// These inputs distinguish direct aliases, model adapter overrides and an
// explicit protocol alternate. Account endpoints must retain precedence.
for (provider, id, protocol, level, options, budget) in [
(
"minimax-coding-plan",
"MiniMax-M2.5",
None,
High,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"effort":"high"}}),
4000,
),
(
"opencode",
"claude-opus-4-6",
None,
High,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"thinking":{"type":"enabled","budgetTokens":1024},"effort":"high"}}),
2976,
),
(
"zenmux",
"anthropic/claude-opus-4.5",
None,
High,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"thinking":{"type":"enabled","budgetTokens":1024},"effort":"high"}}),
2976,
),
(
"opencode",
"custom-model",
Some(ApiProtocol::AnthropicMessages),
High,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"effort":"high"}}),
4000,
),
(
"anthropic",
"claude-opus-4-6",
None,
High,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"thinking":{"type":"adaptive","display":"summarized"},"effort":"high"}}),
4000,
),
(
"anthropic",
"claude-sonnet-4-5",
None,
Off,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"thinking":{"type":"disabled"}}}),
4000,
),
] {
let binding = binding(provider);
let mut input = request(
&binding,
id,
ModelOverride {
api_protocol: protocol,
thinking_levels: Some(vec![level]),
max_output_tokens: Some(4000),
..Default::default()
},
);
input.thinking_level = Some(level);
let model = binding.prepare(input).await.unwrap();
assert!(
matches!(model.protocol, ProviderKind::Anthropic),
"{provider}/{id}"
);
assert_eq!(model.adapter, "anthropic-messages");
assert_eq!(model.base_url, "https://relay.test/v1");
assert_eq!(model.info.id, id);
assert_eq!(model.provider_options, options, "{provider}/{id}");
assert_eq!(model.main_output_limit, Some(budget), "{provider}/{id}");
}
// With the catalog endpoint unchanged, the model's endpoint override owns
// routing; a custom account endpoint above deliberately takes precedence.
let binding = binding("zenmux");
let mut input = request(
&binding,
"anthropic/claude-opus-4.5",
ModelOverride {
max_output_tokens: Some(4000),
..Default::default()
},
);
input.connection.configuration = binding
.configure(json!({"baseUrl":"https://zenmux.ai/api/v1"}))
.unwrap();
let model = binding.prepare(input).await.unwrap();
assert!(matches!(model.protocol, ProviderKind::Anthropic));
assert_eq!(model.base_url, "https://zenmux.ai/api/anthropic/v1");
assert_eq!(
model.provider_options,
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"thinking":{"type":"enabled","budgetTokens":1024}}})
);
assert_eq!(model.main_output_limit, Some(2976));
}
#[tokio::test]
async fn resolved_openai_routes_preserve_explicit_effort_and_parallel_policy() {
for (provider, id, wire) in [
(
"volcengine-agent-plan",
"ark-code-latest",
ApiProtocol::OpenaiResponses,
),
("xai", "grok-4.5", ApiProtocol::OpenaiResponses),
("opencode", "gpt-5.2", ApiProtocol::OpenaiResponses),
("opencode", "custom-model", ApiProtocol::OpenaiResponses),
(
"zenmux",
"openai/gpt-5.2",
ApiProtocol::OpenaiChatCompletions,
),
] {
let binding = binding(provider);
let mut input = request(
&binding,
id,
ModelOverride {
api_protocol: Some(wire),
thinking_levels: Some(vec![High]),
max_output_tokens: Some(4000),
capabilities: Some(ModelOverrideCapabilities {
parallel_tool_calls: Some(false),
..Default::default()
}),
..Default::default()
},
);
input.thinking_level = Some(High);
input.model.supports_reasoning_summary = Some(false);
let model = binding.prepare(input).await.unwrap();
match wire {
ApiProtocol::OpenaiResponses => {
assert!(matches!(model.protocol, ProviderKind::OpenaiResponses));
assert_eq!(model.adapter, "responses");
}
ApiProtocol::OpenaiChatCompletions => {
assert!(matches!(
model.protocol,
ProviderKind::OpenaiChatCompletions
));
assert_eq!(model.adapter, "chat-completions");
}
ApiProtocol::AnthropicMessages => unreachable!(),
}
assert_eq!(model.base_url, "https://relay.test/v1");
assert_eq!(model.info.id, id);
assert_eq!(
model.provider_options,
json!({"openai":{"store":false,"parallelToolCalls":false,"reasoningEffort":"high"}}),
"{provider}/{id}"
);
assert_eq!(model.main_output_limit, Some(4000));
}
}
#[tokio::test]
async fn anthropic_parallel_opt_out_is_encoded_on_canonical_and_alias_routes() {
for provider in ["anthropic", "anthropic-compatible"] {
let binding = binding(provider);
for parallel in [None, Some(false)] {
let input = request(
&binding,
"custom-model",
ModelOverride {
capabilities: Some(ModelOverrideCapabilities {
parallel_tool_calls: parallel,
..Default::default()
}),
..Default::default()
},
);
let model = binding.prepare(input).await.unwrap();
assert!(matches!(model.protocol, ProviderKind::Anthropic));
assert_eq!(
model.provider_options,
if parallel == Some(false) {
json!({"anthropic":{"cacheControl":{"type":"ephemeral"},"disableParallelToolUse":true}})
} else {
json!({"anthropic":{"cacheControl":{"type":"ephemeral"}}})
},
"{provider}/{parallel:?}"
);
}
}
}
#[tokio::test]
async fn supported_options_do_not_enable_unsupported_execution_contracts() {
let mut observed = Vec::new();
for (provider, id, wire) in [
("MiniMax", "custom-model", ApiProtocol::AnthropicMessages),
(
"commandcode",
"custom-model",
ApiProtocol::AnthropicMessages,
),
("zenmux", "openai/gpt-5.2", ApiProtocol::OpenaiResponses),
("kimi-coding-plan", "k3", ApiProtocol::OpenaiChatCompletions),
(
"anthropic-compatible",
"custom-model",
ApiProtocol::OpenaiResponses,
),
] {
let binding = binding(provider);
let input = request(
&binding,
id,
ModelOverride {
api_protocol: Some(wire),
thinking_levels: Some(vec![High]),
default_thinking_level: Some(High),
..Default::default()
},
);
observed.push(json!({
"case": provider,
"error": binding.prepare(input).await.err(),
}));
}
let binding = binding("deepseek");
let input = request(
&binding,
"custom-model",
ModelOverride {
api_protocol: Some(ApiProtocol::OpenaiResponses),
capabilities: Some(ModelOverrideCapabilities {
parallel_tool_calls: Some(false),
..Default::default()
}),
..Default::default()
},
);
observed.push(json!({
"case": "open-responses-parallel",
"error": binding.prepare(input).await.err(),
}));
let binding = super::binding("kimi-coding-plan");
let mut input = request(&binding, "k3", ModelOverride::default());
input.thinking_level = Some(Medium);
observed.push(json!({
"case": "undeclared-kimi-level",
"error": binding.prepare(input).await.err(),
}));
assert_eq!(
json!(observed),
json!([
{"case":"MiniMax","error":{"kind":"unsupported_execution_profile"}},
{"case":"commandcode","error":{"kind":"unsupported_execution_profile"}},
{"case":"zenmux","error":{"kind":"unsupported_execution_profile"}},
{"case":"kimi-coding-plan","error":{"kind":"unsupported_execution_profile"}},
{"case":"anthropic-compatible","error":{"kind":"unsupported_protocol"}},
{"case":"open-responses-parallel","error":{"kind":"unsupported_parallel_policy"}},
{"case":"undeclared-kimi-level","error":{"kind":"unsupported_thinking_level"}},
])
);
}