Unverified 提交 c145a0cd authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Local agent auto (#2134)

<!-- CURSOR_SUMMARY --> > [!NOTE] > Improves local-agent auto mode with robust model routing and updates tests accordingly. > > - Centralizes pro model selection in `getProModelClient`, routing OpenAI via `responses` in local-agent and adding local fallback across `GPT_5.2`, `Claude Sonnet 4.5`, and `Gemini 3 Flash` > - Standardizes model IDs via constants and updates provider/thinking flags (adds reasoning for `auto` provider) > - e2e: introduce `clickRunSecurityReview()` helper; select Anthropic model in local-agent; refresh snapshots to use `reasoning_effort` and reflect non-OpenAI model > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b3fd078d2b893ebc6d011365295948a17ea58c4f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Enables reliable local-agent auto mode with smart model fallback and routes OpenAI models through Responses to keep reasoning features working. Standardizes model names with constants. - **New Features** - Auto mode falls back across GPT‑5.2 (OpenAI), Claude Sonnet 4.5 (Anthropic), and Gemini 3 Flash (Google). - OpenAI models use the Responses API to support thinking summaries and full functionality. - Auto provider enables reasoning options in local-agent. - **Refactors** - Centralized pro model routing in getProModelClient and avoids free variants. - Added constants for model IDs (GPT_5_2_MODEL_NAME, SONNET_4_5, GEMINI_3_FLASH). <sup>Written for commit b3fd078d2b893ebc6d011365295948a17ea58c4f. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. -->
上级 46cc8650
......@@ -326,6 +326,14 @@ export class PageObject {
}
await this.setUpDyadProvider();
await this.goToAppsTab();
// Select a non-openAI model for local agent mode,
// since openAI models go to the responses API.
if (localAgent) {
await this.selectModel({
provider: "Anthropic",
model: "Claude Opus 4.5",
});
}
}
async ensurePnpmInstall() {
......@@ -724,6 +732,18 @@ export class PageObject {
});
}
////////////////////////////////
// Security review
////////////////////////////////
async clickRunSecurityReview() {
const runSecurityReviewButton = this.page
.getByRole("button", { name: "Run Security Review" })
.first();
await runSecurityReviewButton.click();
await runSecurityReviewButton.waitFor({ state: "hidden" });
await this.waitForChatCompletion();
}
async snapshotSecurityFindingsTable() {
await expect(
this.page.getByTestId("security-findings-table"),
......
......@@ -11,11 +11,7 @@ testSkipIfWindows("local-agent - security review fix", async ({ po }) => {
// First, trigger a security review
await po.selectPreviewMode("security");
await po.page
.getByRole("button", { name: "Run Security Review" })
.first()
.click();
await po.waitForChatCompletion();
await po.clickRunSecurityReview();
await po.snapshotServerDump("all-messages");
});
......
......@@ -9,11 +9,7 @@ testSkipIfWindows("security review", async ({ po }) => {
await po.selectPreviewMode("security");
await po.page
.getByRole("button", { name: "Run Security Review" })
.first()
.click();
await po.waitForChatCompletion();
await po.clickRunSecurityReview();
await po.snapshotServerDump("all-messages");
await po.snapshotSecurityFindingsTable();
......@@ -36,11 +32,7 @@ test("security review - edit and use knowledge", async ({ po }) => {
.fill("testing\nrules123");
await po.page.getByRole("button", { name: "Save" }).click();
await po.page
.getByRole("button", { name: "Run Security Review" })
.first()
.click();
await po.waitForChatCompletion();
await po.clickRunSecurityReview();
await po.snapshotServerDump("all-messages");
});
......
......@@ -14,11 +14,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"files": [
{
......
......@@ -14,11 +14,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"versioned_files": {
"fileIdToContent": {
......
{
"body": {
"model": "dyad/auto",
"model": "anthropic/claude-opus-4-5",
"max_tokens": 32000,
"temperature": 0,
"messages": [
......@@ -14,11 +14,6 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"dyad_options": {
"files": [
{
......
{
"body": {
"model": "dyad/auto",
"model": "anthropic/claude-opus-4-5",
"max_tokens": 32000,
"temperature": 0,
"messages": [
......@@ -327,12 +327,7 @@
}
],
"tool_choice": "auto",
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
}
"stream": true
},
"headers": {
"authorization": "Bearer testdyadkey"
......
......@@ -9,7 +9,7 @@
- button:
- img
- img
- text: auto
- text: claude-opus-4-5
- img
- text: less than a minute ago
- button "Request ID":
......@@ -27,7 +27,7 @@
- button:
- img
- img
- text: auto
- text: claude-opus-4-5
- img
- text: less than a minute ago
- button "Request ID":
......
......@@ -9,7 +9,7 @@
- button:
- img
- img
- text: auto
- text: claude-opus-4-5
- img
- text: less than a minute ago
- button "Request ID":
......@@ -17,7 +17,7 @@
- paragraph: tc=local-agent/read-logs
- paragraph: Let me check the recent console logs to see what's happening in the application.
- img
- text: /LOGSReading \d+ logs?/
- text: LOGSReading 1 logs
- img
- paragraph: Now let me filter for only error logs to identify any issues.
- img
......@@ -31,7 +31,7 @@
- button:
- img
- img
- text: auto
- text: claude-opus-4-5
- img
- text: less than a minute ago
- button "Request ID":
......
......@@ -14,11 +14,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"files": [
{
......
......@@ -14,11 +14,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"files": [
{
......
......@@ -14,11 +14,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"files": [
{
......
......@@ -38,11 +38,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"versioned_files": {
"fileIdToContent": {
......
......@@ -14,11 +14,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"versioned_files": {
"fileIdToContent": {
......
......@@ -30,11 +30,7 @@
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"reasoning_effort": "medium",
"dyad_options": {
"versioned_files": {
"fileIdToContent": {
......
......@@ -3,7 +3,6 @@ import { LanguageModel } from "../ipc_types";
export const PROVIDERS_THAT_SUPPORT_THINKING: (keyof typeof MODEL_OPTIONS)[] = [
"google",
"vertex",
"auto",
];
export interface ModelOption {
......@@ -18,11 +17,15 @@ export interface ModelOption {
contextWindow?: number;
}
export const GPT_5_2_MODEL_NAME = "gpt-5.2";
export const SONNET_4_5 = "claude-sonnet-4-5-20250929";
export const GEMINI_3_FLASH = "gemini-3-flash-preview";
export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
openai: [
// https://platform.openai.com/docs/models/gpt-5.1
{
name: "gpt-5.2",
name: GPT_5_2_MODEL_NAME,
displayName: "GPT 5.2",
description: "OpenAI's latest model",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
......@@ -121,7 +124,7 @@ export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
dollarSigns: 5,
},
{
name: "claude-sonnet-4-5-20250929",
name: SONNET_4_5,
displayName: "Claude Sonnet 4.5",
description:
"Anthropic's best model for coding (note: >200k tokens is very expensive!)",
......@@ -158,7 +161,7 @@ export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
},
// https://ai.google.dev/gemini-api/docs/models#gemini-3-pro
{
name: "gemini-3-flash-preview",
name: GEMINI_3_FLASH,
displayName: "Gemini 3 Flash (Preview)",
description: "Powerful coding model at a good price",
// See Flash 2.5 comment below (go 1 below just to be safe, even though it seems OK now).
......
......@@ -15,10 +15,18 @@ import type {
} from "../../lib/schemas";
import { getEnvVar } from "./read_env";
import log from "electron-log";
import { FREE_OPENROUTER_MODEL_NAMES } from "../shared/language_model_constants";
import {
FREE_OPENROUTER_MODEL_NAMES,
GEMINI_3_FLASH,
GPT_5_2_MODEL_NAME,
SONNET_4_5,
} from "../shared/language_model_constants";
import { getLanguageModelProviders } from "../shared/language_model_helpers";
import { LanguageModelProvider } from "../ipc_types";
import { createDyadEngine } from "./llm_engine_provider";
import {
createDyadEngine,
type DyadEngineProvider,
} from "./llm_engine_provider";
import { LM_STUDIO_BASE_URL } from "./lm_studio_utils";
import { createOllamaProvider } from "./ollama_provider";
......@@ -106,16 +114,15 @@ export async function getModelClient(
// Do not use free variant (for openrouter).
const modelName = model.name.split(":free")[0];
const autoModelClient = {
model: (settings.selectedChatMode === "local-agent" &&
model.provider === "openai"
? provider.responses
: provider)(`${providerConfig.gatewayPrefix || ""}${modelName}`),
builtinProviderId: model.provider,
};
const proModelClient = getProModelClient({
model,
settings,
provider,
modelId: `${providerConfig.gatewayPrefix || ""}${modelName}`,
});
return {
modelClient: autoModelClient,
modelClient: proModelClient,
isEngineEnabled: true,
isSmartContextEnabled: enableSmartFilesContext,
};
......@@ -184,6 +191,53 @@ export async function getModelClient(
return getRegularModelClient(model, settings, providerConfig);
}
function getProModelClient({
model,
settings,
provider,
modelId,
}: {
model: LargeLanguageModel;
settings: UserSettings;
provider: DyadEngineProvider;
modelId: string;
}): ModelClient {
if (
settings.selectedChatMode === "local-agent" &&
model.provider === "auto" &&
model.name === "auto"
) {
return {
// We need to do the fallback here (and not server-side)
// because GPT-5* models need to use responses API to get
// full functionality (e.g. thinking summaries).
model: createFallback({
models: [
// openai requires no prefix.
provider.responses(`${GPT_5_2_MODEL_NAME}`),
provider(`anthropic/${SONNET_4_5}`),
provider(`gemini/${GEMINI_3_FLASH}`),
],
}),
// Using openAI as the default provider.
builtinProviderId: "openai",
};
}
if (
settings.selectedChatMode === "local-agent" &&
model.provider === "openai"
) {
return {
model: provider.responses(modelId),
builtinProviderId: model.provider,
};
}
return {
model: provider(modelId),
builtinProviderId: model.provider,
};
}
function getRegularModelClient(
model: LargeLanguageModel,
settings: UserSettings,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论