opencode-navigator 0.19.3 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1027 @@
1
+ import {PLUGIN_ID
2
+ } from "./chunk-cmmnfes2.js";
3
+ function createCodexAppServerClient(input){const command=input.command??["codex","app-server","--stdio"]
4
+ const timeoutMs=input.timeoutMs??1e4
5
+ const pending=new Map
6
+ let child
7
+ let startup
8
+ let sequence=0
9
+ let disposed=false
10
+ function failAll(error){for(const [id,request] of pending){clearTimeout(request.timer)
11
+ request.cleanup()
12
+ request.reject(error)
13
+ pending.delete(id)
14
+ }}
15
+ function write(message){if(!child?.stdin||typeof child.stdin==="number")
16
+ throw new Error("Codex app-server is unavailable")
17
+ child.stdin.write(`${JSON.stringify(message)}
18
+ `)
19
+ }
20
+ function accept(line){let message
21
+ try{message=JSON.parse(line)
22
+ } catch{return
23
+ }
24
+ if(typeof message.id!=="number")
25
+ return
26
+ const request=pending.get(message.id)
27
+ if(!request)
28
+ return
29
+ pending.delete(message.id)
30
+ clearTimeout(request.timer)
31
+ request.cleanup()
32
+ if(message.error){request.reject(new Error(message.error.code===-32601?"Codex app-server method unavailable":"Codex app-server request failed"))
33
+ return
34
+ }
35
+ request.resolve(message.result)
36
+ }
37
+ function stop(process,error){if(child!==process)
38
+ return
39
+ child=undefined
40
+ startup=undefined
41
+ failAll(error)
42
+ process.kill()
43
+ }
44
+ async function read(process){const output=process.stdout
45
+ if(!output||typeof output==="number")
46
+ return
47
+ const decoder=new TextDecoder
48
+ const reader=output.getReader()
49
+ let buffer=""
50
+ try{while(true){const next=await reader.read()
51
+ if(next.done)
52
+ break
53
+ buffer += decoder.decode(next.value,{stream:true})
54
+ if(buffer.length>1e6)
55
+ throw new Error("Codex app-server response exceeds 1 MB")
56
+ let end
57
+ while((end=buffer.indexOf(`
58
+ `))!==-1){const line=buffer.slice(0,end)
59
+ buffer=buffer.slice(end + 1)
60
+ if(line.length>0)
61
+ accept(line)
62
+ }}} catch(error){stop(process,error instanceof Error?error:new Error("Codex app-server stream failed"))
63
+ } finally{stop(process,new Error("Codex app-server closed"))
64
+ }}
65
+ function send(method,params,signal){if(disposed)
66
+ return Promise.reject(new DOMException("Codex app-server closed","AbortError"))
67
+ if(!child)
68
+ return Promise.reject(new Error("Codex app-server is unavailable"))
69
+ return new Promise((resolve,reject)=>{const id=++sequence
70
+ const abort=()=>{const request=pending.get(id)
71
+ if(!request)
72
+ return
73
+ pending.delete(id)
74
+ clearTimeout(request.timer)
75
+ request.cleanup()
76
+ reject(new DOMException("Codex request cancelled","AbortError"))
77
+ }
78
+ const timer=setTimeout(()=>{pending.delete(id)
79
+ signal?.removeEventListener("abort",abort)
80
+ reject(new Error("Codex app-server timed out"))
81
+ },timeoutMs)
82
+ if(signal?.aborted){clearTimeout(timer)
83
+ reject(new DOMException("Codex request cancelled","AbortError"))
84
+ return
85
+ }
86
+ signal?.addEventListener("abort",abort,{once:true})
87
+ pending.set(id,{resolve,reject,timer,cleanup:()=>signal?.removeEventListener("abort",abort)})
88
+ try{write({id,method,params})
89
+ } catch(error){abort()
90
+ reject(error)
91
+ }})
92
+ }
93
+ async function initialize(){if(disposed)
94
+ throw new Error("Codex app-server is unavailable")
95
+ const process=Bun.spawn([...command],{stdin:"pipe",stdout:"pipe",stderr:"ignore"})
96
+ child=process
97
+ read(process)
98
+ process.exited.then(()=>stop(process,new Error("Codex app-server closed")))
99
+ try{await send("initialize",{clientInfo:{name:"opencode-navigator",title:"Navigator",version:input.version},capabilities:null
100
+ })
101
+ write({method:"initialized"})
102
+ } catch(error){stop(process,error instanceof Error?error:new Error("Codex initialization failed"))
103
+ throw error
104
+ }}
105
+ return{async request(method,params={},signal){if(disposed)
106
+ throw new DOMException("Codex app-server closed","AbortError")
107
+ const starting=startup ??= initialize()
108
+ try{await starting
109
+ } catch(error){if(startup===starting)
110
+ startup=undefined
111
+ throw error
112
+ }
113
+ return await send(method,params,signal)
114
+ },dispose(){disposed=true
115
+ failAll(new DOMException("Codex app-server closed","AbortError"))
116
+ child?.kill()
117
+ }}}
118
+ function normalizeProviderQuota(snapshot){if(!snapshot.providerId||!snapshot.providerName||!snapshot.model.providerID||!snapshot.model.modelID||!snapshot.model.sessionID||!Number.isFinite(snapshot.fetchedAt)){throw new Error("Invalid provider quota identity")
119
+ }
120
+ const windows=snapshot.windows.map((window)=>{if(!window.id||!window.label||window.modelID!==snapshot.model.modelID||!window.unit||window.used!==undefined&&!Number.isFinite(window.used)||window.remaining!==undefined&&!Number.isFinite(window.remaining)||window.used===undefined&&window.remaining===undefined&&window.total===undefined||window.total!==undefined&&(!Number.isFinite(window.total)||window.total<0)||window.durationMinutes!==undefined&&(!Number.isFinite(window.durationMinutes)||window.durationMinutes<0)||window.resetsAt!==undefined&&!Number.isFinite(window.resetsAt)){throw new Error("Quota window does not match the selected model or contains invalid values")
121
+ }
122
+ return{...window}})
123
+ const bankedResets=snapshot.bankedResets
124
+ const balances=snapshot.balances?.map((balance)=>{if(!balance.id||!balance.label||balance.scope!=="account"&&balance.scope!=="key"||balance.amount===undefined&&!balance.unlimited||balance.amount!==undefined&&(typeof balance.amount!=="string"||!/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(balance.amount)||!Number.isFinite(Number(balance.amount)))||balance.unit!==undefined&&(typeof balance.unit!=="string"||!balance.unit))
125
+ throw new Error("Invalid provider balance")
126
+ return{...balance}})
127
+ if(bankedResets&&(!Number.isSafeInteger(bankedResets.availableCount)||bankedResets.availableCount<0)){throw new Error("Invalid banked reset count")
128
+ }
129
+ return{...snapshot,windows,...balances&&{balances},...bankedResets&&{bankedResets:{availableCount:bankedResets.availableCount,...bankedResets.credits&&{credits:[...bankedResets.credits]}}}}}
130
+ import {createSignal as createSignal2,untrack} from "solid-js"
131
+ import {createSignal} from "solid-js"
132
+ var idle={status:"idle"}
133
+ function isAbortError(cause){return typeof DOMException!=="undefined"&&cause instanceof DOMException&&cause.name==="AbortError"||cause instanceof Error&&cause.name==="AbortError"||typeof cause==="object"&&cause!==null&&"name" in cause&&cause.name==="AbortError"
134
+ }
135
+ function isRetryableError(cause){if(isAbortError(cause))
136
+ return false
137
+ if(typeof cause!=="object"||cause===null)
138
+ return true
139
+ const value=cause
140
+ const status=typeof value.status==="number"?value.status:value.response?.status
141
+ if(typeof status!=="number")
142
+ return true
143
+ return status===408||status===425||status===429||status>=500
144
+ }
145
+ function requestError(cause,operation,target){return{operation,target,message:cause instanceof Error?cause.message:`Failed to ${operation}`,retryable:isRetryableError(cause)
146
+ }}
147
+ function createRequestState(lifecycleSignal){const [states,setStates]=createSignal({})
148
+ const active=new Map
149
+ let sequence=0
150
+ function state(target){return states()[target]??idle
151
+ }
152
+ function abort(target){const request=active.get(target)
153
+ if(!request)
154
+ return
155
+ request.controller.abort()
156
+ active.delete(target)
157
+ const current=state(target)
158
+ setStates((values)=>({...values,[target]:current.lastSuccessfulAt?{status:"ready",lastSuccessfulAt:current.lastSuccessfulAt}:idle
159
+ }))
160
+ }
161
+ function abortAll(){for(const target of active.keys())
162
+ abort(target)
163
+ }
164
+ lifecycleSignal?.addEventListener("abort",abortAll,{once:true})
165
+ function start(target,operation,hasData,force=false){const previous=active.get(target)
166
+ if(previous&&!force)
167
+ return
168
+ previous?.controller.abort()
169
+ const id=++sequence
170
+ const controller=new AbortController
171
+ if(lifecycleSignal?.aborted)
172
+ controller.abort(lifecycleSignal.reason)
173
+ const current=state(target)
174
+ active.set(target,{id,controller})
175
+ setStates((values)=>({...values,[target]:{status:hasData?"refreshing":"loading",lastSuccessfulAt:current.lastSuccessfulAt
176
+ }}))
177
+ function isCurrent(){return active.get(target)?.id===id
178
+ }
179
+ function finish(){if(isCurrent())
180
+ active.delete(target)
181
+ }
182
+ return{signal:controller.signal,isCurrent,succeed(){if(!isCurrent())
183
+ return
184
+ setStates((values)=>({...values,[target]:{status:"ready",lastSuccessfulAt:Date.now()}}))
185
+ },fail(cause){if(!isCurrent())
186
+ return
187
+ if(controller.signal.aborted||isAbortError(cause)){setStates((values)=>({...values,[target]:current.lastSuccessfulAt?{status:"ready",lastSuccessfulAt:current.lastSuccessfulAt}:idle
188
+ }))
189
+ return
190
+ }
191
+ setStates((values)=>({...values,[target]:{status:"error",error:requestError(cause,operation,target),lastSuccessfulAt:current.lastSuccessfulAt
192
+ }}))
193
+ },finish
194
+ }}
195
+ return{state,start,abort,abortAll}}
196
+ function createProviderLimitsController(adapters,selectedModel,signal,refreshContext){const requests=createRequestState(signal)
197
+ const [snapshots,setSnapshots]=createSignal2({})
198
+ const targetKey=(model)=>JSON.stringify(model)
199
+ let activeKey
200
+ let targetAbort=new AbortController
201
+ function current(){const model=selectedModel()
202
+ const adapter=model&&adapters.find((candidate)=>candidate.supports(model))
203
+ const key=model?targetKey(model):undefined
204
+ if(activeKey!==key){untrack(requests.abortAll)
205
+ targetAbort.abort()
206
+ targetAbort=new AbortController
207
+ activeKey=key
208
+ }
209
+ return{model,adapter}}
210
+ async function refresh(force=false){if(force&&refreshContext)
211
+ await refreshContext()
212
+ const{model,adapter}=current()
213
+ if(!model||!adapter)
214
+ return
215
+ const key=targetKey(model)
216
+ const request=requests.start(key,"read provider limits",Boolean(snapshots()[key]),force)
217
+ if(!request)
218
+ return
219
+ try{const snapshot=normalizeProviderQuota(await adapter.read(model,request.signal,force))
220
+ if(!request.isCurrent()||targetKey(current().model??model)!==key)
221
+ return
222
+ setSnapshots((value)=>({...value,[key]:snapshot}))
223
+ request.succeed()
224
+ } catch(error){const status=error?.status??error?.response?.status
225
+ if(status===429&&request.isCurrent()){setSnapshots((value)=>({...value,[key]:{model,providerId:adapter.id,providerName:adapter.name,fetchedAt:Date.now(),availability:"rate_limited",windows:[]
226
+ }}))
227
+ request.succeed()
228
+ } else
229
+ request.fail(error)
230
+ } finally{request.finish()
231
+ }}
232
+ return{current,targetSignal(){current()
233
+ return signal?AbortSignal.any([signal,targetAbort.signal]):targetAbort.signal
234
+ },refresh,snapshot(){const{model,adapter}=current()
235
+ return model&&adapter?snapshots()[targetKey(model)]:undefined
236
+ },state(){const{model,adapter}=current()
237
+ return model&&adapter?requests.state(targetKey(model)):{status:"idle"}},dispose(){targetAbort.abort()
238
+ requests.abortAll()
239
+ for(const adapter of adapters)
240
+ adapter.dispose?.()
241
+ }}}
242
+ function windows(limit,model){return ["primary","secondary"].flatMap((name)=>{const value=limit[name]
243
+ return value?[{id:`${limit.limitId ?? model.modelID}.${name}`,label:limit.limitName?`${limit.limitName} \xB7 ${name}`:name,limitID:limit.limitId??undefined,modelID:model.modelID,unit:"%",used:value.usedPercent,...value.windowDurationMins!==null&&{durationMinutes:value.windowDurationMins},...value.resetsAt!==null&&{resetsAt:value.resetsAt}}]:[]
244
+ })
245
+ }
246
+ function createCodexQuotaAdapter(client,validateHost){const adapter={id:"codex",name:"Codex CLI",supports:(model)=>(model.providerID==="openai"||model.hostConnection?.providerID==="openai")&&model.hostConnection?.method!=="api"&&Boolean(model.accountID)&&(!model.hostConnection||model.hostConnection.status==="ready"),...validateHost&&{validateHost},async discoverAccount(signal){const account=await client.request("account/read",{refreshToken:false},signal)
247
+ if(account.account?.type!=="chatgpt")
248
+ throw new Error("Sign in to Codex CLI with ChatGPT")
249
+ const usage=await client.request("account/rateLimits/read",{supportsLunaReserve:false,excludeResetCreditDetails:true},signal)
250
+ if(!usage.accountId)
251
+ throw new Error("Codex account identity is unavailable")
252
+ return usage.accountId
253
+ },async read(model,signal){const base={model,providerId:"codex",providerName:"Codex CLI",fetchedAt:Date.now(),windows:[]
254
+ }
255
+ if(validateHost&&!await validateHost(model,signal))
256
+ return{...base,availability:"unauthenticated"}
257
+ const account=await client.request("account/read",{refreshToken:false},signal)
258
+ if(account.account?.type!=="chatgpt")
259
+ return{...base,availability:"unauthenticated"}
260
+ const usage=await client.request("account/rateLimits/read",{supportsLunaReserve:false,excludeResetCreditDetails:false},signal)
261
+ if(!usage.accountId||usage.accountId!==model.accountID)
262
+ return{...base,availability:"unauthenticated"}
263
+ const candidates=Object.values(usage.rateLimitsByLimitId??{})
264
+ const matched=(candidates.length>0?candidates:[usage.rateLimits]).filter((limit)=>limit.normalModelSlug===model.modelID)
265
+ if(matched.length===0)
266
+ return{...base,accountId:usage.accountId,availability:"unsupported"}
267
+ const creditSummary=usage.rateLimitResetCredits
268
+ return normalizeProviderQuota({...base,accountId:usage.accountId,availability:"ready",ordinaryUsageAllowed:usage.ordinaryUsageAllowed??undefined,windows:matched.flatMap((limit)=>windows(limit,model)),...creditSummary&&{bankedResets:{availableCount:creditSummary.availableCount,...creditSummary.credits&&{credits:creditSummary.credits.map((credit)=>({id:credit.id,type:credit.resetType,status:credit.status,grantedAt:credit.grantedAt,...credit.expiresAt!==null&&{expiresAt:credit.expiresAt},...credit.title&&{title:credit.title},...credit.description&&{description:credit.description}}))
269
+ }}}})
270
+ },async prepareResetCreditConsumption(model,creditID,signal){const snapshot=await adapter.read(model,signal)
271
+ if(snapshot.availability==="unauthenticated"||!snapshot.accountId||snapshot.accountId!==model.accountID){throw new Error("Codex account is unavailable or changed")
272
+ }
273
+ if(snapshot.availability!=="ready"||snapshot.ordinaryUsageAllowed!==false||snapshot.windows.length===0){throw new Error("Selected model has no eligible rate-limit window")
274
+ }
275
+ const credits=snapshot.bankedResets
276
+ if(!credits?.availableCount)
277
+ throw new Error("Codex reset credit is unavailable")
278
+ if(creditID){const credit=credits.credits?.find((item)=>item.id===creditID)
279
+ if(!credit||credit.type!=="codexRateLimits"||credit.status!=="available"||credit.expiresAt!==undefined&&credit.expiresAt<=Math.floor(Date.now() / 1000)){throw new Error("Selected banked reset is no longer available")
280
+ }}
281
+ return snapshot
282
+ },async consumeResetCredit(model,attempt,signal){if(!model.accountID||model.accountID!==attempt.accountID)
283
+ throw new Error("Codex account changed")
284
+ if(attempt.uncertain){const latest=await adapter.read(model,signal)
285
+ if(latest.accountId!==attempt.accountID)
286
+ throw new Error("Codex account changed")
287
+ } else
288
+ await adapter.prepareResetCreditConsumption(model,attempt.creditID,signal)
289
+ signal.throwIfAborted()
290
+ if(validateHost&&!await validateHost(model,signal))
291
+ throw new Error("OpenCode account or connection changed")
292
+ signal.throwIfAborted()
293
+ const result=await client.request("account/rateLimitResetCredit/consume",{idempotencyKey:attempt.idempotencyKey,...attempt.creditID&&{creditId:attempt.creditID}},signal)
294
+ return result.outcome
295
+ },dispose:client.dispose
296
+ }
297
+ return adapter
298
+ }
299
+ import {createComponent as _$createComponent} from "@opentui/solid"
300
+ import {createSignal as createSignal3} from "solid-js"
301
+ import {GuardedConfirmation} from "./tui.js"
302
+ function createCodexAccountLink(input){const [busy,setBusy]=createSignal3(false)
303
+ async function link(){const selected=input.model()
304
+ if(busy()||!selected||selected.providerID!=="openai"&&selected.hostConnection?.providerID!=="openai"||!input.adapter.discoverAccount)
305
+ return
306
+ if(selected.hostConnection&&selected.hostConnection.status!=="ready")
307
+ return
308
+ const key=JSON.stringify(selected)
309
+ const current=()=>JSON.stringify(input.model())===key
310
+ const signal=input.api.lifecycle?.signal??new AbortController().signal
311
+ const validHost=async ()=>!input.adapter.validateHost||await input.adapter.validateHost(selected,signal)
312
+ setBusy(true)
313
+ try{if(!await validHost()||!current())
314
+ return
315
+ const accountID=await input.adapter.discoverAccount(signal)
316
+ if(!current())
317
+ return
318
+ if(selected.hostConnection?.accountID&&selected.hostConnection.accountID!==accountID){input.api.ui.toast({variant:"warning",title:"Codex account",message:"Sign in to Codex CLI with the account selected in OpenCode."
319
+ })
320
+ return
321
+ }
322
+ async function confirm(){if(busy()||!current())
323
+ return
324
+ setBusy(true)
325
+ try{if(!await validHost()||!current())
326
+ return
327
+ const latest=await input.adapter.discoverAccount(signal)
328
+ if(!current()||latest!==accountID)
329
+ return
330
+ input.preferences.setCodexConnectionBinding(selected.providerID,selected.hostConnection?.id,accountID)
331
+ input.dialogs.back()
332
+ } catch{if(current())
333
+ input.api.ui.toast({variant:"warning",title:"Codex account",message:"Account verification failed; try again."
334
+ })
335
+ } finally{setBusy(false)
336
+ }}
337
+ input.dialogs.open(()=>_$createComponent(GuardedConfirmation,{get api(){return input.api
338
+ },current,title:"Link Codex CLI account",get message(){return `Use Codex CLI account ${accountID} for ${selected.providerID}${selected.hostConnection?.id ? " connection" : ""} across models? Only confirm matching accounts.`
339
+ },onConfirm:()=>void confirm()
340
+ }))
341
+ } catch{if(current())
342
+ input.api.ui.toast({variant:"warning",title:"Codex account",message:"Install a supported Codex CLI and sign in with ChatGPT, then try again."
343
+ })
344
+ } finally{setBusy(false)
345
+ }}
346
+ return{link,busy
347
+ }}
348
+ import {effect as _$effect} from "@opentui/solid"
349
+ import {insertNode as _$insertNode} from "@opentui/solid"
350
+ import {insert as _$insert} from "@opentui/solid"
351
+ import {memo as _$memo} from "@opentui/solid"
352
+ import {setProp as _$setProp} from "@opentui/solid"
353
+ import {createElement as _$createElement} from "@opentui/solid"
354
+ function QuotaWindowRow(props){const kind=()=>props.window.kind==="rate_limit"?"API \xB7 ":props.window.kind==="configured_limit"?"Limit \xB7 ":""
355
+ const measurement=()=>{if(props.window.used!==undefined)
356
+ return `${props.window.used}${props.window.total === undefined ? "" : `/${props.window.total}`}${props.window.unit === "%" ? "" : " "}${props.window.unit} used`
357
+ if(props.window.remaining!==undefined)
358
+ return `${props.window.remaining} ${props.window.unit} remaining`
359
+ return `${props.window.total} ${props.window.unit} capacity \xB7 usage unknown`
360
+ }
361
+ return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createElement("text"),_el$4=_$createElement("text")
362
+ _$insertNode(_el$,_el$2)
363
+ _$insertNode(_el$,_el$3)
364
+ _$insertNode(_el$,_el$4)
365
+ _$setProp(_el$2,"wrapMode","none")
366
+ _$setProp(_el$2,"truncate",true)
367
+ _$insert(_el$2,kind,null)
368
+ _$insert(_el$2,()=>props.window.label,null)
369
+ _$insert(_el$2,(()=>{var _c$=_$memo(()=>props.window.durationMinutes===undefined)
370
+ return()=>_c$()?"":` \xB7 ${props.window.durationMinutes} min`
371
+ })(),null)
372
+ _$setProp(_el$3,"wrapMode","word")
373
+ _$insert(_el$3,measurement)
374
+ _$setProp(_el$4,"wrapMode","word")
375
+ _$insert(_el$4,(()=>{var _c$2=_$memo(()=>props.window.resetsAt===undefined)
376
+ return()=>_c$2()?"Reset time unavailable":`resets ${new Date(props.window.resetsAt * 1000).toLocaleString()}`
377
+ })())
378
+ _$effect((_p$)=>{var _v$=props.api.theme.current.text,_v$2=props.api.theme.current.text,_v$3=props.api.theme.current.textMuted
379
+ _v$!==_p$.e&&(_p$.e=_$setProp(_el$2,"fg",_v$,_p$.e))
380
+ _v$2!==_p$.t&&(_p$.t=_$setProp(_el$3,"fg",_v$2,_p$.t))
381
+ _v$3!==_p$.a&&(_p$.a=_$setProp(_el$4,"fg",_v$3,_p$.a))
382
+ return _p$
383
+ },{e:undefined,t:undefined,a:undefined
384
+ })
385
+ return _el$
386
+ })()
387
+ }
388
+ import {createComponent as _$createComponent3} from "@opentui/solid"
389
+ import {createEffect} from "solid-js"
390
+ import {SelectionMenu,useDialogs} from "./tui.js"
391
+ import {createComponent as _$createComponent2} from "@opentui/solid"
392
+ import {randomUUID} from "crypto"
393
+ import {GuardedConfirmation as GuardedConfirmation2,useDialogState} from "./tui.js"
394
+ function createResetCreditActions(input){const [busy,setBusy]=useDialogState("credit-busy",false)
395
+ const signal=input.controller.targetSignal
396
+ const warn=(message)=>input.api.ui.toast({variant:"warning",title:"Codex reset credit",message
397
+ })
398
+ async function consume(creditID){if(busy()||!input.current()||!input.model.accountID||!input.adapter.consumeResetCredit)
399
+ return
400
+ setBusy(true)
401
+ const previous=input.preferences.pendingResetAttempt()
402
+ const attempt=previous??{providerID:input.model.providerID,modelID:input.model.modelID,accountID:input.model.accountID,...creditID&&{creditID
403
+ },idempotencyKey:randomUUID(),createdAt:Date.now(),...input.model.hostConnection?.id&&{connectionID:input.model.hostConnection.id
404
+ }}
405
+ try{if(attempt.accountID!==input.model.accountID||attempt.modelID!==input.model.modelID||attempt.providerID!==input.model.providerID||attempt.connectionID!==undefined&&attempt.connectionID!==input.model.hostConnection?.id){throw new Error("Resolve the earlier attempt on its linked account and model first")
406
+ }
407
+ if(!previous)
408
+ await input.preferences.beginResetAttempt(attempt)
409
+ if(!input.current())
410
+ return
411
+ const outcome=await input.adapter.consumeResetCredit(input.model,{...attempt,uncertain:Boolean(previous)
412
+ },signal())
413
+ await input.preferences.finishResetAttempt(attempt.idempotencyKey)
414
+ await input.controller.refresh(true)
415
+ input.api.ui.toast({variant:outcome==="reset"||outcome==="alreadyRedeemed"?"success":"info",title:"Codex reset credit",message:{reset:"Reset applied. Restart or continue your Codex session.",alreadyRedeemed:"This attempt was already redeemed; no second credit was used.",noCredit:"No reset credit is available.",nothingToReset:"There is no eligible usage window to reset."
416
+ }[outcome]
417
+ })
418
+ if(input.current())
419
+ input.dialogs.close()
420
+ } catch(error){if(input.current()){warn(`${error instanceof Error ? error.message : "Reset request failed"} \xB7 retry keeps the same attempt`)
421
+ input.dialogs.back()
422
+ }} finally{setBusy(false)
423
+ }}
424
+ async function review(creditID){if(busy()||!input.current()||!input.adapter.prepareResetCreditConsumption||!input.adapter.consumeResetCredit)
425
+ return
426
+ setBusy(true)
427
+ try{const snapshot=await input.adapter.prepareResetCreditConsumption(input.model,creditID,signal())
428
+ if(!input.current()||snapshot.accountId!==input.model.accountID)
429
+ return
430
+ input.dialogs.open(()=>_$createComponent2(GuardedConfirmation2,{get api(){return input.api
431
+ },get current(){return input.current
432
+ },title:"Use one banked reset?",get message(){return `Account ${snapshot.accountId} \xB7 model ${input.model.modelID}. This may reset other eligible Codex windows.`
433
+ },onConfirm:()=>void consume(creditID)
434
+ }))
435
+ } catch(error){if(input.current())
436
+ warn(error instanceof Error?error.message:"Credit eligibility is unavailable")
437
+ } finally{setBusy(false)
438
+ }}
439
+ return{review,consume,busy
440
+ }}
441
+ function ResetCreditDialog(props){const dialogs=useDialogs(props.api)
442
+ const key=JSON.stringify(props.model)
443
+ const current=()=>JSON.stringify(props.controller.current().model)===key
444
+ const pending=props.preferences.pendingResetAttempt
445
+ const credits=()=>props.controller.snapshot()?.bankedResets
446
+ const actions=createResetCreditActions({...props,dialogs,current
447
+ })
448
+ createEffect(()=>{if(!current())
449
+ dialogs.close()
450
+ })
451
+ const options=()=>[...credits()?.credits?.map((credit)=>({title:credit.title??`Credit ${credit.id}`,value:`credit:${credit.id}`,icon:"presets",description:[credit.status,credit.description,credit.expiresAt&&`expires ${new Date(credit.expiresAt * 1000).toLocaleString()}`].filter(Boolean).join(" \xB7 ")
452
+ }))??[],...credits()?.availableCount&&(credits()?.credits?.length??0)<credits().availableCount?[{title:"Use next available credit",value:"credit:",icon:"presets",description:"Credit details unavailable"
453
+ }]:[],...pending()?[{title:"Reconcile pending attempt",value:"retry",icon:"retry",description:"Reuse the previous idempotency key"
454
+ }]:[],{title:"Cancel",value:"cancel",icon:"close",description:"No credit is used"
455
+ }]
456
+ return _$createComponent3(SelectionMenu,{get api(){return props.api
457
+ },get title(){return `Banked resets \xB7 ${credits()?.availableCount ?? 0} available${actions.busy() ? " \xB7 pending" : ""}`
458
+ },get options(){return options()
459
+ },get height(){return Math.min(18,options().length*3)
460
+ },onSelect:(option)=>{if(option.value==="cancel")
461
+ dialogs.back()
462
+ else if(option.value==="retry")
463
+ actions.consume(pending()?.creditID)
464
+ else if(option.value.startsWith("credit:"))
465
+ actions.review(option.value.slice("credit:".length)||undefined)
466
+ }})
467
+ }
468
+ import {createComponent as _$createComponent4} from "@opentui/solid"
469
+ import {effect as _$effect3} from "@opentui/solid"
470
+ import {insert as _$insert3} from "@opentui/solid"
471
+ import {memo as _$memo3} from "@opentui/solid"
472
+ import {setProp as _$setProp3} from "@opentui/solid"
473
+ import {createElement as _$createElement3} from "@opentui/solid"
474
+ import {For} from "solid-js"
475
+ import {effect as _$effect2} from "@opentui/solid"
476
+ import {memo as _$memo2} from "@opentui/solid"
477
+ import {createTextNode as _$createTextNode} from "@opentui/solid"
478
+ import {insertNode as _$insertNode2} from "@opentui/solid"
479
+ import {insert as _$insert2} from "@opentui/solid"
480
+ import {setProp as _$setProp2} from "@opentui/solid"
481
+ import {createElement as _$createElement2} from "@opentui/solid"
482
+ function QuotaBalanceRow(props){return(()=>{var _el$=_$createElement2("box"),_el$2=_$createElement2("text"),_el$3=_$createTextNode(` \xB7 `),_el$4=_$createElement2("text")
483
+ _$insertNode2(_el$,_el$2)
484
+ _$insertNode2(_el$,_el$4)
485
+ _$insertNode2(_el$2,_el$3)
486
+ _$setProp2(_el$2,"wrapMode","word")
487
+ _$insert2(_el$2,()=>props.balance.label,_el$3)
488
+ _$insert2(_el$2,()=>props.balance.scope,null)
489
+ _$setProp2(_el$4,"wrapMode","word")
490
+ _$insert2(_el$4,(()=>{var _c$=_$memo2(()=>!!props.balance.unlimited)
491
+ return()=>_c$()?"No configured cap":`${props.balance.amount} ${props.balance.unit ?? "(provider unit not specified)"}`
492
+ })())
493
+ _$effect2((_p$)=>{var _v$=props.api.theme.current.textMuted,_v$2=props.api.theme.current.text
494
+ _v$!==_p$.e&&(_p$.e=_$setProp2(_el$2,"fg",_v$,_p$.e))
495
+ _v$2!==_p$.t&&(_p$.t=_$setProp2(_el$4,"fg",_v$2,_p$.t))
496
+ return _p$
497
+ },{e:undefined,t:undefined
498
+ })
499
+ return _el$
500
+ })()
501
+ }
502
+ function QuotaSnapshotRows(props){return [(()=>{var _el$=_$createElement3("text")
503
+ _$setProp3(_el$,"wrapMode","none")
504
+ _$setProp3(_el$,"truncate",true)
505
+ _$insert3(_el$,()=>props.snapshot.providerName,null)
506
+ _$insert3(_el$,(()=>{var _c$=_$memo3(()=>!!props.snapshot.accountId)
507
+ return()=>_c$()?` \xB7 ${props.snapshot.accountId}`:""
508
+ })(),null)
509
+ _$effect3((_$p)=>_$setProp3(_el$,"fg",props.api.theme.current.textMuted,_$p))
510
+ return _el$
511
+ })(),_$createComponent4(For,{get each(){return props.snapshot.windows
512
+ },children:(window)=>_$createComponent4(QuotaWindowRow,{get api(){return props.api
513
+ },window
514
+ })
515
+ }),_$createComponent4(For,{get each(){return props.snapshot.balances
516
+ },children:(balance)=>_$createComponent4(QuotaBalanceRow,{get api(){return props.api
517
+ },balance
518
+ })
519
+ })]
520
+ }
521
+ import {createComponent as _$createComponent5} from "@opentui/solid"
522
+ import {createSignal as createSignal4,onMount} from "solid-js"
523
+ import {SelectionMenu as SelectionMenu2,useDialogs as useDialogs2} from "./tui.js"
524
+ var rows=[["openai","OpenAI / Codex",["subscription","api-rate-limits"],"Codex account RPC; HTTP response request/token limits for API connections.","https://developers.openai.com/api/docs/guides/rate-limits"],["anthropic","Anthropic / Claude",["api-rate-limits","configured-limits"],"Model HTTP headers; eligible organization/admin credentials can read configured model limits. Claude subscription windows are separate.","https://platform.claude.com/docs/en/manage-claude/rate-limits-api"],["openrouter","OpenRouter",["key-budget","account-balance"],"Inference-key budget; account credits require a management key. Free-model request limits apply only to free variants.","https://openrouter.ai/docs/api/api-reference/api-keys/get-current-api-key"],["deepseek","DeepSeek",["account-balance"],"Provider-reported USD/CNY available, granted and topped-up balances.","https://api-docs.deepseek.com/api/get-user-balance"],["moonshotai","Moonshot / Kimi API",["account-balance"],"International USD balances; coding subscriptions are a different product.","https://platform.moonshot.ai/docs/api/balance"],["moonshotai-cn","Moonshot / Kimi API China",["account-balance"],"China CNY balances; never reuse an international key against the China endpoint.","https://platform.moonshot.cn/docs/api/balance"],["minimax","MiniMax",["subscription","account-balance"],"Token Plan model rows/explicit percentages; official SDK balance response for pay-as-you-go keys.","https://platform.minimax.io/docs/token-plan/faq"],["minimax-cn","MiniMax China",["subscription","account-balance"],"Region-specific native Token Plan and SDK balance endpoints.","https://github.com/MiniMax-AI/cli/tree/main/src/sdk/quota"],["github-copilot","GitHub Copilot",["subscription"],"Official SDK token-scoped quota/model RPC; no direct copilot_internal calls or ambient CLI login.","https://github.com/github/copilot-sdk/blob/main/docs/features/usage-and-billing.md"],["groq","Groq",["api-rate-limits"],"Native model response headers: requests per day and tokens per minute.","https://console.groq.com/docs/rate-limits"],["fireworks-ai","Fireworks",["configured-limits","api-rate-limits"],"Native model token/second capacities and account monthly spend quota; GPU quotas are excluded.","https://docs.fireworks.ai/serverless/rate-limits"],["siliconflow","SiliconFlow",["account-balance"],"Native current-user balances; retain unknown currency when the API does not report it.","https://docs.siliconflow.com/en/api-reference/userinfo/get-user-info"],["siliconflow-cn","SiliconFlow China",["account-balance"],"Region-specific current-user balances; currency is unknown when the API omits it.","https://docs.siliconflow.cn/"],["novita-ai","Novita",["account-balance"],"Native current-user balances in 1/10000 USD.","https://novita.ai/docs/api-reference/basic-get-user-balance"],["poe","Poe",["account-balance"],"API account point balance; includes plan and add-on points, not session token usage.","https://creator.poe.com/docs/resources/usage-api"],["nano-gpt","NanoGPT",["account-balance","subscription"],"Native USD/Nano balance and documented subscription token windows; null/degraded counters remain unknown.","https://docs.nano-gpt.com/api-reference/endpoint/subscription-usage"],["aihubmix","AIHubMix",["account-balance"],"Current-account balance API needs a Manage Key; inference keys do not grant management access.","https://docs.aihubmix.com/en/api/CliEndpoints/get-self"],["azure","Azure OpenAI",["api-rate-limits"],"Remaining request/token headers for the actual deployment; no ChatGPT subscription or inferred cloud balance.","https://learn.microsoft.com/en-us/azure/ai-foundry/openai/quotas-limits"],["cerebras","Cerebras",["api-rate-limits"],"Only self-describing native counters actually returned for the selected model; console tier examples are not live quota.","https://inference-docs.cerebras.ai/support/rate-limits"],["mistral","Mistral",[],"Documented remaining header lacks a verified unit/window association; no inferred subscription balance.","https://docs.mistral.ai/resources/known-limitations"],["togetherai","Together AI",[],"Dynamic model limits expose retry intervals, not remaining quota; billing UI does not establish a remaining-credit API.","https://docs.together.ai/docs/rate-limits"],["xai","xAI / Grok",[],"Management billing needs separate team credentials; no verified portable remaining API for this inference connection.","https://docs.x.ai/"],["perplexity","Perplexity",[],"No verified remaining-credit API for an inference key. API credits, consumer subscriptions and Computer credits are distinct.","https://www.perplexity.ai/help-center/en/articles/10354847-api-payment-and-billing"],["google","Google Gemini",["configured-limits"],"Native model-matched QuotaFailure responses when provided; cloud quota permissions differ from an inference key.","https://ai.google.dev/gemini-api/docs/rate-limits"],["google-vertex","Google Vertex AI",["configured-limits"],"Native model-matched quota error details; cloud project/region management and dynamic shared quotas are separate.","https://cloud.google.com/vertex-ai/generative-ai/docs/quotas"],["amazon-bedrock","Amazon Bedrock",[],"Service Quotas/CloudWatch require cloud IAM/region/model mapping not exposed by a portable inference connection.","https://docs.aws.amazon.com/bedrock/latest/userguide/quotas.html"],["cloudflare-workers-ai","Cloudflare Workers AI",[],"Account analytics do not prove remaining free allowance; separate account management capability is required.","https://developers.cloudflare.com/workers-ai/platform/limits/"],["huggingface","Hugging Face Inference Providers",[],"Hub request quota is not an inference-model quota or inference-credit balance.","https://huggingface.co/docs/inference-providers/pricing"],["zai","Z.ai / GLM Coding Plan",[],"No verified external quota contract for monitor/dashboard APIs; private endpoints are excluded.","https://docs.z.ai/devpack/overview"],["kimi-for-coding","Kimi Code subscription",[],"Keep separate from Moonshot API balance; an external subscription usage contract/model mapping must be verified.","https://www.kimi.com/code/docs/en/"],["alibaba","Alibaba Cloud / Qwen / DashScope",[],"Cloud account/project billing permissions and model mapping are required; browser OAuth state is not a quota source.","https://www.alibabacloud.com/help/en/model-studio/"],["opencode","OpenCode Zen / Go",[],"Session token/cost statistics are not provider quota; no undocumented billing endpoints.","https://opencode.ai/docs/zen/"],["cursor","Cursor",[],"Consumer usage needs a supported external API; no cookie or credential-file integrations.","https://docs.cursor.com/"],["windsurf","Windsurf / Codeium",[],"Consumer plan windows need a supported external API; no private account endpoints.","https://docs.windsurf.com/"],["amp","Amp",[],"Product credits need a supported external account API; no cookie or local credential extraction.","https://ampcode.com/manual"],["ollama","Ollama",[],"Local inference has no provider subscription window; cloud products require their own account API.","https://docs.ollama.com/api"],["lmstudio","LM Studio",[],"Local inference has no provider subscription quota to read.","https://lmstudio.ai/docs/developer"],["nvidia","NVIDIA NIM / NGC",[],"Inference access does not identify cloud/trial NGC credit balance or self-hosted resource quota.","https://docs.nvidia.com/nim/"],["cohere","Cohere",[],"No verified native remaining-counter contract for this integration; static organization tier examples are not live quotas.","https://docs.cohere.com/docs/rate-limits"],["ai21","AI21",[],"No verified native remaining-counter contract or subscription balance endpoint for this integration.","https://docs.ai21.com/"],["replicate","Replicate",[],"Prediction cost is not remaining account credit; no inferred balance from execution cost.","https://replicate.com/docs/topics/billing"],["chutes","Chutes",[],"Current-user quota APIs exist, but their public schemas do not establish native units/model allowance semantics yet.","https://chutes.ai/docs/api-reference/users"],["requesty","Requesty",[],"Enterprise management key and key ID are required; dated usage reports alone are not remaining budget.","https://docs.requesty.ai/features/key-management-api"],["baseten","Baseten",[],"No independently verified portable remaining quota API for the configured inference connection.","https://docs.baseten.co/"],["modelscope","ModelScope",[],"No independently verified portable remaining quota API/model mapping for the selected connection.","https://opencode.ai/docs/providers/"],["nebius","Nebius",[],"Cloud billing/management access is separate from a plain inference credential.","https://docs.nebius.com/"],["vultr","Vultr",[],"Cloud management quota/billing permissions are separate from inference access.","https://docs.vultr.com/"],["v0","Vercel v0",[],"Consumer/product credits need their own documented external account API.","https://v0.dev/docs"]]
525
+ var PROVIDER_QUOTA_CAPABILITIES= rows.map(([id,name,sources,reason,url])=>({id,name,sources,reason,documentation:[url]}))
526
+ var aliases={fireworks:"fireworks-ai",novita:"novita-ai",moonshot:"moonshotai","minimax-coding-plan":"minimax","minimax-coding-plan-cn":"minimax-cn",together:"togetherai","silicon-flow":"siliconflow","github-copilot-enterprise":"github-copilot","google-vertex-anthropic":"google-vertex","z-ai":"zai",zhipuai:"zai",dashscope:"alibaba","alibaba-cn":"alibaba",qwen:"alibaba","opencode-go":"opencode","opencode-zen":"opencode",codeium:"windsurf","azure-cognitive-services":"azure"
527
+ }
528
+ function providerQuotaCapability(id,canonical){const key=aliases[canonical??id]??canonical??id
529
+ const found=PROVIDER_QUOTA_CAPABILITIES.find((entry)=>entry.id===key)
530
+ return found??{id,name:id,sources:[],reason:"No independent quota/balance API or native counter semantics are verified for this provider.",documentation:["https://opencode.ai/docs/providers/"]
531
+ }}
532
+ function providerQuotaCatalog(providers=[]){const entries=new Map(PROVIDER_QUOTA_CAPABILITIES.map((entry)=>[entry.id,entry]))
533
+ for(const provider of providers)
534
+ entries.set(provider.id,{...providerQuotaCapability(provider.id,provider.canonical),id:provider.id,name:provider.name
535
+ })
536
+ return [...entries.values()].sort((a,b)=>a.name.localeCompare(b.name))
537
+ }
538
+ function ProviderSupportDialog(props){const dialogs=useDialogs2(props.api)
539
+ const [entries,setEntries]=createSignal4(providerQuotaCatalog())
540
+ onMount(()=>{if(props.load)
541
+ props.load().then(setEntries).catch(()=>{})
542
+ })
543
+ return _$createComponent5(SelectionMenu2,{get api(){return props.api
544
+ },get title(){return `Provider sources \xB7 ${entries().length}`
545
+ },height:18,get options(){return [...entries().map((entry)=>({title:entry.name,value:entry.id,icon:entry.sources.length>0?"info":"help",description:`${entry.sources.length > 0 ? entry.sources.join(", ") : "API-dependent"} \xB7 ${entry.reason}`
546
+ })),{title:"Close",value:"close",description:"Return to the sidebar",icon:"close"
547
+ }]
548
+ },onSelect:(option)=>{if(option.value==="close")
549
+ dialogs.back()
550
+ }})
551
+ }
552
+ var text={type:"string",minLength:1,maxLength:512}
553
+ var model2={type:"object",properties:{providerID:text,modelID:text,sessionID:text,connectionID:text,variant:text,force:{type:"boolean"}},required:["providerID","modelID","sessionID"],additionalProperties:false
554
+ }
555
+ var NavigatorQuotaRpc={id:"opencode-navigator.quotas",methods:{read:{input:model2,output:{type:"object",additionalProperties:true}},capabilities:{input:{type:"object",additionalProperties:false},output:{type:"array",items:{type:"object",additionalProperties:true}}}},events:{updated:{schema:{type:"object",properties:{providerID:text,modelID:text},required:["providerID","modelID"],additionalProperties:false
556
+ }}}};
557
+ function missing(error){return typeof error==="object"&&error!==null&&"type" in error&&error.type==="rpc.unavailable"
558
+ }
559
+ function createNativeQuotaAdapter(context){const rpc=context.client.rpc(NavigatorQuotaRpc)
560
+ const listeners=new Set
561
+ const unsubscribe=rpc.events.on("updated",(event)=>{const location=context.location??context.data.location.default()
562
+ if(event.location.directory!==location.directory)
563
+ return
564
+ if(typeof event.data.providerID!=="string"||typeof event.data.modelID!=="string")
565
+ return
566
+ for(const listener of listeners)
567
+ listener(event.data.providerID,event.data.modelID)
568
+ })
569
+ const adapter={id:"provider-api",name:"Provider-native quotas",supports:(model)=>model.hostConnection?.status!=="loading",async read(model,signal,force){const base={model,providerId:model.providerID,providerName:model.providerID,availability:"setup_required",fetchedAt:Date.now(),windows:[]
570
+ }
571
+ try{if(typeof rpc.read!=="function")
572
+ return{...base,message:"Enable the Navigator OpenCode 2 server plugin for native provider quotas and balances."
573
+ }
574
+ const value=await rpc.read({providerID:model.providerID,modelID:model.modelID,sessionID:model.sessionID,...model.variant&&{variant:model.variant},...model.hostConnection?.id&&{connectionID:model.hostConnection.id},force
575
+ },{location:context.location??context.data.location.default(),signal})
576
+ if(model.hostConnection?.id&&value.connectionID&&value.connectionID!==model.hostConnection.id)
577
+ return{...base,availability:"unauthenticated",message:"The provider connection changed during this read."
578
+ }
579
+ return normalizeProviderQuota({model,providerId:value.providerId,providerName:value.providerName,availability:value.availability,fetchedAt:value.fetchedAt,windows:value.windows,...value.balances&&{balances:value.balances},...value.accountId&&{accountId:value.accountId},...value.connectionID&&{connectionID:value.connectionID},...value.message&&{message:value.message}})
580
+ } catch(error){if(!missing(error))
581
+ throw error
582
+ return{...base,message:"Enable the Navigator OpenCode 2 server plugin for native provider quotas and balances."
583
+ }}},dispose(){unsubscribe()
584
+ listeners.clear()
585
+ }}
586
+ return{adapter,onUpdated(listener){listeners.add(listener)
587
+ return()=>listeners.delete(listener)
588
+ },async capabilities(){try{return await rpc.capabilities({})
589
+ } catch{return providerQuotaCatalog(context.data.location.provider?.list?.(context.location)??[])
590
+ }}}}
591
+ import {mergeProps as _$mergeProps} from "@opentui/solid"
592
+ import {insert as _$insert7} from "@opentui/solid"
593
+ import {setProp as _$setProp8} from "@opentui/solid"
594
+ import {effect as _$effect8} from "@opentui/solid"
595
+ import {createTextNode as _$createTextNode4} from "@opentui/solid"
596
+ import {insertNode as _$insertNode5} from "@opentui/solid"
597
+ import {createElement as _$createElement8} from "@opentui/solid"
598
+ import {memo as _$memo6} from "@opentui/solid"
599
+ import {createComponent as _$createComponent10} from "@opentui/solid"
600
+ import {Show as Show4} from "solid-js"
601
+ import {useDialogs as useDialogs4,useIcons as useIcons3} from "./tui.js"
602
+ import {setProp as _$setProp5} from "@opentui/solid"
603
+ import {effect as _$effect5} from "@opentui/solid"
604
+ import {createTextNode as _$createTextNode2} from "@opentui/solid"
605
+ import {insertNode as _$insertNode3} from "@opentui/solid"
606
+ import {createElement as _$createElement5} from "@opentui/solid"
607
+ import {memo as _$memo4} from "@opentui/solid"
608
+ import {createComponent as _$createComponent7} from "@opentui/solid"
609
+ import {Show} from "solid-js"
610
+ import {useDialogs as useDialogs3,useIcons} from "./tui.js"
611
+ import {createComponent as _$createComponent6} from "@opentui/solid"
612
+ import {effect as _$effect4} from "@opentui/solid"
613
+ import {insert as _$insert4} from "@opentui/solid"
614
+ import {setProp as _$setProp4} from "@opentui/solid"
615
+ import {createElement as _$createElement4} from "@opentui/solid"
616
+ import {SelectionBox} from "./tui.js"
617
+ import {createSignal as createSignal5,onCleanup} from "solid-js"
618
+ function sidebarInteractiveColors(theme,state,normalColor=theme.text,mode="row"){const highlighted=!state.disabled&&(state.focused||mode==="control"&&state.hovered)
619
+ let backgroundColor=theme.backgroundPanel
620
+ let foregroundColor=normalColor
621
+ if(highlighted)
622
+ backgroundColor=theme.primary
623
+ else if(mode==="control")
624
+ backgroundColor="transparent"
625
+ else if(state.focused||state.hovered)
626
+ backgroundColor=theme.backgroundElement
627
+ if(state.disabled)
628
+ foregroundColor=theme.textMuted
629
+ else if(highlighted)
630
+ foregroundColor=theme.selectedListItemText
631
+ return{backgroundColor,foregroundColor}}
632
+ function useSidebarItem(api,interaction,descriptor,normalColor=()=>api.theme.current.text,mode="row"){let unregister
633
+ const [hovered,setHovered]=createSignal5(false)
634
+ const disabled=()=>descriptor.disabled?.()===true
635
+ const focused=()=>Boolean(interaction?.ownsFocus()&&interaction.isSelected(descriptor.id))
636
+ const colors=()=>sidebarInteractiveColors(api.theme.current,{focused:focused(),hovered:hovered(),disabled:disabled()},normalColor(),mode)
637
+ const ref=(renderable)=>{unregister?.()
638
+ unregister=interaction?.register({...descriptor,renderable})
639
+ }
640
+ onCleanup(()=>unregister?.())
641
+ return{ref,disabled,focused,backgroundColor:()=>colors().backgroundColor,foregroundColor:()=>colors().foregroundColor,onMouseOver:()=>setHovered(true),onMouseOut:()=>setHovered(false),select(event){interaction?.mouseSelect(descriptor.id,event)
642
+ },activate(event){if(interaction)
643
+ return interaction.mouseActivate(descriptor.id,event)
644
+ if(disabled())
645
+ return false
646
+ descriptor.activate()
647
+ return true
648
+ }}}
649
+ function LimitsAction(props){const item=useSidebarItem(props.api,props.interaction,{id:props.id,position:()=>props.position,disabled:()=>props.disabled===true,activate:props.onActivate
650
+ })
651
+ return _$createComponent6(SelectionBox,{ref:(node)=>item.ref(node),get id(){return props.id
652
+ },height:1,get backgroundColor(){return item.backgroundColor()
653
+ },get onMouseOver(){return item.onMouseOver
654
+ },get onMouseOut(){return item.onMouseOut
655
+ },onMouseUp:(event)=>{event.stopPropagation()
656
+ item.activate(event)
657
+ },get children(){var _el$=_$createElement4("text")
658
+ _$setProp4(_el$,"wrapMode","none")
659
+ _$setProp4(_el$,"truncate",true)
660
+ _$insert4(_el$,()=>props.label)
661
+ _$effect4((_$p)=>_$setProp4(_el$,"fg",item.foregroundColor(),_$p))
662
+ return _el$
663
+ }})
664
+ }
665
+ function CodexAccountControls(props){const icons=useIcons()
666
+ const model=()=>props.controller.current().model
667
+ const accountLink=createCodexAccountLink({api:props.api,preferences:props.preferences,adapter:props.codex,model,dialogs:useDialogs3(props.api)
668
+ })
669
+ const eligible=()=>(props.model.providerID==="openai"||props.model.hostConnection?.providerID==="openai")&&props.model.hostConnection?.method!=="api"
670
+ const action=(id,row,label,onActivate)=>_$createComponent7(LimitsAction,{get api(){return props.api
671
+ },get interaction(){return props.interaction
672
+ },id:`${PLUGIN_ID}.limits.${id}`,get position(){return{section:props.navigationSection,row,column:0
673
+ }},label,onActivate,get disabled(){return accountLink.busy()
674
+ }})
675
+ return _$createComponent7(Show,{get when(){return eligible()
676
+ },get children(){return [_$createComponent7(Show,{get when(){return _$memo4(()=>!!!props.model.accountID)()&&(!props.model.hostConnection||props.model.hostConnection.status==="ready")
677
+ },get children(){return [(()=>{var _el$=_$createElement5("text")
678
+ _$insertNode3(_el$,_$createTextNode2(`Link the matching Codex account once for this connection`))
679
+ _$effect5((_$p)=>_$setProp5(_el$,"fg",props.api.theme.current.textMuted,_$p))
680
+ return _el$
681
+ })(),_$memo4(()=>action("link",1,`${icons.icon("scope")} Link Codex CLI account`,()=>void accountLink.link()))]
682
+ }}),_$createComponent7(Show,{get when(){return _$memo4(()=>!!props.model.accountID)()&&props.model.accountSource!=="host"
683
+ },get children(){return [_$memo4(()=>action("unlink",4,`${icons.icon("close")} Unlink Codex account`,()=>props.preferences.setCodexConnectionBinding(props.model.providerID,props.model.hostConnection?.id))),_$createComponent7(Show,{get when(){return props.controller.snapshot()?.availability==="unauthenticated"
684
+ },get children(){return action("relink",5,`${icons.icon("scope")} Relink Codex account`,()=>void accountLink.link())
685
+ }})]
686
+ }})]
687
+ }})
688
+ }
689
+ import {createComponent as _$createComponent8} from "@opentui/solid"
690
+ import {effect as _$effect6} from "@opentui/solid"
691
+ import {insert as _$insert5} from "@opentui/solid"
692
+ import {setProp as _$setProp6} from "@opentui/solid"
693
+ import {createElement as _$createElement6} from "@opentui/solid"
694
+ import {Show as Show2} from "solid-js"
695
+ function LimitsStatus(props){const message=()=>{const model=props.controller.current().model
696
+ const connection=model?.hostConnection
697
+ const snapshot=props.controller.snapshot()
698
+ const state=props.controller.state()
699
+ if(state.status==="error")
700
+ return snapshot?"Stale \xB7 refresh failed; retry below":"Quota read failed \xB7 retry below"
701
+ if(snapshot?.message)
702
+ return snapshot.availability==="stale"?`Stale \xB7 ${snapshot.message}`:snapshot.availability==="ready"?`Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()} \xB7 ${snapshot.message}`:snapshot.message
703
+ if(connection?.status==="loading")
704
+ return "Checking the OpenCode account\u2026"
705
+ if(connection?.status==="unavailable")
706
+ return "OpenCode account metadata unavailable \xB7 refresh below"
707
+ if(connection?.status==="unsupported")
708
+ return "Codex quotas require an active ChatGPT OAuth connection"
709
+ if(!snapshot)
710
+ return state.status==="loading"?"Reading provider limits\u2026":props.controller.current().adapter?"Refresh to read provider limits":providerQuotaCapability(model?.providerID??"").reason
711
+ const availability=snapshot.availability
712
+ if(availability==="unauthenticated")
713
+ return model?.accountSource==="host"?"Sign in to Codex CLI with the account selected in OpenCode":"Codex sign-in or linked account changed \xB7 relink below"
714
+ if(availability==="unsupported")
715
+ return "No documented quota bucket for this model"
716
+ if(availability==="rate_limited")
717
+ return "Provider rate-limited this read \xB7 try again later"
718
+ if(availability==="stale")
719
+ return "Stale provider data \xB7 refresh below"
720
+ return `Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()}`
721
+ }
722
+ return _$createComponent8(Show2,{get when(){return props.controller.current().model
723
+ },get children(){var _el$=_$createElement6("text")
724
+ _$setProp6(_el$,"wrapMode","word")
725
+ _$insert5(_el$,message)
726
+ _$effect6((_$p)=>_$setProp6(_el$,"fg",props.api.theme.current.textMuted,_$p))
727
+ return _el$
728
+ }})
729
+ }
730
+ import {createComponent as _$createComponent9} from "@opentui/solid"
731
+ import {effect as _$effect7} from "@opentui/solid"
732
+ import {createTextNode as _$createTextNode3} from "@opentui/solid"
733
+ import {insertNode as _$insertNode4} from "@opentui/solid"
734
+ import {memo as _$memo5} from "@opentui/solid"
735
+ import {insert as _$insert6} from "@opentui/solid"
736
+ import {setProp as _$setProp7} from "@opentui/solid"
737
+ import {createElement as _$createElement7} from "@opentui/solid"
738
+ import {Show as Show3,createEffect as createEffect2,createSignal as createSignal6} from "solid-js"
739
+ import {SelectionBox as SelectionBox2,useIcons as useIcons2} from "./tui.js"
740
+ var graphemes=new Intl.Segmenter(undefined,{granularity:"grapheme"
741
+ })
742
+ function truncateEnd(value,maxWidth){if(Bun.stringWidth(value)<=maxWidth)
743
+ return value
744
+ if(maxWidth<=0)
745
+ return ""
746
+ const ellipsis=maxWidth>=3?"...":".".repeat(maxWidth)
747
+ const contentWidth=maxWidth-Bun.stringWidth(ellipsis)
748
+ let result=""
749
+ for(const{segment
750
+ } of graphemes.segment(value)){if(Bun.stringWidth(result + segment)>contentWidth)
751
+ break
752
+ result += segment
753
+ }
754
+ return result + ellipsis
755
+ }
756
+ function Section(props){const icons=useIcons2()
757
+ const [actionWidth,setActionWidth]=createSignal6()
758
+ const icon=()=>props.section?icons.section(props.section):props.icon
759
+ const theme=()=>props.api.theme.current
760
+ const item=useSidebarItem(props.api,props.interaction,{id:props.sectionId??`section.${props.title}`,position:()=>({section:props.navigationSection??0,row:0,column:0
761
+ }),activate:props.onToggle
762
+ })
763
+ const action=props.headerAction?useSidebarItem(props.api,props.interaction,{id:props.headerAction.id,position:()=>({section:props.navigationSection??0,row:0,column:1
764
+ }),disabled:props.headerAction.disabled,activate:props.headerAction.onActivate
765
+ },()=>theme().accent,"control"):undefined
766
+ createEffect2(()=>{props.headerAction?.label()
767
+ setActionWidth(undefined)
768
+ })
769
+ const actionLabel=()=>{const label=props.headerAction?.label()??""
770
+ const width=actionWidth()
771
+ return width===undefined?label:truncateEnd(label,width-2)
772
+ }
773
+ return(()=>{var _el$=_$createElement7("box")
774
+ _$setProp7(_el$,"gap",1)
775
+ _$insert6(_el$,_$createComponent9(SelectionBox2,{ref:(node)=>item.ref(node),get id(){return props.sectionId
776
+ },flexDirection:"row",height:1,justifyContent:"space-between",gap:1,paddingLeft:1,paddingRight:1,get backgroundColor(){return item.backgroundColor()
777
+ },get onMouseOver(){return item.onMouseOver
778
+ },get onMouseOut(){return item.onMouseOut
779
+ },onMouseDown:(event)=>item.activate(event),onSizeChange:()=>setActionWidth(undefined),get children(){return [(()=>{var _el$2=_$createElement7("text"),_el$3=_$createElement7("span"),_el$4=_$createTextNode3(` `),_el$5=_$createElement7("span"),_el$6=_$createElement7("b")
780
+ _$insertNode4(_el$2,_el$3)
781
+ _$insertNode4(_el$2,_el$4)
782
+ _$insertNode4(_el$2,_el$5)
783
+ _$insertNode4(_el$2,_el$6)
784
+ _$setProp7(_el$2,"flexShrink",0)
785
+ _$setProp7(_el$2,"wrapMode","none")
786
+ _$insert6(_el$3,()=>icons.icon(props.open?"expanded":"collapsed"))
787
+ _$insert6(_el$5,(()=>{var _c$=_$memo5(()=>!!icon())
788
+ return()=>_c$()?`${icon()} `:""
789
+ })())
790
+ _$insert6(_el$6,()=>props.title)
791
+ _$effect7((_p$)=>{var _v$=item.foregroundColor(),_v$2={fg:item.focused()?item.foregroundColor():theme().accent
792
+ },_v$3={fg:item.focused()?item.foregroundColor():theme().accent
793
+ }
794
+ _v$!==_p$.e&&(_p$.e=_$setProp7(_el$2,"fg",_v$,_p$.e))
795
+ _v$2!==_p$.t&&(_p$.t=_$setProp7(_el$3,"style",_v$2,_p$.t))
796
+ _v$3!==_p$.a&&(_p$.a=_$setProp7(_el$5,"style",_v$3,_p$.a))
797
+ return _p$
798
+ },{e:undefined,t:undefined,a:undefined
799
+ })
800
+ return _el$2
801
+ })(),(()=>{var _el$7=_$createElement7("box"),_el$8=_$createElement7("text")
802
+ _$insertNode4(_el$7,_el$8)
803
+ _$setProp7(_el$7,"flexDirection","row")
804
+ _$setProp7(_el$7,"gap",1)
805
+ _$setProp7(_el$7,"flexGrow",1)
806
+ _$setProp7(_el$7,"minWidth",0)
807
+ _$setProp7(_el$7,"justifyContent","flex-end")
808
+ _$insert6(_el$7,_$createComponent9(Show3,{when:action,children:(control)=>_$createComponent9(SelectionBox2,{ref:(node)=>control().ref(node),get id(){return props.headerAction?.id
809
+ },flexShrink:1,minWidth:3,overflow:"hidden",get backgroundColor(){return control().backgroundColor()
810
+ },onSizeChange:function(){setActionWidth(this.width)
811
+ },get onMouseOver(){return control().onMouseOver
812
+ },get onMouseOut(){return control().onMouseOut
813
+ },onMouseDown:(event)=>event.stopPropagation(),onMouseUp:(event)=>{event.stopPropagation()
814
+ control().activate(event)
815
+ },get children(){var _el$9=_$createElement7("text")
816
+ _$setProp7(_el$9,"wrapMode","none")
817
+ _$setProp7(_el$9,"height",1)
818
+ _$insert6(_el$9,actionLabel)
819
+ _$effect7((_$p)=>_$setProp7(_el$9,"fg",control().foregroundColor(),_$p))
820
+ return _el$9
821
+ }})
822
+ }),_el$8)
823
+ _$setProp7(_el$8,"wrapMode","none")
824
+ _$setProp7(_el$8,"truncate",true)
825
+ _$setProp7(_el$8,"height",1)
826
+ _$insert6(_el$8,()=>props.summary)
827
+ _$effect7((_p$)=>{var _v$4=item.focused()?item.foregroundColor():theme().textMuted,_v$5=props.headerAction?0:1
828
+ _v$4!==_p$.e&&(_p$.e=_$setProp7(_el$8,"fg",_v$4,_p$.e))
829
+ _v$5!==_p$.t&&(_p$.t=_$setProp7(_el$8,"flexShrink",_v$5,_p$.t))
830
+ return _p$
831
+ },{e:undefined,t:undefined
832
+ })
833
+ return _el$7
834
+ })()]
835
+ }}),null)
836
+ _$insert6(_el$,_$createComponent9(Show3,{get when(){return props.open
837
+ },get children(){return props.children
838
+ }}),null)
839
+ return _el$
840
+ })()
841
+ }
842
+ function ProviderLimitsSection(props){const icons=useIcons3()
843
+ const dialogs=useDialogs4(props.api)
844
+ const model=()=>props.controller.current().model
845
+ const snapshot=props.controller.snapshot
846
+ const count=()=>(snapshot()?.windows.length??0) + (snapshot()?.balances?.length??0)
847
+ const action=(id,row,label,onActivate,disabled=false)=>_$createComponent10(LimitsAction,{get api(){return props.api
848
+ },get interaction(){return props.interaction
849
+ },id:`${PLUGIN_ID}.limits.${id}`,get position(){return{section:props.navigationSection,row,column:0
850
+ }},label,onActivate,disabled
851
+ })
852
+ function openCredits(){const selected=model()
853
+ if(selected)
854
+ dialogs.open(()=>_$createComponent10(ResetCreditDialog,{get api(){return props.api
855
+ },get preferences(){return props.preferences
856
+ },get controller(){return props.controller
857
+ },get adapter(){return props.codex
858
+ },model:selected
859
+ }))
860
+ }
861
+ return _$createComponent10(Section,{get api(){return props.api
862
+ },get interaction(){return props.interaction
863
+ },sectionId:`${PLUGIN_ID}.section.limits`,get navigationSection(){return props.navigationSection
864
+ },title:"LIMITS",section:"limits",get summary(){return _$memo6(()=>!!count())()?String(count()):""
865
+ },get open(){return props.preferences.expanded().limits
866
+ },onToggle:()=>props.preferences.toggleSectionExpanded("limits"),get children(){return _$createComponent10(Show4,{get when(){return model()
867
+ },get fallback(){return(()=>{var _el$=_$createElement8("text")
868
+ _$insertNode5(_el$,_$createTextNode4(`Open a session to view model limits`))
869
+ _$effect8((_$p)=>_$setProp8(_el$,"fg",props.api.theme.current.textMuted,_$p))
870
+ return _el$
871
+ })()
872
+ },children:(selected)=>(()=>{var _el$3=_$createElement8("box"),_el$4=_$createElement8("text"),_el$5=_$createTextNode4(`/`)
873
+ _$insertNode5(_el$3,_el$4)
874
+ _$setProp8(_el$3,"gap",1)
875
+ _$insertNode5(_el$4,_el$5)
876
+ _$setProp8(_el$4,"wrapMode","none")
877
+ _$setProp8(_el$4,"truncate",true)
878
+ _$insert7(_el$4,()=>selected().providerID,_el$5)
879
+ _$insert7(_el$4,()=>selected().modelID,null)
880
+ _$insert7(_el$3,_$createComponent10(CodexAccountControls,_$mergeProps(props,{get model(){return selected()
881
+ }})),null)
882
+ _$insert7(_el$3,_$createComponent10(Show4,{get when(){return _$memo6(()=>!!(snapshot()&&["ready","stale"].includes(snapshot().availability)))()?snapshot():undefined
883
+ },children:(quota)=>_$createComponent10(QuotaSnapshotRows,{get api(){return props.api
884
+ },get snapshot(){return quota()
885
+ }})
886
+ }),null)
887
+ _$insert7(_el$3,_$createComponent10(Show4,{get when(){return snapshot()?.bankedResets
888
+ },children:(credits)=>[(()=>{var _el$6=_$createElement8("text"),_el$7=_$createTextNode4(`Banked resets: `)
889
+ _$insertNode5(_el$6,_el$7)
890
+ _$insert7(_el$6,()=>credits().availableCount,null)
891
+ _$effect8((_$p)=>_$setProp8(_el$6,"fg",props.api.theme.current.textMuted,_$p))
892
+ return _el$6
893
+ })(),_$createComponent10(Show4,{get when(){return credits().availableCount>0||props.preferences.pendingResetAttempt()
894
+ },get children(){return action("credits",2,`${icons.icon("info")} Review reset credits`,openCredits)
895
+ }})]
896
+ }),null)
897
+ _$insert7(_el$3,_$createComponent10(LimitsStatus,{get api(){return props.api
898
+ },get controller(){return props.controller
899
+ }}),null)
900
+ _$insert7(_el$3,_$createComponent10(Show4,{get when(){return props.controller.current().adapter||selected().hostConnection?.status==="unavailable"
901
+ },get children(){return action("refresh",3,`${icons.icon("retry")} ${props.controller.state().status === "error" ? "Retry" : "Refresh"}`,()=>void props.controller.refresh(true),["loading","refreshing"].includes(props.controller.state().status))
902
+ }}),null)
903
+ _$insert7(_el$3,()=>action("sources",6,`${icons.icon("info")} Provider sources`,()=>dialogs.open(()=>_$createComponent10(ProviderSupportDialog,{get api(){return props.api
904
+ },get load(){return props.capabilities
905
+ }}))),null)
906
+ _$effect8((_$p)=>_$setProp8(_el$4,"fg",props.api.theme.current.textMuted,_$p))
907
+ return _el$3
908
+ })()
909
+ })
910
+ }})
911
+ }
912
+ function selectedV1Model(api){const route=api.route.current
913
+ const sessionID="params" in route?route.params?.sessionID:undefined
914
+ if(route.name!=="session"||typeof sessionID!=="string")
915
+ return
916
+ const model=api.state.session.get(sessionID)?.model
917
+ if(!model)
918
+ return
919
+ return{sessionID,providerID:model.providerID,modelID:model.id,...model.variant&&{variant:model.variant}}}
920
+ import {createSignal as createSignal7,untrack as untrack2} from "solid-js"
921
+ var CHATGPT_METHODS=new Set(["chatgpt-browser","chatgpt-headless"])
922
+ function providerAccountIdentity(provider,credentials){if(!provider.integrationID||provider.activation==="disabled")
923
+ return{status:"unsupported"}
924
+ const active=credentials.filter((entry)=>entry.integrationID===provider.integrationID&&entry.active)
925
+ if(active.length!==1)
926
+ return{status:"unavailable"}
927
+ const credential=active[0]
928
+ const id=`credential:${credential.id}`
929
+ const providerID=provider.canonical??provider.id
930
+ if(credential.value.type==="key")
931
+ return{status:"ready",id,method:"api",providerID}
932
+ if(provider.integrationID!=="openai")
933
+ return{status:"ready",id,method:"oauth",providerID}
934
+ if(!CHATGPT_METHODS.has(credential.value.methodID))
935
+ return{status:"unsupported",id}
936
+ const account=credential.value.metadata?.accountID
937
+ const accountID=typeof account==="string"&&account.trim()?account:undefined
938
+ const header=Object.entries(provider.headers??{}).find(([name])=>name.toLowerCase()==="chatgpt-account-id")?.[1]
939
+ if(accountID&&header&&header!==accountID)
940
+ return{status:"loading"}
941
+ return{status:"ready",id,method:"oauth",providerID,...accountID&&{accountID}}}
942
+ function createOpenCodeV2AccountSource(context,lifecycle){const [connections,setConnections]=createSignal7({})
943
+ let generation=0
944
+ let disposed=false
945
+ let lastModel
946
+ let pending
947
+ const location=()=>context.location??context.data.location.default()
948
+ const key=(model)=>JSON.stringify([model.providerID,location()])
949
+ function save(target,connection){if(JSON.stringify(connections()[target])!==JSON.stringify(connection))
950
+ setConnections((values)=>({...values,[target]:connection}))
951
+ }
952
+ async function read(model,signal=lifecycle){if(disposed||signal.aborted)
953
+ return{status:"unavailable"}
954
+ const target=key(model)
955
+ const epoch=generation
956
+ if(pending?.key===target&&pending.generation===epoch)
957
+ return pending.promise
958
+ const promise=(async ()=>{try{if(!context.client.provider?.get||!context.client.credential?.list)
959
+ throw new Error("Account metadata unavailable")
960
+ const options={signal:AbortSignal.any([lifecycle,signal])}
961
+ const [provider,credentials]=await Promise.all([context.client.provider.get({providerID:model.providerID,location:location()},options),context.client.credential.list(options)])
962
+ let connection=provider.data.id===model.providerID?providerAccountIdentity(provider.data,credentials):{status:"unavailable"}
963
+ if(connection.status==="unavailable"&&credentials.every((entry)=>!(entry.integrationID===provider.data.integrationID&&entry.active))&&provider.data.integrationID&&typeof context.client.integration?.get==="function"){const integration=await context.client.integration.get({integrationID:provider.data.integrationID,location:location()},options)
964
+ const active=integration.data.connections[0]
965
+ if(active?.type==="env"&&!active.status)
966
+ connection={status:"ready",id:`env:${active.name}`,method:"api",providerID:provider.data.canonical??provider.data.id
967
+ }}
968
+ if(disposed||signal.aborted||epoch!==generation||key(model)!==target)
969
+ return{status:"unavailable"}
970
+ save(target,connection)
971
+ return connection
972
+ } catch{if(!disposed&&!signal.aborted&&epoch===generation)
973
+ save(target,{status:"unavailable"})
974
+ return{status:"unavailable"}}})()
975
+ pending={key:target,generation:epoch,promise}
976
+ try{return await promise
977
+ } finally{if(pending?.promise===promise)
978
+ pending=undefined
979
+ }}
980
+ function invalidate(){generation++
981
+ setConnections({})
982
+ if(lastModel&&!disposed)
983
+ read(lastModel)
984
+ }
985
+ const events=["credential.switched","credential.updated","integration.updated","provider.updated","server.connected"]
986
+ const unsubscribes=events.map((event)=>context.data.on(event,()=>untrack2(invalidate)))
987
+ return{current(model){lastModel=model
988
+ return{...model,hostConnection:connections()[key(model)]??{status:"loading"}}},async refresh(model){await read(model)
989
+ },async validate(model,signal){const actual=await read(model,signal)
990
+ const expected=model.hostConnection
991
+ return actual.status==="ready"&&expected?.status==="ready"&&actual.id===expected.id&&actual.accountID===expected.accountID&&(!actual.accountID||actual.accountID===model.accountID)
992
+ },dispose(){disposed=true
993
+ generation++
994
+ for(const unsubscribe of unsubscribes)
995
+ unsubscribe()
996
+ }}}
997
+ function bindCodexAccount(model,preferences){const connection=model.hostConnection
998
+ if(model.providerID!=="openai"&&connection?.providerID!=="openai"||connection?.method==="api")
999
+ return{...model,accountID:undefined,accountSource:undefined}
1000
+ if(connection&&connection.status!=="ready")
1001
+ return{...model,accountID:undefined,accountSource:undefined}
1002
+ const accountID=connection?.accountID??preferences.codexAccountForConnection(model.providerID,connection?.id,model.modelID)
1003
+ const accountSource=connection?.accountID?"host":"binding"
1004
+ return{...model,accountID,accountSource:accountID?accountSource:undefined}}
1005
+ function selectedV2Model(context){const route=context.ui.router.current()
1006
+ const model=context.ui.model.current()
1007
+ if(route.type!=="session"||!model)
1008
+ return
1009
+ return{sessionID:route.sessionID,providerID:model.providerID,modelID:model.modelID,...model.variant&&{variant:model.variant}}}
1010
+ function createProviderQuotaIntegration(api,preferences,version,context){const selectModel=()=>context?selectedV2Model(context):selectedV1Model(api)
1011
+ const accountSource=context?createOpenCodeV2AccountSource(context,api.lifecycle.signal):undefined
1012
+ const codex=createCodexQuotaAdapter(createCodexAppServerClient({version}),accountSource?.validate)
1013
+ const native=context?createNativeQuotaAdapter(context):undefined
1014
+ const controller=createProviderLimitsController([codex,...native?[native.adapter]:[]],()=>{const model=selectModel()
1015
+ return model?bindCodexAccount(accountSource?.current(model)??model,preferences):undefined
1016
+ },api.lifecycle.signal,accountSource&&(async ()=>{const model=selectModel()
1017
+ if(model)
1018
+ await accountSource.refresh(model)
1019
+ }))
1020
+ native?.onUpdated((providerID,modelID)=>{const selected=selectModel()
1021
+ if(selected?.providerID===providerID&&selected.modelID===modelID)
1022
+ controller.refresh()
1023
+ })
1024
+ return{controller,codex,accountSource,selectModel,capabilities:native?.capabilities??(async ()=>providerQuotaCatalog())
1025
+ }}
1026
+ export{ProviderLimitsSection,bindCodexAccount,createCodexAppServerClient,createCodexQuotaAdapter,createOpenCodeV2AccountSource,createProviderLimitsController,createProviderQuotaIntegration,selectedV1Model
1027
+ }