@@ -16,6 +16,17 @@ final class GaryxThreadModelOverridePresentationTests: XCTestCase {
1616 )
1717 }
1818
19+ func testProviderLevelDefaultReasoningEffortDecodesSnakeAndCamelKeys( ) throws {
20+ XCTAssertEqual (
21+ try decodeProviderModels ( #"{ "default_reasoning_effort": "max" }"# ) . defaultReasoningEffort,
22+ " max "
23+ )
24+ XCTAssertEqual (
25+ try decodeProviderModels ( #"{ "defaultReasoningEffort": "high" }"# ) . defaultReasoningEffort,
26+ " high "
27+ )
28+ }
29+
1930 func testReasoningEffortOptionsFollowSelectedModel( ) throws {
2031 let providerModels = try decodeProviderModels ( claudeProviderJSON)
2132
@@ -38,6 +49,76 @@ final class GaryxThreadModelOverridePresentationTests: XCTestCase {
3849 XCTAssertEqual ( unknownModelOptions. map ( \. id) , [ " low " , " high " ] )
3950 }
4051
52+ func testDefaultStateUsesProviderDefaultModelAndConfiguredReasoningEffort( ) throws {
53+ let providerModels = try decodeProviderModels ( configuredClaudeProviderJSON)
54+
55+ let defaultOptions = GaryxThreadModelOverridePresentation . reasoningEffortOptions (
56+ providerModels: providerModels,
57+ model: nil
58+ )
59+ XCTAssertEqual ( defaultOptions. map ( \. id) , [ " low " , " high " , " max " ] )
60+
61+ XCTAssertEqual (
62+ GaryxThreadModelOverridePresentation . defaultReasoningEffort (
63+ providerModels: providerModels,
64+ model: nil
65+ ) ,
66+ " max "
67+ )
68+
69+ XCTAssertEqual (
70+ GaryxThreadModelOverridePresentation . controlLabel (
71+ providerModels: providerModels,
72+ model: nil ,
73+ reasoningEffort: nil ,
74+ fallback: " Model "
75+ ) ,
76+ " Claude Opus 4.8 · Max "
77+ )
78+ }
79+
80+ func testConfiguredProviderDefaultReasoningEffortMustBeSupportedByCurrentModel( ) throws {
81+ let providerModels = try decodeProviderModels ( configuredClaudeProviderJSON)
82+
83+ let sonnetOptions = GaryxThreadModelOverridePresentation . reasoningEffortOptions (
84+ providerModels: providerModels,
85+ model: " claude-sonnet-4-6 "
86+ )
87+ XCTAssertEqual ( sonnetOptions. map ( \. id) , [ " low " , " high " ] )
88+
89+ XCTAssertEqual (
90+ GaryxThreadModelOverridePresentation . defaultReasoningEffort (
91+ providerModels: providerModels,
92+ model: " claude-sonnet-4-6 "
93+ ) ,
94+ " high "
95+ )
96+ }
97+
98+ func testTraexPerModelReasoningEffortsRenderThroughGatewayShape( ) throws {
99+ let providerModels = try decodeProviderModels ( traexProviderJSON)
100+
101+ let reasonerOptions = GaryxThreadModelOverridePresentation . reasoningEffortOptions (
102+ providerModels: providerModels,
103+ model: " traex-reasoner "
104+ )
105+ XCTAssertEqual ( reasonerOptions. map ( \. id) , [ " medium " , " max " ] )
106+ XCTAssertEqual ( reasonerOptions. map ( \. label) , [ " Medium " , " max " ] )
107+ XCTAssertTrue (
108+ GaryxThreadModelOverridePresentation . reasoningEffortOptions (
109+ providerModels: providerModels,
110+ model: " traex-fast "
111+ ) . isEmpty
112+ )
113+ XCTAssertEqual (
114+ GaryxThreadModelOverridePresentation . modelLabel (
115+ providerModels: providerModels,
116+ model: " traex-reasoner "
117+ ) ,
118+ " traex-reasoner "
119+ )
120+ }
121+
41122 func testReasoningEffortOptionsEmptyWhenSelectionUnsupported( ) throws {
42123 let providerModels = try decodeProviderModels ( geminiProviderJSON)
43124 XCTAssertTrue (
@@ -158,21 +239,33 @@ final class GaryxThreadModelOverridePresentationTests: XCTestCase {
158239 )
159240 }
160241
161- func testEffortFilterModelPrefersOverrideThenAgentModel( ) {
242+ func testEffortFilterModelPrefersOverrideThenAgentModel( ) throws {
243+ let providerModels = try decodeProviderModels ( configuredClaudeProviderJSON)
244+
162245 XCTAssertEqual (
163246 GaryxThreadModelOverridePresentation . effortFilterModel (
164247 override: " claude-opus-4-8 " ,
165- agentConfiguredModel: " claude-haiku-4-5 "
248+ agentConfiguredModel: " claude-haiku-4-5 " ,
249+ providerModels: providerModels
166250 ) ,
167251 " claude-opus-4-8 "
168252 )
169253 XCTAssertEqual (
170254 GaryxThreadModelOverridePresentation . effortFilterModel (
171255 override: " " ,
172- agentConfiguredModel: " claude-haiku-4-5 "
256+ agentConfiguredModel: " claude-haiku-4-5 " ,
257+ providerModels: providerModels
173258 ) ,
174259 " claude-haiku-4-5 "
175260 )
261+ XCTAssertEqual (
262+ GaryxThreadModelOverridePresentation . effortFilterModel (
263+ override: nil ,
264+ agentConfiguredModel: " " ,
265+ providerModels: providerModels
266+ ) ,
267+ " claude-opus-4-8 "
268+ )
176269 XCTAssertNil (
177270 GaryxThreadModelOverridePresentation . effortFilterModel (
178271 override: nil ,
@@ -241,6 +334,69 @@ final class GaryxThreadModelOverridePresentationTests: XCTestCase {
241334 }
242335 """
243336
337+ private let configuredClaudeProviderJSON = """
338+ {
339+ " provider_type " : " claude_code " ,
340+ " supports_model_selection " : true,
341+ " supports_reasoning_effort_selection " : true,
342+ " default_model " : " claude-opus-4-8 " ,
343+ " default_reasoning_effort " : " max " ,
344+ " source " : " claude_code_builtin " ,
345+ " reasoning_efforts " : [
346+ { " id " : " low " , " label " : " Low " , " recommended " : false },
347+ { " id " : " high " , " label " : " High " , " recommended " : true }
348+ ],
349+ " models " : [
350+ {
351+ " id " : " claude-sonnet-4-6 " ,
352+ " label " : " Claude Sonnet 4.6 " ,
353+ " recommended " : true,
354+ " supported_reasoning_efforts " : [
355+ { " id " : " low " , " label " : " Low " , " recommended " : false },
356+ { " id " : " high " , " label " : " High " , " recommended " : true }
357+ ]
358+ },
359+ {
360+ " id " : " claude-opus-4-8 " ,
361+ " label " : " Claude Opus 4.8 " ,
362+ " recommended " : false,
363+ " supported_reasoning_efforts " : [
364+ { " id " : " low " , " label " : " Low " , " recommended " : false },
365+ { " id " : " high " , " label " : " High " , " recommended " : true },
366+ { " id " : " max " , " label " : " Max " , " recommended " : false }
367+ ]
368+ }
369+ ]
370+ }
371+ """
372+
373+ private let traexProviderJSON = """
374+ {
375+ " provider_type " : " traex " ,
376+ " supports_model_selection " : true,
377+ " supports_reasoning_effort_selection " : true,
378+ " default_model " : " traex-fast " ,
379+ " source " : " traex_builtin " ,
380+ " reasoning_efforts " : [],
381+ " models " : [
382+ {
383+ " id " : " traex-fast " ,
384+ " label " : " TRAE Fast " ,
385+ " recommended " : true,
386+ " supported_reasoning_efforts " : []
387+ },
388+ {
389+ " id " : " traex-reasoner " ,
390+ " recommended " : false,
391+ " supported_reasoning_efforts " : [
392+ { " id " : " medium " , " label " : " Medium " , " recommended " : true },
393+ { " id " : " max " , " recommended " : false }
394+ ]
395+ }
396+ ]
397+ }
398+ """
399+
244400 private let geminiProviderJSON = """
245401 {
246402 " provider_type " : " gemini_cli " ,
0 commit comments