@@ -25,62 +25,168 @@ export const DEFAULT_SURF_UPSTREAM_URL = "https://surf.cascade.fyi/api/v1/infere
2525export const DEFAULT_PROVIDER_PROTOCOL : PaymentProtocol = "mpp" ;
2626export const DEFAULT_MPP_SESSION_BUDGET = "0.5" ;
2727
28- export const DEFAULT_SURF_MODELS : Array < Omit < ModelEntry , "provider" > > = [
29- {
30- id : "anthropic/claude-opus-4.6" ,
28+ /** Known model metadata for cost/capability enrichment. */
29+ const MODEL_METADATA : Record < string , Omit < ModelEntry , "provider" | "id" > > = {
30+ "anthropic/claude-opus-4.6" : {
3131 name : "Claude Opus 4.6" ,
3232 maxTokens : 200000 ,
3333 reasoning : true ,
3434 input : [ "text" , "image" ] ,
3535 cost : { input : 0.015 , output : 0.075 , cacheRead : 0.0015 , cacheWrite : 0.01875 } ,
3636 contextWindow : 200000 ,
3737 } ,
38- {
39- id : "anthropic/claude-sonnet-4.6" ,
38+ "anthropic/claude-opus-4.5" : {
39+ name : "Claude Opus 4.5" ,
40+ maxTokens : 200000 ,
41+ reasoning : true ,
42+ input : [ "text" , "image" ] ,
43+ cost : { input : 0.015 , output : 0.075 , cacheRead : 0.0015 , cacheWrite : 0.01875 } ,
44+ contextWindow : 200000 ,
45+ } ,
46+ "anthropic/claude-sonnet-4.6" : {
4047 name : "Claude Sonnet 4.6" ,
4148 maxTokens : 200000 ,
4249 reasoning : true ,
4350 input : [ "text" , "image" ] ,
4451 cost : { input : 0.003 , output : 0.015 , cacheRead : 0.0003 , cacheWrite : 0.00375 } ,
4552 contextWindow : 200000 ,
4653 } ,
47- {
48- id : "x-ai/grok-4.20-beta" ,
54+ "anthropic/claude-sonnet-4.5" : {
55+ name : "Claude Sonnet 4.5" ,
56+ maxTokens : 200000 ,
57+ reasoning : true ,
58+ input : [ "text" , "image" ] ,
59+ cost : { input : 0.003 , output : 0.015 , cacheRead : 0.0003 , cacheWrite : 0.00375 } ,
60+ contextWindow : 200000 ,
61+ } ,
62+ "x-ai/grok-4.20-beta" : {
4963 name : "Grok 4.20 Beta" ,
5064 maxTokens : 131072 ,
5165 reasoning : true ,
5266 input : [ "text" ] ,
5367 cost : { input : 0.003 , output : 0.015 , cacheRead : 0 , cacheWrite : 0 } ,
5468 contextWindow : 131072 ,
5569 } ,
56- {
57- id : "minimax/minimax-m2.5" ,
70+ "x-ai/grok-4.1-fast" : {
71+ name : "Grok 4.1 Fast" ,
72+ maxTokens : 131072 ,
73+ reasoning : false ,
74+ input : [ "text" ] ,
75+ cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
76+ contextWindow : 131072 ,
77+ } ,
78+ "minimax/minimax-m2.7" : {
79+ name : "MiniMax M2.7" ,
80+ maxTokens : 1000000 ,
81+ reasoning : false ,
82+ input : [ "text" ] ,
83+ cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
84+ contextWindow : 1000000 ,
85+ } ,
86+ "minimax/minimax-m2.5" : {
5887 name : "MiniMax M2.5" ,
5988 maxTokens : 1000000 ,
6089 reasoning : false ,
6190 input : [ "text" ] ,
6291 cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
6392 contextWindow : 1000000 ,
6493 } ,
65- {
66- id : "moonshotai/kimi-k2.5" ,
94+ "moonshotai/kimi-k2.5" : {
6795 name : "Kimi K2.5" ,
6896 maxTokens : 131072 ,
6997 reasoning : true ,
7098 input : [ "text" ] ,
7199 cost : { input : 0.002 , output : 0.008 , cacheRead : 0 , cacheWrite : 0 } ,
72100 contextWindow : 131072 ,
73101 } ,
74- {
75- id : "z-ai/glm-5" ,
102+ "z-ai/glm-5" : {
76103 name : "GLM-5" ,
77104 maxTokens : 128000 ,
78105 reasoning : false ,
79106 input : [ "text" ] ,
80107 cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
81108 contextWindow : 128000 ,
82109 } ,
83- ] ;
110+ "z-ai/glm-5-turbo" : {
111+ name : "GLM-5 Turbo" ,
112+ maxTokens : 128000 ,
113+ reasoning : false ,
114+ input : [ "text" ] ,
115+ cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
116+ contextWindow : 128000 ,
117+ } ,
118+ "qwen/qwen-2.5-7b-instruct" : {
119+ name : "Qwen 2.5 7B Instruct" ,
120+ maxTokens : 32768 ,
121+ reasoning : false ,
122+ input : [ "text" ] ,
123+ cost : { input : 0.0005 , output : 0.002 , cacheRead : 0 , cacheWrite : 0 } ,
124+ contextWindow : 131072 ,
125+ } ,
126+ "stepfun/step-3.5-flash" : {
127+ name : "Step 3.5 Flash" ,
128+ maxTokens : 131072 ,
129+ reasoning : false ,
130+ input : [ "text" ] ,
131+ cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
132+ contextWindow : 131072 ,
133+ } ,
134+ "xiaomi/mimo-v2-pro" : {
135+ name : "MiMo V2 Pro" ,
136+ maxTokens : 131072 ,
137+ reasoning : true ,
138+ input : [ "text" ] ,
139+ cost : { input : 0.001 , output : 0.005 , cacheRead : 0 , cacheWrite : 0 } ,
140+ contextWindow : 131072 ,
141+ } ,
142+ } ;
143+
144+ const DEFAULT_CONTEXT_WINDOW = 131072 ;
145+ const ZERO_COST = { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ;
146+
147+ export function modelFromId ( id : string ) : Omit < ModelEntry , "provider" > {
148+ const known = MODEL_METADATA [ id ] ;
149+ if ( known ) return { id, ...known } ;
150+ const raw = id . split ( "/" ) . pop ( ) ?? id ;
151+ const name = raw . replace ( / [ - _ ] / g, " " ) . replace ( / \b \w / g, ( c ) => c . toUpperCase ( ) ) ;
152+ return {
153+ id,
154+ name,
155+ maxTokens : DEFAULT_CONTEXT_WINDOW ,
156+ reasoning : false ,
157+ input : [ "text" ] ,
158+ cost : ZERO_COST ,
159+ contextWindow : DEFAULT_CONTEXT_WINDOW ,
160+ } ;
161+ }
162+
163+ /** Static fallback models used when upstream fetch fails. */
164+ export const DEFAULT_SURF_MODELS : Array < Omit < ModelEntry , "provider" > > =
165+ Object . keys ( MODEL_METADATA ) . map ( modelFromId ) ;
166+
167+ const MODELS_CACHE_TTL_MS = 5 * 60 * 1000 ;
168+ let modelsCache : { models : Array < Omit < ModelEntry , "provider" > > ; fetchedAt : number } | null = null ;
169+
170+ export async function fetchUpstreamModels (
171+ upstreamUrl : string ,
172+ ) : Promise < Array < Omit < ModelEntry , "provider" > > > {
173+ if ( modelsCache && Date . now ( ) - modelsCache . fetchedAt < MODELS_CACHE_TTL_MS ) {
174+ return modelsCache . models ;
175+ }
176+ try {
177+ const res = await globalThis . fetch ( `${ upstreamUrl } /v1/models` , {
178+ signal : AbortSignal . timeout ( 5000 ) ,
179+ } ) ;
180+ if ( ! res . ok ) return modelsCache ?. models ?? DEFAULT_SURF_MODELS ;
181+ const data = ( await res . json ( ) ) as { data ?: Array < { id : string } > } ;
182+ if ( ! data . data ?. length ) return modelsCache ?. models ?? DEFAULT_SURF_MODELS ;
183+ const models = data . data . map ( ( m ) => modelFromId ( m . id ) ) ;
184+ modelsCache = { models, fetchedAt : Date . now ( ) } ;
185+ return models ;
186+ } catch {
187+ return modelsCache ?. models ?? DEFAULT_SURF_MODELS ;
188+ }
189+ }
84190
85191export function resolveProviders ( config : Record < string , unknown > ) : {
86192 providers : ResolvedProviderConfig [ ] ;
0 commit comments