@@ -6,14 +6,21 @@ import { Installation } from "../installation"
66import { Flag } from "../flag/flag"
77import { lazy } from "@/util/lazy"
88import { Filesystem } from "../util/filesystem"
9+ import { Flock } from "@/util/flock"
10+ import { Hash } from "@/util/hash"
911
1012// Try to import bundled snapshot (generated at build time)
1113// Falls back to undefined in dev mode when snapshot doesn't exist
1214/* @ts -ignore */
1315
1416export namespace ModelsDev {
1517 const log = Log . create ( { service : "models.dev" } )
16- const filepath = path . join ( Global . Path . cache , "models.json" )
18+ const source = url ( )
19+ const filepath = path . join (
20+ Global . Path . cache ,
21+ source === "https://models.dev" ? "models.json" : `models-${ Hash . fast ( source ) } .json` ,
22+ )
23+ const ttl = 5 * 60 * 1000
1724
1825 export const Model = z . object ( {
1926 id : z . string ( ) ,
@@ -85,6 +92,22 @@ export namespace ModelsDev {
8592 return Flag . OPENCODE_MODELS_URL || "https://models.dev"
8693 }
8794
95+ function fresh ( ) {
96+ return Date . now ( ) - Number ( Filesystem . stat ( filepath ) ?. mtimeMs ?? 0 ) < ttl
97+ }
98+
99+ function skip ( force : boolean ) {
100+ return ! force && fresh ( )
101+ }
102+
103+ const fetchApi = async ( ) => {
104+ const result = await fetch ( `${ url ( ) } /api.json` , {
105+ headers : { "User-Agent" : Installation . USER_AGENT } ,
106+ signal : AbortSignal . timeout ( 10000 ) ,
107+ } )
108+ return { ok : result . ok , text : await result . text ( ) }
109+ }
110+
88111 export const Data = lazy ( async ( ) => {
89112 const result = await Filesystem . readJson ( Flag . OPENCODE_MODELS_PATH ?? filepath ) . catch ( ( ) => { } )
90113 if ( result ) return result
@@ -94,30 +117,37 @@ export namespace ModelsDev {
94117 . catch ( ( ) => undefined )
95118 if ( snapshot ) return snapshot
96119 if ( Flag . OPENCODE_DISABLE_MODELS_FETCH ) return { }
97- const json = await fetch ( `${ url ( ) } /api.json` ) . then ( ( x ) => x . text ( ) )
98- return JSON . parse ( json )
120+ return Flock . withLock ( `models-dev:${ filepath } ` , async ( ) => {
121+ const result = await Filesystem . readJson ( Flag . OPENCODE_MODELS_PATH ?? filepath ) . catch ( ( ) => { } )
122+ if ( result ) return result
123+ const result2 = await fetchApi ( )
124+ if ( result2 . ok ) {
125+ await Filesystem . write ( filepath , result2 . text ) . catch ( ( e ) => {
126+ log . error ( "Failed to write models cache" , { error : e } )
127+ } )
128+ }
129+ return JSON . parse ( result2 . text )
130+ } )
99131 } )
100132
101133 export async function get ( ) {
102134 const result = await Data ( )
103135 return result as Record < string , Provider >
104136 }
105137
106- export async function refresh ( ) {
107- const result = await fetch ( `${ url ( ) } /api.json` , {
108- headers : {
109- "User-Agent" : Installation . USER_AGENT ,
110- } ,
111- signal : AbortSignal . timeout ( 10 * 1000 ) ,
138+ export async function refresh ( force = false ) {
139+ if ( skip ( force ) ) return ModelsDev . Data . reset ( )
140+ await Flock . withLock ( `models-dev:${ filepath } ` , async ( ) => {
141+ if ( skip ( force ) ) return ModelsDev . Data . reset ( )
142+ const result = await fetchApi ( )
143+ if ( ! result . ok ) return
144+ await Filesystem . write ( filepath , result . text )
145+ ModelsDev . Data . reset ( )
112146 } ) . catch ( ( e ) => {
113147 log . error ( "Failed to fetch models.dev" , {
114148 error : e ,
115149 } )
116150 } )
117- if ( result && result . ok ) {
118- await Filesystem . write ( filepath , await result . text ( ) )
119- ModelsDev . Data . reset ( )
120- }
121151 }
122152}
123153
0 commit comments