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