opencode-navigator 0.21.8 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -6,30 +6,6 @@ const statuses=new Set(["pending","in_progress","completed","cancelled"])
6
6
  const priorities=new Set(["high","medium","low"])
7
7
  const todos=Array.isArray(state.todos)?state.todos.filter((item)=>item&&typeof item.content==="string"&&item.content.trim().length>0&&item.content.length<=512&&statuses.has(item.status)).slice(0,100).map((item)=>({content:item.content.trim(),status:item.status,...priorities.has(item.priority??"")&&{priority:item.priority}})):[]
8
8
  return{todos,guidance:state.guidance===true}}
9
- function todoGuidance(state){const lines=state.todos.map((todo)=>`- [${todo.status}] ${todo.content}`)
10
- return ["Navigator Todo: when work has multiple steps, use navigator_todo_write to keep the session Todo list current.","Mark work in progress, completed, or cancelled as it changes; avoid claiming unfinished work is done.",...lines.length>0?["Current Navigator Todo items:",...lines]:[]].join(`
11
- `)
12
- }
13
- function key(sessionID){return `session.todo.${sessionID}`
14
- }
15
- function createManagedTodos(storage,onChange){const pending=new Map
16
- async function list(sessionID){return normalizeTodoState(await storage.get(key(sessionID)))
17
- }
18
- function change(sessionID,apply){const request=(pending.get(sessionID)??Promise.resolve()).catch(()=>{}).then(async ()=>{const next=apply(await list(sessionID))
19
- await storage.set(key(sessionID),next)
20
- await onChange(sessionID,next)
21
- return next
22
- })
23
- pending.set(sessionID,request)
24
- const clear=()=>{if(pending.get(sessionID)===request)
25
- pending.delete(sessionID)
26
- }
27
- request.then(clear).catch(clear)
28
- return request
29
- }
30
- return{list,replace(sessionID,todos){return change(sessionID,(current)=>({...current,todos:normalizeTodoState({todos}).todos}))
31
- },guidance(sessionID,enabled){return change(sessionID,(current)=>({...current,guidance:enabled}))
32
- }}}
33
9
  var item={type:"object",properties:{content:{type:"string",minLength:1,maxLength:512},status:{type:"string",enum:["pending","in_progress","completed","cancelled"]},priority:{type:"string",enum:["high","medium","low"]}},required:["content","status"],additionalProperties:false
34
10
  }
35
11
  var todos={type:"array",items:item,maxItems:100}
@@ -43,14 +19,23 @@ var NavigatorTodoRpc={id:"opencode-navigator.todo",methods:{list:{input:{type:"o
43
19
  },output
44
20
  }},events:{updated:{schema:{type:"object",properties:{sessionID,todos,guidance:{type:"boolean"}},required:["sessionID","todos","guidance"],additionalProperties:false
45
21
  }}}};
22
+ var LegacyTodoArchiveRpc={id:NavigatorTodoRpc.id,methods:{list:NavigatorTodoRpc.methods.list},events:{}}
23
+ async function readLegacyTodoArchive(get,sessionID){return normalizeTodoState(await get(`session.todo.${sessionID}`))
24
+ }
46
25
  var text={type:"string",minLength:1,maxLength:512}
47
26
  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
27
  }
49
28
  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
29
  }}}};
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")
30
+ function windowScopeMatches(snapshot,window){if(window.scope==="workspace")
31
+ return Boolean(snapshot.workspaceId)&&(window.modelID===undefined||window.modelID===snapshot.model.modelID)
32
+ if(window.scope==="account")
33
+ return Boolean(snapshot.accountId)&&window.modelID===undefined
34
+ return window.modelID===snapshot.model.modelID
52
35
  }
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")
36
+ function normalizeProviderQuota(snapshot){if(!snapshot.providerId||!snapshot.providerName||!snapshot.model.providerID||!snapshot.model.modelID||!snapshot.model.sessionID||snapshot.workspaceId!==undefined&&(typeof snapshot.workspaceId!=="string"||!snapshot.workspaceId.trim())||!Number.isFinite(snapshot.fetchedAt)){throw new Error("Invalid provider quota identity")
37
+ }
38
+ const windows=snapshot.windows.map((window)=>{if(!window.id||!window.label||window.scope!==undefined&&window.scope!=="account"&&window.scope!=="model"&&window.scope!=="workspace"||!windowScopeMatches(snapshot,window)||!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
39
  }
55
40
  return{...window}})
56
41
  const bankedResets=snapshot.bankedResets
@@ -60,7 +45,7 @@ return{...balance}})
60
45
  if(bankedResets&&(!Number.isSafeInteger(bankedResets.availableCount)||bankedResets.availableCount<0)){throw new Error("Invalid banked reset count")
61
46
  }
62
47
  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"]]
48
+ 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 Console / Go",["workspace-budget","subscription"],"OpenCode 2.2 public usage API: applicable workspace USD budgets and percent-based Go allowance; not a universal account balance API.","https://github.com/anomalyco/opencode/blob/v2.2.0/packages/schema/src/usage-limits.ts"],["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
49
  var PROVIDER_QUOTA_CAPABILITIES= rows.map(([id,name,sources,reason,url])=>({id,name,sources,reason,documentation:[url]}))
65
50
  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
51
  }
@@ -484,6 +469,10 @@ const windows=openRouterFreeWindow(body,input.model.modelID,input.apiModelID)
484
469
  const id=typeof data.organization_id==="string"?data.organization_id:undefined
485
470
  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
471
  }}
472
+ function credentialToken(credential){if(!credential||credential.type==="external")
473
+ return
474
+ return credential.type==="oauth"?credential.access:credential.key
475
+ }
487
476
  async function nativeQuotaContext(host,model,signal,fetcher){const [provider,catalog]=await Promise.all([host.provider.get({providerID:model.providerID}),host.model.list()])
488
477
  const selected=catalog.data.find((entry)=>entry.providerID===model.providerID&&entry.id===model.modelID&&entry.enabled)
489
478
  if(!selected||!provider.data.integrationID||provider.data.activation==="disabled")
@@ -492,7 +481,7 @@ const connection=await host.integration.connection.active(provider.data.integrat
492
481
  if(!connection)
493
482
  return
494
483
  const credential=await host.integration.connection.resolve(connection)
495
- const token=credential?.type==="oauth"?credential.access:credential?.key
484
+ const token=credentialToken(credential)
496
485
  if(!credential||!token)
497
486
  return
498
487
  signal.throwIfAborted()
@@ -502,7 +491,7 @@ async function sameNativeConnection(host,input){const current=input.provider.int
502
491
  if(!current||connectionKey(current)!==input.connectionID)
503
492
  return false
504
493
  const credential=await host.integration.connection.resolve(current)
505
- const token=credential?.type==="oauth"?credential.access:credential?.key
494
+ const token=credentialToken(credential)
506
495
  return token===input.token
507
496
  }
508
497
  function requestUsesCredential(request,input){const url=new URL(request.url)
@@ -518,7 +507,7 @@ if(["x-api-key","api-key","x-goog-api-key"].some((header)=>request.headers.get(h
518
507
  return true
519
508
  return url.hostname==="generativelanguage.googleapis.com"&&url.searchParams.get("key")===input.token
520
509
  }
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")])
510
+ function key(input){return JSON.stringify([input.model.providerID,input.model.modelID,input.model.variant,input.connectionID,createHash("sha256").update(input.token).digest("hex")])
522
511
  }
523
512
  function modelForEvent(event){return{providerID:event.model.providerID,modelID:event.model.id,sessionID:event.sessionID}}
524
513
  function rejectedRead(model,error,input){const status=error instanceof QuotaReadError?error.status:undefined
@@ -540,7 +529,7 @@ if(input.canonical==="github-copilot")
540
529
  return copilot.read(input)
541
530
  return readNativeRest(input)
542
531
  }
543
- async function currentResult(input,force){const identity=key2(input)
532
+ async function currentResult(input,force){const identity=key(input)
544
533
  const headers=observed.get(identity)
545
534
  const existing=cached.get(identity)
546
535
  let result=!force&&existing&&Date.now()-existing.fetchedAt<30000?existing:undefined
@@ -561,13 +550,13 @@ const epoch=generation
561
550
  const observation=AbortSignal.any([lifecycle,AbortSignal.timeout(1000)])
562
551
  try{const input=await withinQuotaSignal(observation,()=>nativeQuotaContext(host,modelForEvent(event),observation,fetcher))
563
552
  if(input&&epoch===generation&&requestUsesCredential(event.request,input)&&await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
564
- captured.set(event.request,{key:key2(input),generation:epoch})
553
+ captured.set(event.request,{key:key(input),generation:epoch})
565
554
  } catch{}},async response(event){const request=captured.get(event.request)
566
555
  if(!request||request.generation!==generation||lifecycle.aborted)
567
556
  return false
568
557
  const observation=AbortSignal.any([lifecycle,AbortSignal.timeout(1000)])
569
558
  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)))
559
+ if(!input||key(input)!==request.key||!await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
571
560
  return false
572
561
  let windows=nativeHeaderWindows(input.canonical,input.model.modelID,event.response.headers)
573
562
  if(windows.length===0&&event.response.status===429&&["google","google-vertex"].includes(input.canonical)&&event.response.headers.get("content-type")?.includes("json"))
@@ -619,7 +608,8 @@ const request=await host.session.hook("http.request",(event)=>service.request(ev
619
608
  const response=await host.session.hook("http.response",async (event)=>{if(await service.response(event))
620
609
  await rpc.events.emit("updated",{providerID:event.model.providerID,modelID:event.model.id}).catch(()=>{})
621
610
  });
622
- (async ()=>{const events=host.event.subscribe({signal:lifecycle.signal})
611
+ (async ()=>{const events=host.event.subscribe({signal:lifecycle.signal,types:["credential.updated","credential.switched","integration.updated","provider.updated"]
612
+ })
623
613
  for await (const event of events){if(["credential.updated","credential.switched","integration.updated","provider.updated"].includes(event.type))
624
614
  service.invalidate()
625
615
  }})().catch(()=>{})
@@ -629,32 +619,11 @@ await response.dispose()
629
619
  await rpc.dispose()
630
620
  await service.dispose()
631
621
  }}
632
- var input={type:"object",properties:{todos:NavigatorTodoRpc.methods.replace.input.properties.todos
633
- },required:["todos"],additionalProperties:false
634
- }
635
- var noNotify=async ()=>{}
636
- var todo_server_default=Plugin.define({id:"opencode-navigator.todo-server",async setup(host){let notify=noNotify
637
- const todos=createManagedTodos({get:(key)=>host.storage.get(key),set:(key,value)=>host.storage.set(key,value)
638
- },(sessionID,state)=>notify(sessionID,state))
639
- const rpc=await host.rpc.register(NavigatorTodoRpc,{list:async (value)=>todos.list(value.sessionID),replace:async (value)=>{const{sessionID,todos:items}=value
640
- return todos.replace(sessionID,items)
641
- },guidance:async (value)=>{const{sessionID,enabled}=value
642
- return todos.guidance(sessionID,enabled)
643
- }})
644
- notify=(sessionID,state)=>rpc.events.emit("updated",{sessionID,...state})
645
- const tools=await host.tool.transform((editor)=>{editor.namespace({name:"navigator",description:"Navigator session tasks"})
646
- editor.add({name:"todo_write",description:"Replace the Navigator Todo list for this session with the current tasks and their statuses.",input,options:{namespace:"navigator"},execute:async (value,context)=>{const state=await todos.replace(context.sessionID,value.todos)
647
- return{content:`Navigator Todo updated: ${state.todos.length} tasks.`}}})
648
- })
649
- const context=await host.session.hook("context",async (event)=>{const state=await todos.list(event.sessionID)
650
- if(state.guidance)
651
- event.system.push({type:"text",text:todoGuidance(state)})
622
+ var server_plugin_default=Plugin.define({id:"opencode-navigator.todo-server",async setup(host){const archive=await host.rpc.register(LegacyTodoArchiveRpc,{list:async (value)=>readLegacyTodoArchive((key)=>host.storage.get(key),value.sessionID)
652
623
  })
653
624
  const quotaCleanup=typeof host.integration?.connection?.active==="function"&&typeof host.provider?.get==="function"?await setupProviderQuotaServer(host):undefined
654
625
  return async ()=>{await quotaCleanup?.()
655
- await context.dispose()
656
- await tools.dispose()
657
- await rpc.dispose()
626
+ await archive.dispose()
658
627
  }}})
659
- export{todo_server_default as default
628
+ export{server_plugin_default as default
660
629
  }
@@ -10,16 +10,17 @@ _$insertNode(_el$,_$createTextNode(`Paste versioned Navigator JSON.`))
10
10
  _$effect((_$p)=>_$setProp(_el$,"fg",input.api.theme.current.textMuted,_$p))
11
11
  return _el$
12
12
  })(),onConfirm(source){const preview=input.preferences.previewPortableSettings(source)
13
- const options=[...preview.settings.layout?[{title:`Layout \xB7 ${preview.layoutChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:`${Object.keys(preview.settings.layout.sections).length} visibility \xB7 ${Object.keys(preview.settings.layout.expanded).length} expansion values`,icon:preview.layoutChanged?"edit":"done"
13
+ const options=[...preview.settings.behavior?[{title:`Toolbar overflow \xB7 ${preview.behaviorChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:preview.settings.behavior.toolbarOverflow??"unchanged",icon:"settings"
14
+ }]:[],...preview.settings.layout?[{title:`Layout \xB7 ${preview.layoutChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:`${Object.keys(preview.settings.layout.sections).length} visibility \xB7 ${Object.keys(preview.settings.layout.expanded).length} expansion values`,icon:preview.layoutChanged?"edit":"done"
14
15
  }]:[],...preview.settings.mcp?[{title:`MCP \xB7 ${preview.mcpChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:`${Object.keys(preview.settings.mcp).length} remembered server states`,icon:preview.mcpChanged?"edit":"done"
15
16
  }]:[],...preview.unsupported.map((path)=>({title:"Unsupported field \xB7 SKIPPED",value:"status",description:path,icon:"error"
16
- })),...preview.layoutChanged||preview.mcpChanged?[{title:"Apply imported settings",value:"apply",description:preview.target,icon:"done"
17
+ })),...preview.layoutChanged||preview.mcpChanged||preview.behaviorChanged?[{title:"Apply imported settings",value:"apply",description:preview.target,icon:"done"
17
18
  }]:[],{title:"Cancel",value:"cancel",description:"return without changes",icon:"close"
18
19
  }]
19
20
  let applying=false
20
21
  async function apply(){applying=true
21
22
  try{await input.preferences.applyPortableSettings(preview)
22
- input.api.ui.toast({variant:"success",title:"Portable settings",message:"Layout and MCP settings imported",duration:3000
23
+ input.api.ui.toast({variant:"success",title:"Portable settings",message:"Supported settings imported",duration:3000
23
24
  })
24
25
  input.dialogs.back()
25
26
  } catch(error){applying=false