mirror of
https://github.com/cline/cline.git
synced 2026-10-24 02:02:27 +08:00
Anthropic thinking fixes.
This commit is contained in:
committed by
Dominic Cooney
parent
a5d3b037c8
commit
5f8a302087
@@ -4,13 +4,26 @@ import {
|
||||
type Server,
|
||||
type Socket,
|
||||
} from "node:net";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { AgentToolContext } from "@cline/shared";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Config } from "../../utils/types";
|
||||
import {
|
||||
createInteractiveComputerUser,
|
||||
resolveHelperModelId,
|
||||
} from "./computer-user";
|
||||
|
||||
const createCliCoreMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../../session/session", () => ({
|
||||
createCliCore: createCliCoreMock,
|
||||
}));
|
||||
|
||||
const toolContext: AgentToolContext = {
|
||||
agentId: "driver-agent",
|
||||
conversationId: "driver-conversation",
|
||||
iteration: 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Stub qbt backend answering get_display_info, which tool construction
|
||||
* always performs (the backend is the sole source of truth for display
|
||||
@@ -81,6 +94,10 @@ describe("createInteractiveComputerUser", () => {
|
||||
let server: Server | undefined;
|
||||
let destroyConnections: (() => void) | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
createCliCoreMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
destroyConnections?.();
|
||||
destroyConnections = undefined;
|
||||
@@ -141,9 +158,79 @@ describe("createInteractiveComputerUser", () => {
|
||||
"computer_user_status",
|
||||
]);
|
||||
// The raw computer tool must not be among the driver's tools.
|
||||
expect(
|
||||
result?.driverTools.some((tool) => tool.name === "computer"),
|
||||
).toBe(false);
|
||||
expect(result?.driverTools.some((tool) => tool.name === "computer")).toBe(
|
||||
false,
|
||||
);
|
||||
await result?.dispose();
|
||||
});
|
||||
|
||||
it("starts the helper with one adaptive reasoning snapshot", async () => {
|
||||
const started = await startStubBackend();
|
||||
server = started.server;
|
||||
destroyConnections = started.destroyConnections;
|
||||
const start = vi.fn(
|
||||
async (_input: {
|
||||
config: Record<string, unknown>;
|
||||
interactive: boolean;
|
||||
}) => ({ sessionId: "helper-session" }),
|
||||
);
|
||||
const send = vi.fn(() => new Promise(() => {}));
|
||||
createCliCoreMock.mockResolvedValue({
|
||||
start,
|
||||
send,
|
||||
abort: vi.fn(async () => {}),
|
||||
stop: vi.fn(async () => {}),
|
||||
dispose: vi.fn(async () => {}),
|
||||
});
|
||||
|
||||
const result = await createInteractiveComputerUser({
|
||||
config: makeConfig(),
|
||||
providerSettingsManager: makeSettings({
|
||||
provider: "anthropic",
|
||||
apiKey: "sk-ant-x",
|
||||
model: "claude-sonnet-4-5",
|
||||
client: "openai",
|
||||
protocol: "openai-responses",
|
||||
routingProviderId: "openai-native",
|
||||
reasoning: {
|
||||
enabled: true,
|
||||
effort: "low",
|
||||
budgetTokens: 8192,
|
||||
},
|
||||
}),
|
||||
notifyDriver: () => {},
|
||||
env: {
|
||||
CLINE_COMPUTER_USE_PORT: String(started.port),
|
||||
CLINE_COMPUTER_USER_MODEL: "claude-opus-4-7",
|
||||
} as NodeJS.ProcessEnv,
|
||||
});
|
||||
const startTool = result?.driverTools.find(
|
||||
(tool) => tool.name === "computer_user_start",
|
||||
);
|
||||
|
||||
await startTool?.execute({ task: "inspect the desktop" }, toolContext);
|
||||
|
||||
expect(start).toHaveBeenCalledWith({
|
||||
interactive: true,
|
||||
config: expect.objectContaining({
|
||||
providerId: "anthropic",
|
||||
modelId: "claude-opus-4-7",
|
||||
thinking: true,
|
||||
reasoningEffort: "high",
|
||||
providerConfig: expect.objectContaining({
|
||||
providerId: "anthropic",
|
||||
modelId: "claude-opus-4-7",
|
||||
thinking: true,
|
||||
reasoningEffort: "high",
|
||||
clientType: undefined,
|
||||
routingProviderId: undefined,
|
||||
thinkingBudgetTokens: undefined,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
expect(start.mock.calls[0]?.[0]?.config).not.toHaveProperty(
|
||||
"thinkingBudgetTokens",
|
||||
);
|
||||
await result?.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
createComputerUserDriverTools,
|
||||
createComputerUseToolFromEnv,
|
||||
type ProviderSettingsManager,
|
||||
toProviderConfig,
|
||||
} from "@cline/core";
|
||||
import type { AgentTool } from "@cline/shared";
|
||||
import { createCliCore } from "../../session/session";
|
||||
@@ -21,14 +22,18 @@ import type { Config } from "../../utils/types";
|
||||
* `computer` tool; when the coordinator is active the driver deliberately
|
||||
* does NOT get the raw tool, so all GUI work flows through the helper.
|
||||
*
|
||||
* Helper consistency boundary: provider, credentials, tool inventory, and
|
||||
* prompt are resolved here, once, when the runtime starts. Changing them
|
||||
* requires a new CLI session.
|
||||
* Helper consistency boundary: provider, credentials, reasoning, tool
|
||||
* inventory, and prompt are resolved here, once, when the runtime starts.
|
||||
* Changing them requires a new CLI session.
|
||||
*/
|
||||
|
||||
const HELPER_PROVIDER_ID = "anthropic";
|
||||
const HELPER_DEFAULT_MODEL_ID = "claude-sonnet-4-6";
|
||||
const HELPER_MODEL_ENV_VAR = "CLINE_COMPUTER_USER_MODEL";
|
||||
const HELPER_REASONING = {
|
||||
thinking: true,
|
||||
reasoningEffort: "high" as const,
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves the helper's Anthropic model id. The helper's model is chosen
|
||||
@@ -95,6 +100,27 @@ export async function createInteractiveComputerUser(input: {
|
||||
helperSettings,
|
||||
input.env ?? process.env,
|
||||
);
|
||||
// Helper model and reasoning settings become effective together when this
|
||||
// session is created. Keep the provider config and session config derived
|
||||
// from this snapshot so saved manual thinking budgets cannot conflict with
|
||||
// adaptive thinking on current Claude models.
|
||||
const helperProviderConfig = {
|
||||
...toProviderConfig({
|
||||
...helperSettings,
|
||||
provider: HELPER_PROVIDER_ID,
|
||||
model: helperModelId,
|
||||
client: undefined,
|
||||
protocol: undefined,
|
||||
routingProviderId: undefined,
|
||||
reasoning: {
|
||||
enabled: HELPER_REASONING.thinking,
|
||||
effort: HELPER_REASONING.reasoningEffort,
|
||||
},
|
||||
}),
|
||||
clientType: undefined,
|
||||
routingProviderId: undefined,
|
||||
thinkingBudgetTokens: undefined,
|
||||
};
|
||||
|
||||
// The helper config and the coordinator reference each other (the
|
||||
// collaboration tools call back into the coordinator). Break the cycle
|
||||
@@ -103,9 +129,14 @@ export async function createInteractiveComputerUser(input: {
|
||||
// session can start.
|
||||
const helperExtraTools: AgentTool[] = [computerTool];
|
||||
const helperConfig = {
|
||||
providerId: HELPER_PROVIDER_ID,
|
||||
modelId: helperModelId,
|
||||
apiKey: helperApiKey,
|
||||
providerId: helperProviderConfig.providerId,
|
||||
modelId: helperProviderConfig.modelId,
|
||||
apiKey: helperProviderConfig.apiKey,
|
||||
baseUrl: helperProviderConfig.baseUrl,
|
||||
headers: helperProviderConfig.headers,
|
||||
knownModels: helperProviderConfig.knownModels,
|
||||
providerConfig: helperProviderConfig,
|
||||
...HELPER_REASONING,
|
||||
cwd: input.config.cwd,
|
||||
workspaceRoot: input.config.workspaceRoot?.trim() || input.config.cwd,
|
||||
mode: "act" as const,
|
||||
|
||||
@@ -362,6 +362,65 @@ describe("prepareLocalRuntimeBootstrap", () => {
|
||||
expect(bootstrap.config.systemPrompt).toBe(explicitPrompt);
|
||||
});
|
||||
|
||||
it("keeps an explicit session provider snapshot authoritative over stored routing and reasoning", async () => {
|
||||
const { prepareLocalRuntimeBootstrap } = await import(
|
||||
"./local-runtime-bootstrap"
|
||||
);
|
||||
const input = createStartInput();
|
||||
input.config.providerId = "anthropic";
|
||||
input.config.modelId = "claude-opus-4-7";
|
||||
Object.assign(input.config, {
|
||||
thinking: true,
|
||||
reasoningEffort: "high",
|
||||
providerConfig: {
|
||||
providerId: "anthropic",
|
||||
modelId: "claude-opus-4-7",
|
||||
apiKey: "session-key",
|
||||
thinking: true,
|
||||
reasoningEffort: "high",
|
||||
thinkingBudgetTokens: undefined,
|
||||
clientType: undefined,
|
||||
routingProviderId: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const bootstrap = await prepareLocalRuntimeBootstrap({
|
||||
input,
|
||||
sessionId: "helper-session",
|
||||
providerSettingsManager: createProviderSettingsManager({
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5",
|
||||
apiKey: "stored-key",
|
||||
client: "openai",
|
||||
protocol: "openai-responses",
|
||||
routingProviderId: "openai-native",
|
||||
reasoning: {
|
||||
enabled: true,
|
||||
effort: "low",
|
||||
budgetTokens: 8192,
|
||||
},
|
||||
}) as never,
|
||||
defaultTelemetry: undefined,
|
||||
defaultToolPolicies: undefined,
|
||||
onPluginEvent: () => {},
|
||||
onTeamEvent: () => {},
|
||||
createSpawnTool,
|
||||
readSessionMetadata: async () => undefined,
|
||||
writeSessionMetadata: async () => {},
|
||||
});
|
||||
|
||||
expect(bootstrap.providerConfig).toMatchObject({
|
||||
providerId: "anthropic",
|
||||
modelId: "claude-opus-4-7",
|
||||
apiKey: "session-key",
|
||||
thinking: true,
|
||||
reasoningEffort: "high",
|
||||
});
|
||||
expect(bootstrap.providerConfig.clientType).toBeUndefined();
|
||||
expect(bootstrap.providerConfig.routingProviderId).toBeUndefined();
|
||||
expect(bootstrap.providerConfig.thinkingBudgetTokens).toBeUndefined();
|
||||
});
|
||||
|
||||
it("filters globally disabled plugin tools before extension setup", async () => {
|
||||
vi.resetModules();
|
||||
resetModulesAfterEach = true;
|
||||
|
||||
@@ -499,6 +499,12 @@ export function buildAnthropicCompatibleReasoningOptions(
|
||||
context: GatewayProviderContext,
|
||||
) {
|
||||
const policy = resolveAnthropicReasoningRequestPolicy(request, context);
|
||||
if (
|
||||
request.reasoning?.enabled === false &&
|
||||
request.modelId.toLowerCase().includes("claude-fable")
|
||||
) {
|
||||
return { max_tokens: ANTHROPIC_DEFAULT_THINKING_BUDGET_TOKENS };
|
||||
}
|
||||
if (
|
||||
policy.kind === "none" ||
|
||||
(!request.reasoning?.enabled &&
|
||||
|
||||
Reference in New Issue
Block a user