@bitkyc08/opencodex 2.7.12 → 2.7.17

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.12`,[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.17`,[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-B19F0Ren.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-m4o3xsSn.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.12",
3
+ "version": "2.7.17",
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",
@@ -51,4 +51,6 @@ export interface AdapterFetchContext {
51
51
  timeoutMs?: number;
52
52
  /** Return final non-2xx responses untouched so the caller can own the error-body read. */
53
53
  returnRawErrors?: boolean;
54
+ /** Whether the upstream response will be consumed as a stream; adapters may select low-latency transport settings. */
55
+ stream?: boolean;
54
56
  }
@@ -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 {
@@ -10,7 +10,7 @@ export const CURSOR_SANDBOX_FULL_ACCESS_RE = /sandbox_mode[^\n]{0,80}danger-full
10
10
  export function resolveCursorNativeExecMode(provider: OcxProviderConfig): CursorNativeExecMode {
11
11
  const mode = provider.nativeLocalExec;
12
12
  if (mode === "off" || mode === "codex-sandbox" || mode === "on") return mode;
13
- return provider.unsafeAllowNativeLocalExec === true ? "on" : "off";
13
+ return provider.unsafeAllowNativeLocalExec === true ? "on" : "codex-sandbox";
14
14
  }
15
15
 
16
16
  /**
@@ -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" });
@@ -44,7 +44,7 @@ function codexNativeMutationRefusal(operation: "write" | "delete"): string {
44
44
  }
45
45
 
46
46
  const NATIVE_LOCAL_EXEC_DISABLED =
47
- "Cursor native local filesystem execution is disabled by default because it bypasses Codex approval and sandbox enforcement. Set provider.unsafeAllowNativeLocalExec=true only for trusted local experiments that may read or mutate local files directly.";
47
+ "Cursor native local filesystem execution is not available for this request. Use the exec_command tool with equivalent shell commands (cat, head, ls, rg, grep) for file reads and searches, or apply_patch for file edits.";
48
48
 
49
49
  export function rejectReadExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
50
50
  if (execMsg.message.case !== "readArgs") throw new Error("invalid read exec");
@@ -7,7 +7,7 @@ export interface CursorNativeNetworkDeps {
7
7
  }
8
8
 
9
9
  const NATIVE_FETCH_DISABLED =
10
- "Cursor native fetch execution is disabled by default because it bypasses Codex approval and sandbox enforcement. Set provider.unsafeAllowNativeLocalExec=true only for trusted local experiments that may make local network requests directly.";
10
+ "Cursor native fetch execution is not available for this request. Use the exec_command tool with curl or wget to make network requests instead.";
11
11
 
12
12
  export function rejectFetchExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
13
13
  if (execMsg.message.case !== "fetchArgs") throw new Error("invalid fetch exec");
@@ -24,7 +24,7 @@ const backgroundShells = new Map<number, { child: ChildProcessWithoutNullStreams
24
24
  let nextShellId = 1;
25
25
 
26
26
  const NATIVE_SHELL_DISABLED =
27
- "Cursor native shell execution is disabled by default because it bypasses Codex approval and sandbox enforcement. Set provider.unsafeAllowNativeLocalExec=true only for trusted local experiments that may run local commands directly.";
27
+ "Cursor native shell execution is not available for this request. Use the exec_command tool to run shell commands instead.";
28
28
 
29
29
  function rejectedShellResult(command: string, cwd: string, started: number) {
30
30
  return create(ShellResultSchema, {
@@ -272,6 +272,9 @@ export function buildCursorToolGuidanceSystemNote(
272
272
  ? `Use ${discoveryTools} only for explicit discovery/resource tasks, not generic tool-count demos.`
273
273
  : undefined,
274
274
  "Do not count or report a tool call unless a tool result was actually returned.",
275
+ hasBareExec
276
+ ? "If a built-in file read, directory listing, grep, or shell operation is rejected by the runtime, use \`exec_command\` with the equivalent shell command instead (e.g. \`cat\`, \`ls\`, \`rg\`, \`grep\`). For file edits, use \`apply_patch\` when available."
277
+ : undefined,
275
278
  ].filter((note): note is string => typeof note === "string");
276
279
  return notes.join(" ");
277
280
  }
@@ -36,7 +36,7 @@ export async function fetchGoogleWithRetry(label: string, request: AdapterReques
36
36
  method: request.method,
37
37
  headers: request.headers,
38
38
  body: request.body,
39
- }, timeoutMs, ctx.abortSignal);
39
+ }, timeoutMs, ctx.abortSignal, ctx.stream);
40
40
  if (!retryableGoogleStatus(res.status) || attempt === GOOGLE_RETRY_ATTEMPTS - 1) {
41
41
  return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
42
42
  }
@@ -39,7 +39,7 @@ export async function fetchKiroWithRetry(request: AdapterRequest, ctx: AdapterFe
39
39
  method: request.method,
40
40
  headers: request.headers,
41
41
  body: request.body,
42
- }, timeoutMs, ctx.abortSignal);
42
+ }, timeoutMs, ctx.abortSignal, ctx.stream);
43
43
  if (!retryableKiroStatus(res.status) || attempt === KIRO_RETRY_ATTEMPTS - 1) {
44
44
  return ctx.returnRawErrors ? res : normalizeFinalKiroHttpError(res, ctx.abortSignal);
45
45
  }
@@ -125,7 +125,49 @@ function safeToolName(name: string | undefined): string {
125
125
  return sanitized;
126
126
  }
127
127
 
128
- function toolsToChatFormat(parsed: OcxParsedRequest): unknown[] | undefined {
128
+ const XAI_SCHEMA_BASE_URLS = new Set(["api.x.ai", "cli-chat-proxy.grok.com"]);
129
+
130
+ function isXaiSchemaTarget(provider: OcxProviderConfig): boolean {
131
+ try {
132
+ return XAI_SCHEMA_BASE_URLS.has(new URL(provider.baseUrl).hostname);
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ function expandXaiRootObjectSchemas(schema: unknown): Record<string, unknown>[] | undefined {
139
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return undefined;
140
+ const obj = schema as Record<string, unknown>;
141
+ const compositionKey = ["oneOf", "anyOf"].find(key => Array.isArray(obj[key]));
142
+ if (!compositionKey) {
143
+ if (obj.type !== undefined && obj.type !== "object") return undefined;
144
+ return [{ ...obj, type: "object" }];
145
+ }
146
+
147
+ const siblings = Object.fromEntries(Object.entries(obj).filter(([key]) => key !== compositionKey));
148
+ const branches = obj[compositionKey];
149
+ if (!Array.isArray(branches)) return undefined;
150
+ const expanded: Record<string, unknown>[] = [];
151
+ for (const branch of branches) {
152
+ const variants = expandXaiRootObjectSchemas(branch);
153
+ if (!variants) return undefined;
154
+ for (const variant of variants) expanded.push({ ...siblings, ...variant });
155
+ }
156
+ return expanded.length > 0 ? expanded : undefined;
157
+ }
158
+
159
+ function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown> | undefined {
160
+ const variants = expandXaiRootObjectSchemas(parameters);
161
+ if (!variants) return undefined;
162
+ if (variants.length === 1) return variants[0];
163
+ const root = parameters && typeof parameters === "object" && !Array.isArray(parameters)
164
+ ? parameters as Record<string, unknown>
165
+ : {};
166
+ const metadata = Object.fromEntries(Object.entries(root).filter(([key]) => key !== "oneOf" && key !== "anyOf" && key !== "type"));
167
+ return { ...metadata, oneOf: variants };
168
+ }
169
+
170
+ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig): unknown[] | undefined {
129
171
  if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
130
172
  const allowed = isAllowedToolChoice(parsed.options.toolChoice)
131
173
  ? new Set(parsed.options.toolChoice.allowedTools)
@@ -134,15 +176,21 @@ function toolsToChatFormat(parsed: OcxParsedRequest): unknown[] | undefined {
134
176
  ? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
135
177
  : parsed.context.tools;
136
178
  if (tools.length === 0) return undefined;
137
- return tools.map(t => ({
179
+ const xaiTarget = isXaiSchemaTarget(provider);
180
+ const formatted = tools.flatMap(t => {
181
+ const parameters = xaiTarget ? normalizeXaiToolParameters(t.parameters) : t.parameters;
182
+ if (parameters === undefined) return [];
183
+ return [{
138
184
  type: "function",
139
185
  function: {
140
186
  name: namespacedToolName(t.namespace, t.name),
141
187
  description: t.description,
142
- parameters: t.parameters,
188
+ parameters,
143
189
  ...(t.strict !== undefined ? { strict: t.strict } : {}),
144
190
  },
145
- }));
191
+ }];
192
+ });
193
+ return formatted.length > 0 ? formatted : undefined;
146
194
  }
147
195
 
148
196
  function toolChoiceToChatFormat(tc: OcxParsedRequest["options"]["toolChoice"], tools: OcxParsedRequest["context"]["tools"]): unknown {
@@ -190,7 +238,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
190
238
  }
191
239
 
192
240
  const messages = messagesToChatFormat(parsed, provider);
193
- const tools = toolsToChatFormat(parsed);
241
+ const tools = toolsToChatFormat(parsed, provider);
194
242
  const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools);
195
243
 
196
244
  const body: Record<string, unknown> = {
@@ -52,6 +52,55 @@ function sanitizeReasoningInputContent(body: unknown): unknown {
52
52
  return changed ? { ...raw, input } : body;
53
53
  }
54
54
 
55
+ function stripInvalidItemIds(body: unknown): unknown {
56
+ if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
57
+
58
+ const validPrefixes: Record<string, string> = {
59
+ message: "msg_",
60
+ reasoning: "rs_",
61
+ function_call: "fc_",
62
+ custom_tool_call: "ctc_",
63
+ tool_search_call: "tsc_",
64
+ web_search_call: "ws_",
65
+ };
66
+ let changed = false;
67
+ const input = body.input.map(item => {
68
+ if (!isPlainObject(item) || typeof item.type !== "string") return item;
69
+ const validPrefix = validPrefixes[item.type];
70
+ if (!validPrefix) return item;
71
+ if (typeof item.id === "string" && item.id.startsWith(validPrefix)) return item;
72
+ if (!("id" in item)) return item;
73
+ changed = true;
74
+ const next = { ...item };
75
+ delete next.id;
76
+ return next;
77
+ });
78
+
79
+ return changed ? { ...body, input } : body;
80
+ }
81
+
82
+ /**
83
+ * When `store` is false, the upstream API does not persist response items. Any item ID
84
+ * forwarded in `input` is then interpreted as a reference to a stored item that does not
85
+ * exist, producing a 404. Strip all item IDs in this case — `call_id` pairing is unaffected.
86
+ * Matches codex-rs behavior (core/src/client.rs:918-925).
87
+ */
88
+ function stripItemIdsWhenUnstored(body: unknown): unknown {
89
+ if (!isPlainObject(body) || body.store !== false) return body;
90
+ if (!Array.isArray(body.input)) return body;
91
+
92
+ let changed = false;
93
+ const input = body.input.map(item => {
94
+ if (!isPlainObject(item) || !("id" in item)) return item;
95
+ changed = true;
96
+ const next = { ...item };
97
+ delete next.id;
98
+ return next;
99
+ });
100
+
101
+ return changed ? { ...body, input } : body;
102
+ }
103
+
55
104
  /**
56
105
  * Replace proxy-minted compaction items (`encrypted_content` starting with `ocx1:`) with plain
57
106
  * user messages before forwarding to the ChatGPT backend. Our envelope is transparent base64, not
@@ -282,7 +331,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
282
331
  url,
283
332
  method: "POST",
284
333
  headers,
285
- body: JSON.stringify(stripUnsupportedHostedTools(sanitizeReasoningInputContent(scrubOcxCompactionItems(outBody)))),
334
+ body: JSON.stringify(stripItemIdsWhenUnstored(stripInvalidItemIds(stripUnsupportedHostedTools(sanitizeReasoningInputContent(scrubOcxCompactionItems(outBody)))))),
286
335
  };
287
336
  },
288
337
 
package/src/bridge.ts CHANGED
@@ -146,11 +146,13 @@ export function bridgeToResponsesSSE(
146
146
 
147
147
  return new ReadableStream<Uint8Array>({
148
148
  async start(controller) {
149
+ let emittedSinceYield = false;
149
150
  const emit = (name: string, data: Record<string, unknown>) => {
150
151
  if (closed) return;
151
152
  activity = true;
152
153
  try {
153
154
  controller.enqueue(encoder.encode(sseEvent(name, { type: name, sequence_number: seq++, ...data })));
155
+ emittedSinceYield = true;
154
156
  } catch {
155
157
  closed = true;
156
158
  }
@@ -264,7 +266,7 @@ export function bridgeToResponsesSSE(
264
266
  let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string } | null = null;
265
267
  // Open native web-search cell (between begin and end). Holds the output index allocated on
266
268
  // begin so the matching done reuses it; closed as `failed` if the stream terminates early.
267
- let currentWebSearch: { itemId: string; outputIndex: number } | null = null;
269
+ let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
268
270
  // Sources from completed web searches, awaiting the next assistant message. Attached as
269
271
  // url_citation annotations on that message (the desktop app's Sources chip), then cleared so
270
272
  // they bind to exactly one message. Deduped by URL across multiple searches in the turn.
@@ -399,9 +401,19 @@ export function bridgeToResponsesSSE(
399
401
  // we synthesize response.completed below, so Codex never hits the parser's
400
402
  // "stream closed before response.completed" (responses.rs) -> ApiError::Stream.
401
403
  let terminated = false;
404
+ let macrotaskFired = true;
405
+ let macrotaskTimer: ReturnType<typeof setTimeout> | undefined;
402
406
 
403
407
  try {
404
408
  for await (const event of events) {
409
+ if (!macrotaskFired && emittedSinceYield) {
410
+ await new Promise<void>(r => setTimeout(r, 0));
411
+ macrotaskFired = true;
412
+ }
413
+ emittedSinceYield = false;
414
+ macrotaskFired = false;
415
+ if (macrotaskTimer !== undefined) clearTimeout(macrotaskTimer);
416
+ macrotaskTimer = setTimeout(() => { macrotaskFired = true; macrotaskTimer = undefined; }, 0);
405
417
  activity = true;
406
418
  stallTicks = 0;
407
419
  // Compaction turns emit ONLY the synthetic compaction item + response.completed. The
@@ -498,12 +510,12 @@ export function bridgeToResponsesSSE(
498
510
  if (currentRawReasoning) closeCurrentRawReasoning();
499
511
  flushHiddenRawReasoning();
500
512
  if (currentToolCall) closeCurrentToolCall();
501
- const itemId = `fc_${uuid()}`;
502
513
  const mapped = toolNsMap?.get(event.name);
503
514
  const realName = mapped?.name ?? event.name;
504
515
  const ns = mapped?.namespace;
505
516
  const toolSearch = toolSearchToolNames?.has(realName) ?? false;
506
517
  const freeform = !toolSearch && (freeformToolNames?.has(realName) ?? false);
518
+ const itemId = `${toolSearch ? "tsc" : freeform ? "ctc" : "fc"}_${uuid()}`;
507
519
  const item = toolSearch
508
520
  ? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
509
521
  : freeform
@@ -554,23 +566,25 @@ export function bridgeToResponsesSSE(
554
566
  flushHiddenRawReasoning();
555
567
  if (currentToolCall) closeCurrentToolCall();
556
568
  if (currentWebSearch) closeCurrentWebSearch("completed", []);
569
+ const wsItemId = `ws_${uuid()}`;
557
570
  emit("response.output_item.added", {
558
571
  output_index: outputIndex,
559
- item: { type: "web_search_call", id: event.id, status: "in_progress" },
572
+ item: { type: "web_search_call", id: wsItemId, status: "in_progress" },
560
573
  });
561
- currentWebSearch = { itemId: event.id, outputIndex };
574
+ currentWebSearch = { itemId: wsItemId, eventId: event.id, outputIndex };
562
575
  break;
563
576
  }
564
577
  case "web_search_call_end": {
565
578
  // The sidecar resolved — finalize the cell as "Searched <query>". If no begin opened
566
579
  // (defensive), synthesize the added frame first so the done has a matching item.
567
- if (!currentWebSearch || currentWebSearch.itemId !== event.id) {
580
+ if (!currentWebSearch || currentWebSearch.eventId !== event.id) {
568
581
  if (currentWebSearch) closeCurrentWebSearch("completed", []);
582
+ const wsItemId2 = `ws_${uuid()}`;
569
583
  emit("response.output_item.added", {
570
584
  output_index: outputIndex,
571
- item: { type: "web_search_call", id: event.id, status: "in_progress" },
585
+ item: { type: "web_search_call", id: wsItemId2, status: "in_progress" },
572
586
  });
573
- currentWebSearch = { itemId: event.id, outputIndex };
587
+ currentWebSearch = { itemId: wsItemId2, eventId: event.id, outputIndex };
574
588
  }
575
589
  closeCurrentWebSearch(event.status ?? "completed", event.queries, event.sources);
576
590
  // Queue this search's sources for the next assistant message (dedup by URL).
@@ -650,6 +664,7 @@ export function bridgeToResponsesSSE(
650
664
  }
651
665
 
652
666
  if (beat) clearInterval(beat);
667
+ if (macrotaskTimer !== undefined) clearTimeout(macrotaskTimer);
653
668
 
654
669
  if (!terminated) {
655
670
  // The adapter generator ended without an explicit done/error event. Mark as incomplete
@@ -782,13 +797,13 @@ export function buildResponseJSON(
782
797
  const freeform = !toolSearch && (options?.freeformToolNames?.has(realName) ?? false);
783
798
  if (toolSearch) {
784
799
  output.push({
785
- type: "tool_search_call", id: `fc_${uuid()}`,
800
+ type: "tool_search_call", id: `tsc_${uuid()}`,
786
801
  call_id: currentToolCallId, execution: "client",
787
802
  arguments: parseArgsObj(currentToolCallArgs), status: "completed",
788
803
  });
789
804
  } else if (freeform) {
790
805
  output.push({
791
- type: "custom_tool_call", id: `fc_${uuid()}`,
806
+ type: "custom_tool_call", id: `ctc_${uuid()}`,
792
807
  call_id: currentToolCallId, name: realName,
793
808
  input: freeformInput(currentToolCallArgs), status: "completed",
794
809
  });
@@ -862,7 +877,7 @@ export function buildResponseJSON(
862
877
  if (currentRawReasoning) flushRawReasoning();
863
878
  flushToolCall();
864
879
  output.push({
865
- type: "web_search_call", id: e.id, status: e.status ?? "completed",
880
+ type: "web_search_call", id: `ws_${uuid()}`, status: e.status ?? "completed",
866
881
  action: webSearchAction(e.queries),
867
882
  ...(e.sources && e.sources.length > 0 ? { sources: e.sources } : {}),
868
883
  });
@@ -94,11 +94,17 @@ export async function fetchWithAttemptDeadline(
94
94
  init: RequestInit,
95
95
  timeoutMs: number,
96
96
  abortSignal?: AbortSignal,
97
+ preferIdentityEncoding = false,
97
98
  ): Promise<Response> {
98
99
  const attemptTimeout = clearableDeadline(timeoutMs, abortSignal);
100
+ const headers = new Headers(init.headers);
101
+ if (preferIdentityEncoding && !headers.has("accept-encoding")) {
102
+ headers.set("accept-encoding", "identity");
103
+ }
99
104
  try {
100
105
  return await fetch(url, {
101
106
  ...init,
107
+ headers,
102
108
  signal: attemptTimeout.signal,
103
109
  });
104
110
  } finally {
@@ -202,7 +202,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
202
202
  authKind: "oauth",
203
203
  featured: false,
204
204
  dashboardPreset: true,
205
- note: "Experimental Cursor bridge. Live transport and live model discovery are enabled after a standalone PKCE browser login via 'ocx login cursor'; native read/write/delete/shell/fetch execution stays disabled unless you set \"nativeLocalExec\": \"on\" (always) or \"codex-sandbox\" (only for requests declaring the Codex danger-full-access sandbox; the declaration is caller-controlled prose the proxy cannot verify, and the auth-free loopback bind admits any process on this host, including other local users — enable only where every data-plane client is trusted) — legacy \"unsafeAllowNativeLocalExec\": true still means \"on\" — on providers.cursor in ~/.opencodex/config.json (dashboard: Providers → Cursor → Edit JSON) for a trusted local experiment.",
205
+ note: "Experimental Cursor bridge. Live transport and live model discovery are enabled after a standalone PKCE browser login via 'ocx login cursor'; native read/write/delete/shell/fetch execution defaults to codex-sandbox mode (auto-enabled when the request declares Codex danger-full-access sandbox); override with \"nativeLocalExec\": \"on\" (always) or \"codex-sandbox\" (only for requests declaring the Codex danger-full-access sandbox; the declaration is caller-controlled prose the proxy cannot verify, and the auth-free loopback bind admits any process on this host, including other local users — enable only where every data-plane client is trusted) — legacy \"unsafeAllowNativeLocalExec\": true still means \"on\" — on providers.cursor in ~/.opencodex/config.json (dashboard: Providers → Cursor → Edit JSON) for a trusted local experiment.",
206
206
  models: cursorModelIds(CURSOR_STATIC_MODELS),
207
207
  liveModels: true,
208
208
  defaultModel: "auto",
@@ -346,9 +346,10 @@ export function sanitizeEncryptedContentInPlace(input: unknown): number {
346
346
  && (child as { type?: unknown }).type === "agent_message"
347
347
  && !hasEncryptedContentPart((child as { content?: unknown }).content)
348
348
  ) {
349
- const message = child as { type: string; role?: string; author?: unknown; recipient?: unknown };
349
+ const message = child as { type: string; role?: string; id?: unknown; author?: unknown; recipient?: unknown };
350
350
  message.type = "message";
351
351
  message.role = "user";
352
+ delete message.id;
352
353
  delete message.author;
353
354
  delete message.recipient;
354
355
  }
@@ -923,7 +924,7 @@ export async function handleResponses(
923
924
  let upstreamResponse: Response;
924
925
  try {
925
926
  upstreamResponse = adapter.fetchResponse
926
- ? await adapter.fetchResponse(request, { abortSignal: upstream.signal, timeoutMs: connectMs })
927
+ ? await adapter.fetchResponse(request, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
927
928
  : await fetchWithResetRetry(
928
929
  () => fetchWithHeaderTimeout(request.url, {
929
930
  method: request.method, headers: request.headers, body: request.body,
@@ -962,7 +963,7 @@ export async function handleResponses(
962
963
  const retryRequest = await retryAdapter.buildRequest(parsed, { headers: selectedForwardHeaders });
963
964
  try {
964
965
  upstreamResponse = retryAdapter.fetchResponse
965
- ? await retryAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs })
966
+ ? await retryAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
966
967
  : await fetchWithHeaderTimeout(retryRequest.url, {
967
968
  method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
968
969
  }, upstream.signal, connectMs, parsed.stream);
@@ -271,14 +271,19 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
271
271
  abortSignal: headerDeadline.signal,
272
272
  timeoutMs: connectTimeoutMs,
273
273
  returnRawErrors: true,
274
+ stream: true,
274
275
  })
275
276
  : await fetchWithResetRetry(
276
- () => fetch(request.url, {
277
- method: request.method,
278
- headers: request.headers,
279
- body: request.body,
280
- signal: headerDeadline.signal,
281
- }),
277
+ () => {
278
+ const h = new Headers(request.headers);
279
+ if (!h.has("accept-encoding")) h.set("accept-encoding", "identity");
280
+ return fetch(request.url, {
281
+ method: request.method,
282
+ headers: h,
283
+ body: request.body,
284
+ signal: headerDeadline.signal,
285
+ });
286
+ },
282
287
  { abortSignal: headerDeadline.signal, label: "web-search-loop" },
283
288
  );
284
289
  return { response, responseAdapter: requestAdapter };