Files
moreminimore-vibe/src/lib/schemas.ts
Will Chen a8f3c97396 Turbo edits v2 (#1653)
Fixes #1222 #1646 

TODOs
- [x] description?
- [x] collect errors across all files for turbo edits
- [x] be forgiving around whitespaces
- [x] write e2e tests
- [x] do more manual testing across different models



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds Turbo Edits v2 search-replace flow with settings/UI selector,
parser/renderer, dry-run validation + fallback, proposal integration,
and comprehensive tests; updates licensing.
> 
> - **Engine/Processing**:
> - Add `dyad-search-replace` end-to-end: parsing
(`getDyadSearchReplaceTags`), markdown rendering (`DyadSearchReplace`),
and application (`applySearchReplace`) with dry-run validation and
fallback to `dyad-write`.
> - Inject Turbo Edits v2 system prompt; toggle via
`isTurboEditsV2Enabled`; disable classic lazy edits when v2 is on.
> - Include search-replace edits in proposals and full-response
processing.
> - **Settings/UI**:
> - Introduce `proLazyEditsMode` (`off`|`v1`|`v2`) and helper selectors;
update `ProModeSelector` with Turbo Edits and Smart Context selectors
(`data-testid`s).
> - **LLM/token flow**:
> - Construct system prompt conditionally; update token counting and
chat stream to validate and repair search-replace responses.
> - **Tests**:
> - Add unit tests for search-replace processor; e2e tests for Turbo
Edits v2 and options; fixtures and snapshots.
> - **Licensing/Docs**:
> - Add `src/pro/LICENSE` (FSL 1.1 ALv2 future), update root `LICENSE`
and README license section.
> - **Tooling**:
> - Update `.prettierignore`; enhance test helpers (selectors, path
normalization, snapshot filtering).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7aefa02bfae2fe22a25c7d87f3c4c326f820f1e6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-10-28 11:36:20 -07:00

375 lines
10 KiB
TypeScript

import { z } from "zod";
export const SecretSchema = z.object({
value: z.string(),
encryptionType: z.enum(["electron-safe-storage", "plaintext"]).optional(),
});
export type Secret = z.infer<typeof SecretSchema>;
/**
* Zod schema for chat summary objects returned by the get-chats IPC
*/
export const ChatSummarySchema = z.object({
id: z.number(),
appId: z.number(),
title: z.string().nullable(),
createdAt: z.date(),
});
/**
* Type derived from the ChatSummarySchema
*/
export type ChatSummary = z.infer<typeof ChatSummarySchema>;
/**
* Zod schema for an array of chat summaries
*/
export const ChatSummariesSchema = z.array(ChatSummarySchema);
/**
* Zod schema for chat search result objects returned by the search-chats IPC
*/
export const ChatSearchResultSchema = z.object({
id: z.number(),
appId: z.number(),
title: z.string().nullable(),
createdAt: z.date(),
matchedMessageContent: z.string().nullable(),
});
/**
* Type derived from the ChatSearchResultSchema
*/
export type ChatSearchResult = z.infer<typeof ChatSearchResultSchema>;
export const ChatSearchResultsSchema = z.array(ChatSearchResultSchema);
// Zod schema for app search result objects returned by the search-app IPC
export const AppSearchResultSchema = z.object({
id: z.number(),
name: z.string(),
createdAt: z.date(),
matchedChatTitle: z.string().nullable(),
matchedChatMessage: z.string().nullable(),
});
// Type derived from AppSearchResultSchema
export type AppSearchResult = z.infer<typeof AppSearchResultSchema>;
export const AppSearchResultsSchema = z.array(AppSearchResultSchema);
const providers = [
"openai",
"anthropic",
"google",
"vertex",
"auto",
"openrouter",
"ollama",
"lmstudio",
"azure",
"xai",
"bedrock",
] as const;
export const cloudProviders = providers.filter(
(provider) => provider !== "ollama" && provider !== "lmstudio",
);
/**
* Zod schema for large language model configuration
*/
export const LargeLanguageModelSchema = z.object({
name: z.string(),
provider: z.string(),
customModelId: z.number().optional(),
});
/**
* Type derived from the LargeLanguageModelSchema
*/
export type LargeLanguageModel = z.infer<typeof LargeLanguageModelSchema>;
/**
* Zod schema for provider settings
* Regular providers use only apiKey. Vertex has additional optional fields.
*/
export const RegularProviderSettingSchema = z.object({
apiKey: SecretSchema.optional(),
});
export const AzureProviderSettingSchema = z.object({
apiKey: SecretSchema.optional(),
resourceName: z.string().optional(),
});
export const VertexProviderSettingSchema = z.object({
// We make this undefined so that it makes existing callsites easier.
apiKey: z.undefined(),
projectId: z.string().optional(),
location: z.string().optional(),
serviceAccountKey: SecretSchema.optional(),
});
export const ProviderSettingSchema = z.union([
// Must use more specific type first!
// Zod uses the first type that matches.
//
// We use passthrough as a hack because Azure and Vertex
// will match together since their required fields overlap.
//
// In addition, there may be future provider settings that
// we may want to preserve (e.g. user downgrades to older version)
// so doing passthrough keeps these extra fields.
AzureProviderSettingSchema.passthrough(),
VertexProviderSettingSchema.passthrough(),
RegularProviderSettingSchema.passthrough(),
]);
/**
* Type derived from the ProviderSettingSchema
*/
export type ProviderSetting = z.infer<typeof ProviderSettingSchema>;
export type RegularProviderSetting = z.infer<
typeof RegularProviderSettingSchema
>;
export type AzureProviderSetting = z.infer<typeof AzureProviderSettingSchema>;
export type VertexProviderSetting = z.infer<typeof VertexProviderSettingSchema>;
export const RuntimeModeSchema = z.enum(["web-sandbox", "local-node", "unset"]);
export type RuntimeMode = z.infer<typeof RuntimeModeSchema>;
export const RuntimeMode2Schema = z.enum(["host", "docker"]);
export type RuntimeMode2 = z.infer<typeof RuntimeMode2Schema>;
export const ChatModeSchema = z.enum(["build", "ask", "agent"]);
export type ChatMode = z.infer<typeof ChatModeSchema>;
export const GitHubSecretsSchema = z.object({
accessToken: SecretSchema.nullable(),
});
export type GitHubSecrets = z.infer<typeof GitHubSecretsSchema>;
export const GithubUserSchema = z.object({
email: z.string(),
});
export type GithubUser = z.infer<typeof GithubUserSchema>;
export const SupabaseSchema = z.object({
accessToken: SecretSchema.optional(),
refreshToken: SecretSchema.optional(),
expiresIn: z.number().optional(),
tokenTimestamp: z.number().optional(),
});
export type Supabase = z.infer<typeof SupabaseSchema>;
export const NeonSchema = z.object({
accessToken: SecretSchema.optional(),
refreshToken: SecretSchema.optional(),
expiresIn: z.number().optional(),
tokenTimestamp: z.number().optional(),
});
export type Neon = z.infer<typeof NeonSchema>;
export const ExperimentsSchema = z.object({
// Deprecated
enableSupabaseIntegration: z.boolean().describe("DEPRECATED").optional(),
enableFileEditing: z.boolean().describe("DEPRECATED").optional(),
});
export type Experiments = z.infer<typeof ExperimentsSchema>;
export const DyadProBudgetSchema = z.object({
budgetResetAt: z.string(),
maxBudget: z.number(),
});
export type DyadProBudget = z.infer<typeof DyadProBudgetSchema>;
export const GlobPathSchema = z.object({
globPath: z.string(),
});
export type GlobPath = z.infer<typeof GlobPathSchema>;
export const AppChatContextSchema = z.object({
contextPaths: z.array(GlobPathSchema),
smartContextAutoIncludes: z.array(GlobPathSchema),
excludePaths: z.array(GlobPathSchema).optional(),
});
export type AppChatContext = z.infer<typeof AppChatContextSchema>;
export type ContextPathResult = GlobPath & {
files: number;
tokens: number;
};
export type ContextPathResults = {
contextPaths: ContextPathResult[];
smartContextAutoIncludes: ContextPathResult[];
excludePaths: ContextPathResult[];
};
export const ReleaseChannelSchema = z.enum(["stable", "beta"]);
export type ReleaseChannel = z.infer<typeof ReleaseChannelSchema>;
/**
* Zod schema for user settings
*/
export const UserSettingsSchema = z.object({
selectedModel: LargeLanguageModelSchema,
providerSettings: z.record(z.string(), ProviderSettingSchema),
githubUser: GithubUserSchema.optional(),
githubAccessToken: SecretSchema.optional(),
vercelAccessToken: SecretSchema.optional(),
supabase: SupabaseSchema.optional(),
neon: NeonSchema.optional(),
autoApproveChanges: z.boolean().optional(),
telemetryConsent: z.enum(["opted_in", "opted_out", "unset"]).optional(),
telemetryUserId: z.string().optional(),
hasRunBefore: z.boolean().optional(),
enableDyadPro: z.boolean().optional(),
experiments: ExperimentsSchema.optional(),
lastShownReleaseNotesVersion: z.string().optional(),
maxChatTurnsInContext: z.number().optional(),
thinkingBudget: z.enum(["low", "medium", "high"]).optional(),
enableProLazyEditsMode: z.boolean().optional(),
proLazyEditsMode: z.enum(["off", "v1", "v2"]).optional(),
enableProSmartFilesContextMode: z.boolean().optional(),
enableProWebSearch: z.boolean().optional(),
proSmartContextOption: z.enum(["balanced", "conservative"]).optional(),
selectedTemplateId: z.string(),
enableSupabaseWriteSqlMigration: z.boolean().optional(),
selectedChatMode: ChatModeSchema.optional(),
acceptedCommunityCode: z.boolean().optional(),
enableAutoFixProblems: z.boolean().optional(),
enableNativeGit: z.boolean().optional(),
enableAutoUpdate: z.boolean(),
releaseChannel: ReleaseChannelSchema,
runtimeMode2: RuntimeMode2Schema.optional(),
customNodePath: z.string().optional().nullable(),
////////////////////////////////
// E2E TESTING ONLY.
////////////////////////////////
isTestMode: z.boolean().optional(),
////////////////////////////////
// DEPRECATED.
////////////////////////////////
enableProSaverMode: z.boolean().optional(),
dyadProBudget: DyadProBudgetSchema.optional(),
runtimeMode: RuntimeModeSchema.optional(),
});
/**
* Type derived from the UserSettingsSchema
*/
export type UserSettings = z.infer<typeof UserSettingsSchema>;
export function isDyadProEnabled(settings: UserSettings): boolean {
return settings.enableDyadPro === true && hasDyadProKey(settings);
}
export function hasDyadProKey(settings: UserSettings): boolean {
return !!settings.providerSettings?.auto?.apiKey?.value;
}
export function isTurboEditsV2Enabled(settings: UserSettings): boolean {
return Boolean(
isDyadProEnabled(settings) &&
settings.enableProLazyEditsMode === true &&
settings.proLazyEditsMode === "v2",
);
}
// Define interfaces for the props
export interface SecurityRisk {
type: "warning" | "danger";
title: string;
description: string;
}
export interface FileChange {
name: string;
path: string;
summary: string;
type: "write" | "rename" | "delete";
isServerFunction: boolean;
}
export interface CodeProposal {
type: "code-proposal";
title: string;
securityRisks: SecurityRisk[];
filesChanged: FileChange[];
packagesAdded: string[];
sqlQueries: SqlQuery[];
}
export type SuggestedAction =
| RestartAppAction
| SummarizeInNewChatAction
| RefactorFileAction
| WriteCodeProperlyAction
| RebuildAction
| RestartAction
| RefreshAction
| KeepGoingAction;
export interface RestartAppAction {
id: "restart-app";
}
export interface SummarizeInNewChatAction {
id: "summarize-in-new-chat";
}
export interface WriteCodeProperlyAction {
id: "write-code-properly";
}
export interface RefactorFileAction {
id: "refactor-file";
path: string;
}
export interface RebuildAction {
id: "rebuild";
}
export interface RestartAction {
id: "restart";
}
export interface RefreshAction {
id: "refresh";
}
export interface KeepGoingAction {
id: "keep-going";
}
export interface ActionProposal {
type: "action-proposal";
actions: SuggestedAction[];
}
export interface TipProposal {
type: "tip-proposal";
title: string;
description: string;
}
export type Proposal = CodeProposal | ActionProposal | TipProposal;
export interface ProposalResult {
proposal: Proposal;
chatId: number;
messageId: number;
}
export interface SqlQuery {
content: string;
description?: string;
}