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

Responses API for local agent & openAI (#2126)

<!-- CURSOR_SUMMARY --> > [!NOTE] > Introduces Responses API support for local-agent flows and aligns OpenAI reasoning options. > > - In `get_model_client.ts`, when using Dyad Pro and `selectedChatMode` is `"local-agent"` with OpenAI, route to `provider.responses(...)` instead of chat model > - In `llm_engine_provider.ts`, add `responses(modelId)` returning `OpenAIResponsesLanguageModel`, refactor request handling into `createDyadFetch()`, and expose `provider.responses` > - In `thinking_utils.ts`, for OpenAI in `local-agent` mode, send `{ reasoning: { summary: "detailed", effort: "medium" } }`; otherwise keep `reasoning_effort: "medium"` > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f434775c1c42fd9a647b5ee0e51cf5aecc7a7c8f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
上级 99a5ffe0
...@@ -107,7 +107,10 @@ export async function getModelClient( ...@@ -107,7 +107,10 @@ export async function getModelClient(
// Do not use free variant (for openrouter). // Do not use free variant (for openrouter).
const modelName = model.name.split(":free")[0]; const modelName = model.name.split(":free")[0];
const autoModelClient = { const autoModelClient = {
model: provider(`${providerConfig.gatewayPrefix || ""}${modelName}`), model: (settings.selectedChatMode === "local-agent" &&
model.provider === "openai"
? provider.responses
: provider)(`${providerConfig.gatewayPrefix || ""}${modelName}`),
builtinProviderId: model.provider, builtinProviderId: model.provider,
}; };
......
import { OpenAICompatibleChatLanguageModel } from "@ai-sdk/openai-compatible"; import { OpenAICompatibleChatLanguageModel } from "@ai-sdk/openai-compatible";
import { OpenAIResponsesLanguageModel } from "@ai-sdk/openai/internal";
import { import {
FetchFunction, FetchFunction,
loadApiKey, loadApiKey,
...@@ -59,6 +60,8 @@ Creates a chat model for text generation. ...@@ -59,6 +60,8 @@ Creates a chat model for text generation.
modelId: ExampleChatModelId, modelId: ExampleChatModelId,
settings?: ExampleChatSettings, settings?: ExampleChatSettings,
): LanguageModel; ): LanguageModel;
responses(modelId: ExampleChatModelId): LanguageModel;
} }
export function createDyadEngine( export function createDyadEngine(
...@@ -99,110 +102,121 @@ export function createDyadEngine( ...@@ -99,110 +102,121 @@ export function createDyadEngine(
fetch: options.fetch, fetch: options.fetch,
}); });
const createChatModel = (modelId: ExampleChatModelId) => { // Custom fetch implementation that adds dyad-specific options to the request
// Create configuration with file handling const createDyadFetch = (): FetchFunction => {
const config = { return (input: RequestInfo | URL, init?: RequestInit) => {
...getCommonModelConfig(), // Use default fetch if no init or body
// defaultObjectGenerationMode: if (!init || !init.body || typeof init.body !== "string") {
// "tool" as LanguageModelV1ObjectGenerationMode, return (options.fetch || fetch)(input, init);
// Custom fetch implementation that adds files to the request }
fetch: (input: RequestInfo | URL, init?: RequestInit) => {
// Use default fetch if no init or body try {
if (!init || !init.body || typeof init.body !== "string") { // Parse the request body to manipulate it
return (options.fetch || fetch)(input, init); const parsedBody = {
...JSON.parse(init.body),
...getExtraProviderOptions(
options.originalProviderId,
options.settings,
),
};
const dyadVersionedFiles = parsedBody.dyadVersionedFiles;
if ("dyadVersionedFiles" in parsedBody) {
delete parsedBody.dyadVersionedFiles;
}
const dyadFiles = parsedBody.dyadFiles;
if ("dyadFiles" in parsedBody) {
delete parsedBody.dyadFiles;
}
const requestId = parsedBody.dyadRequestId;
if ("dyadRequestId" in parsedBody) {
delete parsedBody.dyadRequestId;
}
const dyadAppId = parsedBody.dyadAppId;
if ("dyadAppId" in parsedBody) {
delete parsedBody.dyadAppId;
}
const dyadDisableFiles = parsedBody.dyadDisableFiles;
if ("dyadDisableFiles" in parsedBody) {
delete parsedBody.dyadDisableFiles;
}
const dyadMentionedApps = parsedBody.dyadMentionedApps;
if ("dyadMentionedApps" in parsedBody) {
delete parsedBody.dyadMentionedApps;
}
const dyadSmartContextMode = parsedBody.dyadSmartContextMode;
if ("dyadSmartContextMode" in parsedBody) {
delete parsedBody.dyadSmartContextMode;
} }
try { // Track and modify requestId with attempt number
// Parse the request body to manipulate it let modifiedRequestId = requestId;
const parsedBody = { if (requestId) {
...JSON.parse(init.body), const currentAttempt = (requestIdAttempts.get(requestId) || 0) + 1;
...getExtraProviderOptions( requestIdAttempts.set(requestId, currentAttempt);
options.originalProviderId, modifiedRequestId = `${requestId}:attempt-${currentAttempt}`;
options.settings, }
),
// Add files to the request if they exist
if (!dyadDisableFiles) {
parsedBody.dyad_options = {
files: dyadFiles,
versioned_files: dyadVersionedFiles,
enable_lazy_edits: options.dyadOptions.enableLazyEdits,
enable_smart_files_context:
options.dyadOptions.enableSmartFilesContext,
smart_context_mode: dyadSmartContextMode,
enable_web_search: options.dyadOptions.enableWebSearch,
app_id: dyadAppId,
}; };
const dyadVersionedFiles = parsedBody.dyadVersionedFiles; if (dyadMentionedApps?.length) {
if ("dyadVersionedFiles" in parsedBody) { parsedBody.dyad_options.mentioned_apps = dyadMentionedApps;
delete parsedBody.dyadVersionedFiles;
}
const dyadFiles = parsedBody.dyadFiles;
if ("dyadFiles" in parsedBody) {
delete parsedBody.dyadFiles;
}
const requestId = parsedBody.dyadRequestId;
if ("dyadRequestId" in parsedBody) {
delete parsedBody.dyadRequestId;
}
const dyadAppId = parsedBody.dyadAppId;
if ("dyadAppId" in parsedBody) {
delete parsedBody.dyadAppId;
}
const dyadDisableFiles = parsedBody.dyadDisableFiles;
if ("dyadDisableFiles" in parsedBody) {
delete parsedBody.dyadDisableFiles;
}
const dyadMentionedApps = parsedBody.dyadMentionedApps;
if ("dyadMentionedApps" in parsedBody) {
delete parsedBody.dyadMentionedApps;
}
const dyadSmartContextMode = parsedBody.dyadSmartContextMode;
if ("dyadSmartContextMode" in parsedBody) {
delete parsedBody.dyadSmartContextMode;
} }
}
// Track and modify requestId with attempt number // Return modified request with files included and requestId in headers
let modifiedRequestId = requestId; const modifiedInit = {
if (requestId) { ...init,
const currentAttempt = (requestIdAttempts.get(requestId) || 0) + 1; headers: {
requestIdAttempts.set(requestId, currentAttempt); ...init.headers,
modifiedRequestId = `${requestId}:attempt-${currentAttempt}`; ...(modifiedRequestId && {
} "X-Dyad-Request-Id": modifiedRequestId,
}),
},
body: JSON.stringify(parsedBody),
};
// Use the provided fetch or default fetch
return (options.fetch || fetch)(input, modifiedInit);
} catch (e) {
logger.error("Error parsing request body", e);
// If parsing fails, use original request
return (options.fetch || fetch)(input, init);
}
};
};
// Add files to the request if they exist const createChatModel = (modelId: ExampleChatModelId) => {
if (!dyadDisableFiles) { const config = {
parsedBody.dyad_options = { ...getCommonModelConfig(),
files: dyadFiles, fetch: createDyadFetch(),
versioned_files: dyadVersionedFiles, };
enable_lazy_edits: options.dyadOptions.enableLazyEdits,
enable_smart_files_context:
options.dyadOptions.enableSmartFilesContext,
smart_context_mode: dyadSmartContextMode,
enable_web_search: options.dyadOptions.enableWebSearch,
app_id: dyadAppId,
};
if (dyadMentionedApps?.length) {
parsedBody.dyad_options.mentioned_apps = dyadMentionedApps;
}
}
// Return modified request with files included and requestId in headers return new OpenAICompatibleChatLanguageModel(modelId, config);
const modifiedInit = { };
...init,
headers: {
...init.headers,
...(modifiedRequestId && {
"X-Dyad-Request-Id": modifiedRequestId,
}),
},
body: JSON.stringify(parsedBody),
};
// Use the provided fetch or default fetch const createResponsesModel = (modelId: ExampleChatModelId) => {
return (options.fetch || fetch)(input, modifiedInit); const config = {
} catch (e) { ...getCommonModelConfig(),
logger.error("Error parsing request body", e); fetch: createDyadFetch(),
// If parsing fails, use original request
return (options.fetch || fetch)(input, init);
}
},
}; };
return new OpenAICompatibleChatLanguageModel(modelId, config); return new OpenAIResponsesLanguageModel(modelId, config);
}; };
const provider = (modelId: ExampleChatModelId) => createChatModel(modelId); const provider = (modelId: ExampleChatModelId) => createChatModel(modelId);
provider.chatModel = createChatModel; provider.chatModel = createChatModel;
provider.responses = createResponsesModel;
return provider; return provider;
} }
...@@ -24,9 +24,15 @@ export function getExtraProviderOptions( ...@@ -24,9 +24,15 @@ export function getExtraProviderOptions(
return {}; return {};
} }
if (providerId === "openai") { if (providerId === "openai") {
return { if (settings.selectedChatMode === "local-agent") {
reasoning_effort: "medium", return {
}; reasoning: {
summary: "detailed",
effort: "medium",
},
};
}
return { reasoning_effort: "medium" };
} }
if (PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId)) { if (PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId)) {
const budgetTokens = getThinkingBudgetTokens(settings?.thinkingBudget); const budgetTokens = getThinkingBudgetTokens(settings?.thinkingBudget);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论