1- use crate :: cli_client:: CliClient ;
21use anyhow:: Result ;
32use chrono:: Utc ;
43use clap:: { Args , Subcommand } ;
5- use rullm_core:: LlmError ;
4+ use serde:: Deserialize ;
5+ use std:: collections:: HashMap ;
66use strum:: IntoEnumIterator ;
77
88use crate :: {
99 aliases:: UserAliasConfig ,
1010 args:: { Cli , CliConfig } ,
11- client,
1211 commands:: { ModelsCache , format_duration} ,
1312 constants:: { ALIASES_CONFIG_FILE , MODEL_FILE_NAME } ,
1413 output:: OutputLevel ,
@@ -23,14 +22,14 @@ pub struct ModelsArgs {
2322
2423#[ derive( Subcommand ) ]
2524pub enum ModelsAction {
26- /// List available models for the current provider (default)
25+ /// List cached models
2726 List ,
2827 /// Set a default model that will be used when --model is not supplied
2928 Default {
3029 /// Model identifier in the form provider:model-name (e.g. openai:gpt-4o)
3130 model : Option < String > ,
3231 } ,
33- /// Fetch fresh models from all providers with available API keys and update local cache
32+ /// Fetch fresh models from models.dev and update local cache
3433 Update ,
3534 /// Clear the local models cache
3635 Clear ,
@@ -41,7 +40,7 @@ impl ModelsArgs {
4140 & self ,
4241 output_level : OutputLevel ,
4342 cli_config : & mut CliConfig ,
44- cli : & Cli ,
43+ _cli : & Cli ,
4544 ) -> Result < ( ) > {
4645 match & self . action {
4746 ModelsAction :: List => {
@@ -67,34 +66,28 @@ impl ModelsArgs {
6766 }
6867 }
6968 ModelsAction :: Update => {
70- // List of supported providers
71- let providers = Provider :: iter ( ) ;
72- let mut updated = vec ! [ ] ;
73- let mut skipped = vec ! [ ] ;
74-
75- for provider in providers {
76- let provider = format ! ( "{provider}" ) ;
77- // Try to create a client for this provider
78- let model_hint = format ! ( "{provider}:dummy" ) ; // dummy model name, just to get the client
79- let client = match client:: from_model ( & model_hint, cli, cli_config) . await {
80- Ok ( c) => c,
81- Err ( _) => {
82- skipped. push ( provider) ;
83- continue ;
84- }
85- } ;
86- match update_models ( cli_config, & client, output_level) . await {
87- Ok ( _) => updated. push ( provider) ,
88- Err ( _) => skipped. push ( provider) ,
89- }
69+ let supported: Vec < & str > = Provider :: iter ( ) . map ( |p| p. models_dev_id ( ) ) . collect ( ) ;
70+
71+ crate :: output:: progress ( "Fetching models from models.dev..." , output_level) ;
72+
73+ let models = fetch_models_from_models_dev ( & supported) . await ?;
74+ if models. is_empty ( ) {
75+ anyhow:: bail!( "No models returned by models.dev" ) ;
9076 }
9177
92- if !skipped . is_empty ( ) {
93- crate :: output :: note (
94- & format ! ( "Skipped (no API key or error): {}" , skipped . join ( ", " ) ) ,
95- output_level ,
96- ) ;
78+ let cache = ModelsCache :: new ( models ) ;
79+ let path = cli_config . data_base_path . join ( MODEL_FILE_NAME ) ;
80+
81+ if let Some ( parent ) = path . parent ( ) {
82+ std :: fs :: create_dir_all ( parent ) ? ;
9783 }
84+
85+ std:: fs:: write ( & path, serde_json:: to_string_pretty ( & cache) ?) ?;
86+
87+ crate :: output:: success (
88+ & format ! ( "Updated {} models" , cache. models. len( ) ) ,
89+ output_level,
90+ ) ;
9891 }
9992 ModelsAction :: Clear => {
10093 clear_models_cache ( cli_config, output_level) ?;
@@ -216,85 +209,6 @@ pub fn clear_models_cache(cli_config: &CliConfig, output_level: OutputLevel) ->
216209 Ok ( ( ) )
217210}
218211
219- pub async fn update_models (
220- cli_config : & mut CliConfig ,
221- client : & CliClient ,
222- output_level : OutputLevel ,
223- ) -> Result < ( ) , LlmError > {
224- crate :: output:: progress (
225- & format ! (
226- "Fetching models from {}..." ,
227- crate :: output:: format_provider( client. provider_name( ) )
228- ) ,
229- output_level,
230- ) ;
231-
232- let mut models = client. available_models ( ) . await . map_err ( |e| {
233- crate :: output:: error ( & format ! ( "Failed to fetch models: {e}" ) , output_level) ;
234- e
235- } ) ?;
236-
237- if models. is_empty ( ) {
238- crate :: output:: error ( "No models returned by provider" , output_level) ;
239- return Err ( LlmError :: model (
240- "No models returned by provider" . to_string ( ) ,
241- ) ) ;
242- }
243-
244- models. sort ( ) ;
245- models. dedup ( ) ;
246-
247- _cache_models ( cli_config, client. provider_name ( ) , & models) . map_err ( |e| {
248- crate :: output:: error ( & format ! ( "Failed to update models cache: {e}" ) , output_level) ;
249- LlmError :: unknown ( e. to_string ( ) )
250- } ) ?;
251-
252- crate :: output:: success (
253- & format ! (
254- "Updated {} models for {}" ,
255- models. len( ) ,
256- client. provider_name( )
257- ) ,
258- output_level,
259- ) ;
260-
261- Ok ( ( ) )
262- }
263-
264- fn _cache_models ( cli_config : & CliConfig , provider_name : & str , models : & [ String ] ) -> Result < ( ) > {
265- use std:: fs;
266-
267- let path = cli_config. data_base_path . join ( MODEL_FILE_NAME ) ;
268- // TODO: we shouldn't need to do this here, this should be done while cli_config is created
269- // TODO: Remove if we already do this.
270- if let Some ( parent) = path. parent ( ) {
271- fs:: create_dir_all ( parent) ?;
272- }
273-
274- // Load existing cache if present
275- let mut entries = if let Ok ( Some ( cache) ) = load_models_cache ( cli_config) {
276- cache. models
277- } else {
278- Vec :: new ( )
279- } ;
280-
281- // Remove all entries for this provider
282- let prefix = format ! ( "{}:" , provider_name. to_lowercase( ) ) ;
283- entries. retain ( |m| !m. starts_with ( & prefix) ) ;
284-
285- // Add new models for this provider
286- let new_entries: Vec < String > = models
287- . iter ( )
288- . map ( |m| format ! ( "{}:{}" , provider_name. to_lowercase( ) , m) )
289- . collect ( ) ;
290- entries. extend ( new_entries) ;
291-
292- let cache = ModelsCache :: new ( entries) ;
293- let json = serde_json:: to_string_pretty ( & cache) ?;
294- fs:: write ( path, json) ?;
295- Ok ( ( ) )
296- }
297-
298212pub ( crate ) fn load_models_cache ( cli_config : & CliConfig ) -> Result < Option < ModelsCache > > {
299213 use std:: fs;
300214
@@ -314,3 +228,35 @@ pub(crate) fn load_models_cache(cli_config: &CliConfig) -> Result<Option<ModelsC
314228 // Old format doesn't have timestamp info
315229 Ok ( None )
316230}
231+
232+ #[ derive( Deserialize ) ]
233+ struct ModelsDevProvider {
234+ models : HashMap < String , ModelsDevModel > ,
235+ }
236+
237+ #[ derive( Deserialize ) ]
238+ struct ModelsDevModel {
239+ #[ serde( default ) ]
240+ id : Option < String > ,
241+ }
242+
243+ async fn fetch_models_from_models_dev ( supported_providers : & [ & str ] ) -> Result < Vec < String > > {
244+ let response = reqwest:: get ( "https://models.dev/api.json" )
245+ . await ?
246+ . error_for_status ( ) ?;
247+ let providers: HashMap < String , ModelsDevProvider > = response. json ( ) . await ?;
248+
249+ let mut all_models = Vec :: new ( ) ;
250+ for provider_id in supported_providers {
251+ if let Some ( provider) = providers. get ( * provider_id) {
252+ for ( model_id, model) in & provider. models {
253+ let id = model. id . as_deref ( ) . unwrap_or ( model_id) ;
254+ all_models. push ( format ! ( "{provider_id}:{id}" ) ) ;
255+ }
256+ }
257+ }
258+
259+ all_models. sort ( ) ;
260+ all_models. dedup ( ) ;
261+ Ok ( all_models)
262+ }
0 commit comments