@messenger-agent/codex-agent 0.26.0 → 0.27.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -290,6 +290,10 @@ export type ToolRequestUserInputParams = {
290
290
  turnId: string;
291
291
  itemId: string;
292
292
  questions: Array<ToolRequestUserInputQuestion>;
293
+ /** Missing on Codex versions before 0.147.0; legacy requests are treated as blocking. */
294
+ isBlocking?: boolean;
295
+ /** @deprecated Use isBlocking to decide whether the request should block. */
296
+ autoResolutionMs?: number | null;
293
297
  };
294
298
  /**
295
299
  * EXPERIMENTAL. Params sent with a request_user_input event.
@@ -1,2 +1,2 @@
1
- import{Hono as Ht}from"hono";import{HTTPException as D}from"hono/http-exception";import{zValidator as Jt}from"@hono/zod-validator";import{streamSSE as Kt}from"hono/streaming";import{randomUUID as Qt}from"node:crypto";import{sessionManager as j,WorkdirNotFoundError as Vt}from"../codex.js";import{logger as rt}from"@messenger-agent/shared/logger";import{ChatBodySchema as Gt,RequestUserInputToolPartSchema as Yt}from"../schemas.js";import{appConfig as xt}from"../config.js";import{setManagedTaskToolContext as Zt}from"../dynamic-tools.js";import{resolveAgentWorkspaceRoot as te}from"@messenger-agent/shared/agent-workspaces";import{saveUploadedFilePart as ee}from"@messenger-agent/shared/uploaded-files";import{commandExecutionItem as bt,addTokenUsage as ne,displayCommand as ae,dynamicToolCallItem as Tt,dynamicToolCallOutput as re,fileChangeItem as St,itemText as Et,mcpToolCallItem as Rt,mcpToolCallOutput as oe,toUsage as ie,turnOptions as se,webSearchItem as kt,webSearchPayload as Ot}from"./shared.js";const B=new Ht,f=new Map,z=new Set,ot=new Map,k=new Map;function Le(){const t=new Set([...k.values()].map(e=>e.conversationId).filter(e=>!!e));return{active:z.size,waiting:t.size}}function de(t){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}}function ue(t){const e=v(t,"X-Matrix-Room-Id")?.trim(),r=v(t,"X-Matrix-Thread-ID")?.trim();return e&&r?`${e}/${r}`:void 0}function le(t){const e=v(t,"X-Matrix-Room-Id")?.trim(),r=v(t,"X-Matrix-Event-ID")?.trim(),o=v(t,"X-Matrix-Thread-ID")?.trim();if(!(!e||!r))return{roomId:e,eventId:r,...o?{threadRootEventId:o}:null}}class q extends Error{status;constructor(e,r){super(r),this.status=e,this.name="ChatRequestError"}}function v(t,e){return t?.get(e)??void 0}async function _t(t){const e=f.get(t);if(!e)return!1;for(e.cancelRequested=!0,e.turnId||e.owner?.abortController.abort();;){const r=e.transition;if(await r,r===e.transition)break}return e.thread&&e.turnId&&(e.interrupt||(e.interrupt=e.thread.interrupt(e.turnId).finally(()=>{e.interrupt=void 0})),await e.interrupt),e.owner?.abortController.abort(),e.resolveOwnerReady?.(),f.get(t)===e&&f.delete(t),!0}function Mt(t){let e=f.get(t);e||(e={conversationId:t,generation:0,cancelRequested:!1,transition:Promise.resolve()},f.set(t,e));const r=++e.generation,o=new AbortController,i=!e.owner;return i&&(e.owner={generation:r,abortController:o},e.ownerReady=new Promise(d=>{e.resolveOwnerReady=d})),{state:e,generation:r,abortController:o,ownsOutput:i}}function N(t){return f.get(t.state.conversationId)===t.state&&t.state.owner?.generation===t.generation}async function H(t,e){const r=t.transition;let o;const i=new Promise(d=>{o=d});t.transition=r.then(()=>i),await r;try{return await e()}finally{o()}}async function ce(t,e,r,o){!t.ownsOutput&&!t.state.turnId&&await t.state.ownerReady;try{return await H(t.state,async()=>{const{state:i}=t;if(f.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");i.thread=e;const d=i.turnId?await e.steerStreamed(r,i.turnId,{signal:t.abortController.signal}):await e.runStreamed(r,{...o,signal:t.abortController.signal});return i.turnId=d.turnId,d})}finally{t.ownsOutput&&t.state.resolveOwnerReady?.()}}async function pe(t,e,r){return H(t.state,async()=>{const{state:o}=t;if(!N(t)||o.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");o.thread=e;const i=await r();return o.turnId=i.turnId,o.resolveOwnerReady?.(),i})}async function fe(t,e,r,o){return H(t.state,async()=>{const{state:i}=t;if(f.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");i.thread=e;const d=await e.submitPendingUserInput(r,o);return d&&(i.turnId=d),d})}async function me(t,e,r,o,i){return H(t.state,async()=>{const{state:d}=t;if(f.get(d.conversationId)!==d||d.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");d.thread=e,await e.interruptPendingUserInput(r.turnId,r.toolCallId),d.turnId=void 0;const T=await e.runStreamed(o,{...i,signal:t.abortController.signal});return d.turnId=T.turnId,T})}function Pt(t,e,r){t.state.thread=e,t.state.turnId=r}function Dt(t){const{state:e}=t;N(t)&&(e.owner=void 0,e.resolveOwnerReady?.(),e.turnId=void 0,f.get(e.conversationId)===e&&f.delete(e.conversationId))}function qt(t){N(t)&&(t.state.owner=void 0,t.state.resolveOwnerReady?.(),!t.state.turnId&&f.get(t.state.conversationId)===t.state&&f.delete(t.state.conversationId))}function we(t){N(t)&&(qt(t),t.state.resolveOwnerReady?.(),!t.state.turnId&&f.get(t.state.conversationId)===t.state&&f.delete(t.state.conversationId))}function J(t,e){return`${t??"unknown"}:${e}`}function ye(t){if(t)return[...k.values()].find(e=>e.conversationId===t)}function Nt(t,e){return t??e}function ge(t){return t.type==="file"&&"mediaType"in t&&typeof t.mediaType=="string"&&"url"in t&&typeof t.url=="string"&&(!("filename"in t)||t.filename===void 0||typeof t.filename=="string")}async function Ie(t){if(t.type==="text"&&typeof t.text=="string")return t.text;if(ge(t))return ee(t,xt.fileUploads.tempDir);throw new D(400,{message:`Unsupported message part type: ${t.type}`})}async function he(t){const r=(await Promise.all(t.map(Ie))).filter(o=>!!(o&&o.trim().length>0)).join(`
2
- `).trim();if(!r)throw new D(400,{message:"No supported message content found"});return r}function Ce(t){if(!t.some(o=>o.type==="tool"))return;if(t.length!==1)throw new D(400,{message:"When a tool part is present, the message must contain exactly one part"});const r=Yt.safeParse(t[0]);if(!r.success)throw new D(400,{message:`Invalid requestUserInput tool part: ${r.error.message}`});return{itemId:r.data.toolCallId,response:r.data.output,sourceTurnId:r.data.sourceTurnId}}function m(t,e,r,o){const i=[e,r,o].filter(Boolean).join(":"),d=t.get(i);if(d)return d;const T=Qt();return t.set(i,T),T}function ve(t,e){return t&&t.length>0?t:e}function xe(t){rt.debug("Received chat stream event:",t)}function be(t){if(t instanceof Vt||t&&typeof t=="object"&&"path"in t&&typeof t.path=="string")return t.path;if(t instanceof Error&&t.message.startsWith("Workdir not found: "))return t.message.slice(19)}B.post("/v1/chat/:conversation_id/cancel",async t=>{const e=t.req.param("conversation_id");try{return await _t(e)?t.json({success:!0}):t.json({error:de(e)},404)}catch(r){const o=r instanceof Error?r.message:"Failed to interrupt Codex conversation";return rt.error(`Failed to cancel Codex conversation ${e}:`,r),t.json({error:{type:"server_error",code:"cancel_failed",message:o}},500)}}),B.get("/admin/status",t=>t.json({activeTaskCount:z.size,waitingClientToolCount:k.size,clientToolWaits:[...k.values()].map(({conversationId:e,toolCallId:r,toolName:o,since:i})=>({conversationId:e,toolCallId:r,toolName:o,since:i}))}));async function Ut({body:t,headers:e}){const{conversationId:r,fork:o,model:i,effort:d,message:T}=t,K=v(e,"X-Matrix-Bot-Name"),Q=v(e,"X-Matrix-Workdir"),U=v(e,"X-Matrix-Workspace-ID"),Lt=v(e,"X-Matrix-Agent-Mode"),V=v(e,"X-Elevo-LLM-Binding")?.trim()||void 0,G=o?void 0:j.bindingFor(r);if(V&&G&&V!==G)throw new q(409,"LLM proxy binding does not match the existing conversation");const L=G??V,At=ue(e),Wt=le(e),Y=Ce(T.parts),Z=Y?void 0:await he(T.parts),it=Y?void 0:ye(r),c=Y,O=Z&&it?it:void 0;let A=!o&&r?Mt(r):void 0;const Ft=(()=>{try{return te(xt,U)}catch(S){throw new q(400,S instanceof Error?S.message:"Invalid workspace")}})(),{thread:y,isNewSession:Xt}=await(async()=>{try{if(o){if(!("threadId"in o))throw new q(400,"Codex fork requires threadId and turnId");return await j.fork(o,K,Q,U,i,"danger-full-access",L)}return await j.getOrCreate(r,K,Q,U,i,"danger-full-access",L)}catch(S){A&&we(A);const _=be(S);throw _?new q(400,`Workdir not found: ${_}`):S}})();A??=Mt(y.id),Zt(y.id,{workspaceRoot:Ft,threadRef:At,matrixEvent:Wt,llmProxyBinding:L});const st=se(Lt,i,d);return{run:async(S,_)=>{const w=A,W=w.abortController;z.add(W);const tt=()=>{N(w)&&_t(w.state.conversationId).catch(a=>rt.error("Failed to cancel disconnected Codex stream:",a))};_?.aborted&&tt(),_?.addEventListener("abort",tt,{once:!0});let x=!!c,dt=!!c,ut=!1,l,I=y.id,h,F=!1,P;const lt=new Set,ct=new Set,X=new Set,p=new Map,pt=new Set,$t=new Set,ft=new Set;let mt,et,wt;const yt=()=>{P=setTimeout(()=>{s({type:"heartbeat"}).catch(()=>{})},6e4)},gt=async a=>{P&&clearTimeout(P),a.data==="[DONE]"?await S.writeDone():await S.writeData(JSON.parse(String(a.data))),yt()},s=async a=>{await gt({data:JSON.stringify(a)})},$=async()=>{await gt({data:"[DONE]"})},jt=async()=>{const a=Nt(l,y.id),n=ot.get(a);n&&(ot.delete(a),await s({type:"data",kind:"diff",data:{diff:n}}))},It=async(a,n)=>{if(a.type!=="contextCompaction")return!1;if(n==="end"&&!pt.has(a.id))return!0;const u=n==="start"?pt:$t;return u.has(a.id)||(u.add(a.id),await s({type:"data",kind:"compact",data:n==="start"?{phase:n,activityId:a.id}:{phase:n,activityId:a.id,status:"completed"}})),!0},nt=async a=>{if(a.type!=="subAgentActivity")return!1;if(ft.has(a.id))return!0;ft.add(a.id);const n=m(p,l,a.id),u={activity:a.kind,agentThreadId:a.agentThreadId,...a.agentPath?{agentPath:a.agentPath}:null};return await s({type:"tool-input-available",toolCallId:n,toolName:`subagent_${a.kind}`,...a.agentPath?{title:a.agentPath}:null,input:u}),await s(a.kind==="interrupted"?{type:"tool-output-error",toolCallId:n,errorText:"Subagent interrupted"}:{type:"tool-output-available",toolCallId:n,output:u}),!0};yt();const ht=a=>{l!==a&&(l=a,Xt&&j.persist(a,K,Q,U,L))},E=async(a,n)=>{x||(x=!0,ht(a),await s({type:"start",messageMetadata:{conversationId:a,threadId:a,...n?{turnId:n}:null}}),await s({type:"start-step"}),dt=!0)},at=async a=>{ut||(ut=!0,dt&&(await jt(),a==="stop"&&et&&await s({type:"data",kind:"token_usage",data:et}),await s({type:"finish-step"})),await s({type:"finish",finishReason:a}),await $())},Ct=async a=>{if(lt.has(a.id))return;lt.add(a.id);const n=a.type==="plan"?{type:"plan"}:void 0;await s({type:"text-start",id:a.id,...n?{textMetadata:n}:{}})},vt=async a=>{ct.has(a.id)||(ct.add(a.id),await s({type:"reasoning-start",id:a.id}))},Bt=async a=>{const n=bt(a);if(n)return await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:"bash",input:ae(n.command)}),!0;const u=St(a);if(u){for(const[b,C]of u.changes.entries())await s({type:"tool-input-available",toolCallId:m(p,l,a.id,String(b)),toolName:C.kind.type==="update"?"Edit":C.kind.type==="add"?"Add":"Delete",title:C.path,input:C});return!0}const g=Rt(a);if(g)return await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:g.tool,input:g.arguments}),!0;const R=Tt(a);if(R)return await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:R.tool,input:R.arguments}),!0;const M=kt(a);return M?(await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:"web_search",title:M.query,input:Ot(M)}),!0):!1},zt=async a=>{const n=bt(a);if(n){const b=n.status==="failed"||n.status==="declined";return await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id),errorText:ve(n.aggregatedOutput,"Command execution failed")}:{type:"tool-output-available",toolCallId:m(p,l,a.id),output:n.aggregatedOutput??""}),!0}const u=St(a);if(u){const b=u.status==="failed"||u.status==="declined";for(const[C]of u.changes.entries())await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id,String(C)),errorText:u.status}:{type:"tool-output-available",toolCallId:m(p,l,a.id,String(C)),output:u.status,preliminary:!1});return!0}const g=Rt(a);if(g){const b=g.status==="failed";return await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id),errorText:g.error?.message??"MCP tool call failed"}:{type:"tool-output-available",toolCallId:m(p,l,a.id),output:oe(g)}),!0}const R=Tt(a);if(R){const b=R.status==="failed"||R.success===!1,C=re(R);return await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id),errorText:C||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:m(p,l,a.id),output:C}),!0}const M=kt(a);return M?(await s({type:"tool-output-available",toolCallId:m(p,l,a.id),output:Ot(M)}),!0):!1};try{if(c&&!w.ownsOutput){await fe(w,y,c.itemId,c.response),k.delete(J(r,c.itemId)),await $();return}const a=O?await me(w,y,O,Z,st):c?await pe(w,y,()=>y.continueWithUserInput(c.itemId,c.response,{signal:W.signal,sourceTurnId:c.sourceTurnId})):await ce(w,y,Z,st);if(c?k.delete(J(r,c.itemId)):O&&k.delete(J(r,O.toolCallId)),Pt(w,y,a.turnId),!w.ownsOutput){await $();return}c?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:y.id,turnId:a.turnId}}),await s({type:"tool-output-available",toolCallId:c.itemId,toolName:"request_user_input",output:c.response})):O&&await s({type:"tool-output-error",toolCallId:O.toolCallId,toolName:"request_user_input",errorText:"Question interrupted by text response"});for await(const n of a.events)if(n.type!=="item.delta"&&xe(n),n.type==="thread.started")I=n.thread_id,ht(I);else if(n.type==="turn.started")h=n.turn_id,Pt(w,y,h),await E(I,h);else if(n.type==="item.started"){if(x||await E(I,h),await It(n.item,"start")||await nt(n.item)||await Bt(n.item))continue;n.item.type==="agentMessage"||n.item.type==="plan"?await Ct(n.item):n.item.type==="reasoning"&&await vt(n.item)}else if(n.type==="item.delta")x||await E(I,h),n.item_type==="agentMessage"||n.item_type==="plan"?(X.add(n.item_id),await s({type:"text-delta",id:n.item_id,delta:n.delta})):n.item_type==="reasoning"&&(X.add(n.item_id),await s({type:"reasoning-delta",id:n.item_id,delta:n.delta}));else if(n.type==="item.updated"){if(await nt(n.item))continue}else if(n.type==="item.completed"){if(x||await E(I,h),await It(n.item,"end")||await nt(n.item)||await zt(n.item))continue;if(n.item.type==="agentMessage"||n.item.type==="plan"){await Ct(n.item);const u=Et(n.item)??"";u&&!X.has(n.item.id)&&await s({type:"text-delta",id:n.item.id,delta:u}),await s({type:"text-end",id:n.item.id})}else if(n.item.type==="reasoning"){await vt(n.item);const u=Et(n.item)??"";u&&!X.has(n.item.id)&&await s({type:"reasoning-delta",id:n.item.id,delta:u}),await s({type:"reasoning-end",id:n.item.id})}}else if(n.type==="item.tool.requestUserInput"){x||await E(I,h),k.set(J(l,n.params.itemId),{conversationId:l,toolCallId:n.params.itemId,toolName:"request_user_input",turnId:n.params.turnId,since:new Date().toISOString()}),await s({type:"tool-input-available",toolCallId:n.params.itemId,toolName:"request_user_input",input:n.params}),await $();return}else if(n.type==="turn.diff.updated")x||await E(I,h),ot.set(Nt(l,y.id),n.diff);else if(n.type==="thread.tokenUsage.updated"){const u=ie(n.usage);if(u){const g=ne(wt,u.last);g&&(wt=g,et={...u,last:g})}}else if(n.type==="turn.failed"){mt!==n.error.message&&await s({type:"error",errorText:n.error.message}),await at("error"),F=!0,Dt(w);return}else n.type==="error"&&(mt=n.message,await s({type:"error",errorText:n.message}));F=!W.signal.aborted,F&&Dt(w),x||await E(I,h),await at("stop")}catch(a){const n=a instanceof Error?a.message:"Chat stream failed";x||await E(I,h),await s({type:"error",errorText:n}),await at("error")}finally{P&&clearTimeout(P),_?.removeEventListener("abort",tt),z.delete(W),F||qt(w)}}}}async function Ae({body:t,headers:e,signal:r},o){await(await Ut({body:t,headers:e})).run(o,r)}B.post("/v1/chat/stream",Jt("json",Gt),async t=>{const e=t.req.valid("json"),r=await(async()=>{try{return await Ut({body:e,headers:t.req.raw.headers})}catch(o){throw o instanceof q?new D(o.status,{message:o.message}):o}})();return Kt(t,async o=>{const i=async d=>{await o.writeSSE(d)};await r.run({writeData:async d=>{await i({data:JSON.stringify(d)})},writeDone:async()=>{await i({data:"[DONE]"})}},t.req.raw.signal)})});var We=B;export{q as ChatRequestError,_t as cancelCodexConversation,Le as codexActivitySnapshot,We as default,Ae as handleCodexChatStream,Ut as prepareCodexChatStream};
1
+ import{Hono as Jt}from"hono";import{HTTPException as q}from"hono/http-exception";import{zValidator as Kt}from"@hono/zod-validator";import{streamSSE as Qt}from"hono/streaming";import{randomUUID as Vt}from"node:crypto";import{sessionManager as j,WorkdirNotFoundError as Gt}from"../codex.js";import{logger as B}from"@messenger-agent/shared/logger";import{ChatBodySchema as Yt,RequestUserInputToolPartSchema as Zt}from"../schemas.js";import{appConfig as xt}from"../config.js";import{setManagedTaskToolContext as te}from"../dynamic-tools.js";import{resolveAgentWorkspaceRoot as ee}from"@messenger-agent/shared/agent-workspaces";import{saveUploadedFilePart as ne}from"@messenger-agent/shared/uploaded-files";import{commandExecutionItem as bt,addTokenUsage as ae,displayCommand as re,dynamicToolCallItem as Tt,dynamicToolCallOutput as oe,fileChangeItem as St,itemText as Et,mcpToolCallItem as kt,mcpToolCallOutput as ie,toUsage as se,turnOptions as de,webSearchItem as Rt,webSearchPayload as _t}from"./shared.js";const z=new Jt,m=new Map,H=new Set,ot=new Map,R=new Map;function Le(){const t=new Set([...R.values()].map(e=>e.conversationId).filter(e=>!!e));return{active:H.size,waiting:t.size}}function ue(t){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}}function le(t){const e=v(t,"X-Matrix-Room-Id")?.trim(),r=v(t,"X-Matrix-Thread-ID")?.trim();return e&&r?`${e}/${r}`:void 0}function ce(t){const e=v(t,"X-Matrix-Room-Id")?.trim(),r=v(t,"X-Matrix-Event-ID")?.trim(),o=v(t,"X-Matrix-Thread-ID")?.trim();if(!(!e||!r))return{roomId:e,eventId:r,...o?{threadRootEventId:o}:null}}class D extends Error{status;constructor(e,r){super(r),this.status=e,this.name="ChatRequestError"}}function v(t,e){return t?.get(e)??void 0}async function Ot(t){const e=m.get(t);if(!e)return!1;for(e.cancelRequested=!0,e.turnId||e.owner?.abortController.abort();;){const r=e.transition;if(await r,r===e.transition)break}return e.thread&&e.turnId&&(e.interrupt||(e.interrupt=e.thread.interrupt(e.turnId).finally(()=>{e.interrupt=void 0})),await e.interrupt),e.owner?.abortController.abort(),e.resolveOwnerReady?.(),m.get(t)===e&&m.delete(t),!0}function Mt(t){let e=m.get(t);e||(e={conversationId:t,generation:0,cancelRequested:!1,transition:Promise.resolve()},m.set(t,e));const r=++e.generation,o=new AbortController,i=!e.owner;return i&&(e.owner={generation:r,abortController:o},e.ownerReady=new Promise(d=>{e.resolveOwnerReady=d})),{state:e,generation:r,abortController:o,ownsOutput:i}}function N(t){return m.get(t.state.conversationId)===t.state&&t.state.owner?.generation===t.generation}async function J(t,e){const r=t.transition;let o;const i=new Promise(d=>{o=d});t.transition=r.then(()=>i),await r;try{return await e()}finally{o()}}async function pe(t,e,r,o){!t.ownsOutput&&!t.state.turnId&&await t.state.ownerReady;try{return await J(t.state,async()=>{const{state:i}=t;if(m.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");i.thread=e;const d=i.turnId?await e.steerStreamed(r,i.turnId,{signal:t.abortController.signal}):await e.runStreamed(r,{...o,signal:t.abortController.signal});return i.turnId=d.turnId,d})}finally{t.ownsOutput&&t.state.resolveOwnerReady?.()}}async function fe(t,e,r){return J(t.state,async()=>{const{state:o}=t;if(!N(t)||o.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");o.thread=e;const i=await r();return o.turnId=i.turnId,o.resolveOwnerReady?.(),i})}async function Pt(t,e,r,o){return J(t.state,async()=>{const{state:i}=t;if(m.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");i.thread=e;const d=await e.submitPendingUserInput(r,o);return d&&(i.turnId=d),d})}async function me(t,e,r,o,i){return J(t.state,async()=>{const{state:d}=t;if(m.get(d.conversationId)!==d||d.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");d.thread=e,await e.interruptPendingUserInput(r.turnId,r.toolCallId),d.turnId=void 0;const T=await e.runStreamed(o,{...i,signal:t.abortController.signal});return d.turnId=T.turnId,T})}function qt(t,e,r){t.state.thread=e,t.state.turnId=r}function Dt(t){const{state:e}=t;N(t)&&(e.owner=void 0,e.resolveOwnerReady?.(),e.turnId=void 0,m.get(e.conversationId)===e&&m.delete(e.conversationId))}function Nt(t){N(t)&&(t.state.owner=void 0,t.state.resolveOwnerReady?.(),!t.state.turnId&&m.get(t.state.conversationId)===t.state&&m.delete(t.state.conversationId))}function we(t){N(t)&&(Nt(t),t.state.resolveOwnerReady?.(),!t.state.turnId&&m.get(t.state.conversationId)===t.state&&m.delete(t.state.conversationId))}function K(t,e){return`${t??"unknown"}:${e}`}function ye(t){if(t)return[...R.values()].find(e=>e.conversationId===t)}function Ut(t,e){return t??e}function ge(t){return t.type==="file"&&"mediaType"in t&&typeof t.mediaType=="string"&&"url"in t&&typeof t.url=="string"&&(!("filename"in t)||t.filename===void 0||typeof t.filename=="string")}async function Ie(t){if(t.type==="text"&&typeof t.text=="string")return t.text;if(ge(t))return ne(t,xt.fileUploads.tempDir);throw new q(400,{message:`Unsupported message part type: ${t.type}`})}async function he(t){const r=(await Promise.all(t.map(Ie))).filter(o=>!!(o&&o.trim().length>0)).join(`
2
+ `).trim();if(!r)throw new q(400,{message:"No supported message content found"});return r}function Ce(t){if(!t.some(o=>o.type==="tool"))return;if(t.length!==1)throw new q(400,{message:"When a tool part is present, the message must contain exactly one part"});const r=Zt.safeParse(t[0]);if(!r.success)throw new q(400,{message:`Invalid requestUserInput tool part: ${r.error.message}`});return{itemId:r.data.toolCallId,response:r.data.output,sourceTurnId:r.data.sourceTurnId}}function w(t,e,r,o){const i=[e,r,o].filter(Boolean).join(":"),d=t.get(i);if(d)return d;const T=Vt();return t.set(i,T),T}function ve(t,e){return t&&t.length>0?t:e}function xe(t){B.debug("Received chat stream event:",t)}function be(t){if(t instanceof Gt||t&&typeof t=="object"&&"path"in t&&typeof t.path=="string")return t.path;if(t instanceof Error&&t.message.startsWith("Workdir not found: "))return t.message.slice(19)}z.post("/v1/chat/:conversation_id/cancel",async t=>{const e=t.req.param("conversation_id");try{return await Ot(e)?t.json({success:!0}):t.json({error:ue(e)},404)}catch(r){const o=r instanceof Error?r.message:"Failed to interrupt Codex conversation";return B.error(`Failed to cancel Codex conversation ${e}:`,r),t.json({error:{type:"server_error",code:"cancel_failed",message:o}},500)}}),z.get("/admin/status",t=>t.json({activeTaskCount:H.size,waitingClientToolCount:R.size,clientToolWaits:[...R.values()].map(({conversationId:e,toolCallId:r,toolName:o,since:i})=>({conversationId:e,toolCallId:r,toolName:o,since:i}))}));async function Lt({body:t,headers:e}){const{conversationId:r,fork:o,model:i,effort:d,message:T}=t,Q=v(e,"X-Matrix-Bot-Name"),V=v(e,"X-Matrix-Workdir"),U=v(e,"X-Matrix-Workspace-ID"),At=v(e,"X-Matrix-Agent-Mode"),G=v(e,"X-Elevo-LLM-Binding")?.trim()||void 0,Y=o?void 0:j.bindingFor(r);if(G&&Y&&G!==Y)throw new D(409,"LLM proxy binding does not match the existing conversation");const L=Y??G,Wt=le(e),$t=ce(e),Z=Ce(T.parts),tt=Z?void 0:await he(T.parts),it=Z?void 0:ye(r),c=Z,_=tt&&it?it:void 0;let A=!o&&r?Mt(r):void 0;const Ft=(()=>{try{return ee(xt,U)}catch(S){throw new D(400,S instanceof Error?S.message:"Invalid workspace")}})(),{thread:y,isNewSession:Xt}=await(async()=>{try{if(o){if(!("threadId"in o))throw new D(400,"Codex fork requires threadId and turnId");return await j.fork(o,Q,V,U,i,"danger-full-access",L)}return await j.getOrCreate(r,Q,V,U,i,"danger-full-access",L)}catch(S){A&&we(A);const O=be(S);throw O?new D(400,`Workdir not found: ${O}`):S}})();A??=Mt(y.id),te(y.id,{workspaceRoot:Ft,threadRef:Wt,matrixEvent:$t,llmProxyBinding:L});const st=de(At,i,d);return{run:async(S,O)=>{const p=A,W=p.abortController;H.add(W);const et=()=>{N(p)&&Ot(p.state.conversationId).catch(a=>B.error("Failed to cancel disconnected Codex stream:",a))};O?.aborted&&et(),O?.addEventListener("abort",et,{once:!0});let x=!!c,dt=!!c,ut=!1,l,I=y.id,h,$=!1,P;const lt=new Set,ct=new Set,F=new Set,f=new Map,pt=new Set,jt=new Set,ft=new Set;let mt,nt,wt;const yt=()=>{P=setTimeout(()=>{s({type:"heartbeat"}).catch(()=>{})},6e4)},gt=async a=>{P&&clearTimeout(P),a.data==="[DONE]"?await S.writeDone():await S.writeData(JSON.parse(String(a.data))),yt()},s=async a=>{await gt({data:JSON.stringify(a)})},X=async()=>{await gt({data:"[DONE]"})},Bt=async()=>{const a=Ut(l,y.id),n=ot.get(a);n&&(ot.delete(a),await s({type:"data",kind:"diff",data:{diff:n}}))},It=async(a,n)=>{if(a.type!=="contextCompaction")return!1;if(n==="end"&&!pt.has(a.id))return!0;const u=n==="start"?pt:jt;return u.has(a.id)||(u.add(a.id),await s({type:"data",kind:"compact",data:n==="start"?{phase:n,activityId:a.id}:{phase:n,activityId:a.id,status:"completed"}})),!0},at=async a=>{if(a.type!=="subAgentActivity")return!1;if(ft.has(a.id))return!0;ft.add(a.id);const n=w(f,l,a.id),u={activity:a.kind,agentThreadId:a.agentThreadId,...a.agentPath?{agentPath:a.agentPath}:null};return await s({type:"tool-input-available",toolCallId:n,toolName:`subagent_${a.kind}`,...a.agentPath?{title:a.agentPath}:null,input:u}),await s(a.kind==="interrupted"?{type:"tool-output-error",toolCallId:n,errorText:"Subagent interrupted"}:{type:"tool-output-available",toolCallId:n,output:u}),!0};yt();const ht=a=>{l!==a&&(l=a,Xt&&j.persist(a,Q,V,U,L))},E=async(a,n)=>{x||(x=!0,ht(a),await s({type:"start",messageMetadata:{conversationId:a,threadId:a,...n?{turnId:n}:null}}),await s({type:"start-step"}),dt=!0)},rt=async a=>{ut||(ut=!0,dt&&(await Bt(),a==="stop"&&nt&&await s({type:"data",kind:"token_usage",data:nt}),await s({type:"finish-step"})),await s({type:"finish",finishReason:a}),await X())},Ct=async a=>{if(lt.has(a.id))return;lt.add(a.id);const n=a.type==="plan"?{type:"plan"}:void 0;await s({type:"text-start",id:a.id,...n?{textMetadata:n}:{}})},vt=async a=>{ct.has(a.id)||(ct.add(a.id),await s({type:"reasoning-start",id:a.id}))},zt=async a=>{const n=bt(a);if(n)return await s({type:"tool-input-available",toolCallId:w(f,l,a.id),toolName:"bash",input:re(n.command)}),!0;const u=St(a);if(u){for(const[b,C]of u.changes.entries())await s({type:"tool-input-available",toolCallId:w(f,l,a.id,String(b)),toolName:C.kind.type==="update"?"Edit":C.kind.type==="add"?"Add":"Delete",title:C.path,input:C});return!0}const g=kt(a);if(g)return await s({type:"tool-input-available",toolCallId:w(f,l,a.id),toolName:g.tool,input:g.arguments}),!0;const k=Tt(a);if(k)return await s({type:"tool-input-available",toolCallId:w(f,l,a.id),toolName:k.tool,input:k.arguments}),!0;const M=Rt(a);return M?(await s({type:"tool-input-available",toolCallId:w(f,l,a.id),toolName:"web_search",title:M.query,input:_t(M)}),!0):!1},Ht=async a=>{const n=bt(a);if(n){const b=n.status==="failed"||n.status==="declined";return await s(b?{type:"tool-output-error",toolCallId:w(f,l,a.id),errorText:ve(n.aggregatedOutput,"Command execution failed")}:{type:"tool-output-available",toolCallId:w(f,l,a.id),output:n.aggregatedOutput??""}),!0}const u=St(a);if(u){const b=u.status==="failed"||u.status==="declined";for(const[C]of u.changes.entries())await s(b?{type:"tool-output-error",toolCallId:w(f,l,a.id,String(C)),errorText:u.status}:{type:"tool-output-available",toolCallId:w(f,l,a.id,String(C)),output:u.status,preliminary:!1});return!0}const g=kt(a);if(g){const b=g.status==="failed";return await s(b?{type:"tool-output-error",toolCallId:w(f,l,a.id),errorText:g.error?.message??"MCP tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,a.id),output:ie(g)}),!0}const k=Tt(a);if(k){const b=k.status==="failed"||k.success===!1,C=oe(k);return await s(b?{type:"tool-output-error",toolCallId:w(f,l,a.id),errorText:C||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,a.id),output:C}),!0}const M=Rt(a);return M?(await s({type:"tool-output-available",toolCallId:w(f,l,a.id),output:_t(M)}),!0):!1};try{if(c&&!p.ownsOutput){await Pt(p,y,c.itemId,c.response),R.delete(K(r,c.itemId)),await X();return}const a=_?await me(p,y,_,tt,st):c?await fe(p,y,()=>y.continueWithUserInput(c.itemId,c.response,{signal:W.signal,sourceTurnId:c.sourceTurnId})):await pe(p,y,tt,st);if(c?R.delete(K(r,c.itemId)):_&&R.delete(K(r,_.toolCallId)),qt(p,y,a.turnId),!p.ownsOutput){await X();return}c?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:y.id,turnId:a.turnId}}),await s({type:"tool-output-available",toolCallId:c.itemId,toolName:"request_user_input",output:c.response})):_&&await s({type:"tool-output-error",toolCallId:_.toolCallId,toolName:"request_user_input",errorText:"Question interrupted by text response"});for await(const n of a.events)if(n.type!=="item.delta"&&xe(n),n.type==="thread.started")I=n.thread_id,ht(I);else if(n.type==="turn.started")h=n.turn_id,qt(p,y,h),await E(I,h);else if(n.type==="item.started"){if(x||await E(I,h),await It(n.item,"start")||await at(n.item)||await zt(n.item))continue;n.item.type==="agentMessage"||n.item.type==="plan"?await Ct(n.item):n.item.type==="reasoning"&&await vt(n.item)}else if(n.type==="item.delta")x||await E(I,h),n.item_type==="agentMessage"||n.item_type==="plan"?(F.add(n.item_id),await s({type:"text-delta",id:n.item_id,delta:n.delta})):n.item_type==="reasoning"&&(F.add(n.item_id),await s({type:"reasoning-delta",id:n.item_id,delta:n.delta}));else if(n.type==="item.updated"){if(await at(n.item))continue}else if(n.type==="item.completed"){if(x||await E(I,h),await It(n.item,"end")||await at(n.item)||await Ht(n.item))continue;if(n.item.type==="agentMessage"||n.item.type==="plan"){await Ct(n.item);const u=Et(n.item)??"";u&&!F.has(n.item.id)&&await s({type:"text-delta",id:n.item.id,delta:u}),await s({type:"text-end",id:n.item.id})}else if(n.item.type==="reasoning"){await vt(n.item);const u=Et(n.item)??"";u&&!F.has(n.item.id)&&await s({type:"reasoning-delta",id:n.item.id,delta:u}),await s({type:"reasoning-end",id:n.item.id})}}else if(n.type==="item.tool.requestUserInput"){if(x||await E(I,h),n.params.isBlocking===!1){if(!await Pt(p,y,n.params.itemId,{answers:{}}))throw new Error(`Non-blocking user input request is no longer pending: ${n.params.itemId}`);B.info(`Auto-resolved non-blocking request_user_input ${n.params.itemId} for turn ${n.params.turnId}`);continue}R.set(K(l,n.params.itemId),{conversationId:l,toolCallId:n.params.itemId,toolName:"request_user_input",turnId:n.params.turnId,since:new Date().toISOString()}),await s({type:"tool-input-available",toolCallId:n.params.itemId,toolName:"request_user_input",input:n.params}),await X();return}else if(n.type==="turn.diff.updated")x||await E(I,h),ot.set(Ut(l,y.id),n.diff);else if(n.type==="thread.tokenUsage.updated"){const u=se(n.usage);if(u){const g=ae(wt,u.last);g&&(wt=g,nt={...u,last:g})}}else if(n.type==="turn.failed"){mt!==n.error.message&&await s({type:"error",errorText:n.error.message}),await rt("error"),$=!0,Dt(p);return}else n.type==="error"&&(mt=n.message,await s({type:"error",errorText:n.message}));$=!W.signal.aborted,$&&Dt(p),x||await E(I,h),await rt("stop")}catch(a){const n=a instanceof Error?a.message:"Chat stream failed";x||await E(I,h),await s({type:"error",errorText:n}),await rt("error")}finally{P&&clearTimeout(P),O?.removeEventListener("abort",et),H.delete(W),$||Nt(p)}}}}async function Ae({body:t,headers:e,signal:r},o){await(await Lt({body:t,headers:e})).run(o,r)}z.post("/v1/chat/stream",Kt("json",Yt),async t=>{const e=t.req.valid("json"),r=await(async()=>{try{return await Lt({body:e,headers:t.req.raw.headers})}catch(o){throw o instanceof D?new q(o.status,{message:o.message}):o}})();return Qt(t,async o=>{const i=async d=>{await o.writeSSE(d)};await r.run({writeData:async d=>{await i({data:JSON.stringify(d)})},writeDone:async()=>{await i({data:"[DONE]"})}},t.req.raw.signal)})});var We=z;export{D as ChatRequestError,Ot as cancelCodexConversation,Le as codexActivitySnapshot,We as default,Ae as handleCodexChatStream,Lt as prepareCodexChatStream};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-agent/codex-agent",
3
- "version": "0.26.0",
3
+ "version": "0.27.0-alpha.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -15,14 +15,14 @@
15
15
  "@ai-sdk/openai": "^3.0.63",
16
16
  "@hono/node-server": "^2.0.2",
17
17
  "@hono/zod-validator": "^0.7.6",
18
- "@openai/codex": "^0.145.0",
18
+ "@openai/codex": "^0.147.0",
19
19
  "ai": "^6.0.182",
20
20
  "hono": "^4.12.18",
21
21
  "undici": "^8.4.1",
22
22
  "ws": "^8.21.0",
23
23
  "yaml": "^2.9.0",
24
24
  "zod": "^4.4.3",
25
- "@messenger-agent/shared": "0.26.0"
25
+ "@messenger-agent/shared": "0.27.0-alpha.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/ws": "^8.18.1"