opencode-navigator 0.25.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.
- package/CHANGELOG.md +14 -0
- package/HOST_API.md +125 -16
- package/LINT_COVERAGE.md +32 -29
- package/PROVIDERS.md +9 -0
- package/README.md +97 -12
- package/dist/{chunk-bv0nm1tj.js → chunk-dm1y8x49.js} +2 -1
- package/dist/gui-experimental.cjs +15526 -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 +213 -154
- 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 +42 -37
- package/dist/sidebar-shortcuts.js +1 -1
- package/dist/toolbar-settings.js +68 -20
- package/dist/tui.js +792 -661
- package/dist/upgrade-notes.js +1 -1
- package/package.json +30 -17
- /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
|
|
@@ -14,8 +14,9 @@ pending.delete(id)
|
|
|
14
14
|
}}
|
|
15
15
|
function write(message){if(!child?.stdin||typeof child.stdin==="number")
|
|
16
16
|
throw new Error("Codex app-server is unavailable")
|
|
17
|
-
child.stdin.write(`${JSON.stringify(message)}
|
|
18
|
-
`)
|
|
17
|
+
Promise.resolve(child.stdin.write(`${JSON.stringify(message)}
|
|
18
|
+
`)).catch((error)=>{failAll(error instanceof Error?error:new Error("Codex app-server write failed"))
|
|
19
|
+
})
|
|
19
20
|
}
|
|
20
21
|
function accept(line){let message
|
|
21
22
|
try{message=JSON.parse(line)
|
|
@@ -133,6 +134,29 @@ return{...balance}})
|
|
|
133
134
|
if(bankedResets&&(!Number.isSafeInteger(bankedResets.availableCount)||bankedResets.availableCount<0)){throw new Error("Invalid banked reset count")
|
|
134
135
|
}
|
|
135
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}}}
|
|
136
160
|
import {createSignal as createSignal2,untrack} from "solid-js"
|
|
137
161
|
import {createSignal} from "solid-js"
|
|
138
162
|
var idle={status:"idle"}
|
|
@@ -200,6 +224,7 @@ setStates((values)=>({...values,[target]:{status:"error",error:requestError(caus
|
|
|
200
224
|
}}
|
|
201
225
|
return{state,start,abort,abortAll}}
|
|
202
226
|
function createProviderLimitsController(adapters,selectedModel,signal,refreshContext){const requests=createRequestState(signal)
|
|
227
|
+
const pending=new Map
|
|
203
228
|
const [snapshots,setSnapshots]=createSignal2({})
|
|
204
229
|
const targetKey=(model)=>JSON.stringify(model)
|
|
205
230
|
let activeKey
|
|
@@ -208,6 +233,7 @@ function current(){const model=selectedModel()
|
|
|
208
233
|
const adapter=model&&adapters.find((candidate)=>candidate.supports(model))
|
|
209
234
|
const key=model?targetKey(model):undefined
|
|
210
235
|
if(activeKey!==key){untrack(requests.abortAll)
|
|
236
|
+
pending.clear()
|
|
211
237
|
targetAbort.abort()
|
|
212
238
|
targetAbort=new AbortController
|
|
213
239
|
activeKey=key
|
|
@@ -219,10 +245,13 @@ const{model,adapter}=current()
|
|
|
219
245
|
if(!model||!adapter)
|
|
220
246
|
return
|
|
221
247
|
const key=targetKey(model)
|
|
248
|
+
const existing=pending.get(key)
|
|
249
|
+
if(existing&&!force)
|
|
250
|
+
return existing
|
|
222
251
|
const request=requests.start(key,"read provider limits",Boolean(snapshots()[key]),force)
|
|
223
252
|
if(!request)
|
|
224
253
|
return
|
|
225
|
-
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))
|
|
226
255
|
if(!request.isCurrent()||targetKey(current().model??model)!==key)
|
|
227
256
|
return
|
|
228
257
|
setSnapshots((value)=>({...value,[key]:snapshot}))
|
|
@@ -234,6 +263,11 @@ request.succeed()
|
|
|
234
263
|
} else
|
|
235
264
|
request.fail(error)
|
|
236
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)
|
|
237
271
|
}}
|
|
238
272
|
return{current,targetSignal(){current()
|
|
239
273
|
return signal?AbortSignal.any([signal,targetAbort.signal]):targetAbort.signal
|
|
@@ -242,6 +276,7 @@ return model&&adapter?snapshots()[targetKey(model)]:undefined
|
|
|
242
276
|
},state(){const{model,adapter}=current()
|
|
243
277
|
return model&&adapter?requests.state(targetKey(model)):{status:"idle"}},dispose(){targetAbort.abort()
|
|
244
278
|
requests.abortAll()
|
|
279
|
+
pending.clear()
|
|
245
280
|
for(const adapter of adapters)
|
|
246
281
|
adapter.dispose?.()
|
|
247
282
|
}}}
|
|
@@ -282,7 +317,9 @@ return [{id:`${id}.${name}`,label:account?`Codex account \xB7 ${name}`:`${limit.
|
|
|
282
317
|
})
|
|
283
318
|
})
|
|
284
319
|
}
|
|
285
|
-
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)
|
|
286
323
|
if(account.account?.type!=="chatgpt")
|
|
287
324
|
throw new Error("Sign in to Codex CLI with ChatGPT")
|
|
288
325
|
const usage=await client.request("account/rateLimits/read",{supportsLunaReserve:false,excludeResetCreditDetails:true},signal)
|
|
@@ -337,35 +374,53 @@ return adapter
|
|
|
337
374
|
import {createComponent as _$createComponent} from "@opentui/solid"
|
|
338
375
|
import {createSignal as createSignal3} from "solid-js"
|
|
339
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
|
+
}}}
|
|
340
403
|
function createCodexAccountLink(input){const [busy,setBusy]=createSignal3(false)
|
|
341
404
|
async function link(){const selected=input.model()
|
|
342
405
|
if(busy()||!selected||selected.providerID!=="openai"&&selected.hostConnection?.providerID!=="openai"||!input.adapter.discoverAccount)
|
|
343
406
|
return
|
|
344
407
|
if(selected.hostConnection&&selected.hostConnection.status!=="ready")
|
|
345
408
|
return
|
|
346
|
-
const key=JSON.stringify(selected)
|
|
347
|
-
const current=()=>JSON.stringify(input.model())===key
|
|
348
409
|
const signal=input.api.lifecycle?.signal??new AbortController().signal
|
|
349
|
-
const
|
|
350
|
-
setBusy(true)
|
|
351
|
-
try{if(!await validHost()||!current())
|
|
352
|
-
return
|
|
353
|
-
const accountID=await input.adapter.discoverAccount(signal)
|
|
354
|
-
if(!current())
|
|
355
|
-
return
|
|
356
|
-
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
|
|
357
411
|
})
|
|
412
|
+
const current=model.current
|
|
413
|
+
setBusy(true)
|
|
414
|
+
try{const preview=await model.discover()
|
|
415
|
+
if(!preview)
|
|
358
416
|
return
|
|
359
|
-
|
|
417
|
+
const{accountID
|
|
418
|
+
}=preview
|
|
360
419
|
async function confirm(){if(busy()||!current())
|
|
361
420
|
return
|
|
362
421
|
setBusy(true)
|
|
363
|
-
try{if(
|
|
364
|
-
|
|
365
|
-
const latest=await input.adapter.discoverAccount(signal)
|
|
366
|
-
if(!current()||latest!==accountID)
|
|
367
|
-
return
|
|
368
|
-
input.preferences.setCodexConnectionBinding(selected.providerID,selected.hostConnection?.id,accountID)
|
|
422
|
+
try{if(await model.confirm({accountID
|
|
423
|
+
}))
|
|
369
424
|
input.dialogs.back()
|
|
370
425
|
} catch{if(current())
|
|
371
426
|
input.api.ui.toast({variant:"warning",title:"Codex account",message:"Account verification failed; try again."
|
|
@@ -376,8 +431,8 @@ input.dialogs.open(()=>_$createComponent(GuardedConfirmation,{get api(){return i
|
|
|
376
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.`
|
|
377
432
|
},onConfirm:()=>void confirm()
|
|
378
433
|
}))
|
|
379
|
-
} catch{if(current())
|
|
380
|
-
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."
|
|
381
436
|
})
|
|
382
437
|
} finally{setBusy(false)
|
|
383
438
|
}}
|
|
@@ -479,41 +534,55 @@ import {SelectionMenu,useDialogs} from "./tui.js"
|
|
|
479
534
|
import {createComponent as _$createComponent2} from "@opentui/solid"
|
|
480
535
|
import {randomUUID} from "crypto"
|
|
481
536
|
import {GuardedConfirmation as GuardedConfirmation2,useDialogState} from "./tui.js"
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
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)
|
|
487
542
|
return
|
|
488
|
-
setBusy(true)
|
|
543
|
+
input.setBusy(true)
|
|
489
544
|
const previous=input.preferences.pendingResetAttempt()
|
|
490
|
-
const attempt=previous??{providerID:input.model.providerID,modelID:input.model.modelID,accountID:input.model.accountID,...creditID&&{creditID
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
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")
|
|
494
|
-
}
|
|
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")
|
|
495
548
|
if(!previous)
|
|
496
549
|
await input.preferences.beginResetAttempt(attempt)
|
|
497
550
|
if(!input.current())
|
|
498
551
|
return
|
|
499
|
-
const outcome=await input.adapter.consumeResetCredit(input.model,{...attempt,uncertain:Boolean(previous)
|
|
500
|
-
},signal())
|
|
552
|
+
const outcome=await input.adapter.consumeResetCredit(input.model,{...attempt,uncertain:Boolean(previous)},input.signal())
|
|
501
553
|
await input.preferences.finishResetAttempt(attempt.idempotencyKey)
|
|
502
|
-
await input.
|
|
503
|
-
|
|
504
|
-
}
|
|
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]
|
|
505
578
|
})
|
|
506
579
|
if(input.current())
|
|
507
580
|
input.dialogs.close()
|
|
508
581
|
} catch(error){if(input.current()){warn(`${error instanceof Error ? error.message : "Reset request failed"} \xB7 retry keeps the same attempt`)
|
|
509
582
|
input.dialogs.back()
|
|
510
|
-
}}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return
|
|
514
|
-
setBusy(true)
|
|
515
|
-
try{const snapshot=await input.adapter.prepareResetCreditConsumption(input.model,creditID,signal())
|
|
516
|
-
if(!input.current()||snapshot.accountId!==input.model.accountID)
|
|
583
|
+
}}}
|
|
584
|
+
async function review(creditID){try{const snapshot=await model.review(creditID)
|
|
585
|
+
if(!snapshot)
|
|
517
586
|
return
|
|
518
587
|
input.dialogs.open(()=>_$createComponent2(GuardedConfirmation2,{get api(){return input.api
|
|
519
588
|
},get current(){return input.current
|
|
@@ -522,7 +591,6 @@ input.dialogs.open(()=>_$createComponent2(GuardedConfirmation2,{get api(){return
|
|
|
522
591
|
}))
|
|
523
592
|
} catch(error){if(input.current())
|
|
524
593
|
warn(error instanceof Error?error.message:"Credit eligibility is unavailable")
|
|
525
|
-
} finally{setBusy(false)
|
|
526
594
|
}}
|
|
527
595
|
return{review,consume,busy
|
|
528
596
|
}}
|
|
@@ -677,13 +745,13 @@ setRevision((value)=>value + 1)
|
|
|
677
745
|
}),context.data.on("server.connected",()=>{generation++
|
|
678
746
|
setRevision((value)=>value + 1)
|
|
679
747
|
})]
|
|
680
|
-
const adapter={id:"opencode-usage",name:"OpenCode workspace limits",supports:(model)=>typeof context.client.usage?.limits==="function"&&provider(model)?.integrationID==="opencode",async read(model,signal){signal.throwIfAborted()
|
|
748
|
+
const adapter={id:"opencode-usage",name:"OpenCode workspace limits",supports:(model)=>typeof context.client.experimental?.usage?.limits==="function"&&provider(model)?.integrationID==="opencode",async read(model,signal){signal.throwIfAborted()
|
|
681
749
|
const epoch=generation
|
|
682
750
|
const target=key()
|
|
683
751
|
const activeProvider=provider(model)
|
|
684
752
|
if(!activeProvider||activeProvider.integrationID!=="opencode")
|
|
685
753
|
throw new Error("Native usage provider changed")
|
|
686
|
-
try{const result=await context.client.usage.limits({location:{directory:location().directory}},{signal})
|
|
754
|
+
try{const result=await context.client.experimental.usage.limits({location:{directory:location().directory}},{signal})
|
|
687
755
|
if(epoch!==generation||target!==key()||signal.aborted||result.location.directory!==location().directory)
|
|
688
756
|
throw new DOMException("Workspace usage target changed","AbortError")
|
|
689
757
|
if(!result.data)
|
|
@@ -920,35 +988,103 @@ import {createElement as _$createElement8} from "@opentui/solid"
|
|
|
920
988
|
import {createComponent as _$createComponent11} from "@opentui/solid"
|
|
921
989
|
import {memo as _$memo5} from "@opentui/solid"
|
|
922
990
|
import {Show as Show5} from "solid-js"
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
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
|
|
927
1052
|
if(state.status==="error")
|
|
928
1053
|
return snapshot?"Stale \xB7 refresh failed":"Quota read failed \xB7 refresh to retry"
|
|
929
|
-
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))
|
|
930
1055
|
return
|
|
931
|
-
if(snapshot?.message)
|
|
932
|
-
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
|
|
933
1061
|
if(connection?.status==="loading")
|
|
934
1062
|
return "Checking the OpenCode account\u2026"
|
|
935
1063
|
if(connection?.status==="unavailable")
|
|
936
1064
|
return "Account metadata unavailable \xB7 refresh to retry"
|
|
937
1065
|
if(connection?.status==="unsupported")
|
|
938
1066
|
return "Codex quotas require an active ChatGPT OAuth connection"
|
|
939
|
-
if(!snapshot)
|
|
940
|
-
return
|
|
941
|
-
|
|
942
|
-
|
|
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")
|
|
943
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"
|
|
944
|
-
if(availability==="unsupported")
|
|
1073
|
+
if(snapshot.availability==="unsupported")
|
|
945
1074
|
return "No documented quota bucket for this model"
|
|
946
|
-
if(availability==="rate_limited")
|
|
1075
|
+
if(snapshot.availability==="rate_limited")
|
|
947
1076
|
return "Provider rate-limited this read \xB7 try again later"
|
|
948
|
-
if(availability==="stale")
|
|
1077
|
+
if(snapshot.availability==="stale")
|
|
949
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
|
|
950
1081
|
return `Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()}`
|
|
951
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
|
+
}
|
|
952
1088
|
return _$createComponent11(Show5,{get when(){return _$memo5(()=>!!props.controller.current().model)()&&message()
|
|
953
1089
|
},children:(value)=>(()=>{var _el$=_$createElement8("text")
|
|
954
1090
|
_$setProp8(_el$,"wrapMode","word")
|
|
@@ -966,10 +1102,10 @@ import {memo as _$memo6} from "@opentui/solid"
|
|
|
966
1102
|
import {insert as _$insert8} from "@opentui/solid"
|
|
967
1103
|
import {setProp as _$setProp9} from "@opentui/solid"
|
|
968
1104
|
import {createElement as _$createElement9} from "@opentui/solid"
|
|
969
|
-
import {Show as Show6,createSignal as
|
|
1105
|
+
import {Show as Show6,createSignal as createSignal9} from "solid-js"
|
|
970
1106
|
import {SelectionBox as SelectionBox3,useIcons as useIcons4} from "./tui.js"
|
|
971
1107
|
function Section(props){const icons=useIcons4()
|
|
972
|
-
const [headerWidth,setHeaderWidth]=
|
|
1108
|
+
const [headerWidth,setHeaderWidth]=createSignal9(0)
|
|
973
1109
|
const icon=()=>props.section?icons.section(props.section):props.icon
|
|
974
1110
|
const theme=()=>props.api.theme.current
|
|
975
1111
|
const item=useSidebarItem(props.api,props.interaction,{id:props.sectionId??`section.${props.title}`,position:()=>({section:props.navigationSection??0,row:0,column:0
|
|
@@ -1062,7 +1198,7 @@ return _$createComponent13(Section,{get api(){return props.api
|
|
|
1062
1198
|
},get navigationSection(){return props.navigationSection
|
|
1063
1199
|
},get credits(){return(snapshot()?.bankedResets?.availableCount??0)>0||Boolean(props.preferences.pendingResetAttempt())
|
|
1064
1200
|
},get refreshing(){return ["loading","refreshing"].includes(props.controller.state().status)
|
|
1065
|
-
},get canRefresh(){return Boolean(model()&&(props.controller.current().adapter
|
|
1201
|
+
},get canRefresh(){return Boolean(model()&&(props.controller.current().adapter??model()?.hostConnection?.status==="unavailable"))
|
|
1066
1202
|
},onCredits:openCredits,onRefresh:()=>void props.controller.refresh(true)
|
|
1067
1203
|
})
|
|
1068
1204
|
},get open(){return props.preferences.expanded().limits
|
|
@@ -1103,49 +1239,11 @@ const model=api.state.session.get(sessionID)?.model
|
|
|
1103
1239
|
if(!model)
|
|
1104
1240
|
return
|
|
1105
1241
|
return{sessionID,providerID:model.providerID,modelID:model.id,...model.variant&&{variant:model.variant}}}
|
|
1106
|
-
import {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
return{status:"unsupported"}
|
|
1110
|
-
const active=credentials.filter((entry)=>entry.integrationID===provider.integrationID&&entry.active)
|
|
1111
|
-
if(active.length!==1)
|
|
1112
|
-
return{status:"unavailable"}
|
|
1113
|
-
const credential=active[0]
|
|
1114
|
-
const id=`credential:${credential.id}`
|
|
1115
|
-
if(credential.value.type==="external")
|
|
1116
|
-
return{status:"unsupported",id}
|
|
1117
|
-
const providerID=provider.canonical??provider.id
|
|
1118
|
-
if(credential.value.type==="key")
|
|
1119
|
-
return{status:"ready",id,method:"api",providerID}
|
|
1120
|
-
if(provider.integrationID!=="openai")
|
|
1121
|
-
return{status:"ready",id,method:"oauth",providerID}
|
|
1122
|
-
if(!CHATGPT_METHODS.has(credential.value.methodID))
|
|
1123
|
-
return{status:"unsupported",id}
|
|
1124
|
-
const account=credential.value.metadata?.accountID
|
|
1125
|
-
const accountID=typeof account==="string"&&account.trim()?account:undefined
|
|
1126
|
-
const header=Object.entries(provider.headers??{}).find(([name])=>name.toLowerCase()==="chatgpt-account-id")?.[1]
|
|
1127
|
-
if(accountID&&header&&header!==accountID)
|
|
1128
|
-
return{status:"loading"}
|
|
1129
|
-
return{status:"ready",id,method:"oauth",providerID,...accountID&&{accountID}}}
|
|
1130
|
-
function createOpenCodeV2AccountSource(context,lifecycle){const [connections,setConnections]=createSignal9({})
|
|
1131
|
-
let generation=0
|
|
1132
|
-
let disposed=false
|
|
1133
|
-
let lastModel
|
|
1134
|
-
let pending
|
|
1135
|
-
const location=()=>context.location??context.data.location.default()
|
|
1136
|
-
const key=(model)=>JSON.stringify([model.providerID,location()])
|
|
1137
|
-
function save(target,connection){if(JSON.stringify(connections()[target])!==JSON.stringify(connection))
|
|
1138
|
-
setConnections((values)=>({...values,[target]:connection}))
|
|
1139
|
-
}
|
|
1140
|
-
async function read(model,signal=lifecycle){if(disposed||signal.aborted)
|
|
1141
|
-
return{status:"unavailable"}
|
|
1142
|
-
const target=key(model)
|
|
1143
|
-
const epoch=generation
|
|
1144
|
-
if(pending?.key===target&&pending.generation===epoch)
|
|
1145
|
-
return pending.promise
|
|
1146
|
-
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)
|
|
1147
1245
|
throw new Error("Account metadata unavailable")
|
|
1148
|
-
const options={signal
|
|
1246
|
+
const options={signal}
|
|
1149
1247
|
const [provider,credentials]=await Promise.all([context.client.provider.get({providerID:model.providerID,location:location()},options),context.client.credential.list(options)])
|
|
1150
1248
|
let connection=provider.data.id===model.providerID?providerAccountIdentity(provider.data,credentials):{status:"unavailable"}
|
|
1151
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)
|
|
@@ -1153,56 +1251,17 @@ const active=integration.data.connections[0]
|
|
|
1153
1251
|
if(active?.type==="env"&&!active.status)
|
|
1154
1252
|
connection={status:"ready",id:`env:${active.name}`,method:"api",providerID:provider.data.canonical??provider.data.id
|
|
1155
1253
|
}}
|
|
1156
|
-
if(disposed||signal.aborted||epoch!==generation||key(model)!==target)
|
|
1157
|
-
return{status:"unavailable"}
|
|
1158
|
-
save(target,connection)
|
|
1159
1254
|
return connection
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
return{status:"unavailable"}}})()
|
|
1163
|
-
pending={key:target,generation:epoch,promise}
|
|
1164
|
-
try{return await promise
|
|
1165
|
-
} finally{if(pending?.promise===promise)
|
|
1166
|
-
pending=undefined
|
|
1167
|
-
}}
|
|
1168
|
-
function invalidate(eager=true){generation++
|
|
1169
|
-
if(eager)
|
|
1170
|
-
setConnections({})
|
|
1171
|
-
else{const active=lastModel&&key(lastModel)
|
|
1172
|
-
setConnections((values)=>Object.fromEntries(Object.entries(values).filter(([target])=>target===active)))
|
|
1173
|
-
}
|
|
1174
|
-
if(lastModel&&!disposed)
|
|
1175
|
-
read(lastModel)
|
|
1176
|
-
}
|
|
1177
|
-
const events=["credential.switched","credential.updated","integration.updated","provider.updated","server.connected"]
|
|
1178
|
-
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
|
|
1179
1257
|
if((event==="provider.updated"||event==="integration.updated")&&directory&&directory!==location().directory)
|
|
1180
1258
|
return
|
|
1181
1259
|
invalidate(event!=="provider.updated")
|
|
1182
1260
|
})))
|
|
1183
|
-
return
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
pending=undefined
|
|
1188
|
-
},current(model){lastModel=model
|
|
1189
|
-
return{...model,hostConnection:connections()[key(model)]??{status:"loading"}}},async refresh(model){await read(model)
|
|
1190
|
-
},async validate(model,signal){const actual=await read(model,signal)
|
|
1191
|
-
const expected=model.hostConnection
|
|
1192
|
-
return actual.status==="ready"&&expected?.status==="ready"&&actual.id===expected.id&&actual.accountID===expected.accountID&&(!actual.accountID||actual.accountID===model.accountID)
|
|
1193
|
-
},dispose(){disposed=true
|
|
1194
|
-
generation++
|
|
1195
|
-
for(const unsubscribe of unsubscribes)
|
|
1196
|
-
unsubscribe()
|
|
1197
|
-
}}}
|
|
1198
|
-
function bindCodexAccount(model,preferences){const connection=model.hostConnection
|
|
1199
|
-
if(model.providerID!=="openai"&&connection?.providerID!=="openai"||connection?.method==="api")
|
|
1200
|
-
return{...model,accountID:undefined,accountSource:undefined}
|
|
1201
|
-
if(connection&&connection.status!=="ready")
|
|
1202
|
-
return{...model,accountID:undefined,accountSource:undefined}
|
|
1203
|
-
const accountID=connection?.accountID??preferences.codexAccountForConnection(model.providerID,connection?.id,model.modelID)
|
|
1204
|
-
const accountSource=connection?.accountID?"host":"binding"
|
|
1205
|
-
return{...model,accountID,accountSource:accountID?accountSource:undefined}}
|
|
1261
|
+
return()=>{for(const cleanup of cleanups)
|
|
1262
|
+
cleanup()
|
|
1263
|
+
}}})
|
|
1264
|
+
}
|
|
1206
1265
|
var commandName=(kind)=>kind==="model"?"model.list":"variant.list"
|
|
1207
1266
|
function createLimitsModelSelection(api,current,context){const sameTarget=(model)=>{const active=current()
|
|
1208
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
|
}
|