@bitkyc08/opencodex 2.7.7 → 2.7.8

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
  -d '{
38
38
  "model": "gpt-5.4",
39
39
  "input": "Hello, world!"
40
- }'`})]})]})}var zt=`opencodex-api-token`,Bt=!1,Vt=null;function Ht(e){try{let t=e instanceof Request?e.url:String(e);return new URL(t,window.location.href).pathname}catch{return null}}function Ut(e){let t=Ht(e);return!!t&&(t.startsWith(`/api/`)||t.startsWith(`/v1/`))}function Wt(){try{return sessionStorage.getItem(zt)?.trim()||null}catch{return null}}function Gt(e){try{sessionStorage.setItem(zt,e)}catch{}}function Kt(){try{sessionStorage.removeItem(zt)}catch{}}function qt(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 Jt(){return Vt||(Vt=Promise.resolve().then(()=>window.prompt(`OpenCodex API token`)?.trim()||null).finally(()=>{Vt=null}),Vt)}function Yt(){if(Bt)return;Bt=!0;let e=window.fetch.bind(window);window.fetch=async(t,n)=>{if(!Ut(t))return e(t,n);let r=Wt(),[i,a]=r?qt(t,n,r):[t,n],o=await e(i,a);if(o.status!==401)return o;r&&Kt();let s=await Jt();if(!s)return o;Gt(s);let[c,l]=qt(t,n,s),u=await e(c,l);return u.status===401&&Kt(),u}}Yt();var Xt=new Set([`dashboard`,`providers`,`models`,`subagents`,`logs`,`debug`,`usage`,`codex-auth`,`api`]);function Zt(){let e=location.hash.replace(/^#\/?/,``);return Xt.has(e)?e:`dashboard`}var Qt=``,$t=`ocx-theme`,en=[{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}],tn={light:fe,dark:pe,system:me},nn={light:`theme.light`,dark:`theme.dark`,system:`theme.system`};function rn(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 an(){let e=localStorage.getItem($t);return e===`light`||e===`dark`?e:`system`}function on(){let[e,t]=(0,_.useState)(Zt),n=e=>{location.hash=e,t(e)},[r,i]=(0,_.useState)(an),[a,o]=(0,_.useState)(null),{locale:s,setLocale:c}=we(),l=Te();(0,_.useEffect)(()=>{let e=()=>t(Zt());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),(0,_.useEffect)(()=>{let e=document.documentElement;r===`system`?(e.removeAttribute(`data-theme`),localStorage.removeItem($t)):(e.setAttribute(`data-theme`,r),localStorage.setItem($t,r))},[r]),(0,_.useEffect)(()=>{let e=!1,t=async()=>{try{let t=await fetch(`${Qt}/healthz`);if(!t.ok)return;let n=rn(await t.json());!e&&n&&o(n)}catch{}};t();let n=setInterval(t,3e4);return()=>{e=!0,clearInterval(n)}},[]);let u=()=>i(e=>e===`light`?`dark`:e===`dark`?`system`:`light`),d=tn[r],f=a??`2.7.7`,[p,m]=(0,_.useState)(!1);return(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`,f]})]}),(0,S.jsx)(`nav`,{children:en.map(({id:t,tkey:r,Icon:i})=>(0,S.jsxs)(`button`,{className:`nav-item${e===t?` active`:``}`,"data-page":t,onClick:()=>n(t),"aria-current":e===t?`page`:void 0,children:[(0,S.jsx)(i,{}),` `,l(r)]},t))}),(0,S.jsxs)(`div`,{className:`sidebar-foot`,children:[(0,S.jsxs)(`div`,{className:`lang-toggle`,children:[(0,S.jsx)(he,{"aria-hidden":!0}),(0,S.jsx)(Me,{value:s,options:ve.map(e=>({value:e.code,label:e.name})),onChange:e=>c(e),label:l(`lang.label`),placement:`right`,style:{flex:1,minWidth:0,width:`100%`}})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:u,"aria-label":`${l(`theme.label`)}: ${l(nn[r])}`,title:`${l(`theme.label`)}: ${l(nn[r])}`,children:[(0,S.jsx)(d,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(nn[r])})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle stop-toggle`,onClick:async()=>{if(confirm(l(`dash.stopConfirm`))){m(!0);try{await fetch(`${Qt}/api/stop`,{method:`POST`})}catch{}}},disabled:p,"aria-label":l(`dash.stop`),title:l(`dash.stop`),children:[(0,S.jsx)(I,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(p?`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,{}),` `,l(`common.github`)]})]})]}),(0,S.jsx)(`main`,{className:`main`,children:(0,S.jsxs)(`div`,{className:`main-inner`,children:[e===`dashboard`&&(0,S.jsx)(qe,{apiBase:Qt}),e===`providers`&&(0,S.jsx)(st,{apiBase:Qt}),e===`models`&&(0,S.jsx)(ft,{apiBase:Qt}),e===`subagents`&&(0,S.jsx)(pt,{apiBase:Qt}),e===`logs`&&(0,S.jsx)(Ct,{apiBase:Qt}),e===`debug`&&(0,S.jsx)(wt,{apiBase:Qt}),e===`usage`&&(0,S.jsx)(jt,{apiBase:Qt}),e===`codex-auth`&&(0,S.jsx)(Nt,{apiBase:Qt}),e===`api`&&(0,S.jsx)(Rt,{apiBase:Qt})]})})]})}v.createRoot(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(Ce,{children:(0,S.jsx)(on,{})})}));
40
+ }'`})]})]})}var zt=`opencodex-api-token`,Bt=!1,Vt=null;function Ht(e){try{let t=e instanceof Request?e.url:String(e);return new URL(t,window.location.href).pathname}catch{return null}}function Ut(e){let t=Ht(e);return!!t&&(t.startsWith(`/api/`)||t.startsWith(`/v1/`))}function Wt(){try{return sessionStorage.getItem(zt)?.trim()||null}catch{return null}}function Gt(e){try{sessionStorage.setItem(zt,e)}catch{}}function Kt(){try{sessionStorage.removeItem(zt)}catch{}}function qt(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 Jt(){return Vt||(Vt=Promise.resolve().then(()=>window.prompt(`OpenCodex API token`)?.trim()||null).finally(()=>{Vt=null}),Vt)}function Yt(){if(Bt)return;Bt=!0;let e=window.fetch.bind(window);window.fetch=async(t,n)=>{if(!Ut(t))return e(t,n);let r=Wt(),[i,a]=r?qt(t,n,r):[t,n],o=await e(i,a);if(o.status!==401)return o;r&&Kt();let s=await Jt();if(!s)return o;Gt(s);let[c,l]=qt(t,n,s),u=await e(c,l);return u.status===401&&Kt(),u}}Yt();var Xt=new Set([`dashboard`,`providers`,`models`,`subagents`,`logs`,`debug`,`usage`,`codex-auth`,`api`]);function Zt(){let e=location.hash.replace(/^#\/?/,``);return Xt.has(e)?e:`dashboard`}var Qt=``,$t=`ocx-theme`,en=[{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}],tn={light:fe,dark:pe,system:me},nn={light:`theme.light`,dark:`theme.dark`,system:`theme.system`};function rn(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 an(){let e=localStorage.getItem($t);return e===`light`||e===`dark`?e:`system`}function on(){let[e,t]=(0,_.useState)(Zt),n=e=>{location.hash=e,t(e)},[r,i]=(0,_.useState)(an),[a,o]=(0,_.useState)(null),{locale:s,setLocale:c}=we(),l=Te();(0,_.useEffect)(()=>{let e=()=>t(Zt());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),(0,_.useEffect)(()=>{let e=document.documentElement;r===`system`?(e.removeAttribute(`data-theme`),localStorage.removeItem($t)):(e.setAttribute(`data-theme`,r),localStorage.setItem($t,r))},[r]),(0,_.useEffect)(()=>{let e=!1,t=async()=>{try{let t=await fetch(`${Qt}/healthz`);if(!t.ok)return;let n=rn(await t.json());!e&&n&&o(n)}catch{}};t();let n=setInterval(t,3e4);return()=>{e=!0,clearInterval(n)}},[]);let u=()=>i(e=>e===`light`?`dark`:e===`dark`?`system`:`light`),d=tn[r],f=a??`2.7.8`,[p,m]=(0,_.useState)(!1);return(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`,f]})]}),(0,S.jsx)(`nav`,{children:en.map(({id:t,tkey:r,Icon:i})=>(0,S.jsxs)(`button`,{className:`nav-item${e===t?` active`:``}`,"data-page":t,onClick:()=>n(t),"aria-current":e===t?`page`:void 0,children:[(0,S.jsx)(i,{}),` `,l(r)]},t))}),(0,S.jsxs)(`div`,{className:`sidebar-foot`,children:[(0,S.jsxs)(`div`,{className:`lang-toggle`,children:[(0,S.jsx)(he,{"aria-hidden":!0}),(0,S.jsx)(Me,{value:s,options:ve.map(e=>({value:e.code,label:e.name})),onChange:e=>c(e),label:l(`lang.label`),placement:`right`,style:{flex:1,minWidth:0,width:`100%`}})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:u,"aria-label":`${l(`theme.label`)}: ${l(nn[r])}`,title:`${l(`theme.label`)}: ${l(nn[r])}`,children:[(0,S.jsx)(d,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(nn[r])})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle stop-toggle`,onClick:async()=>{if(confirm(l(`dash.stopConfirm`))){m(!0);try{await fetch(`${Qt}/api/stop`,{method:`POST`})}catch{}}},disabled:p,"aria-label":l(`dash.stop`),title:l(`dash.stop`),children:[(0,S.jsx)(I,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(p?`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,{}),` `,l(`common.github`)]})]})]}),(0,S.jsx)(`main`,{className:`main`,children:(0,S.jsxs)(`div`,{className:`main-inner`,children:[e===`dashboard`&&(0,S.jsx)(qe,{apiBase:Qt}),e===`providers`&&(0,S.jsx)(st,{apiBase:Qt}),e===`models`&&(0,S.jsx)(ft,{apiBase:Qt}),e===`subagents`&&(0,S.jsx)(pt,{apiBase:Qt}),e===`logs`&&(0,S.jsx)(Ct,{apiBase:Qt}),e===`debug`&&(0,S.jsx)(wt,{apiBase:Qt}),e===`usage`&&(0,S.jsx)(jt,{apiBase:Qt}),e===`codex-auth`&&(0,S.jsx)(Nt,{apiBase:Qt}),e===`api`&&(0,S.jsx)(Rt,{apiBase:Qt})]})})]})}v.createRoot(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(Ce,{children:(0,S.jsx)(on,{})})}));
@@ -16,7 +16,7 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-DzEDGLZh.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-Bp8dDrs5.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-C0xVu72_.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.7",
3
+ "version": "2.7.8",
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",
@@ -278,6 +278,34 @@ export function parseRequest(body: unknown): OcxParsedRequest {
278
278
  continue;
279
279
  }
280
280
 
281
+ if (effectiveType === "agent_message") {
282
+ const agentMessage = item as {
283
+ author?: string;
284
+ recipient?: string;
285
+ content?: unknown;
286
+ };
287
+
288
+ const content = inputContentParts(
289
+ agentMessage.content as unknown[] | string | undefined,
290
+ );
291
+
292
+ const hasContent =
293
+ typeof content === "string"
294
+ ? content.trim().length > 0
295
+ : content.length > 0;
296
+
297
+ // An agent_message is external input delivered to the parent agent.
298
+ // Preserve it as a user-role turn so signed Anthropic thinking blocks
299
+ // on either side are never merged into one modified assistant response.
300
+ messages.push({
301
+ role: "user",
302
+ content: hasContent ? content : "(sub-agent message received)",
303
+ timestamp: now,
304
+ });
305
+
306
+ continue;
307
+ }
308
+
281
309
  if (effectiveType === "message") {
282
310
  const msg = item as { role?: string; content?: unknown };
283
311
  switch (msg.role) {
@@ -298,10 +298,19 @@ function encryptedSlotParts(payload: string): Array<Record<string, string>> {
298
298
  return parts.length > 0 ? parts : [{ type: "input_text", text: payload }];
299
299
  }
300
300
 
301
+ function hasEncryptedContentPart(content: unknown): boolean {
302
+ return Array.isArray(content) && content.some(part => (
303
+ part && typeof part === "object"
304
+ && (part as { type?: unknown }).type === "encrypted_content"
305
+ ));
306
+ }
307
+
301
308
  /**
302
309
  * Rewrite non-ciphertext `{type:"encrypted_content"}` parts into `{type:"input_text"}`
303
- * throughout a native-bound request's input items (message content and
304
- * function_call_output content arrays share the part shape, codex-rs protocol/models.rs).
310
+ * throughout a request's input items (message content and function_call_output content
311
+ * arrays share the part shape, codex-rs protocol/models.rs). An `agent_message` whose
312
+ * payload becomes entirely plaintext is normalized to a user message so routed parsers
313
+ * that do not understand the internal item type still deliver the spawn task.
305
314
  * Genuine backend blobs are left byte-identical so replay/cache semantics survive, and
306
315
  * MIXED slots (plaintext preamble + embedded Fernet task body) are split so the backend
307
316
  * decrypts the blob while the prose passes as text. Returns the number of parts rewritten.
@@ -309,7 +318,8 @@ function encryptedSlotParts(payload: string): Array<Record<string, string>> {
309
318
  export function sanitizeEncryptedContentInPlace(input: unknown): number {
310
319
  if (!Array.isArray(input)) return 0;
311
320
  let rewritten = 0;
312
- const visit = (node: unknown): void => {
321
+ const visit = (node: unknown): number => {
322
+ const before = rewritten;
313
323
  if (Array.isArray(node)) {
314
324
  for (let i = 0; i < node.length; i += 1) {
315
325
  const child = node[i] as unknown;
@@ -327,13 +337,26 @@ export function sanitizeEncryptedContentInPlace(input: unknown): number {
327
337
  continue;
328
338
  }
329
339
  }
330
- visit(child);
340
+ const childRewrites = visit(child);
341
+ if (
342
+ childRewrites > 0
343
+ && child && typeof child === "object"
344
+ && (child as { type?: unknown }).type === "agent_message"
345
+ && !hasEncryptedContentPart((child as { content?: unknown }).content)
346
+ ) {
347
+ const message = child as { type: string; role?: string; author?: unknown; recipient?: unknown };
348
+ message.type = "message";
349
+ message.role = "user";
350
+ delete message.author;
351
+ delete message.recipient;
352
+ }
331
353
  }
332
- return;
354
+ return rewritten - before;
333
355
  }
334
356
  if (node && typeof node === "object") {
335
357
  for (const value of Object.values(node)) visit(value);
336
358
  }
359
+ return rewritten - before;
337
360
  };
338
361
  visit(input);
339
362
  return rewritten;
@@ -410,6 +433,21 @@ export async function handleResponses(
410
433
  body = expandPreviousResponseInput(body);
411
434
  const previousResponseInputExpanded = body !== originalBody;
412
435
 
436
+ // Spawn-message compatibility (both directions): agent_message task payloads ride in
437
+ // encrypted_content slots as plaintext. Rewrite them to input_text on the RAW body BEFORE
438
+ // parsing so every consumer sees the payload: parseRequest (routed/translated providers read
439
+ // the parsed messages) and the native passthrough (_rawBody is this same object, serialized
440
+ // verbatim). Genuine backend ciphertext is left byte-identical (looksLikeBackendCiphertext).
441
+ {
442
+ const rewritten = sanitizeEncryptedContentInPlace(
443
+ (body as { input?: unknown } | undefined)?.input,
444
+ );
445
+ if (rewritten > 0)
446
+ console.warn(
447
+ `[opencodex] rewrote ${rewritten} plaintext encrypted_content part(s) to input_text (spawn-message compatibility)`,
448
+ );
449
+ }
450
+
413
451
  let parsed;
414
452
  try {
415
453
  parsed = parseRequest(body);
@@ -452,15 +490,6 @@ export async function handleResponses(
452
490
  // Runs BEFORE the mock-max clamp below so the synthetic top tier (ultra arrives
453
491
  // as max on the codex wire) is still visible. Both request shapes are rewritten.
454
492
  {
455
- const requestedModelId = logCtx.requestedModel ?? route.modelId;
456
- // Cross-provider spawn poison fix: native-bound requests may carry plaintext parked in
457
- // encrypted_content slots (spawn messages minted under a routed parent). Rewrite them
458
- // to input_text before the passthrough serializes _rawBody verbatim.
459
- if (!requestedModelId.includes("/")) {
460
- const raw = parsed._rawBody as { input?: unknown } | undefined;
461
- const rewritten = sanitizeEncryptedContentInPlace(raw?.input);
462
- if (rewritten > 0) console.warn(`[opencodex] ${route.modelId}: rewrote ${rewritten} plaintext encrypted_content part(s) to input_text (routed-parent spawn compatibility)`);
463
- }
464
493
  const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort, config.subagentModels, config.injectionPrompt);
465
494
  if (guidance) {
466
495
  injectDeveloperMessage(parsed, guidance);