@messenger-agent/codex-agent 0.43.0 → 0.43.1-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.
@@ -1,2 +1,2 @@
1
- import{Hono as Jt}from"hono";import{HTTPException as U}from"hono/http-exception";import{zValidator as Qt}from"@hono/zod-validator";import{streamSSE as Vt}from"hono/streaming";import{randomUUID as Gt}from"node:crypto";import{join as Yt}from"node:path";import{sessionManager as z,WorkdirNotFoundError as Zt}from"../codex.js";import{logger as H}from"@messenger-agent/shared/logger";import{ChatBodySchema as te,RequestUserInputToolPartSchema as ee}from"../schemas.js";import{appConfig as ot}from"../config.js";import{setManagedTaskToolContext as ae}from"../dynamic-tools.js";import{resolveAgentWorkspaceRoot as ne}from"@messenger-agent/shared/agent-workspaces";import{saveUploadedFilePart as re}from"@messenger-agent/shared/uploaded-files";import{WakeupIdempotencyStore as oe}from"@messenger-agent/shared/wakeup-idempotency";import{commandExecutionItem as Tt,addTokenUsage as ie,displayCommand as se,dynamicToolCallItem as St,dynamicToolCallOutput as de,fileChangeItem as kt,itemText as Et,mcpToolCallItem as Rt,mcpToolCallOutput as ue,toUsage as le,turnOptions as ce,webSearchItem as Ot,webSearchPayload as _t}from"./shared.js";const K=new Jt;let pe;function it(){return pe??=new oe(Yt(ot.dataDir,"wakeup-idempotency.sqlite"))}const m=new Map,J=new Set,st=new Map,_=new Map;function je(){const t=new Set([..._.values()].map(a=>a.conversationId).filter(a=>!!a));return{active:J.size,waiting:t.size}}function fe(t){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}}function me(t){const a=x(t,"X-Matrix-Room-Id")?.trim(),r=x(t,"X-Matrix-Thread-ID")?.trim();return a&&r?`${a}/${r}`:void 0}function we(t){const a=x(t,"X-Matrix-Room-Id")?.trim(),r=x(t,"X-Matrix-Event-ID")?.trim(),o=x(t,"X-Matrix-Thread-ID")?.trim();if(!(!a||!r))return{roomId:a,eventId:r,...o?{threadRootEventId:o}:null}}class W extends Error{status;constructor(a,r){super(r),this.status=a,this.name="ChatRequestError"}}function x(t,a){return t?.get(a)??void 0}async function qt(t){const a=m.get(t);if(!a)return!1;for(a.cancelRequested=!0,a.turnId||a.owner?.abortController.abort();;){const r=a.transition;if(await r,r===a.transition)break}return a.thread&&a.turnId&&(a.interrupt||(a.interrupt=a.thread.interrupt(a.turnId).finally(()=>{a.interrupt=void 0})),await a.interrupt),a.owner?.abortController.abort(),a.resolveOwnerReady?.(),m.get(t)===a&&m.delete(t),!0}function Mt(t){let a=m.get(t);a||(a={conversationId:t,generation:0,cancelRequested:!1,transition:Promise.resolve()},m.set(t,a));const r=++a.generation,o=new AbortController,i=!a.owner;return i&&(a.owner={generation:r,abortController:o},a.ownerReady=new Promise(d=>{a.resolveOwnerReady=d})),{state:a,generation:r,abortController:o,ownsOutput:i}}function L(t){return m.get(t.state.conversationId)===t.state&&t.state.owner?.generation===t.generation}async function Q(t,a){const r=t.transition;let o;const i=new Promise(d=>{o=d});t.transition=r.then(()=>i),await r;try{return await a()}finally{o()}}async function ye(t,a,r,o){!t.ownsOutput&&!t.state.turnId&&await t.state.ownerReady;try{return await Q(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=a;const d=i.turnId?await a.steerStreamed(r,i.turnId,{signal:t.abortController.signal}):await a.runStreamed(r,{...o,signal:t.abortController.signal});return i.turnId=d.turnId,d})}finally{t.ownsOutput&&t.state.resolveOwnerReady?.()}}async function ge(t,a,r){return Q(t.state,async()=>{const{state:o}=t;if(!L(t)||o.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");o.thread=a;const i=await r();return o.turnId=i.turnId,o.resolveOwnerReady?.(),i})}async function Pt(t,a,r,o){return Q(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=a;const d=await a.submitPendingUserInput(r,o);return d&&(i.turnId=d),d})}async function Ie(t,a,r,o,i){return Q(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=a,await a.interruptPendingUserInput(r.turnId,r.toolCallId),d.turnId=void 0;const h=await a.runStreamed(o,{...i,signal:t.abortController.signal});return d.turnId=h.turnId,h})}function Dt(t,a,r){t.state.thread=a,t.state.turnId=r}function Nt(t){const{state:a}=t;L(t)&&(a.owner=void 0,a.resolveOwnerReady?.(),a.turnId=void 0,m.get(a.conversationId)===a&&m.delete(a.conversationId))}function Ut(t){L(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 he(t){L(t)&&(Ut(t),t.state.resolveOwnerReady?.(),!t.state.turnId&&m.get(t.state.conversationId)===t.state&&m.delete(t.state.conversationId))}function V(t,a){return`${t??"unknown"}:${a}`}function ve(t){if(t)return[..._.values()].find(a=>a.conversationId===t)}function Wt(t,a){return t??a}function Ce(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 xe(t){if(t.type==="text"&&typeof t.text=="string")return t.text;if(Ce(t))return re(t,ot.fileUploads.tempDir);throw new U(400,{message:`Unsupported message part type: ${t.type}`})}async function be(t){const r=(await Promise.all(t.map(xe))).filter(o=>!!(o&&o.trim().length>0)).join(`
2
- `).trim();if(!r)throw new U(400,{message:"No supported message content found"});return r}function Te(t){if(!t.some(o=>o.type==="tool"))return;if(t.length!==1)throw new U(400,{message:"When a tool part is present, the message must contain exactly one part"});const r=ee.safeParse(t[0]);if(!r.success)throw new U(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,a,r,o){const i=[a,r,o].filter(Boolean).join(":"),d=t.get(i);if(d)return d;const h=Gt();return t.set(i,h),h}function Se(t,a){return t&&t.length>0?t:a}function ke(t){H.debug("Received chat stream event:",t)}function Ee(t){if(t instanceof Zt||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)}K.post("/v1/chat/:conversation_id/cancel",async t=>{const a=t.req.param("conversation_id");try{return await qt(a)?t.json({success:!0}):t.json({error:fe(a)},404)}catch(r){const o=r instanceof Error?r.message:"Failed to interrupt Codex conversation";return H.error(`Failed to cancel Codex conversation ${a}:`,r),t.json({error:{type:"server_error",code:"cancel_failed",message:o}},500)}}),K.get("/admin/status",t=>t.json({activeTaskCount:J.size,waitingClientToolCount:_.size,clientToolWaits:[..._.values()].map(({conversationId:a,toolCallId:r,toolName:o,since:i})=>({conversationId:a,toolCallId:r,toolName:o,since:i}))}));async function Lt({body:t,headers:a}){const{conversationId:r,fork:o,model:i,effort:d,message:h}=t,q=x(a,"X-Matrix-Bot-Name"),M=x(a,"X-Matrix-Workdir"),k=x(a,"X-Matrix-Workspace-ID"),$t=x(a,"X-Matrix-Agent-Mode"),G=x(a,"X-Elevo-LLM-Binding")?.trim()||void 0,Y=o?void 0:z.bindingFor(r);if(G&&Y&&G!==Y)throw new W(409,"LLM proxy binding does not match the existing conversation");const $=Y??G,At=me(a),Ft=we(a),Z=Te(h.parts),tt=Z?void 0:await be(h.parts),dt=Z?void 0:ve(r),p=Z,P=tt&&dt?dt:void 0;let A=!o&&r?Mt(r):void 0;const Xt=(()=>{try{return ne(ot,k)}catch(E){throw new W(400,E instanceof Error?E.message:"Invalid workspace")}})(),{thread:y,isNewSession:jt}=await(async()=>{try{if(o){if(!("threadId"in o))throw new W(400,"Codex fork requires threadId and turnId");return await z.fork(o,q,M,k,i,"danger-full-access",$)}return await z.getOrCreate(r,q,M,k,i,"danger-full-access",$)}catch(E){A&&he(A);const D=Ee(E);throw D?new W(400,`Workdir not found: ${D}`):E}})();A??=Mt(y.id),ae(y.id,{workspaceRoot:Xt,threadRef:At,matrixEvent:Ft,llmProxyBinding:$});const ut=ce($t,i,d);return{run:async(E,D)=>{const c=A,F=c.abortController;J.add(F);const et=()=>{L(c)&&qt(c.state.conversationId).catch(n=>H.error("Failed to cancel disconnected Codex stream:",n))};D?.aborted&&et(),D?.addEventListener("abort",et,{once:!0});let T=!!p,lt=!!p,ct=!1,l,I=y.id,v,X=!1,N;const pt=new Set,ft=new Set,j=new Set,f=new Map,mt=new Set,Bt=new Set,wt=new Set;let yt,at,gt;const It=()=>{N=setTimeout(()=>{s({type:"heartbeat"}).catch(()=>{})},6e4)},ht=async n=>{N&&clearTimeout(N),n.data==="[DONE]"?await E.writeDone():await E.writeData(JSON.parse(String(n.data))),It()},s=async n=>{await ht({data:JSON.stringify(n)})},B=async()=>{await ht({data:"[DONE]"})},zt=async()=>{const n=Wt(l,y.id),e=st.get(n);e&&(st.delete(n),await s({type:"data",kind:"diff",data:{diff:e}}))},vt=async(n,e)=>{if(n.type!=="contextCompaction")return!1;if(e==="end"&&!mt.has(n.id))return!0;const u=e==="start"?mt:Bt;return u.has(n.id)||(u.add(n.id),await s({type:"data",kind:"compact",data:e==="start"?{phase:e,activityId:n.id}:{phase:e,activityId:n.id,status:"completed"}})),!0},nt=async n=>{if(n.type!=="subAgentActivity")return!1;if(wt.has(n.id))return!0;wt.add(n.id);const e=w(f,l,n.id),u={activity:n.kind,agentThreadId:n.agentThreadId,...n.agentPath?{agentPath:n.agentPath}:null};return await s({type:"tool-input-available",toolCallId:e,toolName:`subagent_${n.kind}`,...n.agentPath?{title:n.agentPath}:null,input:u}),await s(n.kind==="interrupted"?{type:"tool-output-error",toolCallId:e,errorText:"Subagent interrupted"}:{type:"tool-output-available",toolCallId:e,output:u}),!0};It();const Ct=n=>{l!==n&&(l=n,jt&&z.persist(n,q,M,k,$))},R=async(n,e)=>{T||(T=!0,Ct(n),await s({type:"start",messageMetadata:{conversationId:n,threadId:n,...e?{turnId:e}:null}}),await s({type:"start-step"}),lt=!0)},rt=async n=>{ct||(ct=!0,lt&&(await zt(),n==="stop"&&at&&await s({type:"data",kind:"token_usage",data:at}),await s({type:"finish-step"})),await s({type:"finish",finishReason:n}),await B())},xt=async n=>{if(pt.has(n.id))return;pt.add(n.id);const e=n.type==="plan"?{type:"plan"}:void 0;await s({type:"text-start",id:n.id,...e?{textMetadata:e}:{}})},bt=async n=>{ft.has(n.id)||(ft.add(n.id),await s({type:"reasoning-start",id:n.id}))},Ht=async n=>{const e=Tt(n);if(e)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"bash",input:se(e.command)}),!0;const u=kt(n);if(u){for(const[S,b]of u.changes.entries())await s({type:"tool-input-available",toolCallId:w(f,l,n.id,String(S)),toolName:b.kind.type==="update"?"Edit":b.kind.type==="add"?"Add":"Delete",title:b.path,input:b});return!0}const g=Rt(n);if(g)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:g.tool,input:g.arguments}),!0;const C=St(n);if(C)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:C.tool,input:C.arguments}),!0;const O=Ot(n);return O?(await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"web_search",title:O.query,input:_t(O)}),!0):!1},Kt=async n=>{const e=Tt(n);if(e){const S=e.status==="failed"||e.status==="declined";return await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:Se(e.aggregatedOutput,"Command execution failed")}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:e.aggregatedOutput??""}),!0}const u=kt(n);if(u){const S=u.status==="failed"||u.status==="declined";for(const[b]of u.changes.entries())await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id,String(b)),errorText:u.status}:{type:"tool-output-available",toolCallId:w(f,l,n.id,String(b)),output:u.status,preliminary:!1});return!0}const g=Rt(n);if(g){const S=g.status==="failed";return await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:g.error?.message??"MCP tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:ue(g)}),!0}const C=St(n);if(C){const S=C.status==="failed"||C.success===!1,b=de(C);return await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:b||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:b}),!0}const O=Ot(n);return O?(await s({type:"tool-output-available",toolCallId:w(f,l,n.id),output:_t(O)}),!0):!1};try{if(p&&!c.ownsOutput){await Pt(c,y,p.itemId,p.response),_.delete(V(r,p.itemId)),await B();return}const n=P?await Ie(c,y,P,tt,ut):p?await ge(c,y,()=>y.continueWithUserInput(p.itemId,p.response,{signal:F.signal,sourceTurnId:p.sourceTurnId})):await ye(c,y,tt,ut);if(p?_.delete(V(r,p.itemId)):P&&_.delete(V(r,P.toolCallId)),Dt(c,y,n.turnId),!c.ownsOutput){await B();return}p?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:y.id,turnId:n.turnId}}),await s({type:"tool-output-available",toolCallId:p.itemId,toolName:"request_user_input",output:p.response})):P&&await s({type:"tool-output-error",toolCallId:P.toolCallId,toolName:"request_user_input",errorText:"Question interrupted by text response"});for await(const e of n.events)if(e.type!=="item.delta"&&ke(e),e.type==="thread.started")I=e.thread_id,Ct(I);else if(e.type==="turn.started")v=e.turn_id,Dt(c,y,v),await R(I,v);else if(e.type==="item.started"){if(T||await R(I,v),await vt(e.item,"start")||await nt(e.item)||await Ht(e.item))continue;e.item.type==="agentMessage"||e.item.type==="plan"?await xt(e.item):e.item.type==="reasoning"&&await bt(e.item)}else if(e.type==="item.delta")T||await R(I,v),e.item_type==="agentMessage"||e.item_type==="plan"?(j.add(e.item_id),await s({type:"text-delta",id:e.item_id,delta:e.delta})):e.item_type==="reasoning"&&(j.add(e.item_id),await s({type:"reasoning-delta",id:e.item_id,delta:e.delta}));else if(e.type==="item.updated"){if(await nt(e.item))continue}else if(e.type==="item.completed"){if(T||await R(I,v),await vt(e.item,"end")||await nt(e.item)||await Kt(e.item))continue;if(e.item.type==="agentMessage"||e.item.type==="plan"){await xt(e.item);const u=Et(e.item)??"";if(u&&!j.has(e.item.id)&&await s({type:"text-delta",id:e.item.id,delta:u}),await s({type:"text-end",id:e.item.id,...e.item.type==="agentMessage"&&e.item.phase!=null?{phase:e.item.phase}:{}}),e.item.type==="agentMessage"&&e.item.delivery==="async"&&e.item.questions?.length){const g=`${l??I}:${e.item.id}`;await s({type:"async-question",request:{version:1,requestId:g,conversationId:l??I,threadId:I,turnId:v??c.state.turnId??"",toolCallId:e.item.id,questions:e.item.questions.map((C,O)=>({id:`${e.item.id}:${O}`,title:C.title,options:C.options,allowOther:!0}))}})}}else if(e.item.type==="reasoning"){await bt(e.item);const u=Et(e.item)??"";u&&!j.has(e.item.id)&&await s({type:"reasoning-delta",id:e.item.id,delta:u}),await s({type:"reasoning-end",id:e.item.id})}}else if(e.type==="item.tool.requestUserInput"){if(T||await R(I,v),e.params.isBlocking===!1){if(!await Pt(c,y,e.params.itemId,{answers:{}}))throw new Error(`Non-blocking user input request is no longer pending: ${e.params.itemId}`);H.info(`Auto-resolved non-blocking request_user_input ${e.params.itemId} for turn ${e.params.turnId}`);continue}_.set(V(l,e.params.itemId),{conversationId:l,toolCallId:e.params.itemId,toolName:"request_user_input",turnId:e.params.turnId,since:new Date().toISOString()}),await s({type:"tool-input-available",toolCallId:e.params.itemId,toolName:"request_user_input",input:e.params}),await B();return}else if(e.type==="turn.diff.updated")T||await R(I,v),st.set(Wt(l,y.id),e.diff);else if(e.type==="thread.tokenUsage.updated"){const u=le(e.usage);if(u){const g=ie(gt,u.last);g&&(gt=g,at={...u,last:g})}}else if(e.type==="turn.failed"){yt!==e.error.message&&await s({type:"error",errorText:e.error.message}),await rt("error"),X=!0,Nt(c);return}else e.type==="error"&&(yt=e.message,await s({type:"error",errorText:e.message}));X=!F.signal.aborted,X&&Nt(c),T||await R(I,v),await rt("stop")}catch(n){const e=n instanceof Error?n.message:"Chat stream failed";T||await R(I,v),await s({type:"error",errorText:e}),await rt("error")}finally{N&&clearTimeout(N),D?.removeEventListener("abort",et),J.delete(F),X||Ut(c)}}}}async function Be({body:t,headers:a,signal:r},o){await(await Lt({body:t,headers:a})).run(o,r)}K.post("/v1/chat/stream",Qt("json",te),async t=>{const a=t.req.valid("json"),r=x(t.req.raw.headers,"X-Elevo-Wakeup-Key")?.trim()||void 0;let o;if(r){const d=it().claim(r);if(d.status!=="claimed")return t.header("X-Elevo-Wakeup-Duplicate","true"),t.json({error:"Scheduled wakeup was already accepted"},409);o=d.token}let i;try{i=await Lt({body:a,headers:t.req.raw.headers})}catch(d){throw r&&o&&it().release(r,o),d instanceof W?new U(d.status,{message:d.message}):d}return Vt(t,async d=>{let h=!1;const q=()=>{!r||!o||h||(h=!0,it().markStarted(r,o))},M=async k=>{await d.writeSSE(k)};await i.run({writeData:async k=>{q(),await M({data:JSON.stringify(k)})},writeDone:async()=>{q(),await M({data:"[DONE]"})}},t.req.raw.signal)})});var ze=K;export{W as ChatRequestError,qt as cancelCodexConversation,je as codexActivitySnapshot,ze as default,Be as handleCodexChatStream,Lt as prepareCodexChatStream};
1
+ import{Hono as Jt}from"hono";import{HTTPException as U}from"hono/http-exception";import{zValidator as Qt}from"@hono/zod-validator";import{streamSSE as Vt}from"hono/streaming";import{randomUUID as Gt}from"node:crypto";import{join as Yt}from"node:path";import{sessionManager as z,WorkdirNotFoundError as Zt}from"../codex.js";import{logger as H}from"@messenger-agent/shared/logger";import{ChatBodySchema as te,RequestUserInputToolPartSchema as ee}from"../schemas.js";import{appConfig as ot}from"../config.js";import{setManagedTaskToolContext as ae}from"../dynamic-tools.js";import{resolveAgentWorkspaceRoot as ne}from"@messenger-agent/shared/agent-workspaces";import{saveUploadedFilePart as re}from"@messenger-agent/shared/uploaded-files";import{WakeupIdempotencyStore as oe}from"@messenger-agent/shared/wakeup-idempotency";import{commandExecutionItem as St,addTokenUsage as ie,displayCommand as se,dynamicToolCallItem as kt,dynamicToolCallOutput as de,fileChangeItem as Et,itemText as Rt,mcpToolCallItem as Ot,mcpToolCallOutput as ue,toUsage as le,turnOptions as ce,webSearchItem as _t,webSearchPayload as qt}from"./shared.js";const K=new Jt;let pe;function it(){return pe??=new oe(Yt(ot.dataDir,"wakeup-idempotency.sqlite"))}const m=new Map,J=new Set,st=new Map,_=new Map;function je(){const t=new Set([..._.values()].map(a=>a.conversationId).filter(a=>!!a));return{active:J.size,waiting:t.size}}function fe(t){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}}function me(t){const a=x(t,"X-Matrix-Room-Id")?.trim(),r=x(t,"X-Matrix-Thread-ID")?.trim();return a&&r?`${a}/${r}`:void 0}function we(t){const a=x(t,"X-Matrix-Room-Id")?.trim(),r=x(t,"X-Matrix-Event-ID")?.trim(),o=x(t,"X-Matrix-Thread-ID")?.trim();if(!(!a||!r))return{roomId:a,eventId:r,...o?{threadRootEventId:o}:null}}class W extends Error{status;constructor(a,r){super(r),this.status=a,this.name="ChatRequestError"}}function x(t,a){return t?.get(a)??void 0}async function Mt(t){const a=m.get(t);if(!a)return!1;for(a.cancelRequested=!0,a.turnId||a.owner?.abortController.abort();;){const r=a.transition;if(await r,r===a.transition)break}return a.thread&&a.turnId&&(a.interrupt||(a.interrupt=a.thread.interrupt(a.turnId).finally(()=>{a.interrupt=void 0})),await a.interrupt),a.owner?.abortController.abort(),a.resolveOwnerReady?.(),m.get(t)===a&&m.delete(t),!0}function Pt(t){let a=m.get(t);a||(a={conversationId:t,generation:0,cancelRequested:!1,transition:Promise.resolve()},m.set(t,a));const r=++a.generation,o=new AbortController,i=!a.owner;return i&&(a.owner={generation:r,abortController:o},a.ownerReady=new Promise(d=>{a.resolveOwnerReady=d})),{state:a,generation:r,abortController:o,ownsOutput:i}}function L(t){return m.get(t.state.conversationId)===t.state&&t.state.owner?.generation===t.generation}async function Q(t,a){const r=t.transition;let o;const i=new Promise(d=>{o=d});t.transition=r.then(()=>i),await r;try{return await a()}finally{o()}}async function ye(t,a,r,o){!t.ownsOutput&&!t.state.turnId&&await t.state.ownerReady;try{return await Q(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=a;const d=i.turnId?await a.steerStreamed(r,i.turnId,{signal:t.abortController.signal}):await a.runStreamed(r,{...o,signal:t.abortController.signal});return i.turnId=d.turnId,d})}finally{t.ownsOutput&&t.state.resolveOwnerReady?.()}}async function ge(t,a,r){return Q(t.state,async()=>{const{state:o}=t;if(!L(t)||o.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");o.thread=a;const i=await r();return o.turnId=i.turnId,o.resolveOwnerReady?.(),i})}async function Dt(t,a,r,o){return Q(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=a;const d=await a.submitPendingUserInput(r,o);return d&&(i.turnId=d),d})}async function Ie(t,a,r,o,i){return Q(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=a,await a.interruptPendingUserInput(r.turnId,r.toolCallId),d.turnId=void 0;const h=await a.runStreamed(o,{...i,signal:t.abortController.signal});return d.turnId=h.turnId,h})}function Nt(t,a,r){t.state.thread=a,t.state.turnId=r}function Ut(t){const{state:a}=t;L(t)&&(a.owner=void 0,a.resolveOwnerReady?.(),a.turnId=void 0,m.get(a.conversationId)===a&&m.delete(a.conversationId))}function Wt(t){L(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 he(t){L(t)&&(Wt(t),t.state.resolveOwnerReady?.(),!t.state.turnId&&m.get(t.state.conversationId)===t.state&&m.delete(t.state.conversationId))}function V(t,a){return`${t??"unknown"}:${a}`}function ve(t){if(t)return[..._.values()].find(a=>a.conversationId===t)}function Lt(t,a){return t??a}function Ce(t){return t.type==="file"&&"mediaType"in t&&typeof t.mediaType=="string"&&"url"in t&&typeof t.url=="string"&&(!("attachmentId"in t)||t.attachmentId===void 0||typeof t.attachmentId=="string")&&(!("filename"in t)||t.filename===void 0||typeof t.filename=="string")}async function xe(t,a){if(t.type==="text"&&typeof t.text=="string")return t.text;if(Ce(t))return re(t,ot.fileUploads.tempDir,a);throw new U(400,{message:`Unsupported message part type: ${t.type}`})}async function be(t,a){const o=(await Promise.all(t.map(i=>xe(i,a)))).filter(i=>!!(i&&i.trim().length>0)).join(`
2
+ `).trim();if(!o)throw new U(400,{message:"No supported message content found"});return o}function Te(t){if(!t.some(o=>o.type==="tool"))return;if(t.length!==1)throw new U(400,{message:"When a tool part is present, the message must contain exactly one part"});const r=ee.safeParse(t[0]);if(!r.success)throw new U(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,a,r,o){const i=[a,r,o].filter(Boolean).join(":"),d=t.get(i);if(d)return d;const h=Gt();return t.set(i,h),h}function Se(t,a){return t&&t.length>0?t:a}function ke(t){H.debug("Received chat stream event:",t)}function Ee(t){if(t instanceof Zt||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)}K.post("/v1/chat/:conversation_id/cancel",async t=>{const a=t.req.param("conversation_id");try{return await Mt(a)?t.json({success:!0}):t.json({error:fe(a)},404)}catch(r){const o=r instanceof Error?r.message:"Failed to interrupt Codex conversation";return H.error(`Failed to cancel Codex conversation ${a}:`,r),t.json({error:{type:"server_error",code:"cancel_failed",message:o}},500)}}),K.get("/admin/status",t=>t.json({activeTaskCount:J.size,waitingClientToolCount:_.size,clientToolWaits:[..._.values()].map(({conversationId:a,toolCallId:r,toolName:o,since:i})=>({conversationId:a,toolCallId:r,toolName:o,since:i}))}));async function $t({body:t,headers:a}){const{conversationId:r,fork:o,model:i,effort:d,message:h}=t,q=x(a,"X-Matrix-Bot-Name"),M=x(a,"X-Matrix-Workdir"),k=x(a,"X-Matrix-Workspace-ID"),At=x(a,"X-Matrix-Agent-Mode"),G=x(a,"X-Elevo-LLM-Binding")?.trim()||void 0,Y=o?void 0:z.bindingFor(r);if(G&&Y&&G!==Y)throw new W(409,"LLM proxy binding does not match the existing conversation");const $=Y??G,Ft=me(a),Xt=we(a),Z=Te(h.parts),dt=(()=>{try{return ne(ot,k)}catch(E){throw new W(400,E instanceof Error?E.message:"Invalid workspace")}})(),tt=Z?void 0:await be(h.parts,dt),ut=Z?void 0:ve(r),p=Z,P=tt&&ut?ut:void 0;let A=!o&&r?Pt(r):void 0;const{thread:y,isNewSession:jt}=await(async()=>{try{if(o){if(!("threadId"in o))throw new W(400,"Codex fork requires threadId and turnId");return await z.fork(o,q,M,k,i,"danger-full-access",$)}return await z.getOrCreate(r,q,M,k,i,"danger-full-access",$)}catch(E){A&&he(A);const D=Ee(E);throw D?new W(400,`Workdir not found: ${D}`):E}})();A??=Pt(y.id),ae(y.id,{workspaceRoot:dt,threadRef:Ft,matrixEvent:Xt,llmProxyBinding:$});const lt=ce(At,i,d);return{run:async(E,D)=>{const c=A,F=c.abortController;J.add(F);const et=()=>{L(c)&&Mt(c.state.conversationId).catch(n=>H.error("Failed to cancel disconnected Codex stream:",n))};D?.aborted&&et(),D?.addEventListener("abort",et,{once:!0});let T=!!p,ct=!!p,pt=!1,l,I=y.id,v,X=!1,N;const ft=new Set,mt=new Set,j=new Set,f=new Map,wt=new Set,Bt=new Set,yt=new Set;let gt,at,It;const ht=()=>{N=setTimeout(()=>{s({type:"heartbeat"}).catch(()=>{})},6e4)},vt=async n=>{N&&clearTimeout(N),n.data==="[DONE]"?await E.writeDone():await E.writeData(JSON.parse(String(n.data))),ht()},s=async n=>{await vt({data:JSON.stringify(n)})},B=async()=>{await vt({data:"[DONE]"})},zt=async()=>{const n=Lt(l,y.id),e=st.get(n);e&&(st.delete(n),await s({type:"data",kind:"diff",data:{diff:e}}))},Ct=async(n,e)=>{if(n.type!=="contextCompaction")return!1;if(e==="end"&&!wt.has(n.id))return!0;const u=e==="start"?wt:Bt;return u.has(n.id)||(u.add(n.id),await s({type:"data",kind:"compact",data:e==="start"?{phase:e,activityId:n.id}:{phase:e,activityId:n.id,status:"completed"}})),!0},nt=async n=>{if(n.type!=="subAgentActivity")return!1;if(yt.has(n.id))return!0;yt.add(n.id);const e=w(f,l,n.id),u={activity:n.kind,agentThreadId:n.agentThreadId,...n.agentPath?{agentPath:n.agentPath}:null};return await s({type:"tool-input-available",toolCallId:e,toolName:`subagent_${n.kind}`,...n.agentPath?{title:n.agentPath}:null,input:u}),await s(n.kind==="interrupted"?{type:"tool-output-error",toolCallId:e,errorText:"Subagent interrupted"}:{type:"tool-output-available",toolCallId:e,output:u}),!0};ht();const xt=n=>{l!==n&&(l=n,jt&&z.persist(n,q,M,k,$))},R=async(n,e)=>{T||(T=!0,xt(n),await s({type:"start",messageMetadata:{conversationId:n,threadId:n,...e?{turnId:e}:null}}),await s({type:"start-step"}),ct=!0)},rt=async n=>{pt||(pt=!0,ct&&(await zt(),n==="stop"&&at&&await s({type:"data",kind:"token_usage",data:at}),await s({type:"finish-step"})),await s({type:"finish",finishReason:n}),await B())},bt=async n=>{if(ft.has(n.id))return;ft.add(n.id);const e=n.type==="plan"?{type:"plan"}:void 0;await s({type:"text-start",id:n.id,...e?{textMetadata:e}:{}})},Tt=async n=>{mt.has(n.id)||(mt.add(n.id),await s({type:"reasoning-start",id:n.id}))},Ht=async n=>{const e=St(n);if(e)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"bash",input:se(e.command)}),!0;const u=Et(n);if(u){for(const[S,b]of u.changes.entries())await s({type:"tool-input-available",toolCallId:w(f,l,n.id,String(S)),toolName:b.kind.type==="update"?"Edit":b.kind.type==="add"?"Add":"Delete",title:b.path,input:b});return!0}const g=Ot(n);if(g)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:g.tool,input:g.arguments}),!0;const C=kt(n);if(C)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:C.tool,input:C.arguments}),!0;const O=_t(n);return O?(await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"web_search",title:O.query,input:qt(O)}),!0):!1},Kt=async n=>{const e=St(n);if(e){const S=e.status==="failed"||e.status==="declined";return await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:Se(e.aggregatedOutput,"Command execution failed")}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:e.aggregatedOutput??""}),!0}const u=Et(n);if(u){const S=u.status==="failed"||u.status==="declined";for(const[b]of u.changes.entries())await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id,String(b)),errorText:u.status}:{type:"tool-output-available",toolCallId:w(f,l,n.id,String(b)),output:u.status,preliminary:!1});return!0}const g=Ot(n);if(g){const S=g.status==="failed";return await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:g.error?.message??"MCP tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:ue(g)}),!0}const C=kt(n);if(C){const S=C.status==="failed"||C.success===!1,b=de(C);return await s(S?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:b||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:b}),!0}const O=_t(n);return O?(await s({type:"tool-output-available",toolCallId:w(f,l,n.id),output:qt(O)}),!0):!1};try{if(p&&!c.ownsOutput){await Dt(c,y,p.itemId,p.response),_.delete(V(r,p.itemId)),await B();return}const n=P?await Ie(c,y,P,tt,lt):p?await ge(c,y,()=>y.continueWithUserInput(p.itemId,p.response,{signal:F.signal,sourceTurnId:p.sourceTurnId})):await ye(c,y,tt,lt);if(p?_.delete(V(r,p.itemId)):P&&_.delete(V(r,P.toolCallId)),Nt(c,y,n.turnId),!c.ownsOutput){await B();return}p?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:y.id,turnId:n.turnId}}),await s({type:"tool-output-available",toolCallId:p.itemId,toolName:"request_user_input",output:p.response})):P&&await s({type:"tool-output-error",toolCallId:P.toolCallId,toolName:"request_user_input",errorText:"Question interrupted by text response"});for await(const e of n.events)if(e.type!=="item.delta"&&ke(e),e.type==="thread.started")I=e.thread_id,xt(I);else if(e.type==="turn.started")v=e.turn_id,Nt(c,y,v),await R(I,v);else if(e.type==="item.started"){if(T||await R(I,v),await Ct(e.item,"start")||await nt(e.item)||await Ht(e.item))continue;e.item.type==="agentMessage"||e.item.type==="plan"?await bt(e.item):e.item.type==="reasoning"&&await Tt(e.item)}else if(e.type==="item.delta")T||await R(I,v),e.item_type==="agentMessage"||e.item_type==="plan"?(j.add(e.item_id),await s({type:"text-delta",id:e.item_id,delta:e.delta})):e.item_type==="reasoning"&&(j.add(e.item_id),await s({type:"reasoning-delta",id:e.item_id,delta:e.delta}));else if(e.type==="item.updated"){if(await nt(e.item))continue}else if(e.type==="item.completed"){if(T||await R(I,v),await Ct(e.item,"end")||await nt(e.item)||await Kt(e.item))continue;if(e.item.type==="agentMessage"||e.item.type==="plan"){await bt(e.item);const u=Rt(e.item)??"";if(u&&!j.has(e.item.id)&&await s({type:"text-delta",id:e.item.id,delta:u}),await s({type:"text-end",id:e.item.id,...e.item.type==="agentMessage"&&e.item.phase!=null?{phase:e.item.phase}:{}}),e.item.type==="agentMessage"&&e.item.delivery==="async"&&e.item.questions?.length){const g=`${l??I}:${e.item.id}`;await s({type:"async-question",request:{version:1,requestId:g,conversationId:l??I,threadId:I,turnId:v??c.state.turnId??"",toolCallId:e.item.id,questions:e.item.questions.map((C,O)=>({id:`${e.item.id}:${O}`,title:C.title,options:C.options,allowOther:!0}))}})}}else if(e.item.type==="reasoning"){await Tt(e.item);const u=Rt(e.item)??"";u&&!j.has(e.item.id)&&await s({type:"reasoning-delta",id:e.item.id,delta:u}),await s({type:"reasoning-end",id:e.item.id})}}else if(e.type==="item.tool.requestUserInput"){if(T||await R(I,v),e.params.isBlocking===!1){if(!await Dt(c,y,e.params.itemId,{answers:{}}))throw new Error(`Non-blocking user input request is no longer pending: ${e.params.itemId}`);H.info(`Auto-resolved non-blocking request_user_input ${e.params.itemId} for turn ${e.params.turnId}`);continue}_.set(V(l,e.params.itemId),{conversationId:l,toolCallId:e.params.itemId,toolName:"request_user_input",turnId:e.params.turnId,since:new Date().toISOString()}),await s({type:"tool-input-available",toolCallId:e.params.itemId,toolName:"request_user_input",input:e.params}),await B();return}else if(e.type==="turn.diff.updated")T||await R(I,v),st.set(Lt(l,y.id),e.diff);else if(e.type==="thread.tokenUsage.updated"){const u=le(e.usage);if(u){const g=ie(It,u.last);g&&(It=g,at={...u,last:g})}}else if(e.type==="turn.failed"){gt!==e.error.message&&await s({type:"error",errorText:e.error.message}),await rt("error"),X=!0,Ut(c);return}else e.type==="error"&&(gt=e.message,await s({type:"error",errorText:e.message}));X=!F.signal.aborted,X&&Ut(c),T||await R(I,v),await rt("stop")}catch(n){const e=n instanceof Error?n.message:"Chat stream failed";T||await R(I,v),await s({type:"error",errorText:e}),await rt("error")}finally{N&&clearTimeout(N),D?.removeEventListener("abort",et),J.delete(F),X||Wt(c)}}}}async function Be({body:t,headers:a,signal:r},o){await(await $t({body:t,headers:a})).run(o,r)}K.post("/v1/chat/stream",Qt("json",te),async t=>{const a=t.req.valid("json"),r=x(t.req.raw.headers,"X-Elevo-Wakeup-Key")?.trim()||void 0;let o;if(r){const d=it().claim(r);if(d.status!=="claimed")return t.header("X-Elevo-Wakeup-Duplicate","true"),t.json({error:"Scheduled wakeup was already accepted"},409);o=d.token}let i;try{i=await $t({body:a,headers:t.req.raw.headers})}catch(d){throw r&&o&&it().release(r,o),d instanceof W?new U(d.status,{message:d.message}):d}return Vt(t,async d=>{let h=!1;const q=()=>{!r||!o||h||(h=!0,it().markStarted(r,o))},M=async k=>{await d.writeSSE(k)};await i.run({writeData:async k=>{q(),await M({data:JSON.stringify(k)})},writeDone:async()=>{q(),await M({data:"[DONE]"})}},t.req.raw.signal)})});var ze=K;export{W as ChatRequestError,Mt as cancelCodexConversation,je as codexActivitySnapshot,ze as default,Be as handleCodexChatStream,$t as prepareCodexChatStream};
package/dist/schemas.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare const ChatFilePartSchema: z.ZodObject<{
7
7
  type: z.ZodLiteral<"file">;
8
8
  mediaType: z.ZodString;
9
9
  filename: z.ZodOptional<z.ZodString>;
10
+ attachmentId: z.ZodOptional<z.ZodString>;
10
11
  url: z.ZodString;
11
12
  }, z.core.$strip>;
12
13
  export declare const ChatStepStartPartSchema: z.ZodObject<{
@@ -82,6 +83,7 @@ export declare const ChatBodySchema: z.ZodObject<{
82
83
  type: z.ZodLiteral<"file">;
83
84
  mediaType: z.ZodString;
84
85
  filename: z.ZodOptional<z.ZodString>;
86
+ attachmentId: z.ZodOptional<z.ZodString>;
85
87
  url: z.ZodString;
86
88
  }, z.core.$strip>, z.ZodObject<{
87
89
  type: z.ZodLiteral<"step-start">;
package/dist/schemas.js CHANGED
@@ -1 +1 @@
1
- import{z as t}from"zod";const o=t.object({type:t.literal("text"),text:t.string()}),e=t.object({type:t.literal("file"),mediaType:t.string(),filename:t.string().optional(),url:t.string()}),r=t.object({type:t.literal("step-start")}),n=t.object({type:t.literal("tool"),toolCallId:t.string(),toolName:t.string(),title:t.string().optional(),state:t.string(),input:t.unknown().optional(),output:t.unknown().optional(),approval:t.object({id:t.string(),approved:t.boolean().optional(),reason:t.string().optional()}).optional()}),a=t.looseObject({type:t.string()}),s=t.strictObject({threadId:t.string().min(1),turnId:t.string().min(1)}),i=t.strictObject({sessionId:t.string().min(1),messageId:t.string().min(1)}),l=t.union([s,i]),p=t.enum(["low","medium","high","xhigh","max"]),g=t.object({conversationId:t.string().optional(),fork:l.optional(),model:t.string(),effort:p.default("medium"),message:t.object({id:t.string().optional(),role:t.enum(["user","assistant"]).optional(),parts:t.array(t.union([o,e,r,n,a]))})}),c=t.object({answers:t.array(t.string())}),m=t.object({answers:t.record(t.string(),c)}),h=t.object({type:t.literal("tool"),toolCallId:t.string(),toolName:t.literal("request_user_input"),state:t.literal("output-available"),sourceTurnId:t.string().min(1).optional(),output:m});export{p as AgentEffortSchema,g as ChatBodySchema,a as ChatCustomPartSchema,e as ChatFilePartSchema,l as ChatForkSchema,r as ChatStepStartPartSchema,o as ChatTextPartSchema,n as ChatToolPartSchema,i as ClaudeChatForkSchema,s as CodexChatForkSchema,h as RequestUserInputToolPartSchema,c as ToolRequestUserInputAnswerSchema,m as ToolRequestUserInputResponseSchema};
1
+ import{z as t}from"zod";const o=t.object({type:t.literal("text"),text:t.string()}),e=t.object({type:t.literal("file"),mediaType:t.string(),filename:t.string().optional(),attachmentId:t.string().min(1).optional(),url:t.string()}),n=t.object({type:t.literal("step-start")}),r=t.object({type:t.literal("tool"),toolCallId:t.string(),toolName:t.string(),title:t.string().optional(),state:t.string(),input:t.unknown().optional(),output:t.unknown().optional(),approval:t.object({id:t.string(),approved:t.boolean().optional(),reason:t.string().optional()}).optional()}),a=t.looseObject({type:t.string()}),s=t.strictObject({threadId:t.string().min(1),turnId:t.string().min(1)}),i=t.strictObject({sessionId:t.string().min(1),messageId:t.string().min(1)}),l=t.union([s,i]),p=t.enum(["low","medium","high","xhigh","max"]),g=t.object({conversationId:t.string().optional(),fork:l.optional(),model:t.string(),effort:p.default("medium"),message:t.object({id:t.string().optional(),role:t.enum(["user","assistant"]).optional(),parts:t.array(t.union([o,e,n,r,a]))})}),c=t.object({answers:t.array(t.string())}),m=t.object({answers:t.record(t.string(),c)}),h=t.object({type:t.literal("tool"),toolCallId:t.string(),toolName:t.literal("request_user_input"),state:t.literal("output-available"),sourceTurnId:t.string().min(1).optional(),output:m});export{p as AgentEffortSchema,g as ChatBodySchema,a as ChatCustomPartSchema,e as ChatFilePartSchema,l as ChatForkSchema,n as ChatStepStartPartSchema,o as ChatTextPartSchema,r as ChatToolPartSchema,i as ClaudeChatForkSchema,s as CodexChatForkSchema,h as RequestUserInputToolPartSchema,c as ToolRequestUserInputAnswerSchema,m as ToolRequestUserInputResponseSchema};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-agent/codex-agent",
3
- "version": "0.43.0",
3
+ "version": "0.43.1-alpha.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "ws": "^8.21.0",
23
23
  "yaml": "^2.9.0",
24
24
  "zod": "^4.4.3",
25
- "@messenger-agent/shared": "0.43.0"
25
+ "@messenger-agent/shared": "0.43.1-alpha.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/ws": "^8.18.1"