Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions scripts/build-macos-local.sh
Original file line number Diff line number Diff line change
@@ -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}"
3 changes: 3 additions & 0 deletions src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface SessionState {
account: Account | null;
cwd: string;
sessionMcpServers?: Array<string>;
mcpToolLogs: Map<string, Array<string>>;
}

export class CodexAcpServer implements acp.Agent {
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
47 changes: 45 additions & 2 deletions src/CodexEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
createCommandExecutionUpdate,
createDynamicToolCallUpdate,
createFileChangeUpdate,
createMcpRawInput,
createMcpRawOutput,
createFuzzyFileSearchComplete,
createFuzzyFileSearchStartOrUpdate,
createMcpToolCallUpdate,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -258,6 +270,37 @@ export class CodexEventHandler {
}
}

private createMcpToolProgressEvent(event: { itemId: string, message: string }): UpdateSessionEvent {
const logDelta = this.appendMcpToolLog(event.itemId, event.message);
return {
sessionUpdate: "tool_call_update",
toolCallId: event.itemId,
_meta: {
mcp_output_delta: {
data: logDelta,
}
}
};
}

private appendMcpToolLog(toolCallId: string, message: string): string {
const cleaned = message.trim();
if (cleaned.length === 0) {
return "";
}

const logs = this.sessionState.mcpToolLogs.get(toolCallId) ?? [];
logs.push(cleaned);
this.sessionState.mcpToolLogs.set(toolCallId, logs);
return cleaned;
}

private consumeMcpToolLogs(toolCallId: string): Array<string> {
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",
Expand Down
88 changes: 86 additions & 2 deletions src/CodexToolCallMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
CommandExecutionStatus,
DynamicToolCallStatus,
FileUpdateChange,
McpToolCallError,
McpToolCallResult,
McpToolCallStatus,
PatchApplyStatus,
ThreadItem,
Expand Down Expand Up @@ -84,7 +86,12 @@ export async function createCommandExecutionUpdate(
export async function createMcpToolCallUpdate(
item: ThreadItem & { type: "mcpToolCall" }
): Promise<UpdateSessionEvent> {
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(
Expand All @@ -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<string, JsonValue | string>,
rawOutput?: Record<string, JsonValue | string | null>,
): Promise<UpdateSessionEvent> {
return {
sessionUpdate: "tool_call",
Expand All @@ -105,9 +113,85 @@ export async function createExecuteToolCallUpdate(
title: title,
status: toAcpStatus(item.status),
rawInput: rawInput,
rawOutput: rawOutput,
};
}

export function createMcpRawInput(server: string, tool: string, argumentsValue: JsonValue): Record<string, JsonValue | string> {
const invocation = `Called ${server}.${tool} (${formatJsonInline(argumentsValue)})`;
return {
server,
tool,
arguments: argumentsValue,
invocation,
prettyArguments: formatJsonPretty(argumentsValue),
};
}

export function createMcpRawOutput(
logs: Array<string>,
result: McpToolCallResult | null,
error: McpToolCallError | null,
): Record<string, JsonValue | string | null> | 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<string>,
error: McpToolCallError | null,
result: McpToolCallResult | null,
): Array<string> {
const lines = logs
.map((log) => log.trim())
.filter((log) => log.length > 0);

const appendTrailingUnique = (value: string | null | undefined) => {
const cleaned = value?.trim();
if (!cleaned || lines.at(-1) === cleaned) {
return;
}
lines.push(cleaned);
};

appendTrailingUnique(error?.message);
appendTrailingUnique(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}`;
}
Expand Down
Loading
Loading