Skip to content
Open
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
31 changes: 29 additions & 2 deletions packages/agent/src/adapters/codex/codex-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ import {
} from "@agentclientprotocol/sdk";
import packageJson from "../../../package.json" with { type: "json" };
import { POSTHOG_NOTIFICATIONS } from "../../acp-extensions";
import {
CODE_EXECUTION_MODES,
type CodeExecutionMode,
} from "../../execution-mode";
import type { ProcessSpawnedCallback } from "../../types";
import { Logger } from "../../utils/logger";
import {
Expand Down Expand Up @@ -80,6 +84,20 @@ type CodexSession = BaseSession & {
settingsManager: CodexSettingsManager;
};

function toCodeExecutionMode(mode?: string): CodeExecutionMode {
if (mode && (CODE_EXECUTION_MODES as readonly string[]).includes(mode)) {
return mode as CodeExecutionMode;
}
return "default";
}

const CODEX_NATIVE_MODE: Record<CodeExecutionMode, string> = {
default: "default",
acceptEdits: "default",
plan: "plan",
bypassPermissions: "default",
};

export class CodexAcpAgent extends BaseAcpAgent {
readonly adapterName = "codex";
declare session: CodexSession;
Expand Down Expand Up @@ -182,6 +200,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
taskId: meta?.taskId ?? meta?.persistence?.taskId,
modeId: response.modes?.currentModeId ?? "default",
modelId: response.models?.currentModelId,
permissionMode: toCodeExecutionMode(meta?.permissionMode),
});
this.sessionId = response.sessionId;
this.sessionState.configOptions = response.configOptions ?? [];
Expand Down Expand Up @@ -353,9 +372,17 @@ export class CodexAcpAgent extends BaseAcpAgent {
async setSessionMode(
params: SetSessionModeRequest,
): Promise<SetSessionModeResponse> {
const response = await this.codexConnection.setSessionMode(params);
const requestedMode = toCodeExecutionMode(params.modeId);
const nativeMode = CODEX_NATIVE_MODE[requestedMode];

const response = await this.codexConnection.setSessionMode({
...params,
modeId: nativeMode,
});

if (this.sessionState) {
this.sessionState.modeId = params.modeId;
this.sessionState.modeId = nativeMode;
this.sessionState.permissionMode = requestedMode;
}
return response ?? {};
}
Expand Down
52 changes: 48 additions & 4 deletions packages/agent/src/adapters/codex/codex-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import type {
TerminalHandle,
TerminalOutputRequest,
TerminalOutputResponse,
ToolKind,
WaitForTerminalExitRequest,
WaitForTerminalExitResponse,
WriteTextFileRequest,
WriteTextFileResponse,
} from "@agentclientprotocol/sdk";
import type { CodeExecutionMode } from "../../execution-mode";
import type { Logger } from "../../utils/logger";
import type { CodexSessionState } from "./session-state";

Expand All @@ -36,6 +38,33 @@ export interface CodexClientCallbacks {
onUsageUpdate?: (update: Record<string, unknown>) => void;
}

const AUTO_APPROVED_KINDS: Record<CodeExecutionMode, Set<ToolKind>> = {
default: new Set(["read", "search", "fetch", "think"]),
acceptEdits: new Set(["read", "edit", "search", "fetch", "think"]),
plan: new Set(["read", "search", "fetch", "think"]),
bypassPermissions: new Set([
"read",
"edit",
"delete",
"move",
"search",
"execute",
"think",
"fetch",
"switch_mode",
"other",
]),
};

function shouldAutoApprove(
mode: CodeExecutionMode,
kind: ToolKind | null | undefined,
): boolean {
if (mode === "bypassPermissions") return true;
if (!kind) return false;
return AUTO_APPROVED_KINDS[mode]?.has(kind) ?? false;
}

/**
* Creates an ACP Client that delegates all requests from codex-acp
* to the upstream PostHog Code client (via AgentSideConnection).
Expand All @@ -46,16 +75,31 @@ export function createCodexClient(
sessionState: CodexSessionState,
callbacks?: CodexClientCallbacks,
): Client {
// Track terminal handles for delegation
const terminalHandles = new Map<string, TerminalHandle>();

return {
async requestPermission(
params: RequestPermissionRequest,
): Promise<RequestPermissionResponse> {
logger.debug("Relaying permission request to upstream", {
sessionId: params.sessionId,
});
const kind = params.toolCall?.kind as ToolKind | null | undefined;

if (shouldAutoApprove(sessionState.permissionMode, kind)) {
logger.debug("Auto-approving permission", {
mode: sessionState.permissionMode,
kind,
toolCallId: params.toolCall?.toolCallId,
});
const allowOption = params.options?.find(
(o) => o.kind === "allow_once" || o.kind === "allow_always",
);
return {
outcome: {
outcome: "selected",
optionId: allowOption?.optionId ?? "allow",
},
};
}

return upstreamClient.requestPermission(params);
},

Expand Down
4 changes: 4 additions & 0 deletions packages/agent/src/adapters/codex/session-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import type { SessionConfigOption } from "@agentclientprotocol/sdk";
import type { CodeExecutionMode } from "../../execution-mode";

export interface CodexUsage {
inputTokens: number;
Expand All @@ -21,6 +22,7 @@ export interface CodexSessionState {
accumulatedUsage: CodexUsage;
contextSize?: number;
contextUsed?: number;
permissionMode: CodeExecutionMode;
cancelled: boolean;
interruptReason?: string;
taskRunId?: string;
Expand All @@ -35,6 +37,7 @@ export function createSessionState(
taskId?: string;
modeId?: string;
modelId?: string;
permissionMode?: CodeExecutionMode;
},
): CodexSessionState {
return {
Expand All @@ -49,6 +52,7 @@ export function createSessionState(
cachedReadTokens: 0,
cachedWriteTokens: 0,
},
permissionMode: opts?.permissionMode ?? "default",
cancelled: false,
taskRunId: opts?.taskRunId,
taskId: opts?.taskId,
Expand Down
Loading