@messenger-agent/codex-agent 0.24.0-alpha.2 → 0.24.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,316 +1,8 @@
1
- import { spawn } from "node:child_process";
2
- import { EventEmitter } from "node:events";
3
- import { statSync } from "node:fs";
4
- import { createRequire } from "node:module";
5
- import path from "node:path";
6
- import { handleDynamicToolCall, inheritManagedTaskToolContextFromNotification } from "./dynamic-tools.js";
7
- import { logger } from "@messenger-agent/shared/logger";
8
- const CODEX_NPM_NAME = "@openai/codex";
9
- const PLATFORM_PACKAGE_BY_TARGET = {
10
- "x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
11
- "aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
12
- "x86_64-apple-darwin": "@openai/codex-darwin-x64",
13
- "aarch64-apple-darwin": "@openai/codex-darwin-arm64",
14
- "x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
15
- "aarch64-pc-windows-msvc": "@openai/codex-win32-arm64",
16
- };
17
- const moduleRequire = createRequire(import.meta.url);
18
- export function findCodexPath() {
19
- const { platform, arch } = process;
20
- let targetTriple = null;
21
- switch (platform) {
22
- case "linux":
23
- case "android":
24
- if (arch === "x64")
25
- targetTriple = "x86_64-unknown-linux-musl";
26
- if (arch === "arm64")
27
- targetTriple = "aarch64-unknown-linux-musl";
28
- break;
29
- case "darwin":
30
- if (arch === "x64")
31
- targetTriple = "x86_64-apple-darwin";
32
- if (arch === "arm64")
33
- targetTriple = "aarch64-apple-darwin";
34
- break;
35
- case "win32":
36
- if (arch === "x64")
37
- targetTriple = "x86_64-pc-windows-msvc";
38
- if (arch === "arm64")
39
- targetTriple = "aarch64-pc-windows-msvc";
40
- break;
41
- default:
42
- break;
43
- }
44
- if (!targetTriple) {
45
- throw new Error(`Unsupported platform: ${platform} (${arch})`);
46
- }
47
- const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
48
- if (!platformPackage) {
49
- throw new Error(`Unsupported target triple: ${targetTriple}`);
50
- }
51
- let vendorRoot;
52
- try {
53
- const codexPackageJsonPath = moduleRequire.resolve(`${CODEX_NPM_NAME}/package.json`);
54
- const codexRequire = createRequire(codexPackageJsonPath);
55
- const platformPackageJsonPath = codexRequire.resolve(`${platformPackage}/package.json`);
56
- vendorRoot = path.join(path.dirname(platformPackageJsonPath), "vendor");
57
- }
58
- catch {
59
- throw new Error(`Unable to locate Codex CLI binaries. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies.`);
60
- }
61
- const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
62
- const nativePackage = resolveNativePackage(vendorRoot, targetTriple, codexBinaryName);
63
- if (!nativePackage) {
64
- throw new Error(`Unable to locate Codex CLI binaries for ${targetTriple}. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies.`);
65
- }
66
- return nativePackage.executablePath;
67
- }
68
- function resolveNativePackage(vendorRoot, targetTriple, codexBinaryName) {
69
- const packageRoot = path.join(vendorRoot, targetTriple);
70
- const packageBinaryPath = path.join(packageRoot, "bin", codexBinaryName);
71
- if (isFile(packageBinaryPath) && isFile(path.join(packageRoot, "codex-package.json"))) {
72
- return {
73
- executablePath: packageBinaryPath,
74
- pathDirs: existingDirs(path.join(packageRoot, "codex-path")),
75
- };
76
- }
77
- const legacyBinaryPath = path.join(packageRoot, "codex", codexBinaryName);
78
- if (isFile(legacyBinaryPath)) {
79
- return {
80
- executablePath: legacyBinaryPath,
81
- pathDirs: existingDirs(path.join(packageRoot, "path")),
82
- };
83
- }
84
- return null;
85
- }
86
- function existingDirs(...dirs) {
87
- return dirs.filter(isDirectory);
88
- }
89
- function isFile(filePath) {
90
- try {
91
- return statSync(filePath).isFile();
92
- }
93
- catch {
94
- return false;
95
- }
96
- }
97
- function isDirectory(filePath) {
98
- try {
99
- return statSync(filePath).isDirectory();
100
- }
101
- catch {
102
- return false;
103
- }
104
- }
105
- function parseCommand(command) {
106
- const trimmed = command.trim();
107
- if (!trimmed)
108
- return { command: "codex", args: ["app-server", "--listen", "stdio://"] };
109
- const parts = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
110
- const [cmd, ...args] = parts.map((part) => {
111
- if ((part.startsWith('"') && part.endsWith('"')) || (part.startsWith("'") && part.endsWith("'"))) {
112
- return part.slice(1, -1);
113
- }
114
- return part;
115
- });
116
- return { command: cmd ?? "codex", args };
117
- }
118
- export class AppServerClient extends EventEmitter {
119
- toolHandler;
120
- child;
121
- nextId = 1;
122
- pending = new Map();
123
- pendingUserInputRequests = new Map();
124
- notifiedUserInputRequests = new Set();
125
- answeredUserInputRequests = new Set();
126
- started;
127
- buffer = "";
128
- constructor(toolHandler = handleDynamicToolCall) {
129
- super();
130
- this.toolHandler = toolHandler;
131
- }
132
- async request(method, params) {
133
- await this.ensureStarted();
134
- return this.writeRequest(method, params);
135
- }
136
- async writeRequest(method, params) {
137
- if (!this.child)
138
- throw new Error("Codex app-server is not running");
139
- const id = this.nextId++;
140
- const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params });
141
- return new Promise((resolve, reject) => {
142
- this.pending.set(id, { resolve: (value) => resolve(value), reject });
143
- this.child?.stdin.write(`${payload}\n`, (err) => {
144
- if (err) {
145
- this.pending.delete(id);
146
- reject(err);
147
- }
148
- });
149
- });
150
- }
151
- async notify(method, params) {
152
- await this.ensureStarted();
153
- if (!this.child)
154
- throw new Error("Codex app-server is not running");
155
- this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
156
- }
157
- async respondToUserInput(threadId, itemId, response) {
158
- await this.ensureStarted();
159
- if (!this.child)
160
- throw new Error("Codex app-server is not running");
161
- const key = userInputRequestKey(threadId, itemId);
162
- if (this.answeredUserInputRequests.has(key)) {
163
- throw new Error(`User input request already answered for item: ${itemId}`);
164
- }
165
- const pending = this.pendingUserInputRequests.get(key);
166
- if (!pending)
167
- return false;
168
- this.pendingUserInputRequests.delete(key);
169
- this.answeredUserInputRequests.add(key);
170
- this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: pending.requestId, result: response })}\n`);
171
- return true;
172
- }
173
- pendingUserInputTurnId(threadId, itemId) {
174
- return this.pendingUserInputRequests.get(userInputRequestKey(threadId, itemId))?.turnId;
175
- }
176
- discardPendingUserInput(threadId, itemId) {
177
- const key = userInputRequestKey(threadId, itemId);
178
- this.pendingUserInputRequests.delete(key);
179
- this.notifiedUserInputRequests.delete(key);
180
- this.answeredUserInputRequests.delete(key);
181
- }
182
- async ensureStarted() {
183
- if (!this.started) {
184
- this.started = this.start();
185
- }
186
- return this.started;
187
- }
188
- async start() {
189
- const configured = process.env.CODEX_APP_SERVER_COMMAND;
190
- const { command, args } = configured
191
- ? parseCommand(configured)
192
- : { command: findCodexPath(), args: ["app-server", "--listen", "stdio://"] };
193
- const env = { ...process.env };
194
- this.child = spawn(command, args, { env, stdio: ["pipe", "pipe", "pipe"] });
195
- this.child.stdout.setEncoding("utf-8");
196
- this.child.stderr.setEncoding("utf-8");
197
- this.child.stdout.on("data", (chunk) => this.handleStdout(chunk));
198
- this.child.stderr.on("data", (chunk) => logger.debug(`codex app-server stderr: ${chunk.trimEnd()}`));
199
- this.child.on("exit", (code, signal) => this.handleExit(code, signal));
200
- this.child.on("error", (err) => this.handleProcessError(err));
201
- await this.writeRequest("initialize", {
202
- clientInfo: { name: "codex-agent", title: "codex-agent", version: "0.1.0" },
203
- capabilities: { experimentalApi: true, requestAttestation: false },
204
- });
205
- }
206
- handleStdout(chunk) {
207
- this.buffer += chunk;
208
- while (true) {
209
- const newline = this.buffer.indexOf("\n");
210
- if (newline === -1)
211
- break;
212
- const line = this.buffer.slice(0, newline).trim();
213
- this.buffer = this.buffer.slice(newline + 1);
214
- if (!line)
215
- continue;
216
- try {
217
- this.handleMessage(JSON.parse(line));
218
- }
219
- catch (err) {
220
- logger.error("Failed to parse codex app-server JSON-RPC message:", err);
221
- }
222
- }
223
- }
224
- handleMessage(message) {
225
- if (!message.method ||
226
- !(message.method.endsWith("/delta") ||
227
- message.method.endsWith("/outputDelta") ||
228
- message.method === "turn/diff/updated")) {
229
- logger.debug(`Received message:`, message);
230
- }
231
- if (message.id !== undefined && message.method) {
232
- this.handleServerRequest(message).catch((err) => logger.error("Failed to handle app-server request:", err));
233
- return;
234
- }
235
- if (message.id !== undefined) {
236
- const pending = this.pending.get(message.id);
237
- if (!pending)
238
- return;
239
- this.pending.delete(message.id);
240
- if (message.error) {
241
- pending.reject(new Error(message.error.message ?? `Codex app-server request failed: ${message.error.code}`));
242
- }
243
- else {
244
- pending.resolve(message.result);
245
- }
246
- return;
247
- }
248
- if (message.method) {
249
- inheritManagedTaskToolContextFromNotification(message.method, message.params);
250
- this.emit("notification", { method: message.method, params: message.params });
251
- }
252
- }
253
- async handleServerRequest(message) {
254
- if (!this.child || message.id === undefined || !message.method)
255
- return;
256
- try {
257
- if (message.method === "item/tool/call") {
258
- const result = await this.toolHandler(message.params);
259
- this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: message.id, result })}\n`);
260
- return;
261
- }
262
- if (message.method === "item/tool/requestUserInput") {
263
- const requestParams = message.params;
264
- const requestKey = userInputRequestKey(requestParams.threadId, requestParams.itemId);
265
- this.pendingUserInputRequests.set(requestKey, {
266
- threadId: requestParams.threadId,
267
- turnId: requestParams.turnId,
268
- itemId: requestParams.itemId,
269
- requestId: message.id,
270
- });
271
- if (this.notifiedUserInputRequests.has(requestKey))
272
- return;
273
- this.notifiedUserInputRequests.add(requestKey);
274
- this.emit("notification", { method: message.method, params: requestParams });
275
- return;
276
- }
277
- this.child.stdin.write(`${JSON.stringify({
278
- jsonrpc: "2.0",
279
- id: message.id,
280
- error: { code: -32601, message: `Unsupported server request: ${message.method}` },
281
- })}\n`);
282
- }
283
- catch (err) {
284
- const messageText = err instanceof Error ? err.message : String(err);
285
- this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: message.id, error: { code: -32000, message: messageText } })}\n`);
286
- }
287
- }
288
- handleExit(code, signal) {
289
- const err = new Error(`Codex app-server exited${signal ? ` with signal ${signal}` : ` with code ${code}`}`);
290
- for (const pending of this.pending.values()) {
291
- pending.reject(err);
292
- }
293
- this.pending.clear();
294
- this.pendingUserInputRequests.clear();
295
- this.notifiedUserInputRequests.clear();
296
- this.answeredUserInputRequests.clear();
297
- this.child = undefined;
298
- this.started = undefined;
299
- this.emit("exit", err);
300
- }
301
- handleProcessError(err) {
302
- for (const pending of this.pending.values()) {
303
- pending.reject(err);
304
- }
305
- this.pending.clear();
306
- this.pendingUserInputRequests.clear();
307
- this.notifiedUserInputRequests.clear();
308
- this.answeredUserInputRequests.clear();
309
- this.emit("exit", err);
310
- }
311
- }
312
- function userInputRequestKey(threadId, itemId) {
313
- return `${threadId}:${itemId}`;
314
- }
315
- export const appServerClient = new AppServerClient();
316
- appServerClient.setMaxListeners(20);
1
+ import{spawn as m}from"node:child_process";import{EventEmitter as g}from"node:events";import{statSync as l}from"node:fs";import{createRequire as f}from"node:module";import o from"node:path";import{handleDynamicToolCall as q,inheritManagedTaskToolContextFromNotification as I}from"./dynamic-tools.js";import{logger as c}from"@messenger-agent/shared/logger";const h="@openai/codex",R={"x86_64-unknown-linux-musl":"@openai/codex-linux-x64","aarch64-unknown-linux-musl":"@openai/codex-linux-arm64","x86_64-apple-darwin":"@openai/codex-darwin-x64","aarch64-apple-darwin":"@openai/codex-darwin-arm64","x86_64-pc-windows-msvc":"@openai/codex-win32-x64","aarch64-pc-windows-msvc":"@openai/codex-win32-arm64"},v=f(import.meta.url);function y(){const{platform:s,arch:e}=process;let t=null;switch(s){case"linux":case"android":e==="x64"&&(t="x86_64-unknown-linux-musl"),e==="arm64"&&(t="aarch64-unknown-linux-musl");break;case"darwin":e==="x64"&&(t="x86_64-apple-darwin"),e==="arm64"&&(t="aarch64-apple-darwin");break;case"win32":e==="x64"&&(t="x86_64-pc-windows-msvc"),e==="arm64"&&(t="aarch64-pc-windows-msvc");break;default:break}if(!t)throw new Error(`Unsupported platform: ${s} (${e})`);const r=R[t];if(!r)throw new Error(`Unsupported target triple: ${t}`);let i;try{const a=v.resolve(`${h}/package.json`),x=f(a).resolve(`${r}/package.json`);i=o.join(o.dirname(x),"vendor")}catch{throw new Error(`Unable to locate Codex CLI binaries. Ensure ${h} is installed with optional dependencies.`)}const n=process.platform==="win32"?"codex.exe":"codex",d=U(i,t,n);if(!d)throw new Error(`Unable to locate Codex CLI binaries for ${t}. Ensure ${h} is installed with optional dependencies.`);return d.executablePath}function U(s,e,t){const r=o.join(s,e),i=o.join(r,"bin",t);if(p(i)&&p(o.join(r,"codex-package.json")))return{executablePath:i,pathDirs:w(o.join(r,"codex-path"))};const n=o.join(r,"codex",t);return p(n)?{executablePath:n,pathDirs:w(o.join(r,"path"))}:null}function w(...s){return s.filter(E)}function p(s){try{return l(s).isFile()}catch{return!1}}function E(s){try{return l(s).isDirectory()}catch{return!1}}function P(s){const e=s.trim();if(!e)return{command:"codex",args:["app-server","--listen","stdio://"]};const t=e.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)??[],[r,...i]=t.map(n=>n.startsWith('"')&&n.endsWith('"')||n.startsWith("'")&&n.endsWith("'")?n.slice(1,-1):n);return{command:r??"codex",args:i}}class S extends g{toolHandler;child;nextId=1;pending=new Map;pendingUserInputRequests=new Map;notifiedUserInputRequests=new Set;answeredUserInputRequests=new Set;started;buffer="";constructor(e=q){super(),this.toolHandler=e}async request(e,t){return await this.ensureStarted(),this.writeRequest(e,t)}async writeRequest(e,t){if(!this.child)throw new Error("Codex app-server is not running");const r=this.nextId++,i=JSON.stringify({jsonrpc:"2.0",id:r,method:e,params:t});return new Promise((n,d)=>{this.pending.set(r,{resolve:a=>n(a),reject:d}),this.child?.stdin.write(`${i}
2
+ `,a=>{a&&(this.pending.delete(r),d(a))})})}async notify(e,t){if(await this.ensureStarted(),!this.child)throw new Error("Codex app-server is not running");this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",method:e,params:t})}
3
+ `)}async respondToUserInput(e,t,r){if(await this.ensureStarted(),!this.child)throw new Error("Codex app-server is not running");const i=u(e,t);if(this.answeredUserInputRequests.has(i))throw new Error(`User input request already answered for item: ${t}`);const n=this.pendingUserInputRequests.get(i);return n?(this.pendingUserInputRequests.delete(i),this.answeredUserInputRequests.add(i),this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",id:n.requestId,result:r})}
4
+ `),!0):!1}pendingUserInputTurnId(e,t){return this.pendingUserInputRequests.get(u(e,t))?.turnId}discardPendingUserInput(e,t){const r=u(e,t);this.pendingUserInputRequests.delete(r),this.notifiedUserInputRequests.delete(r),this.answeredUserInputRequests.delete(r)}async ensureStarted(){return this.started||(this.started=this.start()),this.started}async start(){const e=process.env.CODEX_APP_SERVER_COMMAND,{command:t,args:r}=e?P(e):{command:y(),args:["app-server","--listen","stdio://"]},i={...process.env};this.child=m(t,r,{env:i,stdio:["pipe","pipe","pipe"]}),this.child.stdout.setEncoding("utf-8"),this.child.stderr.setEncoding("utf-8"),this.child.stdout.on("data",n=>this.handleStdout(n)),this.child.stderr.on("data",n=>c.debug(`codex app-server stderr: ${n.trimEnd()}`)),this.child.on("exit",(n,d)=>this.handleExit(n,d)),this.child.on("error",n=>this.handleProcessError(n)),await this.writeRequest("initialize",{clientInfo:{name:"codex-agent",title:"codex-agent",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}})}handleStdout(e){for(this.buffer+=e;;){const t=this.buffer.indexOf(`
5
+ `);if(t===-1)break;const r=this.buffer.slice(0,t).trim();if(this.buffer=this.buffer.slice(t+1),!!r)try{this.handleMessage(JSON.parse(r))}catch(i){c.error("Failed to parse codex app-server JSON-RPC message:",i)}}}handleMessage(e){if((!e.method||!(e.method.endsWith("/delta")||e.method.endsWith("/outputDelta")||e.method==="turn/diff/updated"))&&c.debug("Received message:",e),e.id!==void 0&&e.method){this.handleServerRequest(e).catch(t=>c.error("Failed to handle app-server request:",t));return}if(e.id!==void 0){const t=this.pending.get(e.id);if(!t)return;this.pending.delete(e.id),e.error?t.reject(new Error(e.error.message??`Codex app-server request failed: ${e.error.code}`)):t.resolve(e.result);return}e.method&&(I(e.method,e.params),this.emit("notification",{method:e.method,params:e.params}))}async handleServerRequest(e){if(!(!this.child||e.id===void 0||!e.method))try{if(e.method==="item/tool/call"){const t=await this.toolHandler(e.params);this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",id:e.id,result:t})}
6
+ `);return}if(e.method==="item/tool/requestUserInput"){const t=e.params,r=u(t.threadId,t.itemId);if(this.pendingUserInputRequests.set(r,{threadId:t.threadId,turnId:t.turnId,itemId:t.itemId,requestId:e.id}),this.notifiedUserInputRequests.has(r))return;this.notifiedUserInputRequests.add(r),this.emit("notification",{method:e.method,params:t});return}this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",id:e.id,error:{code:-32601,message:`Unsupported server request: ${e.method}`}})}
7
+ `)}catch(t){const r=t instanceof Error?t.message:String(t);this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",id:e.id,error:{code:-32e3,message:r}})}
8
+ `)}}handleExit(e,t){const r=new Error(`Codex app-server exited${t?` with signal ${t}`:` with code ${e}`}`);for(const i of this.pending.values())i.reject(r);this.pending.clear(),this.pendingUserInputRequests.clear(),this.notifiedUserInputRequests.clear(),this.answeredUserInputRequests.clear(),this.child=void 0,this.started=void 0,this.emit("exit",r)}handleProcessError(e){for(const t of this.pending.values())t.reject(e);this.pending.clear(),this.pendingUserInputRequests.clear(),this.notifiedUserInputRequests.clear(),this.answeredUserInputRequests.clear(),this.emit("exit",e)}}function u(s,e){return`${s}:${e}`}const $=new S;$.setMaxListeners(20);export{S as AppServerClient,$ as appServerClient,y as findCodexPath};
@@ -1 +0,0 @@
1
- export {};
package/dist/app.js CHANGED
@@ -1,15 +1 @@
1
- import { Hono } from "hono";
2
- import { logger as honoLogger } from "hono/logger";
3
- import chat from "./routes/chat.js";
4
- import { logger } from "@messenger-agent/shared/logger";
5
- import { agentAuthMiddleware } from "@messenger-agent/shared/agent-auth";
6
- import { appConfig } from "./config.js";
7
- import { installAgentActivityResponder } from "@messenger-agent/shared/agent-activity";
8
- import { codexActivitySnapshot } from "./routes/chat.js";
9
- const app = new Hono();
10
- installAgentActivityResponder("codex", codexActivitySnapshot);
11
- app.use(honoLogger((str, ...rest) => logger.info(str, ...rest)));
12
- app.get("/health", (c) => c.json({ status: "ok" }));
13
- app.use("/*", agentAuthMiddleware(appConfig.authTokens));
14
- app.route("/", chat);
15
- export default app;
1
+ import{Hono as e}from"hono";import{logger as i}from"hono/logger";import m from"./routes/chat.js";import{logger as p}from"@messenger-agent/shared/logger";import{agentAuthMiddleware as n}from"@messenger-agent/shared/agent-auth";import{appConfig as a}from"./config.js";import{installAgentActivityResponder as f}from"@messenger-agent/shared/agent-activity";import{codexActivitySnapshot as s}from"./routes/chat.js";const o=new e;f("codex",s),o.use(i((t,...r)=>p.info(t,...r))),o.get("/health",t=>t.json({status:"ok"})),o.use("/*",n(a.authTokens)),o.route("/",m);var k=o;export{k as default};