@bitkyc08/opencodex 2.7.11 → 2.7.13

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.
@@ -37,4 +37,4 @@ v2: 多线程代理(spawn_agent)。所有模型使用 v2 协作界面。
37
37
  "model": "gpt-5.4",
38
38
  "input": "Hello, world!"
39
39
  }'`})]})]})}function mn({apiBase:e}){let t=Te(),[n,r]=(0,_.useState)(null),[i,a]=(0,_.useState)([]),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(!1),[u,d]=(0,_.useState)(!0),f=(0,_.useCallback)(async()=>{try{let t=await fetch(`${e}/api/claude-code`).then(e=>e.json());r({...t,systemEnv:t.systemEnv!==!1,fastMode:t.fastMode??null,maxContextTokens:t.maxContextTokens??null,autoContext:t.autoContext!==!1,autoCompactWindow:t.autoCompactWindow??null,injectAgents:t.injectAgents!==!1,effectiveModelEnv:t.effectiveModelEnv??{}}),a(Object.entries(t.modelMap??{}).map(([e,t])=>({from:e,to:String(t)})))}catch{l(!1),s(t(`claude.loadFail`))}finally{d(!1)}},[e,t]);(0,_.useEffect)(()=>{let e=window.setTimeout(()=>{f()},0);return()=>window.clearTimeout(e)},[f]);let p=(0,_.useMemo)(()=>{let e=(n?.available??[]).map(e=>({value:e,label:L(e)}));return[{value:``,label:t(`claude.slotUnset`)},...e]},[n?.available,t]),m=(0,_.useMemo)(()=>{let e=[1e5,2e5,25e4,3e5,35e4,4e5,5e5,6e5,75e4,9e5,1e6],r=e=>e>=1e6?`1M`:`${Math.round(e/1e3)}k`,i=n?.autoCompactWindow??null,a=i!==null&&!e.includes(i)?[...e,i].sort((e,t)=>e-t):e;return[{value:``,label:t(`claude.autoCompactDefault`)},...a.map(e=>({value:String(e),label:r(e)}))]},[n?.autoCompactWindow,t]),h=async()=>{if(!n)return;s(``);let r={};for(let e of i)e.from.trim()&&e.to.trim()&&(r[e.from.trim()]=e.to.trim());try{let i=await fetch(`${e}/api/claude-code`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({enabled:n.enabled,systemEnv:n.systemEnv,fastMode:n.fastMode,autoContext:n.autoContext,autoCompactWindow:n.autoCompactWindow,injectAgents:n.injectAgents,smallFastModel:n.smallFastModel,modelMap:r,webSearchSidecar:n.webSearchSidecar?{backend:n.webSearchSidecar.backend??null,model:n.webSearchSidecar.model??``}:null,visionSidecar:n.visionSidecar?{backend:n.visionSidecar.backend??null,model:n.visionSidecar.model??``}:null})}),a=await i.json();l(i.ok),s(i.ok?t(`claude.saved`):a.error||t(`claude.saveFailed`)),i.ok&&await f()}catch{l(!1),s(t(`claude.networkError`))}};if(u)return(0,S.jsx)(`div`,{className:`muted`,style:{padding:8},children:t(`claude.loading`)});if(!n)return(0,S.jsx)(Me,{tone:`err`,children:o||t(`claude.loadFail`)});let g=`http://127.0.0.1:${n.port}`,v=n.effectiveModelEnv,y=n.autoContext&&n.maxContextTokens===null,b=[`export ANTHROPIC_BASE_URL=${g}`,`# no ANTHROPIC_AUTH_TOKEN: your claude.ai login (and connectors) stay active`,`export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`,...y?[`export CLAUDE_CODE_AUTO_COMPACT_WINDOW=${n.autoCompactWindow??35e4}`]:[],...[`ANTHROPIC_MODEL`,`ANTHROPIC_DEFAULT_OPUS_MODEL`,`ANTHROPIC_DEFAULT_SONNET_MODEL`,`ANTHROPIC_DEFAULT_HAIKU_MODEL`,`ANTHROPIC_DEFAULT_FABLE_MODEL`].filter(e=>v[e]).map(e=>`export ${e}=${v[e]}`),`claude`].join(`
40
- `);return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`div`,{className:`page-head`,children:(0,S.jsxs)(`h2`,{children:[t(`nav.claude`),` Code`]})}),(0,S.jsx)(`p`,{className:`page-sub`,children:t(`claude.subtitle`)}),o&&(0,S.jsx)(Me,{tone:c?`ok`:`err`,children:o}),(0,S.jsxs)(`div`,{className:`card`,style:{overflow:`hidden`},children:[(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.enabledLabel`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.enabledHint`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.enabled,onChange:e=>r({...n,enabled:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.systemEnv`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.systemEnvDesc`)}),n.systemEnv&&(0,S.jsx)(`span`,{className:`desc`,style:{color:`var(--red)`},children:t(`claude.systemEnvWarn`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.systemEnv,onChange:e=>r({...n,systemEnv:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.fastMode`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.fastModeDesc`)})]}),(0,S.jsxs)(`select`,{value:n.fastMode===null?`auto`:n.fastMode?`on`:`off`,onChange:e=>{let t=e.target.value;r({...n,fastMode:t===`auto`?null:t===`on`})},style:{padding:`5px 10px`,borderRadius:`var(--radius-xs)`,border:`1px solid var(--border)`,background:`var(--surface)`,color:`var(--text)`,fontSize:12.5,fontWeight:500},children:[(0,S.jsx)(`option`,{value:`auto`,children:t(`claude.fastAuto`)}),(0,S.jsx)(`option`,{value:`on`,children:t(`claude.fastOn`)}),(0,S.jsx)(`option`,{value:`off`,children:t(`claude.fastOff`)})]})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.autoContext`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.autoContextDesc`)}),n.maxContextTokens!==null&&(0,S.jsx)(`span`,{className:`desc`,style:{color:`var(--muted)`},children:t(`claude.autoContextInert`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.autoContext,onChange:e=>r({...n,autoContext:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),n.autoContext&&(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.autoCompactWindow`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.autoCompactWindowDesc`)}),n.autoCompactWindow!==null&&(0,S.jsx)(`span`,{className:`desc`,style:{color:`var(--red)`},children:t(`claude.autoCompactWindowWarn`)})]}),(0,S.jsx)(Ne,{value:n.autoCompactWindow===null?``:String(n.autoCompactWindow),options:m,onChange:e=>r({...n,autoCompactWindow:e===``?null:Number(e)}),label:t(`claude.autoCompactWindow`),style:{minWidth:130}})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.injectAgents`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.injectAgentsDesc`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.injectAgents,onChange:e=>r({...n,injectAgents:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),[`webSearchSidecar`,`visionSidecar`].map(e=>{let i=n[e],a=e===`webSearchSidecar`?`claude.webSearchSidecar`:`claude.visionSidecar`,o=e===`webSearchSidecar`?`claude.webSearchSidecarHint`:`claude.visionSidecarHint`;return(0,S.jsxs)(`div`,{className:`setting-row`,style:{alignItems:`flex-start`},children:[(0,S.jsxs)(`div`,{className:`setting-label setting-copy`,style:{flex:1},children:[(0,S.jsx)(`span`,{className:`title`,children:t(a)}),(0,S.jsx)(`span`,{className:`desc`,children:t(o)})]}),(0,S.jsxs)(`div`,{className:`setting-controls`,style:{display:`flex`,gap:8},children:[(0,S.jsx)(Ne,{value:i?i.backend??`auto`:`inherit`,options:[{value:`inherit`,label:t(`claude.useMainSetting`)},{value:`auto`,label:t(`dash.backendAuto`)},{value:`openai`,label:t(`dash.backendOpenAI`)},{value:`anthropic`,label:t(`dash.backendAnthropic`)}],onChange:t=>r({...n,[e]:t===`inherit`?void 0:{...i,backend:t===`auto`?void 0:t}}),label:t(`dash.sidecarBackend`)}),(0,S.jsx)(`input`,{className:`input mono`,value:i?.model??``,onChange:t=>r({...n,[e]:{...i,model:t.target.value}}),placeholder:t(`claude.sidecarModelPlaceholder`),disabled:!i,"aria-label":t(`dash.sidecarModel`),style:{minWidth:210}})]})]},e)})]}),(0,S.jsx)(`div`,{className:`h-section`,children:t(`claude.quickstart`)}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:(0,S.jsx)(De,{k:`claude.quickstartHint`,cmd:`ocx claude`})}),(0,S.jsx)(`pre`,{className:`mono card`,style:{padding:`10px 14px`,overflowX:`auto`,margin:0},children:`ocx claude`}),(0,S.jsxs)(`details`,{style:{margin:`10px 0 0`},children:[(0,S.jsx)(`summary`,{className:`muted`,style:{fontSize:12.5,cursor:`pointer`,padding:`2px 2px`},children:t(`claude.manualEnv`)}),(0,S.jsx)(`pre`,{className:`mono card`,style:{padding:`10px 14px`,overflowX:`auto`,margin:`6px 0 0`,fontSize:12},children:b})]}),(0,S.jsx)(`div`,{className:`h-section`,children:t(`claude.smallFastModel`)}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:t(`claude.smallFastModelHint`)}),(0,S.jsx)(Ne,{value:n.smallFastModel,options:p,onChange:e=>r({...n,smallFastModel:e}),label:t(`claude.smallFastModel`),style:{maxWidth:420}}),(0,S.jsxs)(`div`,{className:`h-section`,children:[t(`claude.modelMap`),` `,(0,S.jsx)(`span`,{className:`count`,children:i.length})]}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:t(`claude.modelMapHint`)}),(0,S.jsx)(`div`,{className:`stack`,style:{gap:8},children:i.map((e,n)=>(0,S.jsxs)(`div`,{className:`row`,style:{gap:8},children:[(0,S.jsx)(`input`,{className:`input mono`,value:e.from,placeholder:t(`claude.mapFrom`),onChange:e=>a(t=>t.map((t,r)=>r===n?{...t,from:e.target.value}:t)),style:{flex:1}}),(0,S.jsx)(`span`,{className:`muted`,"aria-hidden":!0,children:`→`}),(0,S.jsx)(`input`,{className:`input mono`,value:e.to,placeholder:t(`claude.mapTo`),onChange:e=>a(t=>t.map((t,r)=>r===n?{...t,to:e.target.value}:t)),style:{flex:1}}),(0,S.jsx)(`button`,{className:`btn btn-ghost btn-icon btn-sm`,onClick:()=>a(e=>e.filter((e,t)=>t!==n)),"aria-label":t(`claude.removeMapping`),style:{color:`var(--red)`},children:(0,S.jsx)(k,{})})]},n))}),(0,S.jsx)(`div`,{style:{marginTop:8},children:(0,S.jsxs)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>a(e=>[...e,{from:``,to:``}]),children:[(0,S.jsx)(re,{}),` `,t(`claude.addMapping`)]})}),(0,S.jsx)(`div`,{style:{marginTop:14},children:(0,S.jsx)(`button`,{className:`btn btn-primary`,onClick:h,children:t(`common.save`)})}),(0,S.jsxs)(`div`,{className:`h-section`,children:[t(`claude.aliases`),` `,(0,S.jsx)(`span`,{className:`count`,children:n.aliases.length})]}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:t(`claude.aliasesHint`)}),n.aliases.length===0?(0,S.jsx)(`div`,{className:`muted`,style:{fontSize:12.5},children:t(`claude.none`)}):(0,S.jsx)(`div`,{className:`stack`,style:{gap:6,maxHeight:320,overflowY:`auto`},children:Array.from(n.aliases.reduce((e,t)=>{let n=/\(([^)]+)\)\s*$/.exec(t.display_name),r=n?n[1]:`etc`;return(e.get(r)??e.set(r,[]).get(r)).push(t),e},new Map)).map(([e,t])=>(0,S.jsxs)(`div`,{children:[(0,S.jsxs)(`div`,{className:`muted`,style:{fontSize:11.5,fontWeight:600,textTransform:`uppercase`,letterSpacing:0,margin:`6px 2px 4px`},children:[e,` · `,t.length]}),(0,S.jsx)(`div`,{className:`stack`,style:{gap:4},children:t.map(e=>(0,S.jsxs)(`div`,{className:`card row`,style:{padding:`6px 12px`,gap:10},children:[(0,S.jsx)(`code`,{className:`mono`,style:{flex:1,fontSize:12},children:e.id}),(0,S.jsx)(`span`,{className:`muted`,style:{fontSize:12},children:e.display_name})]},e.id))})]},e))})]})}var hn=`opencodex-api-token`,gn=!1,_n=null;function vn(e){try{let t=e instanceof Request?e.url:String(e);return new URL(t,window.location.href).pathname}catch{return null}}function yn(e){let t=vn(e);return!!t&&(t.startsWith(`/api/`)||t.startsWith(`/v1/`))}function bn(){try{return sessionStorage.getItem(hn)?.trim()||null}catch{return null}}function xn(e){try{sessionStorage.setItem(hn,e)}catch{}}function Sn(){try{sessionStorage.removeItem(hn)}catch{}}function Cn(e,t,n){let r=new Headers(t?.headers??(e instanceof Request?e.headers:void 0));return r.set(`X-OpenCodex-API-Key`,n),e instanceof Request?[new Request(e,{headers:r}),t?{...t,headers:r}:void 0]:[e,{...t,headers:r}]}async function wn(){return _n||(_n=Promise.resolve().then(()=>window.prompt(`OpenCodex API token`)?.trim()||null).finally(()=>{_n=null}),_n)}function Tn(){if(gn)return;gn=!0;let e=window.fetch.bind(window);window.fetch=async(t,n)=>{if(!yn(t))return e(t,n);let r=bn(),[i,a]=r?Cn(t,n,r):[t,n],o=await e(i,a);if(o.status!==401)return o;r&&Sn();let s=await wn();if(!s)return o;xn(s);let[c,l]=Cn(t,n,s),u=await e(c,l);return u.status===401&&Sn(),u}}Tn();var En=new Set([`dashboard`,`providers`,`models`,`subagents`,`logs`,`debug`,`usage`,`codex-auth`,`api`,`claude`]);function Dn(){let e=location.hash.replace(/^#\/?/,``);return En.has(e)?e:`dashboard`}var On=``,kn=`ocx-theme`,An=[{id:`dashboard`,tkey:`nav.dashboard`,Icon:w},{id:`providers`,tkey:`nav.providers`,Icon:T},{id:`models`,tkey:`nav.models`,Icon:E},{id:`subagents`,tkey:`nav.subagents`,Icon:ee},{id:`logs`,tkey:`nav.logs`,Icon:D},{id:`debug`,tkey:`nav.debug`,Icon:te},{id:`usage`,tkey:`nav.usage`,Icon:ne},{id:`codex-auth`,tkey:`nav.codexAuth`,Icon:ce},{id:`api`,tkey:`nav.api`,Icon:he},{id:`claude`,tkey:`nav.claude`,Icon:ge}],jn={light:fe,dark:pe,system:me},Mn={light:`theme.light`,dark:`theme.dark`,system:`theme.system`};function Nn(e){if(!e||typeof e!=`object`||!(`version`in e))return null;let t=e.version;return typeof t==`string`&&t.length>0?t:null}function Pn(){let e=localStorage.getItem(kn);return e===`light`||e===`dark`?e:`system`}function Fn(){let[e,t]=(0,_.useState)(Dn),[n,r]=(0,_.useState)(Pn),[i,a]=(0,_.useState)(null),{locale:o,setLocale:s}=we(),c=Te();(0,_.useEffect)(()=>{let e=()=>t(Dn());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),(0,_.useEffect)(()=>{let t=`#${e}`;window.location.hash!==t&&(window.location.hash=e)},[e]),(0,_.useEffect)(()=>{let e=document.documentElement;n===`system`?(e.removeAttribute(`data-theme`),localStorage.removeItem(kn)):(e.setAttribute(`data-theme`,n),localStorage.setItem(kn,n))},[n]),(0,_.useEffect)(()=>{let e=!1,t=async()=>{try{let t=await fetch(`${On}/healthz`);if(!t.ok)return;let n=Nn(await t.json());!e&&n&&a(n)}catch{}};t();let n=setInterval(t,3e4);return()=>{e=!0,clearInterval(n)}},[]);let l=()=>r(e=>e===`light`?`dark`:e===`dark`?`system`:`light`),u=jn[n],d=i??`2.7.11`,[f,p]=(0,_.useState)(!1),[m,h]=(0,_.useState)(null);return(0,_.useEffect)(()=>{let e=!1;return fetch(`${On}/api/claude-code`).then(e=>e.json()).then(t=>{!e&&typeof t.enabled==`boolean`&&h(t.enabled)}).catch(()=>{}),()=>{e=!0}},[]),(0,S.jsxs)(`div`,{className:`app`,children:[(0,S.jsxs)(`aside`,{className:`sidebar`,children:[(0,S.jsxs)(`div`,{className:`brand`,children:[(0,S.jsx)(`span`,{className:`brand-logo`,role:`img`,"aria-label":`opencodex logo`}),(0,S.jsx)(`span`,{className:`name`,children:`opencodex`}),(0,S.jsxs)(`span`,{className:`ver`,children:[`v`,d]})]}),(0,S.jsx)(`nav`,{children:An.map(({id:n,tkey:r,Icon:i})=>(0,S.jsxs)(`button`,{className:`nav-item${e===n?` active`:``}`,"data-page":n,onClick:()=>t(n),"aria-current":e===n?`page`:void 0,children:[(0,S.jsx)(i,{}),` `,c(r)]},n))}),(0,S.jsxs)(`div`,{className:`sidebar-foot`,children:[m!==null&&(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:async()=>{if(m===null)return;let e=!m;h(e);try{(await fetch(`${On}/api/claude-code`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({enabled:e})})).ok||h(!e)}catch{h(!e)}},"aria-pressed":m,"aria-label":c(`claude.toggleAria`),title:c(`claude.toggleAria`),style:m?{color:`var(--accent)`}:void 0,children:[(0,S.jsx)(ge,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:m?`Claude ON`:`Claude OFF`})]}),(0,S.jsxs)(`div`,{className:`lang-toggle`,children:[(0,S.jsx)(he,{"aria-hidden":!0}),(0,S.jsx)(Ne,{value:o,options:ye.map(e=>({value:e.code,label:e.name})),onChange:e=>s(e),label:c(`lang.label`),placement:`right`,style:{flex:1,minWidth:0,width:`100%`}})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:l,"aria-label":`${c(`theme.label`)}: ${c(Mn[n])}`,title:`${c(`theme.label`)}: ${c(Mn[n])}`,children:[(0,S.jsx)(u,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:c(Mn[n])})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle stop-toggle`,onClick:async()=>{if(confirm(c(`dash.stopConfirm`))){p(!0);try{await fetch(`${On}/api/stop`,{method:`POST`})}catch{}}},disabled:f,"aria-label":c(`dash.stop`),title:c(`dash.stop`),children:[(0,S.jsx)(I,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:c(f?`dash.stopping`:`dash.stop`)})]}),(0,S.jsxs)(`a`,{className:`sidebar-link`,href:`https://github.com/lidge-jun/opencodex`,target:`_blank`,rel:`noreferrer`,children:[(0,S.jsx)(F,{}),` `,c(`common.github`)]})]})]}),(0,S.jsx)(`main`,{className:`main`,children:(0,S.jsxs)(`div`,{className:`main-inner`,children:[e===`dashboard`&&(0,S.jsx)(Ke,{apiBase:On}),e===`providers`&&(0,S.jsx)(st,{apiBase:On}),e===`models`&&(0,S.jsx)(pt,{apiBase:On}),e===`subagents`&&(0,S.jsx)(mt,{apiBase:On}),e===`logs`&&(0,S.jsx)(Yt,{apiBase:On}),e===`debug`&&(0,S.jsx)(Qt,{apiBase:On}),e===`usage`&&(0,S.jsx)(on,{apiBase:On}),e===`codex-auth`&&(0,S.jsx)(cn,{apiBase:On}),e===`api`&&(0,S.jsx)(pn,{apiBase:On}),e===`claude`&&(0,S.jsx)(mn,{apiBase:On})]})})]})}v.createRoot(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(Ee,{children:(0,S.jsx)(Fn,{})})}));
40
+ `);return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`div`,{className:`page-head`,children:(0,S.jsxs)(`h2`,{children:[t(`nav.claude`),` Code`]})}),(0,S.jsx)(`p`,{className:`page-sub`,children:t(`claude.subtitle`)}),o&&(0,S.jsx)(Me,{tone:c?`ok`:`err`,children:o}),(0,S.jsxs)(`div`,{className:`card`,style:{overflow:`hidden`},children:[(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.enabledLabel`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.enabledHint`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.enabled,onChange:e=>r({...n,enabled:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.systemEnv`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.systemEnvDesc`)}),n.systemEnv&&(0,S.jsx)(`span`,{className:`desc`,style:{color:`var(--red)`},children:t(`claude.systemEnvWarn`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.systemEnv,onChange:e=>r({...n,systemEnv:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.fastMode`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.fastModeDesc`)})]}),(0,S.jsxs)(`select`,{value:n.fastMode===null?`auto`:n.fastMode?`on`:`off`,onChange:e=>{let t=e.target.value;r({...n,fastMode:t===`auto`?null:t===`on`})},style:{padding:`5px 10px`,borderRadius:`var(--radius-xs)`,border:`1px solid var(--border)`,background:`var(--surface)`,color:`var(--text)`,fontSize:12.5,fontWeight:500},children:[(0,S.jsx)(`option`,{value:`auto`,children:t(`claude.fastAuto`)}),(0,S.jsx)(`option`,{value:`on`,children:t(`claude.fastOn`)}),(0,S.jsx)(`option`,{value:`off`,children:t(`claude.fastOff`)})]})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.autoContext`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.autoContextDesc`)}),n.maxContextTokens!==null&&(0,S.jsx)(`span`,{className:`desc`,style:{color:`var(--muted)`},children:t(`claude.autoContextInert`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.autoContext,onChange:e=>r({...n,autoContext:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),n.autoContext&&(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.autoCompactWindow`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.autoCompactWindowDesc`)}),n.autoCompactWindow!==null&&(0,S.jsx)(`span`,{className:`desc`,style:{color:`var(--red)`},children:t(`claude.autoCompactWindowWarn`)})]}),(0,S.jsx)(Ne,{value:n.autoCompactWindow===null?``:String(n.autoCompactWindow),options:m,onChange:e=>r({...n,autoCompactWindow:e===``?null:Number(e)}),label:t(`claude.autoCompactWindow`),style:{minWidth:130}})]}),(0,S.jsxs)(`div`,{className:`setting-row`,children:[(0,S.jsxs)(`div`,{className:`setting-label`,children:[(0,S.jsx)(`span`,{className:`title`,children:t(`claude.injectAgents`)}),(0,S.jsx)(`span`,{className:`desc`,children:t(`claude.injectAgentsDesc`)})]}),(0,S.jsxs)(`label`,{className:`toggle`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:n.injectAgents,onChange:e=>r({...n,injectAgents:e.target.checked})}),(0,S.jsx)(`span`,{className:`slider`})]})]}),[`webSearchSidecar`,`visionSidecar`].map(e=>{let i=n[e],a=e===`webSearchSidecar`?`claude.webSearchSidecar`:`claude.visionSidecar`,o=e===`webSearchSidecar`?`claude.webSearchSidecarHint`:`claude.visionSidecarHint`;return(0,S.jsxs)(`div`,{className:`setting-row`,style:{alignItems:`flex-start`},children:[(0,S.jsxs)(`div`,{className:`setting-label setting-copy`,style:{flex:1},children:[(0,S.jsx)(`span`,{className:`title`,children:t(a)}),(0,S.jsx)(`span`,{className:`desc`,children:t(o)})]}),(0,S.jsxs)(`div`,{className:`setting-controls`,style:{display:`flex`,gap:8},children:[(0,S.jsx)(Ne,{value:i?i.backend??`auto`:`inherit`,options:[{value:`inherit`,label:t(`claude.useMainSetting`)},{value:`auto`,label:t(`dash.backendAuto`)},{value:`openai`,label:t(`dash.backendOpenAI`)},{value:`anthropic`,label:t(`dash.backendAnthropic`)}],onChange:t=>r({...n,[e]:t===`inherit`?void 0:{...i,backend:t===`auto`?void 0:t}}),label:t(`dash.sidecarBackend`)}),(0,S.jsx)(`input`,{className:`input mono`,value:i?.model??``,onChange:t=>r({...n,[e]:{...i,model:t.target.value}}),placeholder:t(`claude.sidecarModelPlaceholder`),disabled:!i,"aria-label":t(`dash.sidecarModel`),style:{minWidth:210}})]})]},e)})]}),(0,S.jsx)(`div`,{className:`h-section`,children:t(`claude.quickstart`)}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:(0,S.jsx)(De,{k:`claude.quickstartHint`,cmd:`ocx claude`})}),(0,S.jsx)(`pre`,{className:`mono card`,style:{padding:`10px 14px`,overflowX:`auto`,margin:0},children:`ocx claude`}),(0,S.jsxs)(`details`,{style:{margin:`10px 0 0`},children:[(0,S.jsx)(`summary`,{className:`muted`,style:{fontSize:12.5,cursor:`pointer`,padding:`2px 2px`},children:t(`claude.manualEnv`)}),(0,S.jsx)(`pre`,{className:`mono card`,style:{padding:`10px 14px`,overflowX:`auto`,margin:`6px 0 0`,fontSize:12},children:b})]}),(0,S.jsx)(`div`,{className:`h-section`,children:t(`claude.smallFastModel`)}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:t(`claude.smallFastModelHint`)}),(0,S.jsx)(Ne,{value:n.smallFastModel,options:p,onChange:e=>r({...n,smallFastModel:e}),label:t(`claude.smallFastModel`),style:{maxWidth:420}}),(0,S.jsxs)(`div`,{className:`h-section`,children:[t(`claude.modelMap`),` `,(0,S.jsx)(`span`,{className:`count`,children:i.length})]}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:t(`claude.modelMapHint`)}),(0,S.jsx)(`div`,{className:`stack`,style:{gap:8},children:i.map((e,n)=>(0,S.jsxs)(`div`,{className:`row`,style:{gap:8},children:[(0,S.jsx)(`input`,{className:`input mono`,value:e.from,placeholder:t(`claude.mapFrom`),onChange:e=>a(t=>t.map((t,r)=>r===n?{...t,from:e.target.value}:t)),style:{flex:1}}),(0,S.jsx)(`span`,{className:`muted`,"aria-hidden":!0,children:`→`}),(0,S.jsx)(`input`,{className:`input mono`,value:e.to,placeholder:t(`claude.mapTo`),onChange:e=>a(t=>t.map((t,r)=>r===n?{...t,to:e.target.value}:t)),style:{flex:1}}),(0,S.jsx)(`button`,{className:`btn btn-ghost btn-icon btn-sm`,onClick:()=>a(e=>e.filter((e,t)=>t!==n)),"aria-label":t(`claude.removeMapping`),style:{color:`var(--red)`},children:(0,S.jsx)(k,{})})]},n))}),(0,S.jsx)(`div`,{style:{marginTop:8},children:(0,S.jsxs)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>a(e=>[...e,{from:``,to:``}]),children:[(0,S.jsx)(re,{}),` `,t(`claude.addMapping`)]})}),(0,S.jsx)(`div`,{style:{marginTop:14},children:(0,S.jsx)(`button`,{className:`btn btn-primary`,onClick:h,children:t(`common.save`)})}),(0,S.jsxs)(`div`,{className:`h-section`,children:[t(`claude.aliases`),` `,(0,S.jsx)(`span`,{className:`count`,children:n.aliases.length})]}),(0,S.jsx)(`p`,{className:`muted`,style:{fontSize:12.5,margin:`0 0 8px`},children:t(`claude.aliasesHint`)}),n.aliases.length===0?(0,S.jsx)(`div`,{className:`muted`,style:{fontSize:12.5},children:t(`claude.none`)}):(0,S.jsx)(`div`,{className:`stack`,style:{gap:6,maxHeight:320,overflowY:`auto`},children:Array.from(n.aliases.reduce((e,t)=>{let n=/\(([^)]+)\)\s*$/.exec(t.display_name),r=n?n[1]:`etc`;return(e.get(r)??e.set(r,[]).get(r)).push(t),e},new Map)).map(([e,t])=>(0,S.jsxs)(`div`,{children:[(0,S.jsxs)(`div`,{className:`muted`,style:{fontSize:11.5,fontWeight:600,textTransform:`uppercase`,letterSpacing:0,margin:`6px 2px 4px`},children:[e,` · `,t.length]}),(0,S.jsx)(`div`,{className:`stack`,style:{gap:4},children:t.map(e=>(0,S.jsxs)(`div`,{className:`card row`,style:{padding:`6px 12px`,gap:10},children:[(0,S.jsx)(`code`,{className:`mono`,style:{flex:1,fontSize:12},children:e.id}),(0,S.jsx)(`span`,{className:`muted`,style:{fontSize:12},children:e.display_name})]},e.id))})]},e))})]})}var hn=`opencodex-api-token`,gn=!1,_n=null;function vn(e){try{let t=e instanceof Request?e.url:String(e);return new URL(t,window.location.href).pathname}catch{return null}}function yn(e){let t=vn(e);return!!t&&(t.startsWith(`/api/`)||t.startsWith(`/v1/`))}function bn(){try{return sessionStorage.getItem(hn)?.trim()||null}catch{return null}}function xn(e){try{sessionStorage.setItem(hn,e)}catch{}}function Sn(){try{sessionStorage.removeItem(hn)}catch{}}function Cn(e,t,n){let r=new Headers(t?.headers??(e instanceof Request?e.headers:void 0));return r.set(`X-OpenCodex-API-Key`,n),e instanceof Request?[new Request(e,{headers:r}),t?{...t,headers:r}:void 0]:[e,{...t,headers:r}]}async function wn(){return _n||(_n=Promise.resolve().then(()=>window.prompt(`OpenCodex API token`)?.trim()||null).finally(()=>{_n=null}),_n)}function Tn(){if(gn)return;gn=!0;let e=window.fetch.bind(window);window.fetch=async(t,n)=>{if(!yn(t))return e(t,n);let r=bn(),[i,a]=r?Cn(t,n,r):[t,n],o=await e(i,a);if(o.status!==401)return o;r&&Sn();let s=await wn();if(!s)return o;xn(s);let[c,l]=Cn(t,n,s),u=await e(c,l);return u.status===401&&Sn(),u}}Tn();var En=new Set([`dashboard`,`providers`,`models`,`subagents`,`logs`,`debug`,`usage`,`codex-auth`,`api`,`claude`]);function Dn(){let e=location.hash.replace(/^#\/?/,``);return En.has(e)?e:`dashboard`}var On=``,kn=`ocx-theme`,An=[{id:`dashboard`,tkey:`nav.dashboard`,Icon:w},{id:`providers`,tkey:`nav.providers`,Icon:T},{id:`models`,tkey:`nav.models`,Icon:E},{id:`subagents`,tkey:`nav.subagents`,Icon:ee},{id:`logs`,tkey:`nav.logs`,Icon:D},{id:`debug`,tkey:`nav.debug`,Icon:te},{id:`usage`,tkey:`nav.usage`,Icon:ne},{id:`codex-auth`,tkey:`nav.codexAuth`,Icon:ce},{id:`api`,tkey:`nav.api`,Icon:he},{id:`claude`,tkey:`nav.claude`,Icon:ge}],jn={light:fe,dark:pe,system:me},Mn={light:`theme.light`,dark:`theme.dark`,system:`theme.system`};function Nn(e){if(!e||typeof e!=`object`||!(`version`in e))return null;let t=e.version;return typeof t==`string`&&t.length>0?t:null}function Pn(){let e=localStorage.getItem(kn);return e===`light`||e===`dark`?e:`system`}function Fn(){let[e,t]=(0,_.useState)(Dn),[n,r]=(0,_.useState)(Pn),[i,a]=(0,_.useState)(null),{locale:o,setLocale:s}=we(),c=Te();(0,_.useEffect)(()=>{let e=()=>t(Dn());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),(0,_.useEffect)(()=>{let t=`#${e}`;window.location.hash!==t&&(window.location.hash=e)},[e]),(0,_.useEffect)(()=>{let e=document.documentElement;n===`system`?(e.removeAttribute(`data-theme`),localStorage.removeItem(kn)):(e.setAttribute(`data-theme`,n),localStorage.setItem(kn,n))},[n]),(0,_.useEffect)(()=>{let e=!1,t=async()=>{try{let t=await fetch(`${On}/healthz`);if(!t.ok)return;let n=Nn(await t.json());!e&&n&&a(n)}catch{}};t();let n=setInterval(t,3e4);return()=>{e=!0,clearInterval(n)}},[]);let l=()=>r(e=>e===`light`?`dark`:e===`dark`?`system`:`light`),u=jn[n],d=i??`2.7.13`,[f,p]=(0,_.useState)(!1),[m,h]=(0,_.useState)(null);return(0,_.useEffect)(()=>{let e=!1;return fetch(`${On}/api/claude-code`).then(e=>e.json()).then(t=>{!e&&typeof t.enabled==`boolean`&&h(t.enabled)}).catch(()=>{}),()=>{e=!0}},[]),(0,S.jsxs)(`div`,{className:`app`,children:[(0,S.jsxs)(`aside`,{className:`sidebar`,children:[(0,S.jsxs)(`div`,{className:`brand`,children:[(0,S.jsx)(`span`,{className:`brand-logo`,role:`img`,"aria-label":`opencodex logo`}),(0,S.jsx)(`span`,{className:`name`,children:`opencodex`}),(0,S.jsxs)(`span`,{className:`ver`,children:[`v`,d]})]}),(0,S.jsx)(`nav`,{children:An.map(({id:n,tkey:r,Icon:i})=>(0,S.jsxs)(`button`,{className:`nav-item${e===n?` active`:``}`,"data-page":n,onClick:()=>t(n),"aria-current":e===n?`page`:void 0,children:[(0,S.jsx)(i,{}),` `,c(r)]},n))}),(0,S.jsxs)(`div`,{className:`sidebar-foot`,children:[m!==null&&(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:async()=>{if(m===null)return;let e=!m;h(e);try{(await fetch(`${On}/api/claude-code`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({enabled:e})})).ok||h(!e)}catch{h(!e)}},"aria-pressed":m,"aria-label":c(`claude.toggleAria`),title:c(`claude.toggleAria`),style:m?{color:`var(--accent)`}:void 0,children:[(0,S.jsx)(ge,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:m?`Claude ON`:`Claude OFF`})]}),(0,S.jsxs)(`div`,{className:`lang-toggle`,children:[(0,S.jsx)(he,{"aria-hidden":!0}),(0,S.jsx)(Ne,{value:o,options:ye.map(e=>({value:e.code,label:e.name})),onChange:e=>s(e),label:c(`lang.label`),placement:`right`,style:{flex:1,minWidth:0,width:`100%`}})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:l,"aria-label":`${c(`theme.label`)}: ${c(Mn[n])}`,title:`${c(`theme.label`)}: ${c(Mn[n])}`,children:[(0,S.jsx)(u,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:c(Mn[n])})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle stop-toggle`,onClick:async()=>{if(confirm(c(`dash.stopConfirm`))){p(!0);try{await fetch(`${On}/api/stop`,{method:`POST`})}catch{}}},disabled:f,"aria-label":c(`dash.stop`),title:c(`dash.stop`),children:[(0,S.jsx)(I,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:c(f?`dash.stopping`:`dash.stop`)})]}),(0,S.jsxs)(`a`,{className:`sidebar-link`,href:`https://github.com/lidge-jun/opencodex`,target:`_blank`,rel:`noreferrer`,children:[(0,S.jsx)(F,{}),` `,c(`common.github`)]})]})]}),(0,S.jsx)(`main`,{className:`main`,children:(0,S.jsxs)(`div`,{className:`main-inner`,children:[e===`dashboard`&&(0,S.jsx)(Ke,{apiBase:On}),e===`providers`&&(0,S.jsx)(st,{apiBase:On}),e===`models`&&(0,S.jsx)(pt,{apiBase:On}),e===`subagents`&&(0,S.jsx)(mt,{apiBase:On}),e===`logs`&&(0,S.jsx)(Yt,{apiBase:On}),e===`debug`&&(0,S.jsx)(Qt,{apiBase:On}),e===`usage`&&(0,S.jsx)(on,{apiBase:On}),e===`codex-auth`&&(0,S.jsx)(cn,{apiBase:On}),e===`api`&&(0,S.jsx)(pn,{apiBase:On}),e===`claude`&&(0,S.jsx)(mn,{apiBase:On})]})})]})}v.createRoot(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(Ee,{children:(0,S.jsx)(Fn,{})})}));
@@ -16,7 +16,7 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-Bx1J-IaA.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-BNySqP9I.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-Cq8maiJf.css">
21
21
  </head>
22
22
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.7.11",
3
+ "version": "2.7.13",
4
4
  "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -1,4 +1,4 @@
1
- import { cursorModelEffortLadder } from "./effort-map";
1
+ import { CANONICAL_EFFORT_SUFFIXES, cursorModelEffortLadder } from "./effort-map";
2
2
 
3
3
  export interface CursorModelInfo {
4
4
  id: string;
@@ -55,18 +55,26 @@ export function normalizeCursorModels(models: readonly CursorModelInfo[]): Curso
55
55
  return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
56
56
  }
57
57
 
58
- // Live GetUsableModels ids append effort tiers to the base id (`claude-4.6-opus-high`). Only
59
- // these suffixes may activate a base model otherwise a sibling model like `claude-4-sonnet-1m`
60
- // would falsely activate `claude-4-sonnet` (PR #73 review finding).
61
- const LIVE_EFFORT_SUFFIXES = ["low", "medium", "high", "xhigh", "max"] as const;
58
+ /**
59
+ * Strip the `cursor-` wire prefix that some Cursor GetUsableModels responses prepend to model ids
60
+ * (e.g. `cursor-grok-4.5-high` instead of `grok-4.5-high`). Applied at the comparison boundary
61
+ * so upstream wire ids stay verbatim everywhere else (issue #117).
62
+ */
63
+ function stripCursorWirePrefix(id: string): string {
64
+ return id.startsWith("cursor-") ? id.slice("cursor-".length) : id;
65
+ }
62
66
 
63
67
  /**
64
68
  * True when a configured Cursor base model should remain exposed after live GetUsableModels filtering.
65
69
  * Live ids are full effort-suffixed variants (`claude-4.6-opus-high`); base ids match exactly or by prefix.
66
70
  */
67
71
  export function isCursorModelAvailableForAccount(modelId: string, liveIds: readonly string[]): boolean {
68
- return liveIds.some(id =>
69
- id === modelId || LIVE_EFFORT_SUFFIXES.some(suffix => id === `${modelId}-${suffix}`));
72
+ return liveIds.some(raw => {
73
+ const id = stripCursorWirePrefix(raw);
74
+ if (id === modelId) return true;
75
+ const effortPrefix = `${modelId}-`;
76
+ return id.startsWith(effortPrefix) && CANONICAL_EFFORT_SUFFIXES.has(id.slice(effortPrefix.length));
77
+ });
70
78
  }
71
79
 
72
80
  /** Codex-facing id for Cursor's auto-router. Always kept in the catalog even when live discovery omits it. */
@@ -46,6 +46,12 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
46
46
  "gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
47
47
  };
48
48
 
49
+ /** All effort suffixes accepted when matching live Cursor model ids to configured base ids. */
50
+ export const CANONICAL_EFFORT_SUFFIXES: ReadonlySet<string> = new Set([
51
+ "none",
52
+ ...Object.values(CURSOR_MODEL_EFFORT_TIERS).flat(),
53
+ ]);
54
+
49
55
  const CANONICAL_CODEX_EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"] as const;
50
56
 
51
57
  function normalizeRequestedEffort(reasoning: string | undefined): string | undefined {
@@ -94,9 +94,17 @@ export async function fetchCursorUsableModels(opts: CursorUsableModelsOptions):
94
94
  const response = fromBinary(GetUsableModelsResponseSchema, new Uint8Array(Buffer.concat(chunks)));
95
95
  // Account filtering uses wire `model_id` values only. Aliases like `composer-2-5` must not
96
96
  // make stale configured ids such as `composer-2` look activated.
97
- const ids = (response.models ?? [])
98
- .map(model => (model as { modelId?: string }).modelId)
99
- .filter((id): id is string => typeof id === "string" && id.length > 0);
97
+ const ids: string[] = [];
98
+ const seenIds = new Set<string>();
99
+ for (const model of response.models ?? []) {
100
+ const rawId = (model as { modelId?: string }).modelId;
101
+ if (typeof rawId !== "string") continue;
102
+ const id = rawId.trim();
103
+ if (id.length === 0 || /[\x00-\x1f]/.test(id) || seenIds.has(id)) continue;
104
+ seenIds.add(id);
105
+ ids.push(id);
106
+ if (ids.length === 500) break;
107
+ }
100
108
  close(ids.length > 0 ? { ok: true, models: ids } : { ok: false, error: "empty" });
101
109
  } catch {
102
110
  close({ ok: false, error: "decode", detail: "Invalid GetUsableModels protobuf response" });
@@ -269,9 +269,21 @@ export function planInteractionQueryReply(query: InteractionQuery): { response:
269
269
  };
270
270
  }
271
271
  if (q.case === "setupVmEnvironmentArgs") {
272
- throw new Error("Cursor setupVmEnvironment is not supported by opencodex");
272
+ // setupVmEnvironment is not supported reply with an empty InteractionResponse so the stream
273
+ // stays alive instead of throwing (which kills the entire gRPC connection via failAndClear).
274
+ return {
275
+ response: respond({ case: undefined, value: undefined }),
276
+ replyCase: "unsupported:setupVmEnvironment",
277
+ };
273
278
  }
274
- throw new Error(`Unsupported Cursor interaction query case: ${q.case ?? "unknown"}`);
279
+ // Unknown interaction query case — Cursor added a new query type that our protobuf definition
280
+ // does not include yet. Gracefully reply with an empty InteractionResponse (matching id, no
281
+ // result) so the server unblocks and the stream stays alive. Previously this threw, which
282
+ // propagated through .catch → failAndClear and killed the entire connection (#116).
283
+ return {
284
+ response: respond({ case: undefined, value: undefined }),
285
+ replyCase: `unsupported:${q.case ?? "unknown"}`,
286
+ };
275
287
  }
276
288
 
277
289
  /**
@@ -28,6 +28,7 @@ import {
28
28
  rejectWriteExecForPolicy,
29
29
  writeExec,
30
30
  } from "./native-exec-fs";
31
+ import { debugProviderDiagnostic } from "../../lib/debug";
31
32
  import { fetchExec, rejectFetchExecForPolicy, type CursorNativeNetworkDeps } from "./native-exec-network";
32
33
  import {
33
34
  backgroundShellSpawnExec,
@@ -190,7 +191,11 @@ export async function handleCursorNativeExec(execMsg: ExecServerMessage, deps: C
190
191
  },
191
192
  }))];
192
193
  }
193
- throw new Error(`Unsupported Cursor native exec case: ${execCase ?? "unknown"}`);
194
+ // Unknown exec case — Cursor added a new native exec type that our protobuf definition does not
195
+ // include yet. Return an empty reply so the stream stays alive instead of throwing (which kills
196
+ // the entire gRPC connection via failAndClear). Same class of bug as #116.
197
+ debugProviderDiagnostic("cursor", "unknown-exec-case", { execCase: execCase ?? "unknown", execId: execMsg.execId });
198
+ return [];
194
199
  }
195
200
 
196
201