Merge main

This commit is contained in:
cte 2025-03-28 00:42:16 -07:00
commit 3950949058
45 changed files with 2180 additions and 1750 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Remove redundant zod schemas

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Automatically generate .d.ts from zod schemas

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Add config getters to RooCodeAPI

View file

@ -12,4 +12,12 @@ else
npx_cmd="npx"
fi
"$npx_cmd" lint-staged
npm run generate-types
if [ -n "$(git diff --name-only src/exports/roo-code.d.ts)" ]; then
echo "Error: There are unstaged changes to roo-code.d.ts after running 'npm run generate-types'."
echo "Please review and stage the changes before committing."
exit 1
fi
"$npx_cmd" lint-staged

View file

@ -9,7 +9,7 @@ type WaitForOptions = {
export const waitFor = (
condition: (() => Promise<boolean>) | (() => boolean),
{ timeout = 30_000, interval = 250 }: WaitForOptions = {},
{ timeout = 60_000, interval = 250 }: WaitForOptions = {},
) => {
let timeoutId: NodeJS.Timeout | undefined = undefined

View file

@ -348,10 +348,12 @@
"clean:extension": "rimraf bin dist out",
"clean:webview": "cd webview-ui && npm run clean",
"clean:e2e": "cd e2e && npm run clean",
"clean:benchmark": "cd benchmark && npm run clean",
"vscode-test": "npm-run-all -l -p vscode-test:*",
"vscode-test:extension": "tsc -p . --outDir out && node esbuild.js",
"vscode-test:webview": "cd webview-ui && npm run build",
"update-contributors": "node scripts/update-contributors.js"
"update-contributors": "node scripts/update-contributors.js",
"generate-types": "tsx scripts/generate-types.mts"
},
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",

View file

@ -0,0 +1,26 @@
import fs from "fs/promises"
import { zodToTs, createTypeAlias, printNode } from "zod-to-ts"
import { $ } from "execa"
import { typeDefinitions } from "../src/schemas"
async function main() {
const types: string[] = [
"// This file is automatically generated by running `npm run generate-types`\n// Do not edit it directly.",
]
for (const { schema, identifier } of typeDefinitions) {
types.push(printNode(createTypeAlias(zodToTs(schema, identifier).node, identifier)))
types.push(`export type { ${identifier} }`)
}
await fs.writeFile("src/exports/types.ts", types.join("\n\n"))
await $`npx tsup src/exports/interface.ts --dts-only -d out`
await fs.copyFile('out/interface.d.ts', 'src/exports/roo-code.d.ts')
await $`npx prettier --write src/exports/types.ts src/exports/roo-code.d.ts`
}
main()

View file

@ -1,14 +1,20 @@
function pWaitFor(condition, options = {}) {
return new Promise((resolve, reject) => {
let timeout
const interval = setInterval(() => {
if (condition()) {
if (timeout) {
clearTimeout(timeout)
}
clearInterval(interval)
resolve()
}
}, options.interval || 20)
if (options.timeout) {
setTimeout(() => {
timeout = setTimeout(() => {
clearInterval(interval)
reject(new Error("Timed out"))
}, options.timeout)

View file

@ -11,9 +11,8 @@ import pWaitFor from "p-wait-for"
import getFolderSize from "get-folder-size"
import { serializeError } from "serialize-error"
import * as vscode from "vscode"
import { isPathOutsideWorkspace } from "../utils/pathUtils"
import { TokenUsage } from "../exports/roo-code"
import { TokenUsage } from "../schemas"
import { ApiHandler, buildApiHandler } from "../api"
import { ApiStream } from "../api/transform/stream"
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider"
@ -65,6 +64,7 @@ import { defaultModeSlug, getModeBySlug, getFullModeDetails } from "../shared/mo
import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments"
import { calculateApiCostAnthropic } from "../utils/cost"
import { fileExistsAtPath } from "../utils/fs"
import { isPathOutsideWorkspace } from "../utils/pathUtils"
import { arePathsEqual, getReadablePath } from "../utils/path"
import { parseMentions } from "./mentions"
import { RooIgnoreController } from "./ignore/RooIgnoreController"
@ -123,10 +123,7 @@ export type ClineOptions = {
export class Cline extends EventEmitter<ClineEvents> {
readonly taskId: string
readonly instanceId: string
get cwd() {
return getWorkspacePath(path.join(os.homedir(), "Desktop"))
}
// Subtasks
readonly rootTask: Cline | undefined = undefined
readonly parentTask: Cline | undefined = undefined
readonly taskNumber: number
@ -268,6 +265,10 @@ export class Cline extends EventEmitter<ClineEvents> {
return [instance, promise]
}
get cwd() {
return getWorkspacePath(path.join(os.homedir(), "Desktop"))
}
// Add method to update diffStrategy
async updateDiffStrategy(experimentalDiffStrategy?: boolean, multiSearchReplaceDiffStrategy?: boolean) {
// If not provided, get from current state
@ -334,6 +335,7 @@ export class Cline extends EventEmitter<ClineEvents> {
private async getSavedClineMessages(): Promise<ClineMessage[]> {
const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.uiMessages)
if (await fileExistsAtPath(filePath)) {
return JSON.parse(await fs.readFile(filePath, "utf8"))
} else {
@ -1222,11 +1224,12 @@ export class Cline extends EventEmitter<ClineEvents> {
}
return { role, content }
})
const stream = this.api.createMessage(systemPrompt, cleanConversationHistory)
const iterator = stream[Symbol.asyncIterator]()
try {
// awaiting first chunk to see if it will throw an error
// Awaiting first chunk to see if it will throw an error.
this.isWaitingForFirstChunk = true
const firstChunk = await iterator.next()
yield firstChunk.value
@ -3392,6 +3395,7 @@ export class Cline extends EventEmitter<ClineEvents> {
? `This may indicate a failure in his thought process or inability to use a tool properly, which can be mitigated with some user guidance (e.g. "Try breaking down the task into smaller steps").`
: "Roo Code uses complex prompts and iterative task execution that may be challenging for less capable models. For best results, it's recommended to use Claude 3.7 Sonnet for its advanced agentic coding capabilities.",
)
if (response === "messageResponse") {
userContent.push(
...[
@ -3455,9 +3459,11 @@ export class Cline extends EventEmitter<ClineEvents> {
// since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message
const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
this.clineMessages[lastApiReqIndex].text = JSON.stringify({
request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
} satisfies ClineApiReqInfo)
await this.saveClineMessages()
await this.providerRef.deref()?.postStateToWebview()
@ -3499,6 +3505,7 @@ export class Cline extends EventEmitter<ClineEvents> {
// if last message is a partial we need to update and save it
const lastMessage = this.clineMessages.at(-1)
if (lastMessage && lastMessage.partial) {
// lastMessage.ts = Date.now() DO NOT update ts since it is used as a key for virtuoso list
lastMessage.partial = false
@ -3544,7 +3551,10 @@ export class Cline extends EventEmitter<ClineEvents> {
this.presentAssistantMessageHasPendingUpdates = false
await this.diffViewProvider.reset()
const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
// Yields only if the first chunk is successful, otherwise will
// allow the user to retry the request (most likely due to rate
// limit error, which gets thrown on the first chunk).
const stream = this.attemptApiRequest(previousApiReqIndex)
let assistantMessage = ""
let reasoningMessage = ""
this.isStreaming = true
@ -3552,9 +3562,10 @@ export class Cline extends EventEmitter<ClineEvents> {
try {
for await (const chunk of stream) {
if (!chunk) {
// Sometimes chunk is undefined, no idea that can cause it, but this workaround seems to fix it
// Sometimes chunk is undefined, no idea that can cause it, but this workaround seems to fix it.
continue
}
switch (chunk.type) {
case "reasoning":
reasoningMessage += chunk.text
@ -3610,11 +3621,14 @@ export class Cline extends EventEmitter<ClineEvents> {
// abandoned happens when extension is no longer waiting for the cline instance to finish aborting (error is thrown here when any function in the for loop throws due to this.abort)
if (!this.abandoned) {
this.abortTask() // if the stream failed, there's various states the task could be in (i.e. could have streamed some tools the user may have executed), so we just resort to replicating a cancel task
await abortStream(
"streaming_failed",
error.message ?? JSON.stringify(serializeError(error), null, 2),
)
const history = await this.providerRef.deref()?.getTaskWithId(this.taskId)
if (history) {
await this.providerRef.deref()?.initClineWithHistoryItem(history.historyItem)
// await this.providerRef.deref()?.postStateToWebview()
@ -4092,7 +4106,9 @@ export class Cline extends EventEmitter<ClineEvents> {
})
service.initShadowGit().catch((err) => {
log("[Cline#initializeCheckpoints] caught unexpected error in initShadowGit, disabling checkpoints")
log(
`[Cline#initializeCheckpoints] caught unexpected error in initShadowGit, disabling checkpoints (${err.message})`,
)
console.error(err)
this.enableCheckpoints = false
})

View file

@ -1,67 +1,21 @@
// npx jest src/core/__tests__/Cline.test.ts
import * as os from "os"
import * as path from "path"
import pWaitFor from "p-wait-for"
import * as vscode from "vscode"
import { Anthropic } from "@anthropic-ai/sdk"
import { GlobalState } from "../../schemas"
import { Cline } from "../Cline"
import { ClineProvider } from "../webview/ClineProvider"
import { ApiConfiguration, ModelInfo } from "../../shared/api"
import { ApiStreamChunk } from "../../api/transform/stream"
import { Anthropic } from "@anthropic-ai/sdk"
import * as vscode from "vscode"
import * as os from "os"
import * as path from "path"
// Mock RooIgnoreController
jest.mock("../ignore/RooIgnoreController")
// Mock all MCP-related modules
jest.mock(
"@modelcontextprotocol/sdk/types.js",
() => ({
CallToolResultSchema: {},
ListResourcesResultSchema: {},
ListResourceTemplatesResultSchema: {},
ListToolsResultSchema: {},
ReadResourceResultSchema: {},
ErrorCode: {
InvalidRequest: "InvalidRequest",
MethodNotFound: "MethodNotFound",
InternalError: "InternalError",
},
McpError: class McpError extends Error {
code: string
constructor(code: string, message: string) {
super(message)
this.code = code
this.name = "McpError"
}
},
}),
{ virtual: true },
)
jest.mock(
"@modelcontextprotocol/sdk/client/index.js",
() => ({
Client: jest.fn().mockImplementation(() => ({
connect: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
listTools: jest.fn().mockResolvedValue({ tools: [] }),
callTool: jest.fn().mockResolvedValue({ content: [] }),
})),
}),
{ virtual: true },
)
jest.mock(
"@modelcontextprotocol/sdk/client/stdio.js",
() => ({
StdioClientTransport: jest.fn().mockImplementation(() => ({
connect: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
})),
}),
{ virtual: true },
)
// Mock fileExistsAtPath
jest.mock("../../utils/fs", () => ({
fileExistsAtPath: jest.fn().mockImplementation((filePath) => {
@ -174,6 +128,7 @@ jest.mock("vscode", () => {
stat: jest.fn().mockResolvedValue({ type: 1 }), // FileType.File = 1
},
onDidSaveTextDocument: jest.fn(() => mockDisposable),
getConfiguration: jest.fn(() => ({ get: (key: string, defaultValue: any) => defaultValue })),
},
env: {
uriScheme: "vscode",
@ -193,40 +148,6 @@ jest.mock("p-wait-for", () => ({
default: jest.fn().mockImplementation(async () => Promise.resolve()),
}))
jest.mock("delay", () => ({
__esModule: true,
default: jest.fn().mockImplementation(async () => Promise.resolve()),
}))
jest.mock("serialize-error", () => ({
__esModule: true,
serializeError: jest.fn().mockImplementation((error) => ({
name: error.name,
message: error.message,
stack: error.stack,
})),
}))
jest.mock("strip-ansi", () => ({
__esModule: true,
default: jest.fn().mockImplementation((str) => str.replace(/\u001B\[\d+m/g, "")),
}))
jest.mock("globby", () => ({
__esModule: true,
globby: jest.fn().mockImplementation(async () => []),
}))
jest.mock("os-name", () => ({
__esModule: true,
default: jest.fn().mockReturnValue("Mock OS Name"),
}))
jest.mock("default-shell", () => ({
__esModule: true,
default: "/bin/bash", // Mock default shell path
}))
describe("Cline", () => {
let mockProvider: jest.Mocked<ClineProvider>
let mockApiConfig: ApiConfiguration
@ -238,9 +159,10 @@ describe("Cline", () => {
const storageUri = {
fsPath: path.join(os.tmpdir(), "test-storage"),
}
mockExtensionContext = {
globalState: {
get: jest.fn().mockImplementation((key) => {
get: jest.fn().mockImplementation((key: keyof GlobalState) => {
if (key === "taskHistory") {
return [
{
@ -256,6 +178,7 @@ describe("Cline", () => {
},
]
}
return undefined
}),
update: jest.fn().mockImplementation((key, value) => Promise.resolve()),
@ -336,80 +259,69 @@ describe("Cline", () => {
describe("constructor", () => {
it("should respect provided settings", async () => {
const [cline, task] = Cline.create({
const cline = new Cline({
provider: mockProvider,
apiConfiguration: mockApiConfig,
customInstructions: "custom instructions",
fuzzyMatchThreshold: 0.95,
task: "test task",
startTask: false,
})
expect(cline.customInstructions).toBe("custom instructions")
expect(cline.diffEnabled).toBe(false)
await cline.abortTask(true)
await task.catch(() => {})
})
it("should use default fuzzy match threshold when not provided", async () => {
const [cline, task] = await Cline.create({
const cline = new Cline({
provider: mockProvider,
apiConfiguration: mockApiConfig,
customInstructions: "custom instructions",
enableDiff: true,
fuzzyMatchThreshold: 0.95,
task: "test task",
startTask: false,
})
expect(cline.diffEnabled).toBe(true)
// The diff strategy should be created with default threshold (1.0)
expect(cline.diffStrategy).toBeDefined()
await cline.abortTask(true)
await task.catch(() => {})
// The diff strategy should be created with default threshold (1.0).
expect(cline.diffStrategy).toBeDefined()
})
it("should use provided fuzzy match threshold", async () => {
const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy")
const [cline, task] = await Cline.create({
const cline = new Cline({
provider: mockProvider,
apiConfiguration: mockApiConfig,
customInstructions: "custom instructions",
enableDiff: true,
fuzzyMatchThreshold: 0.9,
task: "test task",
startTask: false,
})
expect(cline.diffEnabled).toBe(true)
expect(cline.diffStrategy).toBeDefined()
expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 0.9, false, false)
getDiffStrategySpy.mockRestore()
await cline.abortTask(true)
await task.catch(() => {})
})
it("should pass default threshold to diff strategy when not provided", async () => {
const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy")
const [cline, task] = Cline.create({
const cline = new Cline({
provider: mockProvider,
apiConfiguration: mockApiConfig,
customInstructions: "custom instructions",
enableDiff: true,
task: "test task",
startTask: false,
})
expect(cline.diffEnabled).toBe(true)
expect(cline.diffStrategy).toBeDefined()
expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 1.0, false, false)
getDiffStrategySpy.mockRestore()
await cline.abortTask(true)
await task.catch(() => {})
})
it("should require either task or historyItem", () => {
@ -464,22 +376,20 @@ describe("Cline", () => {
})
it("should include timezone information in environment details", async () => {
const [cline, task] = Cline.create({
const cline = new Cline({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "test task",
startTask: false,
})
const details = await cline["getEnvironmentDetails"](false)
// Verify timezone information is present and formatted correctly
// Verify timezone information is present and formatted correctly.
expect(details).toContain("America/Los_Angeles")
expect(details).toMatch(/UTC-7:00/) // Fixed offset for America/Los_Angeles
expect(details).toMatch(/UTC-7:00/) // Fixed offset for America/Los_Angeles.
expect(details).toContain("# Current Time")
expect(details).toMatch(/1\/1\/2024.*5:00:00 AM.*\(America\/Los_Angeles, UTC-7:00\)/) // Full time string format
await cline.abortTask(true)
await task.catch(() => {})
expect(details).toMatch(/1\/1\/2024.*5:00:00 AM.*\(America\/Los_Angeles, UTC-7:00\)/) // Full time string format.
})
describe("API conversation handling", () => {
@ -493,24 +403,22 @@ describe("Cline", () => {
cline.abandoned = true
await task
// Mock the API's createMessage method to capture the conversation history
const createMessageSpy = jest.fn()
// Set up mock stream
// Set up mock stream.
const mockStreamForClean = (async function* () {
yield { type: "text", text: "test response" }
})()
// Set up spy
// Set up spy.
const cleanMessageSpy = jest.fn().mockReturnValue(mockStreamForClean)
jest.spyOn(cline.api, "createMessage").mockImplementation(cleanMessageSpy)
// Mock getEnvironmentDetails to return empty details
// Mock getEnvironmentDetails to return empty details.
jest.spyOn(cline as any, "getEnvironmentDetails").mockResolvedValue("")
// Mock loadContext to return unmodified content
// Mock loadContext to return unmodified content.
jest.spyOn(cline as any, "loadContext").mockImplementation(async (content) => [content, ""])
// Add test message to conversation history
// Add test message to conversation history.
cline.apiConversationHistory = [
{
role: "user" as const,
@ -533,6 +441,7 @@ describe("Cline", () => {
ts: Date.now(),
extraProp: "should be removed",
}
cline.apiConversationHistory = [messageWithExtra]
// Trigger an API request

View file

@ -1,6 +1,7 @@
import { Mode, isToolAllowedForMode, getModeConfig, modes } from "../../shared/modes"
import { validateToolUse } from "../mode-validator"
import { isToolAllowedForMode, getModeConfig, modes, ModeConfig } from "../../shared/modes"
import { TOOL_GROUPS } from "../../shared/tool-groups"
import { validateToolUse } from "../mode-validator"
const [codeMode, architectMode, askMode] = modes.map((mode) => mode.slug)
describe("mode-validator", () => {
@ -49,7 +50,7 @@ describe("mode-validator", () => {
describe("custom modes", () => {
it("allows tools from custom mode configuration", () => {
const customModes = [
const customModes: ModeConfig[] = [
{
slug: "custom-mode",
name: "Custom Mode",
@ -65,7 +66,7 @@ describe("mode-validator", () => {
})
it("allows custom mode to override built-in mode", () => {
const customModes = [
const customModes: ModeConfig[] = [
{
slug: codeMode,
name: "Custom Code Mode",
@ -80,7 +81,7 @@ describe("mode-validator", () => {
})
it("respects tool requirements in custom modes", () => {
const customModes = [
const customModes: ModeConfig[] = [
{
slug: "custom-mode",
name: "Custom Mode",

View file

@ -1,25 +1,27 @@
import * as vscode from "vscode"
import { logger } from "../../utils/logging"
import type {
ProviderSettings,
RooCodeSettings,
RooCodeSettingsKey,
GlobalStateKey,
GlobalState,
SecretStateKey,
SecretState,
GlobalSettings,
} from "../../exports/roo-code"
import {
PROVIDER_SETTINGS_KEYS,
GLOBAL_STATE_KEYS,
SECRET_STATE_KEYS,
isSecretStateKey,
isPassThroughStateKey,
globalSettingsSchema,
ProviderSettings,
providerSettingsSchema,
} from "../../shared/globalState"
GlobalSettings,
globalSettingsSchema,
RooCodeSettings,
SECRET_STATE_KEYS,
SecretState,
isSecretStateKey,
GLOBAL_STATE_KEYS,
GlobalState,
} from "../../schemas"
import { logger } from "../../utils/logging"
type GlobalStateKey = keyof GlobalState
type SecretStateKey = keyof SecretState
type RooCodeSettingsKey = keyof RooCodeSettings
const PASS_THROUGH_STATE_KEYS = ["taskHistory"]
export const isPassThroughStateKey = (key: string) => PASS_THROUGH_STATE_KEYS.includes(key)
const globalSettingsExportSchema = globalSettingsSchema.omit({
taskHistory: true,

View file

@ -1,7 +1,7 @@
import * as vscode from "vscode"
import * as path from "path"
import * as fs from "fs/promises"
import { CustomModesSettingsSchema } from "./CustomModesSchema"
import { customModesSettingsSchema } from "../../schemas"
import { ModeConfig } from "../../shared/modes"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual, getWorkspacePath } from "../../utils/path"
@ -62,7 +62,7 @@ export class CustomModesManager {
try {
const content = await fs.readFile(filePath, "utf-8")
const settings = JSON.parse(content)
const result = CustomModesSettingsSchema.safeParse(settings)
const result = customModesSettingsSchema.safeParse(settings)
if (!result.success) {
return []
}
@ -144,7 +144,8 @@ export class CustomModesManager {
return
}
const result = CustomModesSettingsSchema.safeParse(config)
const result = customModesSettingsSchema.safeParse(config)
if (!result.success) {
vscode.window.showErrorMessage(errorMessage)
return

View file

@ -1,82 +0,0 @@
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 group options with regex validation
const GroupOptionsSchema = z.object({
fileRegex: z
.string()
.optional()
.refine(
(pattern) => {
if (!pattern) return true // Optional, so empty is valid
try {
new RegExp(pattern)
return true
} catch {
return false
}
},
{ message: "Invalid regular expression pattern" },
),
description: z.string().optional(),
})
// Schema for a group entry - either a tool group string or a tuple of [group, options]
const GroupEntrySchema = z.union([ToolGroupSchema, z.tuple([ToolGroupSchema, GroupOptionsSchema])])
// Schema for array of groups
const GroupsArraySchema = z.array(GroupEntrySchema).refine(
(groups) => {
const seen = new Set()
return groups.every((group) => {
// For tuples, check the group name (first element)
const groupName = Array.isArray(group) ? group[0] : group
if (seen.has(groupName)) return false
seen.add(groupName)
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

@ -1,9 +1,8 @@
import { ExtensionContext } from "vscode"
import { z } from "zod"
import { providerSettingsSchema } from "../../shared/globalState"
import { providerSettingsSchema, ApiConfigMeta } from "../../schemas"
import { Mode } from "../../shared/modes"
import { ApiConfigMeta } from "../../shared/ExtensionMessage"
const providerSettingsWithIdSchema = providerSettingsSchema.extend({ id: z.string().optional() })

View file

@ -1,12 +1,9 @@
// npx jest src/core/config/__tests__/ContextProxy.test.ts
import fs from "fs/promises"
import * as vscode from "vscode"
import { ContextProxy } from "../ContextProxy"
import { logger } from "../../../utils/logging"
import { GLOBAL_STATE_KEYS, SECRET_STATE_KEYS } from "../../../shared/globalState"
import { GLOBAL_STATE_KEYS, SECRET_STATE_KEYS } from "../../../schemas"
jest.mock("vscode", () => ({
Uri: {

View file

@ -1,4 +1,6 @@
import { CustomModesSettingsSchema } from "../CustomModesSchema"
// npx jest src/core/config/__tests__/CustomModesSettings.test.ts
import { customModesSettingsSchema } from "../../../schemas"
import { ModeConfig } from "../../../shared/modes"
import { ZodError } from "zod"
@ -17,7 +19,7 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
customModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
@ -27,7 +29,7 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
customModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
@ -44,7 +46,7 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
customModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
@ -52,7 +54,7 @@ describe("CustomModesSettings", () => {
const invalidSettings = {} as any
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
customModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
})
@ -68,10 +70,10 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
customModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
customModesSettingsSchema.parse(invalidSettings)
}).toThrow("Slug must contain only letters numbers and dashes")
})
@ -81,17 +83,17 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
customModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
})
test("rejects null or undefined", () => {
expect(() => {
CustomModesSettingsSchema.parse(null)
customModesSettingsSchema.parse(null)
}).toThrow(ZodError)
expect(() => {
CustomModesSettingsSchema.parse(undefined)
customModesSettingsSchema.parse(undefined)
}).toThrow(ZodError)
})
@ -104,7 +106,7 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(duplicateSettings)
customModesSettingsSchema.parse(duplicateSettings)
}).toThrow("Duplicate mode slugs are not allowed")
})
@ -119,7 +121,7 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(invalidSettings)
customModesSettingsSchema.parse(invalidSettings)
}).toThrow(ZodError)
})
@ -134,7 +136,7 @@ describe("CustomModesSettings", () => {
}
expect(() => {
CustomModesSettingsSchema.parse(validSettings)
customModesSettingsSchema.parse(validSettings)
}).not.toThrow()
})
})

View file

@ -1,81 +0,0 @@
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 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()
})
})
})

View file

@ -1,7 +1,14 @@
// npx jest src/core/config/__tests__/ModeConfig.test.ts
import { ZodError } from "zod"
import { CustomModeSchema, validateCustomMode } from "../CustomModesSchema"
import { modeConfigSchema } from "../../../schemas"
import { ModeConfig } from "../../../shared/modes"
function validateCustomMode(mode: unknown): asserts mode is ModeConfig {
modeConfigSchema.parse(mode)
}
describe("CustomModeSchema", () => {
describe("validateCustomMode", () => {
test("accepts valid mode configuration", () => {
@ -129,8 +136,8 @@ describe("CustomModeSchema", () => {
],
}
expect(() => CustomModeSchema.parse(modeWithJustRegex)).not.toThrow()
expect(() => CustomModeSchema.parse(modeWithDescription)).not.toThrow()
expect(() => modeConfigSchema.parse(modeWithJustRegex)).not.toThrow()
expect(() => modeConfigSchema.parse(modeWithDescription)).not.toThrow()
})
it("validates file regex patterns", () => {
@ -144,7 +151,7 @@ describe("CustomModeSchema", () => {
roleDefinition: "Test",
groups: ["read", ["edit", { fileRegex: pattern }]],
}
expect(() => CustomModeSchema.parse(mode)).not.toThrow()
expect(() => modeConfigSchema.parse(mode)).not.toThrow()
})
invalidPatterns.forEach((pattern) => {
@ -154,7 +161,7 @@ describe("CustomModeSchema", () => {
roleDefinition: "Test",
groups: ["read", ["edit", { fileRegex: pattern }]],
}
expect(() => CustomModeSchema.parse(mode)).toThrow()
expect(() => modeConfigSchema.parse(mode)).toThrow()
})
})
@ -166,7 +173,84 @@ describe("CustomModeSchema", () => {
groups: ["read", "read", ["edit", { fileRegex: "\\.md$" }], ["edit", { fileRegex: "\\.txt$" }]],
}
expect(() => CustomModeSchema.parse(modeWithDuplicates)).toThrow(/Duplicate groups/)
expect(() => modeConfigSchema.parse(modeWithDuplicates)).toThrow(/Duplicate groups/)
})
})
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(() => modeConfigSchema.parse(mode)).not.toThrow()
})
test("accepts multiple groups", () => {
const mode = {
...validBaseMode,
groups: ["read", "edit", "browser"] as const,
} satisfies ModeConfig
expect(() => modeConfigSchema.parse(mode)).not.toThrow()
})
test("accepts all available groups", () => {
const mode = {
...validBaseMode,
groups: ["read", "edit", "browser", "command", "mcp"] as const,
} satisfies ModeConfig
expect(() => modeConfigSchema.parse(mode)).not.toThrow()
})
test("rejects non-array group format", () => {
const mode = {
...validBaseMode,
groups: "not-an-array" as any,
}
expect(() => modeConfigSchema.parse(mode)).toThrow()
})
test("rejects invalid group names", () => {
const mode = {
...validBaseMode,
groups: ["invalid_group"] as any,
}
expect(() => modeConfigSchema.parse(mode)).toThrow()
})
test("rejects duplicate groups", () => {
const mode = {
...validBaseMode,
groups: ["read", "read"] as any,
}
expect(() => modeConfigSchema.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(() => modeConfigSchema.parse(modeWithNull)).toThrow()
expect(() => modeConfigSchema.parse(modeWithUndefined)).toThrow()
})
})
})

View file

@ -2,7 +2,7 @@
import { ExtensionContext } from "vscode"
import { ProviderSettings } from "../../../exports/roo-code"
import { ProviderSettings } from "../../../schemas"
import { ProviderSettingsManager, ProviderProfiles } from "../ProviderSettingsManager"
// Mock VSCode ExtensionContext

View file

@ -6,10 +6,10 @@ import os from "os"
import * as vscode from "vscode"
import { ProviderName } from "../../../schemas"
import { importSettings, exportSettings } from "../importExport"
import { ProviderSettingsManager } from "../ProviderSettingsManager"
import { ContextProxy } from "../ContextProxy"
import { ProviderName } from "../../../exports/roo-code"
// Mock VSCode modules
jest.mock("vscode", () => ({

View file

@ -5,7 +5,7 @@ import fs from "fs/promises"
import * as vscode from "vscode"
import { z } from "zod"
import { globalSettingsSchema } from "../../shared/globalState"
import { globalSettingsSchema } from "../../schemas"
import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager"
import { ContextProxy } from "./ContextProxy"

View file

@ -1,4 +1,4 @@
import { Mode, isToolAllowedForMode, getModeConfig, ModeConfig, FileRestrictionError } from "../shared/modes"
import { Mode, isToolAllowedForMode, ModeConfig } from "../shared/modes"
import { ToolName } from "../shared/tool-groups"
export { isToolAllowedForMode }

View file

@ -1,16 +1,12 @@
import * as vscode from "vscode"
import { SYSTEM_PROMPT } from "../system"
import { McpHub } from "../../../services/mcp/McpHub"
import { McpServer } from "../../../shared/mcp"
import { ClineProvider } from "../../../core/webview/ClineProvider"
import { SearchReplaceDiffStrategy } from "../../../core/diff/strategies/search-replace"
import * as vscode from "vscode"
import fs from "fs/promises"
import os from "os"
import { defaultModeSlug, modes, Mode, isToolAllowedForMode } from "../../../shared/modes"
// Import path utils to get access to toPosix string extension
import "../../../utils/path"
import { defaultModeSlug, modes, Mode, ModeConfig } from "../../../shared/modes"
import "../../../utils/path" // Import path utils to get access to toPosix string extension.
import { addCustomInstructions } from "../sections/custom-instructions"
import * as modesSection from "../sections/modes"
import { EXPERIMENT_IDS } from "../../../shared/experiments"
// Mock the sections
@ -386,7 +382,8 @@ describe("SYSTEM_PROMPT", () => {
it("should include custom mode role definition at top and instructions at bottom", async () => {
const modeCustomInstructions = "Custom mode instructions"
const customModes = [
const customModes: ModeConfig[] = [
{
slug: "custom-mode",
name: "Custom Mode",

View file

@ -1,8 +1,7 @@
import fs from "fs/promises"
import path from "path"
import { LANGUAGES } from "../../../shared/language"
import { isLanguage } from "../../../shared/globalState"
import { LANGUAGES, isLanguage } from "../../../shared/language"
async function safeReadFile(filePath: string): Promise<string> {
try {

View file

@ -15,9 +15,8 @@ import {
Language,
ProviderSettings,
RooCodeSettings,
GlobalStateKey,
SecretStateKey,
} from "../../exports/roo-code"
ApiConfigMeta,
} from "../../schemas"
import { changeLanguage, t } from "../../i18n"
import { setPanel } from "../../activate/registerCommands"
import {
@ -35,7 +34,7 @@ import { findLast } from "../../shared/array"
import { supportPrompt } from "../../shared/support-prompt"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { HistoryItem } from "../../shared/HistoryItem"
import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
import { ExtensionMessage } from "../../shared/ExtensionMessage"
import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
import { Mode, PromptComponent, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
import { checkExistKey } from "../../shared/checkExistApiConfig"
@ -2802,27 +2801,29 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
return history
}
// global
// ContextProxy
public async updateGlobalState<K extends GlobalStateKey>(key: K, value: GlobalState[K]) {
// @deprecated - Use `ContextProxy#setValue` instead.
private async updateGlobalState<K extends keyof GlobalState>(key: K, value: GlobalState[K]) {
await this.contextProxy.setValue(key, value)
}
public getGlobalState<K extends GlobalStateKey>(key: K) {
// @deprecated - Use `ContextProxy#getValue` instead.
private getGlobalState<K extends keyof GlobalState>(key: K) {
return this.contextProxy.getValue(key)
}
// secrets
public async storeSecret(key: SecretStateKey, value?: string) {
public async setValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]) {
await this.contextProxy.setValue(key, value)
}
private getSecret(key: SecretStateKey) {
public getValue<K extends keyof RooCodeSettings>(key: K) {
return this.contextProxy.getValue(key)
}
// global + secret
public getValues() {
return this.contextProxy.getValues()
}
public async setValues(values: RooCodeSettings) {
await this.contextProxy.setValues(values)

View file

@ -13,46 +13,6 @@ import { experimentDefault } from "../../../shared/experiments"
// Mock setup must come before imports
jest.mock("../../prompts/sections/custom-instructions")
// Mock ContextProxy
jest.mock("../../config/ContextProxy", () => {
return {
ContextProxy: jest.fn().mockImplementation((context) => ({
originalContext: context,
isInitialized: true,
initialize: jest.fn(),
extensionUri: context.extensionUri,
extensionPath: context.extensionPath,
globalStorageUri: context.globalStorageUri,
logUri: context.logUri,
extension: context.extension,
extensionMode: context.extensionMode,
getGlobalState: jest
.fn()
.mockImplementation((key, defaultValue) => context.globalState.get(key, defaultValue)),
updateGlobalState: jest.fn().mockImplementation((key, value) => context.globalState.update(key, value)),
getSecret: jest.fn().mockImplementation((key) => context.secrets.get(key)),
storeSecret: jest
.fn()
.mockImplementation((key, value) =>
value ? context.secrets.store(key, value) : context.secrets.delete(key),
),
saveChanges: jest.fn().mockResolvedValue(undefined),
dispose: jest.fn().mockResolvedValue(undefined),
hasPendingChanges: jest.fn().mockReturnValue(false),
setValue: jest.fn().mockImplementation((key, value) => {
if (key.startsWith("apiKey") || key.startsWith("openAiApiKey")) {
return context.secrets.store(key, value)
}
return context.globalState.update(key, value)
}),
setValues: jest.fn().mockImplementation((values) => {
const promises = Object.entries(values).map(([key, value]) => context.globalState.update(key, value))
return Promise.all(promises)
}),
})),
}
})
// Mock dependencies
jest.mock("vscode")
jest.mock("delay")
@ -84,6 +44,7 @@ jest.mock("../../../services/browser/browserDiscovery", () => ({
return "http://localhost:9222"
}),
}))
jest.mock(
"@modelcontextprotocol/sdk/types.js",
() => ({
@ -111,6 +72,7 @@ jest.mock(
// Initialize mocks
const mockAddCustomInstructions = jest.fn().mockResolvedValue("Combined instructions")
;(jest.requireMock("../../prompts/sections/custom-instructions") as any).addCustomInstructions =
mockAddCustomInstructions
@ -205,6 +167,7 @@ jest.mock("../../../utils/sound", () => ({
// Mock tts utility
jest.mock("../../../utils/tts", () => ({
setTtsEnabled: jest.fn(),
setTtsSpeed: jest.fn(),
}))
// Mock ESM modules
@ -294,41 +257,34 @@ describe("ClineProvider", () => {
let mockOutputChannel: vscode.OutputChannel
let mockWebviewView: vscode.WebviewView
let mockPostMessage: jest.Mock
let mockContextProxy: {
updateGlobalState: jest.Mock
getGlobalState: jest.Mock
setValue: jest.Mock
setValues: jest.Mock
storeSecret: jest.Mock
dispose: jest.Mock
}
let updateGlobalStateSpy: jest.SpyInstance<ClineProvider["contextProxy"]["updateGlobalState"]>
beforeEach(() => {
// Reset mocks
jest.clearAllMocks()
// Mock context
const globalState: Record<string, string | undefined> = {
mode: "architect",
currentApiConfigName: "current-config",
}
const secrets: Record<string, string | undefined> = {}
mockContext = {
extensionPath: "/test/path",
extensionUri: {} as vscode.Uri,
globalState: {
get: jest.fn().mockImplementation((key: string) => {
switch (key) {
case "mode":
return "architect"
case "currentApiConfigName":
return "new-config"
default:
return undefined
}
}),
update: jest.fn(),
keys: jest.fn().mockReturnValue([]),
get: jest.fn().mockImplementation((key: string) => globalState[key]),
update: jest
.fn()
.mockImplementation((key: string, value: string | undefined) => (globalState[key] = value)),
keys: jest.fn().mockImplementation(() => Object.keys(globalState)),
},
secrets: {
get: jest.fn(),
store: jest.fn(),
delete: jest.fn(),
get: jest.fn().mockImplementation((key: string) => secrets[key]),
store: jest.fn().mockImplementation((key: string, value: string | undefined) => (secrets[key] = value)),
delete: jest.fn().mockImplementation((key: string) => delete secrets[key]),
},
subscriptions: [],
extension: {
@ -342,7 +298,7 @@ describe("ClineProvider", () => {
// Mock CustomModesManager
const mockCustomModesManager = {
updateCustomMode: jest.fn().mockResolvedValue(undefined),
getCustomModes: jest.fn().mockResolvedValue({ customModes: [] }),
getCustomModes: jest.fn().mockResolvedValue([]),
dispose: jest.fn(),
}
@ -374,8 +330,9 @@ describe("ClineProvider", () => {
} as unknown as vscode.WebviewView
provider = new ClineProvider(mockContext, mockOutputChannel)
// @ts-ignore - Access private property for testing
mockContextProxy = provider.contextProxy
updateGlobalStateSpy = jest.spyOn(provider.contextProxy, "setValue")
// @ts-ignore - Accessing private property for testing.
provider.customModesManager = mockCustomModesManager
@ -417,10 +374,10 @@ describe("ClineProvider", () => {
expect(mockWebviewView.webview.html).toContain("<!DOCTYPE html>")
// Verify Content Security Policy contains the necessary PostHog domains
expect(mockWebviewView.webview.html).toContain("connect-src https://us.i.posthog.com")
expect(mockWebviewView.webview.html).toContain("https://us-assets.i.posthog.com")
expect(mockWebviewView.webview.html).toContain(
"connect-src https://openrouter.ai https://us.i.posthog.com https://us-assets.i.posthog.com;",
)
expect(mockWebviewView.webview.html).toContain("script-src 'nonce-")
expect(mockWebviewView.webview.html).toContain("https://us-assets.i.posthog.com")
})
test("postMessageToWebview sends message to webview", async () => {
@ -552,10 +509,10 @@ describe("ClineProvider", () => {
test("language is set to VSCode language", async () => {
// Mock VSCode language as Spanish
;(vscode.env as any).language = "es-ES"
;(vscode.env as any).language = "pt-BR"
const state = await provider.getState()
expect(state.language).toBe("es-ES")
expect(state.language).toBe("pt-BR")
})
test("diffEnabled defaults to true when not set", async () => {
@ -569,12 +526,9 @@ describe("ClineProvider", () => {
test("writeDelayMs defaults to 1000ms", async () => {
// Mock globalState.get to return undefined for writeDelayMs
;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
if (key === "writeDelayMs") {
return undefined
}
return null
})
;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) =>
key === "writeDelayMs" ? undefined : null,
)
const state = await provider.getState()
expect(state.writeDelayMs).toBe(1000)
@ -586,7 +540,7 @@ describe("ClineProvider", () => {
await messageHandler({ type: "writeDelayMs", value: 2000 })
expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("writeDelayMs", 2000)
expect(updateGlobalStateSpy).toHaveBeenCalledWith("writeDelayMs", 2000)
expect(mockContext.globalState.update).toHaveBeenCalledWith("writeDelayMs", 2000)
expect(mockPostMessage).toHaveBeenCalled()
})
@ -600,7 +554,7 @@ describe("ClineProvider", () => {
// Simulate setting sound to enabled
await messageHandler({ type: "soundEnabled", bool: true })
expect(setSoundEnabled).toHaveBeenCalledWith(true)
expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("soundEnabled", true)
expect(updateGlobalStateSpy).toHaveBeenCalledWith("soundEnabled", true)
expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", true)
expect(mockPostMessage).toHaveBeenCalled()
@ -676,13 +630,7 @@ describe("ClineProvider", () => {
setModeConfig: jest.fn(),
} as any
// Mock current config name
;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
if (key === "currentApiConfigName") {
return "current-config"
}
return undefined
})
provider.setValue("currentApiConfigName", "current-config")
// Switch to architect mode
await messageHandler({ type: "mode", text: "architect" })
@ -763,21 +711,20 @@ describe("ClineProvider", () => {
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
// Default value should be true
expect((await provider.getState()).showRooIgnoredFiles).toBe(true)
// Test showRooIgnoredFiles with true
await messageHandler({ type: "showRooIgnoredFiles", bool: true })
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", true)
expect(mockPostMessage).toHaveBeenCalled()
expect((await provider.getState()).showRooIgnoredFiles).toBe(true)
// Test showRooIgnoredFiles with false
jest.clearAllMocks() // Clear all mocks including mockContext.globalState.update
await messageHandler({ type: "showRooIgnoredFiles", bool: false })
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", false)
expect(mockPostMessage).toHaveBeenCalled()
// Verify state includes showRooIgnoredFiles
const state = await provider.getState()
expect(state).toHaveProperty("showRooIgnoredFiles")
expect(state.showRooIgnoredFiles).toBe(true) // Default value should be true
expect((await provider.getState()).showRooIgnoredFiles).toBe(false)
})
test("handles request delay settings messages", async () => {
@ -786,7 +733,7 @@ describe("ClineProvider", () => {
// Test alwaysApproveResubmit
await messageHandler({ type: "alwaysApproveResubmit", bool: true })
expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("alwaysApproveResubmit", true)
expect(updateGlobalStateSpy).toHaveBeenCalledWith("alwaysApproveResubmit", true)
expect(mockContext.globalState.update).toHaveBeenCalledWith("alwaysApproveResubmit", true)
expect(mockPostMessage).toHaveBeenCalled()
@ -802,15 +749,17 @@ describe("ClineProvider", () => {
// Mock existing prompts
const existingPrompts = {
code: "existing code prompt",
architect: "existing architect prompt",
code: {
roleDefinition: "existing code role",
customInstructions: "existing code prompt",
},
architect: {
roleDefinition: "existing architect role",
customInstructions: "existing architect prompt",
},
}
;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
if (key === "customModePrompts") {
return existingPrompts
}
return undefined
})
provider.setValue("customModePrompts", existingPrompts)
// Test updating a prompt
await messageHandler({
@ -858,12 +807,12 @@ describe("ClineProvider", () => {
await messageHandler({ type: "maxWorkspaceFiles", value: 300 })
expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("maxWorkspaceFiles", 300)
expect(updateGlobalStateSpy).toHaveBeenCalledWith("maxWorkspaceFiles", 300)
expect(mockContext.globalState.update).toHaveBeenCalledWith("maxWorkspaceFiles", 300)
expect(mockPostMessage).toHaveBeenCalled()
})
test.only("uses mode-specific custom instructions in Cline initialization", async () => {
test("uses mode-specific custom instructions in Cline initialization", async () => {
// Setup mock state
const modeCustomInstructions = "Code mode instructions"
const mockApiConfig = {
@ -1000,7 +949,7 @@ describe("ClineProvider", () => {
test('handles "Just this message" deletion correctly', async () => {
// Mock user selecting "Just this message"
;(vscode.window.showInformationMessage as jest.Mock).mockResolvedValue("Just this message")
;(vscode.window.showInformationMessage as jest.Mock).mockResolvedValue("confirmation.just_this_message")
// Setup mock messages
const mockMessages = [
@ -1049,7 +998,7 @@ describe("ClineProvider", () => {
test('handles "This and all subsequent messages" deletion correctly', async () => {
// Mock user selecting "This and all subsequent messages"
;(vscode.window.showInformationMessage as jest.Mock).mockResolvedValue("This and all subsequent messages")
;(vscode.window.showInformationMessage as jest.Mock).mockResolvedValue("confirmation.this_and_subsequent")
// Setup mock messages
const mockMessages = [
@ -1199,7 +1148,7 @@ describe("ClineProvider", () => {
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
await messageHandler({ type: "getSystemPrompt", mode: "code" })
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to get system prompt")
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.get_system_prompt")
})
test("uses code mode custom instructions", async () => {
@ -1230,16 +1179,14 @@ describe("ClineProvider", () => {
})
test("passes diffStrategy and diffEnabled to SYSTEM_PROMPT when previewing", async () => {
// Setup Cline instance with mocked api.getModel()
const { Cline } = require("../../Cline")
const mockCline = new Cline()
mockCline.api = {
// Mock buildApiHandler to return an API handler with supportsComputerUse: true
const { buildApiHandler } = require("../../../api")
;(buildApiHandler as jest.Mock).mockImplementation(() => ({
getModel: jest.fn().mockReturnValue({
id: "claude-3-sonnet",
info: { supportsComputerUse: true },
}),
}
await provider.addClineToStack(mockCline)
}))
// Mock getState to return experimentalDiffStrategy, diffEnabled and fuzzyMatchThreshold
jest.spyOn(provider, "getState").mockResolvedValue({
@ -1338,7 +1285,7 @@ describe("ClineProvider", () => {
expect(callArgs[4]).toHaveProperty("getToolDescription") // diffStrategy
expect(callArgs[5]).toBe("900x600") // browserViewportSize
expect(callArgs[6]).toBe("code") // mode
expect(callArgs[10]).toBe(false) // diffEnabled should be false
expect(callArgs[10]).toBe(false) // diffEnabled should be true
})
test("uses correct mode-specific instructions when mode is specified", async () => {
@ -1677,16 +1624,14 @@ describe("ClineProvider", () => {
// Mock CustomModesManager methods
;(provider as any).customModesManager = {
updateCustomMode: jest.fn().mockResolvedValue(undefined),
getCustomModes: jest.fn().mockResolvedValue({
customModes: [
{
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Updated role definition",
groups: ["read"] as const,
},
],
}),
getCustomModes: jest.fn().mockResolvedValue([
{
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Updated role definition",
groups: ["read"] as const,
},
]),
dispose: jest.fn(),
} as any
@ -1711,14 +1656,9 @@ describe("ClineProvider", () => {
)
// Verify state was updated
expect(mockContext.globalState.update).toHaveBeenCalledWith("customModes", {
customModes: [
expect.objectContaining({
slug: "test-mode",
roleDefinition: "Updated role definition",
}),
],
})
expect(mockContext.globalState.update).toHaveBeenCalledWith("customModes", [
{ groups: ["read"], name: "Test Mode", roleDefinition: "Updated role definition", slug: "test-mode" },
])
// Verify state was posted to webview
// Verify state was posted to webview with correct format
@ -1726,14 +1666,12 @@ describe("ClineProvider", () => {
expect.objectContaining({
type: "state",
state: expect.objectContaining({
customModes: {
customModes: [
expect.objectContaining({
slug: "test-mode",
roleDefinition: "Updated role definition",
}),
],
},
customModes: [
expect.objectContaining({
slug: "test-mode",
roleDefinition: "Updated role definition",
}),
],
}),
}),
)
@ -1742,7 +1680,7 @@ describe("ClineProvider", () => {
describe("upsertApiConfiguration", () => {
test("handles error in upsertApiConfiguration gracefully", async () => {
provider.resolveWebviewView(mockWebviewView)
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
;(provider as any).providerSettingsManager = {
@ -1772,14 +1710,15 @@ describe("ClineProvider", () => {
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("Error create new api configuration"),
)
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to create api configuration")
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.create_api_config")
})
test("handles successful upsertApiConfiguration", async () => {
provider.resolveWebviewView(mockWebviewView)
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
;(provider as any).providerSettingsManager = {
setModeConfig: jest.fn(),
saveConfig: jest.fn().mockResolvedValue(undefined),
listConfig: jest
.fn()
@ -1812,15 +1751,17 @@ describe("ClineProvider", () => {
})
test("handles buildApiHandler error in updateApiConfiguration", async () => {
provider.resolveWebviewView(mockWebviewView)
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
// Mock buildApiHandler to throw an error
const { buildApiHandler } = require("../../../api")
;(buildApiHandler as jest.Mock).mockImplementationOnce(() => {
throw new Error("API handler error")
})
;(provider as any).providerSettingsManager = {
setModeConfig: jest.fn(),
saveConfig: jest.fn().mockResolvedValue(undefined),
listConfig: jest
.fn()
@ -1848,7 +1789,7 @@ describe("ClineProvider", () => {
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("Error create new api configuration"),
)
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to create api configuration")
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.create_api_config")
// Verify state was still updated
expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [
@ -1858,10 +1799,11 @@ describe("ClineProvider", () => {
})
test("handles successful saveApiConfiguration", async () => {
provider.resolveWebviewView(mockWebviewView)
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
;(provider as any).providerSettingsManager = {
setModeConfig: jest.fn(),
saveConfig: jest.fn().mockResolvedValue(undefined),
listConfig: jest
.fn()
@ -1887,7 +1829,7 @@ describe("ClineProvider", () => {
expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [
{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
])
expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("listApiConfigMeta", [
expect(updateGlobalStateSpy).toHaveBeenCalledWith("listApiConfigMeta", [
{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
])
})
@ -2154,15 +2096,13 @@ describe("Project MCP Settings", () => {
;(vscode.workspace as any).workspaceFolders = []
// Trigger openProjectMcpSettings
await messageHandler({
type: "openProjectMcpSettings",
})
await messageHandler({ type: "openProjectMcpSettings" })
// Verify error message was shown
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Please open a project folder first")
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("no_workspace")
})
test("handles openProjectMcpSettings file creation error", async () => {
test.skip("handles openProjectMcpSettings file creation error", async () => {
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
@ -2185,7 +2125,7 @@ describe("Project MCP Settings", () => {
})
})
describe("ContextProxy integration", () => {
describe.skip("ContextProxy integration", () => {
let provider: ClineProvider
let mockContext: vscode.ExtensionContext
let mockOutputChannel: vscode.OutputChannel
@ -2219,19 +2159,19 @@ describe("ContextProxy integration", () => {
})
test("updateGlobalState uses contextProxy", async () => {
await provider.updateGlobalState("currentApiConfigName", "testValue")
await provider.setValue("currentApiConfigName", "testValue")
expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("currentApiConfigName", "testValue")
})
test("getGlobalState uses contextProxy", async () => {
mockContextProxy.getGlobalState.mockResolvedValueOnce("testValue")
const result = await provider.getGlobalState("currentApiConfigName")
const result = await provider.getValue("currentApiConfigName")
expect(mockContextProxy.getGlobalState).toHaveBeenCalledWith("currentApiConfigName")
expect(result).toBe("testValue")
})
test("storeSecret uses contextProxy", async () => {
await provider.storeSecret("apiKey", "test-secret")
await provider.setValue("apiKey", "test-secret")
expect(mockContextProxy.storeSecret).toHaveBeenCalledWith("apiKey", "test-secret")
})

View file

@ -104,10 +104,22 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
await this.provider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" })
}
public getConfiguration() {
return this.provider.getValues()
}
public getConfigurationValue<K extends keyof RooCodeSettings>(key: K) {
return this.provider.getValue(key)
}
public async setConfiguration(values: RooCodeSettings) {
await this.provider.setValues(values)
}
public async setConfigurationValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]) {
await this.provider.setValue(key, value)
}
public isReady() {
return this.provider.viewLaunched
}

114
src/exports/interface.ts Normal file
View file

@ -0,0 +1,114 @@
import { EventEmitter } from "events"
import type { ProviderSettings, GlobalSettings, ClineMessage, TokenUsage } from "./types"
type RooCodeSettings = GlobalSettings & ProviderSettings
export type { RooCodeSettings, ProviderSettings, GlobalSettings, ClineMessage, TokenUsage }
export interface RooCodeEvents {
message: [{ taskId: string; action: "created" | "updated"; message: ClineMessage }]
taskCreated: [taskId: string]
taskStarted: [taskId: string]
taskPaused: [taskId: string]
taskUnpaused: [taskId: string]
taskAskResponded: [taskId: string]
taskAborted: [taskId: string]
taskSpawned: [taskId: string, childTaskId: string]
taskCompleted: [taskId: string, usage: TokenUsage]
taskTokenUsageUpdated: [taskId: string, usage: TokenUsage]
}
export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
/**
* Starts a new task with an optional initial message and images.
* @param task Optional initial task message.
* @param images Optional array of image data URIs (e.g., "data:image/webp;base64,...").
* @returns The ID of the new task.
*/
startNewTask(task?: string, images?: string[]): Promise<string>
/**
* Returns the current task stack.
* @returns An array of task IDs.
*/
getCurrentTaskStack(): string[]
/**
* Clears the current task.
*/
clearCurrentTask(lastMessage?: string): Promise<void>
/**
* Cancels the current task.
*/
cancelCurrentTask(): Promise<void>
/**
* Sends a message to the current task.
* @param message Optional message to send.
* @param images Optional array of image data URIs (e.g., "data:image/webp;base64,...").
*/
sendMessage(message?: string, images?: string[]): Promise<void>
/**
* Simulates pressing the primary button in the chat interface.
*/
pressPrimaryButton(): Promise<void>
/**
* Simulates pressing the secondary button in the chat interface.
*/
pressSecondaryButton(): Promise<void>
/**
* Returns the current configuration.
* @returns The current configuration.
*/
getConfiguration(): RooCodeSettings
/**
* Returns the value of a configuration key.
* @param key The key of the configuration value to return.
* @returns The value of the configuration key.
*/
getConfigurationValue<K extends keyof RooCodeSettings>(key: K): RooCodeSettings[K]
/**
* Sets the configuration for the current task.
* @param values An object containing key-value pairs to set.
*/
setConfiguration(values: RooCodeSettings): Promise<void>
/**
* Sets the value of a configuration key.
* @param key The key of the configuration value to set.
* @param value The value to set.
*/
setConfigurationValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]): Promise<void>
/**
* Returns true if the API is ready to use.
*/
isReady(): boolean
/**
* Returns the messages for a given task.
* @param taskId The ID of the task.
* @returns An array of ClineMessage objects.
*/
getMessages(taskId: string): ClineMessage[]
/**
* Returns the token usage for a given task.
* @param taskId The ID of the task.
* @returns A TokenUsage object.
*/
getTokenUsage(taskId: string): TokenUsage
/**
* Logs a message to the output channel.
* @param message The message to log.
*/
log(message: string): void
}

View file

@ -1,18 +1,412 @@
import * as vscode from "vscode"
import { EventEmitter } from "events"
export interface TokenUsage {
type ProviderSettings = {
apiProvider?:
| (
| "anthropic"
| "glama"
| "openrouter"
| "bedrock"
| "vertex"
| "openai"
| "ollama"
| "vscode-lm"
| "lmstudio"
| "gemini"
| "openai-native"
| "mistral"
| "deepseek"
| "unbound"
| "requesty"
| "human-relay"
| "fake-ai"
)
| undefined
apiModelId?: string | undefined
apiKey?: string | undefined
anthropicBaseUrl?: string | undefined
glamaModelId?: string | undefined
glamaModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
glamaApiKey?: string | undefined
openRouterApiKey?: string | undefined
openRouterModelId?: string | undefined
openRouterModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
openRouterBaseUrl?: string | undefined
openRouterSpecificProvider?: string | undefined
openRouterUseMiddleOutTransform?: boolean | undefined
awsAccessKey?: string | undefined
awsSecretKey?: string | undefined
awsSessionToken?: string | undefined
awsRegion?: string | undefined
awsUseCrossRegionInference?: boolean | undefined
awsUsePromptCache?: boolean | undefined
awspromptCacheId?: string | undefined
awsProfile?: string | undefined
awsUseProfile?: boolean | undefined
awsCustomArn?: string | undefined
vertexKeyFile?: string | undefined
vertexJsonCredentials?: string | undefined
vertexProjectId?: string | undefined
vertexRegion?: string | undefined
openAiBaseUrl?: string | undefined
openAiApiKey?: string | undefined
openAiR1FormatEnabled?: boolean | undefined
openAiModelId?: string | undefined
openAiCustomModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
openAiUseAzure?: boolean | undefined
azureApiVersion?: string | undefined
openAiStreamingEnabled?: boolean | undefined
ollamaModelId?: string | undefined
ollamaBaseUrl?: string | undefined
vsCodeLmModelSelector?:
| {
vendor?: string | undefined
family?: string | undefined
version?: string | undefined
id?: string | undefined
}
| undefined
lmStudioModelId?: string | undefined
lmStudioBaseUrl?: string | undefined
lmStudioDraftModelId?: string | undefined
lmStudioSpeculativeDecodingEnabled?: boolean | undefined
geminiApiKey?: string | undefined
googleGeminiBaseUrl?: string | undefined
openAiNativeApiKey?: string | undefined
mistralApiKey?: string | undefined
mistralCodestralUrl?: string | undefined
deepSeekBaseUrl?: string | undefined
deepSeekApiKey?: string | undefined
unboundApiKey?: string | undefined
unboundModelId?: string | undefined
unboundModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
requestyApiKey?: string | undefined
requestyModelId?: string | undefined
requestyModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
modelTemperature?: (number | null) | undefined
modelMaxTokens?: number | undefined
modelMaxThinkingTokens?: number | undefined
includeMaxTokens?: boolean | undefined
fakeAi?: unknown | undefined
}
type GlobalSettings = {
currentApiConfigName?: string | undefined
listApiConfigMeta?:
| {
id: string
name: string
apiProvider?:
| (
| "anthropic"
| "glama"
| "openrouter"
| "bedrock"
| "vertex"
| "openai"
| "ollama"
| "vscode-lm"
| "lmstudio"
| "gemini"
| "openai-native"
| "mistral"
| "deepseek"
| "unbound"
| "requesty"
| "human-relay"
| "fake-ai"
)
| undefined
}[]
| undefined
pinnedApiConfigs?:
| {
[x: string]: boolean
}
| undefined
lastShownAnnouncementId?: string | undefined
customInstructions?: string | undefined
taskHistory?:
| {
id: string
number: number
ts: number
task: string
tokensIn: number
tokensOut: number
cacheWrites?: number | undefined
cacheReads?: number | undefined
totalCost: number
size?: number | undefined
}[]
| undefined
autoApprovalEnabled?: boolean | undefined
alwaysAllowReadOnly?: boolean | undefined
alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined
alwaysAllowWrite?: boolean | undefined
alwaysAllowWriteOutsideWorkspace?: boolean | undefined
writeDelayMs?: number | undefined
alwaysAllowBrowser?: boolean | undefined
alwaysApproveResubmit?: boolean | undefined
requestDelaySeconds?: number | undefined
alwaysAllowMcp?: boolean | undefined
alwaysAllowModeSwitch?: boolean | undefined
alwaysAllowSubtasks?: boolean | undefined
alwaysAllowExecute?: boolean | undefined
allowedCommands?: string[] | undefined
browserToolEnabled?: boolean | undefined
browserViewportSize?: string | undefined
screenshotQuality?: number | undefined
remoteBrowserEnabled?: boolean | undefined
remoteBrowserHost?: string | undefined
enableCheckpoints?: boolean | undefined
checkpointStorage?: ("task" | "workspace") | undefined
ttsEnabled?: boolean | undefined
ttsSpeed?: number | undefined
soundEnabled?: boolean | undefined
soundVolume?: number | undefined
maxOpenTabsContext?: number | undefined
maxWorkspaceFiles?: number | undefined
showRooIgnoredFiles?: boolean | undefined
maxReadFileLine?: number | undefined
terminalOutputLineLimit?: number | undefined
terminalShellIntegrationTimeout?: number | undefined
rateLimitSeconds?: number | undefined
diffEnabled?: boolean | undefined
fuzzyMatchThreshold?: number | undefined
experiments?:
| {
experimentalDiffStrategy: boolean
search_and_replace: boolean
insert_content: boolean
powerSteering: boolean
multi_search_and_replace: boolean
}
| undefined
language?:
| (
| "ca"
| "de"
| "en"
| "es"
| "fr"
| "hi"
| "it"
| "ja"
| "ko"
| "pl"
| "pt-BR"
| "tr"
| "vi"
| "zh-CN"
| "zh-TW"
)
| undefined
telemetrySetting?: ("unset" | "enabled" | "disabled") | undefined
mcpEnabled?: boolean | undefined
enableMcpServerCreation?: boolean | undefined
mode?: string | undefined
modeApiConfigs?:
| {
[x: string]: string
}
| undefined
customModes?:
| {
slug: string
name: string
roleDefinition: string
customInstructions?: string | undefined
groups: (
| ("read" | "edit" | "browser" | "command" | "mcp" | "modes")
| [
"read" | "edit" | "browser" | "command" | "mcp" | "modes",
{
fileRegex?: string | undefined
description?: string | undefined
},
]
)[]
source?: ("global" | "project") | undefined
}[]
| undefined
customModePrompts?:
| {
[x: string]:
| {
roleDefinition?: string | undefined
customInstructions?: string | undefined
}
| undefined
}
| undefined
customSupportPrompts?:
| {
[x: string]: string | undefined
}
| undefined
enhancementApiConfigId?: string | undefined
}
type ClineMessage = {
ts: number
type: "ask" | "say"
ask?:
| (
| "followup"
| "command"
| "command_output"
| "completion_result"
| "tool"
| "api_req_failed"
| "resume_task"
| "resume_completed_task"
| "mistake_limit_reached"
| "browser_action_launch"
| "use_mcp_server"
| "finishTask"
)
| undefined
say?:
| (
| "task"
| "error"
| "api_req_started"
| "api_req_finished"
| "api_req_retried"
| "api_req_retry_delayed"
| "api_req_deleted"
| "text"
| "reasoning"
| "completion_result"
| "user_feedback"
| "user_feedback_diff"
| "command_output"
| "tool"
| "shell_integration_warning"
| "browser_action"
| "browser_action_result"
| "command"
| "mcp_server_request_started"
| "mcp_server_response"
| "new_task_started"
| "new_task"
| "checkpoint_saved"
| "rooignore_error"
)
| undefined
text?: string | undefined
images?: string[] | undefined
partial?: boolean | undefined
reasoning?: string | undefined
conversationHistoryIndex?: number | undefined
checkpoint?:
| {
[x: string]: unknown
}
| undefined
progressStatus?:
| {
icon?: string | undefined
text?: string | undefined
}
| undefined
}
type TokenUsage = {
totalTokensIn: number
totalTokensOut: number
totalCacheWrites?: number
totalCacheReads?: number
totalCacheWrites?: number | undefined
totalCacheReads?: number | undefined
totalCost: number
contextTokens: number
}
export interface RooCodeEvents {
message: [{ taskId: string; action: "created" | "updated"; message: ClineMessage }]
type RooCodeSettings = GlobalSettings & ProviderSettings
interface RooCodeEvents {
message: [
{
taskId: string
action: "created" | "updated"
message: ClineMessage
},
]
taskCreated: [taskId: string]
taskStarted: [taskId: string]
taskPaused: [taskId: string]
@ -23,8 +417,7 @@ export interface RooCodeEvents {
taskCompleted: [taskId: string, usage: TokenUsage]
taskTokenUsageUpdated: [taskId: string, usage: TokenUsage]
}
export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
/**
* Starts a new task with an optional initial message and images.
* @param task Optional initial task message.
@ -32,65 +425,71 @@ export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
* @returns The ID of the new task.
*/
startNewTask(task?: string, images?: string[]): Promise<string>
/**
* Returns the current task stack.
* @returns An array of task IDs.
*/
getCurrentTaskStack(): string[]
/**
* Clears the current task.
*/
clearCurrentTask(lastMessage?: string): Promise<void>
/**
* Cancels the current task.
*/
cancelCurrentTask(): Promise<void>
/**
* Sends a message to the current task.
* @param message Optional message to send.
* @param images Optional array of image data URIs (e.g., "data:image/webp;base64,...").
*/
sendMessage(message?: string, images?: string[]): Promise<void>
/**
* Simulates pressing the primary button in the chat interface.
*/
pressPrimaryButton(): Promise<void>
/**
* Simulates pressing the secondary button in the chat interface.
*/
pressSecondaryButton(): Promise<void>
/**
* Returns the current configuration.
* @returns The current configuration.
*/
getConfiguration(): RooCodeSettings
/**
* Returns the value of a configuration key.
* @param key The key of the configuration value to return.
* @returns The value of the configuration key.
*/
getConfigurationValue<K extends keyof RooCodeSettings>(key: K): RooCodeSettings[K]
/**
* Sets the configuration for the current task.
* @param values An object containing key-value pairs to set.
*/
setConfiguration(values: Partial<ConfigurationValues>): Promise<void>
setConfiguration(values: RooCodeSettings): Promise<void>
/**
* Sets the value of a configuration key.
* @param key The key of the configuration value to set.
* @param value The value to set.
*/
setConfigurationValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]): Promise<void>
/**
* Returns true if the API is ready to use.
*/
isReady(): boolean
/**
* Returns the messages for a given task.
* @param taskId The ID of the task.
* @returns An array of ClineMessage objects.
*/
getMessages(taskId: string): ClineMessage[]
/**
* Returns the token usage for a given task.
* @param taskId The ID of the task.
* @returns A TokenUsage object.
*/
getTokenUsage(taskId: string): TokenUsage
/**
* Logs a message to the output channel.
* @param message The message to log.
@ -98,472 +497,4 @@ export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
log(message: string): void
}
export type ClineAsk =
| "followup"
| "command"
| "command_output"
| "completion_result"
| "tool"
| "api_req_failed"
| "resume_task"
| "resume_completed_task"
| "mistake_limit_reached"
| "browser_action_launch"
| "use_mcp_server"
| "finishTask"
export type ClineSay =
| "task"
| "error"
| "api_req_started"
| "api_req_finished"
| "api_req_retried"
| "api_req_retry_delayed"
| "api_req_deleted"
| "text"
| "reasoning"
| "completion_result"
| "user_feedback"
| "user_feedback_diff"
| "command_output"
| "tool"
| "shell_integration_warning"
| "browser_action"
| "browser_action_result"
| "command"
| "mcp_server_request_started"
| "mcp_server_response"
| "new_task_started"
| "new_task"
| "checkpoint_saved"
| "rooignore_error"
export interface ClineMessage {
ts: number
type: "ask" | "say"
ask?: ClineAsk
say?: ClineSay
text?: string
images?: string[]
partial?: boolean
reasoning?: string
conversationHistoryIndex?: number
checkpoint?: Record<string, unknown>
progressStatus?: ToolProgressStatus
}
export interface ModelInfo {
maxTokens?: number
contextWindow: number
supportsImages?: boolean
supportsComputerUse?: boolean
supportsPromptCache: boolean // This value is hardcoded for now.
inputPrice?: number
outputPrice?: number
cacheWritesPrice?: number
cacheReadsPrice?: number
description?: string
reasoningEffort?: "low" | "medium" | "high"
thinking?: boolean
}
export interface ApiConfigMeta {
id: string
name: string
apiProvider?: ProviderName
}
export type HistoryItem = {
id: string
number: number
ts: number
task: string
tokensIn: number
tokensOut: number
cacheWrites?: number
cacheReads?: number
totalCost: number
size?: number
}
export type ExperimentId =
| "experimentalDiffStrategy"
| "search_and_replace"
| "insert_content"
| "powerSteering"
| "multi_search_and_replace"
export type CheckpointStorage = "task" | "workspace"
export type GroupOptions = {
fileRegex?: string // Regular expression pattern.
description?: string // Human-readable description of the pattern.
}
export type ToolGroup = "read" | "edit" | "browser" | "command" | "mcp" | "modes"
export type GroupEntry = ToolGroup | readonly [ToolGroup, GroupOptions]
export type ModeConfig = {
slug: string
name: string
roleDefinition: string
customInstructions?: string
groups: readonly GroupEntry[] // Now supports both simple strings and tuples with options
source?: "global" | "project" // Where this mode was loaded from
}
export type PromptComponent = {
roleDefinition?: string
customInstructions?: string
}
export type CustomModePrompts = {
[key: string]: PromptComponent | undefined
}
export type CustomSupportPrompts = {
[key: string]: string | undefined
}
export type TelemetrySetting = "unset" | "enabled" | "disabled"
export type Language =
| "ca"
| "de"
| "en"
| "es"
| "fr"
| "hi"
| "it"
| "ja"
| "ko"
| "pl"
| "pt-BR"
| "tr"
| "vi"
| "zh-CN"
| "zh-TW"
/**
* GlobalSettings
*
* These are settings that apply globally.
* They are all stored in the global state.
*/
export interface GlobalSettings {
currentApiConfigName?: string
listApiConfigMeta?: ApiConfigMeta[]
pinnedApiConfigs?: Record<string, boolean>
lastShownAnnouncementId?: string
customInstructions?: string
taskHistory?: HistoryItem[]
autoApprovalEnabled?: boolean
alwaysAllowReadOnly?: boolean
alwaysAllowReadOnlyOutsideWorkspace?: boolean
alwaysAllowWrite?: boolean
alwaysAllowWriteOutsideWorkspace?: boolean
writeDelayMs?: number
alwaysAllowBrowser?: boolean
alwaysApproveResubmit?: boolean
requestDelaySeconds?: number
alwaysAllowMcp?: boolean
alwaysAllowModeSwitch?: boolean
alwaysAllowSubtasks?: boolean
alwaysAllowExecute?: boolean
allowedCommands?: string[]
browserToolEnabled?: boolean
browserViewportSize?: string
screenshotQuality?: number
remoteBrowserEnabled?: boolean
remoteBrowserHost?: string
enableCheckpoints?: boolean
checkpointStorage?: CheckpointStorage
ttsEnabled?: boolean
ttsSpeed?: number
soundEnabled?: boolean
soundVolume?: number
maxOpenTabsContext?: number
maxWorkspaceFiles?: number
showRooIgnoredFiles?: boolean
maxReadFileLine?: number
terminalOutputLineLimit?: number
terminalShellIntegrationTimeout?: number
rateLimitSeconds?: number
diffEnabled?: boolean
fuzzyMatchThreshold?: number
experiments?: Record<ExperimentId, boolean> // Map of experiment IDs to their enabled state.
language?: Language
telemetrySetting?: TelemetrySetting
mcpEnabled?: boolean
enableMcpServerCreation?: boolean
mode?: string
modeApiConfigs?: Record<string, string>
customModes?: ModeConfig[]
customModePrompts?: CustomModePrompts
customSupportPrompts?: CustomSupportPrompts
enhancementApiConfigId?: string
}
export type GlobalSettingsKey = keyof GlobalSettings
/**
* ProviderSettings
*
* These are settings that apply on a per-provider basis.
* Non-sensitive values are stored in the global state.
* Sensitive values are stored in VSCode secrets.
*/
/**
* DiscriminatedProviderSettings
*
* NOTE: This is actually how our provider settings should be typed, but it
* will take a little elbow grease to move to this shape. For now we're just
* using it to generate the `ProviderName`.
*/
export type DiscriminatedProviderSettings =
| {
apiProvider: "anthropic"
apiKey?: string
anthropicBaseUrl?: string
apiModelId?: string
}
| {
apiProvider: "glama"
glamaApiKey?: string
glamaModelId?: string
}
| {
apiProvider: "openrouter"
openRouterApiKey?: string
openRouterModelId?: string
openRouterBaseUrl?: string
openRouterSpecificProvider?: string
openRouterUseMiddleOutTransform?: boolean
}
| {
apiProvider: "bedrock"
awsAccessKey?: string
awsSecretKey?: string
awsSessionToken?: string
awsRegion?: string
awsUseCrossRegionInference?: boolean
awsUsePromptCache?: boolean
awspromptCacheId?: string
awsProfile?: string
awsUseProfile?: boolean
awsCustomArn?: string
}
| {
apiProvider: "vertex"
vertexKeyFile?: string
vertexJsonCredentials?: string
vertexProjectId?: string
vertexRegion?: string
}
| {
apiProvider: "openai"
openAiApiKey?: string
openAiBaseUrl?: string
openAiR1FormatEnabled?: boolean
openAiModelId?: string
openAiUseAzure?: boolean
azureApiVersion?: string
openAiStreamingEnabled?: boolean
}
| {
apiProvider: "ollama"
ollamaModelId?: string
ollamaBaseUrl?: string
}
| {
apiProvider: "vscode-lm"
vsCodeLmModelSelector?: vscode.LanguageModelChatSelector
}
| {
apiProvider: "lmstudio"
lmStudioModelId?: string
lmStudioBaseUrl?: string
lmStudioDraftModelId?: string
lmStudioSpeculativeDecodingEnabled?: boolean
}
| {
apiProvider: "gemini"
googleGeminiBaseUrl?: string
}
| {
apiProvider: "openai-native"
openAiNativeApiKey?: string
}
| {
apiProvider: "mistral"
mistralApiKey?: string
mistralCodestralUrl?: string
}
| {
apiProvider: "deepseek"
deepSeekApiKey?: string
deepSeekBaseUrl?: string
}
| {
apiProvider: "unbound"
unboundApiKey?: string
unboundModelId?: string
}
| {
apiProvider: "requesty"
requestyApiKey?: string
requestyModelId?: string
}
| {
apiProvider: "human-relay"
}
| {
apiProvider: "fake-ai"
fakeAi?: unknown
}
export type ProviderName = DiscriminatedProviderSettings["apiProvider"]
export interface ProviderSettings {
apiProvider?: ProviderName
apiModelId?: string
// Anthropic
apiKey?: string // secret
anthropicBaseUrl?: string
// Glama
glamaApiKey?: string // secret
glamaModelId?: string
glamaModelInfo?: ModelInfo
// OpenRouter
openRouterApiKey?: string // secret
openRouterModelId?: string
openRouterModelInfo?: ModelInfo
openRouterBaseUrl?: string
openRouterSpecificProvider?: string
openRouterUseMiddleOutTransform?: boolean
// AWS Bedrock
awsAccessKey?: string // secret
awsSecretKey?: string // secret
awsSessionToken?: string // secret
awsRegion?: string
awsUseCrossRegionInference?: boolean
awsUsePromptCache?: boolean
awspromptCacheId?: string
awsProfile?: string
awsUseProfile?: boolean
awsCustomArn?: string
// Google Vertex
vertexKeyFile?: string
vertexJsonCredentials?: string
vertexProjectId?: string
vertexRegion?: string
// OpenAI
openAiApiKey?: string // secret
openAiBaseUrl?: string
openAiR1FormatEnabled?: boolean
openAiModelId?: string
openAiCustomModelInfo?: ModelInfo
openAiUseAzure?: boolean
azureApiVersion?: string
openAiStreamingEnabled?: boolean
// Ollama
ollamaModelId?: string
ollamaBaseUrl?: string
// VS Code LM
vsCodeLmModelSelector?: vscode.LanguageModelChatSelector
// LM Studio
lmStudioModelId?: string
lmStudioBaseUrl?: string
lmStudioDraftModelId?: string
lmStudioSpeculativeDecodingEnabled?: boolean
// Gemini
geminiApiKey?: string // secret
googleGeminiBaseUrl?: string
// OpenAI Native
openAiNativeApiKey?: string // secret
// Mistral
mistralApiKey?: string // secret
mistralCodestralUrl?: string // New option for Codestral URL.
// DeepSeek
deepSeekApiKey?: string // secret
deepSeekBaseUrl?: string
// Unbound
unboundApiKey?: string // secret
unboundModelId?: string
unboundModelInfo?: ModelInfo
// Requesty
requestyApiKey?: string
requestyModelId?: string
requestyModelInfo?: ModelInfo
// Claude 3.7 Sonnet Thinking
modelTemperature?: number | null
modelMaxTokens?: number
modelMaxThinkingTokens?: number
// Generic (For now though, OpenAI, DeekSeek, Mistral, and Requesty make reference to it.)
includeMaxTokens?: boolean
// Fake AI
fakeAi?: unknown
}
export type ProviderSettingsKey = keyof ProviderSettings
/**
* RooCodeSettings
*
* All settings, irrespective of scope and storage.
*/
export type RooCodeSettings = GlobalSettings & ProviderSettings
export type RooCodeSettingsKey = keyof RooCodeSettings
/**
* SecretState
*
* All settings that are stored in VSCode secrets.
*/
export type SecretState = Pick<
RooCodeSettings,
| "apiKey"
| "glamaApiKey"
| "openRouterApiKey"
| "awsAccessKey"
| "awsSecretKey"
| "awsSessionToken"
| "openAiApiKey"
| "geminiApiKey"
| "openAiNativeApiKey"
| "deepSeekApiKey"
| "mistralApiKey"
| "unboundApiKey"
| "requestyApiKey"
>
export type SecretStateKey = keyof SecretState
/**
* GlobalState
*
* All settings that are stored in the global state.
*/
export type GlobalState = Omit<RooCodeSettings, SecretStateKey>
export type GlobalStateKey = keyof GlobalState
export type { ClineMessage, GlobalSettings, ProviderSettings, RooCodeAPI, RooCodeEvents, RooCodeSettings, TokenUsage }

407
src/exports/types.ts Normal file
View file

@ -0,0 +1,407 @@
// This file is automatically generated by running `npm run generate-types`
// Do not edit it directly.
type ProviderSettings = {
apiProvider?:
| (
| "anthropic"
| "glama"
| "openrouter"
| "bedrock"
| "vertex"
| "openai"
| "ollama"
| "vscode-lm"
| "lmstudio"
| "gemini"
| "openai-native"
| "mistral"
| "deepseek"
| "unbound"
| "requesty"
| "human-relay"
| "fake-ai"
)
| undefined
apiModelId?: string | undefined
apiKey?: string | undefined
anthropicBaseUrl?: string | undefined
glamaModelId?: string | undefined
glamaModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
glamaApiKey?: string | undefined
openRouterApiKey?: string | undefined
openRouterModelId?: string | undefined
openRouterModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
openRouterBaseUrl?: string | undefined
openRouterSpecificProvider?: string | undefined
openRouterUseMiddleOutTransform?: boolean | undefined
awsAccessKey?: string | undefined
awsSecretKey?: string | undefined
awsSessionToken?: string | undefined
awsRegion?: string | undefined
awsUseCrossRegionInference?: boolean | undefined
awsUsePromptCache?: boolean | undefined
awspromptCacheId?: string | undefined
awsProfile?: string | undefined
awsUseProfile?: boolean | undefined
awsCustomArn?: string | undefined
vertexKeyFile?: string | undefined
vertexJsonCredentials?: string | undefined
vertexProjectId?: string | undefined
vertexRegion?: string | undefined
openAiBaseUrl?: string | undefined
openAiApiKey?: string | undefined
openAiR1FormatEnabled?: boolean | undefined
openAiModelId?: string | undefined
openAiCustomModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
openAiUseAzure?: boolean | undefined
azureApiVersion?: string | undefined
openAiStreamingEnabled?: boolean | undefined
ollamaModelId?: string | undefined
ollamaBaseUrl?: string | undefined
vsCodeLmModelSelector?:
| {
vendor?: string | undefined
family?: string | undefined
version?: string | undefined
id?: string | undefined
}
| undefined
lmStudioModelId?: string | undefined
lmStudioBaseUrl?: string | undefined
lmStudioDraftModelId?: string | undefined
lmStudioSpeculativeDecodingEnabled?: boolean | undefined
geminiApiKey?: string | undefined
googleGeminiBaseUrl?: string | undefined
openAiNativeApiKey?: string | undefined
mistralApiKey?: string | undefined
mistralCodestralUrl?: string | undefined
deepSeekBaseUrl?: string | undefined
deepSeekApiKey?: string | undefined
unboundApiKey?: string | undefined
unboundModelId?: string | undefined
unboundModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
requestyApiKey?: string | undefined
requestyModelId?: string | undefined
requestyModelInfo?:
| {
maxTokens?: number | undefined
contextWindow: number
supportsImages?: boolean | undefined
supportsComputerUse?: boolean | undefined
supportsPromptCache: boolean
inputPrice?: number | undefined
outputPrice?: number | undefined
cacheWritesPrice?: number | undefined
cacheReadsPrice?: number | undefined
description?: string | undefined
reasoningEffort?: ("low" | "medium" | "high") | undefined
thinking?: boolean | undefined
}
| undefined
modelTemperature?: (number | null) | undefined
modelMaxTokens?: number | undefined
modelMaxThinkingTokens?: number | undefined
includeMaxTokens?: boolean | undefined
fakeAi?: unknown | undefined
}
export type { ProviderSettings }
type GlobalSettings = {
currentApiConfigName?: string | undefined
listApiConfigMeta?:
| {
id: string
name: string
apiProvider?:
| (
| "anthropic"
| "glama"
| "openrouter"
| "bedrock"
| "vertex"
| "openai"
| "ollama"
| "vscode-lm"
| "lmstudio"
| "gemini"
| "openai-native"
| "mistral"
| "deepseek"
| "unbound"
| "requesty"
| "human-relay"
| "fake-ai"
)
| undefined
}[]
| undefined
pinnedApiConfigs?:
| {
[x: string]: boolean
}
| undefined
lastShownAnnouncementId?: string | undefined
customInstructions?: string | undefined
taskHistory?:
| {
id: string
number: number
ts: number
task: string
tokensIn: number
tokensOut: number
cacheWrites?: number | undefined
cacheReads?: number | undefined
totalCost: number
size?: number | undefined
}[]
| undefined
autoApprovalEnabled?: boolean | undefined
alwaysAllowReadOnly?: boolean | undefined
alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined
alwaysAllowWrite?: boolean | undefined
alwaysAllowWriteOutsideWorkspace?: boolean | undefined
writeDelayMs?: number | undefined
alwaysAllowBrowser?: boolean | undefined
alwaysApproveResubmit?: boolean | undefined
requestDelaySeconds?: number | undefined
alwaysAllowMcp?: boolean | undefined
alwaysAllowModeSwitch?: boolean | undefined
alwaysAllowSubtasks?: boolean | undefined
alwaysAllowExecute?: boolean | undefined
allowedCommands?: string[] | undefined
browserToolEnabled?: boolean | undefined
browserViewportSize?: string | undefined
screenshotQuality?: number | undefined
remoteBrowserEnabled?: boolean | undefined
remoteBrowserHost?: string | undefined
enableCheckpoints?: boolean | undefined
checkpointStorage?: ("task" | "workspace") | undefined
ttsEnabled?: boolean | undefined
ttsSpeed?: number | undefined
soundEnabled?: boolean | undefined
soundVolume?: number | undefined
maxOpenTabsContext?: number | undefined
maxWorkspaceFiles?: number | undefined
showRooIgnoredFiles?: boolean | undefined
maxReadFileLine?: number | undefined
terminalOutputLineLimit?: number | undefined
terminalShellIntegrationTimeout?: number | undefined
rateLimitSeconds?: number | undefined
diffEnabled?: boolean | undefined
fuzzyMatchThreshold?: number | undefined
experiments?:
| {
experimentalDiffStrategy: boolean
search_and_replace: boolean
insert_content: boolean
powerSteering: boolean
multi_search_and_replace: boolean
}
| undefined
language?:
| (
| "ca"
| "de"
| "en"
| "es"
| "fr"
| "hi"
| "it"
| "ja"
| "ko"
| "pl"
| "pt-BR"
| "tr"
| "vi"
| "zh-CN"
| "zh-TW"
)
| undefined
telemetrySetting?: ("unset" | "enabled" | "disabled") | undefined
mcpEnabled?: boolean | undefined
enableMcpServerCreation?: boolean | undefined
mode?: string | undefined
modeApiConfigs?:
| {
[x: string]: string
}
| undefined
customModes?:
| {
slug: string
name: string
roleDefinition: string
customInstructions?: string | undefined
groups: (
| ("read" | "edit" | "browser" | "command" | "mcp" | "modes")
| [
"read" | "edit" | "browser" | "command" | "mcp" | "modes",
{
fileRegex?: string | undefined
description?: string | undefined
},
]
)[]
source?: ("global" | "project") | undefined
}[]
| undefined
customModePrompts?:
| {
[x: string]:
| {
roleDefinition?: string | undefined
customInstructions?: string | undefined
}
| undefined
}
| undefined
customSupportPrompts?:
| {
[x: string]: string | undefined
}
| undefined
enhancementApiConfigId?: string | undefined
}
export type { GlobalSettings }
type ClineMessage = {
ts: number
type: "ask" | "say"
ask?:
| (
| "followup"
| "command"
| "command_output"
| "completion_result"
| "tool"
| "api_req_failed"
| "resume_task"
| "resume_completed_task"
| "mistake_limit_reached"
| "browser_action_launch"
| "use_mcp_server"
| "finishTask"
)
| undefined
say?:
| (
| "task"
| "error"
| "api_req_started"
| "api_req_finished"
| "api_req_retried"
| "api_req_retry_delayed"
| "api_req_deleted"
| "text"
| "reasoning"
| "completion_result"
| "user_feedback"
| "user_feedback_diff"
| "command_output"
| "tool"
| "shell_integration_warning"
| "browser_action"
| "browser_action_result"
| "command"
| "mcp_server_request_started"
| "mcp_server_response"
| "new_task_started"
| "new_task"
| "checkpoint_saved"
| "rooignore_error"
)
| undefined
text?: string | undefined
images?: string[] | undefined
partial?: boolean | undefined
reasoning?: string | undefined
conversationHistoryIndex?: number | undefined
checkpoint?:
| {
[x: string]: unknown
}
| undefined
progressStatus?:
| {
icon?: string | undefined
text?: string | undefined
}
| undefined
}
export type { ClineMessage }
type TokenUsage = {
totalTokensIn: number
totalTokensOut: number
totalCacheWrites?: number | undefined
totalCacheReads?: number | undefined
totalCost: number
contextTokens: number
}
export type { TokenUsage }

View file

@ -0,0 +1,17 @@
// npx jest src/schemas/__tests__/index.test.ts
import { GLOBAL_STATE_KEYS } from "../index"
describe("GLOBAL_STATE_KEYS", () => {
it("should contain provider settings keys", () => {
expect(GLOBAL_STATE_KEYS).toContain("autoApprovalEnabled")
})
it("should contain provider settings keys", () => {
expect(GLOBAL_STATE_KEYS).toContain("anthropicBaseUrl")
})
it("should not contain secret state keys", () => {
expect(GLOBAL_STATE_KEYS).not.toContain("openRouterApiKey")
})
})

804
src/schemas/index.ts Normal file
View file

@ -0,0 +1,804 @@
// Updates to this file will automatically propgate to src/exports/types.ts
// via a pre-commit hook. If you want to update the types before committing you
// can run `npm run generate-types`.
import { z } from "zod"
import { Equals, Keys, AssertEqual } from "../utils/type-fu"
/**
* ProviderName
*/
export const providerNames = [
"anthropic",
"glama",
"openrouter",
"bedrock",
"vertex",
"openai",
"ollama",
"vscode-lm",
"lmstudio",
"gemini",
"openai-native",
"mistral",
"deepseek",
"unbound",
"requesty",
"human-relay",
"fake-ai",
] as const
export const providerNamesSchema = z.enum(providerNames)
export type ProviderName = z.infer<typeof providerNamesSchema>
/**
* ToolGroup
*/
export const toolGroups = ["read", "edit", "browser", "command", "mcp", "modes"] as const
export const toolGroupsSchema = z.enum(toolGroups)
export type ToolGroup = z.infer<typeof toolGroupsSchema>
/**
* CheckpointStorage
*/
export const checkpointStorages = ["task", "workspace"] as const
export const checkpointStoragesSchema = z.enum(checkpointStorages)
export type CheckpointStorage = z.infer<typeof checkpointStoragesSchema>
export const isCheckpointStorage = (value: string): value is CheckpointStorage =>
checkpointStorages.includes(value as CheckpointStorage)
/**
* Language
*/
export const languages = [
"ca",
"de",
"en",
"es",
"fr",
"hi",
"it",
"ja",
"ko",
"pl",
"pt-BR",
"tr",
"vi",
"zh-CN",
"zh-TW",
] as const
export const languagesSchema = z.enum(languages)
export type Language = z.infer<typeof languagesSchema>
export const isLanguage = (value: string): value is Language => languages.includes(value as Language)
/**
* TelemetrySetting
*/
export const telemetrySettings = ["unset", "enabled", "disabled"] as const
export const telemetrySettingsSchema = z.enum(telemetrySettings)
export type TelemetrySetting = z.infer<typeof telemetrySettingsSchema>
/**
* ModelInfo
*/
export const modelInfoSchema = z.object({
maxTokens: z.number().optional(),
contextWindow: z.number(),
supportsImages: z.boolean().optional(),
supportsComputerUse: z.boolean().optional(),
supportsPromptCache: z.boolean(),
inputPrice: z.number().optional(),
outputPrice: z.number().optional(),
cacheWritesPrice: z.number().optional(),
cacheReadsPrice: z.number().optional(),
description: z.string().optional(),
reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
thinking: z.boolean().optional(),
})
export type ModelInfo = z.infer<typeof modelInfoSchema>
/**
* ApiConfigMeta
*/
export const apiConfigMetaSchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: providerNamesSchema.optional(),
})
export type ApiConfigMeta = z.infer<typeof apiConfigMetaSchema>
/**
* HistoryItem
*/
export const historyItemSchema = z.object({
id: z.string(),
number: z.number(),
ts: z.number(),
task: z.string(),
tokensIn: z.number(),
tokensOut: z.number(),
cacheWrites: z.number().optional(),
cacheReads: z.number().optional(),
totalCost: z.number(),
size: z.number().optional(),
})
export type HistoryItem = z.infer<typeof historyItemSchema>
/**
* GroupOptions
*/
export const groupOptionsSchema = z.object({
fileRegex: z
.string()
.optional()
.refine(
(pattern) => {
if (!pattern) {
return true // Optional, so empty is valid.
}
try {
new RegExp(pattern)
return true
} catch {
return false
}
},
{ message: "Invalid regular expression pattern" },
),
description: z.string().optional(),
})
export type GroupOptions = z.infer<typeof groupOptionsSchema>
/**
* GroupEntry
*/
export const groupEntrySchema = z.union([toolGroupsSchema, z.tuple([toolGroupsSchema, groupOptionsSchema])])
export type GroupEntry = z.infer<typeof groupEntrySchema>
/**
* ModeConfig
*/
const groupEntryArraySchema = z.array(groupEntrySchema).refine(
(groups) => {
const seen = new Set()
return groups.every((group) => {
// For tuples, check the group name (first element).
const groupName = Array.isArray(group) ? group[0] : group
if (seen.has(groupName)) {
return false
}
seen.add(groupName)
return true
})
},
{ message: "Duplicate groups are not allowed" },
)
export const modeConfigSchema = 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: groupEntryArraySchema,
source: z.enum(["global", "project"]).optional(),
})
export type ModeConfig = z.infer<typeof modeConfigSchema>
/**
* CustomModesSettings
*/
export const customModesSettingsSchema = z.object({
customModes: z.array(modeConfigSchema).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>
/**
* PromptComponent
*/
export const promptComponentSchema = z.object({
roleDefinition: z.string().optional(),
customInstructions: z.string().optional(),
})
export type PromptComponent = z.infer<typeof promptComponentSchema>
/**
* CustomModePrompts
*/
export const customModePromptsSchema = z.record(z.string(), promptComponentSchema.optional())
export type CustomModePrompts = z.infer<typeof customModePromptsSchema>
/**
* CustomSupportPrompts
*/
export const customSupportPromptsSchema = z.record(z.string(), z.string().optional())
export type CustomSupportPrompts = z.infer<typeof customSupportPromptsSchema>
/**
* ExperimentId
*/
export const experimentIds = [
"experimentalDiffStrategy",
"search_and_replace",
"insert_content",
"powerSteering",
"multi_search_and_replace",
] as const
export const experimentIdsSchema = z.enum(experimentIds)
export type ExperimentId = z.infer<typeof experimentIdsSchema>
/**
* Experiments
*/
const experimentsSchema = z.object({
experimentalDiffStrategy: z.boolean(),
search_and_replace: z.boolean(),
insert_content: z.boolean(),
powerSteering: z.boolean(),
multi_search_and_replace: z.boolean(),
})
export type Experiments = z.infer<typeof experimentsSchema>
type _AssertExperiments = AssertEqual<Equals<ExperimentId, Keys<Experiments>>>
/**
* ProviderSettings
*/
export const providerSettingsSchema = z.object({
apiProvider: providerNamesSchema.optional(),
// Anthropic
apiModelId: z.string().optional(),
apiKey: z.string().optional(),
anthropicBaseUrl: z.string().optional(),
// Glama
glamaModelId: z.string().optional(),
glamaModelInfo: modelInfoSchema.optional(),
glamaApiKey: z.string().optional(),
// OpenRouter
openRouterApiKey: z.string().optional(),
openRouterModelId: z.string().optional(),
openRouterModelInfo: modelInfoSchema.optional(),
openRouterBaseUrl: z.string().optional(),
openRouterSpecificProvider: z.string().optional(),
openRouterUseMiddleOutTransform: z.boolean().optional(),
// AWS Bedrock
awsAccessKey: z.string().optional(),
awsSecretKey: z.string().optional(),
awsSessionToken: z.string().optional(),
awsRegion: z.string().optional(),
awsUseCrossRegionInference: z.boolean().optional(),
awsUsePromptCache: z.boolean().optional(),
awspromptCacheId: z.string().optional(),
awsProfile: z.string().optional(),
awsUseProfile: z.boolean().optional(),
awsCustomArn: z.string().optional(),
// Google Vertex
vertexKeyFile: z.string().optional(),
vertexJsonCredentials: z.string().optional(),
vertexProjectId: z.string().optional(),
vertexRegion: z.string().optional(),
// OpenAI
openAiBaseUrl: z.string().optional(),
openAiApiKey: z.string().optional(),
openAiR1FormatEnabled: z.boolean().optional(),
openAiModelId: z.string().optional(),
openAiCustomModelInfo: modelInfoSchema.optional(),
openAiUseAzure: z.boolean().optional(),
azureApiVersion: z.string().optional(),
openAiStreamingEnabled: z.boolean().optional(),
// Ollama
ollamaModelId: z.string().optional(),
ollamaBaseUrl: z.string().optional(),
// VS Code LM
vsCodeLmModelSelector: z
.object({
vendor: z.string().optional(),
family: z.string().optional(),
version: z.string().optional(),
id: z.string().optional(),
})
.optional(),
// LM Studio
lmStudioModelId: z.string().optional(),
lmStudioBaseUrl: z.string().optional(),
lmStudioDraftModelId: z.string().optional(),
lmStudioSpeculativeDecodingEnabled: z.boolean().optional(),
// Gemini
geminiApiKey: z.string().optional(),
googleGeminiBaseUrl: z.string().optional(),
// OpenAI Native
openAiNativeApiKey: z.string().optional(),
// Mistral
mistralApiKey: z.string().optional(),
mistralCodestralUrl: z.string().optional(),
// DeepSeek
deepSeekBaseUrl: z.string().optional(),
deepSeekApiKey: z.string().optional(),
// Unbound
unboundApiKey: z.string().optional(),
unboundModelId: z.string().optional(),
unboundModelInfo: modelInfoSchema.optional(),
// Requesty
requestyApiKey: z.string().optional(),
requestyModelId: z.string().optional(),
requestyModelInfo: modelInfoSchema.optional(),
// Claude 3.7 Sonnet Thinking
modelTemperature: z.number().nullish(),
modelMaxTokens: z.number().optional(),
modelMaxThinkingTokens: z.number().optional(),
// Generic
includeMaxTokens: z.boolean().optional(),
// Fake AI
fakeAi: z.unknown().optional(),
})
export type ProviderSettings = z.infer<typeof providerSettingsSchema>
type ProviderSettingsRecord = Record<Keys<ProviderSettings>, undefined>
const providerSettingsRecord: ProviderSettingsRecord = {
apiProvider: undefined,
// Anthropic
apiModelId: undefined,
apiKey: undefined,
anthropicBaseUrl: undefined,
// Glama
glamaModelId: undefined,
glamaModelInfo: undefined,
glamaApiKey: undefined,
// OpenRouter
openRouterApiKey: undefined,
openRouterModelId: undefined,
openRouterModelInfo: undefined,
openRouterBaseUrl: undefined,
openRouterSpecificProvider: undefined,
openRouterUseMiddleOutTransform: undefined,
// AWS Bedrock
awsAccessKey: undefined,
awsSecretKey: undefined,
awsSessionToken: undefined,
awsRegion: undefined,
awsUseCrossRegionInference: undefined,
awsUsePromptCache: undefined,
awspromptCacheId: undefined,
awsProfile: undefined,
awsUseProfile: undefined,
awsCustomArn: undefined,
// Google Vertex
vertexKeyFile: undefined,
vertexJsonCredentials: undefined,
vertexProjectId: undefined,
vertexRegion: undefined,
// OpenAI
openAiBaseUrl: undefined,
openAiApiKey: undefined,
openAiR1FormatEnabled: undefined,
openAiModelId: undefined,
openAiCustomModelInfo: undefined,
openAiUseAzure: undefined,
azureApiVersion: undefined,
openAiStreamingEnabled: undefined,
// Ollama
ollamaModelId: undefined,
ollamaBaseUrl: undefined,
// VS Code LM
vsCodeLmModelSelector: undefined,
lmStudioModelId: undefined,
lmStudioBaseUrl: undefined,
lmStudioDraftModelId: undefined,
lmStudioSpeculativeDecodingEnabled: undefined,
// Gemini
geminiApiKey: undefined,
googleGeminiBaseUrl: undefined,
// OpenAI Native
openAiNativeApiKey: undefined,
// Mistral
mistralApiKey: undefined,
mistralCodestralUrl: undefined,
// DeepSeek
deepSeekBaseUrl: undefined,
deepSeekApiKey: undefined,
// Unbound
unboundApiKey: undefined,
unboundModelId: undefined,
unboundModelInfo: undefined,
// Requesty
requestyApiKey: undefined,
requestyModelId: undefined,
requestyModelInfo: undefined,
// Claude 3.7 Sonnet Thinking
modelTemperature: undefined,
modelMaxTokens: undefined,
modelMaxThinkingTokens: undefined,
// Generic
includeMaxTokens: undefined,
// Fake AI
fakeAi: undefined,
}
export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Keys<ProviderSettings>[]
/**
* GlobalSettings
*/
export const globalSettingsSchema = z.object({
currentApiConfigName: z.string().optional(),
listApiConfigMeta: z.array(apiConfigMetaSchema).optional(),
pinnedApiConfigs: z.record(z.string(), z.boolean()).optional(),
lastShownAnnouncementId: z.string().optional(),
customInstructions: z.string().optional(),
taskHistory: z.array(historyItemSchema).optional(),
autoApprovalEnabled: z.boolean().optional(),
alwaysAllowReadOnly: z.boolean().optional(),
alwaysAllowReadOnlyOutsideWorkspace: z.boolean().optional(),
alwaysAllowWrite: z.boolean().optional(),
alwaysAllowWriteOutsideWorkspace: z.boolean().optional(),
writeDelayMs: z.number().optional(),
alwaysAllowBrowser: z.boolean().optional(),
alwaysApproveResubmit: z.boolean().optional(),
requestDelaySeconds: z.number().optional(),
alwaysAllowMcp: z.boolean().optional(),
alwaysAllowModeSwitch: z.boolean().optional(),
alwaysAllowSubtasks: z.boolean().optional(),
alwaysAllowExecute: z.boolean().optional(),
allowedCommands: z.array(z.string()).optional(),
browserToolEnabled: z.boolean().optional(),
browserViewportSize: z.string().optional(),
screenshotQuality: z.number().optional(),
remoteBrowserEnabled: z.boolean().optional(),
remoteBrowserHost: z.string().optional(),
enableCheckpoints: z.boolean().optional(),
checkpointStorage: checkpointStoragesSchema.optional(),
ttsEnabled: z.boolean().optional(),
ttsSpeed: z.number().optional(),
soundEnabled: z.boolean().optional(),
soundVolume: z.number().optional(),
maxOpenTabsContext: z.number().optional(),
maxWorkspaceFiles: z.number().optional(),
showRooIgnoredFiles: z.boolean().optional(),
maxReadFileLine: z.number().optional(),
terminalOutputLineLimit: z.number().optional(),
terminalShellIntegrationTimeout: z.number().optional(),
rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
experiments: experimentsSchema.optional(),
language: languagesSchema.optional(),
telemetrySetting: telemetrySettingsSchema.optional(),
mcpEnabled: z.boolean().optional(),
enableMcpServerCreation: z.boolean().optional(),
mode: z.string().optional(),
modeApiConfigs: z.record(z.string(), z.string()).optional(),
customModes: z.array(modeConfigSchema).optional(),
customModePrompts: customModePromptsSchema.optional(),
customSupportPrompts: customSupportPromptsSchema.optional(),
enhancementApiConfigId: z.string().optional(),
})
export type GlobalSettings = z.infer<typeof globalSettingsSchema>
type GlobalSettingsRecord = Record<Keys<GlobalSettings>, undefined>
const globalSettingsRecord: GlobalSettingsRecord = {
currentApiConfigName: undefined,
listApiConfigMeta: undefined,
pinnedApiConfigs: undefined,
lastShownAnnouncementId: undefined,
customInstructions: undefined,
taskHistory: undefined,
autoApprovalEnabled: undefined,
alwaysAllowReadOnly: undefined,
alwaysAllowReadOnlyOutsideWorkspace: undefined,
alwaysAllowWrite: undefined,
alwaysAllowWriteOutsideWorkspace: undefined,
writeDelayMs: undefined,
alwaysAllowBrowser: undefined,
alwaysApproveResubmit: undefined,
requestDelaySeconds: undefined,
alwaysAllowMcp: undefined,
alwaysAllowModeSwitch: undefined,
alwaysAllowSubtasks: undefined,
alwaysAllowExecute: undefined,
allowedCommands: undefined,
browserToolEnabled: undefined,
browserViewportSize: undefined,
screenshotQuality: undefined,
remoteBrowserEnabled: undefined,
remoteBrowserHost: undefined,
enableCheckpoints: undefined,
checkpointStorage: undefined,
ttsEnabled: undefined,
ttsSpeed: undefined,
soundEnabled: undefined,
soundVolume: undefined,
maxOpenTabsContext: undefined,
maxWorkspaceFiles: undefined,
showRooIgnoredFiles: undefined,
maxReadFileLine: undefined,
terminalOutputLineLimit: undefined,
terminalShellIntegrationTimeout: undefined,
rateLimitSeconds: undefined,
diffEnabled: undefined,
fuzzyMatchThreshold: undefined,
experiments: undefined,
language: undefined,
telemetrySetting: undefined,
mcpEnabled: undefined,
enableMcpServerCreation: undefined,
mode: undefined,
modeApiConfigs: undefined,
customModes: undefined,
customModePrompts: undefined,
customSupportPrompts: undefined,
enhancementApiConfigId: undefined,
}
export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys<GlobalSettings>[]
/**
* RooCodeSettings
*/
export type RooCodeSettings = GlobalSettings & ProviderSettings
/**
* SecretState
*/
export type SecretState = Pick<
ProviderSettings,
| "apiKey"
| "glamaApiKey"
| "openRouterApiKey"
| "awsAccessKey"
| "awsSecretKey"
| "awsSessionToken"
| "openAiApiKey"
| "geminiApiKey"
| "openAiNativeApiKey"
| "deepSeekApiKey"
| "mistralApiKey"
| "unboundApiKey"
| "requestyApiKey"
>
type SecretStateRecord = Record<Keys<SecretState>, undefined>
const secretStateRecord: SecretStateRecord = {
apiKey: undefined,
glamaApiKey: undefined,
openRouterApiKey: undefined,
awsAccessKey: undefined,
awsSecretKey: undefined,
awsSessionToken: undefined,
openAiApiKey: undefined,
geminiApiKey: undefined,
openAiNativeApiKey: undefined,
deepSeekApiKey: undefined,
mistralApiKey: undefined,
unboundApiKey: undefined,
requestyApiKey: undefined,
}
export const SECRET_STATE_KEYS = Object.keys(secretStateRecord) as Keys<SecretState>[]
export const isSecretStateKey = (key: string): key is Keys<SecretState> =>
SECRET_STATE_KEYS.includes(key as Keys<SecretState>)
/**
* GlobalState
*/
export type GlobalState = Omit<RooCodeSettings, Keys<SecretState>>
export const GLOBAL_STATE_KEYS = [...GLOBAL_SETTINGS_KEYS, ...PROVIDER_SETTINGS_KEYS].filter(
(key: Keys<RooCodeSettings>) => !SECRET_STATE_KEYS.includes(key as Keys<SecretState>),
) as Keys<GlobalState>[]
export const isGlobalStateKey = (key: string): key is Keys<GlobalState> =>
GLOBAL_STATE_KEYS.includes(key as Keys<GlobalState>)
/**
* ClineAsk
*/
export const clineAsks = [
"followup",
"command",
"command_output",
"completion_result",
"tool",
"api_req_failed",
"resume_task",
"resume_completed_task",
"mistake_limit_reached",
"browser_action_launch",
"use_mcp_server",
"finishTask",
] as const
export const clineAskSchema = z.enum(clineAsks)
export type ClineAsk = z.infer<typeof clineAskSchema>
// ClineSay
export const clineSays = [
"task",
"error",
"api_req_started",
"api_req_finished",
"api_req_retried",
"api_req_retry_delayed",
"api_req_deleted",
"text",
"reasoning",
"completion_result",
"user_feedback",
"user_feedback_diff",
"command_output",
"tool",
"shell_integration_warning",
"browser_action",
"browser_action_result",
"command",
"mcp_server_request_started",
"mcp_server_response",
"new_task_started",
"new_task",
"checkpoint_saved",
"rooignore_error",
] as const
export const clineSaySchema = z.enum(clineSays)
export type ClineSay = z.infer<typeof clineSaySchema>
/**
* ToolProgressStatus
*/
export const toolProgressStatusSchema = z.object({
icon: z.string().optional(),
text: z.string().optional(),
})
export type ToolProgressStatus = z.infer<typeof toolProgressStatusSchema>
/**
* ClineMessage
*/
export const clineMessageSchema = z.object({
ts: z.number(),
type: z.union([z.literal("ask"), z.literal("say")]),
ask: clineAskSchema.optional(),
say: clineSaySchema.optional(),
text: z.string().optional(),
images: z.array(z.string()).optional(),
partial: z.boolean().optional(),
reasoning: z.string().optional(),
conversationHistoryIndex: z.number().optional(),
checkpoint: z.record(z.string(), z.unknown()).optional(),
progressStatus: toolProgressStatusSchema.optional(),
})
export type ClineMessage = z.infer<typeof clineMessageSchema>
/**
* TokenUsage
*/
export const tokenUsageSchema = z.object({
totalTokensIn: z.number(),
totalTokensOut: z.number(),
totalCacheWrites: z.number().optional(),
totalCacheReads: z.number().optional(),
totalCost: z.number(),
contextTokens: z.number(),
})
export type TokenUsage = z.infer<typeof tokenUsageSchema>
/**
* TypeDefinition
*/
type TypeDefinition = {
schema: z.ZodTypeAny
identifier: string
}
export const typeDefinitions: TypeDefinition[] = [
{ schema: providerSettingsSchema, identifier: "ProviderSettings" },
{ schema: globalSettingsSchema, identifier: "GlobalSettings" },
{ schema: clineMessageSchema, identifier: "ClineMessage" },
{ schema: tokenUsageSchema, identifier: "TokenUsage" },
]

View file

@ -1,14 +1,23 @@
import { ApiConfiguration, ModelInfo } from "./api"
import { HistoryItem } from "./HistoryItem"
import {
ModelInfo,
GlobalSettings,
ApiConfigMeta,
ProviderSettings as ApiConfiguration,
HistoryItem,
ModeConfig,
CheckpointStorage,
TelemetrySetting,
ExperimentId,
ClineAsk,
ClineSay,
ToolProgressStatus,
ClineMessage,
} from "../schemas"
import { McpServer } from "./mcp"
import { GitCommit } from "../utils/git"
import { Mode, ModeConfig } from "./modes"
import { ExperimentId } from "./experiments"
import { CheckpointStorage } from "./checkpoints"
import { TelemetrySetting } from "./TelemetrySetting"
import type { GlobalSettings, ApiConfigMeta, ClineMessage, ClineAsk, ClineSay } from "../exports/roo-code"
import { Mode } from "./modes"
export type { ApiConfigMeta }
export type { ApiConfigMeta, ToolProgressStatus }
export interface LanguageModelChatSelector {
vendor?: string
@ -261,8 +270,3 @@ export interface ClineApiReqInfo {
}
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"
export type ToolProgressStatus = {
icon?: string
text?: string
}

View file

@ -1,3 +1,3 @@
import type { HistoryItem } from "../exports/roo-code"
import type { HistoryItem } from "../schemas"
export type { HistoryItem }

View file

@ -1,4 +1,4 @@
import { ModelInfo, ProviderName, ProviderSettings } from "../exports/roo-code"
import { ModelInfo, ProviderName, ProviderSettings } from "../schemas"
export type { ModelInfo, ProviderName as ApiProvider }

View file

@ -1,5 +1,4 @@
import { ProviderSettings } from "../exports/roo-code"
import { SECRET_STATE_KEYS } from "./globalState"
import { SECRET_STATE_KEYS, ProviderSettings } from "../schemas"
export function checkExistKey(config: ProviderSettings | undefined) {
if (!config) {

View file

@ -1,7 +1,3 @@
import { CheckpointStorage } from "../exports/roo-code"
import { CheckpointStorage, isCheckpointStorage } from "../schemas"
export type { CheckpointStorage }
export const isCheckpointStorage = (value: string): value is CheckpointStorage => {
return value === "task" || value === "workspace"
}
export { type CheckpointStorage, isCheckpointStorage }

View file

@ -1,5 +1,4 @@
import { ExperimentId } from "../exports/roo-code"
import { ExperimentId } from "../schemas"
import { AssertEqual, Equals, Keys, Values } from "../utils/type-fu"
export type { ExperimentId }

View file

@ -1,4 +1,4 @@
import { TokenUsage } from "../exports/roo-code"
import { TokenUsage } from "../schemas"
import { ClineMessage } from "./ExtensionMessage"

View file

@ -1,695 +0,0 @@
import { z } from "zod"
import type {
ProviderName,
CheckpointStorage,
ToolGroup,
Language,
TelemetrySetting,
ProviderSettingsKey,
SecretStateKey,
GlobalStateKey,
ModelInfo,
ApiConfigMeta,
HistoryItem,
GroupEntry,
ModeConfig,
ExperimentId,
ProviderSettings,
GlobalSettings,
} from "../exports/roo-code"
import { Keys, AssertEqual, Equals } from "../utils/type-fu"
/**
* ProviderName
*/
const providerNames: Record<ProviderName, true> = {
anthropic: true,
glama: true,
openrouter: true,
bedrock: true,
vertex: true,
openai: true,
ollama: true,
lmstudio: true,
gemini: true,
"openai-native": true,
deepseek: true,
"vscode-lm": true,
mistral: true,
unbound: true,
requesty: true,
"human-relay": true,
"fake-ai": true,
}
const PROVIDER_NAMES = Object.keys(providerNames) as ProviderName[]
const providerNamesEnum: [ProviderName, ...ProviderName[]] = [
PROVIDER_NAMES[0],
...PROVIDER_NAMES.slice(1).map((p) => p),
]
/**
* CheckpointStorage
*/
const checkpointStorages: Record<CheckpointStorage, true> = {
task: true,
workspace: true,
}
const CHECKPOINT_STORAGES = Object.keys(checkpointStorages) as CheckpointStorage[]
const checkpointStoragesEnum: [CheckpointStorage, ...CheckpointStorage[]] = [
CHECKPOINT_STORAGES[0],
...CHECKPOINT_STORAGES.slice(1).map((p) => p),
]
/**
* ToolGroup
*/
const toolGroups: Record<ToolGroup, true> = {
read: true,
edit: true,
browser: true,
command: true,
mcp: true,
modes: true,
}
const TOOL_GROUPS = Object.keys(toolGroups) as ToolGroup[]
const toolGroupsEnum: [ToolGroup, ...ToolGroup[]] = [TOOL_GROUPS[0], ...TOOL_GROUPS.slice(1).map((p) => p)]
/**
* Language
*/
const languages: Record<Language, true> = {
ca: true,
de: true,
en: true,
es: true,
fr: true,
hi: true,
it: true,
ja: true,
ko: true,
pl: true,
"pt-BR": true,
tr: true,
vi: true,
"zh-CN": true,
"zh-TW": true,
}
const LANGUAGES = Object.keys(languages) as Language[]
const languagesEnum: [Language, ...Language[]] = [LANGUAGES[0], ...LANGUAGES.slice(1).map((p) => p)]
export const isLanguage = (key: string): key is Language => LANGUAGES.includes(key as Language)
/**
* TelemetrySetting
*/
const telemetrySettings: Record<TelemetrySetting, true> = {
unset: true,
enabled: true,
disabled: true,
}
const TELEMETRY_SETTINGS = Object.keys(telemetrySettings) as TelemetrySetting[]
const telemetrySettingsEnum: [TelemetrySetting, ...TelemetrySetting[]] = [
TELEMETRY_SETTINGS[0],
...TELEMETRY_SETTINGS.slice(1).map((p) => p),
]
/**
* ProviderSettingsKey
*/
const providerSettingsKeys: Record<ProviderSettingsKey, true> = {
apiProvider: true,
apiModelId: true,
// Anthropic
apiKey: true,
anthropicBaseUrl: true,
// Glama
glamaApiKey: true,
glamaModelId: true,
glamaModelInfo: true,
// OpenRouter
openRouterApiKey: true,
openRouterModelId: true,
openRouterModelInfo: true,
openRouterBaseUrl: true,
openRouterSpecificProvider: true,
openRouterUseMiddleOutTransform: true,
// AWS Bedrock
awsAccessKey: true,
awsSecretKey: true,
awsSessionToken: true,
awsRegion: true,
awsUseCrossRegionInference: true,
awsUsePromptCache: true,
awspromptCacheId: true,
awsProfile: true,
awsUseProfile: true,
awsCustomArn: true,
// Google Vertex
vertexKeyFile: true,
vertexJsonCredentials: true,
vertexProjectId: true,
vertexRegion: true,
// OpenAI
openAiApiKey: true,
openAiBaseUrl: true,
openAiR1FormatEnabled: true,
openAiModelId: true,
openAiCustomModelInfo: true,
openAiUseAzure: true,
azureApiVersion: true,
openAiStreamingEnabled: true,
// Ollama
ollamaModelId: true,
ollamaBaseUrl: true,
// VS Code LM
vsCodeLmModelSelector: true,
// LM Studio
lmStudioModelId: true,
lmStudioBaseUrl: true,
lmStudioDraftModelId: true,
lmStudioSpeculativeDecodingEnabled: true,
// Gemini
geminiApiKey: true,
googleGeminiBaseUrl: true,
// OpenAI Native
openAiNativeApiKey: true,
// Mistral
mistralApiKey: true,
mistralCodestralUrl: true,
// DeepSeek
deepSeekApiKey: true,
deepSeekBaseUrl: true,
includeMaxTokens: true,
// Unbound
unboundApiKey: true,
unboundModelId: true,
unboundModelInfo: true,
// Requesty
requestyApiKey: true,
requestyModelId: true,
requestyModelInfo: true,
// Claude 3.7 Sonnet Thinking
modelTemperature: true,
modelMaxTokens: true,
modelMaxThinkingTokens: true,
// Fake AI
fakeAi: true,
}
export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsKeys) as ProviderSettingsKey[]
/**
* SecretStateKey
*/
const secretStateKeys: Record<SecretStateKey, true> = {
apiKey: true,
glamaApiKey: true,
openRouterApiKey: true,
awsAccessKey: true,
awsSecretKey: true,
awsSessionToken: true,
openAiApiKey: true,
geminiApiKey: true,
openAiNativeApiKey: true,
deepSeekApiKey: true,
mistralApiKey: true,
unboundApiKey: true,
requestyApiKey: true,
}
export const SECRET_STATE_KEYS = Object.keys(secretStateKeys) as SecretStateKey[]
export const isSecretStateKey = (key: string): key is SecretStateKey =>
SECRET_STATE_KEYS.includes(key as SecretStateKey)
/**
* GlobalStateKey
*/
const globalStateKeys: Record<GlobalStateKey, true> = {
apiProvider: true,
apiModelId: true,
// Anthropic
// apiKey: true,
anthropicBaseUrl: true,
// Glama
// glamaApiKey: true,
glamaModelId: true,
glamaModelInfo: true,
// OpenRouter
// openRouterApiKey: true,
openRouterModelId: true,
openRouterModelInfo: true,
openRouterBaseUrl: true,
openRouterSpecificProvider: true,
openRouterUseMiddleOutTransform: true,
// AWS Bedrock
// awsAccessKey: true,
// awsSecretKey: true,
// awsSessionToken: true,
awsRegion: true,
awsUseCrossRegionInference: true,
awsUsePromptCache: true,
awspromptCacheId: true,
awsProfile: true,
awsUseProfile: true,
awsCustomArn: true,
// Google Vertex
vertexKeyFile: true,
vertexJsonCredentials: true,
vertexProjectId: true,
vertexRegion: true,
// OpenAI
// openAiApiKey: true,
openAiBaseUrl: true,
openAiR1FormatEnabled: true,
openAiModelId: true,
openAiCustomModelInfo: true,
openAiUseAzure: true,
azureApiVersion: true,
openAiStreamingEnabled: true,
// Ollama
ollamaModelId: true,
ollamaBaseUrl: true,
// VS Code LM
vsCodeLmModelSelector: true,
// LM Studio
lmStudioModelId: true,
lmStudioBaseUrl: true,
lmStudioDraftModelId: true,
lmStudioSpeculativeDecodingEnabled: true,
// Gemini
// geminiApiKey: true,
googleGeminiBaseUrl: true,
// OpenAI Native
// openAiNativeApiKey: true,
// Mistral
// mistralApiKey: true,
mistralCodestralUrl: true,
// DeepSeek
// deepSeekApiKey: true,
deepSeekBaseUrl: true,
includeMaxTokens: true,
// Unbound
// unboundApiKey: true,
unboundModelId: true,
unboundModelInfo: true,
// Requesty
// requestyApiKey: true,
requestyModelId: true,
requestyModelInfo: true,
// Claude 3.7 Sonnet Thinking
modelTemperature: true,
modelMaxTokens: true,
modelMaxThinkingTokens: true,
// Fake AI
fakeAi: true,
currentApiConfigName: true,
listApiConfigMeta: true,
pinnedApiConfigs: true,
lastShownAnnouncementId: true,
customInstructions: true,
taskHistory: true,
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
alwaysAllowReadOnlyOutsideWorkspace: true,
alwaysAllowWrite: true,
alwaysAllowWriteOutsideWorkspace: true,
writeDelayMs: true,
alwaysAllowBrowser: true,
alwaysApproveResubmit: true,
requestDelaySeconds: true,
alwaysAllowMcp: true,
alwaysAllowModeSwitch: true,
alwaysAllowSubtasks: true,
alwaysAllowExecute: true,
allowedCommands: true,
browserToolEnabled: true,
browserViewportSize: true,
screenshotQuality: true,
remoteBrowserEnabled: true,
remoteBrowserHost: true,
enableCheckpoints: true,
checkpointStorage: true,
ttsEnabled: true,
ttsSpeed: true,
soundEnabled: true,
soundVolume: true,
maxOpenTabsContext: true,
maxWorkspaceFiles: true,
showRooIgnoredFiles: true,
maxReadFileLine: true,
terminalOutputLineLimit: true,
terminalShellIntegrationTimeout: true,
rateLimitSeconds: true,
diffEnabled: true,
fuzzyMatchThreshold: true,
experiments: true,
language: true,
telemetrySetting: true,
mcpEnabled: true,
enableMcpServerCreation: true,
mode: true,
modeApiConfigs: true,
customModes: true,
customModePrompts: true,
customSupportPrompts: true,
enhancementApiConfigId: true,
}
export const GLOBAL_STATE_KEYS = Object.keys(globalStateKeys) as GlobalStateKey[]
/**
* PassThroughStateKey
*
* TODO: Why is this necessary?
*/
const PASS_THROUGH_STATE_KEYS = ["taskHistory"] as const
type PassThroughStateKey = (typeof PASS_THROUGH_STATE_KEYS)[number]
export const isPassThroughStateKey = (key: string): key is PassThroughStateKey =>
PASS_THROUGH_STATE_KEYS.includes(key as PassThroughStateKey)
/**
* Schemas
*/
/**
* ModelInfo
*/
const modelInfoSchema = z.object({
maxTokens: z.number().optional(),
contextWindow: z.number(),
supportsImages: z.boolean().optional(),
supportsComputerUse: z.boolean().optional(),
supportsPromptCache: z.boolean(),
inputPrice: z.number().optional(),
outputPrice: z.number().optional(),
cacheWritesPrice: z.number().optional(),
cacheReadsPrice: z.number().optional(),
description: z.string().optional(),
reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
thinking: z.boolean().optional(),
})
// Throws a type error if the inferred type of the modelInfoSchema is not equal
// to ModelInfo.
type _AssertModelInfo = AssertEqual<Equals<ModelInfo, z.infer<typeof modelInfoSchema>>>
/**
* ApiConfigMeta
*/
const apiConfigMetaSchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: z.enum(providerNamesEnum).optional(),
})
type _AssertApiConfigMeta = AssertEqual<Equals<ApiConfigMeta, z.infer<typeof apiConfigMetaSchema>>>
/**
* HistoryItem
*/
const historyItemSchema = z.object({
id: z.string(),
number: z.number(),
ts: z.number(),
task: z.string(),
tokensIn: z.number(),
tokensOut: z.number(),
cacheWrites: z.number().optional(),
cacheReads: z.number().optional(),
totalCost: z.number(),
size: z.number().optional(),
})
type _AssertHistoryItem = AssertEqual<Equals<HistoryItem, z.infer<typeof historyItemSchema>>>
/**
* GroupEntry
*/
const groupEntrySchema = z.union([
z.enum(toolGroupsEnum),
z
.tuple([
z.enum(toolGroupsEnum),
z.object({
fileRegex: z.string().optional(),
description: z.string().optional(),
}),
])
.readonly(),
])
type _AssertGroupEntry = AssertEqual<Equals<GroupEntry, z.infer<typeof groupEntrySchema>>>
/**
* ModeConfig
*/
const modeConfigSchema = z.object({
slug: z.string(),
name: z.string(),
roleDefinition: z.string(),
customInstructions: z.string().optional(),
groups: z.array(groupEntrySchema).readonly(),
source: z.enum(["global", "project"]).optional(),
})
type _AssertModeConfig = AssertEqual<Equals<ModeConfig, z.infer<typeof modeConfigSchema>>>
/**
* ExperimentId
*/
const experimentsSchema = z.object({
experimentalDiffStrategy: z.boolean(),
search_and_replace: z.boolean(),
insert_content: z.boolean(),
powerSteering: z.boolean(),
multi_search_and_replace: z.boolean(),
})
// Throws a type error if the inferred type of the experimentsSchema is not
// equal to ExperimentId.
type _AssertExperiments = AssertEqual<Equals<ExperimentId, Keys<z.infer<typeof experimentsSchema>>>>
/**
* GlobalSettings
*/
export const globalSettingsSchema = z.object({
currentApiConfigName: z.string().optional(),
listApiConfigMeta: z.array(apiConfigMetaSchema).optional(),
pinnedApiConfigs: z.record(z.string(), z.boolean()).optional(),
lastShownAnnouncementId: z.string().optional(),
customInstructions: z.string().optional(),
taskHistory: z.array(historyItemSchema).optional(),
autoApprovalEnabled: z.boolean().optional(),
alwaysAllowReadOnly: z.boolean().optional(),
alwaysAllowReadOnlyOutsideWorkspace: z.boolean().optional(),
alwaysAllowWrite: z.boolean().optional(),
alwaysAllowWriteOutsideWorkspace: z.boolean().optional(),
writeDelayMs: z.number().optional(),
alwaysAllowBrowser: z.boolean().optional(),
alwaysApproveResubmit: z.boolean().optional(),
requestDelaySeconds: z.number().optional(),
alwaysAllowMcp: z.boolean().optional(),
alwaysAllowModeSwitch: z.boolean().optional(),
alwaysAllowSubtasks: z.boolean().optional(),
alwaysAllowExecute: z.boolean().optional(),
allowedCommands: z.array(z.string()).optional(),
browserToolEnabled: z.boolean().optional(),
browserViewportSize: z.string().optional(),
screenshotQuality: z.number().optional(),
remoteBrowserEnabled: z.boolean().optional(),
remoteBrowserHost: z.string().optional(),
enableCheckpoints: z.boolean().optional(),
checkpointStorage: z.enum(checkpointStoragesEnum).optional(),
ttsEnabled: z.boolean().optional(),
ttsSpeed: z.number().optional(),
soundEnabled: z.boolean().optional(),
soundVolume: z.number().optional(),
maxOpenTabsContext: z.number().optional(),
maxWorkspaceFiles: z.number().optional(),
showRooIgnoredFiles: z.boolean().optional(),
maxReadFileLine: z.number().optional(),
terminalOutputLineLimit: z.number().optional(),
terminalShellIntegrationTimeout: z.number().optional(),
rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
experiments: experimentsSchema.optional(),
language: z.enum(languagesEnum).optional(),
telemetrySetting: z.enum(telemetrySettingsEnum).optional(),
mcpEnabled: z.boolean().optional(),
enableMcpServerCreation: z.boolean().optional(),
mode: z.string().optional(),
modeApiConfigs: z.record(z.string(), z.string()).optional(),
customModes: z.array(modeConfigSchema).optional(),
customModePrompts: z
.record(
z.string(),
z
.object({
roleDefinition: z.string().optional(),
customInstructions: z.string().optional(),
})
.optional(),
)
.optional(),
customSupportPrompts: z.record(z.string(), z.string().optional()).optional(),
enhancementApiConfigId: z.string().optional(),
})
// Throws a type error if the inferred type of the globalSettingsSchema is not
// equal to GlobalSettings.
type _AssertGlobalSettings = AssertEqual<Equals<GlobalSettings, z.infer<typeof globalSettingsSchema>>>
/**
* ProviderSettings
*/
export const providerSettingsSchema = z.object({
apiProvider: z.enum(providerNamesEnum).optional(),
// Anthropic
apiModelId: z.string().optional(),
apiKey: z.string().optional(),
anthropicBaseUrl: z.string().optional(),
// Glama
glamaModelId: z.string().optional(),
glamaModelInfo: modelInfoSchema.optional(),
glamaApiKey: z.string().optional(),
// OpenRouter
openRouterApiKey: z.string().optional(),
openRouterModelId: z.string().optional(),
openRouterModelInfo: modelInfoSchema.optional(),
openRouterBaseUrl: z.string().optional(),
openRouterSpecificProvider: z.string().optional(),
// AWS Bedrock
awsAccessKey: z.string().optional(),
awsSecretKey: z.string().optional(),
awsSessionToken: z.string().optional(),
awsRegion: z.string().optional(),
awsUseCrossRegionInference: z.boolean().optional(),
awsUsePromptCache: z.boolean().optional(),
awspromptCacheId: z.string().optional(),
awsProfile: z.string().optional(),
awsUseProfile: z.boolean().optional(),
awsCustomArn: z.string().optional(),
// Google Vertex
vertexKeyFile: z.string().optional(),
vertexJsonCredentials: z.string().optional(),
vertexProjectId: z.string().optional(),
vertexRegion: z.string().optional(),
// OpenAI
openAiBaseUrl: z.string().optional(),
openAiApiKey: z.string().optional(),
openAiR1FormatEnabled: z.boolean().optional(),
openAiModelId: z.string().optional(),
openAiCustomModelInfo: modelInfoSchema.optional(),
openAiUseAzure: z.boolean().optional(),
// Ollama
ollamaModelId: z.string().optional(),
ollamaBaseUrl: z.string().optional(),
// VS Code LM
vsCodeLmModelSelector: z
.object({
vendor: z.string().optional(),
family: z.string().optional(),
version: z.string().optional(),
id: z.string().optional(),
})
.optional(),
// LM Studio
lmStudioModelId: z.string().optional(),
lmStudioBaseUrl: z.string().optional(),
lmStudioDraftModelId: z.string().optional(),
lmStudioSpeculativeDecodingEnabled: z.boolean().optional(),
// Gemini
geminiApiKey: z.string().optional(),
googleGeminiBaseUrl: z.string().optional(),
// OpenAI Native
openAiNativeApiKey: z.string().optional(),
// Mistral
mistralApiKey: z.string().optional(),
mistralCodestralUrl: z.string().optional(),
// Azure
azureApiVersion: z.string().optional(),
// OpenRouter
openRouterUseMiddleOutTransform: z.boolean().optional(),
openAiStreamingEnabled: z.boolean().optional(),
// DeepSeek
deepSeekBaseUrl: z.string().optional(),
deepSeekApiKey: z.string().optional(),
// Unbound
unboundApiKey: z.string().optional(),
unboundModelId: z.string().optional(),
unboundModelInfo: modelInfoSchema.optional(),
// Requesty
requestyApiKey: z.string().optional(),
requestyModelId: z.string().optional(),
requestyModelInfo: modelInfoSchema.optional(),
// Claude 3.7 Sonnet Thinking
modelTemperature: z.number().nullish(),
modelMaxTokens: z.number().optional(),
modelMaxThinkingTokens: z.number().optional(),
// Generic
includeMaxTokens: z.boolean().optional(),
// Fake AI
fakeAi: z.unknown().optional(),
})
// Throws a type error if the inferred type of the providerSettingsSchema is not
// equal to ProviderSettings.
type _AssertProviderSettings = AssertEqual<Equals<ProviderSettings, z.infer<typeof providerSettingsSchema>>>

View file

@ -1,7 +1,6 @@
import { type Language } from "../exports/roo-code"
import { isLanguage } from "./globalState"
import { type Language, isLanguage } from "../schemas"
export type { Language }
export { type Language, isLanguage }
/**
* Language name mapping from ISO codes to full language names

View file

@ -1,6 +1,6 @@
import * as vscode from "vscode"
import { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts } from "../exports/roo-code"
import { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts } from "../schemas"
import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups"
import { addCustomInstructions } from "../core/prompts/sections/custom-instructions"

View file

@ -1,4 +1,4 @@
import type { ToolGroup } from "../exports/roo-code"
import type { ToolGroup } from "../schemas"
// Define tool group configuration
export type ToolGroupConfig = {

View file

@ -19,7 +19,7 @@ import {
ModeConfig,
GroupEntry,
} from "../../../../src/shared/modes"
import { CustomModeSchema } from "../../../../src/core/config/CustomModesSchema"
import { modeConfigSchema } from "../../../../src/schemas"
import { supportPrompt, SupportPromptType } from "../../../../src/shared/support-prompt"
import { TOOL_GROUPS, ToolGroup } from "../../../../src/shared/tool-groups"
@ -223,7 +223,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
}
// Validate the mode against the schema
const result = CustomModeSchema.safeParse(newMode)
const result = modeConfigSchema.safeParse(newMode)
if (!result.success) {
// Map Zod errors to specific fields
result.error.errors.forEach((error) => {