Custom modes

This commit is contained in:
Matt Rubens
2025-01-18 03:39:26 -05:00
parent 332245c33a
commit b8e0aa0cde
65 changed files with 3749 additions and 1531 deletions

View File

@@ -1,6 +1,6 @@
import { ExtensionContext } from "vscode"
import { ApiConfiguration } from "../../shared/api"
import { Mode } from "../prompts/types"
import { Mode } from "../../shared/modes"
import { ApiConfigMeta } from "../../shared/ExtensionMessage"
export interface ApiConfigData {

View File

@@ -0,0 +1,190 @@
import * as vscode from "vscode"
import * as path from "path"
import * as fs from "fs/promises"
import { CustomModesSettingsSchema } from "./CustomModesSchema"
import { ModeConfig } from "../../shared/modes"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual } from "../../utils/path"
export class CustomModesManager {
private disposables: vscode.Disposable[] = []
private isWriting = false
private writeQueue: Array<() => Promise<void>> = []
constructor(
private readonly context: vscode.ExtensionContext,
private readonly onUpdate: () => Promise<void>,
) {
this.watchCustomModesFile()
}
private async queueWrite(operation: () => Promise<void>): Promise<void> {
this.writeQueue.push(operation)
if (!this.isWriting) {
await this.processWriteQueue()
}
}
private async processWriteQueue(): Promise<void> {
if (this.isWriting || this.writeQueue.length === 0) {
return
}
this.isWriting = true
try {
while (this.writeQueue.length > 0) {
const operation = this.writeQueue.shift()
if (operation) {
await operation()
}
}
} finally {
this.isWriting = false
}
}
async getCustomModesFilePath(): Promise<string> {
const settingsDir = await this.ensureSettingsDirectoryExists()
const filePath = path.join(settingsDir, "cline_custom_modes.json")
const fileExists = await fileExistsAtPath(filePath)
if (!fileExists) {
await this.queueWrite(async () => {
await fs.writeFile(filePath, JSON.stringify({ customModes: [] }, null, 2))
})
}
return filePath
}
private async watchCustomModesFile(): Promise<void> {
const settingsPath = await this.getCustomModesFilePath()
this.disposables.push(
vscode.workspace.onDidSaveTextDocument(async (document) => {
if (arePathsEqual(document.uri.fsPath, settingsPath)) {
const content = await fs.readFile(settingsPath, "utf-8")
const errorMessage =
"Invalid custom modes format. Please ensure your settings follow the correct JSON format."
let config: any
try {
config = JSON.parse(content)
} catch (error) {
console.error(error)
vscode.window.showErrorMessage(errorMessage)
return
}
const result = CustomModesSettingsSchema.safeParse(config)
if (!result.success) {
vscode.window.showErrorMessage(errorMessage)
return
}
await this.context.globalState.update("customModes", result.data.customModes)
await this.onUpdate()
}
}),
)
}
async getCustomModes(): Promise<ModeConfig[]> {
const modes = await this.context.globalState.get<ModeConfig[]>("customModes")
// Always read from file to ensure we have the latest
try {
const settingsPath = await this.getCustomModesFilePath()
const content = await fs.readFile(settingsPath, "utf-8")
const settings = JSON.parse(content)
const result = CustomModesSettingsSchema.safeParse(settings)
if (result.success) {
await this.context.globalState.update("customModes", result.data.customModes)
return result.data.customModes
}
return modes ?? []
} catch (error) {
// Return empty array if there's an error reading the file
}
return modes ?? []
}
async updateCustomMode(slug: string, config: ModeConfig): Promise<void> {
try {
const settingsPath = await this.getCustomModesFilePath()
await this.queueWrite(async () => {
// Read and update file
const content = await fs.readFile(settingsPath, "utf-8")
const settings = JSON.parse(content)
const currentModes = settings.customModes || []
const updatedModes = currentModes.filter((m: ModeConfig) => m.slug !== slug)
updatedModes.push(config)
settings.customModes = updatedModes
const newContent = JSON.stringify(settings, null, 2)
// Write to file
await fs.writeFile(settingsPath, newContent)
// Update global state
await this.context.globalState.update("customModes", updatedModes)
// Notify about the update
await this.onUpdate()
})
// Success, no need for message
} catch (error) {
vscode.window.showErrorMessage(
`Failed to update custom mode: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
async deleteCustomMode(slug: string): Promise<void> {
try {
const settingsPath = await this.getCustomModesFilePath()
await this.queueWrite(async () => {
const content = await fs.readFile(settingsPath, "utf-8")
const settings = JSON.parse(content)
settings.customModes = (settings.customModes || []).filter((m: ModeConfig) => m.slug !== slug)
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2))
await this.context.globalState.update("customModes", settings.customModes)
await this.onUpdate()
})
} catch (error) {
vscode.window.showErrorMessage(
`Failed to delete custom mode: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
private async ensureSettingsDirectoryExists(): Promise<string> {
const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings")
await fs.mkdir(settingsDir, { recursive: true })
return settingsDir
}
/**
* Delete the custom modes file and reset to default state
*/
async resetCustomModes(): Promise<void> {
try {
const filePath = await this.getCustomModesFilePath()
await fs.writeFile(filePath, JSON.stringify({ customModes: [] }, null, 2))
await this.context.globalState.update("customModes", [])
await this.onUpdate()
} catch (error) {
vscode.window.showErrorMessage(
`Failed to reset custom modes: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
dispose(): void {
for (const disposable of this.disposables) {
disposable.dispose()
}
this.disposables = []
}
}

View File

@@ -0,0 +1,60 @@
import { z } from "zod"
import { ModeConfig } from "../../shared/modes"
import { TOOL_GROUPS, ToolGroup } from "../../shared/tool-groups"
// Create a schema for valid tool groups using the keys of TOOL_GROUPS
const ToolGroupSchema = z.enum(Object.keys(TOOL_GROUPS) as [ToolGroup, ...ToolGroup[]])
// Schema for array of groups
const GroupsArraySchema = z
.array(ToolGroupSchema)
.min(1, "At least one tool group is required")
.refine(
(groups) => {
const seen = new Set()
return groups.every((group) => {
if (seen.has(group)) return false
seen.add(group)
return true
})
},
{ message: "Duplicate groups are not allowed" },
)
// Schema for mode configuration
export const CustomModeSchema = z.object({
slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"),
name: z.string().min(1, "Name is required"),
roleDefinition: z.string().min(1, "Role definition is required"),
customInstructions: z.string().optional(),
groups: GroupsArraySchema,
}) satisfies z.ZodType<ModeConfig>
// Schema for the entire custom modes settings file
export const CustomModesSettingsSchema = z.object({
customModes: z.array(CustomModeSchema).refine(
(modes) => {
const slugs = new Set()
return modes.every((mode) => {
if (slugs.has(mode.slug)) {
return false
}
slugs.add(mode.slug)
return true
})
},
{
message: "Duplicate mode slugs are not allowed",
},
),
})
export type CustomModesSettings = z.infer<typeof CustomModesSettingsSchema>
/**
* Validates a custom mode configuration against the schema
* @throws {z.ZodError} if validation fails
*/
export function validateCustomMode(mode: unknown): asserts mode is ModeConfig {
CustomModeSchema.parse(mode)
}

View File

@@ -0,0 +1,245 @@
import { ModeConfig } from "../../../shared/modes"
import { CustomModesManager } from "../CustomModesManager"
import * as vscode from "vscode"
import * as fs from "fs/promises"
import * as path from "path"
// Mock dependencies
jest.mock("vscode")
jest.mock("fs/promises")
jest.mock("../../../utils/fs", () => ({
fileExistsAtPath: jest.fn().mockResolvedValue(false),
}))
describe("CustomModesManager", () => {
let manager: CustomModesManager
let mockContext: vscode.ExtensionContext
let mockOnUpdate: jest.Mock
let mockStoragePath: string
beforeEach(() => {
// Reset mocks
jest.clearAllMocks()
// Mock storage path
mockStoragePath = "/test/storage/path"
// Mock context
mockContext = {
globalStorageUri: { fsPath: mockStoragePath },
globalState: {
get: jest.fn().mockResolvedValue([]),
update: jest.fn().mockResolvedValue(undefined),
},
} as unknown as vscode.ExtensionContext
// Mock onUpdate callback
mockOnUpdate = jest.fn().mockResolvedValue(undefined)
// Mock fs.mkdir to do nothing
;(fs.mkdir as jest.Mock).mockResolvedValue(undefined)
// Create manager instance
manager = new CustomModesManager(mockContext, mockOnUpdate)
})
describe("Mode Configuration Validation", () => {
test("validates valid custom mode configuration", async () => {
const validMode = {
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
// Mock file read/write operations
;(fs.readFile as jest.Mock).mockResolvedValue(JSON.stringify({ customModes: [] }))
;(fs.writeFile as jest.Mock).mockResolvedValue(undefined)
await manager.updateCustomMode(validMode.slug, validMode)
// Verify file was written with the new mode
expect(fs.writeFile).toHaveBeenCalledWith(
expect.stringContaining("cline_custom_modes.json"),
expect.stringContaining(validMode.name),
)
// Verify global state was updated
expect(mockContext.globalState.update).toHaveBeenCalledWith(
"customModes",
expect.arrayContaining([validMode]),
)
// Verify onUpdate was called
expect(mockOnUpdate).toHaveBeenCalled()
})
test("handles file read errors gracefully", async () => {
// Mock fs.readFile to throw error
;(fs.readFile as jest.Mock).mockRejectedValueOnce(new Error("Test error"))
const modes = await manager.getCustomModes()
// Should return empty array on error
expect(modes).toEqual([])
})
test("handles file write errors gracefully", async () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
// Mock fs.writeFile to throw error
;(fs.writeFile as jest.Mock).mockRejectedValueOnce(new Error("Write error"))
const mockShowError = jest.fn()
;(vscode.window.showErrorMessage as jest.Mock) = mockShowError
await manager.updateCustomMode(validMode.slug, validMode)
// Should show error message
expect(mockShowError).toHaveBeenCalledWith(expect.stringContaining("Write error"))
})
})
describe("File Operations", () => {
test("creates settings directory if it doesn't exist", async () => {
const configPath = path.join(mockStoragePath, "settings", "cline_custom_modes.json")
await manager.getCustomModesFilePath()
expect(fs.mkdir).toHaveBeenCalledWith(path.dirname(configPath), { recursive: true })
})
test("creates default config if file doesn't exist", async () => {
const configPath = path.join(mockStoragePath, "settings", "cline_custom_modes.json")
await manager.getCustomModesFilePath()
expect(fs.writeFile).toHaveBeenCalledWith(configPath, JSON.stringify({ customModes: [] }, null, 2))
})
test("watches file for changes", async () => {
// Mock file path resolution
const configPath = path.join(mockStoragePath, "settings", "cline_custom_modes.json")
;(fs.readFile as jest.Mock).mockResolvedValue(JSON.stringify({ customModes: [] }))
// Create manager and wait for initialization
const manager = new CustomModesManager(mockContext, mockOnUpdate)
await manager.getCustomModesFilePath() // This ensures watchCustomModesFile has completed
// Get the registered callback
const registerCall = (vscode.workspace.onDidSaveTextDocument as jest.Mock).mock.calls[0]
expect(registerCall).toBeDefined()
const [callback] = registerCall
// Simulate file save event
const mockDocument = {
uri: { fsPath: configPath },
}
await callback(mockDocument)
// Verify file was processed
expect(fs.readFile).toHaveBeenCalledWith(configPath, "utf-8")
expect(mockContext.globalState.update).toHaveBeenCalled()
expect(mockOnUpdate).toHaveBeenCalled()
// Verify file content was processed
expect(fs.readFile).toHaveBeenCalled()
})
})
describe("Mode Operations", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
beforeEach(() => {
// Mock fs.readFile to return empty config
;(fs.readFile as jest.Mock).mockResolvedValue(JSON.stringify({ customModes: [] }))
})
test("adds new custom mode", async () => {
await manager.updateCustomMode(validMode.slug, validMode)
expect(fs.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining(validMode.name))
expect(mockOnUpdate).toHaveBeenCalled()
})
test("updates existing custom mode", async () => {
// Mock existing mode
;(fs.readFile as jest.Mock).mockResolvedValue(
JSON.stringify({
customModes: [validMode],
}),
)
const updatedMode = {
...validMode,
name: "Updated Name",
}
await manager.updateCustomMode(validMode.slug, updatedMode)
expect(fs.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining("Updated Name"))
expect(mockOnUpdate).toHaveBeenCalled()
})
test("deletes custom mode", async () => {
// Mock existing mode
;(fs.readFile as jest.Mock).mockResolvedValue(
JSON.stringify({
customModes: [validMode],
}),
)
await manager.deleteCustomMode(validMode.slug)
expect(fs.writeFile).toHaveBeenCalledWith(expect.any(String), expect.not.stringContaining(validMode.name))
expect(mockOnUpdate).toHaveBeenCalled()
})
test("queues write operations", async () => {
const mode1 = {
...validMode,
name: "Mode 1",
}
const mode2 = {
...validMode,
slug: "mode-2",
name: "Mode 2",
}
// Mock initial empty state and track writes
let currentModes: ModeConfig[] = []
;(fs.readFile as jest.Mock).mockImplementation(() => JSON.stringify({ customModes: currentModes }))
;(fs.writeFile as jest.Mock).mockImplementation(async (path, content) => {
const data = JSON.parse(content)
currentModes = data.customModes
return Promise.resolve()
})
// Start both updates simultaneously
await Promise.all([
manager.updateCustomMode(mode1.slug, mode1),
manager.updateCustomMode(mode2.slug, mode2),
])
// Verify final state
expect(currentModes).toHaveLength(2)
expect(currentModes.map((m) => m.name)).toContain("Mode 1")
expect(currentModes.map((m) => m.name)).toContain("Mode 2")
// Verify write was called with both modes
const lastWriteCall = (fs.writeFile as jest.Mock).mock.calls.pop()
const finalContent = JSON.parse(lastWriteCall[1])
expect(finalContent.customModes).toHaveLength(2)
expect(finalContent.customModes.map((m: ModeConfig) => m.name)).toContain("Mode 1")
expect(finalContent.customModes.map((m: ModeConfig) => m.name)).toContain("Mode 2")
})
})
})

View File

@@ -0,0 +1,122 @@
import { validateCustomMode } from "../CustomModesSchema"
import { ModeConfig } from "../../../shared/modes"
import { ZodError } from "zod"
describe("CustomModesSchema", () => {
describe("validateCustomMode", () => {
test("accepts valid mode configuration", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(validMode)).not.toThrow()
})
test("accepts mode with multiple groups", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read", "edit", "browser"] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(validMode)).not.toThrow()
})
test("accepts mode with optional customInstructions", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
customInstructions: "Custom instructions",
groups: ["read"] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(validMode)).not.toThrow()
})
test("rejects missing required fields", () => {
const invalidModes = [
{}, // All fields missing
{ name: "Test" }, // Missing most fields
{
name: "Test",
roleDefinition: "Role",
}, // Missing slug and groups
]
invalidModes.forEach((invalidMode) => {
expect(() => validateCustomMode(invalidMode)).toThrow(ZodError)
})
})
test("rejects invalid slug format", () => {
const invalidMode = {
slug: "not@a@valid@slug",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies Omit<ModeConfig, "slug"> & { slug: string }
expect(() => validateCustomMode(invalidMode)).toThrow(ZodError)
expect(() => validateCustomMode(invalidMode)).toThrow("Slug must contain only letters numbers and dashes")
})
test("rejects empty strings in required fields", () => {
const emptyNameMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
const emptyRoleMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "",
groups: ["read"] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(emptyNameMode)).toThrow("Name is required")
expect(() => validateCustomMode(emptyRoleMode)).toThrow("Role definition is required")
})
test("rejects invalid group configurations", () => {
const invalidGroupMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["not-a-valid-group"] as any,
}
expect(() => validateCustomMode(invalidGroupMode)).toThrow(ZodError)
})
test("rejects empty groups array", () => {
const invalidMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: [] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(invalidMode)).toThrow("At least one tool group is required")
})
test("handles null and undefined gracefully", () => {
expect(() => validateCustomMode(null)).toThrow(ZodError)
expect(() => validateCustomMode(undefined)).toThrow(ZodError)
})
test("rejects non-object inputs", () => {
const invalidInputs = [42, "string", true, [], () => {}]
invalidInputs.forEach((input) => {
expect(() => validateCustomMode(input)).toThrow(ZodError)
})
})
})
})

View File

@@ -0,0 +1,169 @@
import { CustomModesSettingsSchema } from "../CustomModesSchema"
import { ModeConfig } from "../../../shared/modes"
import { ZodError } from "zod"
describe("CustomModesSettings", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
describe("schema validation", () => {
test("accepts valid settings", () => {
const validSettings = {
customModes: [validMode],
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
test("accepts empty custom modes array", () => {
const validSettings = {
customModes: [],
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
test("accepts multiple custom modes", () => {
const validSettings = {
customModes: [
validMode,
{
...validMode,
slug: "987fcdeb-51a2-43e7-89ab-cdef01234567",
name: "Another Mode",
},
],
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
test("rejects missing customModes field", () => {
const invalidSettings = {} as any
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
})
test("rejects invalid mode in array", () => {
const invalidSettings = {
customModes: [
validMode,
{
...validMode,
slug: "not@a@valid@slug", // Invalid slug
},
],
}
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
}).toThrow("Slug must contain only letters numbers and dashes")
})
test("rejects non-array customModes", () => {
const invalidSettings = {
customModes: "not an array",
}
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
})
test("rejects null or undefined", () => {
expect(() => {
CustomModesSettingsSchema.parse(null)
}).toThrow(ZodError)
expect(() => {
CustomModesSettingsSchema.parse(undefined)
}).toThrow(ZodError)
})
test("rejects duplicate mode slugs", () => {
const duplicateSettings = {
customModes: [
validMode,
{ ...validMode }, // Same slug
],
}
expect(() => {
CustomModesSettingsSchema.parse(duplicateSettings)
}).toThrow("Duplicate mode slugs are not allowed")
})
test("rejects invalid group configurations in modes", () => {
const invalidSettings = {
customModes: [
{
...validMode,
groups: ["invalid_group"] as any,
},
],
}
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
})
test("handles multiple groups", () => {
const validSettings = {
customModes: [
{
...validMode,
groups: ["read", "edit", "browser"] as const,
},
],
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
})
describe("type inference", () => {
test("inferred type includes all required fields", () => {
const settings = {
customModes: [validMode],
}
// TypeScript compilation will fail if the type is incorrect
expect(settings.customModes[0].slug).toBeDefined()
expect(settings.customModes[0].name).toBeDefined()
expect(settings.customModes[0].roleDefinition).toBeDefined()
expect(settings.customModes[0].groups).toBeDefined()
})
test("inferred type allows optional fields", () => {
const settings = {
customModes: [
{
...validMode,
customInstructions: "Optional instructions",
},
],
}
// TypeScript compilation will fail if the type is incorrect
expect(settings.customModes[0].customInstructions).toBeDefined()
})
})
})

View File

@@ -0,0 +1,90 @@
import { CustomModeSchema } from "../CustomModesSchema"
import { ModeConfig } from "../../../shared/modes"
describe("GroupConfigSchema", () => {
const validBaseMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
}
describe("group format validation", () => {
test("accepts single group", () => {
const mode = {
...validBaseMode,
groups: ["read"] as const,
} satisfies ModeConfig
expect(() => CustomModeSchema.parse(mode)).not.toThrow()
})
test("accepts multiple groups", () => {
const mode = {
...validBaseMode,
groups: ["read", "edit", "browser"] as const,
} satisfies ModeConfig
expect(() => CustomModeSchema.parse(mode)).not.toThrow()
})
test("accepts all available groups", () => {
const mode = {
...validBaseMode,
groups: ["read", "edit", "browser", "command", "mcp"] as const,
} satisfies ModeConfig
expect(() => CustomModeSchema.parse(mode)).not.toThrow()
})
test("rejects non-array group format", () => {
const mode = {
...validBaseMode,
groups: "not-an-array" as any,
}
expect(() => CustomModeSchema.parse(mode)).toThrow()
})
test("rejects empty groups array", () => {
const mode = {
...validBaseMode,
groups: [] as const,
} satisfies ModeConfig
expect(() => CustomModeSchema.parse(mode)).toThrow("At least one tool group is required")
})
test("rejects invalid group names", () => {
const mode = {
...validBaseMode,
groups: ["invalid_group"] as any,
}
expect(() => CustomModeSchema.parse(mode)).toThrow()
})
test("rejects duplicate groups", () => {
const mode = {
...validBaseMode,
groups: ["read", "read"] as any,
}
expect(() => CustomModeSchema.parse(mode)).toThrow("Duplicate groups are not allowed")
})
test("rejects null or undefined groups", () => {
const modeWithNull = {
...validBaseMode,
groups: null as any,
}
const modeWithUndefined = {
...validBaseMode,
groups: undefined as any,
}
expect(() => CustomModeSchema.parse(modeWithNull)).toThrow()
expect(() => CustomModeSchema.parse(modeWithUndefined)).toThrow()
})
})
})