opencode-navigator 0.20.0 → 0.21.1

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.
@@ -117,13 +117,16 @@ child?.kill()
117
117
  }}}
118
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
119
  }
120
- const windows=snapshot.windows.map((window)=>{if(!window.id||!window.label||window.modelID!==snapshot.model.modelID||!window.unit||!Number.isFinite(window.used)||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")
120
+ const windows=snapshot.windows.map((window)=>{if(!window.id||!window.label||window.scope!==undefined&&window.scope!=="account"&&window.scope!=="model"||(window.scope==="account"?!snapshot.accountId||window.modelID!==undefined: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
121
  }
122
122
  return{...window}})
123
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}})
124
127
  if(bankedResets&&(!Number.isSafeInteger(bankedResets.availableCount)||bankedResets.availableCount<0)){throw new Error("Invalid banked reset count")
125
128
  }
126
- return{...snapshot,windows,...bankedResets&&{bankedResets:{availableCount:bankedResets.availableCount,...bankedResets.credits&&{credits:[...bankedResets.credits]}}}}}
129
+ return{...snapshot,windows,...balances&&{balances},...bankedResets&&{bankedResets:{availableCount:bankedResets.availableCount,...bankedResets.credits&&{credits:[...bankedResets.credits]}}}}}
127
130
  import {createSignal as createSignal2,untrack} from "solid-js"
128
131
  import {createSignal} from "solid-js"
129
132
  var idle={status:"idle"}
@@ -190,7 +193,7 @@ setStates((values)=>({...values,[target]:{status:"error",error:requestError(caus
190
193
  },finish
191
194
  }}
192
195
  return{state,start,abort,abortAll}}
193
- function createProviderLimitsController(adapters,selectedModel,signal){const requests=createRequestState(signal)
196
+ function createProviderLimitsController(adapters,selectedModel,signal,refreshContext){const requests=createRequestState(signal)
194
197
  const [snapshots,setSnapshots]=createSignal2({})
195
198
  const targetKey=(model)=>JSON.stringify(model)
196
199
  let activeKey
@@ -204,14 +207,16 @@ targetAbort=new AbortController
204
207
  activeKey=key
205
208
  }
206
209
  return{model,adapter}}
207
- async function refresh(force=false){const{model,adapter}=current()
210
+ async function refresh(force=false){if(force&&refreshContext)
211
+ await refreshContext()
212
+ const{model,adapter}=current()
208
213
  if(!model||!adapter)
209
214
  return
210
215
  const key=targetKey(model)
211
216
  const request=requests.start(key,"read provider limits",Boolean(snapshots()[key]),force)
212
217
  if(!request)
213
218
  return
214
- try{const snapshot=normalizeProviderQuota(await adapter.read(model,request.signal))
219
+ try{const snapshot=normalizeProviderQuota(await adapter.read(model,request.signal,force))
215
220
  if(!request.isCurrent()||targetKey(current().model??model)!==key)
216
221
  return
217
222
  setSnapshots((value)=>({...value,[key]:snapshot}))
@@ -234,11 +239,26 @@ requests.abortAll()
234
239
  for(const adapter of adapters)
235
240
  adapter.dispose?.()
236
241
  }}}
237
- function windows(limit,model){return ["primary","secondary"].flatMap((name)=>{const value=limit[name]
238
- 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}}]:[]
242
+ function codexQuotaWindows(usage,modelID){const buckets=new Map
243
+ const legacy=usage.rateLimits
244
+ if(legacy.limitId==="codex"||!legacy.limitId&&!legacy.normalModelSlug)
245
+ buckets.set("codex",legacy)
246
+ const entries=Object.entries(usage.rateLimitsByLimitId??{})
247
+ for(const [key,limit] of entries)
248
+ buckets.set(limit.limitId??key,limit)
249
+ if(legacy.limitId&&!buckets.has(legacy.limitId))
250
+ buckets.set(legacy.limitId,legacy)
251
+ return [...buckets].flatMap(([id,limit])=>{const account=id==="codex"
252
+ if(!account&&limit.normalModelSlug!==modelID)
253
+ return []
254
+ return ["primary","secondary"].flatMap((name)=>{const value=limit[name]
255
+ if(!value)
256
+ return []
257
+ return [{id:`${id}.${name}`,label:account?`Codex account \xB7 ${name}`:`${limit.limitName ?? id} \xB7 ${name}`,limitID:id,...account?{scope:"account"}:{modelID},unit:"%",used:value.usedPercent,...value.windowDurationMins!==null&&{durationMinutes:value.windowDurationMins},...value.resetsAt!==null&&{resetsAt:value.resetsAt}}]
258
+ })
239
259
  })
240
260
  }
241
- function createCodexQuotaAdapter(client){const adapter={id:"codex",name:"Codex CLI",supports:(model)=>model.providerID==="openai"&&Boolean(model.accountID),async discoverAccount(signal){const account=await client.request("account/read",{refreshToken:false},signal)
261
+ 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)
242
262
  if(account.account?.type!=="chatgpt")
243
263
  throw new Error("Sign in to Codex CLI with ChatGPT")
244
264
  const usage=await client.request("account/rateLimits/read",{supportsLunaReserve:false,excludeResetCreditDetails:true},signal)
@@ -247,18 +267,19 @@ throw new Error("Codex account identity is unavailable")
247
267
  return usage.accountId
248
268
  },async read(model,signal){const base={model,providerId:"codex",providerName:"Codex CLI",fetchedAt:Date.now(),windows:[]
249
269
  }
270
+ if(validateHost&&!await validateHost(model,signal))
271
+ return{...base,availability:"unauthenticated"}
250
272
  const account=await client.request("account/read",{refreshToken:false},signal)
251
273
  if(account.account?.type!=="chatgpt")
252
274
  return{...base,availability:"unauthenticated"}
253
275
  const usage=await client.request("account/rateLimits/read",{supportsLunaReserve:false,excludeResetCreditDetails:false},signal)
254
276
  if(!usage.accountId||usage.accountId!==model.accountID)
255
277
  return{...base,availability:"unauthenticated"}
256
- const candidates=Object.values(usage.rateLimitsByLimitId??{})
257
- const matched=(candidates.length>0?candidates:[usage.rateLimits]).filter((limit)=>limit.normalModelSlug===model.modelID)
258
- if(matched.length===0)
259
- return{...base,accountId:usage.accountId,availability:"unsupported"}
278
+ const windows=codexQuotaWindows(usage,model.modelID)
260
279
  const creditSummary=usage.rateLimitResetCredits
261
- 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}}))
280
+ return normalizeProviderQuota({...base,accountId:usage.accountId,availability:windows.length>0?"ready":"unavailable",ordinaryUsageAllowed:usage.ordinaryUsageAllowed??undefined,windows,...windows.some((window)=>window.scope==="account")&&{message:"Codex account quota; shared across models, not a model-specific allowance."
281
+ },...windows.length===0&&{message:"Codex reported no applicable usage windows for this account and model."
282
+ },...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}}))
262
283
  }}}})
263
284
  },async prepareResetCreditConsumption(model,creditID,signal){const snapshot=await adapter.read(model,signal)
264
285
  if(snapshot.availability==="unauthenticated"||!snapshot.accountId||snapshot.accountId!==model.accountID){throw new Error("Codex account is unavailable or changed")
@@ -280,6 +301,9 @@ throw new Error("Codex account changed")
280
301
  } else
281
302
  await adapter.prepareResetCreditConsumption(model,attempt.creditID,signal)
282
303
  signal.throwIfAborted()
304
+ if(validateHost&&!await validateHost(model,signal))
305
+ throw new Error("OpenCode account or connection changed")
306
+ signal.throwIfAborted()
283
307
  const result=await client.request("account/rateLimitResetCredit/consume",{idempotencyKey:attempt.idempotencyKey,...attempt.creditID&&{creditId:attempt.creditID}},signal)
284
308
  return result.outcome
285
309
  },dispose:client.dispose
@@ -291,21 +315,43 @@ import {createSignal as createSignal3} from "solid-js"
291
315
  import {GuardedConfirmation} from "./tui.js"
292
316
  function createCodexAccountLink(input){const [busy,setBusy]=createSignal3(false)
293
317
  async function link(){const selected=input.model()
294
- if(busy()||!selected||selected.providerID!=="openai"||!input.adapter.discoverAccount)
318
+ if(busy()||!selected||selected.providerID!=="openai"&&selected.hostConnection?.providerID!=="openai"||!input.adapter.discoverAccount)
319
+ return
320
+ if(selected.hostConnection&&selected.hostConnection.status!=="ready")
295
321
  return
296
322
  const key=JSON.stringify(selected)
297
323
  const current=()=>JSON.stringify(input.model())===key
324
+ const signal=input.api.lifecycle?.signal??new AbortController().signal
325
+ const validHost=async ()=>!input.adapter.validateHost||await input.adapter.validateHost(selected,signal)
298
326
  setBusy(true)
299
- try{const accountID=await input.adapter.discoverAccount(input.api.lifecycle?.signal??new AbortController().signal)
327
+ try{if(!await validHost()||!current())
328
+ return
329
+ const accountID=await input.adapter.discoverAccount(signal)
300
330
  if(!current())
301
331
  return
302
- input.dialogs.open(()=>_$createComponent(GuardedConfirmation,{get api(){return input.api
303
- },current,title:"Link Codex CLI account",get message(){return `Use Codex CLI account ${accountID} for ${selected.modelID}? Only link accounts you know match this OpenCode provider.`
304
- },onConfirm:()=>{if(!current())
332
+ 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."
333
+ })
334
+ return
335
+ }
336
+ async function confirm(){if(busy()||!current())
305
337
  return
306
- input.preferences.setCodexAccountBinding(selected.providerID,selected.modelID,accountID)
338
+ setBusy(true)
339
+ try{if(!await validHost()||!current())
340
+ return
341
+ const latest=await input.adapter.discoverAccount(signal)
342
+ if(!current()||latest!==accountID)
343
+ return
344
+ input.preferences.setCodexConnectionBinding(selected.providerID,selected.hostConnection?.id,accountID)
307
345
  input.dialogs.back()
308
- }}))
346
+ } catch{if(current())
347
+ input.api.ui.toast({variant:"warning",title:"Codex account",message:"Account verification failed; try again."
348
+ })
349
+ } finally{setBusy(false)
350
+ }}
351
+ input.dialogs.open(()=>_$createComponent(GuardedConfirmation,{get api(){return input.api
352
+ },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.`
353
+ },onConfirm:()=>void confirm()
354
+ }))
309
355
  } catch{if(current())
310
356
  input.api.ui.toast({variant:"warning",title:"Codex account",message:"Install a supported Codex CLI and sign in with ChatGPT, then try again."
311
357
  })
@@ -314,35 +360,41 @@ input.api.ui.toast({variant:"warning",title:"Codex account",message:"Install a s
314
360
  return{link,busy
315
361
  }}
316
362
  import {effect as _$effect} from "@opentui/solid"
317
- import {createTextNode as _$createTextNode} from "@opentui/solid"
318
363
  import {insertNode as _$insertNode} from "@opentui/solid"
319
364
  import {insert as _$insert} from "@opentui/solid"
320
365
  import {memo as _$memo} from "@opentui/solid"
321
366
  import {setProp as _$setProp} from "@opentui/solid"
322
367
  import {createElement as _$createElement} from "@opentui/solid"
323
- function QuotaWindowRow(props){const reset=()=>props.window.resetsAt===undefined?"":` \xB7 resets ${new Date(props.window.resetsAt * 1000).toLocaleString()}`
324
- return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createTextNode(`: `),_el$5=_$createTextNode(` used`),_el$6=_$createElement("text")
368
+ function QuotaWindowRow(props){const kind=()=>props.window.kind==="rate_limit"?"API \xB7 ":props.window.kind==="configured_limit"?"Limit \xB7 ":""
369
+ const measurement=()=>{if(props.window.used!==undefined)
370
+ return `${props.window.used}${props.window.total === undefined ? "" : `/${props.window.total}`}${props.window.unit === "%" ? "" : " "}${props.window.unit} used`
371
+ if(props.window.remaining!==undefined)
372
+ return `${props.window.remaining} ${props.window.unit} remaining`
373
+ return `${props.window.total} ${props.window.unit} capacity \xB7 usage unknown`
374
+ }
375
+ return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createElement("text"),_el$4=_$createElement("text")
325
376
  _$insertNode(_el$,_el$2)
326
- _$insertNode(_el$,_el$6)
327
- _$insertNode(_el$2,_el$3)
328
- _$insertNode(_el$2,_el$5)
377
+ _$insertNode(_el$,_el$3)
378
+ _$insertNode(_el$,_el$4)
329
379
  _$setProp(_el$2,"wrapMode","none")
330
380
  _$setProp(_el$2,"truncate",true)
381
+ _$insert(_el$2,kind,null)
382
+ _$insert(_el$2,()=>props.window.label,null)
331
383
  _$insert(_el$2,(()=>{var _c$=_$memo(()=>props.window.durationMinutes===undefined)
332
- return()=>_c$()?props.window.label:`${props.window.durationMinutes} min`
333
- })(),_el$3)
334
- _$insert(_el$2,()=>props.window.used,_el$5)
335
- _$insert(_el$2,(()=>{var _c$2=_$memo(()=>props.window.total===undefined)
336
- return()=>_c$2()?"":`/${props.window.total}`
337
- })(),_el$5)
338
- _$insert(_el$2,()=>props.window.unit,_el$5)
339
- _$setProp(_el$6,"wrapMode","word")
340
- _$insert(_el$6,()=>reset().replace(" \xB7 ","")||"Reset time unavailable")
341
- _$effect((_p$)=>{var _v$=props.api.theme.current.text,_v$2=props.api.theme.current.textMuted
384
+ return()=>_c$()?"":` \xB7 ${props.window.durationMinutes} min`
385
+ })(),null)
386
+ _$setProp(_el$3,"wrapMode","word")
387
+ _$insert(_el$3,measurement)
388
+ _$setProp(_el$4,"wrapMode","word")
389
+ _$insert(_el$4,(()=>{var _c$2=_$memo(()=>props.window.resetsAt===undefined)
390
+ return()=>_c$2()?"Reset time unavailable":`resets ${new Date(props.window.resetsAt * 1000).toLocaleString()}`
391
+ })())
392
+ _$effect((_p$)=>{var _v$=props.api.theme.current.text,_v$2=props.api.theme.current.text,_v$3=props.api.theme.current.textMuted
342
393
  _v$!==_p$.e&&(_p$.e=_$setProp(_el$2,"fg",_v$,_p$.e))
343
- _v$2!==_p$.t&&(_p$.t=_$setProp(_el$6,"fg",_v$2,_p$.t))
394
+ _v$2!==_p$.t&&(_p$.t=_$setProp(_el$3,"fg",_v$2,_p$.t))
395
+ _v$3!==_p$.a&&(_p$.a=_$setProp(_el$4,"fg",_v$3,_p$.a))
344
396
  return _p$
345
- },{e:undefined,t:undefined
397
+ },{e:undefined,t:undefined,a:undefined
346
398
  })
347
399
  return _el$
348
400
  })()
@@ -362,9 +414,9 @@ return
362
414
  setBusy(true)
363
415
  const previous=input.preferences.pendingResetAttempt()
364
416
  const attempt=previous??{providerID:input.model.providerID,modelID:input.model.modelID,accountID:input.model.accountID,...creditID&&{creditID
365
- },idempotencyKey:randomUUID(),createdAt:Date.now()
366
- }
367
- try{if(attempt.accountID!==input.model.accountID||attempt.modelID!==input.model.modelID||attempt.providerID!==input.model.providerID){throw new Error("Resolve the earlier attempt on its linked account and model first")
417
+ },idempotencyKey:randomUUID(),createdAt:Date.now(),...input.model.hostConnection?.id&&{connectionID:input.model.hostConnection.id
418
+ }}
419
+ 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")
368
420
  }
369
421
  if(!previous)
370
422
  await input.preferences.beginResetAttempt(attempt)
@@ -427,23 +479,156 @@ else if(option.value.startsWith("credit:"))
427
479
  actions.review(option.value.slice("credit:".length)||undefined)
428
480
  }})
429
481
  }
430
- import {insert as _$insert5} from "@opentui/solid"
431
- import {setProp as _$setProp5} from "@opentui/solid"
432
- import {effect as _$effect5} from "@opentui/solid"
433
- import {createTextNode as _$createTextNode3} from "@opentui/solid"
434
- import {insertNode as _$insertNode3} from "@opentui/solid"
435
- import {createElement as _$createElement5} from "@opentui/solid"
436
- import {memo as _$memo3} from "@opentui/solid"
437
- import {createComponent as _$createComponent7} from "@opentui/solid"
438
- import {For,Show as Show3} from "solid-js"
439
- import {useDialogs as useDialogs2,useIcons as useIcons2} from "./tui.js"
440
482
  import {createComponent as _$createComponent4} from "@opentui/solid"
483
+ import {effect as _$effect3} from "@opentui/solid"
484
+ import {insert as _$insert3} from "@opentui/solid"
485
+ import {memo as _$memo3} from "@opentui/solid"
486
+ import {setProp as _$setProp3} from "@opentui/solid"
487
+ import {createElement as _$createElement3} from "@opentui/solid"
488
+ import {For} from "solid-js"
441
489
  import {effect as _$effect2} from "@opentui/solid"
490
+ import {memo as _$memo2} from "@opentui/solid"
491
+ import {createTextNode as _$createTextNode} from "@opentui/solid"
492
+ import {insertNode as _$insertNode2} from "@opentui/solid"
442
493
  import {insert as _$insert2} from "@opentui/solid"
443
494
  import {setProp as _$setProp2} from "@opentui/solid"
444
495
  import {createElement as _$createElement2} from "@opentui/solid"
496
+ function QuotaBalanceRow(props){return(()=>{var _el$=_$createElement2("box"),_el$2=_$createElement2("text"),_el$3=_$createTextNode(` \xB7 `),_el$4=_$createElement2("text")
497
+ _$insertNode2(_el$,_el$2)
498
+ _$insertNode2(_el$,_el$4)
499
+ _$insertNode2(_el$2,_el$3)
500
+ _$setProp2(_el$2,"wrapMode","word")
501
+ _$insert2(_el$2,()=>props.balance.label,_el$3)
502
+ _$insert2(_el$2,()=>props.balance.scope,null)
503
+ _$setProp2(_el$4,"wrapMode","word")
504
+ _$insert2(_el$4,(()=>{var _c$=_$memo2(()=>!!props.balance.unlimited)
505
+ return()=>_c$()?"No configured cap":`${props.balance.amount} ${props.balance.unit ?? "(provider unit not specified)"}`
506
+ })())
507
+ _$effect2((_p$)=>{var _v$=props.api.theme.current.textMuted,_v$2=props.api.theme.current.text
508
+ _v$!==_p$.e&&(_p$.e=_$setProp2(_el$2,"fg",_v$,_p$.e))
509
+ _v$2!==_p$.t&&(_p$.t=_$setProp2(_el$4,"fg",_v$2,_p$.t))
510
+ return _p$
511
+ },{e:undefined,t:undefined
512
+ })
513
+ return _el$
514
+ })()
515
+ }
516
+ function QuotaSnapshotRows(props){return [(()=>{var _el$=_$createElement3("text")
517
+ _$setProp3(_el$,"wrapMode","none")
518
+ _$setProp3(_el$,"truncate",true)
519
+ _$insert3(_el$,()=>props.snapshot.providerName,null)
520
+ _$insert3(_el$,(()=>{var _c$=_$memo3(()=>!!props.snapshot.accountId)
521
+ return()=>_c$()?` \xB7 ${props.snapshot.accountId}`:""
522
+ })(),null)
523
+ _$effect3((_$p)=>_$setProp3(_el$,"fg",props.api.theme.current.textMuted,_$p))
524
+ return _el$
525
+ })(),_$createComponent4(For,{get each(){return props.snapshot.windows
526
+ },children:(window)=>_$createComponent4(QuotaWindowRow,{get api(){return props.api
527
+ },window
528
+ })
529
+ }),_$createComponent4(For,{get each(){return props.snapshot.balances
530
+ },children:(balance)=>_$createComponent4(QuotaBalanceRow,{get api(){return props.api
531
+ },balance
532
+ })
533
+ })]
534
+ }
535
+ import {createComponent as _$createComponent5} from "@opentui/solid"
536
+ import {createSignal as createSignal4,onMount} from "solid-js"
537
+ import {SelectionMenu as SelectionMenu2,useDialogs as useDialogs2} from "./tui.js"
538
+ var rows=[["openai","OpenAI / Codex",["subscription","api-rate-limits"],"Verified Codex account quota shared across models; 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"]]
539
+ var PROVIDER_QUOTA_CAPABILITIES= rows.map(([id,name,sources,reason,url])=>({id,name,sources,reason,documentation:[url]}))
540
+ 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"
541
+ }
542
+ function providerQuotaCapability(id,canonical){const key=aliases[canonical??id]??canonical??id
543
+ const found=PROVIDER_QUOTA_CAPABILITIES.find((entry)=>entry.id===key)
544
+ 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/"]
545
+ }}
546
+ function providerQuotaCatalog(providers=[]){const entries=new Map(PROVIDER_QUOTA_CAPABILITIES.map((entry)=>[entry.id,entry]))
547
+ for(const provider of providers)
548
+ entries.set(provider.id,{...providerQuotaCapability(provider.id,provider.canonical),id:provider.id,name:provider.name
549
+ })
550
+ return [...entries.values()].sort((a,b)=>a.name.localeCompare(b.name))
551
+ }
552
+ function ProviderSupportDialog(props){const dialogs=useDialogs2(props.api)
553
+ const [entries,setEntries]=createSignal4(providerQuotaCatalog())
554
+ onMount(()=>{if(props.load)
555
+ props.load().then(setEntries).catch(()=>{})
556
+ })
557
+ return _$createComponent5(SelectionMenu2,{get api(){return props.api
558
+ },get title(){return `Provider sources \xB7 ${entries().length}`
559
+ },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}`
560
+ })),{title:"Close",value:"close",description:"Return to the sidebar",icon:"close"
561
+ }]
562
+ },onSelect:(option)=>{if(option.value==="close")
563
+ dialogs.back()
564
+ }})
565
+ }
566
+ var text={type:"string",minLength:1,maxLength:512}
567
+ var model2={type:"object",properties:{providerID:text,modelID:text,sessionID:text,connectionID:text,variant:text,force:{type:"boolean"}},required:["providerID","modelID","sessionID"],additionalProperties:false
568
+ }
569
+ 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
570
+ }}}};
571
+ function missing(error){return typeof error==="object"&&error!==null&&"type" in error&&error.type==="rpc.unavailable"
572
+ }
573
+ function createNativeQuotaAdapter(context){const rpc=context.client.rpc(NavigatorQuotaRpc)
574
+ const listeners=new Set
575
+ const unsubscribe=rpc.events.on("updated",(event)=>{const location=context.location??context.data.location.default()
576
+ if(event.location.directory!==location.directory)
577
+ return
578
+ if(typeof event.data.providerID!=="string"||typeof event.data.modelID!=="string")
579
+ return
580
+ for(const listener of listeners)
581
+ listener(event.data.providerID,event.data.modelID)
582
+ })
583
+ 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:[]
584
+ }
585
+ try{if(typeof rpc.read!=="function")
586
+ return{...base,message:"Enable the Navigator OpenCode 2 server plugin for native provider quotas and balances."
587
+ }
588
+ 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
589
+ },{location:context.location??context.data.location.default(),signal})
590
+ if(model.hostConnection?.id&&value.connectionID&&value.connectionID!==model.hostConnection.id)
591
+ return{...base,availability:"unauthenticated",message:"The provider connection changed during this read."
592
+ }
593
+ 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}})
594
+ } catch(error){if(!missing(error))
595
+ throw error
596
+ return{...base,message:"Enable the Navigator OpenCode 2 server plugin for native provider quotas and balances."
597
+ }}},dispose(){unsubscribe()
598
+ listeners.clear()
599
+ }}
600
+ return{adapter,onUpdated(listener){listeners.add(listener)
601
+ return()=>listeners.delete(listener)
602
+ },async capabilities(){try{return await rpc.capabilities({})
603
+ } catch{return providerQuotaCatalog(context.data.location.provider?.list?.(context.location)??[])
604
+ }}}}
605
+ import {mergeProps as _$mergeProps} from "@opentui/solid"
606
+ import {insert as _$insert7} from "@opentui/solid"
607
+ import {setProp as _$setProp8} from "@opentui/solid"
608
+ import {effect as _$effect8} from "@opentui/solid"
609
+ import {createTextNode as _$createTextNode4} from "@opentui/solid"
610
+ import {insertNode as _$insertNode5} from "@opentui/solid"
611
+ import {createElement as _$createElement8} from "@opentui/solid"
612
+ import {memo as _$memo6} from "@opentui/solid"
613
+ import {createComponent as _$createComponent10} from "@opentui/solid"
614
+ import {Show as Show4} from "solid-js"
615
+ import {useDialogs as useDialogs4,useIcons as useIcons3} from "./tui.js"
616
+ import {setProp as _$setProp5} from "@opentui/solid"
617
+ import {effect as _$effect5} from "@opentui/solid"
618
+ import {createTextNode as _$createTextNode2} from "@opentui/solid"
619
+ import {insertNode as _$insertNode3} from "@opentui/solid"
620
+ import {createElement as _$createElement5} from "@opentui/solid"
621
+ import {memo as _$memo4} from "@opentui/solid"
622
+ import {createComponent as _$createComponent7} from "@opentui/solid"
623
+ import {Show} from "solid-js"
624
+ import {useDialogs as useDialogs3,useIcons} from "./tui.js"
625
+ import {createComponent as _$createComponent6} from "@opentui/solid"
626
+ import {effect as _$effect4} from "@opentui/solid"
627
+ import {insert as _$insert4} from "@opentui/solid"
628
+ import {setProp as _$setProp4} from "@opentui/solid"
629
+ import {createElement as _$createElement4} from "@opentui/solid"
445
630
  import {SelectionBox} from "./tui.js"
446
- import {createSignal as createSignal4,onCleanup} from "solid-js"
631
+ import {createSignal as createSignal5,onCleanup} from "solid-js"
447
632
  function sidebarInteractiveColors(theme,state,normalColor=theme.text,mode="row"){const highlighted=!state.disabled&&(state.focused||mode==="control"&&state.hovered)
448
633
  let backgroundColor=theme.backgroundPanel
449
634
  let foregroundColor=normalColor
@@ -459,7 +644,7 @@ else if(highlighted)
459
644
  foregroundColor=theme.selectedListItemText
460
645
  return{backgroundColor,foregroundColor}}
461
646
  function useSidebarItem(api,interaction,descriptor,normalColor=()=>api.theme.current.text,mode="row"){let unregister
462
- const [hovered,setHovered]=createSignal4(false)
647
+ const [hovered,setHovered]=createSignal5(false)
463
648
  const disabled=()=>descriptor.disabled?.()===true
464
649
  const focused=()=>Boolean(interaction?.ownsFocus()&&interaction.isSelected(descriptor.id))
465
650
  const colors=()=>sidebarInteractiveColors(api.theme.current,{focused:focused(),hovered:hovered(),disabled:disabled()},normalColor(),mode)
@@ -477,35 +662,69 @@ return true
477
662
  }}}
478
663
  function LimitsAction(props){const item=useSidebarItem(props.api,props.interaction,{id:props.id,position:()=>props.position,disabled:()=>props.disabled===true,activate:props.onActivate
479
664
  })
480
- return _$createComponent4(SelectionBox,{ref:(node)=>item.ref(node),get id(){return props.id
665
+ return _$createComponent6(SelectionBox,{ref:(node)=>item.ref(node),get id(){return props.id
481
666
  },height:1,get backgroundColor(){return item.backgroundColor()
482
667
  },get onMouseOver(){return item.onMouseOver
483
668
  },get onMouseOut(){return item.onMouseOut
484
669
  },onMouseUp:(event)=>{event.stopPropagation()
485
670
  item.activate(event)
486
- },get children(){var _el$=_$createElement2("text")
487
- _$setProp2(_el$,"wrapMode","none")
488
- _$setProp2(_el$,"truncate",true)
489
- _$insert2(_el$,()=>props.label)
490
- _$effect2((_$p)=>_$setProp2(_el$,"fg",item.foregroundColor(),_$p))
671
+ },get children(){var _el$=_$createElement4("text")
672
+ _$setProp4(_el$,"wrapMode","none")
673
+ _$setProp4(_el$,"truncate",true)
674
+ _$insert4(_el$,()=>props.label)
675
+ _$effect4((_$p)=>_$setProp4(_el$,"fg",item.foregroundColor(),_$p))
491
676
  return _el$
492
677
  }})
493
678
  }
494
- import {createComponent as _$createComponent5} from "@opentui/solid"
495
- import {effect as _$effect3} from "@opentui/solid"
496
- import {insert as _$insert3} from "@opentui/solid"
497
- import {setProp as _$setProp3} from "@opentui/solid"
498
- import {createElement as _$createElement3} from "@opentui/solid"
499
- import {Show} from "solid-js"
500
- function LimitsStatus(props){const message=()=>{const snapshot=props.controller.snapshot()
679
+ function CodexAccountControls(props){const icons=useIcons()
680
+ const model=()=>props.controller.current().model
681
+ const accountLink=createCodexAccountLink({api:props.api,preferences:props.preferences,adapter:props.codex,model,dialogs:useDialogs3(props.api)
682
+ })
683
+ const eligible=()=>(props.model.providerID==="openai"||props.model.hostConnection?.providerID==="openai")&&props.model.hostConnection?.method!=="api"
684
+ const action=(id,row,label,onActivate)=>_$createComponent7(LimitsAction,{get api(){return props.api
685
+ },get interaction(){return props.interaction
686
+ },id:`${PLUGIN_ID}.limits.${id}`,get position(){return{section:props.navigationSection,row,column:0
687
+ }},label,onActivate,get disabled(){return accountLink.busy()
688
+ }})
689
+ return _$createComponent7(Show,{get when(){return eligible()
690
+ },get children(){return [_$createComponent7(Show,{get when(){return _$memo4(()=>!!!props.model.accountID)()&&(!props.model.hostConnection||props.model.hostConnection.status==="ready")
691
+ },get children(){return [(()=>{var _el$=_$createElement5("text")
692
+ _$insertNode3(_el$,_$createTextNode2(`Link the matching Codex account once for this connection`))
693
+ _$effect5((_$p)=>_$setProp5(_el$,"fg",props.api.theme.current.textMuted,_$p))
694
+ return _el$
695
+ })(),_$memo4(()=>action("link",1,`${icons.icon("scope")} Link Codex CLI account`,()=>void accountLink.link()))]
696
+ }}),_$createComponent7(Show,{get when(){return _$memo4(()=>!!props.model.accountID)()&&props.model.accountSource!=="host"
697
+ },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"
698
+ },get children(){return action("relink",5,`${icons.icon("scope")} Relink Codex account`,()=>void accountLink.link())
699
+ }})]
700
+ }})]
701
+ }})
702
+ }
703
+ import {createComponent as _$createComponent8} from "@opentui/solid"
704
+ import {effect as _$effect6} from "@opentui/solid"
705
+ import {insert as _$insert5} from "@opentui/solid"
706
+ import {setProp as _$setProp6} from "@opentui/solid"
707
+ import {createElement as _$createElement6} from "@opentui/solid"
708
+ import {Show as Show2} from "solid-js"
709
+ function LimitsStatus(props){const message=()=>{const model=props.controller.current().model
710
+ const connection=model?.hostConnection
711
+ const snapshot=props.controller.snapshot()
501
712
  const state=props.controller.state()
502
713
  if(state.status==="error")
503
714
  return snapshot?"Stale \xB7 refresh failed; retry below":"Quota read failed \xB7 retry below"
715
+ if(snapshot?.message)
716
+ return snapshot.availability==="stale"?`Stale \xB7 ${snapshot.message}`:snapshot.availability==="ready"?`Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()} \xB7 ${snapshot.message}`:snapshot.message
717
+ if(connection?.status==="loading")
718
+ return "Checking the OpenCode account\u2026"
719
+ if(connection?.status==="unavailable")
720
+ return "OpenCode account metadata unavailable \xB7 refresh below"
721
+ if(connection?.status==="unsupported")
722
+ return "Codex quotas require an active ChatGPT OAuth connection"
504
723
  if(!snapshot)
505
- return state.status==="loading"?"Reading provider limits\u2026":"Refresh to read provider limits"
724
+ return state.status==="loading"?"Reading provider limits\u2026":props.controller.current().adapter?"Refresh to read provider limits":providerQuotaCapability(model?.providerID??"").reason
506
725
  const availability=snapshot.availability
507
726
  if(availability==="unauthenticated")
508
- return "Codex sign-in or linked account changed \xB7 relink below"
727
+ 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"
509
728
  if(availability==="unsupported")
510
729
  return "No documented quota bucket for this model"
511
730
  if(availability==="rate_limited")
@@ -514,24 +733,24 @@ if(availability==="stale")
514
733
  return "Stale provider data \xB7 refresh below"
515
734
  return `Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()}`
516
735
  }
517
- return _$createComponent5(Show,{get when(){return props.controller.current().adapter
518
- },get children(){var _el$=_$createElement3("text")
519
- _$setProp3(_el$,"wrapMode","word")
520
- _$insert3(_el$,message)
521
- _$effect3((_$p)=>_$setProp3(_el$,"fg",props.api.theme.current.textMuted,_$p))
736
+ return _$createComponent8(Show2,{get when(){return props.controller.current().model
737
+ },get children(){var _el$=_$createElement6("text")
738
+ _$setProp6(_el$,"wrapMode","word")
739
+ _$insert5(_el$,message)
740
+ _$effect6((_$p)=>_$setProp6(_el$,"fg",props.api.theme.current.textMuted,_$p))
522
741
  return _el$
523
742
  }})
524
743
  }
525
- import {createComponent as _$createComponent6} from "@opentui/solid"
526
- import {effect as _$effect4} from "@opentui/solid"
527
- import {createTextNode as _$createTextNode2} from "@opentui/solid"
528
- import {insertNode as _$insertNode2} from "@opentui/solid"
529
- import {memo as _$memo2} from "@opentui/solid"
530
- import {insert as _$insert4} from "@opentui/solid"
531
- import {setProp as _$setProp4} from "@opentui/solid"
532
- import {createElement as _$createElement4} from "@opentui/solid"
533
- import {Show as Show2,createEffect as createEffect2,createSignal as createSignal5} from "solid-js"
534
- import {SelectionBox as SelectionBox2,useIcons} from "./tui.js"
744
+ import {createComponent as _$createComponent9} from "@opentui/solid"
745
+ import {effect as _$effect7} from "@opentui/solid"
746
+ import {createTextNode as _$createTextNode3} from "@opentui/solid"
747
+ import {insertNode as _$insertNode4} from "@opentui/solid"
748
+ import {memo as _$memo5} from "@opentui/solid"
749
+ import {insert as _$insert6} from "@opentui/solid"
750
+ import {setProp as _$setProp7} from "@opentui/solid"
751
+ import {createElement as _$createElement7} from "@opentui/solid"
752
+ import {Show as Show3,createEffect as createEffect2,createSignal as createSignal6} from "solid-js"
753
+ import {SelectionBox as SelectionBox2,useIcons as useIcons2} from "./tui.js"
535
754
  var graphemes=new Intl.Segmenter(undefined,{granularity:"grapheme"
536
755
  })
537
756
  function truncateEnd(value,maxWidth){if(Bun.stringWidth(value)<=maxWidth)
@@ -548,8 +767,8 @@ result += segment
548
767
  }
549
768
  return result + ellipsis
550
769
  }
551
- function Section(props){const icons=useIcons()
552
- const [actionWidth,setActionWidth]=createSignal5()
770
+ function Section(props){const icons=useIcons2()
771
+ const [actionWidth,setActionWidth]=createSignal6()
553
772
  const icon=()=>props.section?icons.section(props.section):props.icon
554
773
  const theme=()=>props.api.theme.current
555
774
  const item=useSidebarItem(props.api,props.interaction,{id:props.sectionId??`section.${props.title}`,position:()=>({section:props.navigationSection??0,row:0,column:0
@@ -565,163 +784,140 @@ const actionLabel=()=>{const label=props.headerAction?.label()??""
565
784
  const width=actionWidth()
566
785
  return width===undefined?label:truncateEnd(label,width-2)
567
786
  }
568
- return(()=>{var _el$=_$createElement4("box")
569
- _$setProp4(_el$,"gap",1)
570
- _$insert4(_el$,_$createComponent6(SelectionBox2,{ref:(node)=>item.ref(node),get id(){return props.sectionId
787
+ return(()=>{var _el$=_$createElement7("box")
788
+ _$setProp7(_el$,"gap",1)
789
+ _$insert6(_el$,_$createComponent9(SelectionBox2,{ref:(node)=>item.ref(node),get id(){return props.sectionId
571
790
  },flexDirection:"row",height:1,justifyContent:"space-between",gap:1,paddingLeft:1,paddingRight:1,get backgroundColor(){return item.backgroundColor()
572
791
  },get onMouseOver(){return item.onMouseOver
573
792
  },get onMouseOut(){return item.onMouseOut
574
- },onMouseDown:(event)=>item.activate(event),onSizeChange:()=>setActionWidth(undefined),get children(){return [(()=>{var _el$2=_$createElement4("text"),_el$3=_$createElement4("span"),_el$4=_$createTextNode2(` `),_el$5=_$createElement4("span"),_el$6=_$createElement4("b")
575
- _$insertNode2(_el$2,_el$3)
576
- _$insertNode2(_el$2,_el$4)
577
- _$insertNode2(_el$2,_el$5)
578
- _$insertNode2(_el$2,_el$6)
579
- _$setProp4(_el$2,"flexShrink",0)
580
- _$setProp4(_el$2,"wrapMode","none")
581
- _$insert4(_el$3,()=>icons.icon(props.open?"expanded":"collapsed"))
582
- _$insert4(_el$5,(()=>{var _c$=_$memo2(()=>!!icon())
793
+ },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")
794
+ _$insertNode4(_el$2,_el$3)
795
+ _$insertNode4(_el$2,_el$4)
796
+ _$insertNode4(_el$2,_el$5)
797
+ _$insertNode4(_el$2,_el$6)
798
+ _$setProp7(_el$2,"flexShrink",0)
799
+ _$setProp7(_el$2,"wrapMode","none")
800
+ _$insert6(_el$3,()=>icons.icon(props.open?"expanded":"collapsed"))
801
+ _$insert6(_el$5,(()=>{var _c$=_$memo5(()=>!!icon())
583
802
  return()=>_c$()?`${icon()} `:""
584
803
  })())
585
- _$insert4(_el$6,()=>props.title)
586
- _$effect4((_p$)=>{var _v$=item.foregroundColor(),_v$2={fg:item.focused()?item.foregroundColor():theme().accent
804
+ _$insert6(_el$6,()=>props.title)
805
+ _$effect7((_p$)=>{var _v$=item.foregroundColor(),_v$2={fg:item.focused()?item.foregroundColor():theme().accent
587
806
  },_v$3={fg:item.focused()?item.foregroundColor():theme().accent
588
807
  }
589
- _v$!==_p$.e&&(_p$.e=_$setProp4(_el$2,"fg",_v$,_p$.e))
590
- _v$2!==_p$.t&&(_p$.t=_$setProp4(_el$3,"style",_v$2,_p$.t))
591
- _v$3!==_p$.a&&(_p$.a=_$setProp4(_el$5,"style",_v$3,_p$.a))
808
+ _v$!==_p$.e&&(_p$.e=_$setProp7(_el$2,"fg",_v$,_p$.e))
809
+ _v$2!==_p$.t&&(_p$.t=_$setProp7(_el$3,"style",_v$2,_p$.t))
810
+ _v$3!==_p$.a&&(_p$.a=_$setProp7(_el$5,"style",_v$3,_p$.a))
592
811
  return _p$
593
812
  },{e:undefined,t:undefined,a:undefined
594
813
  })
595
814
  return _el$2
596
- })(),(()=>{var _el$7=_$createElement4("box"),_el$8=_$createElement4("text")
597
- _$insertNode2(_el$7,_el$8)
598
- _$setProp4(_el$7,"flexDirection","row")
599
- _$setProp4(_el$7,"gap",1)
600
- _$setProp4(_el$7,"flexGrow",1)
601
- _$setProp4(_el$7,"minWidth",0)
602
- _$setProp4(_el$7,"justifyContent","flex-end")
603
- _$insert4(_el$7,_$createComponent6(Show2,{when:action,children:(control)=>_$createComponent6(SelectionBox2,{ref:(node)=>control().ref(node),get id(){return props.headerAction?.id
815
+ })(),(()=>{var _el$7=_$createElement7("box"),_el$8=_$createElement7("text")
816
+ _$insertNode4(_el$7,_el$8)
817
+ _$setProp7(_el$7,"flexDirection","row")
818
+ _$setProp7(_el$7,"gap",1)
819
+ _$setProp7(_el$7,"flexGrow",1)
820
+ _$setProp7(_el$7,"minWidth",0)
821
+ _$setProp7(_el$7,"justifyContent","flex-end")
822
+ _$insert6(_el$7,_$createComponent9(Show3,{when:action,children:(control)=>_$createComponent9(SelectionBox2,{ref:(node)=>control().ref(node),get id(){return props.headerAction?.id
604
823
  },flexShrink:1,minWidth:3,overflow:"hidden",get backgroundColor(){return control().backgroundColor()
605
824
  },onSizeChange:function(){setActionWidth(this.width)
606
825
  },get onMouseOver(){return control().onMouseOver
607
826
  },get onMouseOut(){return control().onMouseOut
608
827
  },onMouseDown:(event)=>event.stopPropagation(),onMouseUp:(event)=>{event.stopPropagation()
609
828
  control().activate(event)
610
- },get children(){var _el$9=_$createElement4("text")
611
- _$setProp4(_el$9,"wrapMode","none")
612
- _$setProp4(_el$9,"height",1)
613
- _$insert4(_el$9,actionLabel)
614
- _$effect4((_$p)=>_$setProp4(_el$9,"fg",control().foregroundColor(),_$p))
829
+ },get children(){var _el$9=_$createElement7("text")
830
+ _$setProp7(_el$9,"wrapMode","none")
831
+ _$setProp7(_el$9,"height",1)
832
+ _$insert6(_el$9,actionLabel)
833
+ _$effect7((_$p)=>_$setProp7(_el$9,"fg",control().foregroundColor(),_$p))
615
834
  return _el$9
616
835
  }})
617
836
  }),_el$8)
618
- _$setProp4(_el$8,"wrapMode","none")
619
- _$setProp4(_el$8,"truncate",true)
620
- _$setProp4(_el$8,"height",1)
621
- _$insert4(_el$8,()=>props.summary)
622
- _$effect4((_p$)=>{var _v$4=item.focused()?item.foregroundColor():theme().textMuted,_v$5=props.headerAction?0:1
623
- _v$4!==_p$.e&&(_p$.e=_$setProp4(_el$8,"fg",_v$4,_p$.e))
624
- _v$5!==_p$.t&&(_p$.t=_$setProp4(_el$8,"flexShrink",_v$5,_p$.t))
837
+ _$setProp7(_el$8,"wrapMode","none")
838
+ _$setProp7(_el$8,"truncate",true)
839
+ _$setProp7(_el$8,"height",1)
840
+ _$insert6(_el$8,()=>props.summary)
841
+ _$effect7((_p$)=>{var _v$4=item.focused()?item.foregroundColor():theme().textMuted,_v$5=props.headerAction?0:1
842
+ _v$4!==_p$.e&&(_p$.e=_$setProp7(_el$8,"fg",_v$4,_p$.e))
843
+ _v$5!==_p$.t&&(_p$.t=_$setProp7(_el$8,"flexShrink",_v$5,_p$.t))
625
844
  return _p$
626
845
  },{e:undefined,t:undefined
627
846
  })
628
847
  return _el$7
629
848
  })()]
630
849
  }}),null)
631
- _$insert4(_el$,_$createComponent6(Show2,{get when(){return props.open
850
+ _$insert6(_el$,_$createComponent9(Show3,{get when(){return props.open
632
851
  },get children(){return props.children
633
852
  }}),null)
634
853
  return _el$
635
854
  })()
636
855
  }
637
- function ProviderLimitsSection(props){const icons=useIcons2()
638
- const dialogs=useDialogs2(props.api)
856
+ function ProviderLimitsSection(props){const icons=useIcons3()
857
+ const dialogs=useDialogs4(props.api)
639
858
  const model=()=>props.controller.current().model
640
859
  const snapshot=props.controller.snapshot
641
- const accountLink=createCodexAccountLink({api:props.api,preferences:props.preferences,adapter:props.codex,model,dialogs
642
- })
643
- const action=(id,row,label,onActivate,disabled=false)=>_$createComponent7(LimitsAction,{get api(){return props.api
860
+ const count=()=>(snapshot()?.windows.length??0) + (snapshot()?.balances?.length??0)
861
+ const action=(id,row,label,onActivate,disabled=false)=>_$createComponent10(LimitsAction,{get api(){return props.api
644
862
  },get interaction(){return props.interaction
645
863
  },id:`${PLUGIN_ID}.limits.${id}`,get position(){return{section:props.navigationSection,row,column:0
646
864
  }},label,onActivate,disabled
647
865
  })
648
866
  function openCredits(){const selected=model()
649
- if(!selected)
650
- return
651
- dialogs.open(()=>_$createComponent7(ResetCreditDialog,{get api(){return props.api
867
+ if(selected)
868
+ dialogs.open(()=>_$createComponent10(ResetCreditDialog,{get api(){return props.api
652
869
  },get preferences(){return props.preferences
653
870
  },get controller(){return props.controller
654
871
  },get adapter(){return props.codex
655
872
  },model:selected
656
873
  }))
657
874
  }
658
- return _$createComponent7(Section,{get api(){return props.api
875
+ return _$createComponent10(Section,{get api(){return props.api
659
876
  },get interaction(){return props.interaction
660
877
  },sectionId:`${PLUGIN_ID}.section.limits`,get navigationSection(){return props.navigationSection
661
- },title:"LIMITS",section:"limits",get summary(){return _$memo3(()=>snapshot()?.availability==="ready")()?String(snapshot()?.windows.length):""
878
+ },title:"LIMITS",section:"limits",get summary(){return _$memo6(()=>!!count())()?String(count()):""
662
879
  },get open(){return props.preferences.expanded().limits
663
- },onToggle:()=>props.preferences.toggleSectionExpanded("limits"),get children(){return _$createComponent7(Show3,{get when(){return model()
664
- },get fallback(){return(()=>{var _el$=_$createElement5("text")
665
- _$insertNode3(_el$,_$createTextNode3(`Open a session to view model limits`))
666
- _$effect5((_$p)=>_$setProp5(_el$,"fg",props.api.theme.current.textMuted,_$p))
880
+ },onToggle:()=>props.preferences.toggleSectionExpanded("limits"),get children(){return _$createComponent10(Show4,{get when(){return model()
881
+ },get fallback(){return(()=>{var _el$=_$createElement8("text")
882
+ _$insertNode5(_el$,_$createTextNode4(`Open a session to view model limits`))
883
+ _$effect8((_$p)=>_$setProp8(_el$,"fg",props.api.theme.current.textMuted,_$p))
667
884
  return _el$
668
885
  })()
669
- },children:(selected)=>(()=>{var _el$3=_$createElement5("box"),_el$4=_$createElement5("text"),_el$5=_$createTextNode3(`/`)
670
- _$insertNode3(_el$3,_el$4)
671
- _$setProp5(_el$3,"gap",1)
672
- _$insertNode3(_el$4,_el$5)
673
- _$setProp5(_el$4,"wrapMode","none")
674
- _$setProp5(_el$4,"truncate",true)
675
- _$insert5(_el$4,()=>selected().providerID,_el$5)
676
- _$insert5(_el$4,()=>selected().modelID,null)
677
- _$insert5(_el$3,_$createComponent7(Show3,{get when(){return _$memo3(()=>!!!selected().accountID)()&&selected().providerID==="openai"
678
- },get children(){return [(()=>{var _el$6=_$createElement5("text")
679
- _$insertNode3(_el$6,_$createTextNode3(`Link the matching Codex account to read quotas`))
680
- _$effect5((_$p)=>_$setProp5(_el$6,"fg",props.api.theme.current.textMuted,_$p))
886
+ },children:(selected)=>(()=>{var _el$3=_$createElement8("box"),_el$4=_$createElement8("text"),_el$5=_$createTextNode4(`/`)
887
+ _$insertNode5(_el$3,_el$4)
888
+ _$setProp8(_el$3,"gap",1)
889
+ _$insertNode5(_el$4,_el$5)
890
+ _$setProp8(_el$4,"wrapMode","none")
891
+ _$setProp8(_el$4,"truncate",true)
892
+ _$insert7(_el$4,()=>selected().providerID,_el$5)
893
+ _$insert7(_el$4,()=>selected().modelID,null)
894
+ _$insert7(_el$3,_$createComponent10(CodexAccountControls,_$mergeProps(props,{get model(){return selected()
895
+ }})),null)
896
+ _$insert7(_el$3,_$createComponent10(Show4,{get when(){return _$memo6(()=>!!(snapshot()&&["ready","stale"].includes(snapshot().availability)))()?snapshot():undefined
897
+ },children:(quota)=>_$createComponent10(QuotaSnapshotRows,{get api(){return props.api
898
+ },get snapshot(){return quota()
899
+ }})
900
+ }),null)
901
+ _$insert7(_el$3,_$createComponent10(Show4,{get when(){return snapshot()?.bankedResets
902
+ },children:(credits)=>[(()=>{var _el$6=_$createElement8("text"),_el$7=_$createTextNode4(`Banked resets: `)
903
+ _$insertNode5(_el$6,_el$7)
904
+ _$insert7(_el$6,()=>credits().availableCount,null)
905
+ _$effect8((_$p)=>_$setProp8(_el$6,"fg",props.api.theme.current.textMuted,_$p))
681
906
  return _el$6
682
- })(),_$memo3(()=>action("link",1,`${icons.icon("scope")} Link Codex CLI account`,()=>void accountLink.link(),accountLink.busy()))]
683
- }}),null)
684
- _$insert5(_el$3,_$createComponent7(Show3,{get when(){return _$memo3(()=>!!selected().accountID)()&&snapshot()?.availability==="ready"
685
- },get children(){return [(()=>{var _el$8=_$createElement5("text"),_el$9=_$createTextNode3(`Codex CLI \xB7 `)
686
- _$insertNode3(_el$8,_el$9)
687
- _$setProp5(_el$8,"wrapMode","none")
688
- _$setProp5(_el$8,"truncate",true)
689
- _$insert5(_el$8,()=>snapshot()?.accountId,null)
690
- _$effect5((_$p)=>_$setProp5(_el$8,"fg",props.api.theme.current.textMuted,_$p))
691
- return _el$8
692
- })(),_$createComponent7(For,{get each(){return snapshot()?.windows
693
- },children:(window)=>_$createComponent7(QuotaWindowRow,{get api(){return props.api
694
- },window
695
- })
696
- }),_$createComponent7(Show3,{get when(){return snapshot()?.bankedResets
697
- },children:(credits)=>[(()=>{var _el$10=_$createElement5("text"),_el$11=_$createTextNode3(`Banked resets: `)
698
- _$insertNode3(_el$10,_el$11)
699
- _$insert5(_el$10,()=>credits().availableCount,null)
700
- _$effect5((_$p)=>_$setProp5(_el$10,"fg",props.api.theme.current.textMuted,_$p))
701
- return _el$10
702
- })(),_$createComponent7(Show3,{get when(){return credits().availableCount>0||props.preferences.pendingResetAttempt()
907
+ })(),_$createComponent10(Show4,{get when(){return credits().availableCount>0||props.preferences.pendingResetAttempt()
703
908
  },get children(){return action("credits",2,`${icons.icon("info")} Review reset credits`,openCredits)
704
909
  }})]
705
- })]
706
- }}),null)
707
- _$insert5(_el$3,_$createComponent7(LimitsStatus,{get api(){return props.api
910
+ }),null)
911
+ _$insert7(_el$3,_$createComponent10(LimitsStatus,{get api(){return props.api
708
912
  },get controller(){return props.controller
709
913
  }}),null)
710
- _$insert5(_el$3,_$createComponent7(Show3,{get when(){return selected().providerID!=="openai"
711
- },get children(){var _el$0=_$createElement5("text")
712
- _$insertNode3(_el$0,_$createTextNode3(`No documented quota API for this model`))
713
- _$effect5((_$p)=>_$setProp5(_el$0,"fg",props.api.theme.current.textMuted,_$p))
714
- return _el$0
715
- }}),null)
716
- _$insert5(_el$3,_$createComponent7(Show3,{get when(){return props.controller.current().adapter
914
+ _$insert7(_el$3,_$createComponent10(Show4,{get when(){return props.controller.current().adapter||selected().hostConnection?.status==="unavailable"
717
915
  },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))
718
916
  }}),null)
719
- _$insert5(_el$3,_$createComponent7(Show3,{get when(){return _$memo3(()=>selected().providerID==="openai")()&&selected().accountID
720
- },get children(){return [_$memo3(()=>action("unlink",4,`${icons.icon("close")} Unlink Codex account`,()=>props.preferences.setCodexAccountBinding(selected().providerID,selected().modelID))),_$createComponent7(Show3,{get when(){return snapshot()?.availability==="unauthenticated"
721
- },get children(){return action("relink",5,`${icons.icon("scope")} Relink Codex account`,()=>void accountLink.link(),accountLink.busy())
722
- }})]
723
- }}),null)
724
- _$effect5((_$p)=>_$setProp5(_el$4,"fg",props.api.theme.current.textMuted,_$p))
917
+ _$insert7(_el$3,()=>action("sources",6,`${icons.icon("info")} Provider sources`,()=>dialogs.open(()=>_$createComponent10(ProviderSupportDialog,{get api(){return props.api
918
+ },get load(){return props.capabilities
919
+ }}))),null)
920
+ _$effect8((_$p)=>_$setProp8(_el$4,"fg",props.api.theme.current.textMuted,_$p))
725
921
  return _el$3
726
922
  })()
727
923
  })
@@ -735,5 +931,111 @@ const model=api.state.session.get(sessionID)?.model
735
931
  if(!model)
736
932
  return
737
933
  return{sessionID,providerID:model.providerID,modelID:model.id,...model.variant&&{variant:model.variant}}}
738
- export{ProviderLimitsSection,createCodexAppServerClient,createCodexQuotaAdapter,createProviderLimitsController,selectedV1Model
934
+ import {createSignal as createSignal7,untrack as untrack2} from "solid-js"
935
+ var CHATGPT_METHODS=new Set(["chatgpt-browser","chatgpt-headless"])
936
+ function providerAccountIdentity(provider,credentials){if(!provider.integrationID||provider.activation==="disabled")
937
+ return{status:"unsupported"}
938
+ const active=credentials.filter((entry)=>entry.integrationID===provider.integrationID&&entry.active)
939
+ if(active.length!==1)
940
+ return{status:"unavailable"}
941
+ const credential=active[0]
942
+ const id=`credential:${credential.id}`
943
+ const providerID=provider.canonical??provider.id
944
+ if(credential.value.type==="key")
945
+ return{status:"ready",id,method:"api",providerID}
946
+ if(provider.integrationID!=="openai")
947
+ return{status:"ready",id,method:"oauth",providerID}
948
+ if(!CHATGPT_METHODS.has(credential.value.methodID))
949
+ return{status:"unsupported",id}
950
+ const account=credential.value.metadata?.accountID
951
+ const accountID=typeof account==="string"&&account.trim()?account:undefined
952
+ const header=Object.entries(provider.headers??{}).find(([name])=>name.toLowerCase()==="chatgpt-account-id")?.[1]
953
+ if(accountID&&header&&header!==accountID)
954
+ return{status:"loading"}
955
+ return{status:"ready",id,method:"oauth",providerID,...accountID&&{accountID}}}
956
+ function createOpenCodeV2AccountSource(context,lifecycle){const [connections,setConnections]=createSignal7({})
957
+ let generation=0
958
+ let disposed=false
959
+ let lastModel
960
+ let pending
961
+ const location=()=>context.location??context.data.location.default()
962
+ const key=(model)=>JSON.stringify([model.providerID,location()])
963
+ function save(target,connection){if(JSON.stringify(connections()[target])!==JSON.stringify(connection))
964
+ setConnections((values)=>({...values,[target]:connection}))
965
+ }
966
+ async function read(model,signal=lifecycle){if(disposed||signal.aborted)
967
+ return{status:"unavailable"}
968
+ const target=key(model)
969
+ const epoch=generation
970
+ if(pending?.key===target&&pending.generation===epoch)
971
+ return pending.promise
972
+ const promise=(async ()=>{try{if(!context.client.provider?.get||!context.client.credential?.list)
973
+ throw new Error("Account metadata unavailable")
974
+ const options={signal:AbortSignal.any([lifecycle,signal])}
975
+ const [provider,credentials]=await Promise.all([context.client.provider.get({providerID:model.providerID,location:location()},options),context.client.credential.list(options)])
976
+ let connection=provider.data.id===model.providerID?providerAccountIdentity(provider.data,credentials):{status:"unavailable"}
977
+ 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)
978
+ const active=integration.data.connections[0]
979
+ if(active?.type==="env"&&!active.status)
980
+ connection={status:"ready",id:`env:${active.name}`,method:"api",providerID:provider.data.canonical??provider.data.id
981
+ }}
982
+ if(disposed||signal.aborted||epoch!==generation||key(model)!==target)
983
+ return{status:"unavailable"}
984
+ save(target,connection)
985
+ return connection
986
+ } catch{if(!disposed&&!signal.aborted&&epoch===generation)
987
+ save(target,{status:"unavailable"})
988
+ return{status:"unavailable"}}})()
989
+ pending={key:target,generation:epoch,promise}
990
+ try{return await promise
991
+ } finally{if(pending?.promise===promise)
992
+ pending=undefined
993
+ }}
994
+ function invalidate(){generation++
995
+ setConnections({})
996
+ if(lastModel&&!disposed)
997
+ read(lastModel)
998
+ }
999
+ const events=["credential.switched","credential.updated","integration.updated","provider.updated","server.connected"]
1000
+ const unsubscribes=events.map((event)=>context.data.on(event,()=>untrack2(invalidate)))
1001
+ return{current(model){lastModel=model
1002
+ return{...model,hostConnection:connections()[key(model)]??{status:"loading"}}},async refresh(model){await read(model)
1003
+ },async validate(model,signal){const actual=await read(model,signal)
1004
+ const expected=model.hostConnection
1005
+ return actual.status==="ready"&&expected?.status==="ready"&&actual.id===expected.id&&actual.accountID===expected.accountID&&(!actual.accountID||actual.accountID===model.accountID)
1006
+ },dispose(){disposed=true
1007
+ generation++
1008
+ for(const unsubscribe of unsubscribes)
1009
+ unsubscribe()
1010
+ }}}
1011
+ function bindCodexAccount(model,preferences){const connection=model.hostConnection
1012
+ if(model.providerID!=="openai"&&connection?.providerID!=="openai"||connection?.method==="api")
1013
+ return{...model,accountID:undefined,accountSource:undefined}
1014
+ if(connection&&connection.status!=="ready")
1015
+ return{...model,accountID:undefined,accountSource:undefined}
1016
+ const accountID=connection?.accountID??preferences.codexAccountForConnection(model.providerID,connection?.id,model.modelID)
1017
+ const accountSource=connection?.accountID?"host":"binding"
1018
+ return{...model,accountID,accountSource:accountID?accountSource:undefined}}
1019
+ function selectedV2Model(context){const route=context.ui.router.current()
1020
+ const model=context.ui.model.current()
1021
+ if(route.type!=="session"||!model)
1022
+ return
1023
+ return{sessionID:route.sessionID,providerID:model.providerID,modelID:model.modelID,...model.variant&&{variant:model.variant}}}
1024
+ function createProviderQuotaIntegration(api,preferences,version,context){const selectModel=()=>context?selectedV2Model(context):selectedV1Model(api)
1025
+ const accountSource=context?createOpenCodeV2AccountSource(context,api.lifecycle.signal):undefined
1026
+ const codex=createCodexQuotaAdapter(createCodexAppServerClient({version}),accountSource?.validate)
1027
+ const native=context?createNativeQuotaAdapter(context):undefined
1028
+ const controller=createProviderLimitsController([codex,...native?[native.adapter]:[]],()=>{const model=selectModel()
1029
+ return model?bindCodexAccount(accountSource?.current(model)??model,preferences):undefined
1030
+ },api.lifecycle.signal,accountSource&&(async ()=>{const model=selectModel()
1031
+ if(model)
1032
+ await accountSource.refresh(model)
1033
+ }))
1034
+ native?.onUpdated((providerID,modelID)=>{const selected=selectModel()
1035
+ if(selected?.providerID===providerID&&selected.modelID===modelID)
1036
+ controller.refresh()
1037
+ })
1038
+ return{controller,codex,accountSource,selectModel,capabilities:native?.capabilities??(async ()=>providerQuotaCatalog())
1039
+ }}
1040
+ export{ProviderLimitsSection,bindCodexAccount,createCodexAppServerClient,createCodexQuotaAdapter,createOpenCodeV2AccountSource,createProviderLimitsController,createProviderQuotaIntegration,selectedV1Model
739
1041
  }