Skip to content
Draft
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
109 changes: 94 additions & 15 deletions src/acp-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
PermissionMode,
Query,
query,
Settings,
SDKPartialAssistantMessage,
SDKResultMessage,
SDKUserMessage,
Expand Down Expand Up @@ -116,6 +117,7 @@ type Session = {
accumulatedUsage: AccumulatedUsage;
modes: SessionModeState;
models: SessionModelState;
modelInfos: ModelInfo[];
configOptions: SessionConfigOption[];
promptRunning: boolean;
pendingMessages: Map<string, { resolve: (cancelled: boolean) => void; order: number }>;
Expand Down Expand Up @@ -957,15 +959,13 @@ export class ClaudeAcpAgent implements Agent {
});
} else if (params.configId === "model") {
await this.sessions[params.sessionId].query.setModel(resolvedValue);
} else if (params.configId === "effort") {
await this.sessions[params.sessionId].query.applyFlagSettings({
effortLevel: resolvedValue as Settings["effortLevel"],
});
}

this.syncSessionConfigState(session, params.configId, params.value);

session.configOptions = session.configOptions.map((o) =>
o.id === params.configId && typeof o.currentValue === "string"
? { ...o, currentValue: resolvedValue }
: o,
);
await this.applyConfigOptionValue(session, params.configId, resolvedValue);

return { configOptions: session.configOptions };
}
Expand Down Expand Up @@ -1194,11 +1194,7 @@ export class ClaudeAcpAgent implements Agent {
const session = this.sessions[sessionId];
if (!session) return;

this.syncSessionConfigState(session, configId, value);

session.configOptions = session.configOptions.map((o) =>
o.id === configId && typeof o.currentValue === "string" ? { ...o, currentValue: value } : o,
);
await this.applyConfigOptionValue(session, configId, value);

await this.client.sessionUpdate({
sessionId,
Expand All @@ -1209,12 +1205,45 @@ export class ClaudeAcpAgent implements Agent {
});
}

private syncSessionConfigState(session: Session, configId: string, value: string): void {
private async applyConfigOptionValue(
session: Session,
configId: string,
value: string,
): Promise<void> {
// Sync top-level session state
if (configId === "mode") {
session.modes = { ...session.modes, currentModeId: value };
} else if (configId === "model") {
session.models = { ...session.models, currentModelId: value };
}

// Update configOptions
if (configId === "model") {
// Rebuild config options since effort levels depend on the selected model
const effortOpt = session.configOptions.find((o) => o.id === "effort");
const currentEffort =
typeof effortOpt?.currentValue === "string" ? effortOpt.currentValue : undefined;
session.configOptions = buildConfigOptions(
session.modes,
session.models,
session.modelInfos,
currentEffort,
);

// Sync effort with the SDK if it changed after the model switch
const newEffortOpt = session.configOptions.find((o) => o.id === "effort");
const newEffort =
typeof newEffortOpt?.currentValue === "string" ? newEffortOpt.currentValue : undefined;
if (newEffort !== currentEffort) {
await session.query.applyFlagSettings({
effortLevel: newEffort as Settings["effortLevel"],
});
}
} else {
session.configOptions = session.configOptions.map((o) =>
o.id === configId && typeof o.currentValue === "string" ? { ...o, currentValue: value } : o,
);
}
}

private async getOrCreateSession(params: {
Expand Down Expand Up @@ -1482,7 +1511,20 @@ export class ClaudeAcpAgent implements Agent {
availableModes,
};

const configOptions = buildConfigOptions(modes, models);
const configOptions = buildConfigOptions(
modes,
models,
initializationResult.models,
settingsManager.getSettings().effortLevel,
);

// Apply the initial effort level to the SDK so it matches the UI default
const initialEffort = configOptions.find((o) => o.id === "effort");
if (initialEffort && typeof initialEffort.currentValue === "string") {
await q.applyFlagSettings({
effortLevel: initialEffort.currentValue as Settings["effortLevel"],
});
}

this.sessions[sessionId] = {
query: q,
Expand All @@ -1498,6 +1540,7 @@ export class ClaudeAcpAgent implements Agent {
},
modes,
models,
modelInfos: initializationResult.models,
configOptions,
promptRunning: false,
pendingMessages: new Map(),
Expand Down Expand Up @@ -1544,8 +1587,10 @@ function createEnvForGateway(gatewayMeta?: GatewayAuthMeta) {
function buildConfigOptions(
modes: SessionModeState,
models: SessionModelState,
modelInfos: ModelInfo[],
currentEffortLevel?: string,
): SessionConfigOption[] {
return [
const options: SessionConfigOption[] = [
{
id: "mode",
name: "Mode",
Expand Down Expand Up @@ -1573,6 +1618,40 @@ function buildConfigOptions(
})),
},
];

// Add effort level option based on the currently selected model
const currentModelInfo = modelInfos.find((m) => m.value === models.currentModelId);
const supportedLevels = currentModelInfo?.supportsEffort
? (currentModelInfo.supportedEffortLevels ?? [])
: [];

if (supportedLevels.length > 0) {
const effortOptions = supportedLevels.map((level) => ({
value: level,
name: level.charAt(0).toUpperCase() + level.slice(1),
}));

// Keep the current level if valid, otherwise prefer a sensible default
const includes = (l: string) => (supportedLevels as string[]).includes(l);
const validEffort =
currentEffortLevel && includes(currentEffortLevel)
? currentEffortLevel
: includes("medium")
? "medium"
: supportedLevels[0];

options.push({
id: "effort",
name: "Effort",
description: "Available effort levels for this model",
category: "effort",
type: "select",
currentValue: validEffort,
options: effortOptions,
});
}

return options;
}

// Claude Code CLI persists display strings like "opus[1m]" in settings,
Expand Down
5 changes: 5 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ClaudeCodeSettings {
permissions?: PermissionSettings;
env?: Record<string, string>;
model?: string;
effortLevel?: string;
}

/**
Expand Down Expand Up @@ -168,6 +169,10 @@ export class SettingsManager {
merged.model = settings.model;
}

if (settings.effortLevel !== undefined) {
merged.effortLevel = settings.effortLevel;
}

if (settings.permissions?.defaultMode !== undefined) {
merged.permissions = {
...merged.permissions,
Expand Down
3 changes: 3 additions & 0 deletions src/tests/acp-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,7 @@ describe("stop reason propagation", () => {
currentModelId: "default",
availableModels: [],
},
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
Expand Down Expand Up @@ -1472,6 +1473,7 @@ describe("stop reason propagation", () => {
currentModelId: "default",
availableModels: [],
},
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
Expand Down Expand Up @@ -1545,6 +1547,7 @@ describe("session/close", () => {
currentModelId: "default",
availableModels: [],
},
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
Expand Down
Loading
Loading