@@ -167,3 +167,176 @@ describe("OpenCode2Client message history", () => {
167167 } ) ,
168168 ) ;
169169} ) ;
170+
171+ describe ( "OpenCode2Client usage limits" , ( ) => {
172+ it . effect ( "reads the connected workspace and maps Go and Console budgets" , ( ) =>
173+ Effect . gen ( function * ( ) {
174+ let workspace = "wrk_remote" ;
175+ const transport = HttpClient . make ( ( request ) => {
176+ assert . strictEqual (
177+ request . url ,
178+ "https://remote.example/prefix/api/experimental/usage/limits" ,
179+ ) ;
180+ assert . strictEqual ( request . headers . authorization , utf8Basic ( "remote-password" ) ) ;
181+ assert . deepStrictEqual ( Array . from ( request . urlParams ) , [
182+ [ "location[directory]" , "/remote/project" ] ,
183+ ] ) ;
184+ return Effect . succeed (
185+ HttpClientResponse . fromWeb (
186+ request ,
187+ Response . json ( {
188+ location : { directory : "/remote/project" } ,
189+ data : {
190+ workspace : {
191+ id : workspace ,
192+ name : workspace ,
193+ usageURL : "https://opencode.ai/console/" + workspace ,
194+ } ,
195+ go : {
196+ rolling : { percent : 23 , limited : false , resetsAt : 1793491200000 } ,
197+ weekly : { percent : 99 , limited : true , resetsAt : 1793491200000 } ,
198+ monthly : { percent : 12 , limited : false , resetsAt : 1793491200000 } ,
199+ } ,
200+ budgets : [
201+ {
202+ id : "all" ,
203+ limit : 20 ,
204+ spent : 15 ,
205+ scope : "combined" ,
206+ per : "set" ,
207+ coverage : "all" ,
208+ models : [ ] ,
209+ vendors : [ ] ,
210+ picked : false ,
211+ shared : true ,
212+ resetsAt : 1793491200000 ,
213+ } ,
214+ {
215+ id : "models" ,
216+ limit : 10 ,
217+ spent : 12 ,
218+ scope : "each" ,
219+ per : "model" ,
220+ coverage : "models" ,
221+ models : [
222+ { providerID : "opencode" , modelID : "a" , name : "Model A" , spent : 12 } ,
223+ { providerID : "opencode" , modelID : "b" , name : "Model B" , spent : 2 } ,
224+ ] ,
225+ vendors : [ ] ,
226+ picked : true ,
227+ shared : false ,
228+ resetsAt : 1793491200000 ,
229+ } ,
230+ {
231+ id : "zz_missing" ,
232+ limit : 10 ,
233+ spent : 10 ,
234+ scope : "each" ,
235+ per : "model" ,
236+ coverage : "models" ,
237+ models : [ { providerID : "opencode" , modelID : "unknown" , name : "Unknown spend" } ] ,
238+ shared : false ,
239+ resetsAt : 1793491200000 ,
240+ } ,
241+ {
242+ id : "zero" ,
243+ limit : 0 ,
244+ spent : 0 ,
245+ scope : "each" ,
246+ per : "set" ,
247+ coverage : "all" ,
248+ models : [ ] ,
249+ vendors : [ ] ,
250+ picked : false ,
251+ shared : false ,
252+ resetsAt : 1793491200000 ,
253+ } ,
254+ ] ,
255+ } ,
256+ } ) ,
257+ ) ,
258+ ) ;
259+ } ) ;
260+ const opencode = yield * OpenCode2Client . make . pipe (
261+ Effect . provideService ( HttpClient . HttpClient , transport ) ,
262+ ) ;
263+ const api = yield * opencode . connect ( {
264+ baseUrl : "https://remote.example/prefix" ,
265+ password : "remote-password" ,
266+ } ) ;
267+ const first = yield * api . usageLimits ( "/remote/project" ) ;
268+ assert . isDefined ( first ) ;
269+ assert . deepStrictEqual (
270+ first ?. windows . map ( ( window ) => window . usedPercent ) ,
271+ [ 23 , 100 , 12 , 75 , 100 , 20 , 100 , 100 ] ,
272+ ) ;
273+ assert . isTrue (
274+ first ?. windows . every ( ( window ) => window . resetsAt === "2026-11-01T00:00:00.000Z" ) ,
275+ ) ;
276+ assert . include ( first ?. windows [ 3 ] ?. label ?? "" , "Shared" ) ;
277+ assert . include ( first ?. windows [ 4 ] ?. label ?? "" , "Model A" ) ;
278+ assert . deepStrictEqual ( first ?. windows [ 4 ] ?. modelIds , [ "opencode/a" ] ) ;
279+ assert . strictEqual ( first ?. windows [ 3 ] ?. modelIds , undefined ) ;
280+ assert . deepStrictEqual ( first ?. windows [ 7 ] ?. modelIds , [ ] ) ;
281+ assert . include ( first ?. windows [ 7 ] ?. label ?? "" , "Most used model" ) ;
282+ assert . strictEqual ( first ?. credentialFingerprint , undefined ) ;
283+ workspace = "wrk_switched" ;
284+ const next = yield * api . usageLimits ( "/remote/project" ) ;
285+ assert . strictEqual ( next ?. externalUsage ?. label , "wrk_switched" ) ;
286+ assert . strictEqual ( next ?. externalUsage ?. url , "https://opencode.ai/console/wrk_switched" ) ;
287+ } ) ,
288+ ) ;
289+
290+ it . effect ( "recognizes an older server's SPA fallback" , ( ) =>
291+ Effect . gen ( function * ( ) {
292+ const opencode = yield * OpenCode2Client . make . pipe (
293+ Effect . provideService (
294+ HttpClient . HttpClient ,
295+ HttpClient . make ( ( request ) =>
296+ Effect . succeed (
297+ HttpClientResponse . fromWeb (
298+ request ,
299+ new Response ( "<!doctype html><title>OpenCode</title>" , {
300+ headers : { "content-type" : "text/html; charset=utf-8" } ,
301+ } ) ,
302+ ) ,
303+ ) ,
304+ ) ,
305+ ) ,
306+ ) ;
307+ const api = yield * opencode . connect ( { baseUrl : "https://old.example" , password : "secret" } ) ;
308+ assert . strictEqual ( yield * api . usageLimits ( "/remote" ) , undefined ) ;
309+ } ) ,
310+ ) ;
311+
312+ it . effect ( "distinguishes missing endpoints, signed out, and failed remote probes" , ( ) =>
313+ Effect . gen ( function * ( ) {
314+ for ( const [ status , body , reason ] of [
315+ [ 404 , { } , undefined ] ,
316+ [ 405 , { } , undefined ] ,
317+ [ 200 , { location : { directory : "/remote" } , data : null } , "unsupported" ] ,
318+ [ 503 , { } , "probeFailed" ] ,
319+ [ 401 , { } , "probeFailed" ] ,
320+ [ 200 , { data : { workspace : { } } } , "probeFailed" ] ,
321+ ] as const ) {
322+ const opencode = yield * OpenCode2Client . make . pipe (
323+ Effect . provideService (
324+ HttpClient . HttpClient ,
325+ HttpClient . make ( ( request ) =>
326+ Effect . succeed ( HttpClientResponse . fromWeb ( request , Response . json ( body , { status } ) ) ) ,
327+ ) ,
328+ ) ,
329+ ) ;
330+ const api = yield * opencode . connect ( {
331+ baseUrl : "https://remote.example" ,
332+ password : "secret" ,
333+ } ) ;
334+ const result = yield * api . usageLimits ( "/remote" ) ;
335+ if ( reason === undefined ) assert . strictEqual ( result , undefined ) ;
336+ else assert . strictEqual ( result ?. unavailable ?. reason , reason ) ;
337+ if ( status === 200 && reason === "unsupported" )
338+ assert . include ( result ?. unavailable ?. message ?? "" , "signed in" ) ;
339+ }
340+ } ) ,
341+ ) ;
342+ } ) ;
0 commit comments