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/dist/server.js CHANGED
@@ -43,6 +43,592 @@ var NavigatorTodoRpc={id:"opencode-navigator.todo",methods:{list:{input:{type:"o
43
43
  },output
44
44
  }},events:{updated:{schema:{type:"object",properties:{sessionID,todos,guidance:{type:"boolean"}},required:["sessionID","todos","guidance"],additionalProperties:false
45
45
  }}}};
46
+ var text={type:"string",minLength:1,maxLength:512}
47
+ var model={type:"object",properties:{providerID:text,modelID:text,sessionID:text,connectionID:text,variant:text,force:{type:"boolean"}},required:["providerID","modelID","sessionID"],additionalProperties:false
48
+ }
49
+ var NavigatorQuotaRpc={id:"opencode-navigator.quotas",methods:{read:{input:model,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
50
+ }}}};
51
+ 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")
52
+ }
53
+ 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")
54
+ }
55
+ return{...window}})
56
+ const bankedResets=snapshot.bankedResets
57
+ 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))
58
+ throw new Error("Invalid provider balance")
59
+ return{...balance}})
60
+ if(bankedResets&&(!Number.isSafeInteger(bankedResets.availableCount)||bankedResets.availableCount<0)){throw new Error("Invalid banked reset count")
61
+ }
62
+ return{...snapshot,windows,...balances&&{balances},...bankedResets&&{bankedResets:{availableCount:bankedResets.availableCount,...bankedResets.credits&&{credits:[...bankedResets.credits]}}}}}
63
+ 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"]]
64
+ var PROVIDER_QUOTA_CAPABILITIES= rows.map(([id,name,sources,reason,url])=>({id,name,sources,reason,documentation:[url]}))
65
+ 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"
66
+ }
67
+ function providerQuotaCapability(id,canonical){const key=aliases[canonical??id]??canonical??id
68
+ const found=PROVIDER_QUOTA_CAPABILITIES.find((entry)=>entry.id===key)
69
+ 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/"]
70
+ }}
71
+ function providerQuotaCatalog(providers=[]){const entries=new Map(PROVIDER_QUOTA_CAPABILITIES.map((entry)=>[entry.id,entry]))
72
+ for(const provider of providers)
73
+ entries.set(provider.id,{...providerQuotaCapability(provider.id,provider.canonical),id:provider.id,name:provider.name
74
+ })
75
+ return [...entries.values()].sort((a,b)=>a.name.localeCompare(b.name))
76
+ }
77
+ import {createHash} from "crypto"
78
+ import {mkdtemp,rm} from "fs/promises"
79
+ import {tmpdir} from "os"
80
+ import {join} from "path"
81
+ function object(value){return value&&typeof value==="object"&&!Array.isArray(value)?value:{}}
82
+ function number(value){if(typeof value==="number"&&Number.isFinite(value))
83
+ return value
84
+ return typeof value==="string"&&/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value)&&Number.isFinite(Number(value))?Number(value):undefined
85
+ }
86
+ function balance(id,label,value,unit,scope="account"){if(number(value)===undefined)
87
+ return []
88
+ return [{id,label,amount:String(value),...unit&&{unit},scope}]
89
+ }
90
+ function scaledAmount(value,digits,multiplier=1n){const numeric=typeof value==="number"&&Number.isSafeInteger(value)?String(value):""
91
+ const text=typeof value==="string"?value:numeric
92
+ if(!/^-?\d+$/.test(text))
93
+ return
94
+ const raw=BigInt(text)*multiplier
95
+ const negative=raw<0n
96
+ const absolute=negative?-raw:raw
97
+ const divisor=10n ** BigInt(digits)
98
+ return `${negative ? "-" : ""}${absolute / divisor}.${String(absolute % divisor).padStart(digits, "0")}`
99
+ }
100
+ function milliseconds(value){const time=number(value)
101
+ return time!==undefined&&time>=100000000000?time / 1000:undefined
102
+ }
103
+ async function withinQuotaSignal(signal,work){signal.throwIfAborted()
104
+ let abort
105
+ try{return await Promise.race([new Promise((_,reject)=>{abort=()=>reject(signal.reason)
106
+ signal.addEventListener("abort",abort,{once:true})
107
+ }),Promise.try(()=>{signal.throwIfAborted()
108
+ return work()
109
+ })])
110
+ } finally{if(abort)
111
+ signal.removeEventListener("abort",abort)
112
+ }}
113
+ class QuotaReadError extends Error{status
114
+ constructor(status,message){super(message)
115
+ this.status=status
116
+ this.name="QuotaReadError"
117
+ }}
118
+ async function readProviderJson(fetcher,url,token,signal,input={}){const combined=AbortSignal.any([signal,AbortSignal.timeout(1e4)])
119
+ const response=await withinQuotaSignal(combined,()=>fetcher(url,{method:input.method??"GET",headers:{...input.apiKeyHeader?{[input.apiKeyHeader]:token}:{Authorization:`Bearer ${token}`},Accept:"application/json",...input.headers
120
+ },signal:combined,redirect:"error"
121
+ }))
122
+ if(!response.ok)
123
+ throw new QuotaReadError(response.status,"Provider quota read was rejected")
124
+ return readQuotaResponseBody(response,combined)
125
+ }
126
+ async function readQuotaResponseBody(response,signal=AbortSignal.timeout(1e4)){const reader=response.body?.getReader()
127
+ const decoder=new TextDecoder
128
+ let body=""
129
+ let bytes=0
130
+ if(reader){try{while(true){const next=await withinQuotaSignal(signal,()=>reader.read())
131
+ if(next.done)
132
+ break
133
+ bytes += next.value.byteLength
134
+ if(bytes>1e6)
135
+ throw new QuotaReadError(502,"Provider quota response is too large")
136
+ body += decoder.decode(next.value,{stream:true})
137
+ }
138
+ body += decoder.decode()
139
+ } finally{reader.cancel().catch(()=>{})
140
+ reader.releaseLock()
141
+ }}
142
+ try{return object(JSON.parse(body))
143
+ } catch{throw new QuotaReadError(502,"Provider quota response is invalid")
144
+ }}
145
+ function createCopilotRuntime(load,timeoutMs){let active
146
+ let disposed=false
147
+ async function cleanup(runtime){const client=runtime.client
148
+ const directory=runtime.directory
149
+ runtime.client=undefined
150
+ runtime.directory=undefined
151
+ await withinQuotaSignal(AbortSignal.timeout(timeoutMs),async ()=>client?.forceStop()).catch(()=>{})
152
+ if(directory)
153
+ await rm(directory,{recursive:true,force:true}).catch(()=>{})
154
+ }
155
+ function discard(runtime){if(active===runtime)
156
+ active=undefined
157
+ runtime.controller.abort(new QuotaReadError(503,"Copilot quota runtime stopped"))
158
+ return cleanup(runtime)
159
+ }
160
+ async function start(runtime){const signal=AbortSignal.any([runtime.controller.signal,AbortSignal.timeout(timeoutMs)])
161
+ try{return await withinQuotaSignal(signal,async ()=>{const sdk=await load()
162
+ signal.throwIfAborted()
163
+ const directory=await mkdtemp(join(tmpdir(),"navigator-copilot-quotas-"))
164
+ runtime.directory=directory
165
+ if(signal.aborted){await cleanup(runtime)
166
+ signal.throwIfAborted()
167
+ }
168
+ const client=new sdk.CopilotClient({mode:"empty",baseDirectory:directory,useLoggedInUser:false,logLevel:"none",enableRemoteSessions:false
169
+ })
170
+ runtime.client=client
171
+ try{await client.start()
172
+ } finally{if(signal.aborted){runtime.client=client
173
+ runtime.directory=directory
174
+ await cleanup(runtime)
175
+ }}
176
+ signal.throwIfAborted()
177
+ return client
178
+ })
179
+ } catch{await discard(runtime)
180
+ throw new QuotaReadError(503,"The official Copilot SDK quota runtime is unavailable")
181
+ }}
182
+ return{async use(signal,work){signal.throwIfAborted()
183
+ if(disposed)
184
+ throw new QuotaReadError(503,"Copilot quota reader disposed")
185
+ const runtime=active ??={controller:new AbortController}
186
+ const combined=AbortSignal.any([signal,runtime.controller.signal,AbortSignal.timeout(timeoutMs)])
187
+ runtime.startup ??= start(runtime)
188
+ try{return await withinQuotaSignal(combined,async ()=>{const client=await runtime.startup
189
+ combined.throwIfAborted()
190
+ return work(client)
191
+ })
192
+ } catch(error){discard(runtime)
193
+ throw error
194
+ }},async dispose(){disposed=true
195
+ if(active)
196
+ await discard(active)
197
+ }}}
198
+ function createCopilotQuotaReader(load=()=>import("@github/copilot-sdk"),timeoutMs=1e4){const runtime=createCopilotRuntime(load,timeoutMs)
199
+ return{async read(input){const [catalog,quota]=await runtime.use(input.signal,(sdk)=>Promise.all([sdk.rpc.models.list({gitHubToken:input.token}),sdk.rpc.account.getQuota({gitHubToken:input.token})]))
200
+ const selected=catalog.models.find((model)=>model.id===input.apiModelID)
201
+ const premium=quota.quotaSnapshots.premium_interactions
202
+ const base={model:input.model,providerId:input.model.providerID,providerName:input.provider.name,fetchedAt:Date.now(),windows:[]
203
+ }
204
+ input.signal.throwIfAborted()
205
+ if(!selected||!selected.billing||!selected.billing.tokenPrices&&(selected.billing.multiplier??0)<=0)
206
+ return{...base,availability:"unsupported",message:"Copilot did not associate the selected model with a premium billing allowance."
207
+ }
208
+ if(!premium)
209
+ return{...base,availability:"unavailable",message:"Copilot did not report a premium allowance; missing details are not zero."
210
+ }
211
+ if(premium.isUnlimitedEntitlement)
212
+ return{...base,availability:"ready",message:"Copilot reports an unlimited premium entitlement for this account."
213
+ }
214
+ const reset=premium.resetDate?Date.parse(premium.resetDate) / 1000:undefined
215
+ if(!Number.isFinite(premium.remainingPercentage))
216
+ return{...base,availability:"unavailable",message:"Copilot quota percentage is unavailable."}
217
+ return{...base,availability:"ready",windows:[{id:"copilot-premium",label:"Premium allowance",modelID:input.model.modelID,kind:"subscription",unit:"%",used:100-premium.remainingPercentage,remaining:premium.remainingPercentage,...reset!==undefined&&Number.isFinite(reset)&&{resetsAt:reset}}]
218
+ }},dispose:runtime.dispose
219
+ }}
220
+ function connectionKey(connection){return connection.type==="credential"?`credential:${connection.id}`:`env:${connection.name}`
221
+ }
222
+ function nativeBaseURL(provider){return typeof provider.settings?.baseURL==="string"?provider.settings.baseURL:undefined
223
+ }
224
+ function matchingOrigin(provider,origins){const configured=nativeBaseURL(provider)
225
+ if(!configured)
226
+ return true
227
+ try{return origins.includes(new URL(configured).origin)
228
+ } catch{return false
229
+ }}
230
+ async function readFireworksQuota(input){if(!matchingOrigin(input.provider,["https://api.fireworks.ai"]))
231
+ return
232
+ const read=(url)=>readProviderJson(input.fetcher,url,input.token,input.signal)
233
+ const accounts=await read("https://api.fireworks.ai/v1/accounts?pageSize=200&readMask=name,displayName")
234
+ const candidates=Array.isArray(accounts.accounts)?accounts.accounts.map((entry)=>object(entry)):[]
235
+ const requested=input.credential.metadata?.accountID
236
+ const selected=typeof requested==="string"?candidates.filter((entry)=>entry.name===requested||entry.name===`accounts/${requested}`):candidates
237
+ const base={model:input.model,providerId:input.model.providerID,providerName:input.provider.name,fetchedAt:Date.now(),windows:[]
238
+ }
239
+ if(selected.length!==1||requested===undefined&&(accounts.nextPageToken||number(accounts.totalSize)>1))
240
+ return{...base,availability:"unavailable",message:"Fireworks billing account is ambiguous; it is never inferred from a model owner."
241
+ }
242
+ const name=typeof selected[0].name==="string"?selected[0].name:""
243
+ if(!/^accounts\/[\w-]+$/.test(name))
244
+ return{...base,availability:"unavailable",message:"Fireworks did not report an account resource name."}
245
+ let page
246
+ for(let index=0;index<10; index++){const url=new URL(`https://api.fireworks.ai/v1/${name}/quotas`)
247
+ url.searchParams.set("pageSize","200")
248
+ if(page)
249
+ url.searchParams.set("pageToken",page)
250
+ const body=await read(url.href)
251
+ const quotas=Array.isArray(body.quotas)?body.quotas:[]
252
+ for(const raw of quotas){const quota=object(raw)
253
+ const total=number(quota.value)
254
+ const used=number(quota.usage)
255
+ if(quota.name===`${name}/quotas/monthly-spend-usd`&&total!==undefined)
256
+ base.windows.push({id:"fireworks-monthly-spend",label:"Account monthly spend cap",modelID:input.model.modelID,limitID:quota.name,kind:"configured_limit",unit:"USD",total,...used!==undefined&&{used,remaining:total-used}})
257
+ }
258
+ page=typeof body.nextPageToken==="string"&&body.nextPageToken?body.nextPageToken:undefined
259
+ if(!page)
260
+ break
261
+ }
262
+ return{...base,accountId:name,availability:base.windows.length>0?"ready":"unavailable",message:"Account-wide spend quota. GPU and unrelated deployment quotas are excluded."
263
+ }}
264
+ function googleQuotaFailureWindows(body,modelID,apiModelID){const details=object(body.error).details
265
+ return(Array.isArray(details)?details:[]).flatMap((raw)=>{const detail=object(raw)
266
+ if(detail["@type"]!=="type.googleapis.com/google.rpc.QuotaFailure")
267
+ return []
268
+ return(Array.isArray(detail.violations)?detail.violations:[]).flatMap((rawViolation)=>{const violation=object(rawViolation)
269
+ const dimensions=object(violation.quotaDimensions)
270
+ const value=number(violation.quotaValue)
271
+ const id=typeof violation.quotaId==="string"?violation.quotaId:""
272
+ const metric=typeof violation.quotaMetric==="string"?violation.quotaMetric:""
273
+ const selected=dimensions.model??dimensions.base_model
274
+ if(selected!==apiModelID||value===undefined)
275
+ return []
276
+ const requestUnit=metric.includes("requests")?"requests":undefined
277
+ const unit=metric.includes("tokens")?"tokens":requestUnit
278
+ if(!unit)
279
+ return []
280
+ return [{id:`google-${id || metric}`,label:id||metric,modelID,limitID:id,kind:"configured_limit",unit,total:value
281
+ }]
282
+ })
283
+ })
284
+ }
285
+ function reset(value,now){if(!value||value.length>128)
286
+ return
287
+ if(value.includes("T")){const time=Date.parse(value)
288
+ return Number.isFinite(time)?time / 1000:undefined
289
+ }
290
+ const parts=[...value.matchAll(/(\d+(?:\.\d+)?)(ms|[dhms])/g)]
291
+ if(parts.length===0||parts.map((part)=>part[0]).join("")!==value)
292
+ return
293
+ const units={d:86400,h:3600,m:60,s:1,ms:0.001}
294
+ return now / 1000 + parts.reduce((seconds,part)=>seconds + Number(part[1])*units[part[2]],0)
295
+ }
296
+ function counterWindow(modelID,headers,now,prefix,suffix,label,unit,durationMinutes){const total=number(headers.get(`${prefix}limit-${suffix}`))
297
+ const remaining=number(headers.get(`${prefix}remaining-${suffix}`))
298
+ if(total===undefined&&remaining===undefined||total!==undefined&&total<0||remaining!==undefined&&remaining<0)
299
+ return []
300
+ const resetsAt=reset(headers.get(`${prefix}reset-${suffix}`),now)
301
+ return [{id:`${prefix}${suffix}`,label,modelID,unit,kind:"rate_limit",...total!==undefined&&{total},...remaining!==undefined&&{remaining},...total!==undefined&&remaining!==undefined&&{used:total-remaining},...durationMinutes!==undefined&&{durationMinutes},...resetsAt!==undefined&&{resetsAt}}]
302
+ }
303
+ function nativeHeaderWindows(providerID,modelID,headers,now=Date.now()){const windows=[]
304
+ const add=(prefix,suffix,label,unit,durationMinutes)=>{windows.push(...counterWindow(modelID,headers,now,prefix,suffix,label,unit,durationMinutes))
305
+ }
306
+ const genericFields=["openai","groq","azure"].includes(providerID)?["requests","tokens","project-tokens"]:[]
307
+ const groqPeriods={requests:1440,tokens:1,"project-tokens":1}
308
+ for(const field of genericFields){add("x-ratelimit-",field,field.replaceAll("-"," "),field==="requests"?"requests":"tokens",providerID==="groq"?groqPeriods[field]:undefined)
309
+ }
310
+ const periodDurations={minute:1,hour:60,day:1440}
311
+ const periods=providerID==="cerebras"?["minute","hour","day"]:[]
312
+ for(const period of periods){for(const field of ["requests","req","tokens"])
313
+ add("x-ratelimit-",`${field}-${period}`,`${field === "req" ? "requests" : field} / ${period}`,field==="tokens"?"tokens":"requests",periodDurations[period])
314
+ }
315
+ const anthropicFields=providerID==="anthropic"?["requests","tokens","input-tokens","output-tokens"]:[]
316
+ const priorityFields=providerID==="anthropic"?["input-tokens","output-tokens"]:[]
317
+ for(const field of anthropicFields)
318
+ add("anthropic-ratelimit-",field,field.replaceAll("-"," "),field==="requests"?"requests":"tokens",1)
319
+ for(const field of priorityFields)
320
+ add("anthropic-priority-",field,`Priority ${field.replaceAll("-", " ")}`,"tokens",1)
321
+ if(["fireworks-ai","fireworks"].includes(providerID)){for(const [field,label] of [["prompt","Total prompt throughput"],["cache-adjusted-prompt","Uncached prompt throughput"],["generated","Output throughput"]]){const total=number(headers.get(`x-ratelimit-limit-tokens-${field}`))
322
+ if(total!==undefined&&total>=0)
323
+ windows.push({id:`fireworks-${field}`,label,modelID,total,unit:"tokens/s",kind:"configured_limit"})
324
+ }}
325
+ return windows
326
+ }
327
+ function balanceSnapshot(model,providerName,values,fetchedAt=Date.now(),accountId){return{model,providerId:model.providerID,providerName,fetchedAt,windows:[],balances:values,...accountId&&{accountId},availability:values.length>0?"ready":"unavailable",...values.length===0&&{message:"The provider did not report an available balance; it is not assumed to be zero."
328
+ }}}
329
+ function deepSeekBalances(body){return(Array.isArray(body.balance_infos)?body.balance_infos:[]).flatMap((raw,index)=>{const row=object(raw)
330
+ const unit=typeof row.currency==="string"?row.currency:undefined
331
+ return [...balance(`available-${index}`,"Available balance",row.total_balance,unit),...balance(`granted-${index}`,"Granted balance",row.granted_balance,unit),...balance(`topup-${index}`,"Top-up balance",row.topped_up_balance,unit)]
332
+ })
333
+ }
334
+ function moonshotBalances(body,currency){const data=object(body.data)
335
+ return body.code===0&&body.status!==false?[...balance("available","Available balance",data.available_balance,currency),...balance("voucher","Voucher balance",data.voucher_balance,currency),...balance("cash","Cash balance",data.cash_balance,currency)]:[]
336
+ }
337
+ function siliconFlowBalances(body){const data=object(body.data)
338
+ return body.status===false?[]:[...balance("available","Available balance",data.totalBalance),...balance("charged","Charged balance",data.chargeBalance),...balance("granted","Granted balance",data.balance)]
339
+ }
340
+ function novitaBalances(body){return [["availableBalance","Available balance"],["cashBalance","Cash balance"],["creditLimit","Credit limit"],["pendingCharges","Pending charges"],["outstandingInvoices","Outstanding invoices"]].flatMap(([field,label])=>balance(field,label,scaledAmount(body[field],4),"USD"))
341
+ }
342
+ function miniMaxBalances(body){const status=number(object(body.base_resp).status_code)
343
+ if(status!==undefined&&status!==0)
344
+ return []
345
+ return [["available_amount","Available balance"],["cash_balance","Cash balance"],["voucher_balance","Voucher balance"],["credit_balance","Credit balance"],["owed_amount","Owed amount"]].flatMap(([field,label])=>balance(field,label,body[field]))
346
+ }
347
+ function poeBalances(body){return balance("points","Available plan and add-on points",body.current_point_balance,"points")
348
+ }
349
+ function nanoBalances(body){return [...balance("usd","Account balance",body.usd_balance,"USD"),...balance("nano","Balance in Nano",body.nano_balance,"NANO")]
350
+ }
351
+ function aiHubMixBalances(body){return body.success===true?balance("available","Available balance",scaledAmount(object(body.data).quota,6,2n),"USD"):[]
352
+ }
353
+ function openRouterBudget(body){const data=object(body.data)
354
+ const remaining=number(data.limit_remaining)
355
+ return data.limit===null?[{id:"key-budget",label:"Key spending budget",scope:"key",unlimited:true,unit:"USD"}]:balance("key-budget","Remaining key spending budget",remaining,"USD","key")
356
+ }
357
+ function miniMaxWindows(body,modelID,apiModelID){const responseStatus=number(object(body.base_resp).status_code)
358
+ if(responseStatus!==undefined&&responseStatus!==0)
359
+ return []
360
+ return(Array.isArray(body.model_remains)?body.model_remains:[]).flatMap((raw)=>{const row=object(raw)
361
+ if(row.model_name!==apiModelID)
362
+ return []
363
+ return ["interval","weekly"].flatMap((period)=>{const total=number(row[`current_${period}_total_count`])
364
+ const remainingPercent=number(row[`current_${period}_remaining_percent`])
365
+ const status=number(row[`current_${period}_status`])
366
+ if(total===undefined||total<=0||status===3)
367
+ return []
368
+ const reset=milliseconds(row[period==="weekly"?"weekly_end_time":"end_time"])
369
+ return [{id:`minimax-${period}`,label:period==="weekly"?"Weekly Token Plan":"Token Plan window",modelID,kind:remainingPercent===undefined?"configured_limit":"subscription",unit:remainingPercent===undefined?"usage units":"%",...remainingPercent===undefined?{total}:{used:100-remainingPercent,remaining:remainingPercent},...reset!==undefined&&{resetsAt:reset}}]
370
+ })
371
+ })
372
+ }
373
+ function nanoWindows(body,modelID){if(body.active!==true)
374
+ return []
375
+ const limits=object(body.limits)
376
+ return ["dailyInputTokens","weeklyInputTokens"].flatMap((period)=>{const row=object(body[period])
377
+ const total=number(limits[period])
378
+ const used=number(row.used)
379
+ const remaining=number(row.remaining)
380
+ if(row.degraded===true||total===undefined||used===undefined&&remaining===undefined)
381
+ return []
382
+ const resetsAt=milliseconds(row.resetAt)
383
+ return [{id:`nanogpt-${period}`,label:period==="dailyInputTokens"?"Daily subscription input tokens":"Weekly subscription input tokens",modelID,kind:"subscription",unit:"tokens",total,...used!==undefined&&{used},...remaining!==undefined&&{remaining},...resetsAt!==undefined&&{resetsAt}}]
384
+ })
385
+ }
386
+ function anthropicConfiguredWindows(body,modelID,apiModelID){return(Array.isArray(body.data)?body.data:[]).flatMap((raw)=>{const row=object(raw)
387
+ if(object(row.group).type!=="model_group"||!Array.isArray(row.models)||!row.models.includes(apiModelID))
388
+ return []
389
+ return(Array.isArray(row.limits)?row.limits:[]).flatMap((rawLimit)=>{const limit=object(rawLimit)
390
+ const value=number(limit.value)
391
+ const type=typeof limit.type==="string"?limit.type:""
392
+ const match=/^(requests|input_tokens|output_tokens|tokens)_per_(minute|day)$/.exec(type)
393
+ if(value===undefined||!match)
394
+ return []
395
+ return [{id:`anthropic-${row.id}-${type}`,label:type.replaceAll("_"," "),modelID,limitID:typeof row.id==="string"?row.id:undefined,kind:"configured_limit",unit:match[1].includes("tokens")?"tokens":"requests",durationMinutes:match[2]==="day"?1440:1,total:value
396
+ }]
397
+ })
398
+ })
399
+ }
400
+ async function readAnthropicQuota(input){const options={headers:{"anthropic-version":"2023-06-01"},...input.credential.type==="key"&&{apiKeyHeader:"x-api-key"}}
401
+ const windows=[]
402
+ let page
403
+ for(let index=0;index<10; index++){const url=new URL("https://api.anthropic.com/v1/organizations/rate_limits")
404
+ url.searchParams.set("model",input.apiModelID)
405
+ if(page)
406
+ url.searchParams.set("page",page)
407
+ const body=await readProviderJson(input.fetcher,url.href,input.token,input.signal,options)
408
+ windows.push(...anthropicConfiguredWindows(body,input.model.modelID,input.apiModelID))
409
+ page=typeof body.next_page==="string"&&body.next_page?body.next_page:undefined
410
+ if(!page)
411
+ break
412
+ }
413
+ return{...balanceSnapshot(input.model,input.provider.name,[]),windows,availability:windows.length>0?"ready":"unsupported",message:windows.length>0?"Configured rate limits; remaining capacity is not reported by this API.":"No explicit rate-limit group was reported for the selected model."
414
+ }}
415
+ var origins={deepseek:["https://api.deepseek.com"],moonshotai:["https://api.moonshot.ai"],"moonshotai-cn":["https://api.moonshot.cn"],siliconflow:["https://api.siliconflow.com"],"siliconflow-cn":["https://api.siliconflow.cn"],"novita-ai":["https://api.novita.ai"],poe:["https://api.poe.com"],aihubmix:["https://aihubmix.com","https://api.aihubmix.com"],minimax:["https://api.minimax.io","https://www.minimax.io"],"minimax-cn":["https://api.minimaxi.com","https://www.minimaxi.com"],"nano-gpt":["https://nano-gpt.com","https://api.nano-gpt.com"],anthropic:["https://api.anthropic.com"]
416
+ }
417
+ async function readNativeRest(input){const{canonical,token,signal,fetcher,model,provider,apiModelID}=input
418
+ const allowed=origins[canonical]
419
+ if(!allowed||!matchingOrigin(provider,allowed))
420
+ return
421
+ const read=(url,options)=>readProviderJson(fetcher,url,token,signal,options)
422
+ const make=(values,accountId)=>balanceSnapshot(model,provider.name,values,Date.now(),accountId)
423
+ switch(canonical){case "deepseek":{return make(deepSeekBalances(await read("https://api.deepseek.com/user/balance")))
424
+ }
425
+ case "moonshotai":{return make(moonshotBalances(await read("https://api.moonshot.ai/v1/users/me/balance"),"USD"))
426
+ }
427
+ case "moonshotai-cn":{return make(moonshotBalances(await read("https://api.moonshot.cn/v1/users/me/balance"),"CNY"))
428
+ }
429
+ case "siliconflow":case "siliconflow-cn":{const body=await read(`${allowed[0]}/v1/user/info`)
430
+ const id=object(body.data).id
431
+ return make(siliconFlowBalances(body),typeof id==="string"?id:undefined)
432
+ }
433
+ case "novita-ai":{return make(novitaBalances(await read("https://api.novita.ai/openapi/v1/billing/balance/detail")))
434
+ }
435
+ case "poe":{return make(poeBalances(await read("https://api.poe.com/usage/current_balance")))
436
+ }
437
+ case "aihubmix":{return make(aiHubMixBalances(await read("https://aihubmix.com/api/user/self")))
438
+ }
439
+ case "minimax":case "minimax-cn":{const base=allowed[0]
440
+ if(input.credential.type==="key"&&token.startsWith("sk-api-"))
441
+ return make(miniMaxBalances(await read(`${base}/account/query_balance`)))
442
+ const body=await read(`${base}/v1/token_plan/remains`)
443
+ const windows=miniMaxWindows(body,model.modelID,apiModelID)
444
+ return{...make([]),windows,availability:windows.length>0?"ready":"unavailable",message:windows.length>0?undefined:"No finite, model-associated Token Plan allowance was reported. Missing or ambiguous counts are not treated as remaining quota."
445
+ }}
446
+ case "nano-gpt":{const configured=nativeBaseURL(provider)
447
+ const base=configured?new URL(configured).origin:allowed[0]
448
+ const balances=nanoBalances(await read(`${base}/api/check-balance`,{method:"POST",apiKeyHeader:"x-api-key"}))
449
+ let subscription
450
+ try{subscription=await Promise.all([read(`${base}/api/subscription/v1/usage`),read(`${base}/api/subscription/v1/models`)])
451
+ } catch(error){if(signal.aborted||balances.length===0)
452
+ throw error
453
+ return{...make(balances),message:"Account balance is available; subscription counters could not be read."}}
454
+ const [usage,models]=subscription
455
+ const covered=Array.isArray(models.data)&&models.data.some((entry)=>object(entry).id===apiModelID)
456
+ const windows=covered?nanoWindows(usage,model.modelID):[]
457
+ return{...make(balances),windows,...windows.length>0&&{availability:"ready"},...windows.length===0&&{message:"Subscription windows are shown only for provider-listed subscription models with non-degraded counters."
458
+ }}}
459
+ case "anthropic":{return readAnthropicQuota(input)
460
+ }}
461
+ return
462
+ }
463
+ function openRouterFreeWindow(body,modelID,apiModelID){if(!apiModelID.endsWith(":free"))
464
+ return []
465
+ const data=object(object(body.data).free_model_daily_requests)
466
+ const total=number(data.limit)
467
+ const remaining=number(data.remaining)
468
+ const used=number(data.used)
469
+ return total!==undefined&&(used!==undefined||remaining!==undefined)?[{id:"openrouter-free-daily",label:"Free-model daily requests",modelID,unit:"requests",durationMinutes:1440,kind:"rate_limit",total,...used!==undefined&&{used},...remaining!==undefined&&{remaining}}]:[]
470
+ }
471
+ function managementCredits(body){const data=object(body.data)
472
+ const total=number(data.total_credits)
473
+ const used=number(data.total_usage)
474
+ return total!==undefined&&used!==undefined?balance("account-credits","Remaining account credits",total-used,"USD"):[]
475
+ }
476
+ async function readOpenRouterQuota(input){if(!matchingOrigin(input.provider,["https://openrouter.ai"]))
477
+ return
478
+ const body=await readProviderJson(input.fetcher,"https://openrouter.ai/api/v1/key",input.token,input.signal)
479
+ const data=object(body.data)
480
+ const balances=openRouterBudget(body)
481
+ if(data.is_management_key===true)
482
+ balances.push(...managementCredits(await readProviderJson(input.fetcher,"https://openrouter.ai/api/v1/credits",input.token,input.signal)))
483
+ const windows=openRouterFreeWindow(body,input.model.modelID,input.apiModelID)
484
+ const id=typeof data.organization_id==="string"?data.organization_id:undefined
485
+ return{...balanceSnapshot(input.model,input.provider.name,balances,Date.now(),id),windows,...windows.length>0&&{availability:"ready"},message:data.is_management_key===true?undefined:"Key budget, not account balance. Reading account credits requires a management key."
486
+ }}
487
+ async function nativeQuotaContext(host,model,signal,fetcher){const [provider,catalog]=await Promise.all([host.provider.get({providerID:model.providerID}),host.model.list()])
488
+ const selected=catalog.data.find((entry)=>entry.providerID===model.providerID&&entry.id===model.modelID&&entry.enabled)
489
+ if(!selected||!provider.data.integrationID||provider.data.activation==="disabled")
490
+ return
491
+ const connection=await host.integration.connection.active(provider.data.integrationID)
492
+ if(!connection)
493
+ return
494
+ const credential=await host.integration.connection.resolve(connection)
495
+ const token=credential?.type==="oauth"?credential.access:credential?.key
496
+ if(!credential||!token)
497
+ return
498
+ signal.throwIfAborted()
499
+ return{model,apiModelID:selected.modelID,provider:provider.data,canonical:providerQuotaCapability(provider.data.id,provider.data.canonical).id,connection,connectionID:connectionKey(connection),credential,token,signal,fetcher
500
+ }}
501
+ async function sameNativeConnection(host,input){const current=input.provider.integrationID&&await host.integration.connection.active(input.provider.integrationID)
502
+ if(!current||connectionKey(current)!==input.connectionID)
503
+ return false
504
+ const credential=await host.integration.connection.resolve(current)
505
+ const token=credential?.type==="oauth"?credential.access:credential?.key
506
+ return token===input.token
507
+ }
508
+ function requestUsesCredential(request,input){const url=new URL(request.url)
509
+ const origins={openai:"https://api.openai.com",anthropic:"https://api.anthropic.com",groq:"https://api.groq.com",cerebras:"https://api.cerebras.ai","fireworks-ai":"https://api.fireworks.ai",google:"https://generativelanguage.googleapis.com"
510
+ }
511
+ const cloudOrigin=url.protocol==="https:"&&(input.canonical==="azure"&&url.hostname.endsWith(".openai.azure.com")||input.canonical==="google-vertex"&&url.hostname.endsWith(".aiplatform.googleapis.com"))
512
+ if(!cloudOrigin&&url.origin!==origins[input.canonical])
513
+ return false
514
+ const authorization=request.headers.get("authorization")
515
+ if(authorization===`Bearer ${input.token}`||authorization===`bearer ${input.token}`)
516
+ return true
517
+ if(["x-api-key","api-key","x-goog-api-key"].some((header)=>request.headers.get(header)===input.token))
518
+ return true
519
+ return url.hostname==="generativelanguage.googleapis.com"&&url.searchParams.get("key")===input.token
520
+ }
521
+ function key2(input){return JSON.stringify([input.model.providerID,input.model.modelID,input.model.variant,input.connectionID,createHash("sha256").update(input.token).digest("hex")])
522
+ }
523
+ function modelForEvent(event){return{providerID:event.model.providerID,modelID:event.model.id,sessionID:event.sessionID}}
524
+ function rejectedRead(model,error,input){const status=error instanceof QuotaReadError?error.status:undefined
525
+ const states={429:"rate_limited",401:"unauthenticated",403:"permission_required",404:"unsupported"
526
+ }
527
+ return{model,providerId:model.providerID,providerName:input?.provider.name??providerQuotaCapability(model.providerID).name,fetchedAt:Date.now(),windows:[],...input&&{connectionID:input.connectionID},availability:status===undefined?"unavailable":states[status]??"unavailable",message:status===403?"The active credential does not have the provider quota/billing permission required by this API.":"The documented provider quota source is unavailable. No credentials or raw provider error details are exposed."
528
+ }}
529
+ function createNativeQuotaService(host,lifecycle,fetcher=fetch,copilot=createCopilotQuotaReader()){const observed=new Map
530
+ const cached=new Map
531
+ const captured=new WeakMap
532
+ let generation=0
533
+ const base=(model)=>({model,providerId:model.providerID,providerName:providerQuotaCapability(model.providerID).name,fetchedAt:Date.now(),windows:[],availability:"unavailable"
534
+ })
535
+ async function rest(input){if(input.canonical==="openrouter")
536
+ return readOpenRouterQuota(input)
537
+ if(input.canonical==="fireworks-ai")
538
+ return readFireworksQuota(input)
539
+ if(input.canonical==="github-copilot")
540
+ return copilot.read(input)
541
+ return readNativeRest(input)
542
+ }
543
+ async function currentResult(input,force){const identity=key2(input)
544
+ const headers=observed.get(identity)
545
+ const existing=cached.get(identity)
546
+ let result=!force&&existing&&Date.now()-existing.fetchedAt<30000?existing:undefined
547
+ if(headers&&input.canonical==="anthropic")
548
+ result=headers
549
+ else if(!result){try{result=await rest(input)
550
+ } catch(error){if(!headers)
551
+ throw error
552
+ result=headers
553
+ }}
554
+ return{identity,headers,result:result??headers}}
555
+ return{invalidate(){generation++
556
+ cached.clear()
557
+ observed.clear()
558
+ },async request(event){if(lifecycle.aborted||event.kind!=="primary"||!/(?:\/chat\/completions|\/messages|\/responses|:generateContent|:streamGenerateContent)$/.test(new URL(event.request.url).pathname))
559
+ return
560
+ const epoch=generation
561
+ const observation=AbortSignal.any([lifecycle,AbortSignal.timeout(1000)])
562
+ try{const input=await withinQuotaSignal(observation,()=>nativeQuotaContext(host,modelForEvent(event),observation,fetcher))
563
+ if(input&&epoch===generation&&requestUsesCredential(event.request,input)&&await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
564
+ captured.set(event.request,{key:key2(input),generation:epoch})
565
+ } catch{}},async response(event){const request=captured.get(event.request)
566
+ if(!request||request.generation!==generation||lifecycle.aborted)
567
+ return false
568
+ const observation=AbortSignal.any([lifecycle,AbortSignal.timeout(1000)])
569
+ try{const input=await withinQuotaSignal(observation,()=>nativeQuotaContext(host,modelForEvent(event),observation,fetcher))
570
+ if(!input||key2(input)!==request.key||!await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
571
+ return false
572
+ let windows=nativeHeaderWindows(input.canonical,input.model.modelID,event.response.headers)
573
+ if(windows.length===0&&event.response.status===429&&["google","google-vertex"].includes(input.canonical)&&event.response.headers.get("content-type")?.includes("json"))
574
+ windows=googleQuotaFailureWindows(await readQuotaResponseBody(event.response.clone(),observation),input.model.modelID,input.apiModelID)
575
+ if(windows.length===0||request.generation!==generation)
576
+ return false
577
+ observed.set(request.key,{...base(input.model),providerName:input.provider.name,connectionID:input.connectionID,availability:"ready",windows,message:"Provider-native API limits observed from this model response; no extra inference request was made."
578
+ })
579
+ return true
580
+ } catch{return false
581
+ }},async read(target,signal){const model={providerID:target.providerID,modelID:target.modelID,sessionID:target.sessionID,...target.variant&&{variant:target.variant}}
582
+ const epoch=generation
583
+ const combined=AbortSignal.any([lifecycle,signal,AbortSignal.timeout(15000)])
584
+ let input
585
+ try{input=await withinQuotaSignal(combined,()=>nativeQuotaContext(host,model,combined,fetcher))
586
+ if(!input)
587
+ return{...base(model),availability:"unauthenticated",message:"No eligible active provider connection or selected model was reported by OpenCode."
588
+ }
589
+ if(target.connectionID&&target.connectionID!==input.connectionID)
590
+ return{...base(model),availability:"unauthenticated",message:"The OpenCode connection changed; the old account is not queried."
591
+ }
592
+ const current=input
593
+ const{identity,headers,result}=await withinQuotaSignal(combined,()=>currentResult(current,target.force))
594
+ combined.throwIfAborted()
595
+ if(epoch!==generation||!await withinQuotaSignal(combined,()=>sameNativeConnection(host,current)))
596
+ return{...base(model),availability:"unauthenticated",message:"The provider account changed during the quota read."
597
+ }
598
+ if(!result){const capability=providerQuotaCapability(input.canonical)
599
+ return{...base(model),providerName:input.provider.name,connectionID:input.connectionID,availability:capability.sources.length>0?"waiting":"unsupported",message:capability.reason
600
+ }}
601
+ const freshHeaders=headers&&Date.now()-headers.fetchedAt<=120000
602
+ const merged={...result,model,connectionID:input.connectionID,windows:[...result.windows,...freshHeaders&&headers!==result?headers.windows:[]],...headers===result&&Date.now()-result.fetchedAt>120000&&{availability:"stale"}}
603
+ if(headers!==result)
604
+ cached.set(identity,{...result,model,connectionID:input.connectionID})
605
+ return merged
606
+ } catch(error){if(signal.aborted||lifecycle.aborted)
607
+ throw error
608
+ return rejectedRead(model,error,input)
609
+ }},async dispose(){cached.clear()
610
+ observed.clear()
611
+ await copilot.dispose()
612
+ }}}
613
+ async function setupProviderQuotaServer(host){const lifecycle=new AbortController
614
+ const service=createNativeQuotaService(host,lifecycle.signal)
615
+ const rpc=await host.rpc.register(NavigatorQuotaRpc,{read:async (input,context)=>normalizeProviderQuota(await service.read(input,context.signal)),capabilities:async ()=>{const providers=await host.provider.list()
616
+ return providerQuotaCatalog(providers.data)
617
+ }})
618
+ const request=await host.session.hook("http.request",(event)=>service.request(event))
619
+ const response=await host.session.hook("http.response",async (event)=>{if(await service.response(event))
620
+ await rpc.events.emit("updated",{providerID:event.model.providerID,modelID:event.model.id}).catch(()=>{})
621
+ });
622
+ (async ()=>{const events=host.event.subscribe({signal:lifecycle.signal})
623
+ for await (const event of events){if(["credential.updated","credential.switched","integration.updated","provider.updated"].includes(event.type))
624
+ service.invalidate()
625
+ }})().catch(()=>{})
626
+ return async ()=>{lifecycle.abort()
627
+ await request.dispose()
628
+ await response.dispose()
629
+ await rpc.dispose()
630
+ await service.dispose()
631
+ }}
46
632
  var input={type:"object",properties:{todos:NavigatorTodoRpc.methods.replace.input.properties.todos
47
633
  },required:["todos"],additionalProperties:false
48
634
  }
@@ -64,7 +650,9 @@ const context=await host.session.hook("context",async (event)=>{const state=awai
64
650
  if(state.guidance)
65
651
  event.system.push({type:"text",text:todoGuidance(state)})
66
652
  })
67
- return async ()=>{await context.dispose()
653
+ const quotaCleanup=typeof host.integration?.connection?.active==="function"&&typeof host.provider?.get==="function"?await setupProviderQuotaServer(host):undefined
654
+ return async ()=>{await quotaCleanup?.()
655
+ await context.dispose()
68
656
  await tools.dispose()
69
657
  await rpc.dispose()
70
658
  }}})