@messenger-agent/codex-agent 0.38.0 → 0.39.0-alpha.1
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.
|
@@ -185,6 +185,10 @@ export type ContextCompactionItem = {
|
|
|
185
185
|
type: "contextCompaction";
|
|
186
186
|
id: string;
|
|
187
187
|
};
|
|
188
|
+
export type AsyncUserInputQuestion = {
|
|
189
|
+
title: string;
|
|
190
|
+
options: string[] | null;
|
|
191
|
+
};
|
|
188
192
|
export type SubAgentActivityItem = {
|
|
189
193
|
type: "subAgentActivity";
|
|
190
194
|
id: string;
|
|
@@ -213,6 +217,8 @@ export type ThreadItem = {
|
|
|
213
217
|
id: string;
|
|
214
218
|
text: string;
|
|
215
219
|
phase?: "commentary" | "final_answer" | null;
|
|
220
|
+
delivery?: "async" | null;
|
|
221
|
+
questions?: AsyncUserInputQuestion[] | null;
|
|
216
222
|
} | {
|
|
217
223
|
type: "reasoning";
|
|
218
224
|
id: string;
|
package/dist/routes/chat.js
CHANGED
|
@@ -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
|
|
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 I=Gt();return t.set(i,I),I}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 Lt({body:t,headers:a}){const{conversationId:r,fork:o,model:i,effort:d,message:I}=t,O=h(a,"X-Matrix-Bot-Name"),M=h(a,"X-Matrix-Workdir"),S=h(a,"X-Matrix-Workspace-ID"),At=h(a,"X-Matrix-Agent-Mode"),G=h(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 A=Y??G,$t=me(a),Ft=we(a),Z=Te(I.parts),tt=Z?void 0:await be(I.parts),dt=Z?void 0:Ce(r),c=Z,P=tt&&dt?dt:void 0;let $=!o&&r?Pt(r):void 0;const Xt=(()=>{try{return ne(ot,S)}catch(k){throw new W(400,k instanceof Error?k.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,O,M,S,i,"danger-full-access",A)}return await z.getOrCreate(r,O,M,S,i,"danger-full-access",A)}catch(k){$&&he($);const q=Ee(k);throw q?new W(400,`Workdir not found: ${q}`):k}})();$??=Pt(y.id),ae(y.id,{workspaceRoot:Xt,threadRef:$t,matrixEvent:Ft,llmProxyBinding:A});const ut=ce(At,i,d);return{run:async(k,q)=>{const p=$,F=p.abortController;J.add(F);const et=()=>{L(p)&&Mt(p.state.conversationId).catch(n=>H.error("Failed to cancel disconnected Codex stream:",n))};q?.aborted&&et(),q?.addEventListener("abort",et,{once:!0});let b=!!c,lt=!!c,ct=!1,l,C=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 k.writeDone():await k.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}}))},Ct=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 vt=n=>{l!==n&&(l=n,jt&&z.persist(n,O,M,S,A))},E=async(n,e)=>{b||(b=!0,vt(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[T,x]of u.changes.entries())await s({type:"tool-input-available",toolCallId:w(f,l,n.id,String(T)),toolName:x.kind.type==="update"?"Edit":x.kind.type==="add"?"Add":"Delete",title:x.path,input:x});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 R=St(n);if(R)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:R.tool,input:R.arguments}),!0;const D=_t(n);return D?(await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"web_search",title:D.query,input:Ot(D)}),!0):!1},Kt=async n=>{const e=Tt(n);if(e){const T=e.status==="failed"||e.status==="declined";return await s(T?{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 T=u.status==="failed"||u.status==="declined";for(const[x]of u.changes.entries())await s(T?{type:"tool-output-error",toolCallId:w(f,l,n.id,String(x)),errorText:u.status}:{type:"tool-output-available",toolCallId:w(f,l,n.id,String(x)),output:u.status,preliminary:!1});return!0}const g=Rt(n);if(g){const T=g.status==="failed";return await s(T?{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 R=St(n);if(R){const T=R.status==="failed"||R.success===!1,x=de(R);return await s(T?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:x||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:x}),!0}const D=_t(n);return D?(await s({type:"tool-output-available",toolCallId:w(f,l,n.id),output:Ot(D)}),!0):!1};try{if(c&&!p.ownsOutput){await qt(p,y,c.itemId,c.response),_.delete(V(r,c.itemId)),await B();return}const n=P?await Ie(p,y,P,tt,ut):c?await ge(p,y,()=>y.continueWithUserInput(c.itemId,c.response,{signal:F.signal,sourceTurnId:c.sourceTurnId})):await ye(p,y,tt,ut);if(c?_.delete(V(r,c.itemId)):P&&_.delete(V(r,P.toolCallId)),Dt(p,y,n.turnId),!p.ownsOutput){await B();return}c?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:y.id,turnId:n.turnId}}),await s({type:"tool-output-available",toolCallId:c.itemId,toolName:"request_user_input",output:c.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")C=e.thread_id,vt(C);else if(e.type==="turn.started")v=e.turn_id,Dt(p,y,v),await E(C,v);else if(e.type==="item.started"){if(b||await E(C,v),await Ct(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")b||await E(C,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(b||await E(C,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 xt(e.item);const u=Et(e.item)??"";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}:{}})}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(b||await E(C,v),e.params.isBlocking===!1){if(!await qt(p,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")b||await E(C,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(p);return}else e.type==="error"&&(yt=e.message,await s({type:"error",errorText:e.message}));X=!F.signal.aborted,X&&Nt(p),b||await E(C,v),await rt("stop")}catch(n){const e=n instanceof Error?n.message:"Chat stream failed";b||await E(C,v),await s({type:"error",errorText:e}),await rt("error")}finally{N&&clearTimeout(N),q?.removeEventListener("abort",et),J.delete(F),X||Ut(p)}}}}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=h(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 I=!1;const O=()=>{!r||!o||I||(I=!0,it().markStarted(r,o))},M=async S=>{await d.writeSSE(S)};await i.run({writeData:async S=>{O(),await M({data:JSON.stringify(S)})},writeDone:async()=>{O(),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,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 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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@messenger-agent/codex-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0-alpha.1",
|
|
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": "
|
|
18
|
+
"@openai/codex": "0.154.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.
|
|
25
|
+
"@messenger-agent/shared": "0.39.0-alpha.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/ws": "^8.18.1"
|