opencode-navigator 0.21.1 → 0.21.3

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.
@@ -362,36 +362,66 @@ return{link,busy
362
362
  import {effect as _$effect} from "@opentui/solid"
363
363
  import {insertNode as _$insertNode} from "@opentui/solid"
364
364
  import {insert as _$insert} from "@opentui/solid"
365
- import {memo as _$memo} from "@opentui/solid"
366
365
  import {setProp as _$setProp} from "@opentui/solid"
367
366
  import {createElement as _$createElement} from "@opentui/solid"
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`
367
+ import {createSignal as createSignal4} from "solid-js"
368
+ import {useIcons} from "./tui.js"
369
+ function measurement(window,remaining){if(remaining!==undefined)
370
+ return `${remaining}${window.unit === "%" ? "" : " "}${window.unit} left`
371
+ if(window.used!==undefined)
372
+ return `${window.used} ${window.unit} used`
373
+ return `${window.total} ${window.unit} capacity`
374
+ }
375
+ function resetTime(seconds,now){if(seconds===undefined)
376
+ return "Reset time unknown"
377
+ const date=new Date(seconds*1000)
378
+ const clock=date.toLocaleTimeString(undefined,{hour:"2-digit",minute:"2-digit"})
379
+ if(date.toDateString()===new Date(now).toDateString())
380
+ return `Resets ${clock}`
381
+ return `Resets ${date.toLocaleDateString(undefined, { month: "short", day: "numeric" })} ${clock}`
382
+ }
383
+ function quotaPresentation(window,now=Date.now()){const total=window.unit==="%"?100:window.total
384
+ const remaining=window.remaining??(total!==undefined&&window.used!==undefined?total-window.used:undefined)
385
+ const ratio=remaining!==undefined&&total!==undefined&&total>0?Math.max(0,Math.min(1,remaining / total)):undefined
386
+ return{value:measurement(window,remaining),ratio,reset:resetTime(window.resetsAt,now)}}
387
+ function QuotaWindowRow(props){const icons=useIcons()
388
+ const [columns,setColumns]=createSignal4(0)
389
+ const view=()=>quotaPresentation(props.window)
390
+ const scale=()=>{const ratio=view().ratio
391
+ if(ratio===undefined)
392
+ return " \xB7 scale unknown"
393
+ const width=Math.max(0,columns()-Bun.stringWidth(view().value)-1)
394
+ const filled=Math.round(ratio*width)
395
+ const symbols=icons.style()==="text"?["=","-"]:["\u2501","\u2500"]
396
+ return width?` ${symbols[0].repeat(filled)}${symbols[1].repeat(width - filled)}`:""
374
397
  }
375
- return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createElement("text"),_el$4=_$createElement("text")
398
+ return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createElement("span"),_el$4=_$createElement("text")
376
399
  _$insertNode(_el$,_el$2)
377
- _$insertNode(_el$,_el$3)
378
400
  _$insertNode(_el$,_el$4)
401
+ _$setProp(_el$,"width","100%")
402
+ _$setProp(_el$,"minWidth",0)
403
+ _$setProp(_el$,"flexShrink",1)
404
+ _$setProp(_el$,"overflow","hidden")
405
+ _$setProp(_el$,"onSizeChange",function(){setColumns(this.width)
406
+ })
407
+ _$insertNode(_el$2,_el$3)
408
+ _$setProp(_el$2,"width","100%")
409
+ _$setProp(_el$2,"minWidth",0)
410
+ _$setProp(_el$2,"height",1)
379
411
  _$setProp(_el$2,"wrapMode","none")
380
412
  _$setProp(_el$2,"truncate",true)
381
- _$insert(_el$2,kind,null)
382
- _$insert(_el$2,()=>props.window.label,null)
383
- _$insert(_el$2,(()=>{var _c$=_$memo(()=>props.window.durationMinutes===undefined)
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
413
+ _$insert(_el$3,()=>view().value)
414
+ _$insert(_el$2,scale,null)
415
+ _$setProp(_el$4,"width","100%")
416
+ _$setProp(_el$4,"minWidth",0)
417
+ _$setProp(_el$4,"height",1)
418
+ _$setProp(_el$4,"wrapMode","none")
419
+ _$setProp(_el$4,"truncate",true)
420
+ _$insert(_el$4,()=>view().reset)
421
+ _$effect((_p$)=>{var _v$=props.api.theme.current.textMuted,_v$2={fg:props.api.theme.current.text
422
+ },_v$3=props.api.theme.current.textMuted
393
423
  _v$!==_p$.e&&(_p$.e=_$setProp(_el$2,"fg",_v$,_p$.e))
394
- _v$2!==_p$.t&&(_p$.t=_$setProp(_el$3,"fg",_v$2,_p$.t))
424
+ _v$2!==_p$.t&&(_p$.t=_$setProp(_el$3,"style",_v$2,_p$.t))
395
425
  _v$3!==_p$.a&&(_p$.a=_$setProp(_el$4,"fg",_v$3,_p$.a))
396
426
  return _p$
397
427
  },{e:undefined,t:undefined,a:undefined
@@ -480,49 +510,32 @@ actions.review(option.value.slice("credit:".length)||undefined)
480
510
  }})
481
511
  }
482
512
  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
513
  import {For} from "solid-js"
489
514
  import {effect as _$effect2} from "@opentui/solid"
490
- import {memo as _$memo2} from "@opentui/solid"
491
- import {createTextNode as _$createTextNode} from "@opentui/solid"
492
515
  import {insertNode as _$insertNode2} from "@opentui/solid"
493
516
  import {insert as _$insert2} from "@opentui/solid"
517
+ import {memo as _$memo} from "@opentui/solid"
494
518
  import {setProp as _$setProp2} from "@opentui/solid"
495
519
  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")
520
+ function QuotaBalanceRow(props){return(()=>{var _el$=_$createElement2("text"),_el$2=_$createElement2("span")
497
521
  _$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)
522
+ _$setProp2(_el$,"wrapMode","none")
523
+ _$setProp2(_el$,"truncate",true)
524
+ _$insert2(_el$2,(()=>{var _c$=_$memo(()=>!!props.balance.unlimited)
505
525
  return()=>_c$()?"No configured cap":`${props.balance.amount} ${props.balance.unit ?? "(provider unit not specified)"}`
506
526
  })())
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))
527
+ _$insert2(_el$,()=>` \xB7 ${props.balance.scope} \xB7 ${props.balance.label}`,null)
528
+ _$effect2((_p$)=>{var _v$=props.api.theme.current.textMuted,_v$2={fg:props.api.theme.current.text
529
+ }
530
+ _v$!==_p$.e&&(_p$.e=_$setProp2(_el$,"fg",_v$,_p$.e))
531
+ _v$2!==_p$.t&&(_p$.t=_$setProp2(_el$2,"style",_v$2,_p$.t))
510
532
  return _p$
511
533
  },{e:undefined,t:undefined
512
534
  })
513
535
  return _el$
514
536
  })()
515
537
  }
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
538
+ function QuotaSnapshotRows(props){return [_$createComponent4(For,{get each(){return props.snapshot.windows
526
539
  },children:(window)=>_$createComponent4(QuotaWindowRow,{get api(){return props.api
527
540
  },window
528
541
  })
@@ -532,37 +545,6 @@ return _el$
532
545
  })
533
546
  })]
534
547
  }
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
548
  var text={type:"string",minLength:1,maxLength:512}
567
549
  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
550
  }
@@ -599,34 +581,40 @@ listeners.clear()
599
581
  }}
600
582
  return{adapter,onUpdated(listener){listeners.add(listener)
601
583
  return()=>listeners.delete(listener)
602
- },async capabilities(){try{return await rpc.capabilities({})
603
- } catch{return providerQuotaCatalog(context.data.location.provider?.list?.(context.location)??[])
604
- }}}}
584
+ }}}
585
+ 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"]]
586
+ var PROVIDER_QUOTA_CAPABILITIES= rows.map(([id,name,sources,reason,url])=>({id,name,sources,reason,documentation:[url]}))
587
+ 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"
588
+ }
589
+ function providerQuotaCapability(id,canonical){const key=aliases[canonical??id]??canonical??id
590
+ const found=PROVIDER_QUOTA_CAPABILITIES.find((entry)=>entry.id===key)
591
+ 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/"]
592
+ }}
593
+ import {insert as _$insert8} from "@opentui/solid"
605
594
  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"
595
+ import {setProp as _$setProp9} from "@opentui/solid"
596
+ import {effect as _$effect9} from "@opentui/solid"
609
597
  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"
598
+ import {insertNode as _$insertNode6} from "@opentui/solid"
599
+ import {createElement as _$createElement9} from "@opentui/solid"
600
+ import {memo as _$memo7} from "@opentui/solid"
601
+ import {createComponent as _$createComponent12} from "@opentui/solid"
602
+ import {Show as Show6} from "solid-js"
603
+ import {useDialogs as useDialogs3} from "./tui.js"
628
604
  import {setProp as _$setProp4} from "@opentui/solid"
605
+ import {effect as _$effect4} from "@opentui/solid"
606
+ import {createTextNode as _$createTextNode} from "@opentui/solid"
607
+ import {insertNode as _$insertNode3} from "@opentui/solid"
629
608
  import {createElement as _$createElement4} from "@opentui/solid"
609
+ import {memo as _$memo2} from "@opentui/solid"
610
+ import {createComponent as _$createComponent6} from "@opentui/solid"
611
+ import {Show} from "solid-js"
612
+ import {useDialogs as useDialogs2,useIcons as useIcons2} from "./tui.js"
613
+ import {createComponent as _$createComponent5} from "@opentui/solid"
614
+ import {effect as _$effect3} from "@opentui/solid"
615
+ import {insert as _$insert3} from "@opentui/solid"
616
+ import {setProp as _$setProp3} from "@opentui/solid"
617
+ import {createElement as _$createElement3} from "@opentui/solid"
630
618
  import {SelectionBox} from "./tui.js"
631
619
  import {createSignal as createSignal5,onCleanup} from "solid-js"
632
620
  function sidebarInteractiveColors(theme,state,normalColor=theme.text,mode="row"){const highlighted=!state.disabled&&(state.focused||mode==="control"&&state.hovered)
@@ -661,63 +649,170 @@ descriptor.activate()
661
649
  return true
662
650
  }}}
663
651
  function LimitsAction(props){const item=useSidebarItem(props.api,props.interaction,{id:props.id,position:()=>props.position,disabled:()=>props.disabled===true,activate:props.onActivate
664
- })
665
- return _$createComponent6(SelectionBox,{ref:(node)=>item.ref(node),get id(){return props.id
666
- },height:1,get backgroundColor(){return item.backgroundColor()
652
+ },()=>props.api.theme.current.text,props.inline?"control":"row")
653
+ return _$createComponent5(SelectionBox,{ref:(node)=>item.ref(node),get id(){return props.id
654
+ },height:1,get flexGrow(){return props.flexGrow
655
+ },get maxWidth(){return props.maxWidth
656
+ },flexShrink:1,minWidth:3,get backgroundColor(){return item.backgroundColor()
667
657
  },get onMouseOver(){return item.onMouseOver
668
658
  },get onMouseOut(){return item.onMouseOut
669
659
  },onMouseUp:(event)=>{event.stopPropagation()
670
660
  item.activate(event)
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))
661
+ },get children(){var _el$=_$createElement3("text")
662
+ _$setProp3(_el$,"wrapMode","none")
663
+ _$setProp3(_el$,"truncate",true)
664
+ _$insert3(_el$,()=>props.label)
665
+ _$effect3((_$p)=>_$setProp3(_el$,"fg",item.foregroundColor(),_$p))
676
666
  return _el$
677
667
  }})
678
668
  }
679
- function CodexAccountControls(props){const icons=useIcons()
669
+ function CodexAccountControls(props){const icons=useIcons2()
680
670
  const model=()=>props.controller.current().model
681
- const accountLink=createCodexAccountLink({api:props.api,preferences:props.preferences,adapter:props.codex,model,dialogs:useDialogs3(props.api)
671
+ const accountLink=createCodexAccountLink({api:props.api,preferences:props.preferences,adapter:props.codex,model,dialogs:useDialogs2(props.api)
682
672
  })
683
673
  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
674
+ const action=(id,row,label,onActivate)=>_$createComponent6(LimitsAction,{get api(){return props.api
685
675
  },get interaction(){return props.interaction
686
676
  },id:`${PLUGIN_ID}.limits.${id}`,get position(){return{section:props.navigationSection,row,column:0
687
677
  }},label,onActivate,get disabled(){return accountLink.busy()
688
678
  }})
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))
679
+ return _$createComponent6(Show,{get when(){return eligible()
680
+ },get children(){return [_$createComponent6(Show,{get when(){return _$memo2(()=>!!!props.model.accountID)()&&(!props.model.hostConnection||props.model.hostConnection.status==="ready")
681
+ },get children(){return [(()=>{var _el$=_$createElement4("text")
682
+ _$insertNode3(_el$,_$createTextNode(`Link the matching Codex account once for this connection`))
683
+ _$effect4((_$p)=>_$setProp4(_el$,"fg",props.api.theme.current.textMuted,_$p))
694
684
  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"
685
+ })(),_$memo2(()=>action("link",2,`${icons.icon("scope")} Link Codex CLI account`,()=>void accountLink.link()))]
686
+ }}),_$createComponent6(Show,{get when(){return _$memo2(()=>!!(props.model.accountID&&props.model.accountSource!=="host"))()&&props.controller.snapshot()?.availability==="unauthenticated"
687
+ },get children(){return [_$memo2(()=>action("unlink",4,`${icons.icon("close")} Unlink Codex account`,()=>props.preferences.setCodexConnectionBinding(props.model.providerID,props.model.hostConnection?.id))),_$createComponent6(Show,{get when(){return props.controller.snapshot()?.availability==="unauthenticated"
698
688
  },get children(){return action("relink",5,`${icons.icon("scope")} Relink Codex account`,()=>void accountLink.link())
699
689
  }})]
700
690
  }})]
701
691
  }})
702
692
  }
703
693
  import {createComponent as _$createComponent8} from "@opentui/solid"
704
- import {effect as _$effect6} from "@opentui/solid"
694
+ import {Show as Show2} from "solid-js"
695
+ import {useIcons as useIcons3} from "./tui.js"
696
+ import {createComponent as _$createComponent7} from "@opentui/solid"
697
+ import {effect as _$effect5} from "@opentui/solid"
698
+ import {insert as _$insert4} from "@opentui/solid"
699
+ import {setProp as _$setProp5} from "@opentui/solid"
700
+ import {createElement as _$createElement5} from "@opentui/solid"
701
+ import {memo as _$memo3} from "@opentui/solid"
702
+ import {createEffect as createEffect2,createSignal as createSignal6,on} from "solid-js"
703
+ import {SelectionBox as SelectionBox2} from "./tui.js"
704
+ var graphemes=new Intl.Segmenter(undefined,{granularity:"grapheme"
705
+ })
706
+ function truncateEnd(value,width){if(Bun.stringWidth(value)<=width)
707
+ return value
708
+ if(width<=0)
709
+ return ""
710
+ const ellipsis=width>=3?"...":".".repeat(width)
711
+ let result=""
712
+ for(const{segment
713
+ } of graphemes.segment(value)){if(Bun.stringWidth(result + segment)>width-ellipsis.length)
714
+ break
715
+ result += segment
716
+ }
717
+ return result + ellipsis
718
+ }
719
+ function SectionHeaderControl(props){const [width,setWidth]=createSignal6()
720
+ const control=useSidebarItem(props.api,props.interaction,{id:props.action.id,position:()=>props.position,disabled:props.action.disabled,activate:props.action.onActivate
721
+ },()=>props.api.theme.current.accent,"control")
722
+ createEffect2(on(()=>[props.action.label(),props.resetWidth],()=>setWidth(undefined)))
723
+ const label=()=>width()===undefined||props.iconOnly?props.action.label():truncateEnd(props.action.label(),width()-2)
724
+ return _$createComponent7(SelectionBox2,{ref:(node)=>control.ref(node),get id(){return props.action.id
725
+ },get iconOnly(){return props.iconOnly
726
+ },get width(){return props.iconOnly?3:undefined
727
+ },get flexShrink(){return props.iconOnly?0:1
728
+ },minWidth:3,overflow:"hidden",get alignItems(){return props.iconOnly?"center":undefined
729
+ },get backgroundColor(){return control.backgroundColor()
730
+ },onSizeChange:function(){setWidth(this.width)
731
+ },get onMouseOver(){return control.onMouseOver
732
+ },get onMouseOut(){return control.onMouseOut
733
+ },onMouseDown:(event)=>event.stopPropagation(),onMouseUp:(event)=>{event.stopPropagation()
734
+ control.activate(event)
735
+ },get children(){var _el$=_$createElement5("text")
736
+ _$setProp5(_el$,"wrapMode","none")
737
+ _$setProp5(_el$,"height",1)
738
+ _$insert4(_el$,label)
739
+ _$effect5((_$p)=>_$setProp5(_el$,"fg",control.foregroundColor(),_$p))
740
+ return _el$
741
+ }})
742
+ }
743
+ function LimitsHeaderControls(props){const icons=useIcons3()
744
+ return [_$createComponent8(Show2,{get when(){return props.credits
745
+ },get children(){return _$createComponent8(SectionHeaderControl,{get api(){return props.api
746
+ },get interaction(){return props.interaction
747
+ },get position(){return{section:props.navigationSection,row:0,column:1
748
+ }},get action(){return{id:`${PLUGIN_ID}.limits.credits`,label:()=>"Banked Resets",disabled:()=>false,onActivate:props.onCredits
749
+ }}})
750
+ }}),_$createComponent8(SectionHeaderControl,{get api(){return props.api
751
+ },get interaction(){return props.interaction
752
+ },iconOnly:true,get position(){return{section:props.navigationSection,row:0,column:2
753
+ }},get action(){return{id:`${PLUGIN_ID}.limits.refresh`,label:()=>icons.icon(props.refreshing?"pending":"retry"),disabled:()=>props.refreshing||!props.canRefresh,onActivate:props.onRefresh
754
+ }}})]
755
+ }
705
756
  import {insert as _$insert5} from "@opentui/solid"
757
+ import {effect as _$effect6} from "@opentui/solid"
758
+ import {createTextNode as _$createTextNode2} from "@opentui/solid"
759
+ import {insertNode as _$insertNode4} from "@opentui/solid"
760
+ import {memo as _$memo4} from "@opentui/solid"
761
+ import {createComponent as _$createComponent9} from "@opentui/solid"
706
762
  import {setProp as _$setProp6} from "@opentui/solid"
707
763
  import {createElement as _$createElement6} from "@opentui/solid"
708
- import {Show as Show2} from "solid-js"
764
+ import {Show as Show3} from "solid-js"
765
+ function LimitsModelRow(props){const names=()=>props.selection?.names(props.model)??{provider:props.model.providerID,model:props.model.modelID
766
+ }
767
+ return(()=>{var _el$=_$createElement6("box")
768
+ _$setProp6(_el$,"flexDirection","row")
769
+ _$setProp6(_el$,"minWidth",0)
770
+ _$insert5(_el$,_$createComponent9(LimitsAction,{get api(){return props.api
771
+ },get interaction(){return props.interaction
772
+ },inline:true,flexGrow:1,get maxWidth(){return Bun.stringWidth(`[${names().provider}] ${names().model}`) + 2
773
+ },id:`${PLUGIN_ID}.limits.model`,get position(){return{section:props.navigationSection,row:1,column:0
774
+ }},get label(){return `[${names().provider}] ${names().model}`
775
+ },get disabled(){return !props.selection?.canOpen("model",props.model)
776
+ },onActivate:()=>props.selection?.open("model",props.model)
777
+ }),null)
778
+ _$insert5(_el$,_$createComponent9(Show3,{get when(){return props.model.variant||props.selection?.variants(props.model).length
779
+ },get children(){return [(()=>{var _el$2=_$createElement6("text"),_el$3=_$createTextNode2(` \xB7 `)
780
+ _$insertNode4(_el$2,_el$3)
781
+ _$setProp6(_el$2,"flexShrink",0)
782
+ _$effect6((_$p)=>_$setProp6(_el$2,"fg",props.api.theme.current.textMuted,_$p))
783
+ return _el$2
784
+ })(),_$createComponent9(LimitsAction,{get api(){return props.api
785
+ },get interaction(){return props.interaction
786
+ },inline:true,id:`${PLUGIN_ID}.limits.variant`,get position(){return{section:props.navigationSection,row:1,column:1
787
+ }},get label(){return props.model.variant??"Default"
788
+ },get disabled(){return !props.selection?.canOpen("variant",props.model)
789
+ },onActivate:()=>props.selection?.open("variant",props.model)
790
+ })]
791
+ }}),null)
792
+ return _el$
793
+ })()
794
+ }
795
+ import {effect as _$effect7} from "@opentui/solid"
796
+ import {insert as _$insert6} from "@opentui/solid"
797
+ import {setProp as _$setProp7} from "@opentui/solid"
798
+ import {createElement as _$createElement7} from "@opentui/solid"
799
+ import {createComponent as _$createComponent10} from "@opentui/solid"
800
+ import {memo as _$memo5} from "@opentui/solid"
801
+ import {Show as Show4} from "solid-js"
709
802
  function LimitsStatus(props){const message=()=>{const model=props.controller.current().model
710
803
  const connection=model?.hostConnection
711
804
  const snapshot=props.controller.snapshot()
712
805
  const state=props.controller.state()
713
806
  if(state.status==="error")
714
- return snapshot?"Stale \xB7 refresh failed; retry below":"Quota read failed \xB7 retry below"
807
+ return snapshot?"Stale \xB7 refresh failed":"Quota read failed \xB7 refresh to retry"
808
+ if(snapshot?.availability==="ready"&&(snapshot.windows.length>0||snapshot.balances?.length))
809
+ return
715
810
  if(snapshot?.message)
716
811
  return snapshot.availability==="stale"?`Stale \xB7 ${snapshot.message}`:snapshot.availability==="ready"?`Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()} \xB7 ${snapshot.message}`:snapshot.message
717
812
  if(connection?.status==="loading")
718
813
  return "Checking the OpenCode account\u2026"
719
814
  if(connection?.status==="unavailable")
720
- return "OpenCode account metadata unavailable \xB7 refresh below"
815
+ return "Account metadata unavailable \xB7 refresh to retry"
721
816
  if(connection?.status==="unsupported")
722
817
  return "Codex quotas require an active ChatGPT OAuth connection"
723
818
  if(!snapshot)
@@ -730,194 +825,150 @@ return "No documented quota bucket for this model"
730
825
  if(availability==="rate_limited")
731
826
  return "Provider rate-limited this read \xB7 try again later"
732
827
  if(availability==="stale")
733
- return "Stale provider data \xB7 refresh below"
828
+ return "Stale provider data \xB7 refresh to update"
734
829
  return `Updated ${new Date(snapshot.fetchedAt).toLocaleTimeString()}`
735
830
  }
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))
831
+ return _$createComponent10(Show4,{get when(){return _$memo5(()=>!!props.controller.current().model)()&&message()
832
+ },children:(value)=>(()=>{var _el$=_$createElement7("text")
833
+ _$setProp7(_el$,"wrapMode","word")
834
+ _$insert6(_el$,value)
835
+ _$effect7((_$p)=>_$setProp7(_el$,"fg",props.api.theme.current.textMuted,_$p))
741
836
  return _el$
742
- }})
743
- }
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"
754
- var graphemes=new Intl.Segmenter(undefined,{granularity:"grapheme"
837
+ })()
755
838
  })
756
- function truncateEnd(value,maxWidth){if(Bun.stringWidth(value)<=maxWidth)
757
- return value
758
- if(maxWidth<=0)
759
- return ""
760
- const ellipsis=maxWidth>=3?"...":".".repeat(maxWidth)
761
- const contentWidth=maxWidth-Bun.stringWidth(ellipsis)
762
- let result=""
763
- for(const{segment
764
- } of graphemes.segment(value)){if(Bun.stringWidth(result + segment)>contentWidth)
765
- break
766
- result += segment
767
- }
768
- return result + ellipsis
769
839
  }
770
- function Section(props){const icons=useIcons2()
771
- const [actionWidth,setActionWidth]=createSignal6()
840
+ import {createComponent as _$createComponent11} from "@opentui/solid"
841
+ import {effect as _$effect8} from "@opentui/solid"
842
+ import {createTextNode as _$createTextNode3} from "@opentui/solid"
843
+ import {insertNode as _$insertNode5} from "@opentui/solid"
844
+ import {memo as _$memo6} from "@opentui/solid"
845
+ import {insert as _$insert7} from "@opentui/solid"
846
+ import {setProp as _$setProp8} from "@opentui/solid"
847
+ import {createElement as _$createElement8} from "@opentui/solid"
848
+ import {Show as Show5,createSignal as createSignal7} from "solid-js"
849
+ import {SelectionBox as SelectionBox3,useIcons as useIcons4} from "./tui.js"
850
+ function Section(props){const icons=useIcons4()
851
+ const [headerWidth,setHeaderWidth]=createSignal7(0)
772
852
  const icon=()=>props.section?icons.section(props.section):props.icon
773
853
  const theme=()=>props.api.theme.current
774
854
  const item=useSidebarItem(props.api,props.interaction,{id:props.sectionId??`section.${props.title}`,position:()=>({section:props.navigationSection??0,row:0,column:0
775
855
  }),activate:props.onToggle
776
856
  })
777
- const action=props.headerAction?useSidebarItem(props.api,props.interaction,{id:props.headerAction.id,position:()=>({section:props.navigationSection??0,row:0,column:1
778
- }),disabled:props.headerAction.disabled,activate:props.headerAction.onActivate
779
- },()=>theme().accent,"control"):undefined
780
- createEffect2(()=>{props.headerAction?.label()
781
- setActionWidth(undefined)
782
- })
783
- const actionLabel=()=>{const label=props.headerAction?.label()??""
784
- const width=actionWidth()
785
- return width===undefined?label:truncateEnd(label,width-2)
786
- }
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
790
- },flexDirection:"row",height:1,justifyContent:"space-between",gap:1,paddingLeft:1,paddingRight:1,get backgroundColor(){return item.backgroundColor()
857
+ return(()=>{var _el$=_$createElement8("box")
858
+ _$setProp8(_el$,"gap",1)
859
+ _$insert7(_el$,_$createComponent11(SelectionBox3,{ref:(node)=>item.ref(node),get id(){return props.sectionId
860
+ },flexDirection:"row",height:1,justifyContent:"space-between",gap:1,get backgroundColor(){return item.backgroundColor()
791
861
  },get onMouseOver(){return item.onMouseOver
792
862
  },get onMouseOut(){return item.onMouseOut
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())
863
+ },onMouseDown:(event)=>item.activate(event),onSizeChange:function(){setHeaderWidth(this.width)
864
+ },get children(){return [(()=>{var _el$2=_$createElement8("text"),_el$3=_$createElement8("span"),_el$4=_$createTextNode3(` `),_el$5=_$createElement8("span"),_el$6=_$createElement8("b")
865
+ _$insertNode5(_el$2,_el$3)
866
+ _$insertNode5(_el$2,_el$4)
867
+ _$insertNode5(_el$2,_el$5)
868
+ _$insertNode5(_el$2,_el$6)
869
+ _$setProp8(_el$2,"flexShrink",0)
870
+ _$setProp8(_el$2,"wrapMode","none")
871
+ _$insert7(_el$3,()=>icons.icon(props.open?"expanded":"collapsed"))
872
+ _$insert7(_el$5,(()=>{var _c$=_$memo6(()=>!!icon())
802
873
  return()=>_c$()?`${icon()} `:""
803
874
  })())
804
- _$insert6(_el$6,()=>props.title)
805
- _$effect7((_p$)=>{var _v$=item.foregroundColor(),_v$2={fg:item.focused()?item.foregroundColor():theme().accent
875
+ _$insert7(_el$6,()=>props.title)
876
+ _$effect8((_p$)=>{var _v$=item.foregroundColor(),_v$2={fg:item.focused()?item.foregroundColor():theme().accent
806
877
  },_v$3={fg:item.focused()?item.foregroundColor():theme().accent
807
878
  }
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))
879
+ _v$!==_p$.e&&(_p$.e=_$setProp8(_el$2,"fg",_v$,_p$.e))
880
+ _v$2!==_p$.t&&(_p$.t=_$setProp8(_el$3,"style",_v$2,_p$.t))
881
+ _v$3!==_p$.a&&(_p$.a=_$setProp8(_el$5,"style",_v$3,_p$.a))
811
882
  return _p$
812
883
  },{e:undefined,t:undefined,a:undefined
813
884
  })
814
885
  return _el$2
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
823
- },flexShrink:1,minWidth:3,overflow:"hidden",get backgroundColor(){return control().backgroundColor()
824
- },onSizeChange:function(){setActionWidth(this.width)
825
- },get onMouseOver(){return control().onMouseOver
826
- },get onMouseOut(){return control().onMouseOut
827
- },onMouseDown:(event)=>event.stopPropagation(),onMouseUp:(event)=>{event.stopPropagation()
828
- control().activate(event)
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))
834
- return _el$9
835
- }})
836
- }),_el$8)
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))
886
+ })(),(()=>{var _el$7=_$createElement8("box")
887
+ _$setProp8(_el$7,"flexDirection","row")
888
+ _$setProp8(_el$7,"gap",1)
889
+ _$setProp8(_el$7,"flexGrow",1)
890
+ _$setProp8(_el$7,"minWidth",0)
891
+ _$setProp8(_el$7,"justifyContent","flex-end")
892
+ _$insert7(_el$7,()=>props.headerControls,null)
893
+ _$insert7(_el$7,_$createComponent11(Show5,{get when(){return props.headerAction
894
+ },children:(action)=>_$createComponent11(SectionHeaderControl,{get api(){return props.api
895
+ },get interaction(){return props.interaction
896
+ },get action(){return action()
897
+ },get resetWidth(){return headerWidth()
898
+ },get position(){return{section:props.navigationSection??0,row:0,column:1
899
+ }}})
900
+ }),null)
901
+ _$insert7(_el$7,_$createComponent11(Show5,{get when(){return props.summary
902
+ },get children(){var _el$8=_$createElement8("text")
903
+ _$setProp8(_el$8,"wrapMode","none")
904
+ _$setProp8(_el$8,"truncate",true)
905
+ _$setProp8(_el$8,"height",1)
906
+ _$insert7(_el$8,()=>props.summary)
907
+ _$effect8((_p$)=>{var _v$4=item.focused()?item.foregroundColor():theme().textMuted,_v$5=props.headerAction?0:1
908
+ _v$4!==_p$.e&&(_p$.e=_$setProp8(_el$8,"fg",_v$4,_p$.e))
909
+ _v$5!==_p$.t&&(_p$.t=_$setProp8(_el$8,"flexShrink",_v$5,_p$.t))
844
910
  return _p$
845
911
  },{e:undefined,t:undefined
846
912
  })
913
+ return _el$8
914
+ }}),null)
847
915
  return _el$7
848
916
  })()]
849
917
  }}),null)
850
- _$insert6(_el$,_$createComponent9(Show3,{get when(){return props.open
918
+ _$insert7(_el$,_$createComponent11(Show5,{get when(){return props.open
851
919
  },get children(){return props.children
852
920
  }}),null)
853
921
  return _el$
854
922
  })()
855
923
  }
856
- function ProviderLimitsSection(props){const icons=useIcons3()
857
- const dialogs=useDialogs4(props.api)
924
+ function ProviderLimitsSection(props){const dialogs=useDialogs3(props.api)
858
925
  const model=()=>props.controller.current().model
859
926
  const snapshot=props.controller.snapshot
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
862
- },get interaction(){return props.interaction
863
- },id:`${PLUGIN_ID}.limits.${id}`,get position(){return{section:props.navigationSection,row,column:0
864
- }},label,onActivate,disabled
865
- })
866
927
  function openCredits(){const selected=model()
867
928
  if(selected)
868
- dialogs.open(()=>_$createComponent10(ResetCreditDialog,{get api(){return props.api
929
+ dialogs.open(()=>_$createComponent12(ResetCreditDialog,{get api(){return props.api
869
930
  },get preferences(){return props.preferences
870
931
  },get controller(){return props.controller
871
932
  },get adapter(){return props.codex
872
933
  },model:selected
873
934
  }))
874
935
  }
875
- return _$createComponent10(Section,{get api(){return props.api
936
+ return _$createComponent12(Section,{get api(){return props.api
876
937
  },get interaction(){return props.interaction
877
938
  },sectionId:`${PLUGIN_ID}.section.limits`,get navigationSection(){return props.navigationSection
878
- },title:"LIMITS",section:"limits",get summary(){return _$memo6(()=>!!count())()?String(count()):""
939
+ },title:"LIMITS",section:"limits",summary:"",get headerControls(){return _$createComponent12(LimitsHeaderControls,{get api(){return props.api
940
+ },get interaction(){return props.interaction
941
+ },get navigationSection(){return props.navigationSection
942
+ },get credits(){return(snapshot()?.bankedResets?.availableCount??0)>0||Boolean(props.preferences.pendingResetAttempt())
943
+ },get refreshing(){return ["loading","refreshing"].includes(props.controller.state().status)
944
+ },get canRefresh(){return Boolean(model()&&(props.controller.current().adapter||model()?.hostConnection?.status==="unavailable"))
945
+ },onCredits:openCredits,onRefresh:()=>void props.controller.refresh(true)
946
+ })
879
947
  },get open(){return props.preferences.expanded().limits
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))
948
+ },onToggle:()=>props.preferences.toggleSectionExpanded("limits"),get children(){return _$createComponent12(Show6,{get when(){return model()
949
+ },get fallback(){return(()=>{var _el$=_$createElement9("text")
950
+ _$insertNode6(_el$,_$createTextNode4(`Open a session to view model limits`))
951
+ _$effect9((_$p)=>_$setProp9(_el$,"fg",props.api.theme.current.textMuted,_$p))
884
952
  return _el$
885
953
  })()
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()
954
+ },children:(selected)=>(()=>{var _el$3=_$createElement9("box")
955
+ _$setProp9(_el$3,"width","100%")
956
+ _$setProp9(_el$3,"minWidth",0)
957
+ _$setProp9(_el$3,"flexShrink",1)
958
+ _$setProp9(_el$3,"overflow","hidden")
959
+ _$insert8(_el$3,_$createComponent12(LimitsModelRow,_$mergeProps(props,{get model(){return selected()
960
+ },get selection(){return props.modelSelection
961
+ }})),null)
962
+ _$insert8(_el$3,_$createComponent12(CodexAccountControls,_$mergeProps(props,{get model(){return selected()
895
963
  }})),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
964
+ _$insert8(_el$3,_$createComponent12(Show6,{get when(){return _$memo7(()=>!!(snapshot()&&["ready","stale"].includes(snapshot().availability)))()?snapshot():undefined
965
+ },children:(quota)=>_$createComponent12(QuotaSnapshotRows,{get api(){return props.api
898
966
  },get snapshot(){return quota()
899
967
  }})
900
968
  }),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))
906
- return _el$6
907
- })(),_$createComponent10(Show4,{get when(){return credits().availableCount>0||props.preferences.pendingResetAttempt()
908
- },get children(){return action("credits",2,`${icons.icon("info")} Review reset credits`,openCredits)
909
- }})]
910
- }),null)
911
- _$insert7(_el$3,_$createComponent10(LimitsStatus,{get api(){return props.api
969
+ _$insert8(_el$3,_$createComponent12(LimitsStatus,{get api(){return props.api
912
970
  },get controller(){return props.controller
913
971
  }}),null)
914
- _$insert7(_el$3,_$createComponent10(Show4,{get when(){return props.controller.current().adapter||selected().hostConnection?.status==="unavailable"
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))
916
- }}),null)
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))
921
972
  return _el$3
922
973
  })()
923
974
  })
@@ -931,7 +982,7 @@ const model=api.state.session.get(sessionID)?.model
931
982
  if(!model)
932
983
  return
933
984
  return{sessionID,providerID:model.providerID,modelID:model.id,...model.variant&&{variant:model.variant}}}
934
- import {createSignal as createSignal7,untrack as untrack2} from "solid-js"
985
+ import {createSignal as createSignal8,untrack as untrack2} from "solid-js"
935
986
  var CHATGPT_METHODS=new Set(["chatgpt-browser","chatgpt-headless"])
936
987
  function providerAccountIdentity(provider,credentials){if(!provider.integrationID||provider.activation==="disabled")
937
988
  return{status:"unsupported"}
@@ -953,7 +1004,7 @@ const header=Object.entries(provider.headers??{}).find(([name])=>name.toLowerCas
953
1004
  if(accountID&&header&&header!==accountID)
954
1005
  return{status:"loading"}
955
1006
  return{status:"ready",id,method:"oauth",providerID,...accountID&&{accountID}}}
956
- function createOpenCodeV2AccountSource(context,lifecycle){const [connections,setConnections]=createSignal7({})
1007
+ function createOpenCodeV2AccountSource(context,lifecycle){const [connections,setConnections]=createSignal8({})
957
1008
  let generation=0
958
1009
  let disposed=false
959
1010
  let lastModel
@@ -1016,6 +1067,26 @@ return{...model,accountID:undefined,accountSource:undefined}
1016
1067
  const accountID=connection?.accountID??preferences.codexAccountForConnection(model.providerID,connection?.id,model.modelID)
1017
1068
  const accountSource=connection?.accountID?"host":"binding"
1018
1069
  return{...model,accountID,accountSource:accountID?accountSource:undefined}}
1070
+ var commandName=(kind)=>kind==="model"?"model.list":"variant.list"
1071
+ function createLimitsModelSelection(api,current,context){const sameTarget=(model)=>{const active=current()
1072
+ return active?.sessionID===model.sessionID&&active.providerID===model.providerID&&active.modelID===model.modelID
1073
+ }
1074
+ const legacy=(model)=>api.state.provider?.find((provider)=>provider.id===model.providerID)
1075
+ const variants=(model)=>context?context.ui.model.variant?.list?.()??[]:Object.keys(legacy(model)?.models[model.modelID]?.variants??{})
1076
+ const canOpen=(kind,model)=>{const command=api.keymap.getCommands().find((entry)=>entry.name===commandName(kind))
1077
+ const enabled=typeof command?.enabled==="function"?command.enabled():command?.enabled!==false
1078
+ return Boolean(command&&enabled&&sameTarget(model)&&(kind==="model"||variants(model).length>0))
1079
+ }
1080
+ return{names(model){if(context){const location=context.location??context.data.location.default()
1081
+ const provider=context.data.location.provider?.list?.(location)?.find((entry)=>entry.id===model.providerID)
1082
+ const item=context.data.location.model?.list?.(location)?.find((entry)=>entry.providerID===model.providerID&&entry.id===model.modelID)
1083
+ return{provider:provider?.name??model.providerID,model:item?.name??model.modelID}}
1084
+ const provider=legacy(model)
1085
+ return{provider:provider?.name??model.providerID,model:provider?.models[model.modelID]?.name??model.modelID
1086
+ }},variants,canOpen,open(kind,model){if(!canOpen(kind,model))
1087
+ return false
1088
+ return api.keymap.dispatchCommand(commandName(kind)).ok
1089
+ }}}
1019
1090
  function selectedV2Model(context){const route=context.ui.router.current()
1020
1091
  const model=context.ui.model.current()
1021
1092
  if(route.type!=="session"||!model)
@@ -1035,7 +1106,7 @@ native?.onUpdated((providerID,modelID)=>{const selected=selectModel()
1035
1106
  if(selected?.providerID===providerID&&selected.modelID===modelID)
1036
1107
  controller.refresh()
1037
1108
  })
1038
- return{controller,codex,accountSource,selectModel,capabilities:native?.capabilities??(async ()=>providerQuotaCatalog())
1109
+ return{controller,codex,accountSource,selectModel,modelSelection:createLimitsModelSelection(api,selectModel,context)
1039
1110
  }}
1040
1111
  export{ProviderLimitsSection,bindCodexAccount,createCodexAppServerClient,createCodexQuotaAdapter,createOpenCodeV2AccountSource,createProviderLimitsController,createProviderQuotaIntegration,selectedV1Model
1041
1112
  }