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
6 changes: 6 additions & 0 deletions src/CodexAcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,12 @@ export class CodexAcpClient {
});
}

async getPlanModeReasoningEffort(cwd: string): Promise<string | null> {
const effectiveConfig = await this.codexClient.configRead({includeLayers: false, cwd});
const effort = effectiveConfig?.config?.["plan_mode_reasoning_effort"] ?? this.config["plan_mode_reasoning_effort"];
return typeof effort === "string" ? effort : null;
}

private getCollaborationMode(sessionId: string): ModeKind {
return this.codexClient.getThreadSettings(sessionId)?.collaborationMode.mode ?? "default";
}
Expand Down
23 changes: 23 additions & 0 deletions src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ import {
export interface SessionState {
sessionId: string,
currentModelId: string,
defaultModeModelId: string | null,
availableModels: Array<Model>,
supportedReasoningEfforts: Array<ReasoningEffortOption>,
supportedInputModalities: Array<InputModality>,
Expand Down Expand Up @@ -662,6 +663,7 @@ export class CodexAcpServer {
const sessionState: SessionState = {
sessionId: sessionId,
currentModelId: currentModelId,
defaultModeModelId: null,
availableModels: models,
supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
Expand Down Expand Up @@ -1385,10 +1387,30 @@ export class CodexAcpServer {
if (mode === null) {
throw RequestError.invalidParams();
}
if (mode === PLAN_COLLABORATION_MODE && sessionState.collaborationMode !== PLAN_COLLABORATION_MODE) {
sessionState.defaultModeModelId = sessionState.currentModelId;
sessionState.currentModelId = await this.createPlanModeModelId(sessionState);
} else if (mode === DEFAULT_COLLABORATION_MODE && sessionState.defaultModeModelId !== null) {
sessionState.currentModelId = sessionState.defaultModeModelId;
sessionState.defaultModeModelId = null;
}
await this.codexAcpClient.setCollaborationMode(sessionState.sessionId, mode, sessionState.currentModelId);
sessionState.collaborationMode = mode;
}

private async createPlanModeModelId(sessionState: SessionState): Promise<string> {
const planEffort = await this.codexAcpClient.getPlanModeReasoningEffort(sessionState.cwd);
if (!planEffort) {
return sessionState.currentModelId;
}
const effort = findSupportedEffort(sessionState.supportedReasoningEfforts, planEffort);
if (!effort) {
return sessionState.currentModelId;
}
const {model} = ModelId.fromString(sessionState.currentModelId);
return ModelId.create(model, effort).toString();
}

private applyModelChange(sessionState: SessionState, value: string): void {
const model = sessionState.availableModels.find(m => m.id === value);
if (!model) {
Expand Down Expand Up @@ -1919,6 +1941,7 @@ export class CodexAcpServer {
const sessionState: SessionState = {
sessionId: sessionId,
currentModelId: currentModelId,
defaultModeModelId: null,
availableModels: models,
supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
Expand Down
39 changes: 39 additions & 0 deletions src/__tests__/CodexACPAgent/session-config-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,45 @@ describe("Session config options", () => {
expect(result.configOptions?.find(o => o.id === COLLABORATION_MODE_CONFIG_ID)).toMatchObject({currentValue: "plan"});
});

it("uses plan_mode_reasoning_effort while Plan mode is active", async () => {
const {fast} = buildModels();
const {codexAcpAgent, codexAcpClient} = await createSession("fast-model[low]", [fast]);
vi.spyOn(codexAcpClient, "getPlanModeReasoningEffort").mockResolvedValue("high");
const update = vi.spyOn((codexAcpClient as any).codexClient, "threadSettingsUpdate").mockResolvedValue(undefined);

const enabled = await codexAcpAgent.setSessionConfigOption({
sessionId: "session-id",
configId: COLLABORATION_MODE_CONFIG_ID,
value: PLAN_COLLABORATION_MODE,
});

expect(update).toHaveBeenCalledWith(expect.objectContaining({
threadId: "session-id",
collaborationMode: expect.objectContaining({
mode: "plan",
settings: expect.objectContaining({reasoning_effort: "high"}),
}),
}));
expect(codexAcpAgent.getSessionState("session-id").currentModelId).toBe("fast-model[high]");
expect(enabled.configOptions?.find(o => o.id === REASONING_EFFORT_CONFIG_ID)).toMatchObject({currentValue: "high"});

const disabled = await codexAcpAgent.setSessionConfigOption({
sessionId: "session-id",
configId: COLLABORATION_MODE_CONFIG_ID,
value: "default",
});

expect(update).toHaveBeenLastCalledWith(expect.objectContaining({
threadId: "session-id",
collaborationMode: expect.objectContaining({
mode: "default",
settings: expect.objectContaining({reasoning_effort: "low"}),
}),
}));
expect(codexAcpAgent.getSessionState("session-id").currentModelId).toBe("fast-model[low]");
expect(disabled.configOptions?.find(o => o.id === REASONING_EFFORT_CONFIG_ID)).toMatchObject({currentValue: "low"});
});

it("toggles collaboration mode with /plan without starting a model turn", async () => {
const {fast} = buildModels();
const {fixture, codexAcpAgent, codexAcpClient} = await createSession("fast-model[medium]", [fast]);
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/acp-test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ export function createTestSessionState(overrides?: Partial<SessionState>): Sessi
additionalDirectories: [],
sessionId,
currentModelId: "model-id[effort]",
defaultModeModelId: null,
availableModels: [],
supportedReasoningEfforts: [],
supportedInputModalities: ["text", "image"],
Expand Down