1313 */
1414
1515import type { ThinkingLevel } from "@/common/types/thinking" ;
16+ import modelsData from "@/common/utils/tokens/models.json" ;
1617
1718/**
1819 * Thinking policy is simply the set of allowed thinking levels for a model.
1920 * Pure subset design - no wrapper object, no discriminated union.
2021 */
2122export type ThinkingPolicy = readonly ThinkingLevel [ ] ;
2223
24+ /**
25+ * Helper to look up model metadata from models.json
26+ */
27+ function getModelMetadata ( modelString : string ) : Record < string , unknown > | null {
28+ const colonIndex = modelString . indexOf ( ":" ) ;
29+ const provider = colonIndex !== - 1 ? modelString . slice ( 0 , colonIndex ) : "" ;
30+ const modelName = colonIndex !== - 1 ? modelString . slice ( colonIndex + 1 ) : modelString ;
31+
32+ const lookupKeys : string [ ] = [ modelName ] ;
33+ if ( provider ) {
34+ lookupKeys . push ( `${ provider } /${ modelName } ` ) ;
35+ }
36+
37+ for ( const key of lookupKeys ) {
38+ const data = ( modelsData as Record < string , Record < string , unknown > > ) [ key ] ;
39+ if ( data ) {
40+ return data ;
41+ }
42+ }
43+
44+ return null ;
45+ }
46+
2347/**
2448 * Returns the thinking policy for a given model.
25- *
26- * Rules:
27- * - openai:gpt-5-pro → ["high"] (only supported level)
28- * - default → ["off", "low", "medium", "high"] (all levels selectable)
29- *
30- * Tolerates version suffixes (e.g., gpt-5-pro-2025-10-06).
31- * Does NOT match gpt-5-pro-mini (uses negative lookahead).
3249 */
3350export function getThinkingPolicyForModel ( modelString : string ) : ThinkingPolicy {
34- // Match "openai:" followed by optional whitespace and "gpt-5-pro"
35- // Allow version suffixes like "-2025-10-06" but NOT "-mini" or other text suffixes
36- if ( / ^ o p e n a i : \s * g p t - 5 - p r o (? ! - [ a - z ] ) / . test ( modelString ) ) {
51+ // GPT-5 Pro: always high (but not gpt-5-pro-mini)
52+ if ( modelString . startsWith ( "openai:gpt-5-pro" ) && ! modelString . includes ( "-mini" ) ) {
3753 return [ "high" ] ;
3854 }
3955
40- // Gemini 3 Pro only supports "low" and "high" reasoning levels
56+ // Gemini 3: limited levels
4157 if ( modelString . includes ( "gemini-3" ) ) {
4258 return [ "low" , "high" ] ;
4359 }
4460
45- // Default policy: all levels selectable
61+ // Grok: binary on/off (but not grok-code)
62+ if ( modelString . startsWith ( "xai:grok-" ) && ! modelString . includes ( "grok-code" ) ) {
63+ return [ "off" , "high" ] ;
64+ }
65+
66+ // Check models.json for no reasoning support
67+ const metadata = getModelMetadata ( modelString ) ;
68+ if ( metadata ?. supports_reasoning === false ) {
69+ return [ "off" ] ;
70+ }
71+
72+ // Default: all levels
4673 return [ "off" , "low" , "medium" , "high" ] ;
4774}
4875
4976/**
5077 * Enforce thinking policy by clamping requested level to allowed set.
5178 *
52- * Fallback strategy:
53- * 1. If requested level is allowed, use it
54- * 2. If "medium" is allowed, use it (reasonable default)
55- * 3. Otherwise use first allowed level
79+ * If the requested level isn't allowed:
80+ * - If user wanted reasoning (non-"off"), pick the highest available non-"off" level
81+ * - Otherwise return the first allowed level
5682 */
5783export function enforceThinkingPolicy (
5884 modelString : string ,
@@ -64,6 +90,12 @@ export function enforceThinkingPolicy(
6490 return requested ;
6591 }
6692
67- // Fallback: prefer "medium" if allowed, else use first allowed level
68- return allowed . includes ( "medium" ) ? "medium" : allowed [ 0 ] ;
93+ // If user wanted reasoning, keep it on with the best available level
94+ if ( requested !== "off" ) {
95+ if ( allowed . includes ( "high" ) ) return "high" ;
96+ if ( allowed . includes ( "medium" ) ) return "medium" ;
97+ if ( allowed . includes ( "low" ) ) return "low" ;
98+ }
99+
100+ return allowed [ 0 ] ;
69101}
0 commit comments