opencode-navigator 0.26.0 → 0.27.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.
- package/CHANGELOG.md +15 -0
- package/HOST_API.md +113 -6
- package/LINT_COVERAGE.md +5 -0
- package/PROVIDERS.md +9 -0
- package/README.md +92 -6
- package/dist/{chunk-bv0nm1tj.js → chunk-dm1y8x49.js} +2 -1
- package/dist/gui-experimental.cjs +15659 -0
- package/dist/layout-preset-actions.js +37 -14
- package/dist/native-todo-setup.js +2 -1
- package/dist/navigator-updates.js +2 -2
- package/dist/onboarding.js +11 -4
- package/dist/project-profile.js +34 -38
- package/dist/provider-limits.js +207 -149
- package/dist/quick-actions-settings.js +1 -1
- package/dist/runtime-dependencies.json +4 -4
- package/dist/server.js +379 -0
- package/dist/sidebar-settings.js +37 -34
- package/dist/sidebar-shortcuts.js +1 -1
- package/dist/toolbar-settings.js +68 -20
- package/dist/tui.js +711 -613
- package/dist/upgrade-notes.js +1 -1
- package/package.json +27 -12
- /package/dist/{chunk-pwa1wzer.js → chunk-rrvwmnyh.js} +0 -0
package/dist/provider-limits.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {PLUGIN_ID
|
|
2
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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(
|
|
365
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
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.
|
|
504
|
-
|
|
505
|
-
}
|
|
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
|
-
}}
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
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
|
|
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
|
|
942
|
-
|
|
943
|
-
|
|
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
|
|
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]=
|
|
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 {
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
|
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
|
-
}
|
|
1162
|
-
|
|
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
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"dependencies": {
|
|
4
|
-
"@opencode/plugin": "2.2.
|
|
5
|
-
"@opentui/core": "0.8.
|
|
6
|
-
"@opentui/keymap": "0.8.
|
|
7
|
-
"@opentui/solid": "0.8.
|
|
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
|
}
|