opencode-navigator 0.26.0 → 0.27.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.
@@ -1,5 +1,5 @@
1
1
  import {PLUGIN_ID
2
- } from "./chunk-bv0nm1tj.js";
2
+ } from "./chunk-dm1y8x49.js";
3
3
  function createCodexAppServerClient(input){const command=input.command??["codex","app-server","--stdio"]
4
4
  const timeoutMs=input.timeoutMs??1e4
5
5
  const pending=new Map
@@ -134,6 +134,29 @@ return{...balance}})
134
134
  if(bankedResets&&(!Number.isSafeInteger(bankedResets.availableCount)||bankedResets.availableCount<0)){throw new Error("Invalid banked reset count")
135
135
  }
136
136
  return{...snapshot,windows,...balances&&{balances},...bankedResets&&{bankedResets:{availableCount:bankedResets.availableCount,...bankedResets.credits&&{credits:[...bankedResets.credits]}}}}}
137
+ var CHATGPT_METHODS=new Set(["chatgpt-browser","chatgpt-headless"])
138
+ function providerAccountIdentity(provider,credentials){if(!provider.integrationID||provider.activation==="disabled")
139
+ return{status:"unsupported"}
140
+ const active=credentials.filter((entry)=>entry.integrationID===provider.integrationID&&entry.active)
141
+ if(active.length!==1)
142
+ return{status:"unavailable"}
143
+ const credential=active[0]
144
+ const id=`credential:${credential.id}`
145
+ if(credential.value.type==="external")
146
+ return{status:"unsupported",id}
147
+ const providerID=provider.canonical??provider.id
148
+ if(credential.value.type==="key")
149
+ return{status:"ready",id,method:"api",providerID}
150
+ if(provider.integrationID!=="openai")
151
+ return{status:"ready",id,method:"oauth",providerID}
152
+ if(!credential.value.methodID||!CHATGPT_METHODS.has(credential.value.methodID))
153
+ return{status:"unsupported",id}
154
+ const account=credential.value.metadata?.accountID
155
+ const accountID=typeof account==="string"&&account.trim()?account:undefined
156
+ const header=Object.entries(provider.headers??{}).find(([name])=>name.toLowerCase()==="chatgpt-account-id")?.[1]
157
+ if(accountID&&header&&header!==accountID)
158
+ return{status:"loading"}
159
+ return{status:"ready",id,method:"oauth",providerID,...accountID&&{accountID}}}
137
160
  import {createSignal as createSignal2,untrack} from "solid-js"
138
161
  import {createSignal} from "solid-js"
139
162
  var idle={status:"idle"}
@@ -201,6 +224,7 @@ setStates((values)=>({...values,[target]:{status:"error",error:requestError(caus
201
224
  }}
202
225
  return{state,start,abort,abortAll}}
203
226
  function createProviderLimitsController(adapters,selectedModel,signal,refreshContext){const requests=createRequestState(signal)
227
+ const pending=new Map
204
228
  const [snapshots,setSnapshots]=createSignal2({})
205
229
  const targetKey=(model)=>JSON.stringify(model)
206
230
  let activeKey
@@ -209,6 +233,7 @@ function current(){const model=selectedModel()
209
233
  const adapter=model&&adapters.find((candidate)=>candidate.supports(model))
210
234
  const key=model?targetKey(model):undefined
211
235
  if(activeKey!==key){untrack(requests.abortAll)
236
+ pending.clear()
212
237
  targetAbort.abort()
213
238
  targetAbort=new AbortController
214
239
  activeKey=key
@@ -220,10 +245,13 @@ const{model,adapter}=current()
220
245
  if(!model||!adapter)
221
246
  return
222
247
  const key=targetKey(model)
248
+ const existing=pending.get(key)
249
+ if(existing&&!force)
250
+ return existing
223
251
  const request=requests.start(key,"read provider limits",Boolean(snapshots()[key]),force)
224
252
  if(!request)
225
253
  return
226
- try{const snapshot=normalizeProviderQuota(await adapter.read(model,request.signal,force))
254
+ const promise=(async ()=>{try{const snapshot=normalizeProviderQuota(await adapter.read(model,request.signal,force))
227
255
  if(!request.isCurrent()||targetKey(current().model??model)!==key)
228
256
  return
229
257
  setSnapshots((value)=>({...value,[key]:snapshot}))
@@ -235,6 +263,11 @@ request.succeed()
235
263
  } else
236
264
  request.fail(error)
237
265
  } finally{request.finish()
266
+ }})()
267
+ pending.set(key,promise)
268
+ try{await promise
269
+ } finally{if(pending.get(key)===promise)
270
+ pending.delete(key)
238
271
  }}
239
272
  return{current,targetSignal(){current()
240
273
  return signal?AbortSignal.any([signal,targetAbort.signal]):targetAbort.signal
@@ -243,6 +276,7 @@ return model&&adapter?snapshots()[targetKey(model)]:undefined
243
276
  },state(){const{model,adapter}=current()
244
277
  return model&&adapter?requests.state(targetKey(model)):{status:"idle"}},dispose(){targetAbort.abort()
245
278
  requests.abortAll()
279
+ pending.clear()
246
280
  for(const adapter of adapters)
247
281
  adapter.dispose?.()
248
282
  }}}
@@ -283,7 +317,9 @@ return [{id:`${id}.${name}`,label:account?`Codex account \xB7 ${name}`:`${limit.
283
317
  })
284
318
  })
285
319
  }
286
- 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)
320
+ function supportsCodexQuota(model){return(model.providerID==="openai"||model.hostConnection?.providerID==="openai")&&model.hostConnection?.method!=="api"&&Boolean(model.accountID)&&(!model.hostConnection||model.hostConnection.status==="ready")
321
+ }
322
+ function createCodexQuotaAdapter(client,validateHost){const adapter={id:"codex",name:"Codex CLI",supports:supportsCodexQuota,...validateHost&&{validateHost},async discoverAccount(signal){const account=await client.request("account/read",{refreshToken:false},signal)
287
323
  if(account.account?.type!=="chatgpt")
288
324
  throw new Error("Sign in to Codex CLI with ChatGPT")
289
325
  const usage=await client.request("account/rateLimits/read",{supportsLunaReserve:false,excludeResetCreditDetails:true},signal)
@@ -338,35 +374,53 @@ return adapter
338
374
  import {createComponent as _$createComponent} from "@opentui/solid"
339
375
  import {createSignal as createSignal3} from "solid-js"
340
376
  import {GuardedConfirmation} from "./tui.js"
377
+ var alwaysCurrent=()=>true
378
+ class CodexAccountMismatchError extends Error{constructor(){super("Sign in to Codex CLI with the account selected in OpenCode.")
379
+ this.name="CodexAccountMismatchError"
380
+ }}
381
+ function canLinkCodexAccount(model){return(model.providerID==="openai"||model.hostConnection?.providerID==="openai")&&model.hostConnection?.method!=="api"&&(!model.hostConnection||model.hostConnection.status==="ready")
382
+ }
383
+ function createCodexAccountLinkModel(input){const model=input.selected()
384
+ const key=JSON.stringify(model)
385
+ const current=()=>!input.signal.aborted&&(input.current??alwaysCurrent)()&&JSON.stringify(input.selected())===key
386
+ const validHost=async ()=>Boolean(model&&(!input.adapter.validateHost||await input.adapter.validateHost(model,input.signal)))
387
+ async function discover(){if(!model||!current()||!input.adapter.discoverAccount||!canLinkCodexAccount(model))
388
+ return
389
+ if(!await validHost()||!current())
390
+ return
391
+ const accountID=await input.adapter.discoverAccount(input.signal)
392
+ if(!current())
393
+ return
394
+ if(model.hostConnection?.accountID&&model.hostConnection.accountID!==accountID)
395
+ throw new CodexAccountMismatchError
396
+ return{accountID,model}}
397
+ return{current,discover,async confirm(preview){const latest=await discover()
398
+ if(!latest||!current()||latest.accountID!==preview.accountID)
399
+ return false
400
+ await input.bind(latest.model.providerID,latest.model.hostConnection?.id,latest.accountID)
401
+ return true
402
+ }}}
341
403
  function createCodexAccountLink(input){const [busy,setBusy]=createSignal3(false)
342
404
  async function link(){const selected=input.model()
343
405
  if(busy()||!selected||selected.providerID!=="openai"&&selected.hostConnection?.providerID!=="openai"||!input.adapter.discoverAccount)
344
406
  return
345
407
  if(selected.hostConnection&&selected.hostConnection.status!=="ready")
346
408
  return
347
- const key=JSON.stringify(selected)
348
- const current=()=>JSON.stringify(input.model())===key
349
409
  const signal=input.api.lifecycle?.signal??new AbortController().signal
350
- const validHost=async ()=>!input.adapter.validateHost||await input.adapter.validateHost(selected,signal)
351
- setBusy(true)
352
- try{if(!await validHost()||!current())
353
- return
354
- const accountID=await input.adapter.discoverAccount(signal)
355
- if(!current())
356
- return
357
- 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."
410
+ const model=createCodexAccountLinkModel({adapter:input.adapter,selected:input.model,signal,bind:input.preferences.setCodexConnectionBinding
358
411
  })
412
+ const current=model.current
413
+ setBusy(true)
414
+ try{const preview=await model.discover()
415
+ if(!preview)
359
416
  return
360
- }
417
+ const{accountID
418
+ }=preview
361
419
  async function confirm(){if(busy()||!current())
362
420
  return
363
421
  setBusy(true)
364
- try{if(!await validHost()||!current())
365
- return
366
- const latest=await input.adapter.discoverAccount(signal)
367
- if(!current()||latest!==accountID)
368
- return
369
- input.preferences.setCodexConnectionBinding(selected.providerID,selected.hostConnection?.id,accountID)
422
+ try{if(await model.confirm({accountID
423
+ }))
370
424
  input.dialogs.back()
371
425
  } catch{if(current())
372
426
  input.api.ui.toast({variant:"warning",title:"Codex account",message:"Account verification failed; try again."
@@ -377,8 +431,8 @@ input.dialogs.open(()=>_$createComponent(GuardedConfirmation,{get api(){return i
377
431
  },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.`
378
432
  },onConfirm:()=>void confirm()
379
433
  }))
380
- } catch{if(current())
381
- input.api.ui.toast({variant:"warning",title:"Codex account",message:"Install a supported Codex CLI and sign in with ChatGPT, then try again."
434
+ } catch(error){if(current())
435
+ input.api.ui.toast({variant:"warning",title:"Codex account",message:error instanceof CodexAccountMismatchError?error.message:"Install a supported Codex CLI and sign in with ChatGPT, then try again."
382
436
  })
383
437
  } finally{setBusy(false)
384
438
  }}
@@ -480,41 +534,55 @@ import {SelectionMenu,useDialogs} from "./tui.js"
480
534
  import {createComponent as _$createComponent2} from "@opentui/solid"
481
535
  import {randomUUID} from "crypto"
482
536
  import {GuardedConfirmation as GuardedConfirmation2,useDialogState} from "./tui.js"
483
- function createResetCreditActions(input){const [busy,setBusy]=useDialogState("credit-busy",false)
484
- const signal=input.controller.targetSignal
485
- const warn=(message)=>input.api.ui.toast({variant:"warning",title:"Codex reset credit",message
486
- })
487
- async function consume(creditID){if(busy()||!input.current()||!input.model.accountID||!input.adapter.consumeResetCredit)
537
+ var resetCreditOutcomeMessage={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."
538
+ };
539
+ function resetAttemptMatches(model,attempt){return attempt.accountID===model.accountID&&attempt.modelID===model.modelID&&attempt.providerID===model.providerID&&(attempt.connectionID===undefined||attempt.connectionID===model.hostConnection?.id)
540
+ }
541
+ function createResetCreditModel(input){async function consume(creditID){if(input.busy()||!input.current()||!input.model.accountID||!input.adapter.consumeResetCredit)
488
542
  return
489
- setBusy(true)
543
+ input.setBusy(true)
490
544
  const previous=input.preferences.pendingResetAttempt()
491
- const attempt=previous??{providerID:input.model.providerID,modelID:input.model.modelID,accountID:input.model.accountID,...creditID&&{creditID
492
- },idempotencyKey:randomUUID(),createdAt:Date.now(),...input.model.hostConnection?.id&&{connectionID:input.model.hostConnection.id
493
- }}
494
- 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")
495
- }
545
+ const attempt=previous??{providerID:input.model.providerID,modelID:input.model.modelID,accountID:input.model.accountID,...creditID&&{creditID},idempotencyKey:input.id(),createdAt:input.now(),...input.model.hostConnection?.id&&{connectionID:input.model.hostConnection.id}}
546
+ try{if(!resetAttemptMatches(input.model,attempt))
547
+ throw new Error("Resolve the earlier attempt on its linked account and model first")
496
548
  if(!previous)
497
549
  await input.preferences.beginResetAttempt(attempt)
498
550
  if(!input.current())
499
551
  return
500
- const outcome=await input.adapter.consumeResetCredit(input.model,{...attempt,uncertain:Boolean(previous)
501
- },signal())
552
+ const outcome=await input.adapter.consumeResetCredit(input.model,{...attempt,uncertain:Boolean(previous)},input.signal())
502
553
  await input.preferences.finishResetAttempt(attempt.idempotencyKey)
503
- await input.controller.refresh(true)
504
- 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."
505
- }[outcome]
554
+ await input.refresh()
555
+ return outcome
556
+ } finally{input.setBusy(false)
557
+ }}
558
+ async function review(creditID){if(input.busy()||!input.current()||!input.adapter.prepareResetCreditConsumption||!input.adapter.consumeResetCredit)
559
+ return
560
+ input.setBusy(true)
561
+ try{const snapshot=await input.adapter.prepareResetCreditConsumption(input.model,creditID,input.signal())
562
+ if(!input.current()||snapshot.accountId!==input.model.accountID)
563
+ return
564
+ return snapshot
565
+ } finally{input.setBusy(false)
566
+ }}
567
+ return{review,consume,busy:input.busy}}
568
+ function createResetCreditActions(input){const [busy,setBusy]=useDialogState("credit-busy",false)
569
+ const signal=input.controller.targetSignal
570
+ const warn=(message)=>input.api.ui.toast({variant:"warning",title:"Codex reset credit",message
571
+ })
572
+ const model=createResetCreditModel({preferences:input.preferences,adapter:input.adapter,model:input.model,current:input.current,signal,busy,setBusy,id:randomUUID,now:Date.now,refresh:()=>input.controller.refresh(true)
573
+ })
574
+ async function consume(creditID){try{const outcome=await model.consume(creditID)
575
+ if(!outcome)
576
+ return
577
+ input.api.ui.toast({variant:outcome==="reset"||outcome==="alreadyRedeemed"?"success":"info",title:"Codex reset credit",message:resetCreditOutcomeMessage[outcome]
506
578
  })
507
579
  if(input.current())
508
580
  input.dialogs.close()
509
581
  } catch(error){if(input.current()){warn(`${error instanceof Error ? error.message : "Reset request failed"} \xB7 retry keeps the same attempt`)
510
582
  input.dialogs.back()
511
- }} finally{setBusy(false)
512
- }}
513
- async function review(creditID){if(busy()||!input.current()||!input.adapter.prepareResetCreditConsumption||!input.adapter.consumeResetCredit)
514
- return
515
- setBusy(true)
516
- try{const snapshot=await input.adapter.prepareResetCreditConsumption(input.model,creditID,signal())
517
- if(!input.current()||snapshot.accountId!==input.model.accountID)
583
+ }}}
584
+ async function review(creditID){try{const snapshot=await model.review(creditID)
585
+ if(!snapshot)
518
586
  return
519
587
  input.dialogs.open(()=>_$createComponent2(GuardedConfirmation2,{get api(){return input.api
520
588
  },get current(){return input.current
@@ -523,7 +591,6 @@ input.dialogs.open(()=>_$createComponent2(GuardedConfirmation2,{get api(){return
523
591
  }))
524
592
  } catch(error){if(input.current())
525
593
  warn(error instanceof Error?error.message:"Credit eligibility is unavailable")
526
- } finally{setBusy(false)
527
594
  }}
528
595
  return{review,consume,busy
529
596
  }}
@@ -921,35 +988,103 @@ import {createElement as _$createElement8} from "@opentui/solid"
921
988
  import {createComponent as _$createComponent11} from "@opentui/solid"
922
989
  import {memo as _$memo5} from "@opentui/solid"
923
990
  import {Show as Show5} from "solid-js"
924
- function LimitsStatus(props){const message=()=>{const model=props.controller.current().model
925
- const connection=model?.hostConnection
926
- const snapshot=props.controller.snapshot()
927
- const state=props.controller.state()
991
+ import {createSignal as createSignal8} from "solid-js"
992
+ function createProviderAccountSource(ports){const [connections,setConnections]=createSignal8({})
993
+ let generation=0
994
+ let disposed=false
995
+ let lastModel
996
+ let pending
997
+ function save(target,connection){if(JSON.stringify(connections()[target])!==JSON.stringify(connection))
998
+ setConnections((values)=>({...values,[target]:connection}))
999
+ }
1000
+ async function read(model,signal=ports.lifecycle){if(disposed||ports.lifecycle.aborted||signal.aborted)
1001
+ return{status:"unavailable"}
1002
+ const target=ports.key(model)
1003
+ const epoch=generation
1004
+ if(pending?.key===target&&pending.generation===epoch)
1005
+ return pending.promise
1006
+ const promise=(async ()=>{try{const connection=await ports.read(model,AbortSignal.any([ports.lifecycle,signal]))
1007
+ if(disposed||ports.lifecycle.aborted||signal.aborted||epoch!==generation||ports.key(model)!==target)
1008
+ return{status:"unavailable"}
1009
+ save(target,connection)
1010
+ return connection
1011
+ } catch{if(!disposed&&!ports.lifecycle.aborted&&!signal.aborted&&epoch===generation)
1012
+ save(target,{status:"unavailable"})
1013
+ return{status:"unavailable"}}})()
1014
+ pending={key:target,generation:epoch,promise}
1015
+ try{return await promise
1016
+ } finally{if(pending?.promise===promise)
1017
+ pending=undefined
1018
+ }}
1019
+ const unsubscribe=ports.subscribe((eager=true)=>{generation++
1020
+ if(eager)
1021
+ setConnections({})
1022
+ else{const active=lastModel&&ports.key(lastModel)
1023
+ setConnections((values)=>Object.fromEntries(Object.entries(values).filter(([target])=>target===active)))
1024
+ }
1025
+ if(lastModel&&!disposed)
1026
+ read(lastModel)
1027
+ })
1028
+ return{suspend(){if(!lastModel)
1029
+ return
1030
+ lastModel=undefined
1031
+ generation++
1032
+ pending=undefined
1033
+ },current(model){lastModel=model
1034
+ return{...model,hostConnection:connections()[ports.key(model)]??{status:"loading"}}},async refresh(model){await read(model)
1035
+ },async validate(model,signal){const actual=await read(model,signal)
1036
+ const expected=model.hostConnection
1037
+ return actual.status==="ready"&&expected?.status==="ready"&&actual.id===expected.id&&actual.accountID===expected.accountID&&(!actual.accountID||actual.accountID===model.accountID)
1038
+ },dispose(){disposed=true
1039
+ generation++
1040
+ unsubscribe()
1041
+ }}}
1042
+ function bindCodexAccount(model,preferences){const connection=model.hostConnection
1043
+ if(model.providerID!=="openai"&&connection?.providerID!=="openai"||connection?.method==="api")
1044
+ return{...model,accountID:undefined,accountSource:undefined}
1045
+ if(connection&&connection.status!=="ready")
1046
+ return{...model,accountID:undefined,accountSource:undefined}
1047
+ const accountID=connection?.accountID??preferences?.codexAccountForConnection(model.providerID,connection?.id,model.modelID)
1048
+ const accountSource=connection?.accountID?"host":"binding"
1049
+ return{...model,accountID,accountSource:accountID?accountSource:undefined}}
1050
+ import {createEffect as createEffect3,createMemo,on as on2,onCleanup as onCleanup2} from "solid-js"
1051
+ function quotaStatusMessage(input){const{model,snapshot,state}=input
928
1052
  if(state.status==="error")
929
1053
  return snapshot?"Stale \xB7 refresh failed":"Quota read failed \xB7 refresh to retry"
930
- if(snapshot?.availability==="ready"&&(snapshot.windows.length>0||snapshot.balances?.length))
1054
+ if(!input.showReadyMessage&&snapshot?.availability==="ready"&&(snapshot.windows.length>0||snapshot.balances?.length))
931
1055
  return
932
- if(snapshot?.message)
933
- return snapshot.availability==="stale"?`Stale \xB7 ${snapshot.message}`:snapshot.availability==="ready"?`Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()} \xB7 ${snapshot.message}`:snapshot.message
1056
+ if(snapshot?.message){if(snapshot.availability==="stale")
1057
+ return `Stale \xB7 ${snapshot.message}`
1058
+ return snapshot.availability==="ready"?`Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()} \xB7 ${snapshot.message}`:snapshot.message
1059
+ }
1060
+ const connection=model?.hostConnection
934
1061
  if(connection?.status==="loading")
935
1062
  return "Checking the OpenCode account\u2026"
936
1063
  if(connection?.status==="unavailable")
937
1064
  return "Account metadata unavailable \xB7 refresh to retry"
938
1065
  if(connection?.status==="unsupported")
939
1066
  return "Codex quotas require an active ChatGPT OAuth connection"
940
- if(!snapshot)
941
- return state.status==="loading"?"Reading provider limits\u2026":props.controller.current().adapter?"Refresh to read provider limits":providerQuotaCapability(model?.providerID??"").reason
942
- const availability=snapshot.availability
943
- if(availability==="unauthenticated")
1067
+ if(!snapshot){if(state.status==="loading")
1068
+ return "Reading provider limits\u2026"
1069
+ return input.hasAdapter?"Refresh to read provider limits":providerQuotaCapability(model?.providerID??"").reason
1070
+ }
1071
+ if(snapshot.availability==="unauthenticated")
944
1072
  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"
945
- if(availability==="unsupported")
1073
+ if(snapshot.availability==="unsupported")
946
1074
  return "No documented quota bucket for this model"
947
- if(availability==="rate_limited")
1075
+ if(snapshot.availability==="rate_limited")
948
1076
  return "Provider rate-limited this read \xB7 try again later"
949
- if(availability==="stale")
1077
+ if(snapshot.availability==="stale")
950
1078
  return "Stale provider data \xB7 refresh to update"
1079
+ if(snapshot.availability==="ready"&&input.showReadyMessage&&(snapshot.windows.length>0||snapshot.balances?.length))
1080
+ return
951
1081
  return `Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()}`
952
1082
  }
1083
+ function LimitsStatus(props){const message=()=>{const{model,adapter
1084
+ }=props.controller.current()
1085
+ return quotaStatusMessage({model,snapshot:props.controller.snapshot(),state:props.controller.state(),hasAdapter:Boolean(adapter)
1086
+ })
1087
+ }
953
1088
  return _$createComponent11(Show5,{get when(){return _$memo5(()=>!!props.controller.current().model)()&&message()
954
1089
  },children:(value)=>(()=>{var _el$=_$createElement8("text")
955
1090
  _$setProp8(_el$,"wrapMode","word")
@@ -967,10 +1102,10 @@ import {memo as _$memo6} from "@opentui/solid"
967
1102
  import {insert as _$insert8} from "@opentui/solid"
968
1103
  import {setProp as _$setProp9} from "@opentui/solid"
969
1104
  import {createElement as _$createElement9} from "@opentui/solid"
970
- import {Show as Show6,createSignal as createSignal8} from "solid-js"
1105
+ import {Show as Show6,createSignal as createSignal9} from "solid-js"
971
1106
  import {SelectionBox as SelectionBox3,useIcons as useIcons4} from "./tui.js"
972
1107
  function Section(props){const icons=useIcons4()
973
- const [headerWidth,setHeaderWidth]=createSignal8(0)
1108
+ const [headerWidth,setHeaderWidth]=createSignal9(0)
974
1109
  const icon=()=>props.section?icons.section(props.section):props.icon
975
1110
  const theme=()=>props.api.theme.current
976
1111
  const item=useSidebarItem(props.api,props.interaction,{id:props.sectionId??`section.${props.title}`,position:()=>({section:props.navigationSection??0,row:0,column:0
@@ -1104,49 +1239,11 @@ const model=api.state.session.get(sessionID)?.model
1104
1239
  if(!model)
1105
1240
  return
1106
1241
  return{sessionID,providerID:model.providerID,modelID:model.id,...model.variant&&{variant:model.variant}}}
1107
- import {createSignal as createSignal9,untrack as untrack2} from "solid-js"
1108
- var CHATGPT_METHODS=new Set(["chatgpt-browser","chatgpt-headless"])
1109
- function providerAccountIdentity(provider,credentials){if(!provider.integrationID||provider.activation==="disabled")
1110
- return{status:"unsupported"}
1111
- const active=credentials.filter((entry)=>entry.integrationID===provider.integrationID&&entry.active)
1112
- if(active.length!==1)
1113
- return{status:"unavailable"}
1114
- const credential=active[0]
1115
- const id=`credential:${credential.id}`
1116
- if(credential.value.type==="external")
1117
- return{status:"unsupported",id}
1118
- const providerID=provider.canonical??provider.id
1119
- if(credential.value.type==="key")
1120
- return{status:"ready",id,method:"api",providerID}
1121
- if(provider.integrationID!=="openai")
1122
- return{status:"ready",id,method:"oauth",providerID}
1123
- if(!CHATGPT_METHODS.has(credential.value.methodID))
1124
- return{status:"unsupported",id}
1125
- const account=credential.value.metadata?.accountID
1126
- const accountID=typeof account==="string"&&account.trim()?account:undefined
1127
- const header=Object.entries(provider.headers??{}).find(([name])=>name.toLowerCase()==="chatgpt-account-id")?.[1]
1128
- if(accountID&&header&&header!==accountID)
1129
- return{status:"loading"}
1130
- return{status:"ready",id,method:"oauth",providerID,...accountID&&{accountID}}}
1131
- function createOpenCodeV2AccountSource(context,lifecycle){const [connections,setConnections]=createSignal9({})
1132
- let generation=0
1133
- let disposed=false
1134
- let lastModel
1135
- let pending
1136
- const location=()=>context.location??context.data.location.default()
1137
- const key=(model)=>JSON.stringify([model.providerID,location()])
1138
- function save(target,connection){if(JSON.stringify(connections()[target])!==JSON.stringify(connection))
1139
- setConnections((values)=>({...values,[target]:connection}))
1140
- }
1141
- async function read(model,signal=lifecycle){if(disposed||signal.aborted)
1142
- return{status:"unavailable"}
1143
- const target=key(model)
1144
- const epoch=generation
1145
- if(pending?.key===target&&pending.generation===epoch)
1146
- return pending.promise
1147
- const promise=(async ()=>{try{if(!context.client.provider?.get||!context.client.credential?.list)
1242
+ import {untrack as untrack2} from "solid-js"
1243
+ function createOpenCodeV2AccountSource(context,lifecycle){const location=()=>context.location??context.data.location.default()
1244
+ return createProviderAccountSource({lifecycle,key:(model)=>JSON.stringify([model.providerID,location()]),async read(model,signal){if(!context.client.provider?.get||!context.client.credential?.list)
1148
1245
  throw new Error("Account metadata unavailable")
1149
- const options={signal:AbortSignal.any([lifecycle,signal])}
1246
+ const options={signal}
1150
1247
  const [provider,credentials]=await Promise.all([context.client.provider.get({providerID:model.providerID,location:location()},options),context.client.credential.list(options)])
1151
1248
  let connection=provider.data.id===model.providerID?providerAccountIdentity(provider.data,credentials):{status:"unavailable"}
1152
1249
  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)
@@ -1154,56 +1251,17 @@ const active=integration.data.connections[0]
1154
1251
  if(active?.type==="env"&&!active.status)
1155
1252
  connection={status:"ready",id:`env:${active.name}`,method:"api",providerID:provider.data.canonical??provider.data.id
1156
1253
  }}
1157
- if(disposed||signal.aborted||epoch!==generation||key(model)!==target)
1158
- return{status:"unavailable"}
1159
- save(target,connection)
1160
1254
  return connection
1161
- } catch{if(!disposed&&!signal.aborted&&epoch===generation)
1162
- save(target,{status:"unavailable"})
1163
- return{status:"unavailable"}}})()
1164
- pending={key:target,generation:epoch,promise}
1165
- try{return await promise
1166
- } finally{if(pending?.promise===promise)
1167
- pending=undefined
1168
- }}
1169
- function invalidate(eager=true){generation++
1170
- if(eager)
1171
- setConnections({})
1172
- else{const active=lastModel&&key(lastModel)
1173
- setConnections((values)=>Object.fromEntries(Object.entries(values).filter(([target])=>target===active)))
1174
- }
1175
- if(lastModel&&!disposed)
1176
- read(lastModel)
1177
- }
1178
- const events=["credential.switched","credential.updated","integration.updated","provider.updated","server.connected"]
1179
- const unsubscribes=events.map((event)=>context.data.on(event,(observation)=>untrack2(()=>{const directory=observation?.location?.directory
1255
+ },subscribe(invalidate){const events=["credential.switched","credential.updated","integration.updated","provider.updated","server.connected"]
1256
+ const cleanups=events.map((event)=>context.data.on(event,(observation)=>untrack2(()=>{const directory=observation?.location?.directory
1180
1257
  if((event==="provider.updated"||event==="integration.updated")&&directory&&directory!==location().directory)
1181
1258
  return
1182
1259
  invalidate(event!=="provider.updated")
1183
1260
  })))
1184
- return{suspend(){if(!lastModel)
1185
- return
1186
- lastModel=undefined
1187
- generation++
1188
- pending=undefined
1189
- },current(model){lastModel=model
1190
- return{...model,hostConnection:connections()[key(model)]??{status:"loading"}}},async refresh(model){await read(model)
1191
- },async validate(model,signal){const actual=await read(model,signal)
1192
- const expected=model.hostConnection
1193
- return actual.status==="ready"&&expected?.status==="ready"&&actual.id===expected.id&&actual.accountID===expected.accountID&&(!actual.accountID||actual.accountID===model.accountID)
1194
- },dispose(){disposed=true
1195
- generation++
1196
- for(const unsubscribe of unsubscribes)
1197
- unsubscribe()
1198
- }}}
1199
- function bindCodexAccount(model,preferences){const connection=model.hostConnection
1200
- if(model.providerID!=="openai"&&connection?.providerID!=="openai"||connection?.method==="api")
1201
- return{...model,accountID:undefined,accountSource:undefined}
1202
- if(connection&&connection.status!=="ready")
1203
- return{...model,accountID:undefined,accountSource:undefined}
1204
- const accountID=connection?.accountID??preferences.codexAccountForConnection(model.providerID,connection?.id,model.modelID)
1205
- const accountSource=connection?.accountID?"host":"binding"
1206
- return{...model,accountID,accountSource:accountID?accountSource:undefined}}
1261
+ return()=>{for(const cleanup of cleanups)
1262
+ cleanup()
1263
+ }}})
1264
+ }
1207
1265
  var commandName=(kind)=>kind==="model"?"model.list":"variant.list"
1208
1266
  function createLimitsModelSelection(api,current,context){const sameTarget=(model)=>{const active=current()
1209
1267
  return active?.sessionID===model.sessionID&&active.providerID===model.providerID&&active.modelID===model.modelID
@@ -1,5 +1,5 @@
1
1
  import {PLUGIN_ID
2
- } from "./chunk-bv0nm1tj.js";
2
+ } from "./chunk-dm1y8x49.js";
3
3
  import {memo as _$memo} from "@opentui/solid"
4
4
  import {createComponent as _$createComponent} from "@opentui/solid"
5
5
  import {use as _$use} from "@opentui/solid"
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "version": 1,
3
3
  "dependencies": {
4
- "@opencode/plugin": "2.2.5",
5
- "@opentui/core": "0.8.2",
6
- "@opentui/keymap": "0.8.2",
7
- "@opentui/solid": "0.8.2",
4
+ "@opencode/plugin": "2.2.6",
5
+ "@opentui/core": "0.8.3",
6
+ "@opentui/keymap": "0.8.3",
7
+ "@opentui/solid": "0.8.3",
8
8
  "solid-js": "1.9.18"
9
9
  }
10
10
  }