use super::support::*; use super::*; fn test_oauth_definition_set(channel: &str, models: &[&str]) -> OAuthModelDefinitions { OAuthModelDefinitions { channel: oauth_alias_channel(channel).unwrap(), models: models .iter() .map(|model| CodexModelDefinition { id: (*model).to_string(), display_name: None, description: None, context_window: None, reasoning_levels: Vec::new(), supports_tools: None, }) .collect(), } } fn test_oauth_thinking_source(channel: &str, model: &str) -> ResolvedThinkingAliasSource { let channel = oauth_alias_channel(channel).unwrap(); ResolvedThinkingAliasSource { source: ThinkingAliasSource { id: format!("{}:{model}", channel.kind), model: model.to_string(), display_name: None, provider: channel.provider.to_string(), kind: channel.kind.to_string(), protocol: channel.protocol.to_string(), reasoning_levels: vec!["low".to_string(), "high".to_string()], }, location: ThinkingAliasSourceLocation::Oauth { channel: channel.key, force_mapping: channel.force_mapping, }, } } #[test] fn oauth_auth_provider_names_map_to_alias_channels() { assert_eq!( normalize_oauth_alias_channel("anti-gravity"), Some("antigravity") ); assert_eq!(normalize_oauth_alias_channel("anthropic"), Some("claude")); assert_eq!( normalize_oauth_alias_channel("gemini-cli"), Some("aistudio") ); assert_eq!(normalize_oauth_alias_channel("grok"), Some("xai")); assert_eq!(normalize_oauth_alias_channel("meta"), Some("meta")); } #[test] fn oauth_reasoning_sources_follow_each_model_definition() { let definition_sets = [ "vertex", "aistudio", "antigravity", "claude", "codex", "kimi", "xai", ] .into_iter() .map(|channel| { let mut definitions = test_oauth_definition_set(channel, &[&format!("{channel}-model")]); definitions.models[0].reasoning_levels = vec!["low".to_string(), "high".to_string()]; definitions }) .collect::>(); let available = definition_sets .iter() .map(|definitions| definitions.models[0].id.as_str()) .collect::>(); let sources = resolved_oauth_alias_sources( "{}\n", &definition_sets, &test_agent_models(&available), AliasSourceCapability::Reasoning, ) .unwrap(); assert_eq!(sources.len(), definition_sets.len()); assert!(sources .iter() .all(|source| source.source.reasoning_levels == ["low", "high"])); assert_eq!( sources .iter() .find(|source| source.source.kind == "xai-oauth") .unwrap() .source .protocol, "codex" ); } #[test] fn thinking_alias_uses_source_native_override_parameters() { let cases = [ ( "codex", "gpt-5.6-sol", "high", &["reasoning.effort: high"][..], ), ( "claude", "claude-opus-4-6", "high", &["thinking.type: adaptive", "output_config.effort: high"][..], ), ( "claude", "claude-opus-4-6", "auto", &["thinking.type: adaptive"][..], ), ( "claude", "claude-opus-4-6", "none", &["thinking.type: disabled"][..], ), ( "aistudio", "gemini-3.1-pro", "high", &["generationConfig.thinkingConfig.thinkingLevel: high"][..], ), ( "antigravity", "gemini-3.1-pro", "high", &["generationConfig.thinkingConfig.thinkingLevel: high"][..], ), ( "kimi", "kimi-k2.5", "high", &["thinking.type: enabled", "thinking.effort: high"][..], ), ( "kimi", "kimi-k2.5", "none", &["thinking.type: disabled"][..], ), ("xai", "grok-4", "high", &["reasoning.effort: high"][..]), ]; for (channel, model, effort, expected) in cases { let source = test_oauth_thinking_source(channel, model); let alias = format!("{model}-{effort}"); let rendered = add_model_alias_to_yaml("{}\n", &source, &alias, effort, false).unwrap(); for parameter in expected { assert!( rendered.contains(parameter), "{channel}: missing {parameter}\n{rendered}" ); } assert_eq!( thinking_aliases_from_yaml(&rendered).unwrap()[0] .effort .as_deref(), Some(effort) ); let restored = remove_thinking_alias_from_yaml(&rendered, &alias).unwrap(); assert!(!restored.contains(&alias), "{channel}: {restored}"); } } #[test] fn alias_sources_exclude_blocked_providers_even_when_another_provider_serves_the_model() { for section in ["codex-api-key", "openai-compatibility", "claude-api-key", "gemini-api-key"] { let input = format!(r#" {section}: - name: disabled disabled: true models: [{{name: upstream, alias: gpt-test, thinking: {{levels: [low, high]}}}}] - name: disabled-with-wildcard excluded-models: ['*'] models: [{{name: upstream, alias: gpt-test, thinking: {{levels: [low, high]}}}}] - name: excluded-exactly excluded-models: [' GPT-TEST '] models: [{{name: upstream, alias: gpt-test, thinking: {{levels: [low, high]}}}}] - name: excluded-by-pattern excluded-models: ['gpt-*'] models: [{{name: upstream, alias: gpt-test, thinking: {{levels: [low, high]}}}}] - name: active excluded-models: [unrelated] models: [{{name: upstream, alias: gpt-test, thinking: {{levels: [low, high]}}}}] "#); for capability in [AliasSourceCapability::Base, AliasSourceCapability::Reasoning, AliasSourceCapability::Fast] { if capability == AliasSourceCapability::Fast && !matches!(section, "codex-api-key" | "openai-compatibility") { continue; } let sources = resolved_oauth_alias_sources( &input, &[], &test_agent_models(&["gpt-test"]), capability, ).unwrap(); assert_eq!(sources.len(), 1, "{section}: blocked providers must not borrow availability"); assert_eq!(sources[0].source.provider, "active"); assert!(matches!(sources[0].location, ThinkingAliasSourceLocation::ConfigModel { provider_index: 4, model_index: 0, .. } )); } } } #[test] fn v8_alias_creation_uses_the_enabled_source_and_keeps_disabled_aliases_editable() { let input = r#" config-version: 8 api-keys: codex: - name: disabled-luna excluded-models: ['*'] models: - name: gpt-5.6-luna - name: gpt-5.6-luna alias: gpt-5.6-luna-max-fast-fp keys: [{api-key: disabled-test-key}] - name: active-luna models: [{name: gpt-5.6-luna}] keys: [{api-key: active-test-key}] "#; let content = management_v8_yaml_to_legacy_view(input).unwrap(); let sources = resolved_oauth_alias_sources( &content, &[], &test_agent_models(&["gpt-5.6-luna"]), AliasSourceCapability::Base, ).unwrap(); assert_eq!(sources.len(), 1); assert_eq!(sources[0].source.provider, "active-luna"); let updated = add_model_alias_to_yaml( &content, &sources[0], "luna-active-max-fast", "max", true, ).unwrap(); let before: serde_norway::Value = serde_norway::from_str(&content).unwrap(); let after: serde_norway::Value = serde_norway::from_str(&updated).unwrap(); assert_eq!(before["codex-api-key"][0], after["codex-api-key"][0]); assert_eq!(after["codex-api-key"][1]["models"][1]["alias"].as_str(), Some("luna-active-max-fast")); let context = model_alias_edit_context(&updated, "luna-active-max-fast", &[]).unwrap(); assert_eq!(context.effort.as_deref(), Some("max")); assert!(context.fast); let disabled = model_alias_edit_context(&updated, "gpt-5.6-luna-max-fast-fp", &[]).unwrap(); assert_eq!(disabled.source.provider, "disabled-luna"); } #[test] fn configured_claude_and_gemini_models_use_native_overrides() { let input = "claude-api-key:\n - api-key: claude-key\n models:\n - name: claude-opus-4-6\n thinking:\n levels: [low, high]\ngemini-api-key:\n - api-key: gemini-key\n models:\n - name: gemini-3.1-pro\n thinking:\n levels: [low, high]\n"; let available_models = test_agent_models(&["claude-opus-4-6", "gemini-3.1-pro"]); let sources = resolved_oauth_alias_sources( input, &[], &available_models, AliasSourceCapability::Reasoning, ) .unwrap(); let claude = sources .iter() .find(|source| source.source.kind == "claude-api") .unwrap(); let with_claude = add_model_alias_to_yaml(input, claude, "claude-fixed", "high", false).unwrap(); assert!( with_claude.contains("output_config.effort: high"), "{with_claude}" ); let sources = resolved_oauth_alias_sources( &with_claude, &[], &available_models, AliasSourceCapability::Reasoning, ) .unwrap(); let gemini = sources .iter() .find(|source| source.source.kind == "gemini-api") .unwrap(); let rendered = add_model_alias_to_yaml(&with_claude, gemini, "gemini-fixed", "low", false).unwrap(); assert!( rendered.contains("generationConfig.thinkingConfig.thinkingLevel: low"), "{rendered}" ); assert!(thinking_aliases_from_yaml(&rendered) .unwrap() .iter() .any(|entry| entry.alias == "claude-fixed")); assert!(thinking_aliases_from_yaml(&rendered) .unwrap() .iter() .any(|entry| entry.alias == "gemini-fixed")); let document: serde_norway::Value = serde_norway::from_str(&rendered).unwrap(); let root = document.as_mapping().unwrap(); for (section, alias, protocol) in [ ("claude-api-key", "claude-fixed", "claude"), ("gemini-api-key", "gemini-fixed", "gemini"), ] { let alias_model = yaml_mapping_value(root, section) .and_then(serde_norway::Value::as_sequence) .and_then(|providers| providers[0].as_mapping()) .and_then(|provider| yaml_mapping_value(provider, "models")) .and_then(serde_norway::Value::as_sequence) .unwrap() .iter() .find(|model| { configured_model_identity(model) .is_some_and(|(_, model_alias, _)| model_alias == alias) }) .unwrap(); assert_eq!( configured_model_reasoning_levels(alias_model, protocol), ["low", "high"] ); } } #[test] fn antigravity_alias_uses_its_own_oauth_channel_and_force_mapping() { let available_models = test_agent_models(&["gemini-pro-agent"]); let definitions = vec![test_oauth_definition_set( "antigravity", &["gemini-pro-agent"], )]; let sources = resolved_oauth_alias_sources( "{}\n", &definitions, &available_models, AliasSourceCapability::Base, ) .unwrap(); assert_eq!(sources.len(), 1); assert_eq!(sources[0].source.kind, "antigravity-oauth"); let rendered = add_model_alias_to_yaml("{}\n", &sources[0], "gemini-3.1-pro-preview", "", false).unwrap(); assert!(rendered.contains("antigravity:"), "{rendered}"); assert!(rendered.contains("name: gemini-pro-agent"), "{rendered}"); assert!( rendered.contains("alias: gemini-3.1-pro-preview"), "{rendered}" ); assert!(rendered.contains("force-mapping: true"), "{rendered}"); assert!(!rendered.contains("codex:"), "{rendered}"); } #[test] fn oauth_alias_reader_and_delete_preserve_the_exact_channel() { let input = "oauth-model-alias:\n antigravity:\n - name: gemini-pro-agent\n alias: shared-alias\n force-mapping: true\n codex:\n - name: gpt-5.5\n alias: shared-alias\n fork: true\n"; let entries = thinking_aliases_from_yaml(input).unwrap(); assert_eq!(entries.len(), 2); assert!(entries.iter().any(|entry| { entry.oauth_channel.as_deref() == Some("antigravity") && entry.kind == "antigravity-oauth" })); let rendered = remove_thinking_alias_from_yaml_for_channel(input, "shared-alias", Some("antigravity")) .unwrap(); assert!(!rendered.contains("antigravity:"), "{rendered}"); assert!(rendered.contains("codex:"), "{rendered}"); assert!(rendered.contains("name: gpt-5.5"), "{rendered}"); } #[test] fn fast_is_only_available_for_supported_model_sources() { let input = "openai-compatibility:\n - name: Relay\n base-url: https://example.com/v1\n models:\n - name: gpt-test\n - name: deepseek-chat\n"; let available_models = test_agent_models(&["gpt-test", "deepseek-chat"]); let sources = resolved_oauth_alias_sources(input, &[], &available_models, AliasSourceCapability::Fast) .unwrap(); assert_eq!(sources.len(), 2); assert!(sources .iter() .any(|source| source.source.model == "gpt-test")); assert!(sources .iter() .any(|source| source.source.model == "deepseek-chat")); let deepseek_source = ResolvedThinkingAliasSource { source: ThinkingAliasSource { id: "test:deepseek-chat".to_string(), model: "deepseek-chat".to_string(), display_name: None, provider: "Relay".to_string(), kind: "openai-compatible".to_string(), protocol: "openai".to_string(), reasoning_levels: vec!["low".to_string(), "medium".to_string(), "high".to_string()], }, location: ThinkingAliasSourceLocation::ConfigModel { section: "openai-compatibility", provider_index: 0, model_index: 1, }, }; let deepseek_fast = add_speed_alias_to_yaml(input, &deepseek_source, "deepseek-fast").unwrap(); assert!( deepseek_fast.contains("service_tier: priority"), "{deepseek_fast}" ); let antigravity_source = ResolvedThinkingAliasSource { source: ThinkingAliasSource { id: "antigravity-oauth:gpt-test".to_string(), model: "gpt-test".to_string(), display_name: None, provider: "Antigravity OAuth".to_string(), kind: "antigravity-oauth".to_string(), protocol: "antigravity".to_string(), reasoning_levels: vec!["low".to_string(), "high".to_string()], }, location: ThinkingAliasSourceLocation::Oauth { channel: "antigravity", force_mapping: true, }, }; let antigravity_fast_sources = resolved_oauth_alias_sources( "{}\n", &[test_oauth_definition_set("antigravity", &["gpt-test"])], &test_agent_models(&["gpt-test"]), AliasSourceCapability::Fast, ) .unwrap(); assert!(antigravity_fast_sources.is_empty()); assert!(add_speed_alias_to_yaml("{}\n", &antigravity_source, "gpt-fast").is_err()); } #[test] fn thinking_alias_adds_fork_and_matching_payload_rule() { let input = "# Keep this comment\ndebug: true\npayload:\n override:\n - models:\n - name: existing-fast\n protocol: codex\n params:\n service_tier: priority\n"; let source = test_codex_oauth_thinking_source("gpt-5.5"); let rendered = add_model_alias_to_yaml(input, &source, "gpt-5.5-xhigh", "xhigh", false).unwrap(); let aliases = thinking_aliases_from_yaml(&rendered).unwrap(); assert!(rendered.contains("# Keep this comment"), "{rendered}"); assert!(rendered.contains("service_tier: priority"), "{rendered}"); assert_eq!( aliases, vec![ThinkingAliasEntry { source_model: "gpt-5.5".to_string(), alias: "gpt-5.5-xhigh".to_string(), effort: Some("xhigh".to_string()), provider: "Codex OAuth".to_string(), kind: "codex-oauth".to_string(), oauth_channel: Some("codex".to_string()), }] ); } #[test] fn model_alias_can_be_created_without_overrides() { let source = test_codex_oauth_thinking_source("gpt-5.5"); let rendered = add_model_alias_to_yaml("{}\n", &source, "gpt-5.5-alias", "", false).unwrap(); assert!(rendered.contains("alias: gpt-5.5-alias"), "{rendered}"); assert!(!rendered.contains("payload:"), "{rendered}"); assert_eq!( thinking_aliases_from_yaml(&rendered).unwrap(), vec![ThinkingAliasEntry { source_model: "gpt-5.5".to_string(), alias: "gpt-5.5-alias".to_string(), effort: None, provider: "Codex OAuth".to_string(), kind: "codex-oauth".to_string(), oauth_channel: Some("codex".to_string()), }] ); } #[test] fn configured_model_alias_can_be_created_without_overrides() { let input = "codex-api-key:\n - api-key: test\n base-url: https://example.com/v1\n models:\n - name: gpt-custom\n"; let available_models = test_agent_models(&["gpt-custom"]); let sources = resolved_alias_sources(input, &[], &available_models, false).unwrap(); let source = sources .iter() .find(|source| source.source.model == "gpt-custom") .unwrap(); let rendered = add_model_alias_to_yaml(input, source, "gpt-custom-alias", "", false).unwrap(); assert!(rendered.contains("alias: gpt-custom-alias"), "{rendered}"); assert!(!rendered.contains("thinking:"), "{rendered}"); assert!(!rendered.contains("payload:"), "{rendered}"); let entries = thinking_aliases_from_yaml(&rendered).unwrap(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].effort, None); } #[test] fn thinking_alias_removal_cleans_legacy_combined_rules() { let legacy = "oauth-model-alias:\n codex:\n - name: gpt-5.6-sol\n alias: legacy-combined\n fork: true\npayload:\n override:\n - models:\n - name: legacy-combined\n protocol: codex\n params:\n reasoning.effort: high\n - models:\n - name: legacy-combined\n protocol: codex\n params:\n service_tier: priority\n"; let restored = remove_thinking_alias_from_yaml(legacy, "legacy-combined").unwrap(); assert!(!restored.contains("legacy-combined"), "{restored}"); assert!(!restored.contains("service_tier: priority"), "{restored}"); } #[test] fn model_alias_combines_reasoning_and_fast_as_independent_overrides() { let source = test_codex_oauth_thinking_source("gpt-5.6-sol"); let alias = "gpt-5.6-sol-high-fast"; let rendered = add_model_alias_to_yaml("{}\n", &source, alias, "high", true).unwrap(); assert!(rendered.contains("reasoning.effort: high"), "{rendered}"); assert!(rendered.contains("service_tier: priority"), "{rendered}"); let document: serde_norway::Value = serde_norway::from_str(&rendered).unwrap(); let root = document.as_mapping().unwrap(); let rules = nested_yaml_value(root, &["payload", "override"]) .and_then(serde_norway::Value::as_sequence) .unwrap(); let alias_rules = rules .iter() .filter_map(serde_norway::Value::as_mapping) .filter(|rule| { yaml_mapping_value(rule, "models") .and_then(serde_norway::Value::as_sequence) .is_some_and(|models| { models .iter() .any(|model| thinking_payload_model_matches(model, alias, "codex")) }) }) .collect::>(); assert_eq!(alias_rules.len(), 2, "{rendered}"); assert!(alias_rules.iter().any(|rule| { yaml_mapping_value(rule, "params") .and_then(serde_norway::Value::as_mapping) .is_some_and(|params| { yaml_mapping_value(params, "reasoning.effort").is_some() && yaml_mapping_value(params, "service_tier").is_none() }) })); assert!(alias_rules.iter().any(|rule| { yaml_mapping_value(rule, "params") .and_then(serde_norway::Value::as_mapping) .is_some_and(|params| { yaml_mapping_value(params, "service_tier").is_some() && yaml_mapping_value(params, "reasoning.effort").is_none() }) })); assert_eq!(thinking_aliases_from_yaml(&rendered).unwrap().len(), 1); assert_eq!(speed_aliases_from_yaml(&rendered).unwrap().len(), 1); let restored = remove_thinking_alias_from_yaml(&rendered, alias).unwrap(); assert!(!restored.contains(alias), "{restored}"); } #[test] fn speed_alias_adds_fast_service_tier_and_removes_only_its_rule() { let input = "payload:\n override:\n - models:\n - name: existing-thinker\n protocol: codex\n params:\n reasoning.effort: xhigh\n"; let source = test_codex_oauth_thinking_source("gpt-5.6-sol"); let rendered = add_speed_alias_to_yaml(input, &source, "gpt-5.6-sol-fast").unwrap(); assert!(rendered.contains("alias: gpt-5.6-sol-fast"), "{rendered}"); assert!(rendered.contains("service_tier: priority"), "{rendered}"); assert!(!rendered.contains("reasoning.effort: fast"), "{rendered}"); assert_eq!( speed_aliases_from_yaml(&rendered).unwrap(), vec![SpeedAliasEntry { source_model: "gpt-5.6-sol".to_string(), alias: "gpt-5.6-sol-fast".to_string(), service_tier: "priority".to_string(), provider: "Codex OAuth".to_string(), kind: "codex-oauth".to_string(), oauth_channel: Some("codex".to_string()), }] ); let restored = remove_speed_alias_from_yaml(&rendered, "gpt-5.6-sol-fast").unwrap(); assert!(!restored.contains("gpt-5.6-sol-fast"), "{restored}"); assert!(restored.contains("reasoning.effort: xhigh"), "{restored}"); } #[test] fn speed_alias_supports_openai_compatible_model_entries() { let input = "openai-compatibility:\n - name: Relay\n base-url: https://example.com/v1\n api-key-entries:\n - api-key: test\n models:\n - name: gpt-5.6-terra\n display-name: Terra\n"; let available_models = test_agent_models(&["gpt-5.6-terra"]); let sources = resolved_thinking_alias_sources(input, &[], &available_models).unwrap(); let source = sources .iter() .find(|source| source.source.model == "gpt-5.6-terra") .unwrap(); let rendered = add_speed_alias_to_yaml(input, source, "gpt-5.6-terra-fast").unwrap(); assert!(rendered.contains("alias: gpt-5.6-terra-fast"), "{rendered}"); assert!( rendered.contains("display-name: Terra (Fast)"), "{rendered}" ); assert!(rendered.contains("protocol: openai"), "{rendered}"); assert!(rendered.contains("service_tier: priority"), "{rendered}"); assert_eq!(speed_aliases_from_yaml(&rendered).unwrap().len(), 1); let restored = remove_speed_alias_from_yaml(&rendered, "gpt-5.6-terra-fast").unwrap(); assert!(!restored.contains("gpt-5.6-terra-fast"), "{restored}"); } #[test] fn speed_alias_sources_include_codex_models_without_reasoning_levels() { let definitions = vec![CodexModelDefinition { id: "gpt-speed-only".to_string(), display_name: None, description: None, context_window: None, reasoning_levels: Vec::new(), supports_tools: None, }]; let available_models = test_agent_models(&["gpt-speed-only"]); assert!( resolved_thinking_alias_sources("{}", &definitions, &available_models) .unwrap() .is_empty() ); let sources = resolved_speed_alias_sources("{}", &definitions, &available_models).unwrap(); assert_eq!(sources.len(), 1); assert_eq!(sources[0].source.model, "gpt-speed-only"); } #[test] fn thinking_alias_effort_accepts_provider_defined_levels() { assert_eq!(validate_thinking_alias_effort(" AUTO ").unwrap(), "auto"); assert_eq!(validate_thinking_alias_effort("ultra").unwrap(), "ultra"); assert_eq!( validate_thinking_alias_effort("vendor_level-2.1").unwrap(), "vendor_level-2.1" ); assert!(validate_thinking_alias_effort("").is_err()); assert!(validate_thinking_alias_effort("high value").is_err()); assert!(validate_thinking_alias_effort("32768").is_err()); } #[test] fn existing_aliases_with_spaces_can_be_loaded_and_deleted() { assert_eq!( existing_thinking_alias_model_id(" Codex Auto Review ", "Alias model").unwrap(), "Codex Auto Review" ); assert!(validate_thinking_alias_model_id("Codex Auto Review", "Alias model").is_err()); let content = "codex-api-key:\n - name: provider\n base-url: https://www.loomex.cc\n models:\n - name: codex-auto-review\n alias: Codex Auto Review\n - name: keep-me\n"; let entries = thinking_aliases_from_yaml(content).unwrap(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].alias, "Codex Auto Review"); assert_eq!(entries[0].source_model, "codex-auto-review"); let context = model_alias_edit_context(content, "Codex Auto Review", &[]).unwrap(); assert_eq!(context.source.model, "codex-auto-review"); let deleted = remove_thinking_alias_from_yaml(content, "Codex Auto Review").unwrap(); assert!(thinking_aliases_from_yaml(&deleted).unwrap().is_empty()); assert!(deleted.contains("name: keep-me")); assert!(!deleted.contains("Codex Auto Review")); let source = resolve_model_alias_edit_source(content, "Codex Auto Review", &[]).unwrap(); let renamed = edit_model_alias_in_yaml( content, "Codex Auto Review", &source, "codex-auto-review-alias", "", false, ) .unwrap(); let renamed_entries = thinking_aliases_from_yaml(&renamed).unwrap(); assert_eq!(renamed_entries.len(), 1); assert_eq!(renamed_entries[0].alias, "codex-auto-review-alias"); } #[test] fn thinking_alias_removal_keeps_other_models_in_grouped_rule() { let input = "oauth-model-alias:\n codex:\n - name: gpt-5.5\n alias: gpt-5.5-xhigh\n fork: true\n - name: gpt-5.4\n alias: gpt-5.4-xhigh\n fork: true\npayload:\n override:\n - models:\n - name: gpt-5.5-xhigh\n protocol: codex\n - name: gpt-5.4-xhigh\n protocol: codex\n params:\n reasoning.effort: xhigh\n"; let rendered = remove_thinking_alias_from_yaml(input, "gpt-5.5-xhigh").unwrap(); let aliases = thinking_aliases_from_yaml(&rendered).unwrap(); assert_eq!(aliases.len(), 1); assert_eq!(aliases[0].alias, "gpt-5.4-xhigh"); assert!(!rendered.contains("gpt-5.5-xhigh"), "{rendered}"); assert!(rendered.contains("gpt-5.4-xhigh"), "{rendered}"); assert!(rendered.contains("reasoning.effort: xhigh"), "{rendered}"); } #[test] fn thinking_alias_rejects_duplicate_client_visible_name() { let input = "oauth-model-alias:\n codex:\n - name: gpt-5.5\n alias: gpt-5.5-high\n fork: true\n"; let source = test_codex_oauth_thinking_source("gpt-5.4"); assert!( add_model_alias_to_yaml(input, &source, "GPT-5.5-HIGH", "high", false) .unwrap_err() .contains("already exists") ); } #[test] fn thinking_alias_supports_openai_compatible_model_entries() { let input = "openai-compatibility:\n - name: DeepSeek\n base-url: https://api.deepseek.com\n api-key-entries:\n - api-key: test\n models:\n - name: deepseek-chat\n display-name: DeepSeek Chat\n thinking:\n levels: [low, medium, high]\n"; let available_models = test_agent_models(&["deepseek-chat"]); let sources = resolved_thinking_alias_sources(input, &[], &available_models).unwrap(); let source = sources .iter() .find(|source| source.source.model == "deepseek-chat") .unwrap(); let rendered = add_model_alias_to_yaml(input, source, "deepseek-chat-high", "high", false).unwrap(); let value: serde_norway::Value = serde_norway::from_str(&rendered).unwrap(); let root = value.as_mapping().unwrap(); let providers = yaml_mapping_value(root, "openai-compatibility") .and_then(serde_norway::Value::as_sequence) .unwrap(); let models = yaml_mapping_value(providers[0].as_mapping().unwrap(), "models") .and_then(serde_norway::Value::as_sequence) .unwrap(); let alias_model = models[1].as_mapping().unwrap(); assert_eq!(models.len(), 2); assert_eq!( yaml_mapping_value(alias_model, "name").and_then(serde_norway::Value::as_str), Some("deepseek-chat") ); assert_eq!( yaml_mapping_value(alias_model, "alias").and_then(serde_norway::Value::as_str), Some("deepseek-chat-high") ); assert!(rendered.contains("protocol: openai"), "{rendered}"); assert!(rendered.contains("reasoning_effort: high"), "{rendered}"); assert!(rendered.contains("thinking.type: enabled"), "{rendered}"); assert!(!rendered.contains("oauth-model-alias"), "{rendered}"); let entries = thinking_aliases_from_yaml(&rendered).unwrap(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].provider, "DeepSeek"); assert_eq!(entries[0].kind, "openai-compatible"); let restored = remove_thinking_alias_from_yaml(&rendered, "deepseek-chat-high").unwrap(); assert!(!restored.contains("deepseek-chat-high"), "{restored}"); assert!(!restored.contains("reasoning_effort"), "{restored}"); } #[test] fn thinking_alias_supports_codex_api_model_entries() { let input = "codex-api-key:\n - api-key: test\n base-url: https://example.com/v1\n models:\n - name: gpt-custom\n"; let available_models = test_agent_models(&["gpt-custom"]); let sources = resolved_thinking_alias_sources(input, &[], &available_models).unwrap(); let source = sources .iter() .find(|source| source.source.kind == "codex-api") .unwrap(); let rendered = add_model_alias_to_yaml(input, source, "gpt-custom-xhigh", "xhigh", false).unwrap(); assert!(rendered.contains("alias: gpt-custom-xhigh"), "{rendered}"); assert!(rendered.contains("protocol: codex"), "{rendered}"); assert!(rendered.contains("reasoning.effort: xhigh"), "{rendered}"); assert!(!rendered.contains("oauth-model-alias"), "{rendered}"); let entries = thinking_aliases_from_yaml(&rendered).unwrap(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].kind, "codex-api"); } #[test] fn speed_alias_supports_codex_api_model_entries() { let input = "codex-api-key:\n - api-key: test\n base-url: https://example.com/v1\n models:\n - name: codex-custom\n"; let available_models = test_agent_models(&["codex-custom"]); let sources = resolved_speed_alias_sources(input, &[], &available_models).unwrap(); let source = sources .iter() .find(|source| source.source.kind == "codex-api") .unwrap(); let rendered = add_speed_alias_to_yaml(input, source, "codex-custom-fast").unwrap(); assert!(rendered.contains("alias: codex-custom-fast"), "{rendered}"); assert!(rendered.contains("protocol: codex"), "{rendered}"); assert!(rendered.contains("service_tier: priority"), "{rendered}"); assert!(!rendered.contains("oauth-model-alias"), "{rendered}"); let entries = speed_aliases_from_yaml(&rendered).unwrap(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].kind, "codex-api"); } #[test] fn edit_model_alias_replaces_name_effort_and_fast_in_memory() { let source = test_oauth_thinking_source("codex", "gpt-test"); let original = add_model_alias_to_yaml("port: 8317\n", &source, "old-alias", "high", true).unwrap(); let updated = edit_model_alias_in_yaml(&original, "old-alias", &source, "new-alias", "low", false).unwrap(); let entries = thinking_aliases_from_yaml(&updated).unwrap(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].alias, "new-alias"); assert_eq!(entries[0].effort.as_deref(), Some("low")); assert!(speed_aliases_from_yaml(&updated).unwrap().is_empty()); assert!(updated.contains("port: 8317")); assert!(!updated.contains("old-alias")); let unchanged_name = edit_model_alias_in_yaml(&original, "old-alias", &source, "old-alias", "", false).unwrap(); assert_eq!( thinking_aliases_from_yaml(&unchanged_name).unwrap()[0].effort, None ); } #[test] fn edit_model_alias_rejects_missing_and_ambiguous_aliases() { assert!(resolve_model_alias_edit_source("port: 8317\n", "missing", &[]).is_err()); let content = "oauth-model-alias:\n codex:\n - name: model-a\n alias: shared\n claude:\n - name: model-b\n alias: shared\n"; assert!(resolve_model_alias_edit_source(content, "shared", &[]).is_err()); } #[test] fn devin_aliases_use_the_devin_channel_without_unsupported_overrides() { assert_eq!(normalize_oauth_alias_channel("cognition"), Some("devin")); let available_models = test_agent_models(&["devin/swe-2"]); let mut definitions = test_oauth_definition_set("devin", &["devin/swe-2"]); definitions.models[0].reasoning_levels = vec!["medium".to_string(), "high".to_string()]; let definitions = vec![definitions]; let sources = resolved_oauth_alias_sources("{}\n", &definitions, &available_models, AliasSourceCapability::Base).unwrap(); assert_eq!(sources.len(), 1); assert_eq!(sources[0].source.kind, "devin-oauth"); let rendered = add_model_alias_to_yaml("{}\n", &sources[0], "swe-2", "", false).unwrap(); assert!(rendered.contains("devin:"), "{rendered}"); assert!(rendered.contains("name: devin/swe-2"), "{rendered}"); assert!(rendered.contains("alias: swe-2"), "{rendered}"); assert!(!rendered.contains("payload:"), "{rendered}"); let edited = model_alias_edit_context(&rendered, "swe-2", &definitions).unwrap(); assert!(edited.source.reasoning_levels.is_empty()); for capability in [AliasSourceCapability::Reasoning, AliasSourceCapability::Fast] { assert!(resolved_oauth_alias_sources("{}\n", &definitions, &available_models, capability).unwrap().is_empty()); } }