@messenger-agent/codex-agent 0.26.0-alpha.6 → 0.26.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.
- package/dist/app-server-protocol.d.ts +0 -5
- package/dist/codex.d.ts +0 -1
- package/dist/codex.js +3 -3
- package/dist/config.d.ts +3 -1
- package/dist/config.js +1 -1
- package/dist/dynamic-tools.d.ts +1 -1
- package/dist/dynamic-tools.js +1 -1
- package/dist/platform-instructions.d.ts +1 -2
- package/dist/platform-instructions.js +3 -4
- package/dist/routes/chat.js +2 -2
- package/package.json +2 -2
|
@@ -95,7 +95,6 @@ export type TurnSteerParams = {
|
|
|
95
95
|
};
|
|
96
96
|
export type Thread = {
|
|
97
97
|
id: string;
|
|
98
|
-
name?: string | null;
|
|
99
98
|
turns?: Turn[];
|
|
100
99
|
};
|
|
101
100
|
export type Turn = {
|
|
@@ -228,10 +227,6 @@ export type ItemDeltaEvent = {
|
|
|
228
227
|
export type AppServerEvent = {
|
|
229
228
|
type: "thread.started";
|
|
230
229
|
thread_id: string;
|
|
231
|
-
} | {
|
|
232
|
-
type: "thread.name.updated";
|
|
233
|
-
thread_id: string;
|
|
234
|
-
name: string;
|
|
235
230
|
} | {
|
|
236
231
|
type: "turn.started";
|
|
237
232
|
turn_id: string;
|
package/dist/codex.d.ts
CHANGED
|
@@ -38,7 +38,6 @@ export declare class CodexThread {
|
|
|
38
38
|
submitPendingUserInput(itemId: string, response: ToolRequestUserInputResponse): Promise<string | undefined>;
|
|
39
39
|
interrupt(turnId: string): Promise<void>;
|
|
40
40
|
interruptPendingUserInput(turnId: string, itemId: string): Promise<void>;
|
|
41
|
-
readName(): Promise<string | undefined>;
|
|
42
41
|
private interruptStaleTurn;
|
|
43
42
|
private turnStartParams;
|
|
44
43
|
private eventsForTurn;
|
package/dist/codex.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{mkdirSync as
|
|
2
|
-
`);return{items:d,finalResponse:i||void 0,usage:a}}async runStreamed(e,
|
|
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});
|
|
1
|
+
import{mkdirSync as k,existsSync as P}from"node:fs";import{appServerClient as l}from"./app-server-client.js";import{dynamicToolSpecs as E}from"./dynamic-tools.js";import{loadAllSessions as U,persistSession as _}from"./db.js";import{appConfig as p}from"./config.js";import{logger as I}from"@messenger-agent/shared/logger";import{resolveAgentWorkdir as N}from"@messenger-agent/shared/agent-workspaces";import{loadCodexPlatformInstructions as C}from"./platform-instructions.js";const v=C(void 0,p.schedule?.enabled===!0);class T extends Error{path;constructor(e){super(`Workdir not found: ${e}`),this.path=e}}class x{id;model;workingDirectory;constructor(e,t,n){this.id=e,this.model=t,this.workingDirectory=n}async run(e,t){const n=await this.runStreamed(e,t),r=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)||r.set(u.item.id,u.item);else if(u.type==="item.completed")o.add(u.item.id),r.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(r.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,t){const n=l.request("turn/start",this.turnStartParams(e,t)),r=await this.waitForTurnStart(n,t?.signal),o=r.turn.id;return{turnId:o,events:this.eventsForTurn(o,r.turn,t?.signal)}}async waitForTurnStart(e,t){if(!t)return e;let n,r;const o=new Promise((d,i)=>{r=i}),a=()=>{n=t.reason??new DOMException("The operation was aborted","AbortError"),r(n)};t.addEventListener("abort",a,{once:!0}),t.aborted&&a();try{return await Promise.race([e,o])}catch(d){throw d===n&&e.then(i=>this.interrupt(i.turn.id)).catch(i=>I.error("Failed to clean up aborted Codex turn start:",i)),d}finally{t.removeEventListener("abort",a)}}async steerStreamed(e,t,n){const r={threadId:this.id,input:[{type:"text",text:e}],expectedTurnId:t},o=await l.request("turn/steer",r);return{turnId:o.turnId,events:this.eventsForTurn(o.turnId,void 0,n?.signal,void 0,!0)}}async continueWithUserInput(e,t,n){const r=l.pendingUserInputTurnId(this.id,e);return r?{turnId:r,events:this.eventsForTurn(r,void 0,n?.signal,async()=>{if(!await l.respondToUserInput(this.id,e,t))throw new Error(`No pending user input request found for item: ${e}`)})}:(await this.interruptStaleTurn(n?.sourceTurnId),this.runStreamed(A(e,t),n))}async submitPendingUserInput(e,t){const n=l.pendingUserInputTurnId(this.id,e);if(!(!n||!await l.respondToUserInput(this.id,e,t)))return n}async interrupt(e){await l.request("turn/interrupt",{threadId:this.id,turnId:e})}async interruptPendingUserInput(e,t){await this.interrupt(e),l.discardPendingUserInput(this.id,t)}async interruptStaleTurn(e){if(!e)return;(await l.request("thread/read",{threadId:this.id,includeTurns:!0})).thread.turns?.find(r=>r.id===e)?.status==="inProgress"&&await this.interrupt(e)}turnStartParams(e,t){return{threadId:this.id,input:[{type:"text",text:e,text_elements:[]}],cwd:this.workingDirectory,approvalPolicy:"never",model:this.model,...t?.collaborationMode?{collaborationMode:t.collaborationMode}:null}}async*eventsForTurn(e,t,n,r,o=!1){const a=[...t||o?[{type:"thread.started",thread_id:this.id},{type:"turn.started",turn_id:e}]:[],...t?M(t):[]];let d=t?.status==="completed"||t?.status==="failed"||t?.status==="interrupted",i,u=!1,c=()=>{};const f=({method:h,params:w})=>{if(u)return;const g=R(h,w,this.id,e);g&&(a.push(g),(g.type==="turn.completed"||g.type==="turn.failed")&&(d=!0),c())},y=h=>{i=h,d=!0,c()},m=()=>{u=!0,a.length=0,d=!0,c()};l.on("notification",f),l.on("exit",y),n?.addEventListener("abort",m,{once:!0}),n?.aborted&&m();try{for(await r?.();!d||a.length>0;){for(;a.length>0;)yield a.shift();if(d)break;await new Promise(h=>{const w=setTimeout(()=>{c=()=>{},h()},50);c=()=>{clearTimeout(w),c=()=>{},h()}})}i&&(yield{type:"error",message:i.message})}finally{l.off("notification",f),l.off("exit",y),n?.removeEventListener("abort",m)}}}function A(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});I.info(`SessionManager: loaded ${this.sessions.size} persisted sessions`)}async getOrCreate(e,t,n,r,o,a,d){const i=this.resolveWorkdir(n,r);if(e){const c=this.sessions.get(e);if(c&&c.workdir===i){const f=c.llmProxyBinding??d,y=await l.request("thread/resume",this.threadResumeParams(e,o,i,a,f));if(!c.llmProxyBinding&&f){const m={...c,llmProxyBinding:f};this.sessions.set(e,m),_({sessionId:e,botName:m.botName,workdir:m.workdir,createdAt:Date.now(),llmProxyBinding:f})}return{thread:new x(y.thread.id,o,i),sessionId:e,isNewSession:!1}}}if(n&&!P(i))throw new T(i);k(i,{recursive:!0});const u=await l.request("thread/start",this.threadStartParams(o,i,a,d));return{thread:new x(u.thread.id,o,i),sessionId:void 0,isNewSession:!0}}async fork(e,t,n,r,o,a,d){const i=this.resolveWorkdir(n,r);if(n&&!P(i))throw new T(i);k(i,{recursive:!0});const c=(await l.request("thread/fork",this.threadForkParams(e.threadId,e.turnId,o,i,a,d))).thread.id;return this.persist(c,t,n,r,d),{thread:new x(c,o,i),sessionId:c,isNewSession:!1}}persist(e,t,n,r,o){const a=t??"main",d=this.resolveWorkdir(n,r);this.sessions.set(e,{botName:a,workdir:d,llmProxyBinding:o}),_({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,t){return N(p,t,e)}threadStartParams(e,t,n,r){return{model:e,modelProvider:p.codex.baseUrl?"custom":null,cwd:t,approvalPolicy:"never",sandbox:n,config:this.codexConfig(r),developerInstructions:v,dynamicTools:E(p.schedule?.enabled===!0),experimentalRawEvents:!1,persistExtendedHistory:!1}}threadResumeParams(e,t,n,r,o){return{threadId:e,model:t,modelProvider:p.codex.baseUrl?"custom":null,cwd:n,approvalPolicy:"never",sandbox:r,config:this.codexConfig(o),developerInstructions:v,excludeTurns:!0,persistExtendedHistory:!1}}threadForkParams(e,t,n,r,o,a){return{threadId:e,lastTurnId:t,model:n,modelProvider:p.codex.baseUrl?"custom":null,cwd:r,approvalPolicy:"never",sandbox:o,config:this.codexConfig(a),developerInstructions:v,ephemeral:!1,excludeTurns:!0}}codexConfig(e){return{approval_policy:"never",...p.codex.baseUrl?{model_provider:"custom",model_reasoning_effort:"medium",disable_response_storage:!0,model_providers:{custom:{name:"custom",base_url:p.codex.baseUrl,wire_api:"responses",...p.codex.apiKey?{env_key:"OPENAI_API_KEY"}:null,...e?{http_headers:{"X-Elevo-LLM-Binding":e}}:null}},"features.default_mode_request_user_input":!1}:null}}}p.codex.apiKey&&!process.env.OPENAI_API_KEY&&(process.env.OPENAI_API_KEY=p.codex.apiKey);function M(s){const e=[];for(const t of s.items??[])e.push({type:"item.completed",item:t});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 R(s,e,t,n){const r=e;if(!r)return;const o=r.threadId,a=r.turnId;if(!(typeof o=="string"&&o!==t)&&!(typeof a=="string"&&a!==n)){if(s==="item/started"&&S(r.item))return{type:"item.started",item:r.item};if(s==="item/completed"&&S(r.item))return{type:"item.completed",item:r.item};if(s==="item/agentMessage/delta")return b(r,"agentMessage");if(s==="item/reasoning/summaryTextDelta"||s==="item/reasoning/textDelta")return b(r,"reasoning");if(s==="item/plan/delta")return b(r,"plan");if(s==="item/tool/requestUserInput")return{type:"item.tool.requestUserInput",params:r};if(s==="turn/diff/updated"&&typeof r.diff=="string")return{type:"turn.diff.updated",diff:r.diff};if(s==="thread/tokenUsage/updated")return{type:"thread.tokenUsage.updated",usage:D(r)};if(s==="turn/completed"){const d=r.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:",r),{type:"error",message:q(r)}}}function q(s){const e=s.error,t=e&&typeof e=="object"&&"message"in e?e.message:void 0,n=s.additionalDetails,r=typeof t=="string"&&t.trim()?t:"Codex app-server error";return typeof n=="string"&&n.trim()?`${r}: ${n}`:r}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 D(s){const e=s.tokenUsage;if(!e||typeof e!="object")return null;const t=e,n=t.total,r=t.last;if(!n||typeof n!="object"||!r||typeof r!="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=t.modelContextWindow;return{total:o(n),last:o(r),model_context_window:typeof a=="number"?a:void 0}}function S(s){return!!(s&&typeof s=="object"&&"type"in s&&"id"in s)}const H=new F;export{x as CodexThread,T as WorkdirNotFoundError,H as sessionManager};
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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};
|
|
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({enabled:e.boolean().optional(),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:{enabled:!1},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:{enabled:o.schedule?.enabled??!1,...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.d.ts
CHANGED
|
@@ -9,6 +9,6 @@ type ManagedTaskToolContext = {
|
|
|
9
9
|
export declare function setManagedTaskToolContext(threadId: string, context: ManagedTaskToolContext): void;
|
|
10
10
|
export declare function inheritManagedTaskToolContext(parentThreadId: string, childThreadIds: string[]): void;
|
|
11
11
|
export declare function inheritManagedTaskToolContextFromNotification(method: string, params: unknown): void;
|
|
12
|
-
export declare function dynamicToolSpecs(): DynamicToolSpec[];
|
|
12
|
+
export declare function dynamicToolSpecs(scheduleEnabled?: boolean): DynamicToolSpec[];
|
|
13
13
|
export declare function handleDynamicToolCall(params: DynamicToolCallParams): Promise<DynamicToolCallResponse>;
|
|
14
14
|
export {};
|
package/dist/dynamic-tools.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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 r}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 p=1200*1e3,M=new I({headersTimeout:p,bodyTimeout:p}),R=(t,e)=>x(t,{...e,dispatcher:M}),d=r.object({prompt:r.string().describe("The text prompt describing the image to generate"),store_path:r.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:r.enum(["1024x1024","1536x1024","1024x1536"]).describe("Image size. Leave it undefined to use the auto size.").optional()}),m=r.object({path:r.string().describe("File to send. Absolute paths use the system path; relative paths resolve from the active workspace.")}),N=r.toJSONSchema(d),O=r.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 i=n;if(i.type==="subAgentActivity"&&i.kind==="started"&&typeof o.threadId=="string"&&typeof i.agentThreadId=="string"){u(o.threadId,[i.agentThreadId]);return}i.type==="collabAgentToolCall"&&i.tool==="spawnAgent"&&typeof i.senderThreadId=="string"&&Array.isArray(i.receiverThreadIds)&&u(i.senderThreadId,i.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(t=!1){const e=[{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(o=>({name:o.name,description:o.description,inputSchema:o.inputSchema}))];return t&&e.push({name:c.name,description:c.description,inputSchema:c.inputSchema}),e}async function V(t){try{if(t.tool==="generate_image"){const e=d.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:i}=await T({model:n.image(o.imageModel),prompt:e.prompt,size:e.size,maxRetries:0,abortSignal:AbortSignal.timeout(p)}),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(i.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,2 +1 @@
|
|
|
1
|
-
export declare function loadCodexPlatformInstructions(templatePath?: string): string;
|
|
2
|
-
export declare const codexPlatformInstructions: string;
|
|
1
|
+
export declare function loadCodexPlatformInstructions(templatePath?: string, scheduleEnabled?: boolean): string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as c,readFileSync as d}from"node:fs";import{fileURLToPath as o}from"node:url";import{logger as i}from"@messenger-agent/shared/logger";const m=[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"],s="## 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",l="## 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 h(n,r=!1){const e=r?`
|
|
2
|
+
${l}`:"",t=[n??process.env.CODEX_AGENTS_TEMPLATE_PATH,...m].find(a=>!!(a&&c(a)));return t?`${d(t,"utf-8").trimEnd()}
|
|
2
3
|
|
|
3
|
-
${
|
|
4
|
-
${n}`:(i.warn("Codex platform instructions template not found"),`${a}
|
|
5
|
-
${n}`)}const _=m();export{_ as codexPlatformInstructions,m as loadCodexPlatformInstructions};
|
|
4
|
+
${s}${e}`:(i.warn("Codex platform instructions template not found"),`${s}${e}`)}export{h 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 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};
|
|
1
|
+
import{Hono as Ht}from"hono";import{HTTPException as D}from"hono/http-exception";import{zValidator as Jt}from"@hono/zod-validator";import{streamSSE as Kt}from"hono/streaming";import{randomUUID as Qt}from"node:crypto";import{sessionManager as j,WorkdirNotFoundError as Vt}from"../codex.js";import{logger as rt}from"@messenger-agent/shared/logger";import{ChatBodySchema as Gt,RequestUserInputToolPartSchema as Yt}from"../schemas.js";import{appConfig as xt}from"../config.js";import{setManagedTaskToolContext as Zt}from"../dynamic-tools.js";import{resolveAgentWorkspaceRoot as te}from"@messenger-agent/shared/agent-workspaces";import{saveUploadedFilePart as ee}from"@messenger-agent/shared/uploaded-files";import{commandExecutionItem as bt,addTokenUsage as ne,displayCommand as ae,dynamicToolCallItem as Tt,dynamicToolCallOutput as re,fileChangeItem as St,itemText as Et,mcpToolCallItem as Rt,mcpToolCallOutput as oe,toUsage as ie,turnOptions as se,webSearchItem as kt,webSearchPayload as Ot}from"./shared.js";const B=new Ht,f=new Map,z=new Set,ot=new Map,k=new Map;function Le(){const t=new Set([...k.values()].map(e=>e.conversationId).filter(e=>!!e));return{active:z.size,waiting:t.size}}function de(t){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}}function ue(t){const e=v(t,"X-Matrix-Room-Id")?.trim(),r=v(t,"X-Matrix-Thread-ID")?.trim();return e&&r?`${e}/${r}`:void 0}function le(t){const e=v(t,"X-Matrix-Room-Id")?.trim(),r=v(t,"X-Matrix-Event-ID")?.trim(),o=v(t,"X-Matrix-Thread-ID")?.trim();if(!(!e||!r))return{roomId:e,eventId:r,...o?{threadRootEventId:o}:null}}class q extends Error{status;constructor(e,r){super(r),this.status=e,this.name="ChatRequestError"}}function v(t,e){return t?.get(e)??void 0}async function _t(t){const e=f.get(t);if(!e)return!1;for(e.cancelRequested=!0,e.turnId||e.owner?.abortController.abort();;){const r=e.transition;if(await r,r===e.transition)break}return e.thread&&e.turnId&&(e.interrupt||(e.interrupt=e.thread.interrupt(e.turnId).finally(()=>{e.interrupt=void 0})),await e.interrupt),e.owner?.abortController.abort(),e.resolveOwnerReady?.(),f.get(t)===e&&f.delete(t),!0}function Mt(t){let e=f.get(t);e||(e={conversationId:t,generation:0,cancelRequested:!1,transition:Promise.resolve()},f.set(t,e));const r=++e.generation,o=new AbortController,i=!e.owner;return i&&(e.owner={generation:r,abortController:o},e.ownerReady=new Promise(d=>{e.resolveOwnerReady=d})),{state:e,generation:r,abortController:o,ownsOutput:i}}function N(t){return f.get(t.state.conversationId)===t.state&&t.state.owner?.generation===t.generation}async function H(t,e){const r=t.transition;let o;const i=new Promise(d=>{o=d});t.transition=r.then(()=>i),await r;try{return await e()}finally{o()}}async function ce(t,e,r,o){!t.ownsOutput&&!t.state.turnId&&await t.state.ownerReady;try{return await H(t.state,async()=>{const{state:i}=t;if(f.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");i.thread=e;const d=i.turnId?await e.steerStreamed(r,i.turnId,{signal:t.abortController.signal}):await e.runStreamed(r,{...o,signal:t.abortController.signal});return i.turnId=d.turnId,d})}finally{t.ownsOutput&&t.state.resolveOwnerReady?.()}}async function pe(t,e,r){return H(t.state,async()=>{const{state:o}=t;if(!N(t)||o.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");o.thread=e;const i=await r();return o.turnId=i.turnId,o.resolveOwnerReady?.(),i})}async function fe(t,e,r,o){return H(t.state,async()=>{const{state:i}=t;if(f.get(i.conversationId)!==i||i.cancelRequested)throw new DOMException("Chat stream was cancelled before user input was submitted","AbortError");i.thread=e;const d=await e.submitPendingUserInput(r,o);return d&&(i.turnId=d),d})}async function me(t,e,r,o,i){return H(t.state,async()=>{const{state:d}=t;if(f.get(d.conversationId)!==d||d.cancelRequested)throw new DOMException("Chat stream was cancelled before the turn started","AbortError");d.thread=e,await e.interruptPendingUserInput(r.turnId,r.toolCallId),d.turnId=void 0;const T=await e.runStreamed(o,{...i,signal:t.abortController.signal});return d.turnId=T.turnId,T})}function Pt(t,e,r){t.state.thread=e,t.state.turnId=r}function Dt(t){const{state:e}=t;N(t)&&(e.owner=void 0,e.resolveOwnerReady?.(),e.turnId=void 0,f.get(e.conversationId)===e&&f.delete(e.conversationId))}function qt(t){N(t)&&(t.state.owner=void 0,t.state.resolveOwnerReady?.(),!t.state.turnId&&f.get(t.state.conversationId)===t.state&&f.delete(t.state.conversationId))}function we(t){N(t)&&(qt(t),t.state.resolveOwnerReady?.(),!t.state.turnId&&f.get(t.state.conversationId)===t.state&&f.delete(t.state.conversationId))}function J(t,e){return`${t??"unknown"}:${e}`}function ye(t){if(t)return[...k.values()].find(e=>e.conversationId===t)}function Nt(t,e){return t??e}function ge(t){return t.type==="file"&&"mediaType"in t&&typeof t.mediaType=="string"&&"url"in t&&typeof t.url=="string"&&(!("filename"in t)||t.filename===void 0||typeof t.filename=="string")}async function Ie(t){if(t.type==="text"&&typeof t.text=="string")return t.text;if(ge(t))return ee(t,xt.fileUploads.tempDir);throw new D(400,{message:`Unsupported message part type: ${t.type}`})}async function he(t){const r=(await Promise.all(t.map(Ie))).filter(o=>!!(o&&o.trim().length>0)).join(`
|
|
2
|
+
`).trim();if(!r)throw new D(400,{message:"No supported message content found"});return r}function Ce(t){if(!t.some(o=>o.type==="tool"))return;if(t.length!==1)throw new D(400,{message:"When a tool part is present, the message must contain exactly one part"});const r=Yt.safeParse(t[0]);if(!r.success)throw new D(400,{message:`Invalid requestUserInput tool part: ${r.error.message}`});return{itemId:r.data.toolCallId,response:r.data.output,sourceTurnId:r.data.sourceTurnId}}function m(t,e,r,o){const i=[e,r,o].filter(Boolean).join(":"),d=t.get(i);if(d)return d;const T=Qt();return t.set(i,T),T}function ve(t,e){return t&&t.length>0?t:e}function xe(t){rt.debug("Received chat stream event:",t)}function be(t){if(t instanceof Vt||t&&typeof t=="object"&&"path"in t&&typeof t.path=="string")return t.path;if(t instanceof Error&&t.message.startsWith("Workdir not found: "))return t.message.slice(19)}B.post("/v1/chat/:conversation_id/cancel",async t=>{const e=t.req.param("conversation_id");try{return await _t(e)?t.json({success:!0}):t.json({error:de(e)},404)}catch(r){const o=r instanceof Error?r.message:"Failed to interrupt Codex conversation";return rt.error(`Failed to cancel Codex conversation ${e}:`,r),t.json({error:{type:"server_error",code:"cancel_failed",message:o}},500)}}),B.get("/admin/status",t=>t.json({activeTaskCount:z.size,waitingClientToolCount:k.size,clientToolWaits:[...k.values()].map(({conversationId:e,toolCallId:r,toolName:o,since:i})=>({conversationId:e,toolCallId:r,toolName:o,since:i}))}));async function Ut({body:t,headers:e}){const{conversationId:r,fork:o,model:i,effort:d,message:T}=t,K=v(e,"X-Matrix-Bot-Name"),Q=v(e,"X-Matrix-Workdir"),U=v(e,"X-Matrix-Workspace-ID"),Lt=v(e,"X-Matrix-Agent-Mode"),V=v(e,"X-Elevo-LLM-Binding")?.trim()||void 0,G=o?void 0:j.bindingFor(r);if(V&&G&&V!==G)throw new q(409,"LLM proxy binding does not match the existing conversation");const L=G??V,At=ue(e),Wt=le(e),Y=Ce(T.parts),Z=Y?void 0:await he(T.parts),it=Y?void 0:ye(r),c=Y,O=Z&&it?it:void 0;let A=!o&&r?Mt(r):void 0;const Ft=(()=>{try{return te(xt,U)}catch(S){throw new q(400,S instanceof Error?S.message:"Invalid workspace")}})(),{thread:y,isNewSession:Xt}=await(async()=>{try{if(o){if(!("threadId"in o))throw new q(400,"Codex fork requires threadId and turnId");return await j.fork(o,K,Q,U,i,"danger-full-access",L)}return await j.getOrCreate(r,K,Q,U,i,"danger-full-access",L)}catch(S){A&&we(A);const _=be(S);throw _?new q(400,`Workdir not found: ${_}`):S}})();A??=Mt(y.id),Zt(y.id,{workspaceRoot:Ft,threadRef:At,matrixEvent:Wt,llmProxyBinding:L});const st=se(Lt,i,d);return{run:async(S,_)=>{const w=A,W=w.abortController;z.add(W);const tt=()=>{N(w)&&_t(w.state.conversationId).catch(a=>rt.error("Failed to cancel disconnected Codex stream:",a))};_?.aborted&&tt(),_?.addEventListener("abort",tt,{once:!0});let x=!!c,dt=!!c,ut=!1,l,I=y.id,h,F=!1,P;const lt=new Set,ct=new Set,X=new Set,p=new Map,pt=new Set,$t=new Set,ft=new Set;let mt,et,wt;const yt=()=>{P=setTimeout(()=>{s({type:"heartbeat"}).catch(()=>{})},6e4)},gt=async a=>{P&&clearTimeout(P),a.data==="[DONE]"?await S.writeDone():await S.writeData(JSON.parse(String(a.data))),yt()},s=async a=>{await gt({data:JSON.stringify(a)})},$=async()=>{await gt({data:"[DONE]"})},jt=async()=>{const a=Nt(l,y.id),n=ot.get(a);n&&(ot.delete(a),await s({type:"data",kind:"diff",data:{diff:n}}))},It=async(a,n)=>{if(a.type!=="contextCompaction")return!1;if(n==="end"&&!pt.has(a.id))return!0;const u=n==="start"?pt:$t;return u.has(a.id)||(u.add(a.id),await s({type:"data",kind:"compact",data:n==="start"?{phase:n,activityId:a.id}:{phase:n,activityId:a.id,status:"completed"}})),!0},nt=async a=>{if(a.type!=="subAgentActivity")return!1;if(ft.has(a.id))return!0;ft.add(a.id);const n=m(p,l,a.id),u={activity:a.kind,agentThreadId:a.agentThreadId,...a.agentPath?{agentPath:a.agentPath}:null};return await s({type:"tool-input-available",toolCallId:n,toolName:`subagent_${a.kind}`,...a.agentPath?{title:a.agentPath}:null,input:u}),await s(a.kind==="interrupted"?{type:"tool-output-error",toolCallId:n,errorText:"Subagent interrupted"}:{type:"tool-output-available",toolCallId:n,output:u}),!0};yt();const ht=a=>{l!==a&&(l=a,Xt&&j.persist(a,K,Q,U,L))},E=async(a,n)=>{x||(x=!0,ht(a),await s({type:"start",messageMetadata:{conversationId:a,threadId:a,...n?{turnId:n}:null}}),await s({type:"start-step"}),dt=!0)},at=async a=>{ut||(ut=!0,dt&&(await jt(),a==="stop"&&et&&await s({type:"data",kind:"token_usage",data:et}),await s({type:"finish-step"})),await s({type:"finish",finishReason:a}),await $())},Ct=async a=>{if(lt.has(a.id))return;lt.add(a.id);const n=a.type==="plan"?{type:"plan"}:void 0;await s({type:"text-start",id:a.id,...n?{textMetadata:n}:{}})},vt=async a=>{ct.has(a.id)||(ct.add(a.id),await s({type:"reasoning-start",id:a.id}))},Bt=async a=>{const n=bt(a);if(n)return await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:"bash",input:ae(n.command)}),!0;const u=St(a);if(u){for(const[b,C]of u.changes.entries())await s({type:"tool-input-available",toolCallId:m(p,l,a.id,String(b)),toolName:C.kind.type==="update"?"Edit":C.kind.type==="add"?"Add":"Delete",title:C.path,input:C});return!0}const g=Rt(a);if(g)return await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:g.tool,input:g.arguments}),!0;const R=Tt(a);if(R)return await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:R.tool,input:R.arguments}),!0;const M=kt(a);return M?(await s({type:"tool-input-available",toolCallId:m(p,l,a.id),toolName:"web_search",title:M.query,input:Ot(M)}),!0):!1},zt=async a=>{const n=bt(a);if(n){const b=n.status==="failed"||n.status==="declined";return await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id),errorText:ve(n.aggregatedOutput,"Command execution failed")}:{type:"tool-output-available",toolCallId:m(p,l,a.id),output:n.aggregatedOutput??""}),!0}const u=St(a);if(u){const b=u.status==="failed"||u.status==="declined";for(const[C]of u.changes.entries())await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id,String(C)),errorText:u.status}:{type:"tool-output-available",toolCallId:m(p,l,a.id,String(C)),output:u.status,preliminary:!1});return!0}const g=Rt(a);if(g){const b=g.status==="failed";return await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id),errorText:g.error?.message??"MCP tool call failed"}:{type:"tool-output-available",toolCallId:m(p,l,a.id),output:oe(g)}),!0}const R=Tt(a);if(R){const b=R.status==="failed"||R.success===!1,C=re(R);return await s(b?{type:"tool-output-error",toolCallId:m(p,l,a.id),errorText:C||"Dynamic tool call failed"}:{type:"tool-output-available",toolCallId:m(p,l,a.id),output:C}),!0}const M=kt(a);return M?(await s({type:"tool-output-available",toolCallId:m(p,l,a.id),output:Ot(M)}),!0):!1};try{if(c&&!w.ownsOutput){await fe(w,y,c.itemId,c.response),k.delete(J(r,c.itemId)),await $();return}const a=O?await me(w,y,O,Z,st):c?await pe(w,y,()=>y.continueWithUserInput(c.itemId,c.response,{signal:W.signal,sourceTurnId:c.sourceTurnId})):await ce(w,y,Z,st);if(c?k.delete(J(r,c.itemId)):O&&k.delete(J(r,O.toolCallId)),Pt(w,y,a.turnId),!w.ownsOutput){await $();return}c?(await s({type:"data",kind:"agent_session",data:{agentType:"codex",threadId:y.id,turnId:a.turnId}}),await s({type:"tool-output-available",toolCallId:c.itemId,toolName:"request_user_input",output:c.response})):O&&await s({type:"tool-output-error",toolCallId:O.toolCallId,toolName:"request_user_input",errorText:"Question interrupted by text response"});for await(const n of a.events)if(n.type!=="item.delta"&&xe(n),n.type==="thread.started")I=n.thread_id,ht(I);else if(n.type==="turn.started")h=n.turn_id,Pt(w,y,h),await E(I,h);else if(n.type==="item.started"){if(x||await E(I,h),await It(n.item,"start")||await nt(n.item)||await Bt(n.item))continue;n.item.type==="agentMessage"||n.item.type==="plan"?await Ct(n.item):n.item.type==="reasoning"&&await vt(n.item)}else if(n.type==="item.delta")x||await E(I,h),n.item_type==="agentMessage"||n.item_type==="plan"?(X.add(n.item_id),await s({type:"text-delta",id:n.item_id,delta:n.delta})):n.item_type==="reasoning"&&(X.add(n.item_id),await s({type:"reasoning-delta",id:n.item_id,delta:n.delta}));else if(n.type==="item.updated"){if(await nt(n.item))continue}else if(n.type==="item.completed"){if(x||await E(I,h),await It(n.item,"end")||await nt(n.item)||await zt(n.item))continue;if(n.item.type==="agentMessage"||n.item.type==="plan"){await Ct(n.item);const u=Et(n.item)??"";u&&!X.has(n.item.id)&&await s({type:"text-delta",id:n.item.id,delta:u}),await s({type:"text-end",id:n.item.id})}else if(n.item.type==="reasoning"){await vt(n.item);const u=Et(n.item)??"";u&&!X.has(n.item.id)&&await s({type:"reasoning-delta",id:n.item.id,delta:u}),await s({type:"reasoning-end",id:n.item.id})}}else if(n.type==="item.tool.requestUserInput"){x||await E(I,h),k.set(J(l,n.params.itemId),{conversationId:l,toolCallId:n.params.itemId,toolName:"request_user_input",turnId:n.params.turnId,since:new Date().toISOString()}),await s({type:"tool-input-available",toolCallId:n.params.itemId,toolName:"request_user_input",input:n.params}),await $();return}else if(n.type==="turn.diff.updated")x||await E(I,h),ot.set(Nt(l,y.id),n.diff);else if(n.type==="thread.tokenUsage.updated"){const u=ie(n.usage);if(u){const g=ne(wt,u.last);g&&(wt=g,et={...u,last:g})}}else if(n.type==="turn.failed"){mt!==n.error.message&&await s({type:"error",errorText:n.error.message}),await at("error"),F=!0,Dt(w);return}else n.type==="error"&&(mt=n.message,await s({type:"error",errorText:n.message}));F=!W.signal.aborted,F&&Dt(w),x||await E(I,h),await at("stop")}catch(a){const n=a instanceof Error?a.message:"Chat stream failed";x||await E(I,h),await s({type:"error",errorText:n}),await at("error")}finally{P&&clearTimeout(P),_?.removeEventListener("abort",tt),z.delete(W),F||qt(w)}}}}async function Ae({body:t,headers:e,signal:r},o){await(await Ut({body:t,headers:e})).run(o,r)}B.post("/v1/chat/stream",Jt("json",Gt),async t=>{const e=t.req.valid("json"),r=await(async()=>{try{return await Ut({body:e,headers:t.req.raw.headers})}catch(o){throw o instanceof q?new D(o.status,{message:o.message}):o}})();return Kt(t,async o=>{const i=async d=>{await o.writeSSE(d)};await r.run({writeData:async d=>{await i({data:JSON.stringify(d)})},writeDone:async()=>{await i({data:"[DONE]"})}},t.req.raw.signal)})});var We=B;export{q as ChatRequestError,_t as cancelCodexConversation,Le as codexActivitySnapshot,We as default,Ae as handleCodexChatStream,Ut as prepareCodexChatStream};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@messenger-agent/codex-agent",
|
|
3
|
-
"version": "0.26.0
|
|
3
|
+
"version": "0.26.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.26.0
|
|
25
|
+
"@messenger-agent/shared": "0.26.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/ws": "^8.18.1"
|