@messenger-agent/codex-agent 0.26.0-alpha.5 → 0.26.0-alpha.6
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.
- package/dist/app-server-protocol.d.ts +5 -0
- package/dist/app.js +1 -1
- package/dist/codex.d.ts +2 -0
- package/dist/codex.js +3 -3
- package/dist/config.d.ts +2 -0
- package/dist/config.js +1 -1
- package/dist/dynamic-tools.js +1 -1
- package/dist/platform-instructions.js +4 -2
- package/dist/routes/chat.js +2 -2
- package/package.json +2 -2
|
@@ -95,6 +95,7 @@ export type TurnSteerParams = {
|
|
|
95
95
|
};
|
|
96
96
|
export type Thread = {
|
|
97
97
|
id: string;
|
|
98
|
+
name?: string | null;
|
|
98
99
|
turns?: Turn[];
|
|
99
100
|
};
|
|
100
101
|
export type Turn = {
|
|
@@ -227,6 +228,10 @@ export type ItemDeltaEvent = {
|
|
|
227
228
|
export type AppServerEvent = {
|
|
228
229
|
type: "thread.started";
|
|
229
230
|
thread_id: string;
|
|
231
|
+
} | {
|
|
232
|
+
type: "thread.name.updated";
|
|
233
|
+
thread_id: string;
|
|
234
|
+
name: string;
|
|
230
235
|
} | {
|
|
231
236
|
type: "turn.started";
|
|
232
237
|
turn_id: string;
|
package/dist/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Hono as e}from"hono";import
|
|
1
|
+
import{Hono as e}from"hono";import r from"./routes/chat.js";import{infoRequestLogger as i}from"@messenger-agent/shared/http-logger";import{agentAuthMiddleware as m}from"@messenger-agent/shared/agent-auth";import{appConfig as p}from"./config.js";import{installAgentActivityResponder as a}from"@messenger-agent/shared/agent-activity";import{codexActivitySnapshot as n}from"./routes/chat.js";const o=new e;a("codex",n),o.use(i(["/health"])),o.get("/health",t=>t.json({status:"ok"})),o.use("/*",m(p.authTokens)),o.route("/",r);var l=o;export{l as default};
|
package/dist/codex.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class CodexThread {
|
|
|
27
27
|
usage?: Usage | null;
|
|
28
28
|
}>;
|
|
29
29
|
runStreamed(_prompt: string, options?: TurnRunOptions): Promise<StreamedTurn>;
|
|
30
|
+
private waitForTurnStart;
|
|
30
31
|
steerStreamed(prompt: string, expectedTurnId: string, options?: {
|
|
31
32
|
signal?: AbortSignal;
|
|
32
33
|
}): Promise<StreamedTurn>;
|
|
@@ -37,6 +38,7 @@ export declare class CodexThread {
|
|
|
37
38
|
submitPendingUserInput(itemId: string, response: ToolRequestUserInputResponse): Promise<string | undefined>;
|
|
38
39
|
interrupt(turnId: string): Promise<void>;
|
|
39
40
|
interruptPendingUserInput(turnId: string, itemId: string): Promise<void>;
|
|
41
|
+
readName(): Promise<string | undefined>;
|
|
40
42
|
private interruptStaleTurn;
|
|
41
43
|
private turnStartParams;
|
|
42
44
|
private eventsForTurn;
|
package/dist/codex.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{mkdirSync as
|
|
2
|
-
`);return{items:d,finalResponse:
|
|
3
|
-
`)}class
|
|
1
|
+
import{mkdirSync as _,existsSync as P}from"node:fs";import{appServerClient as c}from"./app-server-client.js";import{dynamicToolSpecs as E}from"./dynamic-tools.js";import{loadAllSessions as U,persistSession as I}from"./db.js";import{appConfig as f}from"./config.js";import{logger as v}from"@messenger-agent/shared/logger";import{resolveAgentWorkdir as N}from"@messenger-agent/shared/agent-workspaces";import{codexPlatformInstructions as x}from"./platform-instructions.js";class T extends Error{path;constructor(e){super(`Workdir not found: ${e}`),this.path=e}}class k{id;model;workingDirectory;constructor(e,r,n){this.id=e,this.model=r,this.workingDirectory=n}async run(e,r){const n=await this.runStreamed(e,r),t=new Map,o=new Set;let a=null;for await(const u of n.events)if(u.type==="item.started")o.has(u.item.id)||t.set(u.item.id,u.item);else if(u.type==="item.completed")o.add(u.item.id),t.set(u.item.id,u.item);else if(u.type==="thread.tokenUsage.updated")a=u.usage;else if(u.type==="turn.failed")throw new Error(u.error.message);const d=Array.from(t.values()),i=d.filter(u=>u.type==="agentMessage").map(u=>u.text).join(`
|
|
2
|
+
`);return{items:d,finalResponse:i||void 0,usage:a}}async runStreamed(e,r){const n=c.request("turn/start",this.turnStartParams(e,r)),t=await this.waitForTurnStart(n,r?.signal),o=t.turn.id;return{turnId:o,events:this.eventsForTurn(o,t.turn,r?.signal)}}async waitForTurnStart(e,r){if(!r)return e;let n,t;const o=new Promise((d,i)=>{t=i}),a=()=>{n=r.reason??new DOMException("The operation was aborted","AbortError"),t(n)};r.addEventListener("abort",a,{once:!0}),r.aborted&&a();try{return await Promise.race([e,o])}catch(d){throw d===n&&e.then(i=>this.interrupt(i.turn.id)).catch(i=>v.error("Failed to clean up aborted Codex turn start:",i)),d}finally{r.removeEventListener("abort",a)}}async steerStreamed(e,r,n){const t={threadId:this.id,input:[{type:"text",text:e}],expectedTurnId:r},o=await c.request("turn/steer",t);return{turnId:o.turnId,events:this.eventsForTurn(o.turnId,void 0,n?.signal,void 0,!0)}}async continueWithUserInput(e,r,n){const t=c.pendingUserInputTurnId(this.id,e);return t?{turnId:t,events:this.eventsForTurn(t,void 0,n?.signal,async()=>{if(!await c.respondToUserInput(this.id,e,r))throw new Error(`No pending user input request found for item: ${e}`)})}:(await this.interruptStaleTurn(n?.sourceTurnId),this.runStreamed(C(e,r),n))}async submitPendingUserInput(e,r){const n=c.pendingUserInputTurnId(this.id,e);if(!(!n||!await c.respondToUserInput(this.id,e,r)))return n}async interrupt(e){await c.request("turn/interrupt",{threadId:this.id,turnId:e})}async interruptPendingUserInput(e,r){await this.interrupt(e),c.discardPendingUserInput(this.id,r)}async readName(){try{return(await c.request("thread/read",{threadId:this.id,includeTurns:!1})).thread?.name?.trim()||void 0}catch(e){v.warn(`Failed to read Codex thread name for ${this.id}:`,e);return}}async interruptStaleTurn(e){if(!e)return;(await c.request("thread/read",{threadId:this.id,includeTurns:!0})).thread.turns?.find(t=>t.id===e)?.status==="inProgress"&&await this.interrupt(e)}turnStartParams(e,r){return{threadId:this.id,input:[{type:"text",text:e,text_elements:[]}],cwd:this.workingDirectory,approvalPolicy:"never",model:this.model,...r?.collaborationMode?{collaborationMode:r.collaborationMode}:null}}async*eventsForTurn(e,r,n,t,o=!1){const a=[...r||o?[{type:"thread.started",thread_id:this.id},{type:"turn.started",turn_id:e}]:[],...r?A(r):[]];let d=r?.status==="completed"||r?.status==="failed"||r?.status==="interrupted",i,u=!1,p=()=>{};const l=({method:h,params:w})=>{if(u)return;const g=M(h,w,this.id,e);g&&(a.push(g),(g.type==="turn.completed"||g.type==="turn.failed")&&(d=!0),p())},y=h=>{i=h,d=!0,p()},m=()=>{u=!0,a.length=0,d=!0,p()};c.on("notification",l),c.on("exit",y),n?.addEventListener("abort",m,{once:!0}),n?.aborted&&m();try{for(await t?.();!d||a.length>0;){for(;a.length>0;)yield a.shift();if(d)break;await new Promise(h=>{const w=setTimeout(()=>{p=()=>{},h()},50);p=()=>{clearTimeout(w),p=()=>{},h()}})}i&&(yield{type:"error",message:i.message})}finally{c.off("notification",l),c.off("exit",y),n?.removeEventListener("abort",m)}}}function C(s,e){return[`The user answered a previous request_user_input prompt (${s}), but the original pending tool request is no longer active.`,"Continue the task using this answer:",JSON.stringify(e,null,2)].join(`
|
|
3
|
+
`)}class F{sessions=new Map;constructor(){for(const e of U())this.sessions.set(e.sessionId,{workdir:e.workdir,botName:e.botName??"main",llmProxyBinding:e.llmProxyBinding});v.info(`SessionManager: loaded ${this.sessions.size} persisted sessions`)}async getOrCreate(e,r,n,t,o,a,d){const i=this.resolveWorkdir(n,t);if(e){const p=this.sessions.get(e);if(p&&p.workdir===i){const l=p.llmProxyBinding??d,y=await c.request("thread/resume",this.threadResumeParams(e,o,i,a,l));if(!p.llmProxyBinding&&l){const m={...p,llmProxyBinding:l};this.sessions.set(e,m),I({sessionId:e,botName:m.botName,workdir:m.workdir,createdAt:Date.now(),llmProxyBinding:l})}return{thread:new k(y.thread.id,o,i),sessionId:e,isNewSession:!1}}}if(n&&!P(i))throw new T(i);_(i,{recursive:!0});const u=await c.request("thread/start",this.threadStartParams(o,i,a,d));return{thread:new k(u.thread.id,o,i),sessionId:void 0,isNewSession:!0}}async fork(e,r,n,t,o,a,d){const i=this.resolveWorkdir(n,t);if(n&&!P(i))throw new T(i);_(i,{recursive:!0});const p=(await c.request("thread/fork",this.threadForkParams(e.threadId,e.turnId,o,i,a,d))).thread.id;return this.persist(p,r,n,t,d),{thread:new k(p,o,i),sessionId:p,isNewSession:!1}}persist(e,r,n,t,o){const a=r??"main",d=this.resolveWorkdir(n,t);this.sessions.set(e,{botName:a,workdir:d,llmProxyBinding:o}),I({sessionId:e,botName:a,workdir:d,createdAt:Date.now(),llmProxyBinding:o})}bindingFor(e){return e?this.sessions.get(e)?.llmProxyBinding:void 0}workdirFor(e){return e?this.sessions.get(e)?.workdir:void 0}hasSession(e){return!!e&&this.sessions.has(e)}resolveWorkdir(e,r){return N(f,r,e)}threadStartParams(e,r,n,t){return{model:e,modelProvider:f.codex.baseUrl?"custom":null,cwd:r,approvalPolicy:"never",sandbox:n,config:this.codexConfig(t),developerInstructions:x,dynamicTools:E(),experimentalRawEvents:!1,persistExtendedHistory:!1}}threadResumeParams(e,r,n,t,o){return{threadId:e,model:r,modelProvider:f.codex.baseUrl?"custom":null,cwd:n,approvalPolicy:"never",sandbox:t,config:this.codexConfig(o),developerInstructions:x,excludeTurns:!0,persistExtendedHistory:!1}}threadForkParams(e,r,n,t,o,a){return{threadId:e,lastTurnId:r,model:n,modelProvider:f.codex.baseUrl?"custom":null,cwd:t,approvalPolicy:"never",sandbox:o,config:this.codexConfig(a),developerInstructions:x,ephemeral:!1,excludeTurns:!0}}codexConfig(e){return{approval_policy:"never",...f.codex.baseUrl?{model_provider:"custom",model_reasoning_effort:"medium",disable_response_storage:!0,model_providers:{custom:{name:"custom",base_url:f.codex.baseUrl,wire_api:"responses",...f.codex.apiKey?{env_key:"OPENAI_API_KEY"}:null,...e?{http_headers:{"X-Elevo-LLM-Binding":e}}:null}},"features.default_mode_request_user_input":!0}:null}}}f.codex.apiKey&&!process.env.OPENAI_API_KEY&&(process.env.OPENAI_API_KEY=f.codex.apiKey);function A(s){const e=[];for(const r of s.items??[])e.push({type:"item.completed",item:r});return s.status==="completed"?e.push({type:"turn.completed"}):s.status==="failed"&&e.push({type:"turn.failed",error:{message:s.error?.message??"Codex turn failed"}}),e}function M(s,e,r,n){const t=e;if(!t)return;const o=t.threadId,a=t.turnId;if(!(typeof o=="string"&&o!==r)&&!(typeof a=="string"&&a!==n)){if(s==="item/started"&&S(t.item))return{type:"item.started",item:t.item};if(s==="item/completed"&&S(t.item))return{type:"item.completed",item:t.item};if(s==="item/agentMessage/delta")return b(t,"agentMessage");if(s==="item/reasoning/summaryTextDelta"||s==="item/reasoning/textDelta")return b(t,"reasoning");if(s==="item/plan/delta")return b(t,"plan");if(s==="item/tool/requestUserInput")return{type:"item.tool.requestUserInput",params:t};if(s==="turn/diff/updated"&&typeof t.diff=="string")return{type:"turn.diff.updated",diff:t.diff};if(s==="thread/tokenUsage/updated")return{type:"thread.tokenUsage.updated",usage:R(t)};if(s==="thread/name/updated"&&typeof t.name=="string"&&t.name.trim())return{type:"thread.name.updated",thread_id:r,name:t.name.trim()};if(s==="turn/completed"){const d=t.turn;return d?.status==="failed"?{type:"turn.failed",error:{message:d.error?.message??"Codex turn failed"}}:{type:"turn.completed"}}if(s==="error")return console.error("Received error notification from app-server:",t),{type:"error",message:q(t)}}}function q(s){const e=s.error,r=e&&typeof e=="object"&&"message"in e?e.message:void 0,n=s.additionalDetails,t=typeof r=="string"&&r.trim()?r:"Codex app-server error";return typeof n=="string"&&n.trim()?`${t}: ${n}`:t}function b(s,e){if(!(typeof s.itemId!="string"||typeof s.delta!="string"))return{type:"item.delta",item_id:s.itemId,item_type:e,delta:s.delta}}function R(s){const e=s.tokenUsage;if(!e||typeof e!="object")return null;const r=e,n=r.total,t=r.last;if(!n||typeof n!="object"||!t||typeof t!="object")return null;const o=d=>{const i=d;return{input_tokens:typeof i.inputTokens=="number"?i.inputTokens:void 0,output_tokens:typeof i.outputTokens=="number"?i.outputTokens:void 0,total_tokens:typeof i.totalTokens=="number"?i.totalTokens:void 0}},a=r.modelContextWindow;return{total:o(n),last:o(t),model_context_window:typeof a=="number"?a:void 0}}function S(s){return!!(s&&typeof s=="object"&&"type"in s&&"id"in s)}const Y=new F;export{k as CodexThread,T as WorkdirNotFoundError,Y as sessionManager};
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AgentAuthConfig, type AgentWorkspacesConfig, type GitInitConfig } from "@messenger-agent/shared/agent-config";
|
|
2
2
|
import { type LogLevel } from "@messenger-agent/shared/logger";
|
|
3
|
+
import { type ScheduleServiceConfig } from "@messenger-agent/shared/schedule-tools";
|
|
3
4
|
export type AppConfig = {
|
|
4
5
|
configPath: string;
|
|
5
6
|
logLevel: LogLevel;
|
|
@@ -19,6 +20,7 @@ export type AppConfig = {
|
|
|
19
20
|
baseUrl?: string;
|
|
20
21
|
imageModel: string;
|
|
21
22
|
};
|
|
23
|
+
schedule: ScheduleServiceConfig;
|
|
22
24
|
tunnel: {
|
|
23
25
|
enabled: boolean;
|
|
24
26
|
serverUrl?: string;
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as M,readFileSync as U}from"node:fs";import{tmpdir as b}from"node:os";import{join as a}from"node:path";import{parse as I}from"yaml";import{z as e}from"zod";import{normalizeOptional as n,parseAgentAuthConfig as v,parseAgentWorkspacesConfig as h,parseGitInitConfig as x}from"@messenger-agent/shared/agent-config";import{logger as s,normalizeLogLevel as L,setLogFile as y,setLogLevel as k}from"@messenger-agent/shared/logger";import{deriveScheduleServiceConfig as A}from"@messenger-agent/shared/schedule-tools";const D="wss://m.elevo.vip/agent-bridge/tunnel",j="https://m.elevo.vip/agent-bridge/llm/v1",F=e.object({log_level:e.string().optional(),port:e.coerce.number().int().positive().optional(),auth_tokens:e.record(e.string(),e.string()).optional(),workspaces:e.array(e.object({id:e.string().optional(),name:e.string().optional(),path:e.string().optional()})).optional(),file_uploads:e.object({temp_dir:e.string().optional()}).optional(),data_dir:e.string().optional(),codex:e.object({port:e.coerce.number().int().positive().optional(),data_dir:e.string().optional(),file_uploads:e.object({temp_dir:e.string().optional()}).optional(),api_key:e.string().optional(),base_url:e.string().optional()}).optional(),openai:e.object({api_key:e.string().optional(),base_url:e.string().optional(),image_model:e.string().optional()}).optional(),schedule:e.object({base_url:e.string().optional(),api_key:e.string().optional()}).optional(),gitlab:e.object({ca_cert_path:e.string().optional(),host:e.string().optional()}).optional(),git:e.object({co_authors:e.array(e.string()).optional()}).optional(),tunnel:e.object({enabled:e.boolean().optional(),server_url:e.string().optional(),tunnel_id:e.string().optional(),token:e.string().optional(),reconnect_initial_ms:e.number().int().positive().optional(),reconnect_max_ms:e.number().int().positive().optional(),heartbeat_interval_ms:e.number().int().positive().optional()}).optional()}).loose();function S(){const t=process.env.AGENT_CONFIG_PATH??"./agent-config.yaml";if(!M(t)){const l="./data",_=a(l,"logs","codex-agent.log");return k("info"),y(_),s.warn(`Config file not found at ${t}`),{configPath:t,logLevel:"info",logFile:_,port:3e3,fileUploads:{tempDir:a(b(),"codex-agent-uploads")},dataDir:l,sessionsDbPath:a(l,"sessions.db"),codex:{},openai:{imageModel:"gpt-image-2"},schedule:{},tunnel:{enabled:!1,reconnectInitialMs:1e3,reconnectMaxMs:3e4,heartbeatIntervalMs:3e4},...v({}),...h({}),...x({})}}let p;try{p=I(U(t,"utf-8"))}catch(l){s.error(`Failed to parse config file at ${t}:`,l),p={}}const r=F.safeParse(p??{});r.success||s.error(`Invalid config format at ${t}:`,r.error.issues);const o=r.success?r.data:{},g=L(o.log_level),c=n(o.codex?.data_dir)??n(o.data_dir)??"./data",u=a(c,"logs","codex-agent.log"),i=o.tunnel?.enabled??!1,d=n(o.tunnel?.token),m=n(o.openai?.api_key)??(i?d:void 0),f=n(o.openai?.base_url)??(i?j:void 0);k(g),y(u);const C={configPath:t,logLevel:g,logFile:u,port:o.codex?.port??o.port??3e3,fileUploads:{tempDir:n(o.codex?.file_uploads?.temp_dir)??n(o.file_uploads?.temp_dir)??a(b(),"codex-agent-uploads")},dataDir:c,sessionsDbPath:a(c,"sessions.db"),codex:{apiKey:n(o.codex?.api_key)??(i?d:void 0),baseUrl:n(o.codex?.base_url)??(i?j:void 0)},openai:{apiKey:m,baseUrl:f,imageModel:n(o.openai?.image_model)??"gpt-image-2"},schedule:A({baseUrl:n(o.schedule?.base_url),apiKey:n(o.schedule?.api_key)},f,m),tunnel:{enabled:i,serverUrl:n(o.tunnel?.server_url)??(i?D:void 0),tunnelId:n(o.tunnel?.tunnel_id),token:d,reconnectInitialMs:o.tunnel?.reconnect_initial_ms??1e3,reconnectMaxMs:o.tunnel?.reconnect_max_ms??3e4,heartbeatIntervalMs:o.tunnel?.heartbeat_interval_ms??3e4},...v(o),...h(o),...x(o)};return s.info(`Loaded agent config from ${t}`),C}const E=S();export{E as appConfig};
|
package/dist/dynamic-tools.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import l from"node:path";import{mkdir as f,writeFile as y}from"node:fs/promises";import{generateImage as T}from"ai";import{createOpenAI as w}from"@ai-sdk/openai";import{Agent as I,fetch as x}from"undici";import{z as i}from"zod";import{executeManagedTaskTool as S,isManagedTaskToolName as b,MANAGED_TASK_TOOL_DEFINITIONS as A}from"@messenger-agent/shared/managed-task-tools";import{AgentFileError as k,resolveAgentFilePath as v}from"@messenger-agent/shared/agent-files";import{executeScheduleTool as E,SCHEDULE_TOOL_DEFINITION as c,ScheduleToolError as _}from"@messenger-agent/shared/schedule-tools";const d=1200*1e3,M=new I({headersTimeout:d,bodyTimeout:d}),R=(t,e)=>x(t,{...e,dispatcher:M}),p=i.object({prompt:i.string().describe("The text prompt describing the image to generate"),store_path:i.string().describe("Absolute file path (including filename) to save the image. If omitted, saves to the current working directory with an auto-generated name.").optional(),size:i.enum(["1024x1024","1536x1024","1024x1536"]).describe("Image size. Leave it undefined to use the auto size.").optional()}),m=i.object({path:i.string().describe("File to send. Absolute paths use the system path; relative paths resolve from the active workspace.")}),N=i.toJSONSchema(p),O=i.toJSONSchema(m);function g(t){return{contentItems:[{type:"inputText",text:JSON.stringify(t)}],success:!0}}const a=new Map;function F(t,e){const o=e.threadRef?.trim();a.set(t,{workspaceRoot:e.workspaceRoot,...o?{threadRef:o}:null,...e.matrixEvent?{matrixEvent:e.matrixEvent}:null,...e.llmProxyBinding?{llmProxyBinding:e.llmProxyBinding}:null})}function u(t,e){const o=a.get(t);if(o)for(const n of e)n&&n!==t&&F(n,o)}function $(t,e){if(t!=="item/started"&&t!=="item/completed"||!e||typeof e!="object")return;const o=e,n=o.item;if(!n||typeof n!="object")return;const r=n;if(r.type==="subAgentActivity"&&r.kind==="started"&&typeof o.threadId=="string"&&typeof r.agentThreadId=="string"){u(o.threadId,[r.agentThreadId]);return}r.type==="collabAgentToolCall"&&r.tool==="spawnAgent"&&typeof r.senderThreadId=="string"&&Array.isArray(r.receiverThreadIds)&&u(r.senderThreadId,r.receiverThreadIds.filter(s=>typeof s=="string"))}async function h(t){const e=a.get(t);if(e)return e.workspaceRoot;const{sessionManager:o}=await import("./codex.js"),n=o.workdirFor(t);if(!n)throw new Error(`No workspace context for thread ${t}; the session may not exist in this process`);return n}async function L(t){const{appConfig:e}=await import("./config.js");if(!e.openai.apiKey)throw new Error("Missing openai.api_key in LLM config");return{apiKey:e.openai.apiKey,baseURL:e.openai.baseUrl,imageModel:e.openai.imageModel,llmProxyBinding:a.get(t)?.llmProxyBinding}}async function C(){const{appConfig:t}=await import("./config.js");return t.schedule}function H(){return[{name:"generate_image",description:"Generate an image from a text prompt. The image is saved to the specified path (absolute path including filename) or the current working directory if not specified. Returns the saved file path.",inputSchema:N},{name:"send_file",description:"Send any readable file to the client. Absolute paths use the system path; relative paths resolve from the active workspace. The agent runtime captures this tool call and performs the actual send.",inputSchema:O},...A.map(t=>({name:t.name,description:t.description,inputSchema:t.inputSchema})),{name:c.name,description:c.description,inputSchema:c.inputSchema}]}async function V(t){try{if(t.tool==="generate_image"){const e=p.parse(t.arguments),o=await L(t.threadId),n=w({apiKey:o.apiKey,baseURL:o.baseURL,fetch:R,...o.llmProxyBinding?{headers:{"X-Elevo-LLM-Binding":o.llmProxyBinding}}:null}),{image:r}=await T({model:n.image(o.imageModel),prompt:e.prompt,size:e.size,maxRetries:0,abortSignal:AbortSignal.timeout(d)}),s=e.store_path?l.resolve(e.store_path):l.join(process.cwd(),`generated_${Date.now()}.png`);return await f(l.dirname(s),{recursive:!0}),await y(s,Buffer.from(r.uint8Array)),{contentItems:[{type:"inputText",text:s}],success:!0}}if(t.tool==="send_file"){const e=m.parse(t.arguments),o=await v(e.path,await h(t.threadId));return{contentItems:[{type:"inputText",text:JSON.stringify({path:o})}],success:!0}}if(b(t.tool)){const e=await S(await h(t.threadId),t.tool,t.arguments,a.get(t.threadId));return g(e)}if(t.tool===c.name)return g(await E(await C(),a.get(t.threadId),t.arguments));throw new Error(`Unknown dynamic tool: ${t.tool}`)}catch(e){const o=e instanceof k&&e.code==="INVALID_PATH"||e instanceof Error?e.message:String(e),n=e instanceof _?{error:{code:e.code,message:e.message,retryable:e.retryable,currentRevision:e.currentRevision}}:o;return{contentItems:[{type:"inputText",text:typeof n=="string"?n:JSON.stringify(n)}],success:!1}}}export{H as dynamicToolSpecs,V as handleDynamicToolCall,u as inheritManagedTaskToolContext,$ as inheritManagedTaskToolContextFromNotification,F as setManagedTaskToolContext};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as r,readFileSync as c}from"node:fs";import{fileURLToPath as o}from"node:url";import{logger as i}from"@messenger-agent/shared/logger";const d=[o(new URL("./assets/codex-home/AGENTS.md",import.meta.url)),o(new URL("../../../assets/codex-home/AGENTS.md",import.meta.url)),"/app/assets/codex-home/AGENTS.md"],a="## Codex Managed Task Tools\n\nWhen using managed task tools in Codex, call these tool names directly: `search_managed_tasks`, `get_managed_task`, `create_managed_task`, `update_managed_task`, `apply_managed_task_patch`, and `delete_managed_task`.\n",n="## Schedule Tool\n\nUse the single `schedule` tool to create, list, inspect, update, pause, resume, or cancel schedules in the current trusted Matrix context. The action is selected inside `input.action`; never ask for or invent a Room ID, actor ID, source event, or service credential. After create or update succeeds, repeat the service's normalized rule, absolute next run time, IANA timezone, message, and mention MXIDs to the user so relative times and recurrence are unambiguous.\n";function m(s){const e=[s??process.env.CODEX_AGENTS_TEMPLATE_PATH,...d].find(t=>!!(t&&r(t)));return e?`${c(e,"utf-8").trimEnd()}
|
|
2
2
|
|
|
3
|
-
${
|
|
3
|
+
${a}
|
|
4
|
+
${n}`:(i.warn("Codex platform instructions template not found"),`${a}
|
|
5
|
+
${n}`)}const _=m();export{_ as codexPlatformInstructions,m as loadCodexPlatformInstructions};
|
package/dist/routes/chat.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Hono as
|
|
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 Kt}from"hono";import{HTTPException as D}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{sessionManager as j,WorkdirNotFoundError as Yt}from"../codex.js";import{logger as ot}from"@messenger-agent/shared/logger";import{ChatBodySchema as Zt,RequestUserInputToolPartSchema as te}from"../schemas.js";import{appConfig as Tt}from"../config.js";import{setManagedTaskToolContext as ee}from"../dynamic-tools.js";import{resolveAgentWorkspaceRoot as ae}from"@messenger-agent/shared/agent-workspaces";import{saveUploadedFilePart as ne}from"@messenger-agent/shared/uploaded-files";import{commandExecutionItem as St,addTokenUsage as re,displayCommand as oe,dynamicToolCallItem as Et,dynamicToolCallOutput as ie,fileChangeItem as Rt,itemText as kt,mcpToolCallItem as _t,mcpToolCallOutput as se,toUsage as de,turnOptions as ue,webSearchItem as Ot,webSearchPayload as Mt}from"./shared.js";const B=new Kt,m=new Map,z=new Set,it=new Map,k=new Map;function We(){const t=new Set([...k.values()].map(e=>e.conversationId).filter(e=>!!e));return{active:z.size,waiting:t.size}}function le(t){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}}function ce(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 pe(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 N 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 Pt(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 Dt(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(u=>{e.resolveOwnerReady=u})),{state:e,generation:r,abortController:o,ownsOutput:i}}function q(t){return m.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(u=>{o=u});t.transition=r.then(()=>i),await r;try{return await e()}finally{o()}}async function fe(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(m.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");i.thread=e;const u=i.turnId?await e.steerStreamed(r,i.turnId,{signal:t.abortController.signal}):await e.runStreamed(r,{...o,signal:t.abortController.signal});return i.turnId=u.turnId,u})}finally{t.ownsOutput&&t.state.resolveOwnerReady?.()}}async function me(t,e,r){return H(t.state,async()=>{const{state:o}=t;if(!q(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 we(t,e,r,o){return H(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 u=await e.submitPendingUserInput(r,o);return u&&(i.turnId=u),u})}async function ye(t,e,r,o,i){return H(t.state,async()=>{const{state:u}=t;if(m.get(u.conversationId)!==u||u.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");u.thread=e,await e.interruptPendingUserInput(r.turnId,r.toolCallId),u.turnId=void 0;const T=await e.runStreamed(o,{...i,signal:t.abortController.signal});return u.turnId=T.turnId,T})}function Nt(t,e,r){t.state.thread=e,t.state.turnId=r}function qt(t){const{state:e}=t;q(t)&&(e.owner=void 0,e.resolveOwnerReady?.(),e.turnId=void 0,m.get(e.conversationId)===e&&m.delete(e.conversationId))}function Ut(t){q(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 ge(t){q(t)&&(Ut(t),t.state.resolveOwnerReady?.(),!t.state.turnId&&m.get(t.state.conversationId)===t.state&&m.delete(t.state.conversationId))}function J(t,e){return`${t??"unknown"}:${e}`}function Ie(t){if(t)return[...k.values()].find(e=>e.conversationId===t)}function Lt(t,e){return t??e}function he(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 Ce(t){if(t.type==="text"&&typeof t.text=="string")return t.text;if(he(t))return ne(t,Tt.fileUploads.tempDir);throw new D(400,{message:`Unsupported message part type: ${t.type}`})}async function ve(t){const r=(await Promise.all(t.map(Ce))).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 xe(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=te.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 w(t,e,r,o){const i=[e,r,o].filter(Boolean).join(":"),u=t.get(i);if(u)return u;const T=Gt();return t.set(i,T),T}function be(t,e){return t&&t.length>0?t:e}function Te(t){ot.debug("Received chat stream event:",t)}function Se(t){if(t instanceof Yt||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 Pt(e)?t.json({success:!0}):t.json({error:le(e)},404)}catch(r){const o=r instanceof Error?r.message:"Failed to interrupt Codex conversation";return ot.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 At({body:t,headers:e}){const{conversationId:r,fork:o,model:i,effort:u,message:T}=t,K=v(e,"X-Matrix-Bot-Name"),Q=v(e,"X-Matrix-Workdir"),U=v(e,"X-Matrix-Workspace-ID"),Wt=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 N(409,"LLM proxy binding does not match the existing conversation");const L=G??V,Ft=ce(e),Xt=pe(e),Y=xe(T.parts),Z=Y?void 0:await ve(T.parts),st=Y?void 0:Ie(r),p=Y,_=Z&&st?st:void 0;let A=!o&&r?Dt(r):void 0;const $t=(()=>{try{return ae(Tt,U)}catch(S){throw new N(400,S instanceof Error?S.message:"Invalid workspace")}})(),{thread:c,isNewSession:jt}=await(async()=>{try{if(o){if(!("threadId"in o))throw new N(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&&ge(A);const O=Se(S);throw O?new N(400,`Workdir not found: ${O}`):S}})();A??=Dt(c.id),ee(c.id,{workspaceRoot:$t,threadRef:Ft,matrixEvent:Xt,llmProxyBinding:L});const dt=ue(Wt,i,u);return{run:async(S,O)=>{const y=A,W=y.abortController;z.add(W);const tt=()=>{q(y)&&Pt(y.state.conversationId).catch(n=>ot.error("Failed to cancel disconnected Codex stream:",n))};O?.aborted&&tt(),O?.addEventListener("abort",tt,{once:!0});let x=!!p,ut=!!p,lt=!1,l,I=c.id,h,F=!1,P;const ct=new Set,pt=new Set,X=new Set,f=new Map,ft=new Set,Bt=new Set,mt=new Set;let wt,et,yt;const gt=()=>{P=setTimeout(()=>{s({type:"heartbeat"}).catch(()=>{})},6e4)},It=async n=>{P&&clearTimeout(P),n.data==="[DONE]"?await S.writeDone():await S.writeData(JSON.parse(String(n.data))),gt()},s=async n=>{await It({data:JSON.stringify(n)})},$=async()=>{await It({data:"[DONE]"})},zt=async()=>{const n=Lt(l,c.id),a=it.get(n);a&&(it.delete(n),await s({type:"data",kind:"diff",data:{diff:a}}))},ht=async(n,a)=>{if(n.type!=="contextCompaction")return!1;if(a==="end"&&!ft.has(n.id))return!0;const d=a==="start"?ft:Bt;return d.has(n.id)||(d.add(n.id),await s({type:"data",kind:"compact",data:a==="start"?{phase:a,activityId:n.id}:{phase:a,activityId:n.id,status:"completed"}})),!0},at=async n=>{if(n.type!=="subAgentActivity")return!1;if(mt.has(n.id))return!0;mt.add(n.id);const a=w(f,l,n.id),d={activity:n.kind,agentThreadId:n.agentThreadId,...n.agentPath?{agentPath:n.agentPath}:null};return await s({type:"tool-input-available",toolCallId:a,toolName:`subagent_${n.kind}`,...n.agentPath?{title:n.agentPath}:null,input:d}),await s(n.kind==="interrupted"?{type:"tool-output-error",toolCallId:a,errorText:"Subagent interrupted"}:{type:"tool-output-available",toolCallId:a,output:d}),!0};gt();const Ct=n=>{l!==n&&(l=n,jt&&j.persist(n,K,Q,U,L))},E=async(n,a)=>{x||(x=!0,Ct(n),await s({type:"start",messageMetadata:{conversationId:n,threadId:n,...a?{turnId:a}:null}}),await s({type:"start-step"}),ut=!0)},nt=async n=>{lt||(lt=!0,ut&&(await zt(),n==="stop"&&et&&await s({type:"data",kind:"token_usage",data:et}),await s({type:"finish-step"})),await s({type:"finish",finishReason:n}),await $())};let vt;const rt=async(n,a)=>{const d=a?.trim();!d||d===vt||(vt=d,await s({type:"data",kind:"session_title",data:{agentType:"codex",threadId:n,title:d}}))},xt=async n=>{if(ct.has(n.id))return;ct.add(n.id);const a=n.type==="plan"?{type:"plan"}:void 0;await s({type:"text-start",id:n.id,...a?{textMetadata:a}:{}})},bt=async n=>{pt.has(n.id)||(pt.add(n.id),await s({type:"reasoning-start",id:n.id}))},Ht=async n=>{const a=St(n);if(a)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"bash",input:oe(a.command)}),!0;const d=Rt(n);if(d){for(const[b,C]of d.changes.entries())await s({type:"tool-input-available",toolCallId:w(f,l,n.id,String(b)),toolName:C.kind.type==="update"?"Edit":C.kind.type==="add"?"Add":"Delete",title:C.path,input:C});return!0}const g=_t(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=Et(n);if(R)return await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:R.tool,input:R.arguments}),!0;const M=Ot(n);return M?(await s({type:"tool-input-available",toolCallId:w(f,l,n.id),toolName:"web_search",title:M.query,input:Mt(M)}),!0):!1},Jt=async n=>{const a=St(n);if(a){const b=a.status==="failed"||a.status==="declined";return await s(b?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:be(a.aggregatedOutput,"Command execution failed")}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:a.aggregatedOutput??""}),!0}const d=Rt(n);if(d){const b=d.status==="failed"||d.status==="declined";for(const[C]of d.changes.entries())await s(b?{type:"tool-output-error",toolCallId:w(f,l,n.id,String(C)),errorText:d.status}:{type:"tool-output-available",toolCallId:w(f,l,n.id,String(C)),output:d.status,preliminary:!1});return!0}const g=_t(n);if(g){const b=g.status==="failed";return await s(b?{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:se(g)}),!0}const R=Et(n);if(R){const b=R.status==="failed"||R.success===!1,C=ie(R);return await s(b?{type:"tool-output-error",toolCallId:w(f,l,n.id),errorText:C||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:w(f,l,n.id),output:C}),!0}const M=Ot(n);return M?(await s({type:"tool-output-available",toolCallId:w(f,l,n.id),output:Mt(M)}),!0):!1};try{if(p&&!y.ownsOutput){await we(y,c,p.itemId,p.response),k.delete(J(r,p.itemId)),await $();return}const n=_?await ye(y,c,_,Z,dt):p?await me(y,c,()=>c.continueWithUserInput(p.itemId,p.response,{signal:W.signal,sourceTurnId:p.sourceTurnId})):await fe(y,c,Z,dt);if(p?k.delete(J(r,p.itemId)):_&&k.delete(J(r,_.toolCallId)),Nt(y,c,n.turnId),!y.ownsOutput){await $();return}p?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:c.id,turnId:n.turnId}}),await s({type:"tool-output-available",toolCallId:p.itemId,toolName:"request_user_input",output:p.response})):_&&await s({type:"tool-output-error",toolCallId:_.toolCallId,toolName:"request_user_input",errorText:"Question interrupted by text response"});for await(const a of n.events)if(a.type!=="item.delta"&&Te(a),a.type==="thread.started")I=a.thread_id,Ct(I);else if(a.type==="thread.name.updated")await rt(a.thread_id,a.name);else if(a.type==="turn.started")h=a.turn_id,Nt(y,c,h),await E(I,h);else if(a.type==="item.started"){if(x||await E(I,h),await ht(a.item,"start")||await at(a.item)||await Ht(a.item))continue;a.item.type==="agentMessage"||a.item.type==="plan"?await xt(a.item):a.item.type==="reasoning"&&await bt(a.item)}else if(a.type==="item.delta")x||await E(I,h),a.item_type==="agentMessage"||a.item_type==="plan"?(X.add(a.item_id),await s({type:"text-delta",id:a.item_id,delta:a.delta})):a.item_type==="reasoning"&&(X.add(a.item_id),await s({type:"reasoning-delta",id:a.item_id,delta:a.delta}));else if(a.type==="item.updated"){if(await at(a.item))continue}else if(a.type==="item.completed"){if(x||await E(I,h),await ht(a.item,"end")||await at(a.item)||await Jt(a.item))continue;if(a.item.type==="agentMessage"||a.item.type==="plan"){await xt(a.item);const d=kt(a.item)??"";d&&!X.has(a.item.id)&&await s({type:"text-delta",id:a.item.id,delta:d}),await s({type:"text-end",id:a.item.id})}else if(a.item.type==="reasoning"){await bt(a.item);const d=kt(a.item)??"";d&&!X.has(a.item.id)&&await s({type:"reasoning-delta",id:a.item.id,delta:d}),await s({type:"reasoning-end",id:a.item.id})}}else if(a.type==="item.tool.requestUserInput"){x||await E(I,h),k.set(J(l,a.params.itemId),{conversationId:l,toolCallId:a.params.itemId,toolName:"request_user_input",turnId:a.params.turnId,since:new Date().toISOString()}),await s({type:"tool-input-available",toolCallId:a.params.itemId,toolName:"request_user_input",input:a.params}),await $();return}else if(a.type==="turn.diff.updated")x||await E(I,h),it.set(Lt(l,c.id),a.diff);else if(a.type==="thread.tokenUsage.updated"){const d=de(a.usage);if(d){const g=re(yt,d.last);g&&(yt=g,et={...d,last:g})}}else if(a.type==="turn.failed"){await rt(c.id,await c.readName()),wt!==a.error.message&&await s({type:"error",errorText:a.error.message}),await nt("error"),F=!0,qt(y);return}else a.type==="error"&&(wt=a.message,await s({type:"error",errorText:a.message}));F=!W.signal.aborted,F&&qt(y),x||await E(I,h),await rt(c.id,await c.readName()),await nt("stop")}catch(n){const a=n instanceof Error?n.message:"Chat stream failed";x||await E(I,h),await s({type:"error",errorText:a}),await nt("error")}finally{P&&clearTimeout(P),O?.removeEventListener("abort",tt),z.delete(W),F||Ut(y)}}}}async function Fe({body:t,headers:e,signal:r},o){await(await At({body:t,headers:e})).run(o,r)}B.post("/v1/chat/stream",Qt("json",Zt),async t=>{const e=t.req.valid("json"),r=await(async()=>{try{return await At({body:e,headers:t.req.raw.headers})}catch(o){throw o instanceof N?new D(o.status,{message:o.message}):o}})();return Vt(t,async o=>{const i=async u=>{await o.writeSSE(u)};await r.run({writeData:async u=>{await i({data:JSON.stringify(u)})},writeDone:async()=>{await i({data:"[DONE]"})}},t.req.raw.signal)})});var Xe=B;export{N as ChatRequestError,Pt as cancelCodexConversation,We as codexActivitySnapshot,Xe as default,Fe as handleCodexChatStream,At as prepareCodexChatStream};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@messenger-agent/codex-agent",
|
|
3
|
-
"version": "0.26.0-alpha.
|
|
3
|
+
"version": "0.26.0-alpha.6",
|
|
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.26.0-alpha.
|
|
25
|
+
"@messenger-agent/shared": "0.26.0-alpha.6"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/ws": "^8.18.1"
|