use super::effort::rule_thinking_modes;
use super::model::{
Capabilities, LiveEndpointFamily, ProviderDefaults, StructuredOutputStrategy, WireDialect,
};
use super::pattern::ModelPatterns;
use super::rule::{
rule_structured_output, rule_structured_output_mode, rule_thinking_block_style,
rule_tool_mode_parity, rule_vision, ProviderRule,
};
pub(super) fn defaults_to_caps(defaults: &ProviderDefaults) -> Capabilities {
let empty = ProviderRule {
model_match: ModelPatterns::One("*".to_string()),
version_min: None,
extends: false,
native_tools: None,
message_wire_format: None,
live_endpoint_family: None,
decision_protocol: None,
decision_question_kinds: None,
decision_limits: None,
native_tool_wire_format: None,
defer_loading: None,
tool_search: None,
responses_api: None,
hosted_tools: None,
remote_mcp: None,
conversation_state: None,
compaction: None,
background_mode: None,
tool_approval_policy: None,
batch_api: None,
batch_wire_format: None,
batch_input_mode: None,
batch_discount_percent: None,
batch_turnaround_hours: None,
batch_max_requests: None,
batch_max_input_bytes: None,
batch_result_retention_days: None,
batch_result_ordering: None,
batch_partial_failure: None,
batch_cancellation: None,
batch_security_notes: None,
batch_operational_notes: None,
batch_regions: None,
max_tools: None,
prompt_caching: None,
prompt_cache_ttls: None,
prompt_cache_min_prefix_tokens: None,
cache_breakpoint_style: None,
vision: None,
audio: None,
pdf: None,
video: None,
files_api_supported: None,
file_upload_wire_format: None,
structured_output: None,
prefers_xml_scaffolding: None,
reserved_tool_call_token: None,
prefers_markdown_scaffolding: None,
structured_output_mode: None,
supports_assistant_prefill: None,
prefers_role_developer: None,
prefers_xml_tools: None,
thinking_block_style: None,
json_schema: None,
thinking_modes: None,
interleaved_thinking_supported: None,
anthropic_beta_features: None,
thinking: None,
vision_supported: None,
image_url_input_supported: None,
preserve_thinking: None,
honors_preserve_thinking_kwarg: None,
reasoning_round_trip: None,
reasoning_history_wire_field: None,
server_parser: None,
honors_chat_template_kwargs: None,
chat_template_options_field: None,
requires_completion_tokens: None,
chat_completions_unsupported: None,
reasoning_tools_require_responses: None,
requires_streaming: None,
reasoning_effort_supported: None,
reasoning_effort_levels: None,
reasoning_none_supported: None,
max_thinking_budget: None,
reasoning_disable_supported: None,
thinking_off_type: None,
reasoning_required_for_tools: None,
reasoning_text_promotable: None,
reasoning_wire_format: None,
reasoning_excluded_portable_options: None,
seed_supported: None,
top_k_supported: None,
temperature_supported: None,
top_p_supported: None,
frequency_penalty_supported: None,
presence_penalty_supported: None,
stop_supported: None,
advanced_generation_options: None,
allowed_tool_choice_modes: None,
requires_tool_result_adjacency: None,
supports_parallel_tool_calls: None,
requires_parallel_tool_calls_false: None,
tools_exclude_response_format: None,
recommended_endpoint: None,
text_tool_wire_format_supported: None,
preferred_tool_format: None,
tool_mode_parity: None,
tool_mode_parity_notes: None,
tool_format_justification: None,
thinking_disable_directive: None,
auto_reasoning_overrides: None,
provider_route_denylist: None,
openrouter_provider_order: None,
serving_precision: None,
computer_use_style: None,
screenshot_scaling: None,
safety_ack_flow: None,
system_message_placement: None,
};
let mut caps = rule_to_caps(&empty, defaults);
caps.preferred_tool_format = None;
caps.tool_mode_parity = None;
caps
}
pub(super) fn rule_to_caps(rule: &ProviderRule, defaults: &ProviderDefaults) -> Capabilities {
let (parity_verdict, parity_source) = rule_tool_mode_parity(rule);
let thinking_modes = rule_thinking_modes(rule);
let reasoning_effort_supported = thinking_modes.iter().any(|mode| mode == "effort");
let thinking_block_style = rule_thinking_block_style(rule);
let prompt_caching = rule.prompt_caching.unwrap_or(false);
let emits_inline_reasoning = thinking_block_style == "inline";
let message_wire_format = WireDialect::from_message_wire_format(
&rule
.message_wire_format
.clone()
.or_else(|| defaults.message_wire_format.clone())
.unwrap_or_else(|| "openai".to_string()),
);
let live_endpoint_family = rule
.live_endpoint_family
.or(defaults.live_endpoint_family)
.or_else(|| {
(message_wire_format == WireDialect::Gemini)
.then_some(LiveEndpointFamily::GeminiGenerateContent)
});
Capabilities {
native_tools: rule.native_tools.unwrap_or(false),
message_wire_format,
live_endpoint_family,
decision_protocol: rule.decision_protocol,
decision_question_kinds: rule.decision_question_kinds.clone().unwrap_or_default(),
decision_limits: rule.decision_limits,
native_tool_wire_format: rule
.native_tool_wire_format
.clone()
.or_else(|| defaults.native_tool_wire_format.clone())
.unwrap_or_else(|| "openai".to_string()),
defer_loading: rule.defer_loading.unwrap_or(false),
tool_search: rule.tool_search.clone().unwrap_or_default(),
responses_api: rule.responses_api.unwrap_or(false),
hosted_tools: rule.hosted_tools.clone().unwrap_or_default(),
remote_mcp: rule.remote_mcp.unwrap_or(false),
conversation_state: rule.conversation_state.unwrap_or(false),
compaction: rule.compaction.unwrap_or(false),
background_mode: rule.background_mode.unwrap_or(false),
batch_api: rule.batch_api.or(defaults.batch_api).unwrap_or(false),
batch_wire_format: rule
.batch_wire_format
.clone()
.or_else(|| defaults.batch_wire_format.clone()),
batch_input_mode: rule
.batch_input_mode
.clone()
.or_else(|| defaults.batch_input_mode.clone()),
batch_discount_percent: rule
.batch_discount_percent
.or(defaults.batch_discount_percent),
batch_turnaround_hours: rule
.batch_turnaround_hours
.or(defaults.batch_turnaround_hours),
batch_max_requests: rule.batch_max_requests.or(defaults.batch_max_requests),
batch_max_input_bytes: rule
.batch_max_input_bytes
.or(defaults.batch_max_input_bytes),
batch_result_retention_days: rule
.batch_result_retention_days
.or(defaults.batch_result_retention_days),
batch_result_ordering: rule
.batch_result_ordering
.clone()
.or_else(|| defaults.batch_result_ordering.clone()),
batch_partial_failure: rule
.batch_partial_failure
.clone()
.or_else(|| defaults.batch_partial_failure.clone()),
batch_cancellation: rule
.batch_cancellation
.clone()
.or_else(|| defaults.batch_cancellation.clone()),
batch_security_notes: rule
.batch_security_notes
.clone()
.or_else(|| defaults.batch_security_notes.clone())
.unwrap_or_default(),
batch_operational_notes: rule
.batch_operational_notes
.clone()
.or_else(|| defaults.batch_operational_notes.clone())
.unwrap_or_default(),
batch_regions: rule
.batch_regions
.clone()
.or_else(|| defaults.batch_regions.clone())
.unwrap_or_default(),
tool_approval_policy: rule.tool_approval_policy.clone(),
max_tools: rule.max_tools,
prompt_caching,
prompt_cache_ttls: if prompt_caching {
rule.prompt_cache_ttls
.clone()
.or_else(|| defaults.prompt_cache_ttls.clone())
.unwrap_or_default()
} else {
Vec::new()
},
prompt_cache_min_prefix_tokens: if prompt_caching {
rule.prompt_cache_min_prefix_tokens
.or(defaults.prompt_cache_min_prefix_tokens)
} else {
None
},
cache_breakpoint_style: rule
.cache_breakpoint_style
.or(defaults.cache_breakpoint_style)
.unwrap_or_default(),
vision: rule_vision(rule),
audio: rule.audio.unwrap_or(false),
pdf: rule.pdf.unwrap_or(false),
video: rule.video.unwrap_or(false),
files_api_supported: rule
.files_api_supported
.or(defaults.files_api_supported)
.unwrap_or(false),
file_upload_wire_format: rule
.file_upload_wire_format
.clone()
.or_else(|| defaults.file_upload_wire_format.clone()),
structured_output: rule_structured_output(rule),
structured_output_strategy: StructuredOutputStrategy::from_declaration(
rule.structured_output
.as_deref()
.or(rule.json_schema.as_deref()),
),
json_schema: rule_structured_output(rule),
prefers_xml_scaffolding: rule.prefers_xml_scaffolding.unwrap_or(false),
reserved_tool_call_token: rule.reserved_tool_call_token.unwrap_or(false),
prefers_markdown_scaffolding: rule.prefers_markdown_scaffolding.unwrap_or(false),
structured_output_mode: rule_structured_output_mode(rule),
supports_assistant_prefill: rule.supports_assistant_prefill.unwrap_or(false),
prefers_role_developer: rule.prefers_role_developer.unwrap_or(false),
prefers_xml_tools: rule.prefers_xml_tools.unwrap_or(false),
thinking_block_style,
emits_inline_reasoning,
thinking_modes,
interleaved_thinking_supported: rule.interleaved_thinking_supported.unwrap_or(false),
anthropic_beta_features: rule.anthropic_beta_features.clone().unwrap_or_default(),
vision_supported: rule.vision_supported.unwrap_or(false),
image_url_input_supported: rule
.image_url_input_supported
.or(defaults.image_url_input_supported)
.unwrap_or(true),
preserve_thinking: rule.preserve_thinking.unwrap_or(false),
honors_preserve_thinking_kwarg: rule.honors_preserve_thinking_kwarg.unwrap_or(false),
reasoning_round_trip: rule
.reasoning_round_trip
.or(defaults.reasoning_round_trip)
.unwrap_or_default(),
reasoning_history_wire_field: rule.reasoning_history_wire_field,
server_parser: rule
.server_parser
.clone()
.unwrap_or_else(|| "none".to_string()),
honors_chat_template_kwargs: rule.honors_chat_template_kwargs.unwrap_or(false),
chat_template_options_field: rule.chat_template_options_field.clone(),
requires_completion_tokens: rule.requires_completion_tokens.unwrap_or(false),
chat_completions_unsupported: rule.chat_completions_unsupported.unwrap_or(false),
reasoning_tools_require_responses: rule.reasoning_tools_require_responses.unwrap_or(false),
requires_streaming: rule.requires_streaming.unwrap_or(false),
reasoning_effort_supported,
reasoning_effort_levels: rule.reasoning_effort_levels.clone().unwrap_or_default(),
reasoning_none_supported: rule.reasoning_none_supported.unwrap_or(false),
max_thinking_budget: rule.max_thinking_budget,
reasoning_disable_supported: rule.reasoning_disable_supported.unwrap_or(true),
thinking_off_type: rule.thinking_off_type.unwrap_or_default(),
reasoning_required_for_tools: rule.reasoning_required_for_tools.unwrap_or(false),
reasoning_text_promotable: rule.reasoning_text_promotable.unwrap_or(false),
reasoning_wire_format: rule
.reasoning_wire_format
.clone()
.or_else(|| defaults.reasoning_wire_format.clone()),
reasoning_excluded_portable_options: rule
.reasoning_excluded_portable_options
.clone()
.unwrap_or_default(),
seed_supported: rule
.seed_supported
.or(defaults.seed_supported)
.unwrap_or(true),
top_k_supported: rule
.top_k_supported
.or(defaults.top_k_supported)
.unwrap_or(true),
temperature_supported: rule
.temperature_supported
.or(defaults.temperature_supported)
.unwrap_or(true),
top_p_supported: rule
.top_p_supported
.or(defaults.top_p_supported)
.unwrap_or(true),
frequency_penalty_supported: rule
.frequency_penalty_supported
.or(defaults.frequency_penalty_supported)
.unwrap_or(true),
presence_penalty_supported: rule
.presence_penalty_supported
.or(defaults.presence_penalty_supported)
.unwrap_or(true),
stop_supported: rule
.stop_supported
.or(defaults.stop_supported)
.unwrap_or(true),
advanced_generation_options: rule
.advanced_generation_options
.clone()
.or(defaults.advanced_generation_options.clone())
.unwrap_or_default(),
allowed_tool_choice_modes: rule.allowed_tool_choice_modes.clone().unwrap_or_default(),
requires_tool_result_adjacency: rule.requires_tool_result_adjacency.unwrap_or(false),
supports_parallel_tool_calls: rule
.supports_parallel_tool_calls
.or(defaults.supports_parallel_tool_calls)
.unwrap_or(true),
requires_parallel_tool_calls_false: rule
.requires_parallel_tool_calls_false
.or(defaults.requires_parallel_tool_calls_false)
.unwrap_or(false),
tools_exclude_response_format: rule.tools_exclude_response_format.unwrap_or(false),
recommended_endpoint: rule.recommended_endpoint.clone(),
text_tool_wire_format_supported: rule.text_tool_wire_format_supported.unwrap_or(true),
preferred_tool_format: Some(rule_preferred_tool_format(rule)),
tool_mode_parity: Some(parity_verdict),
tool_mode_parity_source: Some(parity_source),
tool_mode_parity_notes: rule.tool_mode_parity_notes.clone(),
thinking_disable_directive: rule.thinking_disable_directive.clone(),
auto_reasoning_overrides: rule.auto_reasoning_overrides.clone().unwrap_or_default(),
provider_route_denylist: rule.provider_route_denylist.clone().unwrap_or_default(),
openrouter_provider_order: rule.openrouter_provider_order.clone().unwrap_or_default(),
serving_precision: rule
.serving_precision
.clone()
.unwrap_or_else(|| "unverified".to_string()),
computer_use_style: rule.computer_use_style,
screenshot_scaling: rule.screenshot_scaling,
safety_ack_flow: rule.safety_ack_flow.unwrap_or(false),
system_message_placement: rule.system_message_placement,
runtime_probe: None,
}
}
pub(super) fn rule_preferred_tool_format(rule: &ProviderRule) -> String {
rule.preferred_tool_format.clone().unwrap_or_else(|| {
if rule.native_tools.unwrap_or(false) {
"native".to_string()
} else {
"json".to_string()
}
})
}