@@ -4,12 +4,9 @@ use serde::{Deserialize, Serialize};
44use serde_json:: { json, Value } ;
55use std:: collections:: HashMap ;
66use std:: io:: { self , BufRead , Write } ;
7- use std:: time:: Duration ;
87use tokio:: sync:: mpsc;
98use uuid:: Uuid ;
109
11- const MODEL_DISCOVERY_TIMEOUT : Duration = Duration :: from_secs ( 3 ) ;
12-
1310#[ derive( Debug , Deserialize ) ]
1411pub struct JsonRpcRequest {
1512 pub id : Option < u64 > ,
@@ -235,7 +232,7 @@ impl AcpServer {
235232 if active_provider == "anthropic" {
236233 "claude-sonnet-4-20250514" . to_string ( )
237234 } else {
238- "gpt-4.1-nano " . to_string ( )
235+ "gpt-5.4-mini " . to_string ( )
239236 }
240237 } ) ;
241238 self . model_options = Self :: available_models ( ) . await ;
@@ -260,135 +257,18 @@ impl AcpServer {
260257 }
261258
262259 /// List available models based on configured credentials.
263- /// Queries provider APIs when possible, falls back to known defaults .
260+ /// Uses static model lists (same approach as Pi coding agent) .
264261 async fn available_models ( ) -> Vec < ModelOption > {
265- let has_anthropic = std:: env:: var ( "ANTHROPIC_API_KEY" ) . is_ok ( ) ;
266- let has_openai = crate :: auth:: load_tokens ( ) . is_ok ( ) ;
267-
268- let ( anthropic_models, openai_models) = tokio:: join!(
269- async {
270- if has_anthropic {
271- Self :: fetch_anthropic_models( )
272- . await
273- . unwrap_or_else( |_| Self :: fallback_anthropic_models( ) )
274- } else {
275- Vec :: new( )
276- }
277- } ,
278- async {
279- if has_openai {
280- Self :: fetch_openai_models( )
281- . await
282- . unwrap_or_else( |_| Self :: fallback_openai_models( ) )
283- } else {
284- Vec :: new( )
285- }
286- } ,
287- ) ;
288-
289- let mut models = anthropic_models;
290- models. extend ( openai_models) ;
291-
292- if models. is_empty ( ) {
293- models. push ( ModelOption :: new (
294- "none" ,
295- "No credentials configured" ,
296- "none" ,
297- ) ) ;
298- }
299- models
300- }
301-
302- /// Fetch models from Anthropic /v1/models API.
303- async fn fetch_anthropic_models ( ) -> Result < Vec < ModelOption > , String > {
304- let api_key = std:: env:: var ( "ANTHROPIC_API_KEY" ) . map_err ( |e| e. to_string ( ) ) ?;
305- let client = reqwest:: Client :: builder ( )
306- . timeout ( MODEL_DISCOVERY_TIMEOUT )
307- . build ( )
308- . map_err ( |e| e. to_string ( ) ) ?;
309- let resp = client
310- . get ( "https://api.anthropic.com/v1/models" )
311- . header ( "x-api-key" , & api_key)
312- . header ( "anthropic-version" , "2023-06-01" )
313- . send ( )
314- . await
315- . map_err ( |e| e. to_string ( ) ) ?;
316-
317- if !resp. status ( ) . is_success ( ) {
318- return Err ( format ! ( "Anthropic API returned {}" , resp. status( ) ) ) ;
319- }
320-
321- let body: Value = resp. json ( ) . await . map_err ( |e| e. to_string ( ) ) ?;
322- let mut models = Vec :: new ( ) ;
323- if let Some ( data) = body. get ( "data" ) . and_then ( |d| d. as_array ( ) ) {
324- for m in data {
325- if let Some ( id) = m. get ( "id" ) . and_then ( |v| v. as_str ( ) ) {
326- // Only include chat models (claude-*)
327- if id. starts_with ( "claude" ) {
328- let display = m. get ( "display_name" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( id) ;
329- models. push ( ModelOption :: new ( id, display, "anthropic" ) ) ;
330- }
331- }
332- }
333- }
334- if models. is_empty ( ) {
335- return Err ( "No models returned from Anthropic API" . to_string ( ) ) ;
336- }
337- Ok ( models)
262+ Self :: static_available_models ( )
338263 }
339264
340- /// Fetch models from OpenAI-compatible /models endpoint.
341- async fn fetch_openai_models ( ) -> Result < Vec < ModelOption > , String > {
342- let tokens = crate :: auth:: load_tokens ( ) . map_err ( |e| e. to_string ( ) ) ?;
343- let base_url = std:: env:: var ( "OPENAB_AGENT_OPENAI_BASE_URL" )
344- . unwrap_or_else ( |_| "https://chatgpt.com/backend-api" . to_string ( ) ) ;
345- let client = reqwest:: Client :: builder ( )
346- . timeout ( MODEL_DISCOVERY_TIMEOUT )
347- . build ( )
348- . map_err ( |e| e. to_string ( ) ) ?;
349- let resp = client
350- . get ( format ! ( "{}/models" , base_url) )
351- . bearer_auth ( & tokens. access_token )
352- . send ( )
353- . await
354- . map_err ( |e| e. to_string ( ) ) ?;
355-
356- if !resp. status ( ) . is_success ( ) {
357- return Err ( format ! ( "OpenAI API returned {}" , resp. status( ) ) ) ;
358- }
359-
360- let body: Value = resp. json ( ) . await . map_err ( |e| e. to_string ( ) ) ?;
361- let mut models = Vec :: new ( ) ;
362- // Handle both { "data": [...] } and [...] shapes
363- let items = body
364- . get ( "data" )
365- . and_then ( |d| d. as_array ( ) )
366- . or_else ( || body. as_array ( ) ) ;
367- if let Some ( data) = items {
368- for m in data {
369- if let Some ( id) = m. get ( "id" ) . and_then ( |v| v. as_str ( ) ) {
370- let name = m
371- . get ( "name" )
372- . or_else ( || m. get ( "id" ) )
373- . and_then ( |v| v. as_str ( ) )
374- . unwrap_or ( id) ;
375- models. push ( ModelOption :: new ( id, name, "openai" ) ) ;
376- }
377- }
378- }
379- if models. is_empty ( ) {
380- return Err ( "No models returned from OpenAI API" . to_string ( ) ) ;
381- }
382- Ok ( models)
383- }
384-
385- fn fallback_available_models ( ) -> Vec < ModelOption > {
265+ fn static_available_models ( ) -> Vec < ModelOption > {
386266 let mut models = Vec :: new ( ) ;
387267 if std:: env:: var ( "ANTHROPIC_API_KEY" ) . is_ok ( ) {
388- models. extend ( Self :: fallback_anthropic_models ( ) ) ;
268+ models. extend ( Self :: static_anthropic_models ( ) ) ;
389269 }
390270 if crate :: auth:: load_tokens ( ) . is_ok ( ) {
391- models. extend ( Self :: fallback_openai_models ( ) ) ;
271+ models. extend ( Self :: static_openai_models ( ) ) ;
392272 }
393273 if models. is_empty ( ) {
394274 models. push ( ModelOption :: new (
@@ -400,18 +280,36 @@ impl AcpServer {
400280 models
401281 }
402282
403- fn fallback_anthropic_models ( ) -> Vec < ModelOption > {
283+ fn static_anthropic_models ( ) -> Vec < ModelOption > {
284+ // From models.dev/api.json — Anthropic models with tool_call support.
285+ // Dated versions used for deterministic pinning.
404286 vec ! [
287+ ModelOption :: new( "claude-haiku-4-5-20251001" , "Claude Haiku 4.5" , "anthropic" ) ,
405288 ModelOption :: new( "claude-sonnet-4-20250514" , "Claude Sonnet 4" , "anthropic" ) ,
406- ModelOption :: new( "claude-haiku-4-20250514" , "Claude Haiku 4" , "anthropic" ) ,
289+ ModelOption :: new(
290+ "claude-sonnet-4-5-20250929" ,
291+ "Claude Sonnet 4.5" ,
292+ "anthropic" ,
293+ ) ,
294+ ModelOption :: new( "claude-sonnet-4-6" , "Claude Sonnet 4.6" , "anthropic" ) ,
295+ ModelOption :: new( "claude-opus-4-20250514" , "Claude Opus 4" , "anthropic" ) ,
296+ ModelOption :: new( "claude-opus-4-1-20250805" , "Claude Opus 4.1" , "anthropic" ) ,
297+ ModelOption :: new( "claude-opus-4-5-20251101" , "Claude Opus 4.5" , "anthropic" ) ,
298+ ModelOption :: new( "claude-opus-4-6" , "Claude Opus 4.6" , "anthropic" ) ,
299+ ModelOption :: new( "claude-opus-4-7" , "Claude Opus 4.7" , "anthropic" ) ,
300+ ModelOption :: new( "claude-opus-4-8" , "Claude Opus 4.8" , "anthropic" ) ,
407301 ]
408302 }
409303
410- fn fallback_openai_models ( ) -> Vec < ModelOption > {
304+ fn static_openai_models ( ) -> Vec < ModelOption > {
305+ // Static list matching Pi's openai-codex provider models.
306+ // chatgpt.com/backend-api/models does not support standard model listing,
307+ // so we maintain this list explicitly (same approach as Pi coding agent).
411308 vec ! [
412- ModelOption :: new( "gpt-4.1-nano" , "GPT-4.1 Nano" , "openai" ) ,
413- ModelOption :: new( "gpt-4.1-mini" , "GPT-4.1 Mini" , "openai" ) ,
414- ModelOption :: new( "o4-mini" , "o4-mini" , "openai" ) ,
309+ ModelOption :: new( "gpt-5.3-codex-spark" , "GPT-5.3 Codex Spark" , "openai" ) ,
310+ ModelOption :: new( "gpt-5.4" , "GPT-5.4" , "openai" ) ,
311+ ModelOption :: new( "gpt-5.4-mini" , "GPT-5.4 mini" , "openai" ) ,
312+ ModelOption :: new( "gpt-5.5" , "GPT-5.5" , "openai" ) ,
415313 ]
416314 }
417315
@@ -487,7 +385,7 @@ impl AcpServer {
487385 }
488386
489387 let models = if self . model_options . is_empty ( ) {
490- Self :: fallback_available_models ( )
388+ Self :: static_available_models ( )
491389 } else {
492390 self . model_options . clone ( )
493391 } ;
0 commit comments