Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
93 changes: 93 additions & 0 deletions scripts/build-macos-local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -euo pipefail
Comment thread
NikolaiSviridov marked this conversation as resolved.
Outdated

VERSION_TAG="$1"
DIST_DIR="dist/bin"
IDEA_CODEX_BIN_DIR="$2"
PACKAGE_JSON="package.json"
PACKAGE_JSON_BAK="$(mktemp)"

if [[ -z "${VERSION_TAG}" || -z "${IDEA_CODEX_BIN_DIR}" ]]; then
echo "Usage: $0 <version-tag> <idea-codex-bin-dir>"
exit 1
fi

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"

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

echo "Embedded version: ${VERSION_TAG}"
18 changes: 18 additions & 0 deletions src/CodexAcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import open from "open";
import type {Disposable} from "vscode-jsonrpc";
import type {
ClientInfo,
McpStartupCompleteEvent,
ReasoningEffort,
ServerNotification
} from "./app-server";
Expand All @@ -21,6 +22,7 @@ import type {
GetAccountResponse,
ListMcpServerStatusParams,
ListMcpServerStatusResponse,
McpServerStatusUpdatedNotification,
Model,
SkillsListParams,
SkillsListResponse,
Expand Down Expand Up @@ -275,6 +277,22 @@ export class CodexAcpClient {
return startup.ready;
}

async awaitMcpStartupResult(mcpStartupVersion: number): Promise<McpStartupCompleteEvent> {
return await this.codexClient.awaitMcpStartup(mcpStartupVersion);
}

onMcpServerStatusUpdated(handler: (event: McpServerStatusUpdatedNotification) => void): void {
Comment thread
NikolaiSviridov marked this conversation as resolved.
Outdated
this.codexClient.onMcpServerStatusUpdated(handler);
}

getMcpServerStatusVersion(): number {
return this.codexClient.getMcpServerStatusVersion();
}

getMcpServerStatusUpdates(afterVersion: number): Array<McpServerStatusUpdatedNotification> {
return this.codexClient.getMcpServerStatusUpdates(afterVersion);
}

getMcpStartupCompleteVersion(): number {
return this.codexClient.getMcpStartupCompleteVersion();
}
Expand Down
77 changes: 75 additions & 2 deletions src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ import {CodexApprovalHandler} from "./CodexApprovalHandler";
import {CodexAuthMethods, type CodexAuthRequest} from "./CodexAuthMethod";
import {CodexAcpClient, type SessionMetadata, type SessionMetadataWithThread} from "./CodexAcpClient";
import {ACPSessionConnection, type UpdateSessionEvent} from "./ACPSessionConnection";
import type {Account, CollabAgentToolCallStatus, Model, Thread, ThreadItem, UserInput, ReasoningEffortOption} from "./app-server/v2";
import type {McpStartupCompleteEvent, InputModality, ReasoningEffort} from "./app-server";
import type {
Account,
CollabAgentToolCallStatus,
Model,
Thread,
ThreadItem,
UserInput,
ReasoningEffortOption
} from "./app-server/v2";
import type {RateLimitsMap} from "./RateLimitsMap";
import type {InputModality, ReasoningEffort} from "./app-server";
import {ModelId} from "./ModelId";
import {AgentMode} from "./AgentMode";
import type {TokenCount} from "./TokenCount";
Expand Down Expand Up @@ -41,6 +49,11 @@ export interface SessionState {
account: Account | null;
cwd: string;
sessionMcpServers?: Array<string>;
mcpToolLogs: Map<string, Array<string>>;
Comment thread
NikolaiSviridov marked this conversation as resolved.
Outdated
}

interface PendingMcpStartupSession {
requestedServers: Set<string>;
}

export class CodexAcpServer implements acp.Agent {
Expand All @@ -51,6 +64,7 @@ export class CodexAcpServer implements acp.Agent {
private readonly availableCommands: CodexCommands;

private readonly sessions: Map<string, SessionState>;
private readonly pendingMcpStartupSessions: Map<string, PendingMcpStartupSession>;

constructor(
connection: acp.AgentSideConnection,
Expand All @@ -59,6 +73,7 @@ export class CodexAcpServer implements acp.Agent {
getExitCode?: () => number | null,
) {
this.sessions = new Map();
this.pendingMcpStartupSessions = new Map();
this.connection = connection;
this.codexAcpClient = codexAcpClient;
this.defaultAuthRequest = defaultAuthRequest ?? null;
Expand Down Expand Up @@ -156,9 +171,18 @@ export class CodexAcpServer implements acp.Agent {
account: accountResponse.account,
cwd: request.cwd,
sessionMcpServers: sessionMcpServers,
mcpToolLogs: new Map(),
}
this.sessions.set(sessionId, sessionState);

const requestedMcpServers = request.mcpServers ?? [];
if (requestedMcpServers.length > 0) {
this.pendingMcpStartupSessions.set(sessionId, {
requestedServers: new Set(requestedMcpServers.map(server => server.name)),
});
this.publishMcpStartupStatusAsync(sessionId, mcpStartupVersion);
}

this.publishAvailableCommandsAsync(sessionId);
const sessionModelState: SessionModelState = this.createModelState(models, currentModelId);
const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState();
Expand Down Expand Up @@ -356,9 +380,18 @@ export class CodexAcpServer implements acp.Agent {
account: accountResponse.account,
cwd: request.cwd,
sessionMcpServers: sessionMcpServers,
mcpToolLogs: new Map(),
};
this.sessions.set(sessionId, sessionState);

const requestedMcpServers = request.mcpServers ?? [];
if (requestedMcpServers.length > 0) {
this.pendingMcpStartupSessions.set(sessionId, {
requestedServers: new Set(requestedMcpServers.map(server => server.name)),
});
this.publishMcpStartupStatusAsync(sessionId, mcpStartupVersion);
Comment thread
NikolaiSviridov marked this conversation as resolved.
}

await this.availableCommands.publish(sessionId);
const sessionModelState: SessionModelState = this.createModelState(models, currentModelId);
const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState();
Expand Down Expand Up @@ -621,6 +654,46 @@ export class CodexAcpServer implements acp.Agent {
return await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartup(mcpStartupVersion));
}

private publishMcpStartupStatusAsync(sessionId: string, mcpStartupVersion: number): void {
void (async () => {
try {
const mcpStartup = await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpStartupResult(mcpStartupVersion));
const sessionState = this.sessions.get(sessionId);
const pendingStartup = this.pendingMcpStartupSessions.get(sessionId);
if (sessionState && pendingStartup) {
sessionState.sessionMcpServers = mcpStartup.ready.filter(serverName =>
pendingStartup.requestedServers.has(serverName)
);
}
await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup?.requestedServers);
this.pendingMcpStartupSessions.delete(sessionId);
} catch (err) {
logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err);
}
})();
}

private async publishMcpStartupStatus(
sessionId: string,
mcpStartup: McpStartupCompleteEvent,
requestedServers?: Set<string>
): Promise<void> {
const filteredStartup = requestedServers
? {
ready: mcpStartup.ready.filter(server => requestedServers.has(server)),
failed: mcpStartup.failed.filter(server => requestedServers.has(server.server)),
cancelled: mcpStartup.cancelled.filter(server => requestedServers.has(server)),
}
: mcpStartup;

for (const update of CodexEventHandler.createMcpStartupUpdates(filteredStartup)) {
await this.connection.sessionUpdate({
sessionId,
update,
});
}
}

async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
logger.log("Prompt received", {
sessionId: params.sessionId,
Expand Down
36 changes: 35 additions & 1 deletion src/CodexAppServerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
GetAccountParams,
GetAccountResponse, LoginAccountParams, LoginAccountResponse, LogoutAccountResponse, ModelListParams,
ModelListResponse,
McpServerStatusUpdatedNotification,
ThreadStartParams,
ThreadStartResponse,
ThreadLoadedListParams,
Expand Down Expand Up @@ -63,6 +64,12 @@ export class CodexAppServerClient {
private mcpStartupCompleteVersion = 0;
private lastMcpStartupComplete: McpStartupCompleteEvent | null = null;
private readonly mcpStartupCompleteResolvers: Array<SignalResolver<McpStartupCompleteEvent>> = [];
private mcpServerStatusVersion = 0;
private readonly mcpServerStatusUpdatedHandlers: Array<(event: McpServerStatusUpdatedNotification) => void> = [];
private readonly mcpServerStatusHistory: Array<{
version: number;
event: McpServerStatusUpdatedNotification;
}> = [];

constructor(connection: MessageConnection) {
this.connection = connection;
Expand All @@ -76,8 +83,10 @@ export class CodexAppServerClient {
}
return;
}

const serverNotification = data as ServerNotification;
if (serverNotification.method === "mcpServer/startupStatus/updated") {
Comment thread
NikolaiSviridov marked this conversation as resolved.
Outdated
this.recordMcpServerStatusUpdated(serverNotification.params);
}
this.notify(serverNotification);
for (const callback of this.codexEventHandlers) {
callback({ eventType: "notification", ...serverNotification });
Expand Down Expand Up @@ -166,6 +175,20 @@ export class CodexAppServerClient {
);
}

onMcpServerStatusUpdated(handler: (event: McpServerStatusUpdatedNotification) => void): void {
this.mcpServerStatusUpdatedHandlers.push(handler);
}

getMcpServerStatusVersion(): number {
return this.mcpServerStatusVersion;
}

getMcpServerStatusUpdates(afterVersion: number): Array<McpServerStatusUpdatedNotification> {
return this.mcpServerStatusHistory
.filter(entry => entry.version > afterVersion)
.map(entry => entry.event);
}

async accountRead(params: GetAccountParams): Promise<GetAccountResponse> {
return await this.sendRequest({ method: "account/read", params: params });
}
Expand Down Expand Up @@ -237,6 +260,17 @@ export class CodexAppServerClient {
});
}

private recordMcpServerStatusUpdated(event: McpServerStatusUpdatedNotification): void {
this.mcpServerStatusVersion += 1;
this.mcpServerStatusHistory.push({
version: this.mcpServerStatusVersion,
event,
});
for (const handler of this.mcpServerStatusUpdatedHandlers) {
handler(event);
}
}

private async sendRequest<R>(request: CodexRequest): Promise<R> {
for (const callback of this.codexEventHandlers) {
callback({ eventType: "request", ...request});
Expand Down
Loading
Loading