From d2b90ed4482224f6569d938b770a3e5ed8656932 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Fri, 27 Mar 2026 14:17:06 +0100 Subject: [PATCH 1/4] LLM-26222 [Codex] Show input params, result of running mcp tools --- src/CodexAcpServer.ts | 3 + src/CodexEventHandler.ts | 47 +++++++++- src/CodexToolCallMapper.ts | 91 ++++++++++++++++++- .../command-action-events.test.ts | 58 ++++++++++++ .../data/load-session-history.json | 9 +- .../data/mcp-tool-completed-with-logs.json | 76 ++++++++++++++++ .../data/mcp-tool-in-progress.json | 11 ++- src/__tests__/acp-test-utils.ts | 1 + 8 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 3b695fa1..b1677f3d 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -41,6 +41,7 @@ export interface SessionState { account: Account | null; cwd: string; sessionMcpServers?: Array; + mcpToolLogs: Map>; } export class CodexAcpServer implements acp.Agent { @@ -156,6 +157,7 @@ export class CodexAcpServer implements acp.Agent { account: accountResponse.account, cwd: request.cwd, sessionMcpServers: sessionMcpServers, + mcpToolLogs: new Map(), } this.sessions.set(sessionId, sessionState); @@ -356,6 +358,7 @@ export class CodexAcpServer implements acp.Agent { account: accountResponse.account, cwd: request.cwd, sessionMcpServers: sessionMcpServers, + mcpToolLogs: new Map(), }; this.sessions.set(sessionId, sessionState); diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index 00e27310..a9c8a297 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -25,6 +25,8 @@ import { createCommandExecutionUpdate, createDynamicToolCallUpdate, createFileChangeUpdate, + createMcpRawInput, + createMcpRawOutput, createFuzzyFileSearchComplete, createFuzzyFileSearchStartOrUpdate, createMcpToolCallUpdate, @@ -101,12 +103,13 @@ export class CodexEventHandler { case "turn/diff/updated": case "item/commandExecution/terminalInteraction": case "item/fileChange/outputDelta": - case "item/mcpToolCall/progress": case "serverRequest/resolved": case "account/updated": case "fs/changed": case "mcpServer/startupStatus/updated": return null; + case "item/mcpToolCall/progress": + return this.createMcpToolProgressEvent(notification.params); case "account/rateLimits/updated": this.handleRateLimitsUpdated(notification.params); return null; @@ -213,10 +216,19 @@ export class CodexEventHandler { case "mcpToolCall": case "fileChange": case "dynamicToolCall": + const mcpLogs = event.item.type === "mcpToolCall" + ? this.consumeMcpToolLogs(event.item.id) + : []; return { sessionUpdate: "tool_call_update", toolCallId: event.item.id, - status: event.item.status === "completed" ? "completed" : "failed" + status: event.item.status === "completed" ? "completed" : "failed", + rawInput: event.item.type === "mcpToolCall" + ? createMcpRawInput(event.item.server, event.item.tool, event.item.arguments) + : undefined, + rawOutput: event.item.type === "mcpToolCall" + ? createMcpRawOutput(mcpLogs, event.item.result, event.item.error) + : undefined, } case "commandExecution": return this.completeCommandExecutionEvent(event.item); @@ -258,6 +270,37 @@ export class CodexEventHandler { } } + private createMcpToolProgressEvent(event: { itemId: string, message: string }): UpdateSessionEvent { + const logs = this.appendMcpToolLog(event.itemId, event.message); + return { + sessionUpdate: "tool_call_update", + toolCallId: event.itemId, + rawOutput: { + formatted_output: logs.join("\n\n"), + } + }; + } + + private appendMcpToolLog(toolCallId: string, message: string): Array { + const cleaned = message.trim(); + if (cleaned.length === 0) { + return this.sessionState.mcpToolLogs.get(toolCallId) ?? []; + } + + const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; + if (logs.at(-1) !== cleaned && !logs.includes(cleaned)) { + logs.push(cleaned); + } + this.sessionState.mcpToolLogs.set(toolCallId, logs); + return logs; + } + + private consumeMcpToolLogs(toolCallId: string): Array { + const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; + this.sessionState.mcpToolLogs.delete(toolCallId); + return logs; + } + private completeCommandExecutionEvent(item: ThreadItem & { "type": "commandExecution" }): UpdateSessionEvent { return { sessionUpdate: "tool_call_update", diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index 4f4cb26e..c7c59021 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -13,6 +13,8 @@ import type { CommandExecutionStatus, DynamicToolCallStatus, FileUpdateChange, + McpToolCallError, + McpToolCallResult, McpToolCallStatus, PatchApplyStatus, ThreadItem, @@ -84,7 +86,12 @@ export async function createCommandExecutionUpdate( export async function createMcpToolCallUpdate( item: ThreadItem & { type: "mcpToolCall" } ): Promise { - return createExecuteToolCallUpdate(item, `mcp.${item.server}.${item.tool}`); + return createExecuteToolCallUpdate( + item, + `mcp.${item.server}.${item.tool}`, + createMcpRawInput(item.server, item.tool, item.arguments), + createMcpRawOutput([], item.result, item.error), + ); } export async function createDynamicToolCallUpdate( @@ -96,7 +103,8 @@ export async function createDynamicToolCallUpdate( export async function createExecuteToolCallUpdate( item: ThreadItem & ({ type: "mcpToolCall" } | { type: "dynamicToolCall" }), title: string, - rawInput?: { arguments: JsonValue } + rawInput?: Record, + rawOutput?: Record, ): Promise { return { sessionUpdate: "tool_call", @@ -105,9 +113,88 @@ export async function createExecuteToolCallUpdate( title: title, status: toAcpStatus(item.status), rawInput: rawInput, + rawOutput: rawOutput, }; } +export function createMcpRawInput(server: string, tool: string, argumentsValue: JsonValue): Record { + const invocation = `Called ${server}.${tool} (${formatJsonInline(argumentsValue)})`; + return { + server, + tool, + arguments: argumentsValue, + invocation, + prettyArguments: formatJsonPretty(argumentsValue), + }; +} + +export function createMcpRawOutput( + logs: Array, + result: McpToolCallResult | null, + error: McpToolCallError | null, +): Record | undefined { + const logLines = normalizeMcpLogLines(logs, error, result); + if (logLines.length === 0) { + return undefined; + } + + return { + formatted_output: logLines.join("\n\n"), + result, + error, + }; +} + +function formatMcpResult(result: McpToolCallResult | null): string | null { + if (!result) { + return null; + } + + const parts: string[] = []; + if (result.content.length > 0) { + parts.push(result.content.map((contentItem) => formatJsonPretty(contentItem)).join("\n")); + } + if (result.structuredContent !== null) { + parts.push(formatJsonPretty(result.structuredContent)); + } + + return parts.length > 0 ? parts.join("\n\n") : null; +} + +function normalizeMcpLogLines( + logs: Array, + error: McpToolCallError | null, + result: McpToolCallResult | null, +): Array { + const lines: string[] = []; + const seen = new Set(); + + const append = (value: string | null | undefined) => { + const cleaned = value?.trim(); + if (!cleaned || seen.has(cleaned)) { + return; + } + seen.add(cleaned); + lines.push(cleaned); + }; + + for (const log of logs) { + append(log); + } + append(error?.message); + append(formatMcpResult(result)); + + return lines; +} + +function formatJsonInline(value: JsonValue): string { + return JSON.stringify(value); +} + +function formatJsonPretty(value: JsonValue): string { + return JSON.stringify(value, null, 2); +} + export function fuzzyFileSearchToolCallId(sessionId: string): string { return `fuzzyFileSearch.${sessionId}`; } diff --git a/src/__tests__/CodexACPAgent/command-action-events.test.ts b/src/__tests__/CodexACPAgent/command-action-events.test.ts index c697d980..7d22193d 100644 --- a/src/__tests__/CodexACPAgent/command-action-events.test.ts +++ b/src/__tests__/CodexACPAgent/command-action-events.test.ts @@ -260,6 +260,64 @@ describe('CodexEventHandler - command action events', () => { ); }); + it('should include mcp progress and final logs', async () => { + const notifications: ServerNotification[] = [ + { + method: 'item/started', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "ijproxy", + tool: "read_file", + status: "inProgress", + arguments: { file_path: ".ai/local.md", mode: "slice", start_line: 1, max_lines: 200 }, + result: null, + error: null, + durationMs: null, + }, + }, + }, + { + method: 'item/mcpToolCall/progress', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + itemId: 'call-id', + message: "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened", + }, + }, + { + method: 'item/completed', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "ijproxy", + tool: "read_file", + status: "failed", + arguments: { file_path: ".ai/local.md", mode: "slice", start_line: 1, max_lines: 200 }, + result: null, + error: { + message: "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened", + }, + durationMs: 15, + }, + }, + }, + ]; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + 'data/mcp-tool-completed-with-logs.json' + ); + }); + it('should handle dynamic tools', async () => { const dynamicToolNotification: ServerNotification = { method: 'item/started', diff --git a/src/__tests__/CodexACPAgent/data/load-session-history.json b/src/__tests__/CodexACPAgent/data/load-session-history.json index ba06a341..72357f0f 100644 --- a/src/__tests__/CodexACPAgent/data/load-session-history.json +++ b/src/__tests__/CodexACPAgent/data/load-session-history.json @@ -158,7 +158,14 @@ "toolCallId": "item-mcp-1", "kind": "execute", "title": "mcp.github.search", - "status": "completed" + "status": "completed", + "rawInput": { + "server": "github", + "tool": "search", + "arguments": {}, + "invocation": "Called github.search ({})", + "prettyArguments": "{}" + } } } ] diff --git a/src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json b/src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json new file mode 100644 index 00000000..7b038562 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json @@ -0,0 +1,76 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "_meta": { + "mcp_output_delta": { + "data": "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "status": "failed", + "rawInput": { + "server": "ijproxy", + "tool": "read_file", + "arguments": { + "file_path": ".ai/local.md", + "mode": "slice", + "start_line": 1, + "max_lines": 200 + }, + "invocation": "Called ijproxy.read_file ({\"file_path\":\".ai/local.md\",\"mode\":\"slice\",\"start_line\":1,\"max_lines\":200})", + "prettyArguments": "{\n \"file_path\": \".ai/local.md\",\n \"mode\": \"slice\",\n \"start_line\": 1,\n \"max_lines\": 200\n}" + }, + "rawOutput": { + "formatted_output": "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened", + "result": null, + "error": { + "message": "File /Users/aleksandr.slapoguzov/Projects/ultimate/.ai/local.md doesn't exist or can't be opened" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "call-id", + "kind": "execute", + "title": "mcp.ijproxy.read_file", + "status": "in_progress", + "rawInput": { + "server": "ijproxy", + "tool": "read_file", + "arguments": { + "file_path": ".ai/local.md", + "mode": "slice", + "start_line": 1, + "max_lines": 200 + }, + "invocation": "Called ijproxy.read_file ({\"file_path\":\".ai/local.md\",\"mode\":\"slice\",\"start_line\":1,\"max_lines\":200})", + "prettyArguments": "{\n \"file_path\": \".ai/local.md\",\n \"mode\": \"slice\",\n \"start_line\": 1,\n \"max_lines\": 200\n}" + } + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json b/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json index ea95bdcb..487992f3 100644 --- a/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json +++ b/src/__tests__/CodexACPAgent/data/mcp-tool-in-progress.json @@ -8,7 +8,16 @@ "toolCallId": "call-id", "kind": "execute", "title": "mcp.server-name.tool-name", - "status": "in_progress" + "status": "in_progress", + "rawInput": { + "server": "server-name", + "tool": "tool-name", + "arguments": { + "argument": "example" + }, + "invocation": "Called server-name.tool-name ({\"argument\":\"example\"})", + "prettyArguments": "{\n \"argument\": \"example\"\n}" + } } } ] diff --git a/src/__tests__/acp-test-utils.ts b/src/__tests__/acp-test-utils.ts index a25138b0..b6ac4c8d 100644 --- a/src/__tests__/acp-test-utils.ts +++ b/src/__tests__/acp-test-utils.ts @@ -279,6 +279,7 @@ export function createTestSessionState(overrides?: Partial): Sessi supportedReasoningEfforts: [], supportedInputModalities: ["text", "image"], agentMode: AgentMode.DEFAULT_AGENT_MODE, + mcpToolLogs: new Map(), ...overrides, }; } From 069ecd34aa74e636f32cd8bef4778b5c3ab7baa6 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Tue, 7 Apr 2026 11:35:47 +0200 Subject: [PATCH 2/4] review fix --- src/CodexEventHandler.ts | 18 ++--- src/CodexToolCallMapper.ts | 17 ++--- .../command-action-events.test.ts | 68 +++++++++++++++++++ 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index a9c8a297..354ba290 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -271,28 +271,28 @@ export class CodexEventHandler { } private createMcpToolProgressEvent(event: { itemId: string, message: string }): UpdateSessionEvent { - const logs = this.appendMcpToolLog(event.itemId, event.message); + const logDelta = this.appendMcpToolLog(event.itemId, event.message); return { sessionUpdate: "tool_call_update", toolCallId: event.itemId, - rawOutput: { - formatted_output: logs.join("\n\n"), + _meta: { + mcp_output_delta: { + data: logDelta, + } } }; } - private appendMcpToolLog(toolCallId: string, message: string): Array { + private appendMcpToolLog(toolCallId: string, message: string): string { const cleaned = message.trim(); if (cleaned.length === 0) { - return this.sessionState.mcpToolLogs.get(toolCallId) ?? []; + return ""; } const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? []; - if (logs.at(-1) !== cleaned && !logs.includes(cleaned)) { - logs.push(cleaned); - } + logs.push(cleaned); this.sessionState.mcpToolLogs.set(toolCallId, logs); - return logs; + return cleaned; } private consumeMcpToolLogs(toolCallId: string): Array { diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index c7c59021..fe8c1b0e 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -166,23 +166,20 @@ function normalizeMcpLogLines( error: McpToolCallError | null, result: McpToolCallResult | null, ): Array { - const lines: string[] = []; - const seen = new Set(); + const lines = logs + .map((log) => log.trim()) + .filter((log) => log.length > 0); - const append = (value: string | null | undefined) => { + const appendTrailingUnique = (value: string | null | undefined) => { const cleaned = value?.trim(); - if (!cleaned || seen.has(cleaned)) { + if (!cleaned || lines.at(-1) === cleaned) { return; } - seen.add(cleaned); lines.push(cleaned); }; - for (const log of logs) { - append(log); - } - append(error?.message); - append(formatMcpResult(result)); + appendTrailingUnique(error?.message); + appendTrailingUnique(formatMcpResult(result)); return lines; } diff --git a/src/__tests__/CodexACPAgent/command-action-events.test.ts b/src/__tests__/CodexACPAgent/command-action-events.test.ts index 7d22193d..ebb3d8c7 100644 --- a/src/__tests__/CodexACPAgent/command-action-events.test.ts +++ b/src/__tests__/CodexACPAgent/command-action-events.test.ts @@ -318,6 +318,74 @@ describe('CodexEventHandler - command action events', () => { ); }); + it('should preserve repeated mcp progress messages in final output', async () => { + const repeatedMessage = 'Polling for status'; + const notifications: ServerNotification[] = [ + { + method: 'item/started', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "server-name", + tool: "tool-name", + status: "inProgress", + arguments: { argument: "example" }, + result: null, + error: null, + durationMs: null, + }, + }, + }, + { + method: 'item/mcpToolCall/progress', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + itemId: 'call-id', + message: repeatedMessage, + }, + }, + { + method: 'item/mcpToolCall/progress', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + itemId: 'call-id', + message: repeatedMessage, + }, + }, + { + method: 'item/completed', + params: { + threadId: 'thread-1', + turnId: 'turn-1', + item: { + type: "mcpToolCall", + id: "call-id", + server: "server-name", + tool: "tool-name", + status: "failed", + arguments: { argument: "example" }, + result: null, + error: { + message: repeatedMessage, + }, + durationMs: 15, + }, + }, + }, + ]; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + 'data/mcp-tool-repeated-progress.json' + ); + }); + it('should handle dynamic tools', async () => { const dynamicToolNotification: ServerNotification = { method: 'item/started', From 1b58cc0570c93679af015e792a06c50ce393b4f5 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Tue, 7 Apr 2026 14:35:28 +0200 Subject: [PATCH 3/4] fix missing test data --- .../data/mcp-tool-repeated-progress.json | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json diff --git a/src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json b/src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json new file mode 100644 index 00000000..733306b1 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json @@ -0,0 +1,93 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "rawOutput": { + "formatted_output": "Polling for status" + }, + "_meta": { + "mcp_output_delta": { + "data": "Polling for status" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "rawOutput": { + "formatted_output": "Polling for status\n\nPolling for status" + }, + "_meta": { + "mcp_output_delta": { + "data": "Polling for status" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "call-id", + "status": "failed", + "rawInput": { + "server": "server-name", + "tool": "tool-name", + "arguments": { + "argument": "example" + }, + "invocation": "Called server-name.tool-name ({\"argument\":\"example\"})", + "prettyArguments": "{\n \"argument\": \"example\"\n}" + }, + "rawOutput": { + "formatted_output": "Polling for status\n\nPolling for status", + "result": null, + "error": { + "message": "Polling for status" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "call-id", + "kind": "execute", + "title": "mcp.server-name.tool-name", + "status": "in_progress", + "rawInput": { + "server": "server-name", + "tool": "tool-name", + "arguments": { + "argument": "example" + }, + "invocation": "Called server-name.tool-name ({\"argument\":\"example\"})", + "prettyArguments": "{\n \"argument\": \"example\"\n}" + } + } + } + ] +} \ No newline at end of file From f89fac8f70a378f8c3ca70d0306d4c54a957e704 Mon Sep 17 00:00:00 2001 From: "Nikolai.Sviridov" Date: Wed, 8 Apr 2026 15:54:09 +0200 Subject: [PATCH 4/4] LLM-26758 fix Failed to initialize ACP process, add script for building binary --- scripts/build-macos-local.sh | 92 ++++++++++++++++++++++++++++++++++++ src/index.ts | 19 +++++++- 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100755 scripts/build-macos-local.sh diff --git a/scripts/build-macos-local.sh b/scripts/build-macos-local.sh new file mode 100755 index 00000000..336d837c --- /dev/null +++ b/scripts/build-macos-local.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERSION_TAG="$VERSION_TAG" +DIST_DIR="dist/bin" +IDEA_CODEX_BIN_DIR="${IDEA_CODEX_BIN_DIR:-}" +PACKAGE_JSON="package.json" +PACKAGE_JSON_BAK="$(mktemp)" + +cp "$PACKAGE_JSON" "$PACKAGE_JSON_BAK" + +cleanup() { + cp "$PACKAGE_JSON_BAK" "$PACKAGE_JSON" + rm -f "$PACKAGE_JSON_BAK" +} +trap cleanup EXIT + +mkdir -p "$DIST_DIR" + +# Keep CI-compatible artifact names in dist/bin. +rm -f \ + "$DIST_DIR/codex-acp-x64-darwin" \ + "$DIST_DIR/codex-acp-arm64-darwin" \ + "$DIST_DIR/codex-acp-x64-darwin.zip" \ + "$DIST_DIR/codex-acp-arm64-darwin.zip" + +echo "Temporarily setting package version to: ${VERSION_TAG}" +perl -i -pe 's/"version":\s*"[^"]+"/"version": "'"${VERSION_TAG}"'"/' "$PACKAGE_JSON" + +echo "Building macOS binaries..." +bun build src/index.ts --minify --sourcemap --compile --target=bun-darwin-x64-baseline --outfile dist/bin/codex-acp-x64-darwin +bun build src/index.ts --minify --sourcemap --compile --target=bun-darwin-arm64 --outfile dist/bin/codex-acp-arm64-darwin + +echo "Packaging artifacts in GitHub Actions format..." + +( + cd "$DIST_DIR" + zip -q codex-acp-x64-darwin.zip codex-acp-x64-darwin + zip -q codex-acp-arm64-darwin.zip codex-acp-arm64-darwin +) + +X64_VERSION="$("$DIST_DIR/codex-acp-x64-darwin" --version | tail -n 1)" +ARM64_VERSION="$("$DIST_DIR/codex-acp-arm64-darwin" --version | tail -n 1)" + +if [[ "$X64_VERSION" != *" ${VERSION_TAG}" ]]; then + echo "Version check failed for x64: $X64_VERSION" + exit 1 +fi + +if [[ "$ARM64_VERSION" != *" ${VERSION_TAG}" ]]; then + echo "Version check failed for arm64: $ARM64_VERSION" + exit 1 +fi + +echo "Done. Artifacts:" +ls -lh \ + "$DIST_DIR/codex-acp-x64-darwin" \ + "$DIST_DIR/codex-acp-arm64-darwin" \ + "$DIST_DIR/codex-acp-x64-darwin.zip" \ + "$DIST_DIR/codex-acp-arm64-darwin.zip" + +if [[ -n "$IDEA_CODEX_BIN_DIR" ]]; then + echo "Copying local macOS binaries to IntelliJ dev Codex bin dir..." + COPIED_ARTIFACTS=() + + for artifact in \ + "codex-acp-x64-darwin" \ + "codex-acp-arm64-darwin" \ + "codex-acp-x64-darwin.zip" \ + "codex-acp-arm64-darwin.zip"; do + source_path="$DIST_DIR/$artifact" + target_path="$IDEA_CODEX_BIN_DIR/$artifact" + + if [[ -e "$target_path" ]]; then + cp -f "$source_path" "$target_path" + COPIED_ARTIFACTS+=("$target_path") + else + echo "Skipping missing target: $target_path" + fi + done + + echo "Copied artifacts:" + if [[ ${#COPIED_ARTIFACTS[@]} -gt 0 ]]; then + ls -lh "${COPIED_ARTIFACTS[@]}" + else + echo "No existing target artifacts were updated." + fi +else + echo "IDEA_CODEX_BIN_DIR is not set; skipping IntelliJ artifact copy." +fi + +echo "Embedded version: ${VERSION_TAG}" diff --git a/src/index.ts b/src/index.ts index d925c155..6c7ab482 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,8 +30,7 @@ if (process.argv[2] === "login") { } function startAcpServer() { - const defaultCodexPath = createRequire(import.meta.url).resolve("@openai/codex/bin/codex.js"); - const codexPath = process.env["CODEX_PATH"] ?? defaultCodexPath; + const codexPath = resolveCodexPath(); const configString = process.env["CODEX_CONFIG"]; const authRequestString = process.env["DEFAULT_AUTH_REQUEST"]; const modelProvider = process.env["MODEL_PROVIDER"]; @@ -71,3 +70,19 @@ function startAcpServer() { new acp.AgentSideConnection(createAgent, acpJsonStream); } + +function resolveCodexPath(): string { + const configuredCodexPath = process.env["CODEX_PATH"]; + if (configuredCodexPath) { + return configuredCodexPath; + } + + try { + return createRequire(import.meta.url).resolve("@openai/codex/bin/codex.js"); + } catch (error) { + logger.log("Falling back to codex from PATH because @openai/codex/bin/codex.js could not be resolved", { + error: error instanceof Error ? error.message : String(error), + }); + return "codex"; + } +}