@@ -6,19 +6,22 @@ import { type ModelInfo, chutesModels } from "@roo-code/types"
66import { DEFAULT_HEADERS } from "../constants"
77
88// Chutes models endpoint follows OpenAI /models shape with additional fields.
9+ // All fields are optional to allow graceful handling of incomplete API responses.
910const ChutesModelSchema = z . object ( {
10- id : z . string ( ) ,
11+ id : z . string ( ) . optional ( ) ,
1112 object : z . literal ( "model" ) . optional ( ) ,
1213 owned_by : z . string ( ) . optional ( ) ,
1314 created : z . number ( ) . optional ( ) ,
1415 context_length : z . number ( ) . optional ( ) ,
15- max_model_len : z . number ( ) ,
16+ max_model_len : z . number ( ) . optional ( ) ,
1617 input_modalities : z . array ( z . string ( ) ) . optional ( ) ,
1718 supported_features : z . array ( z . string ( ) ) . optional ( ) ,
1819} )
1920
2021const ChutesModelsResponseSchema = z . object ( { data : z . array ( ChutesModelSchema ) } )
2122
23+ type ChutesModelsResponse = z . infer < typeof ChutesModelsResponseSchema >
24+
2225export async function getChutesModels ( apiKey ?: string ) : Promise < Record < string , ModelInfo > > {
2326 const headers : Record < string , string > = { ...DEFAULT_HEADERS }
2427
@@ -32,33 +35,49 @@ export async function getChutesModels(apiKey?: string): Promise<Record<string, M
3235 const models : Record < string , ModelInfo > = { ...chutesModels }
3336
3437 try {
35- const response = await axios . get ( url , { headers } )
36- const parsed = ChutesModelsResponseSchema . safeParse ( response . data )
37-
38- if ( parsed . success ) {
39- for ( const m of parsed . data . data ) {
40- const contextWindow = m . context_length
41-
42- if ( ! contextWindow ) {
43- continue
44- }
45-
46- const info : ModelInfo = {
47- maxTokens : m . max_model_len ,
48- contextWindow,
49- supportsImages : ( m . input_modalities || [ ] ) . includes ( "image" ) ,
50- supportsPromptCache : false ,
51- supportsNativeTools : ( m . supported_features || [ ] ) . includes ( "tools" ) ,
52- inputPrice : 0 ,
53- outputPrice : 0 ,
54- description : `Chutes AI model: ${ m . id } ` ,
55- }
56-
57- // Union: dynamic models override hardcoded ones if they have the same ID.
58- models [ m . id ] = info
38+ const response = await axios . get < ChutesModelsResponse > ( url , { headers } )
39+ const result = ChutesModelsResponseSchema . safeParse ( response . data )
40+
41+ // Graceful fallback: use parsed data if valid, otherwise fall back to raw response data.
42+ // This mirrors the OpenRouter pattern for handling API responses with some invalid items.
43+ const data = result . success ? result . data . data : response . data ?. data
44+
45+ if ( ! result . success ) {
46+ console . error ( `Error parsing Chutes models response: ${ JSON . stringify ( result . error . format ( ) , null , 2 ) } ` )
47+ }
48+
49+ if ( ! data || ! Array . isArray ( data ) ) {
50+ console . error ( "Chutes models response missing data array" )
51+ return models
52+ }
53+
54+ for ( const m of data ) {
55+ // Skip items missing required fields (e.g., empty objects from API)
56+ if ( ! m || typeof m . id !== "string" || ! m . id ) {
57+ continue
5958 }
60- } else {
61- console . error ( `Error parsing Chutes models: ${ JSON . stringify ( parsed . error . format ( ) , null , 2 ) } ` )
59+
60+ const contextWindow = typeof m . context_length === "number" && Number . isFinite ( m . context_length ) ? m . context_length : undefined
61+ const maxModelLen = typeof m . max_model_len === "number" && Number . isFinite ( m . max_model_len ) ? m . max_model_len : undefined
62+
63+ // Skip models without valid context window information
64+ if ( ! contextWindow ) {
65+ continue
66+ }
67+
68+ const info : ModelInfo = {
69+ maxTokens : maxModelLen ?? Math . ceil ( contextWindow * 0.2 ) ,
70+ contextWindow,
71+ supportsImages : ( m . input_modalities || [ ] ) . includes ( "image" ) ,
72+ supportsPromptCache : false ,
73+ supportsNativeTools : ( m . supported_features || [ ] ) . includes ( "tools" ) ,
74+ inputPrice : 0 ,
75+ outputPrice : 0 ,
76+ description : `Chutes AI model: ${ m . id } ` ,
77+ }
78+
79+ // Union: dynamic models override hardcoded ones if they have the same ID.
80+ models [ m . id ] = info
6281 }
6382 } catch ( error ) {
6483 console . error ( `Error fetching Chutes models: ${ error instanceof Error ? error . message : String ( error ) } ` )
0 commit comments