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