mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 20:42:10 +00:00
feat: add 'Default' option to effort level config (#701)
Add a "Default" selection to the effort level config option, allowing users to let the SDK decide the effort level instead of forcing a specific level. When "default" is selected, applyFlagSettings is called with effortLevel: undefined, clearing any previous override. Replaces the previous fallback logic (xhigh -> high -> supportedLevels[0]) with a simpler "default" fallback that lets the SDK use its own default. Please,agree it. The third-party model may not support the 'Effort', or not support 'high' and 'xhigh'. We need it. --------- Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
@@ -1822,7 +1822,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
typeof newEffortOpt?.currentValue === "string" ? newEffortOpt.currentValue : undefined;
|
||||
if (newEffort !== currentEffort) {
|
||||
await session.query.applyFlagSettings({
|
||||
effortLevel: newEffort as Settings["effortLevel"],
|
||||
effortLevel: toSdkEffortLevel(newEffort),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1847,7 +1847,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
);
|
||||
if (configId === "effort") {
|
||||
await session.query.applyFlagSettings({
|
||||
effortLevel: value as Settings["effortLevel"],
|
||||
effortLevel: toSdkEffortLevel(value),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2223,7 +2223,11 @@ export class ClaudeAcpAgent implements Agent {
|
||||
|
||||
// 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") {
|
||||
if (
|
||||
initialEffort &&
|
||||
typeof initialEffort.currentValue === "string" &&
|
||||
initialEffort.currentValue !== "default"
|
||||
) {
|
||||
await q.applyFlagSettings({
|
||||
effortLevel: initialEffort.currentValue as Settings["effortLevel"],
|
||||
});
|
||||
@@ -2419,6 +2423,16 @@ function buildAvailableModes(modelInfo: ModelInfo | undefined): SessionModeState
|
||||
return modes;
|
||||
}
|
||||
|
||||
// Translate a UI effort value into the flag-layer payload. The SDK
|
||||
// shallow-merges `applyFlagSettings`, drops `undefined` during JSON transport,
|
||||
// and only clears a key when an explicit `null` is sent — see
|
||||
// `applyFlagSettings` in @anthropic-ai/claude-agent-sdk. Mapping both the
|
||||
// `"default"` sentinel and `undefined` (effort option absent for the model) to
|
||||
// `null` ensures any previously-applied flag is actually cleared.
|
||||
function toSdkEffortLevel(value: string | undefined): Settings["effortLevel"] | null {
|
||||
return value === undefined || value === "default" ? null : (value as Settings["effortLevel"]);
|
||||
}
|
||||
|
||||
function buildConfigOptions(
|
||||
modes: SessionModeState,
|
||||
models: SessionModelState,
|
||||
@@ -2461,25 +2475,20 @@ function buildConfigOptions(
|
||||
: [];
|
||||
|
||||
if (supportedLevels.length > 0) {
|
||||
const effortOptions = supportedLevels.map((level) => ({
|
||||
value: level,
|
||||
name: level
|
||||
.split(/[_-]/)
|
||||
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
|
||||
.join(" "),
|
||||
}));
|
||||
const effortOptions = [
|
||||
{ value: "default", name: "Default" },
|
||||
...supportedLevels.map((level) => ({
|
||||
value: level,
|
||||
name: level
|
||||
.split(/[_-]/)
|
||||
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
|
||||
.join(" "),
|
||||
})),
|
||||
];
|
||||
|
||||
// Keep the current level if valid, otherwise prefer xhigh (Claude Code's
|
||||
// recommended default for capable models), then high (the API default).
|
||||
const includes = (l: string) => (supportedLevels as string[]).includes(l);
|
||||
const includes = (l: string) => l === "default" || (supportedLevels as string[]).includes(l);
|
||||
const validEffort =
|
||||
currentEffortLevel && includes(currentEffortLevel)
|
||||
? currentEffortLevel
|
||||
: includes("xhigh")
|
||||
? "xhigh"
|
||||
: includes("high")
|
||||
? "high"
|
||||
: supportedLevels[0];
|
||||
currentEffortLevel && includes(currentEffortLevel) ? currentEffortLevel : "default";
|
||||
|
||||
options.push({
|
||||
id: "effort",
|
||||
|
||||
@@ -65,8 +65,9 @@ const MOCK_CONFIG_OPTIONS = [
|
||||
description: "Available effort levels for this model",
|
||||
type: "select",
|
||||
category: "effort",
|
||||
currentValue: "high",
|
||||
currentValue: "default",
|
||||
options: [
|
||||
{ value: "default", name: "Default" },
|
||||
{ value: "low", name: "Low" },
|
||||
{ value: "medium", name: "Medium" },
|
||||
{ value: "high", name: "High" },
|
||||
@@ -445,7 +446,7 @@ describe("session config options", () => {
|
||||
(o: any) => o.id === "effort",
|
||||
);
|
||||
expect(effortOption).toBeUndefined();
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: undefined });
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
});
|
||||
|
||||
it("clamps effort in config_option_update when new model has different supported levels", async () => {
|
||||
@@ -483,8 +484,8 @@ describe("session config options", () => {
|
||||
(o: any) => o.id === "effort",
|
||||
);
|
||||
expect(effortOption).toBeDefined();
|
||||
expect(effortOption.currentValue).toBe("high");
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: "high" });
|
||||
expect(effortOption.currentValue).toBe("default");
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
});
|
||||
|
||||
it("preserves effort in config_option_update when new model supports same level", async () => {
|
||||
@@ -557,6 +558,31 @@ describe("session config options", () => {
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: "low" });
|
||||
});
|
||||
|
||||
it("calls applyFlagSettings with null effortLevel for 'default'", async () => {
|
||||
// Set effort to a non-default value first
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
|
||||
if (effortOpt) effortOpt.currentValue = "high";
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "effort",
|
||||
value: "default",
|
||||
});
|
||||
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
|
||||
// The SDK's applyFlagSettings travels over a JSON pipe and only clears a
|
||||
// flag-layer key when an explicit `null` is sent — `undefined` is
|
||||
// dropped during JSON.stringify, which would leave the previous effort
|
||||
// override in place. Round-trip the call args through JSON to make sure
|
||||
// the key actually reaches the SDK.
|
||||
const calls = applyFlagSettingsSpy.mock.calls;
|
||||
const lastCallArgs = calls[calls.length - 1]?.[0];
|
||||
const serialized = JSON.parse(JSON.stringify(lastCallArgs));
|
||||
expect(serialized).toHaveProperty("effortLevel", null);
|
||||
});
|
||||
|
||||
it("updates effort currentValue in returned configOptions", async () => {
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
@@ -663,7 +689,7 @@ describe("session config options", () => {
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: undefined });
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
});
|
||||
|
||||
it("adds effort option when switching to a model that supports effort", async () => {
|
||||
@@ -696,8 +722,8 @@ describe("session config options", () => {
|
||||
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption).toBeDefined();
|
||||
// No previous effort, so defaults to "high" (the API default)
|
||||
expect(effortOption?.currentValue).toBe("high");
|
||||
// No previous effort, so defaults to "default" (no effort override)
|
||||
expect(effortOption?.currentValue).toBe("default");
|
||||
});
|
||||
|
||||
it("clamps effort to valid value when new model has different supported levels", async () => {
|
||||
@@ -731,10 +757,10 @@ describe("session config options", () => {
|
||||
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption).toBeDefined();
|
||||
// "max" is not in sonnet's levels, so should fall back to "high" (the API default)
|
||||
expect(effortOption?.currentValue).toBe("high");
|
||||
// SDK should be told about the clamped value
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: "high" });
|
||||
// "max" is not in sonnet's levels, so should fall back to "default" (no effort override)
|
||||
expect(effortOption?.currentValue).toBe("default");
|
||||
// SDK should be told to clear the effort override
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
});
|
||||
|
||||
it("preserves effort value when new model supports the same level", async () => {
|
||||
|
||||
Reference in New Issue
Block a user