mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Merge main to resolve conflicts in ChatView.spec.tsx
This commit is contained in:
commit
02b40ba25b
197 changed files with 8219 additions and 2693 deletions
43
CHANGELOG.md
43
CHANGELOG.md
|
|
@ -1,5 +1,48 @@
|
|||
# Roo Code Changelog
|
||||
|
||||
## [3.36.2] - 2025-12-04
|
||||
|
||||

|
||||
|
||||
- Restrict GPT-5 tool set to apply_patch for improved compatibility (PR #9853 by @hannesrudolph)
|
||||
- Add dynamic settings support for Roo models from API, allowing model-specific configurations to be fetched dynamically (PR #9852 by @hannesrudolph)
|
||||
- Fix: Resolve Chutes provider model fetching issue (PR #9854 by @cte)
|
||||
|
||||
## [3.36.1] - 2025-12-04
|
||||
|
||||

|
||||
|
||||
- Add MessageManager layer for centralized history coordination, fixing message synchronization issues (PR #9842 by @hannesrudolph)
|
||||
- Fix: Prevent cascading truncation loop by only truncating visible messages (PR #9844 by @hannesrudolph)
|
||||
- Fix: Handle unknown/invalid native tool calls to prevent extension freeze (PR #9834 by @daniel-lxs)
|
||||
- Always enable reasoning for models that require it (PR #9836 by @cte)
|
||||
- ChatView: Smoother stick-to-bottom behavior during streaming (PR #8999 by @hannesrudolph)
|
||||
- UX: Improved error messages and documentation links (PR #9777 by @brunobergher)
|
||||
- Fix: Overly round follow-up question suggestions styling (PR #9829 by @brunobergher)
|
||||
- Add symlink support for slash commands in .roo/commands folder (PR #9838 by @mrubens)
|
||||
- Ignore input to the execa terminal process for safer command execution (PR #9827 by @mrubens)
|
||||
- Be safer about large file reads (PR #9843 by @jr)
|
||||
- Add gpt-5.1-codex-max model to OpenAI provider (PR #9848 by @hannesrudolph)
|
||||
- Evals UI: Add filtering, bulk delete, tool consolidation, and run notes (PR #9837 by @hannesrudolph)
|
||||
- Evals UI: Add multi-model launch and UI improvements (PR #9845 by @hannesrudolph)
|
||||
- Web: New pricing page (PR #9821 by @brunobergher)
|
||||
|
||||
## [3.36.0] - 2025-12-04
|
||||
|
||||

|
||||
|
||||
- Fix: Restore context when rewinding after condense (#8295 by @hannesrudolph, PR #9665 by @hannesrudolph)
|
||||
- Add reasoning_details support to Roo provider for enhanced model reasoning visibility (PR #9796 by @app/roomote)
|
||||
- Default to native tools for all models in the Roo provider for improved performance (PR #9811 by @mrubens)
|
||||
- Enable search_and_replace for Minimax models (PR #9780 by @mrubens)
|
||||
- Fix: Resolve Vercel AI Gateway model fetching issues (PR #9791 by @cte)
|
||||
- Fix: Apply conservative max tokens for Cerebras provider (PR #9804 by @sebastiand-cerebras)
|
||||
- Fix: Remove omission detection logic to eliminate false positives (#9785 by @Michaelzag, PR #9787 by @app/roomote)
|
||||
- Refactor: Remove deprecated insert_content tool (PR #9751 by @daniel-lxs)
|
||||
- Chore: Hide parallel tool calls experiment and disable feature (PR #9798 by @hannesrudolph)
|
||||
- Update next.js documentation site dependencies (PR #9799 by @jr)
|
||||
- Fix: Correct download count display on homepage (PR #9807 by @mrubens)
|
||||
|
||||
## [3.35.5] - 2025-12-03
|
||||
|
||||
- Feat: Add provider routing selection for OpenRouter embeddings (#9144 by @SannidhyaSah, PR #9693 by @SannidhyaSah)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import {
|
|||
exerciseLanguages,
|
||||
createRun as _createRun,
|
||||
deleteRun as _deleteRun,
|
||||
updateRun as _updateRun,
|
||||
getIncompleteRuns as _getIncompleteRuns,
|
||||
deleteRunsByIds as _deleteRunsByIds,
|
||||
createTask,
|
||||
getExercisesForLanguage,
|
||||
} from "@roo-code/evals"
|
||||
|
|
@ -20,6 +23,9 @@ import {
|
|||
import { CreateRun } from "@/lib/schemas"
|
||||
import { redisClient } from "@/lib/server/redis"
|
||||
|
||||
// Storage base path for eval logs
|
||||
const EVALS_STORAGE_PATH = "/tmp/evals/runs"
|
||||
|
||||
const EVALS_REPO_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../../../evals")
|
||||
|
||||
export async function createRun({ suite, exercises = [], timeout, iterations = 1, ...values }: CreateRun) {
|
||||
|
|
@ -214,3 +220,150 @@ export async function killRun(runId: number): Promise<KillRunResult> {
|
|||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
export type DeleteIncompleteRunsResult = {
|
||||
success: boolean
|
||||
deletedCount: number
|
||||
deletedRunIds: number[]
|
||||
storageErrors: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all incomplete runs (runs without a taskMetricsId/final score).
|
||||
* Removes both database records and storage folders.
|
||||
*/
|
||||
export async function deleteIncompleteRuns(): Promise<DeleteIncompleteRunsResult> {
|
||||
const storageErrors: string[] = []
|
||||
|
||||
// Get all incomplete runs
|
||||
const incompleteRuns = await _getIncompleteRuns()
|
||||
const runIds = incompleteRuns.map((run) => run.id)
|
||||
|
||||
if (runIds.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
deletedCount: 0,
|
||||
deletedRunIds: [],
|
||||
storageErrors: [],
|
||||
}
|
||||
}
|
||||
|
||||
// Delete storage folders for each run
|
||||
for (const runId of runIds) {
|
||||
const storagePath = path.join(EVALS_STORAGE_PATH, String(runId))
|
||||
try {
|
||||
if (fs.existsSync(storagePath)) {
|
||||
fs.rmSync(storagePath, { recursive: true, force: true })
|
||||
console.log(`Deleted storage folder: ${storagePath}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete storage folder ${storagePath}:`, error)
|
||||
storageErrors.push(`Failed to delete storage for run ${runId}`)
|
||||
}
|
||||
|
||||
// Also try to clear Redis state for any potentially running incomplete runs
|
||||
try {
|
||||
const redis = await redisClient()
|
||||
await redis.del(`heartbeat:${runId}`)
|
||||
await redis.del(`runners:${runId}`)
|
||||
} catch (error) {
|
||||
// Non-critical error, just log it
|
||||
console.error(`Failed to clear Redis state for run ${runId}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
await _deleteRunsByIds(runIds)
|
||||
|
||||
revalidatePath("/runs")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
deletedCount: runIds.length,
|
||||
deletedRunIds: runIds,
|
||||
storageErrors,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of incomplete runs (for UI display)
|
||||
*/
|
||||
export async function getIncompleteRunsCount(): Promise<number> {
|
||||
const incompleteRuns = await _getIncompleteRuns()
|
||||
return incompleteRuns.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all runs older than 30 days.
|
||||
* Removes both database records and storage folders.
|
||||
*/
|
||||
export async function deleteOldRuns(): Promise<DeleteIncompleteRunsResult> {
|
||||
const storageErrors: string[] = []
|
||||
|
||||
// Get all runs older than 30 days
|
||||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||
const { getRuns } = await import("@roo-code/evals")
|
||||
const allRuns = await getRuns()
|
||||
const oldRuns = allRuns.filter((run) => run.createdAt < thirtyDaysAgo)
|
||||
const runIds = oldRuns.map((run) => run.id)
|
||||
|
||||
if (runIds.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
deletedCount: 0,
|
||||
deletedRunIds: [],
|
||||
storageErrors: [],
|
||||
}
|
||||
}
|
||||
|
||||
// Delete storage folders for each run
|
||||
for (const runId of runIds) {
|
||||
const storagePath = path.join(EVALS_STORAGE_PATH, String(runId))
|
||||
try {
|
||||
if (fs.existsSync(storagePath)) {
|
||||
fs.rmSync(storagePath, { recursive: true, force: true })
|
||||
console.log(`Deleted storage folder: ${storagePath}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete storage folder ${storagePath}:`, error)
|
||||
storageErrors.push(`Failed to delete storage for run ${runId}`)
|
||||
}
|
||||
|
||||
// Also try to clear Redis state
|
||||
try {
|
||||
const redis = await redisClient()
|
||||
await redis.del(`heartbeat:${runId}`)
|
||||
await redis.del(`runners:${runId}`)
|
||||
} catch (error) {
|
||||
// Non-critical error, just log it
|
||||
console.error(`Failed to clear Redis state for run ${runId}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
await _deleteRunsByIds(runIds)
|
||||
|
||||
revalidatePath("/runs")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
deletedCount: runIds.length,
|
||||
deletedRunIds: runIds,
|
||||
storageErrors,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the description of a run.
|
||||
*/
|
||||
export async function updateRunDescription(runId: number, description: string | null): Promise<{ success: boolean }> {
|
||||
try {
|
||||
await _updateRun(runId, { description })
|
||||
revalidatePath("/runs")
|
||||
revalidatePath(`/runs/${runId}`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error("Failed to update run description:", error)
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useQuery } from "@tanstack/react-query"
|
|||
import { useForm, FormProvider } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { toast } from "sonner"
|
||||
import { X, Rocket, Check, ChevronsUpDown, SlidersHorizontal, Info } from "lucide-react"
|
||||
import { X, Rocket, Check, ChevronsUpDown, SlidersHorizontal, Info, Plus, Minus } from "lucide-react"
|
||||
|
||||
import {
|
||||
globalSettingsSchema,
|
||||
|
|
@ -16,7 +16,6 @@ import {
|
|||
getModelId,
|
||||
type ProviderSettings,
|
||||
type GlobalSettings,
|
||||
type ReasoningEffort,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { createRun } from "@/actions/runs"
|
||||
|
|
@ -44,7 +43,6 @@ import {
|
|||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
|
|
@ -66,11 +64,6 @@ import {
|
|||
PopoverTrigger,
|
||||
Slider,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
|
|
@ -84,21 +77,38 @@ type ImportedSettings = {
|
|||
currentApiConfigName: string
|
||||
}
|
||||
|
||||
// Type for a model selection entry
|
||||
type ModelSelection = {
|
||||
id: string
|
||||
model: string
|
||||
popoverOpen: boolean
|
||||
}
|
||||
|
||||
// Type for a config selection entry (for import mode)
|
||||
type ConfigSelection = {
|
||||
id: string
|
||||
configName: string
|
||||
popoverOpen: boolean
|
||||
}
|
||||
|
||||
export function NewRun() {
|
||||
const router = useRouter()
|
||||
|
||||
const [provider, setModelSource] = useState<"roo" | "openrouter" | "other">("other")
|
||||
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
|
||||
const [useNativeToolProtocol, setUseNativeToolProtocol] = useState(true)
|
||||
const [useMultipleNativeToolCalls, setUseMultipleNativeToolCalls] = useState(false)
|
||||
const [reasoningEffort, setReasoningEffort] = useState<ReasoningEffort | "">("")
|
||||
const [commandExecutionTimeout, setCommandExecutionTimeout] = useState(20)
|
||||
const [terminalShellIntegrationTimeout, setTerminalShellIntegrationTimeout] = useState(30) // seconds
|
||||
|
||||
// State for imported settings with config selection
|
||||
// State for multiple model selections
|
||||
const [modelSelections, setModelSelections] = useState<ModelSelection[]>([
|
||||
{ id: crypto.randomUUID(), model: "", popoverOpen: false },
|
||||
])
|
||||
|
||||
// State for imported settings with multiple config selections
|
||||
const [importedSettings, setImportedSettings] = useState<ImportedSettings | null>(null)
|
||||
const [selectedConfigName, setSelectedConfigName] = useState<string>("")
|
||||
const [configPopoverOpen, setConfigPopoverOpen] = useState(false)
|
||||
const [configSelections, setConfigSelections] = useState<ConfigSelection[]>([
|
||||
{ id: crypto.randomUUID(), configName: "", popoverOpen: false },
|
||||
])
|
||||
|
||||
const openRouter = useOpenRouterModels()
|
||||
const rooCodeCloud = useRooCodeCloudModels()
|
||||
|
|
@ -134,7 +144,7 @@ export function NewRun() {
|
|||
formState: { isSubmitting },
|
||||
} = form
|
||||
|
||||
const [model, suite, settings] = watch(["model", "suite", "settings", "concurrency"])
|
||||
const [suite, settings] = watch(["suite", "settings", "concurrency"])
|
||||
|
||||
// Load settings from localStorage on mount
|
||||
useEffect(() => {
|
||||
|
|
@ -250,6 +260,60 @@ export function NewRun() {
|
|||
[getExercisesForLanguage, selectedExercises],
|
||||
)
|
||||
|
||||
// Add a new model selection
|
||||
const addModelSelection = useCallback(() => {
|
||||
setModelSelections((prev) => [...prev, { id: crypto.randomUUID(), model: "", popoverOpen: false }])
|
||||
}, [])
|
||||
|
||||
// Remove a model selection
|
||||
const removeModelSelection = useCallback((id: string) => {
|
||||
setModelSelections((prev) => prev.filter((s) => s.id !== id))
|
||||
}, [])
|
||||
|
||||
// Update a model selection
|
||||
const updateModelSelection = useCallback(
|
||||
(id: string, model: string) => {
|
||||
setModelSelections((prev) => prev.map((s) => (s.id === id ? { ...s, model, popoverOpen: false } : s)))
|
||||
// Also set the form model field for validation (use first non-empty model)
|
||||
setValue("model", model)
|
||||
},
|
||||
[setValue],
|
||||
)
|
||||
|
||||
// Toggle popover for a model selection
|
||||
const toggleModelPopover = useCallback((id: string, open: boolean) => {
|
||||
setModelSelections((prev) => prev.map((s) => (s.id === id ? { ...s, popoverOpen: open } : s)))
|
||||
}, [])
|
||||
|
||||
// Add a new config selection
|
||||
const addConfigSelection = useCallback(() => {
|
||||
setConfigSelections((prev) => [...prev, { id: crypto.randomUUID(), configName: "", popoverOpen: false }])
|
||||
}, [])
|
||||
|
||||
// Remove a config selection
|
||||
const removeConfigSelection = useCallback((id: string) => {
|
||||
setConfigSelections((prev) => prev.filter((s) => s.id !== id))
|
||||
}, [])
|
||||
|
||||
// Update a config selection
|
||||
const updateConfigSelection = useCallback(
|
||||
(id: string, configName: string) => {
|
||||
setConfigSelections((prev) => prev.map((s) => (s.id === id ? { ...s, configName, popoverOpen: false } : s)))
|
||||
// Also update the form settings for the first config (for validation)
|
||||
if (importedSettings) {
|
||||
const providerSettings = importedSettings.apiConfigs[configName] ?? {}
|
||||
setValue("model", getModelId(providerSettings) ?? "")
|
||||
setValue("settings", { ...EVALS_SETTINGS, ...providerSettings, ...importedSettings.globalSettings })
|
||||
}
|
||||
},
|
||||
[importedSettings, setValue],
|
||||
)
|
||||
|
||||
// Toggle popover for a config selection
|
||||
const toggleConfigPopover = useCallback((id: string, open: boolean) => {
|
||||
setConfigSelections((prev) => prev.map((s) => (s.id === id ? { ...s, popoverOpen: open } : s)))
|
||||
}, [])
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (values: CreateRun) => {
|
||||
try {
|
||||
|
|
@ -259,74 +323,104 @@ export function NewRun() {
|
|||
return
|
||||
}
|
||||
|
||||
// Build experiments settings
|
||||
const experimentsSettings = useMultipleNativeToolCalls
|
||||
? { experiments: { multipleNativeToolCalls: true } }
|
||||
: {}
|
||||
// Determine which selections to use based on provider
|
||||
const selectionsToLaunch: { model: string; configName?: string }[] = []
|
||||
|
||||
if (provider === "openrouter") {
|
||||
values.settings = {
|
||||
...(values.settings || {}),
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: model,
|
||||
toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000, // Convert to ms
|
||||
...experimentsSettings,
|
||||
if (provider === "other") {
|
||||
// For import mode, use config selections
|
||||
for (const config of configSelections) {
|
||||
if (config.configName) {
|
||||
selectionsToLaunch.push({ model: "", configName: config.configName })
|
||||
}
|
||||
}
|
||||
} else if (provider === "roo") {
|
||||
values.settings = {
|
||||
...(values.settings || {}),
|
||||
apiProvider: "roo",
|
||||
apiModelId: model,
|
||||
toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000, // Convert to ms
|
||||
...experimentsSettings,
|
||||
...(reasoningEffort
|
||||
? {
|
||||
enableReasoningEffort: true,
|
||||
reasoningEffort: reasoningEffort as ReasoningEffort,
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
} else if (provider === "other" && values.settings) {
|
||||
// For imported settings, merge in experiments and tool protocol
|
||||
values.settings = {
|
||||
...values.settings,
|
||||
toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000, // Convert to ms
|
||||
...experimentsSettings,
|
||||
} else {
|
||||
// For openrouter/roo, use model selections
|
||||
for (const selection of modelSelections) {
|
||||
if (selection.model) {
|
||||
selectionsToLaunch.push({ model: selection.model })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { id } = await createRun(values)
|
||||
router.push(`/runs/${id}`)
|
||||
if (selectionsToLaunch.length === 0) {
|
||||
toast.error("Please select at least one model or config")
|
||||
return
|
||||
}
|
||||
|
||||
// Show launching toast
|
||||
const totalRuns = selectionsToLaunch.length
|
||||
toast.info(totalRuns > 1 ? `Launching ${totalRuns} runs (every 20 seconds)...` : "Launching run...")
|
||||
|
||||
// Launch runs with 20-second delay between each
|
||||
for (let i = 0; i < selectionsToLaunch.length; i++) {
|
||||
const selection = selectionsToLaunch[i]!
|
||||
|
||||
// Wait 20 seconds between runs (except for the first one)
|
||||
if (i > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 20000))
|
||||
}
|
||||
|
||||
const runValues = { ...values }
|
||||
|
||||
if (provider === "openrouter") {
|
||||
runValues.model = selection.model
|
||||
runValues.settings = {
|
||||
...(runValues.settings || {}),
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: selection.model,
|
||||
toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000,
|
||||
}
|
||||
} else if (provider === "roo") {
|
||||
runValues.model = selection.model
|
||||
runValues.settings = {
|
||||
...(runValues.settings || {}),
|
||||
apiProvider: "roo",
|
||||
apiModelId: selection.model,
|
||||
toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000,
|
||||
}
|
||||
} else if (provider === "other" && selection.configName && importedSettings) {
|
||||
const providerSettings = importedSettings.apiConfigs[selection.configName] ?? {}
|
||||
runValues.model = getModelId(providerSettings) ?? ""
|
||||
runValues.settings = {
|
||||
...EVALS_SETTINGS,
|
||||
...providerSettings,
|
||||
...importedSettings.globalSettings,
|
||||
toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await createRun(runValues)
|
||||
toast.success(`Run ${i + 1}/${totalRuns} launched`)
|
||||
} catch (e) {
|
||||
toast.error(`Run ${i + 1} failed: ${e instanceof Error ? e.message : "Unknown error"}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate back to main evals UI
|
||||
router.push("/")
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "An unknown error occurred.")
|
||||
}
|
||||
},
|
||||
[
|
||||
provider,
|
||||
model,
|
||||
modelSelections,
|
||||
configSelections,
|
||||
importedSettings,
|
||||
router,
|
||||
useNativeToolProtocol,
|
||||
useMultipleNativeToolCalls,
|
||||
reasoningEffort,
|
||||
commandExecutionTimeout,
|
||||
terminalShellIntegrationTimeout,
|
||||
],
|
||||
)
|
||||
|
||||
const onSelectModel = useCallback(
|
||||
(model: string) => {
|
||||
setValue("model", model)
|
||||
setModelPopoverOpen(false)
|
||||
},
|
||||
[setValue, setModelPopoverOpen],
|
||||
)
|
||||
|
||||
const onImportSettings = useCallback(
|
||||
async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
|
|
@ -355,9 +449,9 @@ export function NewRun() {
|
|||
currentApiConfigName: providerProfiles.currentApiConfigName,
|
||||
})
|
||||
|
||||
// Default to the current config
|
||||
// Default to the current config for the first selection
|
||||
const defaultConfigName = providerProfiles.currentApiConfigName
|
||||
setSelectedConfigName(defaultConfigName)
|
||||
setConfigSelections([{ id: crypto.randomUUID(), configName: defaultConfigName, popoverOpen: false }])
|
||||
|
||||
// Apply the default config
|
||||
const providerSettings = providerProfiles.apiConfigs[defaultConfigName] ?? {}
|
||||
|
|
@ -373,22 +467,6 @@ export function NewRun() {
|
|||
[clearErrors, setValue],
|
||||
)
|
||||
|
||||
const onSelectConfig = useCallback(
|
||||
(configName: string) => {
|
||||
if (!importedSettings) {
|
||||
return
|
||||
}
|
||||
|
||||
setSelectedConfigName(configName)
|
||||
setConfigPopoverOpen(false)
|
||||
|
||||
const providerSettings = importedSettings.apiConfigs[configName] ?? {}
|
||||
setValue("model", getModelId(providerSettings) ?? "")
|
||||
setValue("settings", { ...EVALS_SETTINGS, ...providerSettings, ...importedSettings.globalSettings })
|
||||
},
|
||||
[importedSettings, setValue],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormProvider {...form}>
|
||||
|
|
@ -428,59 +506,91 @@ export function NewRun() {
|
|||
onChange={onImportSettings}
|
||||
/>
|
||||
|
||||
{importedSettings && Object.keys(importedSettings.apiConfigs).length > 1 && (
|
||||
<div className="space-y-1">
|
||||
<Label>API Config</Label>
|
||||
<Popover open={configPopoverOpen} onOpenChange={setConfigPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="input"
|
||||
role="combobox"
|
||||
aria-expanded={configPopoverOpen}
|
||||
className="flex items-center justify-between w-full">
|
||||
<div>{selectedConfigName || "Select config"}</div>
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search configs..."
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No config found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{Object.keys(importedSettings.apiConfigs).map(
|
||||
(configName) => (
|
||||
<CommandItem
|
||||
key={configName}
|
||||
value={configName}
|
||||
onSelect={onSelectConfig}>
|
||||
{configName}
|
||||
{configName ===
|
||||
importedSettings.currentApiConfigName && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(default)
|
||||
</span>
|
||||
)}
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto size-4",
|
||||
configName ===
|
||||
selectedConfigName
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
{importedSettings && Object.keys(importedSettings.apiConfigs).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>API Configs</Label>
|
||||
{configSelections.map((selection, index) => (
|
||||
<div key={selection.id} className="flex items-center gap-2">
|
||||
<Popover
|
||||
open={selection.popoverOpen}
|
||||
onOpenChange={(open) =>
|
||||
toggleConfigPopover(selection.id, open)
|
||||
}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="input"
|
||||
role="combobox"
|
||||
aria-expanded={selection.popoverOpen}
|
||||
className="flex items-center justify-between flex-1">
|
||||
<div>{selection.configName || "Select config"}</div>
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search configs..."
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No config found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{Object.keys(
|
||||
importedSettings.apiConfigs,
|
||||
).map((configName) => (
|
||||
<CommandItem
|
||||
key={configName}
|
||||
value={configName}
|
||||
onSelect={() =>
|
||||
updateConfigSelection(
|
||||
selection.id,
|
||||
configName,
|
||||
)
|
||||
}>
|
||||
{configName}
|
||||
{configName ===
|
||||
importedSettings.currentApiConfigName && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(default)
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
),
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto size-4",
|
||||
configName ===
|
||||
selection.configName
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{index === configSelections.length - 1 ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={addConfigSelection}
|
||||
className="shrink-0">
|
||||
<Plus className="size-4" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => removeConfigSelection(selection.id)}
|
||||
className="shrink-0">
|
||||
<Minus className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -501,18 +611,6 @@ export function NewRun() {
|
|||
/>
|
||||
<span className="text-sm">Use Native Tool Calls</span>
|
||||
</label>
|
||||
<label
|
||||
htmlFor="multipleNativeToolCalls-other"
|
||||
className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
id="multipleNativeToolCalls-other"
|
||||
checked={useMultipleNativeToolCalls}
|
||||
onCheckedChange={(checked: boolean) =>
|
||||
setUseMultipleNativeToolCalls(checked)
|
||||
}
|
||||
/>
|
||||
<span className="text-sm">Use Multiple Native Tool Calls</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -522,110 +620,103 @@ export function NewRun() {
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Popover open={modelPopoverOpen} onOpenChange={setModelPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="input"
|
||||
role="combobox"
|
||||
aria-expanded={modelPopoverOpen}
|
||||
className="flex items-center justify-between">
|
||||
<div>
|
||||
{models?.find(({ id }) => id === model)?.name || `Select`}
|
||||
</div>
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
||||
<Command filter={onFilter}>
|
||||
<CommandInput
|
||||
placeholder="Search"
|
||||
value={searchValue}
|
||||
onValueChange={setSearchValue}
|
||||
className="h-9"
|
||||
<div className="space-y-2">
|
||||
{modelSelections.map((selection, index) => (
|
||||
<div key={selection.id} className="flex items-center gap-2">
|
||||
<Popover
|
||||
open={selection.popoverOpen}
|
||||
onOpenChange={(open) => toggleModelPopover(selection.id, open)}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="input"
|
||||
role="combobox"
|
||||
aria-expanded={selection.popoverOpen}
|
||||
className="flex items-center justify-between flex-1">
|
||||
<div>
|
||||
{models?.find(({ id }) => id === selection.model)
|
||||
?.name || `Select`}
|
||||
</div>
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
||||
<Command filter={onFilter}>
|
||||
<CommandInput
|
||||
placeholder="Search"
|
||||
value={searchValue}
|
||||
onValueChange={setSearchValue}
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No model found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{models?.map(({ id, name }) => (
|
||||
<CommandItem
|
||||
key={id}
|
||||
value={id}
|
||||
onSelect={() =>
|
||||
updateModelSelection(
|
||||
selection.id,
|
||||
id,
|
||||
)
|
||||
}>
|
||||
{name}
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto text-accent group-data-[selected=true]:text-accent-foreground size-4",
|
||||
id === selection.model
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{index === modelSelections.length - 1 ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={addModelSelection}
|
||||
className="shrink-0">
|
||||
<Plus className="size-4" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => removeModelSelection(selection.id)}
|
||||
className="shrink-0">
|
||||
<Minus className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-4 rounded-md bg-muted/30 border border-border space-y-3">
|
||||
<Label className="text-sm font-medium text-muted-foreground">
|
||||
Tool Protocol Options
|
||||
</Label>
|
||||
<div className="flex flex-col gap-2.5 pl-1">
|
||||
<label
|
||||
htmlFor="native"
|
||||
className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
id="native"
|
||||
checked={useNativeToolProtocol}
|
||||
onCheckedChange={(checked: boolean) =>
|
||||
setUseNativeToolProtocol(checked)
|
||||
}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No model found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{models?.map(({ id, name }) => (
|
||||
<CommandItem
|
||||
key={id}
|
||||
value={id}
|
||||
onSelect={onSelectModel}>
|
||||
{name}
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto text-accent group-data-[selected=true]:text-accent-foreground size-4",
|
||||
id === model ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<div className="mt-4 p-4 rounded-md bg-muted/30 border border-border space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium text-muted-foreground">
|
||||
Tool Protocol Options
|
||||
</Label>
|
||||
<div className="flex flex-col gap-2.5 pl-1">
|
||||
<label
|
||||
htmlFor="native"
|
||||
className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
id="native"
|
||||
checked={useNativeToolProtocol}
|
||||
onCheckedChange={(checked: boolean) =>
|
||||
setUseNativeToolProtocol(checked)
|
||||
}
|
||||
/>
|
||||
<span className="text-sm">Use Native Tool Calls</span>
|
||||
</label>
|
||||
<label
|
||||
htmlFor="multipleNativeToolCalls"
|
||||
className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
id="multipleNativeToolCalls"
|
||||
checked={useMultipleNativeToolCalls}
|
||||
onCheckedChange={(checked: boolean) =>
|
||||
setUseMultipleNativeToolCalls(checked)
|
||||
}
|
||||
/>
|
||||
<span className="text-sm">Use Multiple Native Tool Calls</span>
|
||||
</label>
|
||||
</div>
|
||||
<span className="text-sm">Use Native Tool Calls</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{provider === "roo" && (
|
||||
<div className="space-y-2 pt-2 border-t border-border">
|
||||
<Label className="text-sm font-medium text-muted-foreground">
|
||||
Reasoning Effort
|
||||
</Label>
|
||||
<Select
|
||||
value={reasoningEffort || "none"}
|
||||
onValueChange={(value) =>
|
||||
setReasoningEffort(
|
||||
value === "none" ? "" : (value as ReasoningEffort),
|
||||
)
|
||||
}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="None (default)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">None (default)</SelectItem>
|
||||
<SelectItem value="low">Low</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="high">High</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground pl-1">
|
||||
When set, enableReasoningEffort will be automatically enabled
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -732,147 +823,153 @@ export function NewRun() {
|
|||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="concurrency"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Concurrency</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
min={CONCURRENCY_MIN}
|
||||
max={CONCURRENCY_MAX}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value[0])
|
||||
localStorage.setItem("evals-concurrency", String(value[0]))
|
||||
}}
|
||||
/>
|
||||
<div>{field.value}</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Concurrency, Timeout, and Iterations in a 3-column row */}
|
||||
<div className="grid grid-cols-3 gap-4 py-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="concurrency"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Concurrency</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
min={CONCURRENCY_MIN}
|
||||
max={CONCURRENCY_MAX}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value[0])
|
||||
localStorage.setItem("evals-concurrency", String(value[0]))
|
||||
}}
|
||||
/>
|
||||
<div className="w-6 text-right">{field.value}</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="timeout"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Timeout (Minutes)</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
min={TIMEOUT_MIN}
|
||||
max={TIMEOUT_MAX}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value[0])
|
||||
localStorage.setItem("evals-timeout", String(value[0]))
|
||||
}}
|
||||
/>
|
||||
<div>{field.value}</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="timeout"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Timeout (Minutes)</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
min={TIMEOUT_MIN}
|
||||
max={TIMEOUT_MAX}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value[0])
|
||||
localStorage.setItem("evals-timeout", String(value[0]))
|
||||
}}
|
||||
/>
|
||||
<div className="w-6 text-right">{field.value}</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="iterations"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Iterations per Exercise</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
min={ITERATIONS_MIN}
|
||||
max={ITERATIONS_MAX}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value[0])
|
||||
}}
|
||||
/>
|
||||
<div>{field.value}</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>Run each exercise multiple times to compare results</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="iterations"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Iterations</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
min={ITERATIONS_MIN}
|
||||
max={ITERATIONS_MAX}
|
||||
step={1}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value[0])
|
||||
}}
|
||||
/>
|
||||
<div className="w-6 text-right">{field.value}</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormItem className="py-5">
|
||||
<div className="flex items-center gap-1">
|
||||
<Label>Terminal Command Timeout (Seconds)</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="size-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p>
|
||||
Maximum time in seconds to wait for terminal command execution to complete
|
||||
before timing out. This applies to commands run via the execute_command tool.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[commandExecutionTimeout]}
|
||||
min={20}
|
||||
max={60}
|
||||
step={1}
|
||||
onValueChange={([value]) => {
|
||||
if (value !== undefined) {
|
||||
setCommandExecutionTimeout(value)
|
||||
localStorage.setItem("evals-command-execution-timeout", String(value))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="w-8 text-right">{commandExecutionTimeout}</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
{/* Terminal timeouts in a 2-column row */}
|
||||
<div className="grid grid-cols-2 gap-4 py-5">
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-1">
|
||||
<Label>Command Timeout (Seconds)</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="size-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p>
|
||||
Maximum time in seconds to wait for terminal command execution to complete
|
||||
before timing out. This applies to commands run via the execute_command
|
||||
tool.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[commandExecutionTimeout]}
|
||||
min={20}
|
||||
max={60}
|
||||
step={1}
|
||||
onValueChange={([value]) => {
|
||||
if (value !== undefined) {
|
||||
setCommandExecutionTimeout(value)
|
||||
localStorage.setItem("evals-command-execution-timeout", String(value))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="w-8 text-right">{commandExecutionTimeout}</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem className="py-5">
|
||||
<div className="flex items-center gap-1">
|
||||
<Label>Shell Integration Timeout (Seconds)</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="size-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p>
|
||||
Maximum time in seconds to wait for shell integration to initialize when opening
|
||||
a new terminal.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[terminalShellIntegrationTimeout]}
|
||||
min={30}
|
||||
max={60}
|
||||
step={1}
|
||||
onValueChange={([value]) => {
|
||||
if (value !== undefined) {
|
||||
setTerminalShellIntegrationTimeout(value)
|
||||
localStorage.setItem("evals-shell-integration-timeout", String(value))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="w-8 text-right">{terminalShellIntegrationTimeout}</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-1">
|
||||
<Label>Shell Integration Timeout (Seconds)</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="size-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p>
|
||||
Maximum time in seconds to wait for shell integration to initialize when
|
||||
opening a new terminal.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
value={[terminalShellIntegrationTimeout]}
|
||||
min={30}
|
||||
max={60}
|
||||
step={1}
|
||||
onValueChange={([value]) => {
|
||||
if (value !== undefined) {
|
||||
setTerminalShellIntegrationTimeout(value)
|
||||
localStorage.setItem("evals-shell-integration-timeout", String(value))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="w-8 text-right">{terminalShellIntegrationTimeout}</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { useCallback, useState, useRef } from "react"
|
|||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { toast } from "sonner"
|
||||
import { Ellipsis, ClipboardList, Copy, Check, LoaderCircle, Trash, Settings, FileDown } from "lucide-react"
|
||||
import { Ellipsis, ClipboardList, Copy, Check, LoaderCircle, Trash, Settings, FileDown, StickyNote } from "lucide-react"
|
||||
|
||||
import type { Run as EvalsRun, TaskMetrics as EvalsTaskMetrics } from "@roo-code/evals"
|
||||
import type { ToolName } from "@roo-code/types"
|
||||
|
||||
import { deleteRun } from "@/actions/runs"
|
||||
import { deleteRun, updateRunDescription } from "@/actions/runs"
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDateTime,
|
||||
|
|
@ -20,6 +20,10 @@ import {
|
|||
Button,
|
||||
TableCell,
|
||||
TableRow,
|
||||
Textarea,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
|
|
@ -34,6 +38,7 @@ import {
|
|||
AlertDialogTitle,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
ScrollArea,
|
||||
|
|
@ -43,16 +48,41 @@ type RunProps = {
|
|||
run: EvalsRun
|
||||
taskMetrics: EvalsTaskMetrics | null
|
||||
toolColumns: ToolName[]
|
||||
consolidatedToolColumns: string[]
|
||||
}
|
||||
|
||||
export function Run({ run, taskMetrics, toolColumns }: RunProps) {
|
||||
export function Run({ run, taskMetrics, toolColumns, consolidatedToolColumns }: RunProps) {
|
||||
const router = useRouter()
|
||||
const [deleteRunId, setDeleteRunId] = useState<number>()
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [isExportingLogs, setIsExportingLogs] = useState(false)
|
||||
const [showNotesDialog, setShowNotesDialog] = useState(false)
|
||||
const [editingDescription, setEditingDescription] = useState(run.description ?? "")
|
||||
const [isSavingNotes, setIsSavingNotes] = useState(false)
|
||||
const continueRef = useRef<HTMLButtonElement>(null)
|
||||
const { isPending, copyRun, copied } = useCopyRun(run.id)
|
||||
|
||||
const hasDescription = Boolean(run.description && run.description.trim().length > 0)
|
||||
|
||||
const handleSaveDescription = useCallback(async () => {
|
||||
setIsSavingNotes(true)
|
||||
try {
|
||||
const result = await updateRunDescription(run.id, editingDescription.trim() || null)
|
||||
if (result.success) {
|
||||
toast.success("Description saved")
|
||||
setShowNotesDialog(false)
|
||||
router.refresh()
|
||||
} else {
|
||||
toast.error("Failed to save description")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error saving description:", error)
|
||||
toast.error("Failed to save description")
|
||||
} finally {
|
||||
setIsSavingNotes(false)
|
||||
}
|
||||
}, [run.id, editingDescription, router])
|
||||
|
||||
const onExportFailedLogs = useCallback(async () => {
|
||||
if (run.failed === 0) {
|
||||
toast.error("No failed tasks to export")
|
||||
|
|
@ -140,6 +170,68 @@ export function Run({ run, taskMetrics, toolColumns }: RunProps) {
|
|||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
{consolidatedToolColumns.length > 0 && (
|
||||
<TableCell className="text-xs text-center">
|
||||
{taskMetrics?.toolUsage ? (
|
||||
(() => {
|
||||
// Calculate aggregated stats for consolidated tools
|
||||
let totalAttempts = 0
|
||||
let totalFailures = 0
|
||||
const breakdown: Array<{ tool: string; attempts: number; rate: string }> = []
|
||||
|
||||
for (const toolName of consolidatedToolColumns) {
|
||||
const usage = taskMetrics.toolUsage[toolName as ToolName]
|
||||
if (usage) {
|
||||
totalAttempts += usage.attempts
|
||||
totalFailures += usage.failures
|
||||
const rate =
|
||||
usage.attempts > 0
|
||||
? `${Math.round(((usage.attempts - usage.failures) / usage.attempts) * 100)}%`
|
||||
: "0%"
|
||||
breakdown.push({ tool: toolName, attempts: usage.attempts, rate })
|
||||
}
|
||||
}
|
||||
|
||||
const consolidatedRate =
|
||||
totalAttempts > 0 ? ((totalAttempts - totalFailures) / totalAttempts) * 100 : 100
|
||||
const rateColor =
|
||||
consolidatedRate === 100
|
||||
? "text-muted-foreground"
|
||||
: consolidatedRate >= 80
|
||||
? "text-yellow-500"
|
||||
: "text-red-500"
|
||||
|
||||
return totalAttempts > 0 ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="font-medium">{totalAttempts}</span>
|
||||
<span className={rateColor}>{Math.round(consolidatedRate)}%</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="text-xs">
|
||||
<div className="font-semibold mb-1">Consolidated Tools:</div>
|
||||
{breakdown.map(({ tool, attempts, rate }) => (
|
||||
<div key={tool} className="flex justify-between gap-4">
|
||||
<span>{tool}:</span>
|
||||
<span>
|
||||
{attempts} ({rate})
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)
|
||||
})()
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
{toolColumns.map((toolName) => {
|
||||
const usage = taskMetrics?.toolUsage?.[toolName]
|
||||
const successRate =
|
||||
|
|
@ -166,80 +258,107 @@ export function Run({ run, taskMetrics, toolColumns }: RunProps) {
|
|||
<TableCell>{taskMetrics && formatCurrency(taskMetrics.cost)}</TableCell>
|
||||
<TableCell>{taskMetrics && formatDuration(taskMetrics.duration)}</TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()}>
|
||||
<DropdownMenu>
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<DropdownMenuTrigger data-dropdown-trigger>
|
||||
<Ellipsis />
|
||||
</DropdownMenuTrigger>
|
||||
</Button>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`/runs/${run.id}`}>
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Note Icon */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={hasDescription ? "" : "opacity-30 hover:opacity-60"}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setEditingDescription(run.description ?? "")
|
||||
setShowNotesDialog(true)
|
||||
}}>
|
||||
<StickyNote className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[300px]">
|
||||
{hasDescription ? (
|
||||
<div className="whitespace-pre-wrap">{run.description}</div>
|
||||
) : (
|
||||
<div className="text-muted-foreground">No description. Click to add one.</div>
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{/* More Actions Menu */}
|
||||
<DropdownMenu>
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<DropdownMenuTrigger data-dropdown-trigger>
|
||||
<Ellipsis />
|
||||
</DropdownMenuTrigger>
|
||||
</Button>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`/runs/${run.id}`}>
|
||||
<div className="flex items-center gap-1">
|
||||
<ClipboardList />
|
||||
<div>View Tasks</div>
|
||||
</div>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{run.settings && (
|
||||
<DropdownMenuItem onClick={() => setShowSettings(true)}>
|
||||
<div className="flex items-center gap-1">
|
||||
<Settings />
|
||||
<div>View Settings</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{run.taskMetricsId && (
|
||||
<DropdownMenuItem onClick={() => copyRun()} disabled={isPending || copied}>
|
||||
<div className="flex items-center gap-1">
|
||||
{isPending ? (
|
||||
<>
|
||||
<LoaderCircle className="animate-spin" />
|
||||
Copying...
|
||||
</>
|
||||
) : copied ? (
|
||||
<>
|
||||
<Check />
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy />
|
||||
Copy to Production
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{run.failed > 0 && (
|
||||
<DropdownMenuItem onClick={onExportFailedLogs} disabled={isExportingLogs}>
|
||||
<div className="flex items-center gap-1">
|
||||
{isExportingLogs ? (
|
||||
<>
|
||||
<LoaderCircle className="animate-spin" />
|
||||
Exporting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FileDown />
|
||||
Export Failed Logs
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDeleteRunId(run.id)
|
||||
setTimeout(() => continueRef.current?.focus(), 0)
|
||||
}}>
|
||||
<div className="flex items-center gap-1">
|
||||
<ClipboardList />
|
||||
<div>View Tasks</div>
|
||||
</div>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{run.settings && (
|
||||
<DropdownMenuItem onClick={() => setShowSettings(true)}>
|
||||
<div className="flex items-center gap-1">
|
||||
<Settings />
|
||||
<div>View Settings</div>
|
||||
<Trash />
|
||||
<div>Delete</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{run.taskMetricsId && (
|
||||
<DropdownMenuItem onClick={() => copyRun()} disabled={isPending || copied}>
|
||||
<div className="flex items-center gap-1">
|
||||
{isPending ? (
|
||||
<>
|
||||
<LoaderCircle className="animate-spin" />
|
||||
Copying...
|
||||
</>
|
||||
) : copied ? (
|
||||
<>
|
||||
<Check />
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy />
|
||||
Copy to Production
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{run.failed > 0 && (
|
||||
<DropdownMenuItem onClick={onExportFailedLogs} disabled={isExportingLogs}>
|
||||
<div className="flex items-center gap-1">
|
||||
{isExportingLogs ? (
|
||||
<>
|
||||
<LoaderCircle className="animate-spin" />
|
||||
Exporting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FileDown />
|
||||
Export Failed Logs
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDeleteRunId(run.id)
|
||||
setTimeout(() => continueRef.current?.focus(), 0)
|
||||
}}>
|
||||
<div className="flex items-center gap-1">
|
||||
<Trash />
|
||||
<div>Delete</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<AlertDialog open={!!deleteRunId} onOpenChange={() => setDeleteRunId(undefined)}>
|
||||
|
|
@ -268,6 +387,39 @@ export function Run({ run, taskMetrics, toolColumns }: RunProps) {
|
|||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Notes/Description Dialog */}
|
||||
<Dialog open={showNotesDialog} onOpenChange={setShowNotesDialog}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Run Description</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<Textarea
|
||||
placeholder="Add a description or notes for this run..."
|
||||
value={editingDescription}
|
||||
onChange={(e) => setEditingDescription(e.target.value)}
|
||||
rows={4}
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowNotesDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSaveDescription} disabled={isSavingNotes}>
|
||||
{isSavingNotes ? (
|
||||
<>
|
||||
<LoaderCircle className="h-4 w-4 mr-2 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
"Save"
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,45 @@
|
|||
"use client"
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ArrowDown, ArrowUp, ArrowUpDown, Rocket } from "lucide-react"
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
ArrowUpDown,
|
||||
Combine,
|
||||
Ellipsis,
|
||||
LoaderCircle,
|
||||
Rocket,
|
||||
RotateCcw,
|
||||
Trash2,
|
||||
X,
|
||||
} from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import type { Run, TaskMetrics } from "@roo-code/evals"
|
||||
import type { ToolName } from "@roo-code/types"
|
||||
|
||||
import { deleteIncompleteRuns, deleteOldRuns } from "@/actions/runs"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
MultiSelect,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
|
|
@ -26,6 +57,41 @@ type RunWithTaskMetrics = Run & { taskMetrics: TaskMetrics | null }
|
|||
type SortColumn = "model" | "provider" | "passed" | "failed" | "percent" | "cost" | "duration" | "createdAt"
|
||||
type SortDirection = "asc" | "desc"
|
||||
|
||||
type TimeframeOption = "all" | "24h" | "7d" | "30d" | "90d"
|
||||
|
||||
const TIMEFRAME_OPTIONS: { value: TimeframeOption; label: string }[] = [
|
||||
{ value: "all", label: "All time" },
|
||||
{ value: "24h", label: "Last 24 hours" },
|
||||
{ value: "7d", label: "Last 7 days" },
|
||||
{ value: "30d", label: "Last 30 days" },
|
||||
{ value: "90d", label: "Last 90 days" },
|
||||
]
|
||||
|
||||
// LocalStorage keys
|
||||
const STORAGE_KEYS = {
|
||||
TIMEFRAME: "evals-runs-timeframe",
|
||||
MODEL_FILTER: "evals-runs-model-filter",
|
||||
PROVIDER_FILTER: "evals-runs-provider-filter",
|
||||
CONSOLIDATED_TOOLS: "evals-runs-consolidated-tools",
|
||||
}
|
||||
|
||||
function getTimeframeStartDate(timeframe: TimeframeOption): Date | null {
|
||||
if (timeframe === "all") return null
|
||||
const now = new Date()
|
||||
switch (timeframe) {
|
||||
case "24h":
|
||||
return new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
case "7d":
|
||||
return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
case "30d":
|
||||
return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
case "90d":
|
||||
return new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Generate abbreviation from tool name (e.g., "read_file" -> "RF", "list_code_definition_names" -> "LCDN")
|
||||
function getToolAbbreviation(toolName: string): string {
|
||||
return toolName
|
||||
|
|
@ -54,6 +120,109 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
const [sortColumn, setSortColumn] = useState<SortColumn | null>("createdAt")
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>("desc")
|
||||
|
||||
// Filter state - initialize from localStorage
|
||||
const [timeframeFilter, setTimeframeFilter] = useState<TimeframeOption>(() => {
|
||||
if (typeof window === "undefined") return "all"
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.TIMEFRAME)
|
||||
return (stored as TimeframeOption) || "all"
|
||||
})
|
||||
const [modelFilter, setModelFilter] = useState<string[]>(() => {
|
||||
if (typeof window === "undefined") return []
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.MODEL_FILTER)
|
||||
return stored ? JSON.parse(stored) : []
|
||||
})
|
||||
const [providerFilter, setProviderFilter] = useState<string[]>(() => {
|
||||
if (typeof window === "undefined") return []
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.PROVIDER_FILTER)
|
||||
return stored ? JSON.parse(stored) : []
|
||||
})
|
||||
|
||||
// Tool column consolidation state - initialize from localStorage
|
||||
const [consolidatedToolColumns, setConsolidatedToolColumns] = useState<string[]>(() => {
|
||||
if (typeof window === "undefined") return []
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.CONSOLIDATED_TOOLS)
|
||||
return stored ? JSON.parse(stored) : []
|
||||
})
|
||||
|
||||
// Delete runs state
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
const [showDeleteOldConfirm, setShowDeleteOldConfirm] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
|
||||
// Persist filters to localStorage
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEYS.TIMEFRAME, timeframeFilter)
|
||||
}, [timeframeFilter])
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEYS.MODEL_FILTER, JSON.stringify(modelFilter))
|
||||
}, [modelFilter])
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEYS.PROVIDER_FILTER, JSON.stringify(providerFilter))
|
||||
}, [providerFilter])
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEYS.CONSOLIDATED_TOOLS, JSON.stringify(consolidatedToolColumns))
|
||||
}, [consolidatedToolColumns])
|
||||
|
||||
// Count incomplete runs (runs without taskMetricsId)
|
||||
const incompleteRunsCount = useMemo(() => {
|
||||
return runs.filter((run) => run.taskMetrics === null).length
|
||||
}, [runs])
|
||||
|
||||
// Count runs older than 30 days
|
||||
const oldRunsCount = useMemo(() => {
|
||||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||
return runs.filter((run) => run.createdAt < thirtyDaysAgo).length
|
||||
}, [runs])
|
||||
|
||||
const handleDeleteIncompleteRuns = useCallback(async () => {
|
||||
setIsDeleting(true)
|
||||
try {
|
||||
const result = await deleteIncompleteRuns()
|
||||
if (result.success) {
|
||||
toast.success(`Deleted ${result.deletedCount} incomplete run${result.deletedCount !== 1 ? "s" : ""}`)
|
||||
if (result.storageErrors.length > 0) {
|
||||
toast.warning(`Some storage folders could not be deleted: ${result.storageErrors.length} errors`)
|
||||
}
|
||||
router.refresh()
|
||||
} else {
|
||||
toast.error("Failed to delete incomplete runs")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting incomplete runs:", error)
|
||||
toast.error("Failed to delete incomplete runs")
|
||||
} finally {
|
||||
setIsDeleting(false)
|
||||
setShowDeleteConfirm(false)
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const handleDeleteOldRuns = useCallback(async () => {
|
||||
setIsDeleting(true)
|
||||
try {
|
||||
const result = await deleteOldRuns()
|
||||
if (result.success) {
|
||||
toast.success(
|
||||
`Deleted ${result.deletedCount} run${result.deletedCount !== 1 ? "s" : ""} older than 30 days`,
|
||||
)
|
||||
if (result.storageErrors.length > 0) {
|
||||
toast.warning(`Some storage folders could not be deleted: ${result.storageErrors.length} errors`)
|
||||
}
|
||||
router.refresh()
|
||||
} else {
|
||||
toast.error("Failed to delete old runs")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting old runs:", error)
|
||||
toast.error("Failed to delete old runs")
|
||||
} finally {
|
||||
setIsDeleting(false)
|
||||
setShowDeleteOldConfirm(false)
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const handleSort = (column: SortColumn) => {
|
||||
if (sortColumn === column) {
|
||||
setSortDirection(sortDirection === "asc" ? "desc" : "asc")
|
||||
|
|
@ -63,11 +232,59 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
}
|
||||
}
|
||||
|
||||
// Collect all unique tool names from all runs and sort by total attempts
|
||||
const toolColumns = useMemo<ToolName[]>(() => {
|
||||
// Derive unique models and providers from runs
|
||||
const modelOptions = useMemo(() => {
|
||||
const models = new Set<string>()
|
||||
for (const run of runs) {
|
||||
if (run.model) models.add(run.model)
|
||||
}
|
||||
return Array.from(models)
|
||||
.sort()
|
||||
.map((model) => ({ label: model, value: model }))
|
||||
}, [runs])
|
||||
|
||||
const providerOptions = useMemo(() => {
|
||||
const providers = new Set<string>()
|
||||
for (const run of runs) {
|
||||
const provider = run.settings?.apiProvider
|
||||
if (provider) providers.add(provider)
|
||||
}
|
||||
return Array.from(providers)
|
||||
.sort()
|
||||
.map((provider) => ({ label: provider, value: provider }))
|
||||
}, [runs])
|
||||
|
||||
// Filter runs based on filter state
|
||||
const filteredRuns = useMemo(() => {
|
||||
return runs.filter((run) => {
|
||||
// Timeframe filter
|
||||
const timeframeStart = getTimeframeStartDate(timeframeFilter)
|
||||
if (timeframeStart && run.createdAt < timeframeStart) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Model filter
|
||||
if (modelFilter.length > 0 && !modelFilter.includes(run.model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Provider filter
|
||||
if (providerFilter.length > 0) {
|
||||
const provider = run.settings?.apiProvider
|
||||
if (!provider || !providerFilter.includes(provider)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}, [runs, timeframeFilter, modelFilter, providerFilter])
|
||||
|
||||
// Collect all unique tool names from filtered runs and sort by total attempts
|
||||
const allToolColumns = useMemo<ToolName[]>(() => {
|
||||
const toolTotals = new Map<ToolName, number>()
|
||||
|
||||
for (const run of runs) {
|
||||
for (const run of filteredRuns) {
|
||||
if (run.taskMetrics?.toolUsage) {
|
||||
for (const [toolName, usage] of Object.entries(run.taskMetrics.toolUsage)) {
|
||||
const tool = toolName as ToolName
|
||||
|
|
@ -81,13 +298,32 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
return Array.from(toolTotals.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([name]): ToolName => name)
|
||||
}, [runs])
|
||||
}, [filteredRuns])
|
||||
|
||||
// Sort runs based on current sort column and direction
|
||||
// Tool column options for the consolidation dropdown
|
||||
const toolColumnOptions = useMemo(() => {
|
||||
return allToolColumns.map((tool) => ({
|
||||
label: tool,
|
||||
value: tool,
|
||||
}))
|
||||
}, [allToolColumns])
|
||||
|
||||
// Separate consolidated and individual tool columns
|
||||
const individualToolColumns = useMemo(() => {
|
||||
return allToolColumns.filter((tool) => !consolidatedToolColumns.includes(tool))
|
||||
}, [allToolColumns, consolidatedToolColumns])
|
||||
|
||||
// Create a "consolidated" column if any tools are selected for consolidation
|
||||
const hasConsolidatedColumn = consolidatedToolColumns.length > 0
|
||||
|
||||
// Use individualToolColumns for rendering
|
||||
const toolColumns = individualToolColumns
|
||||
|
||||
// Sort filtered runs based on current sort column and direction
|
||||
const sortedRuns = useMemo(() => {
|
||||
if (!sortColumn) return runs
|
||||
if (!sortColumn) return filteredRuns
|
||||
|
||||
return [...runs].sort((a, b) => {
|
||||
return [...filteredRuns].sort((a, b) => {
|
||||
let aVal: string | number | Date | null = null
|
||||
let bVal: string | number | Date | null = null
|
||||
|
||||
|
|
@ -139,14 +375,170 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
|
||||
return sortDirection === "asc" ? comparison : -comparison
|
||||
})
|
||||
}, [runs, sortColumn, sortDirection])
|
||||
}, [filteredRuns, sortColumn, sortDirection])
|
||||
|
||||
// Calculate colSpan for empty state (7 base columns + dynamic tools + 3 end columns)
|
||||
const totalColumns = 7 + toolColumns.length + 3
|
||||
// Calculate colSpan for empty state (7 base columns + dynamic tools + consolidated column + 3 end columns)
|
||||
const totalColumns = 7 + toolColumns.length + (hasConsolidatedColumn ? 1 : 0) + 3
|
||||
|
||||
// Check if any filters or settings are active
|
||||
const hasActiveFilters = timeframeFilter !== "all" || modelFilter.length > 0 || providerFilter.length > 0
|
||||
const hasConsolidatedTools = consolidatedToolColumns.length > 0
|
||||
const hasAnyCustomization = hasActiveFilters || hasConsolidatedTools
|
||||
|
||||
const clearAllFilters = () => {
|
||||
setTimeframeFilter("all")
|
||||
setModelFilter([])
|
||||
setProviderFilter([])
|
||||
}
|
||||
|
||||
const resetAll = () => {
|
||||
setTimeframeFilter("all")
|
||||
setModelFilter([])
|
||||
setProviderFilter([])
|
||||
setConsolidatedToolColumns([])
|
||||
localStorage.removeItem(STORAGE_KEYS.TIMEFRAME)
|
||||
localStorage.removeItem(STORAGE_KEYS.MODEL_FILTER)
|
||||
localStorage.removeItem(STORAGE_KEYS.PROVIDER_FILTER)
|
||||
localStorage.removeItem(STORAGE_KEYS.CONSOLIDATED_TOOLS)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table className="border border-t-0">
|
||||
{/* Filter Controls */}
|
||||
<div className="flex items-center gap-4 p-4 border border-b-0 rounded-t-md bg-muted/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">Timeframe:</span>
|
||||
<Select
|
||||
value={timeframeFilter}
|
||||
onValueChange={(value) => setTimeframeFilter(value as TimeframeOption)}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TIMEFRAME_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">Model:</span>
|
||||
<MultiSelect
|
||||
options={modelOptions}
|
||||
value={modelFilter}
|
||||
onValueChange={setModelFilter}
|
||||
placeholder="All models"
|
||||
className="w-[200px]"
|
||||
maxCount={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">Provider:</span>
|
||||
<MultiSelect
|
||||
options={providerOptions}
|
||||
value={providerFilter}
|
||||
onValueChange={setProviderFilter}
|
||||
placeholder="All providers"
|
||||
className="w-[180px]"
|
||||
maxCount={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-2">
|
||||
<Combine className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium text-muted-foreground">Consolidate:</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Select tool columns to consolidate into a combined column</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="relative min-w-[100px] w-fit max-w-[140px]">
|
||||
<div className={consolidatedToolColumns.length > 0 ? "[&>div>div]:invisible" : ""}>
|
||||
<MultiSelect
|
||||
options={toolColumnOptions}
|
||||
value={consolidatedToolColumns}
|
||||
onValueChange={setConsolidatedToolColumns}
|
||||
placeholder="None"
|
||||
className="w-full min-w-[100px]"
|
||||
maxCount={0}
|
||||
popoverAutoWidth
|
||||
footer={
|
||||
hasAnyCustomization && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full justify-start text-muted-foreground hover:text-foreground"
|
||||
onClick={resetAll}>
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
Reset all filters & consolidation
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{consolidatedToolColumns.length > 0 && (
|
||||
<div className="absolute inset-0 flex items-center px-3 pointer-events-none">
|
||||
<span className="text-sm font-medium whitespace-nowrap">
|
||||
{consolidatedToolColumns.length} tool
|
||||
{consolidatedToolColumns.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasActiveFilters && (
|
||||
<Button variant="ghost" size="sm" onClick={clearAllFilters}>
|
||||
<X className="h-4 w-4 mr-1" />
|
||||
Clear filters
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
{/* Bulk Actions Menu */}
|
||||
{(incompleteRunsCount > 0 || oldRunsCount > 0) && (
|
||||
<DropdownMenu>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<DropdownMenuTrigger disabled={isDeleting}>
|
||||
<Ellipsis className="h-4 w-4" />
|
||||
</DropdownMenuTrigger>
|
||||
</Button>
|
||||
<DropdownMenuContent align="end">
|
||||
{incompleteRunsCount > 0 && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
disabled={isDeleting}
|
||||
className="text-destructive focus:text-destructive">
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete {incompleteRunsCount} incomplete run
|
||||
{incompleteRunsCount !== 1 ? "s" : ""}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{oldRunsCount > 0 && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => setShowDeleteOldConfirm(true)}
|
||||
disabled={isDeleting}
|
||||
className="text-destructive focus:text-destructive">
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete {oldRunsCount} run{oldRunsCount !== 1 ? "s" : ""} over 30d
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{filteredRuns.length} of {runs.length} runs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table className="border border-t-0 rounded-t-none">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
|
|
@ -188,6 +580,23 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
</div>
|
||||
</TableHead>
|
||||
<TableHead>Tokens</TableHead>
|
||||
{hasConsolidatedColumn && (
|
||||
<TableHead className="text-xs text-center">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Combine className="h-3 w-3 inline" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="text-xs">
|
||||
<div className="font-semibold mb-1">Consolidated Tools:</div>
|
||||
{consolidatedToolColumns.map((tool) => (
|
||||
<div key={tool}>{tool}</div>
|
||||
))}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TableHead>
|
||||
)}
|
||||
{toolColumns.map((toolName) => (
|
||||
<TableHead key={toolName} className="text-xs text-center">
|
||||
<Tooltip>
|
||||
|
|
@ -214,16 +623,34 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
<TableBody>
|
||||
{sortedRuns.length ? (
|
||||
sortedRuns.map(({ taskMetrics, ...run }) => (
|
||||
<Row key={run.id} run={run} taskMetrics={taskMetrics} toolColumns={toolColumns} />
|
||||
<Row
|
||||
key={run.id}
|
||||
run={run}
|
||||
taskMetrics={taskMetrics}
|
||||
toolColumns={toolColumns}
|
||||
consolidatedToolColumns={consolidatedToolColumns}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={totalColumns} className="text-center">
|
||||
No eval runs yet.
|
||||
<Button variant="link" onClick={() => router.push("/runs/new")}>
|
||||
Launch
|
||||
</Button>
|
||||
one now.
|
||||
<TableCell colSpan={totalColumns} className="text-center py-8">
|
||||
{runs.length === 0 ? (
|
||||
<>
|
||||
No eval runs yet.
|
||||
<Button variant="link" onClick={() => router.push("/runs/new")}>
|
||||
Launch
|
||||
</Button>
|
||||
one now.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
No runs match the current filters.
|
||||
<Button variant="link" onClick={clearAllFilters}>
|
||||
Clear filters
|
||||
</Button>
|
||||
to see all runs.
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
|
@ -235,6 +662,70 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
|
|||
onClick={() => router.push("/runs/new")}>
|
||||
<Rocket className="size-6" />
|
||||
</Button>
|
||||
|
||||
{/* Delete Incomplete Runs Confirmation Dialog */}
|
||||
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Incomplete Runs</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete {incompleteRunsCount} incomplete run
|
||||
{incompleteRunsCount !== 1 ? "s" : ""}? This will permanently remove all database records
|
||||
and storage folders for these runs. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDeleteIncompleteRuns}
|
||||
disabled={isDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<LoaderCircle className="h-4 w-4 mr-2 animate-spin" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Delete {incompleteRunsCount} run{incompleteRunsCount !== 1 ? "s" : ""}
|
||||
</>
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Delete Old Runs Confirmation Dialog */}
|
||||
<AlertDialog open={showDeleteOldConfirm} onOpenChange={setShowDeleteOldConfirm}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Old Runs</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete {oldRunsCount} run{oldRunsCount !== 1 ? "s" : ""} older than
|
||||
30 days? This will permanently remove all database records and storage folders for these
|
||||
runs. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDeleteOldRuns}
|
||||
disabled={isDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<LoaderCircle className="h-4 w-4 mr-2 animate-spin" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Delete {oldRunsCount} run{oldRunsCount !== 1 ? "s" : ""}
|
||||
</>
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,18 @@ interface MultiSelectProps extends React.HTMLAttributes<HTMLDivElement>, Variant
|
|||
* Optional, can be used to add custom styles.
|
||||
*/
|
||||
className?: string
|
||||
|
||||
/**
|
||||
* If true, popover width will auto-size to content instead of matching trigger width.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
popoverAutoWidth?: boolean
|
||||
|
||||
/**
|
||||
* Optional footer content to render at the bottom of the popover.
|
||||
* Useful for adding reset buttons or other actions.
|
||||
*/
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
export const MultiSelect = React.forwardRef<HTMLDivElement, MultiSelectProps>(
|
||||
|
|
@ -97,6 +109,8 @@ export const MultiSelect = React.forwardRef<HTMLDivElement, MultiSelectProps>(
|
|||
placeholder = "Select options",
|
||||
maxCount = 3,
|
||||
modalPopover = false,
|
||||
popoverAutoWidth = false,
|
||||
footer,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
|
|
@ -243,7 +257,7 @@ export const MultiSelect = React.forwardRef<HTMLDivElement, MultiSelectProps>(
|
|||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0 w-[var(--radix-popover-trigger-width)]"
|
||||
className={cn("p-0", popoverAutoWidth ? "w-auto" : "w-[var(--radix-popover-trigger-width)]")}
|
||||
align="start"
|
||||
onEscapeKeyDown={() => setIsPopoverOpen(false)}>
|
||||
<Command filter={onFilter}>
|
||||
|
|
@ -276,6 +290,7 @@ export const MultiSelect = React.forwardRef<HTMLDivElement, MultiSelectProps>(
|
|||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
{footer && <div className="border-t p-2">{footer}</div>}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const formatTokens = (tokens: number) => {
|
|||
}
|
||||
|
||||
export const formatToolUsageSuccessRate = (usage: { attempts: number; failures: number }) =>
|
||||
usage.attempts === 0 ? "0%" : `${(((usage.attempts - usage.failures) / usage.attempts) * 100).toFixed(1)}%`
|
||||
usage.attempts === 0 ? "0%" : `${Math.round(((usage.attempts - usage.failures) / usage.attempts) * 100)}%`
|
||||
|
||||
export const formatDateTime = (date: Date) => {
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ const nextConfig: NextConfig = {
|
|||
destination: "https://roo-code.notion.site/238fd1401b0a8087b858e1ad431507cf?pvs=105",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/provider/pricing",
|
||||
destination: "/provider",
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.3 MiB |
|
|
@ -1,29 +1,33 @@
|
|||
import {
|
||||
ArrowRight,
|
||||
Bot,
|
||||
Brain,
|
||||
ChartLine,
|
||||
Cloud,
|
||||
Lock,
|
||||
Github,
|
||||
History,
|
||||
LucideIcon,
|
||||
Megaphone,
|
||||
MessageCircleQuestionMark,
|
||||
ReplaceAll,
|
||||
Pencil,
|
||||
Router,
|
||||
Share2,
|
||||
Slack,
|
||||
Users,
|
||||
Users2,
|
||||
} from "lucide-react"
|
||||
import type { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedBackground } from "@/components/homepage"
|
||||
import { AnimatedBackground, UseExamplesSection } from "@/components/homepage"
|
||||
import { SEO } from "@/lib/seo"
|
||||
import { ogImageUrl } from "@/lib/og"
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
import Image from "next/image"
|
||||
// Workaround for next/image choking on these for some reason
|
||||
import screenshotDark from "/public/heroes/cloud-screen.png"
|
||||
|
||||
const TITLE = "Roo Code Cloud"
|
||||
const DESCRIPTION =
|
||||
"Roo Code Cloud gives you and your team the tools to take AI-coding to the next level with cloud agents, remote control, and more."
|
||||
const OG_DESCRIPTION = "Go way beyond the IDE"
|
||||
"Your AI Software Engineering Team in the Cloud. Delegate tasks to autonomous agents, review PRs, and collaborate with your team."
|
||||
const OG_DESCRIPTION = "Your AI Team in the Cloud"
|
||||
const PATH = "/cloud"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
@ -54,161 +58,171 @@ export const metadata: Metadata = {
|
|||
description: DESCRIPTION,
|
||||
images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
|
||||
},
|
||||
keywords: [...SEO.keywords, "cloud", "subscription", "cloud agents", "AI cloud development"],
|
||||
keywords: [...SEO.keywords, "cloud", "subscription", "cloud agents", "AI cloud development", "autonomous agents"],
|
||||
}
|
||||
|
||||
const howItWorks = [
|
||||
{
|
||||
title: "1. Connect your GitHub account",
|
||||
description:
|
||||
"Pick which repos the agents can work with in their isolated containers and choose what model you want to power each of them. You're in control.",
|
||||
icon: Github,
|
||||
},
|
||||
{
|
||||
title: "2. Set up your agent team",
|
||||
description:
|
||||
"Choose the roles you want filled, like Explainer, Planner, Coder, PR Reviewer and PR Fixer. They know how to act in each situation and stay on-task with no deviations.",
|
||||
icon: Users2,
|
||||
},
|
||||
{
|
||||
title: "3. Start giving them tasks",
|
||||
description:
|
||||
"Describe what you want them to do from the web UI, get the Reviewer automatically reviewing PRs, get the Coder building features from Slack threads and much more. They're now part of your team.",
|
||||
icon: Pencil,
|
||||
},
|
||||
]
|
||||
|
||||
interface Feature {
|
||||
icon: LucideIcon
|
||||
title: string
|
||||
description: string
|
||||
logos?: string[]
|
||||
}
|
||||
|
||||
const cloudFeatures: Feature[] = [
|
||||
const features: Feature[] = [
|
||||
{
|
||||
icon: Bot,
|
||||
title: "Autonomous Cloud Agents",
|
||||
description:
|
||||
"Delegate work to specialized agents like the Planner, Coder, Explainer, Reviewer, and Fixer that run 24/7.",
|
||||
},
|
||||
{
|
||||
icon: Brain,
|
||||
title: "Model Agnostic",
|
||||
description:
|
||||
"Bring your own keys or use the Roo Code Cloud Provider with access to all top models with no markup.",
|
||||
},
|
||||
{
|
||||
icon: Github,
|
||||
title: "GitHub PR Reviews",
|
||||
description:
|
||||
"Agents can automatically review Pull Requests, provide feedback, and even push fixes directly to your repository.",
|
||||
},
|
||||
{
|
||||
icon: Slack,
|
||||
title: "Slack Integration",
|
||||
description: "Start tasks, get updates, and collaborate with agents directly from your team's Slack channels.",
|
||||
},
|
||||
{
|
||||
icon: Router,
|
||||
title: "Roomote Control",
|
||||
description: "Control your IDE from anywhere and keep coding away from your computer.",
|
||||
},
|
||||
{
|
||||
icon: Cloud,
|
||||
title: "Cloud Agents",
|
||||
description:
|
||||
"Specialized agents running in the Cloud to get stuff done while you sleep, with a credit-based system that doesn't lock you in or dumb your models down.",
|
||||
"Connect to your local VS Code instance and control the extension remotely from the browser or Slack.",
|
||||
},
|
||||
{
|
||||
icon: ReplaceAll,
|
||||
title: "Still Model-agnostic",
|
||||
description: "Bring your own provider key — no markup, lock-in, no restrictions.",
|
||||
logos: ["Anthropic", "OpenAI", "Gemini", "Grok", "Qwen", "Kimi", "Mistral", "Ollama"],
|
||||
icon: Users,
|
||||
title: "Team Collaboration",
|
||||
description:
|
||||
"Manage your team and their access to tasks and resources, with centralized billing and configuration.",
|
||||
},
|
||||
{
|
||||
icon: ChartLine,
|
||||
title: "Usage Analytics",
|
||||
description: "Detailed token analytics to help you optimize your costs and usage.",
|
||||
description: "Detailed token analytics to help you optimize your costs and usage across your team.",
|
||||
},
|
||||
{
|
||||
icon: Megaphone,
|
||||
title: "Early Model Access",
|
||||
description: "Get early, free access to new, stealth coding models as they become available.",
|
||||
icon: History,
|
||||
title: "Task History",
|
||||
description: "Access from anywhere all of your tasks, from the cloud and the extension",
|
||||
},
|
||||
{
|
||||
icon: Share2,
|
||||
title: "Task Sharing",
|
||||
description: "Share tasks with friends and co-workers and let them follow your work.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Team Management",
|
||||
description:
|
||||
"Manage your team and their access to tasks and resources, with centralized billing, analytics and configuration.",
|
||||
},
|
||||
{
|
||||
icon: Lock,
|
||||
title: "Secure and Private",
|
||||
description:
|
||||
"Your data is never used for training, and we're SOC2 Type 2 and GDPR compliant, following state-of-the-art security practices, with deep respect for your IP.",
|
||||
},
|
||||
{
|
||||
icon: MessageCircleQuestionMark,
|
||||
title: "Priority support",
|
||||
description: "Get quick help from the people who know Roo best.",
|
||||
description: "Share tasks with friends and co-workers and let them follow your work in real-time.",
|
||||
},
|
||||
]
|
||||
|
||||
// Workaround for next/image choking on these for some reason
|
||||
import screenshotDark from "/public/heroes/cloud-screen.png"
|
||||
|
||||
export default function CloudPage() {
|
||||
return (
|
||||
<>
|
||||
<section className="relative flex md:h-[calc(80vh-theme(spacing.12))] items-center overflow-hidden">
|
||||
{/* Hero Section */}
|
||||
<section className="relative flex pt-32 pb-20 items-center overflow-hidden">
|
||||
<AnimatedBackground />
|
||||
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid h-full relative gap-4 md:gap-0 lg:grid-cols-2">
|
||||
<div className="flex flex-col px-4 justify-center space-y-6 sm:space-y-8">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold tracking-tight mt-8 text-center md:text-left md:text-4xl lg:text-5xl lg:mt-0">
|
||||
Go <em>way</em> beyond the IDE
|
||||
</h1>
|
||||
<p className="mt-4 max-w-md text-lg text-muted-foreground text-center md:text-left sm:mt-6">
|
||||
Roo Code Cloud gives you (and your team) the tools to take AI-coding to the next
|
||||
level
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="w-full sm:w-auto bg-white/20 dark:bg-white/10 backdrop-blur-sm border border-black/40 dark:border-white/30 hover:border-blue-400 hover:bg-white/30 dark:hover:bg-white/20 hover:shadow-[0_0_20px_rgba(59,130,246,0.5)] transition-all duration-300">
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex w-full items-center justify-center">
|
||||
Start Free Trial
|
||||
<ArrowRight className="ml-2" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end mx-auto h-full mt-8 lg:mt-0">
|
||||
<div className="md:w-[900px] md:h-[700px] relative rounded-md overflow-clip">
|
||||
<div className="block">
|
||||
<Image
|
||||
src={screenshotDark}
|
||||
alt="Screenshot of Roo Code Cloud"
|
||||
className="max-w-full h-auto"
|
||||
width={1390}
|
||||
height={1012}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container relative flex flex-col items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center max-w-4xl mx-auto mb-12">
|
||||
<h1 className="text-4xl font-bold tracking-tight mb-6 md:text-5xl lg:text-6xl">
|
||||
Your AI Team <span className="text-violet-500">in the Cloud</span>
|
||||
</h1>
|
||||
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
Create your agent team in the Cloud, give them access to GitHub, and start delegating tasks
|
||||
from Web and Slack.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button
|
||||
size="xl"
|
||||
className="bg-violet-600 hover:bg-violet-700 text-white transition-all duration-300 shadow-lg hover:shadow-violet-500/25"
|
||||
asChild>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center">
|
||||
Try Cloud for Free
|
||||
<ArrowRight className="ml-2 size-5" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="outline" size="xl" className="backdrop-blur-sm" asChild>
|
||||
<a href="/pricing" className="flex items-center justify-center">
|
||||
View Pricing
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Screenshot */}
|
||||
<div className="relative mx-auto mt-4 md:max-w-[1000px]">
|
||||
<Image
|
||||
src={screenshotDark}
|
||||
alt="Roo Code Cloud Interface"
|
||||
className="w-full h-auto"
|
||||
width={1390}
|
||||
height={1012}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works Section */}
|
||||
<section className="relative overflow-hidden border-t border-border py-32">
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-12 md:mb-24 max-w-4xl text-center">
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
|
||||
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-500/10 dark:bg-blue-700/20 blur-[140px]" />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mb-12 md:mb-24 max-w-5xl text-center">
|
||||
<div>
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">Power and Flexibility</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Code in the cloud, access free models, get usage analytics and more
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-5xl mb-4">How it works</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||
It only takes 2 minutes to expand your team by 10x.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto md:max-w-[1200px]">
|
||||
<ul className="grid grid-cols-1 place-items-center gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
|
||||
{cloudFeatures.map((feature, index) => {
|
||||
const Icon = feature.icon
|
||||
<ul className="grid grid-cols-1 place-items-center gap-6 md:grid-cols-3 lg:gap-8">
|
||||
{howItWorks.map((step, index) => {
|
||||
const Icon = step.icon
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className="relative h-full border border-border rounded-2xl bg-background p-8 transition-all duration-300">
|
||||
<Icon className="size-6 text-foreground/80" />
|
||||
className="relative h-full border border-border rounded-2xl bg-background p-8 transition-all duration-300 hover:shadow-lg">
|
||||
{Icon && <Icon className="size-6 text-foreground/80" />}
|
||||
<h3 className="mb-3 mt-3 text-xl font-semibold text-foreground">
|
||||
{feature.title}
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="leading-relaxed font-light text-muted-foreground">
|
||||
{feature.description}
|
||||
</p>
|
||||
{feature.logos && (
|
||||
<div className="mt-4 flex flex-wrap items-center gap-4">
|
||||
{feature.logos.map((logo) => (
|
||||
<Image
|
||||
key={logo}
|
||||
width={20}
|
||||
height={20}
|
||||
className="w-5 h-5 overflow-clip opacity-50 dark:invert"
|
||||
src={`/logos/${logo.toLowerCase()}.svg`}
|
||||
alt={`${logo} Logo`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="leading-relaxed font-light text-muted-foreground">
|
||||
{step.description}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
|
@ -217,25 +231,61 @@ export default function CloudPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<div id="faq"></div>
|
||||
{/* Use Cases Section */}
|
||||
<UseExamplesSection />
|
||||
|
||||
{/* Features Grid */}
|
||||
<section className="py-24 bg-muted/30">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative">
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
|
||||
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-violet-500/10 dark:bg-violet-700/20 blur-[140px]" />
|
||||
</div>
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-4">
|
||||
Powering the next generation of software development
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||
Everything you need to scale your development capacity with AI.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto relative">
|
||||
{features.map((feature, index) => {
|
||||
const Icon = feature.icon
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-background p-8 rounded-2xl border border-border hover:shadow-lg transition-all duration-300">
|
||||
<div className="bg-violet-100 dark:bg-violet-900/20 w-12 h-12 rounded-lg flex items-center justify-center mb-6">
|
||||
<Icon className="size-6 text-violet-600 dark:text-violet-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-3">{feature.title}</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">{feature.description}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20">
|
||||
<section className="py-24">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-blue-500/5 via-cyan-500/5 to-purple-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/20 dark:bg-gradient-to-br dark:from-gray-800 dark:via-gray-900 dark:to-black sm:p-12">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">Try Roo Code Cloud now</h2>
|
||||
<p className="mx-auto mb-8 max-w-2xl text-lg text-muted-foreground">Code from anywhere.</p>
|
||||
<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-violet-500/10 via-purple-500/5 to-blue-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/10 sm:p-16">
|
||||
<h2 className="mb-6 text-3xl font-bold tracking-tight sm:text-4xl">
|
||||
Try a completely new way of working.
|
||||
</h2>
|
||||
<p className="mx-auto mb-10 max-w-2xl text-lg text-muted-foreground">Start for free today.</p>
|
||||
<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-black text-white hover:bg-gray-800 hover:shadow-lg hover:shadow-black/20 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:hover:shadow-white/20 transition-all duration-300"
|
||||
className="bg-foreground text-background hover:bg-foreground/90 transition-all duration-300"
|
||||
asChild>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center">
|
||||
Create a free Cloud account
|
||||
Sign up now
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
|
|
|
|||
84
apps/web-roo-code/src/app/extension/page.tsx
Normal file
84
apps/web-roo-code/src/app/extension/page.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* eslint-disable react/jsx-no-target-blank */
|
||||
|
||||
import { getVSCodeDownloads } from "@/lib/stats"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import {
|
||||
AnimatedBackground,
|
||||
CodeExample,
|
||||
CompanyLogos,
|
||||
FAQSection,
|
||||
Features,
|
||||
InstallSection,
|
||||
Testimonials,
|
||||
} from "@/components/homepage"
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
import { ArrowRight } from "lucide-react"
|
||||
import { StructuredData } from "@/components/structured-data"
|
||||
|
||||
// Invalidate cache when a request comes in, at most once every hour.
|
||||
export const revalidate = 3600
|
||||
|
||||
export default async function ExtensionPage() {
|
||||
const downloads = await getVSCodeDownloads()
|
||||
|
||||
return (
|
||||
<>
|
||||
<StructuredData />
|
||||
<section className="relative flex h-[calc(125vh-theme(spacing.12))] items-center overflow-hidden md:h-[calc(80svh-theme(spacing.12))]">
|
||||
<AnimatedBackground />
|
||||
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid h-full relative gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
<div className="flex flex-col justify-center space-y-4 sm:space-y-8">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold tracking-tight mt-8 text-center md:text-left md:text-4xl lg:text-5xl lg:mt-0">
|
||||
The Open Source AI Coding Assistant for serious work.
|
||||
</h1>
|
||||
<p className="mt-4 max-w-lg text-lg text-muted-foreground text-center md:text-left sm:mt-6">
|
||||
Specialized modes stay on task and ship great code.
|
||||
<br />
|
||||
Fully model-agnostic so you can use the best (or most cost-effective) model for each
|
||||
task.
|
||||
</p>
|
||||
<p className="max-w-lg text-lg text-muted-foreground text-center md:text-left sm:mt-6">
|
||||
Stop chasing this week's hot new model or CLI tool and go deep with Roo Code.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full hover:bg-gray-200 dark:bg-white dark:text-black sm:w-auto">
|
||||
<a
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="flex w-full items-center justify-center">
|
||||
Install VS Code Extension
|
||||
<ArrowRight className="ml-2" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="md:max-h-[70px] md:overflow-clip text-center md:text-left pt-6 md:pt-0">
|
||||
<CompanyLogos />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex items-center mx-auto h-full mt-8 lg:mt-0">
|
||||
<div className="flex items-center justify-center">
|
||||
<CodeExample />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="product">
|
||||
<Features />
|
||||
</div>
|
||||
<div id="testimonials">
|
||||
<Testimonials />
|
||||
</div>
|
||||
<div id="faq">
|
||||
<FAQSection />
|
||||
</div>
|
||||
<InstallSection downloads={downloads} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -33,8 +33,9 @@ export const content: AgentPageContent = {
|
|||
],
|
||||
},
|
||||
cta: {
|
||||
buttonText: "Start 14-day Free Trial",
|
||||
disclaimer: "(cancel anytime)",
|
||||
buttonText: "Try now for free",
|
||||
disclaimer: "",
|
||||
tracking: "&agent=pr-fixer",
|
||||
},
|
||||
},
|
||||
howItWorks: {
|
||||
|
|
@ -89,6 +90,6 @@ export const content: AgentPageContent = {
|
|||
cta: {
|
||||
heading: "Ship fixes, not follow-ups.",
|
||||
description: "Let Roo Code's PR Fixer turn your review feedback into clean, ready-to-merge commits.",
|
||||
buttonText: "Start 14-day Free Trial",
|
||||
buttonText: "Try now for free",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud, PlugZap } from "lucide-react"
|
||||
import { Users, ArrowRight, LucideIcon, Check, SquareTerminal, CornerRightDown, Cloud } from "lucide-react"
|
||||
import type { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedBackground } from "@/components/homepage"
|
||||
import { ContactForm } from "@/components/enterprise/contact-form"
|
||||
import { SEO } from "@/lib/seo"
|
||||
import { ogImageUrl } from "@/lib/og"
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
|
||||
const TITLE = "Roo Code Cloud Pricing"
|
||||
const TITLE = "Roo Code Pricing"
|
||||
const DESCRIPTION =
|
||||
"Simple, transparent pricing for Roo Code Cloud. The VS Code extension is free forever. Choose the cloud plan that fits your needs."
|
||||
"Simple, transparent pricing for all Roo Code products. The VS Code extension is free forever. Choose the cloud plan that fits your needs."
|
||||
const OG_DESCRIPTION = ""
|
||||
const PATH = "/pricing"
|
||||
|
||||
|
|
@ -61,6 +60,7 @@ interface PricingTier {
|
|||
name: string
|
||||
icon: LucideIcon
|
||||
price: string
|
||||
priceSuffix: string
|
||||
period?: string
|
||||
creditPrice?: string
|
||||
trial?: string
|
||||
|
|
@ -70,60 +70,57 @@ interface PricingTier {
|
|||
cta: {
|
||||
text: string
|
||||
href?: string
|
||||
isContactForm?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
const pricingTiers: PricingTier[] = [
|
||||
{
|
||||
name: "Cloud Free",
|
||||
icon: Cloud,
|
||||
price: "$0",
|
||||
description: "For folks just getting started",
|
||||
features: [
|
||||
"Token usage analytics",
|
||||
"Access to the Roo Code Cloud Provider, including early access to free stealth models",
|
||||
"Follow your tasks from anywhere",
|
||||
"Share tasks with friends and co-workers",
|
||||
"Community support",
|
||||
],
|
||||
name: "VS Code Extension",
|
||||
icon: SquareTerminal,
|
||||
price: "Free",
|
||||
priceSuffix: "inference",
|
||||
description: "The best local coding agent",
|
||||
features: ["Unlimited local use", "Bring your own model", "Powerful, extensible modes", "Community support"],
|
||||
cta: {
|
||||
text: "Get started",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP,
|
||||
text: "Install Now",
|
||||
href: EXTERNAL_LINKS.MARKETPLACE,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cloud Pro",
|
||||
icon: Star,
|
||||
price: "$20",
|
||||
name: "Cloud Free",
|
||||
icon: Cloud,
|
||||
price: "$0",
|
||||
period: "/mo",
|
||||
trial: "Free for 14 days, then",
|
||||
priceSuffix: "credits",
|
||||
creditPrice: `$${PRICE_CREDITS}`,
|
||||
description: "For pro Roo coders",
|
||||
featuresIntro: "Everything in Free +",
|
||||
description: "For AI-forward engineers",
|
||||
featuresIntro: "Go beyond the extension with",
|
||||
features: [
|
||||
"Cloud Agents: Coder, Explainer, Planner, Reviewer, Fixer and more",
|
||||
"Start tasks from Slack",
|
||||
"Roomote Control: Start, stop and control extension tasks from anywhere",
|
||||
"Paid support",
|
||||
"Access to Cloud Agents: fully autonomous development you can call from Slack, Github and the web",
|
||||
"Access to the Roo Code Cloud Provider",
|
||||
"Follow your tasks from anywhere",
|
||||
"Share tasks with friends and co-workers",
|
||||
"Token usage analytics",
|
||||
"Professional support",
|
||||
],
|
||||
cta: {
|
||||
text: "Get started",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
|
||||
text: "Sign up",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cloud Team",
|
||||
icon: Users,
|
||||
price: "$99",
|
||||
priceSuffix: "credits",
|
||||
period: "/mo",
|
||||
creditPrice: `$${PRICE_CREDITS}`,
|
||||
trial: "Free for 14 days, then",
|
||||
description: "For AI-forward teams",
|
||||
featuresIntro: "Everything in Pro +",
|
||||
featuresIntro: "Everything in Free +",
|
||||
features: ["Unlimited users (no per-seat cost)", "Shared configuration & policies", "Centralized billing"],
|
||||
cta: {
|
||||
text: "Get started",
|
||||
text: "Sign up",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
|
||||
},
|
||||
},
|
||||
|
|
@ -135,70 +132,43 @@ export default function PricingPage() {
|
|||
<AnimatedBackground />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden pt-16 pb-12">
|
||||
<section className="relative overflow-hidden pt-12 pb-10">
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-5xl font-bold tracking-tight">Roo Code Cloud Pricing</h1>
|
||||
<p className="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">
|
||||
Simple, transparent pricing that scales with your needs.
|
||||
<br />
|
||||
No inference markups. Free 14-day trials to kick the tires.
|
||||
<h1 className="text-5xl font-bold tracking-tight">Roo Code Pricing</h1>
|
||||
<p className="mt-4 text-lg text-muted-foreground">
|
||||
For all of our products: the Roo Code VS Code Extension, Roo Code Cloud and the Roo Code
|
||||
Cloud inference Provider.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="mx-6 md:mx-auto max-w-6xl">
|
||||
<div className="rounded-xl p-4 mb-8 text-center bg-gradient-to-r from-blue-500/10 via-cyan-500/10 to-purple-500/10 border border-blue-500/20 dark:border-white/20 ">
|
||||
<p className="text-center">
|
||||
<strong className="font-semibold">The Roo Code extension is totally free! </strong>
|
||||
But Cloud takes you so much further.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-6 md:mx-auto max-w-6xl p-7 mb-4 relative flex flex-col justify-start bg-background border rounded-2xl transition-all shadow-none hover:shadow-lg">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-2 justify-between">
|
||||
Roo Code Provider
|
||||
<PlugZap className="size-6" />
|
||||
</h3>
|
||||
<div className="text-sm text-muted-foreground space-y-1 mt-2">
|
||||
<p className="">
|
||||
On any plan, you can bring your own provider key or use the built-in Roo Code Cloud provider.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
We offer a select mix of tested state of the art closed and open weight LLMs for you to choose,
|
||||
with no markup.
|
||||
<Link href="/provider/pricing" className="underline hover:no-underline ml-1">
|
||||
See detailed pricing
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pricing Tiers */}
|
||||
<section className="">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto grid max-w-6xl gap-4 lg:grid-cols-3">
|
||||
<div className="mx-auto grid max-w-6xl gap-4 md:grid-cols-3 md:px-4">
|
||||
{pricingTiers.map((tier) => {
|
||||
const Icon = tier.icon
|
||||
return (
|
||||
<div
|
||||
key={tier.name}
|
||||
className="relative p-6 flex flex-col justify-start bg-background border rounded-2xl transition-all hover:shadow-lg">
|
||||
className="relative group p-6 flex flex-col justify-start bg-background rounded-2xl outline outline-2 outline-border/50 hover:outline-8 transition-all shadow-xl hover:shadow-2xl hover:outline-6">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-bold tracking-tight">{tier.name}</h3>
|
||||
<Icon className="size-6" />
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{tier.description}</p>
|
||||
<p className="text-sm font-medium">{tier.description}</p>
|
||||
</div>
|
||||
<div className="absolute -right-2 -top-4 rounded-full bg-card shadow-md p-4 outline outline-2 outline-border/50 group-hover:scale-105 group-hover:outline-8 transition-all">
|
||||
<Icon className="size-6" strokeWidth={1.5} />
|
||||
</div>
|
||||
|
||||
<div className="grow mb-8">
|
||||
<p className="text-sm text-muted-foreground font-light mb-2">
|
||||
{tier.featuresIntro}
|
||||
</p>
|
||||
<ul className="space-y-3 my-0 h-[168px]">
|
||||
<ul className="space-y-3 my-0 md:h-[192px]">
|
||||
{tier.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-2">
|
||||
<Check className="mt-0.5 h-4 w-4 text-muted-foreground shrink-0" />
|
||||
|
|
@ -210,58 +180,65 @@ export default function PricingPage() {
|
|||
|
||||
<p className="text-base font-light">{tier.trial}</p>
|
||||
|
||||
<p className="text-xl my-1 tracking-tight font-light">
|
||||
<p className="text-xl mb-1 tracking-tight font-light">
|
||||
<strong className="font-bold">{tier.price}</strong>
|
||||
{tier.period} + prepaid credits
|
||||
{tier.period} + {tier.priceSuffix}
|
||||
<CornerRightDown className="inline size-4 ml-1 relative top-0.5" />
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
<p className="text-sm text-muted-foreground mb-5">
|
||||
{tier.creditPrice && (
|
||||
<>
|
||||
Cloud Agents: {tier.creditPrice}/hour if used
|
||||
Cloud Agents: {tier.creditPrice}/hour in credits
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
Inference:{" "}
|
||||
<Link href="/provider/pricing" className="underline hover:no-underline">
|
||||
<Link href="/provider" className="underline hover:no-underline">
|
||||
Roo Provider pricing
|
||||
</Link>{" "}
|
||||
or{" "}
|
||||
<abbr title="Bring Your Own Key" className="cursor-help">
|
||||
BYOK
|
||||
credits or{" "}
|
||||
<abbr title="Bring Your Own Model" className="cursor-help">
|
||||
BYOM
|
||||
</abbr>
|
||||
</p>
|
||||
|
||||
{tier.cta.isContactForm ? (
|
||||
<ContactForm
|
||||
formType="demo"
|
||||
buttonText={tier.cta.text}
|
||||
buttonClassName="w-full transition-all duration-300"
|
||||
/>
|
||||
) : (
|
||||
<Button size="lg" className="w-full transition-all duration-300" asChild>
|
||||
<Link href={tier.cta.href!} className="flex items-center justify-center">
|
||||
{tier.cta.text}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<Button size="lg" className="w-full transition-all duration-300" asChild>
|
||||
<Link href={tier.cta.href!} className="flex items-center justify-center">
|
||||
{tier.cta.text}
|
||||
<ArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
{/* <div className="bg-foreground/20 h-8 absolute -bottom-8 left-1/2 w-[1px]" /> */}
|
||||
<div className="h-[28px] absolute bottom-[-31px] left-1/2 w-[4px] transition-colors bg-gradient-to-b from-transparent to-violet-700/20 group-hover:from-violet-500/50 group-hover:to-violet-500/20" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto grid max-w-6xl gap-4 mt-4 relative">
|
||||
<p className="bg-background border rounded-2xl p-6 text-center text-sm text-muted-foreground">
|
||||
<Building2 className="inline size-4 mr-2 mb-0.5" />
|
||||
Need SAML, advanced security, custom integrations or terms? Enterprise is for you.
|
||||
<Link
|
||||
href="/enterprise#contact"
|
||||
className="font-medium ml-1 text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
Talk to Sales
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<div className="max-w-6xl mx-auto mt-8 p-7 flex flex-col md:flex-row gap-8 md:gap-4 bg-violet-200/20 outline-violet-700/20 outline outline-1 rounded-2xl transition-all shadow-none">
|
||||
<div className="md:border-r md:pr-4">
|
||||
<h3 className="text-lg font-medium mb-1">Roo Code Provider</h3>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p className="">
|
||||
On any plan, you can use your own LLM provider API key or use the built-in Roo Code
|
||||
Cloud provider – curated models to work with Roo with no markup, including the
|
||||
latest Gemini, GPT and Claude. Paid with credits.
|
||||
<Link href="/provider/pricing" className="underline hover:no-underline ml-1">
|
||||
See per model pricing.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<h3 className="text-lg font-medium mb-1">Credits</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Credits are pre-paid, in dollars, and are deducted with usage for inference and Cloud
|
||||
Agent runs. You're always in control of your spend, no surprises.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -337,6 +314,19 @@ export default function PricingPage() {
|
|||
reflected in your next billing cycle.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-card p-6 md:col-span-2">
|
||||
<h3 className="font-semibold">
|
||||
What if I have enterprise-level needs like SAML/SCIM, large-scale deployments, specific
|
||||
integrations and custom terms?
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
We have an Enterprise plan which can be a fit. Please{" "}
|
||||
<Link href="/enterprise#contact" className="underline hover:no-underline">
|
||||
reach out to our sales team
|
||||
</Link>{" "}
|
||||
to discuss it.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 text-center">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { ModelCard } from "./components/model-card"
|
||||
import { ModelCard } from "./pricing/components/model-card"
|
||||
import { Model, ModelWithTotalPrice, ModelsResponse, SortOption } from "@/lib/types/models"
|
||||
import Link from "next/link"
|
||||
import { ChevronDown, CircleX, Loader, LoaderCircle, Search } from "lucide-react"
|
||||
import { ChevronDown, CircleX, Cloud, Loader, LoaderCircle, Puzzle, Search } from "lucide-react"
|
||||
|
||||
const API_URL = "https://api.roocode.com/proxy/v1/models?include_paid=true"
|
||||
|
||||
|
|
@ -13,28 +13,29 @@ const faqs = [
|
|||
question: "What are AI model providers?",
|
||||
answer: "AI model providers offer various language models with different capabilities and pricing.",
|
||||
},
|
||||
{
|
||||
question: "How is pricing calculated?",
|
||||
answer: "Pricing is based on token usage for input and output, measured per million tokens, like pretty much any other provider out there.",
|
||||
},
|
||||
{
|
||||
question: "What is the Roo Code Cloud Provider?",
|
||||
answer: (
|
||||
<>
|
||||
<p>This is our very own model provider, optimized to work seamlessly with Roo Code Cloud.</p>
|
||||
<p>
|
||||
It offers a selection of state-of-the-art LLMs (both closed and open weight) we know work well with
|
||||
Roo for you to choose, with no markup.
|
||||
</p>
|
||||
<p>
|
||||
We also often feature 100% free models which labs share with us for the community to use and provide
|
||||
feedback.
|
||||
</p>
|
||||
<p>You don't have to use it to use Roo Code, but it's the easiest way to do it.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: "But how much does the Roo Code Cloud service cost?",
|
||||
question: "Do I have to use the Roo Code Cloud Provider to use the Roo Code products?",
|
||||
answer: "Not at all! You can bring your own provider key, no problem. This is just meant to make it easier.",
|
||||
},
|
||||
{
|
||||
question: "How is pricing calculated?",
|
||||
answer: "Pricing is based on token usage for input and output, measured per million tokens, like pretty much any other provider out there.",
|
||||
},
|
||||
{
|
||||
question: "How is my data treated?",
|
||||
answer: "The Roo Code Cloud provider doesn't keep any of your data, the service only aims to make it easier to use Roo Code. Each model vendor has their own privacy policy though, and usually free models use your data for training, so keep that in mind.",
|
||||
},
|
||||
{
|
||||
question: "How much does the Roo Code Cloud service cost?",
|
||||
answer: (
|
||||
<>
|
||||
Our{" "}
|
||||
|
|
@ -57,7 +58,7 @@ function enrichModelWithTotalPrice(model: Model): ModelWithTotalPrice {
|
|||
}
|
||||
}
|
||||
|
||||
export default function ProviderPricingPage() {
|
||||
export default function ProviderPage() {
|
||||
const [models, setModels] = useState<ModelWithTotalPrice[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
|
@ -133,13 +134,22 @@ export default function ProviderPricingPage() {
|
|||
<section className="relative overflow-hidden py-16">
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold tracking-tight">
|
||||
Roo Code Cloud Provider Pricing
|
||||
</h1>
|
||||
<p className="mx-auto mt-4 max-w-2xl md:text-lg text-muted-foreground">
|
||||
See pricing and features for all models we offer in our selection.
|
||||
<br />
|
||||
You can always bring your own key (
|
||||
<h1 className="text-4xl md:text-5xl font-bold tracking-tight">The Roo Code Cloud Provider</h1>
|
||||
<p className="mx-auto mt-4 max-w-3xl md:text-lg text-muted-foreground">
|
||||
The easiest way to use Roo Code (in the{" "}
|
||||
<Link href="/cloud" className="underline hover:no-underline">
|
||||
<Cloud className="inline size-5 mx-1 -mt-0.5" />
|
||||
Cloud
|
||||
</Link>{" "}
|
||||
or the{" "}
|
||||
<Link href="/extension" className="underline hover:no-underline">
|
||||
<Puzzle className="inline size-5 mx-1 -mt-0.5" />
|
||||
Extension
|
||||
</Link>
|
||||
) with the best free and paid models – no separate accounts, no messing with API keys.
|
||||
</p>
|
||||
<p className="mx-auto mt-2 max-w-2xl md:text-lg text-muted-foreground">
|
||||
And you can always bring your own key (
|
||||
<Link href="#faq" className="underline hover:no-underline">
|
||||
FAQ
|
||||
</Link>
|
||||
|
|
@ -240,7 +250,7 @@ export default function ProviderPricingPage() {
|
|||
</div>
|
||||
<div className="mx-auto mt-12 grid max-w-5xl gap-8 md:grid-cols-2">
|
||||
{faqs.map((faq, index) => (
|
||||
<div key={index} className="rounded-lg border border-border bg-card p-6">
|
||||
<div key={index} className="rounded-2xl border border-border bg-card p-6">
|
||||
<h3 className="font-semibold">{faq.question}</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">{faq.answer}</p>
|
||||
</div>
|
||||
|
|
@ -32,8 +32,9 @@ export const content: AgentPageContent = {
|
|||
],
|
||||
},
|
||||
cta: {
|
||||
buttonText: "Start 14-day Free Trial",
|
||||
disclaimer: "(cancel anytime)",
|
||||
buttonText: "Try now for free",
|
||||
disclaimer: "",
|
||||
tracking: "&agent=reviewer",
|
||||
},
|
||||
},
|
||||
howItWorks: {
|
||||
|
|
@ -87,6 +88,6 @@ export const content: AgentPageContent = {
|
|||
cta: {
|
||||
heading: "Ready for better code reviews?",
|
||||
description: "Start finding the issues that matter with AI-powered reviews built for depth, not cost-cutting.",
|
||||
buttonText: "Start 14-day Free Trial",
|
||||
buttonText: "Try now for free",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ export const content: AgentPageContent = {
|
|||
],
|
||||
},
|
||||
cta: {
|
||||
buttonText: "Start 14-day Free Trial",
|
||||
disclaimer: "(cancel anytime)",
|
||||
buttonText: "Try now for free",
|
||||
disclaimer: "",
|
||||
tracking: "&agent=reviewer",
|
||||
},
|
||||
},
|
||||
howItWorks: {
|
||||
|
|
@ -87,6 +88,6 @@ export const content: AgentPageContent = {
|
|||
cta: {
|
||||
heading: "Ready for better code reviews?",
|
||||
description: "Start finding the issues that matter with AI-powered reviews built for depth, not cost-cutting.",
|
||||
buttonText: "Start 14-day Free Trial",
|
||||
buttonText: "Try now for free",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ import Image from "next/image"
|
|||
import Link from "next/link"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedBackground } from "@/components/homepage"
|
||||
import { AgentCarousel } from "@/components/reviewer/agent-carousel"
|
||||
import { AnimatedBackground, UseExamplesSection } from "@/components/homepage"
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
import { type AgentPageContent, type IconName } from "./agent-page-content"
|
||||
|
||||
|
|
@ -93,7 +92,7 @@ export function AgentLandingContent({ content }: { content: AgentPageContent })
|
|||
className="w-full sm:w-auto backdrop-blur-sm border hover:shadow-[0_0_20px_rgba(59,130,246,0.5)] transition-all duration-300"
|
||||
asChild>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP_PRO}
|
||||
href={`${EXTERNAL_LINKS.CLOUD_APP_SIGNUP_PRO}${content.hero.cta.tracking}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex w-full items-center justify-center">
|
||||
|
|
@ -127,6 +126,9 @@ export function AgentLandingContent({ content }: { content: AgentPageContent })
|
|||
|
||||
{/* How It Works Section */}
|
||||
<section className="relative overflow-hidden border-t border-border py-32">
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
|
||||
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-violet-500/10 dark:bg-violet-700/20 blur-[140px]" />
|
||||
</div>
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-12 md:mb-24 max-w-5xl text-center">
|
||||
<div>
|
||||
|
|
@ -161,6 +163,9 @@ export function AgentLandingContent({ content }: { content: AgentPageContent })
|
|||
|
||||
{/* Why Better Section */}
|
||||
<section className="relative overflow-hidden border-t border-border py-32">
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
|
||||
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-500/10 dark:bg-blue-700/20 blur-[140px]" />
|
||||
</div>
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-12 md:mb-24 max-w-5xl text-center">
|
||||
<div>
|
||||
|
|
@ -197,8 +202,7 @@ export function AgentLandingContent({ content }: { content: AgentPageContent })
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* Agent Carousel */}
|
||||
<AgentCarousel currentAgent={content.agentName} />
|
||||
<UseExamplesSection agentTitle={true} />
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20">
|
||||
|
|
@ -214,11 +218,11 @@ export function AgentLandingContent({ content }: { content: AgentPageContent })
|
|||
className="bg-black text-white hover:bg-gray-800 hover:shadow-lg hover:shadow-black/20 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:hover:shadow-white/20 transition-all duration-300"
|
||||
asChild>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP_PRO}
|
||||
href={`${EXTERNAL_LINKS.CLOUD_APP_SIGNUP_PRO}${content.hero.cta.tracking}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center">
|
||||
{content.cta.buttonText}
|
||||
{content.hero.cta.buttonText}
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export type IconName =
|
|||
* serialization from Server Components to Client Components.
|
||||
*/
|
||||
export interface AgentPageContent {
|
||||
/** The agent name used for the carousel display */
|
||||
agentName: string
|
||||
hero: {
|
||||
/** Optional icon name to display in the hero section */
|
||||
|
|
@ -45,6 +44,7 @@ export interface AgentPageContent {
|
|||
cta: {
|
||||
buttonText: string
|
||||
disclaimer: string
|
||||
tracking: string
|
||||
}
|
||||
}
|
||||
howItWorks: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { EXTERNAL_LINKS } from "@/lib/constants"
|
|||
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
|
||||
import { ScrollButton } from "@/components/ui"
|
||||
import ThemeToggle from "@/components/chromes/theme-toggle"
|
||||
import { ChevronDown, X } from "lucide-react"
|
||||
import { Brain, ChevronDown, Cloud, Puzzle, X } from "lucide-react"
|
||||
|
||||
interface NavBarProps {
|
||||
stars: string | null
|
||||
|
|
@ -25,7 +25,7 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
const logoSrc = useLogoSrc()
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
|
||||
<header className="sticky font-light top-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
|
||||
<div className="container flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="flex items-center">
|
||||
|
|
@ -34,72 +34,89 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="grow ml-6 hidden text-sm font-medium md:flex md:items-center">
|
||||
<ScrollButton
|
||||
targetId="product"
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
|
||||
Extension
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/cloud"
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Cloud
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Docs
|
||||
</a>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Pricing
|
||||
</Link>
|
||||
<nav className="grow ml-6 hidden text-sm md:flex md:items-center">
|
||||
{/* Product Dropdown */}
|
||||
<div className="relative group">
|
||||
<button className="flex items-center px-4 py-6 gap-1 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Product
|
||||
<ChevronDown className="size-3 ml-1 mt-0.5" />
|
||||
</button>
|
||||
<div className="absolute left-0 top-12 mt-2 w-[260px] rounded-md border border-border bg-background py-1 shadow-lg opacity-0 -translate-y-2 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-200">
|
||||
<Link
|
||||
href="/extension"
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
|
||||
<Puzzle className="size-3 inline mr-2 -mt-0.5" />
|
||||
Roo Code VS Code Extension
|
||||
</Link>
|
||||
<Link
|
||||
href="/cloud"
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
|
||||
<Cloud className="size-3 inline mr-2 -mt-0.5" />
|
||||
Roo Code Cloud
|
||||
</Link>
|
||||
<Link
|
||||
href="/provider"
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
|
||||
<Brain className="size-3 inline mr-2 -mt-0.5" />
|
||||
Roo Code Cloud Provider
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* Resources Dropdown */}
|
||||
<div className="relative group">
|
||||
<button className="flex items-center px-4 py-6 gap-1 text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
<button className="flex items-center px-4 py-6 gap-1 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Resources
|
||||
<ChevronDown className="size-3" />
|
||||
<ChevronDown className="size-3 ml-1 mt-0.5" />
|
||||
</button>
|
||||
{/* Dropdown Menu */}
|
||||
<div className="absolute left-0 top-12 mt-2 w-40 rounded-md border border-border bg-background py-1 shadow-lg opacity-0 -translate-y-2 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-200">
|
||||
<ScrollButton
|
||||
targetId="faq"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
|
||||
FAQ
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
|
||||
Evals
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
|
||||
Discord
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.SECURITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Trust Center
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Docs
|
||||
</a>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Pricing
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="hidden md:flex md:items-center md:space-x-4 flex-shrink-0">
|
||||
<div className="hidden md:flex md:items-center md:space-x-4 flex-shrink-0 font-medium">
|
||||
<div className="flex flex-row space-x-2 flex-shrink-0">
|
||||
<ThemeToggle />
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
className="hidden items-center gap-1.5 text-sm font-medium text-muted-foreground hover:text-foreground md:flex whitespace-nowrap">
|
||||
className="hidden items-center gap-1.5 text-sm hover:text-foreground md:flex whitespace-nowrap">
|
||||
<RxGithubLogo className="h-4 w-4" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
</Link>
|
||||
|
|
@ -108,20 +125,20 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
href={EXTERNAL_LINKS.CLOUD_APP_LOGIN}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden items-center gap-1.5 rounded-md py-2 text-sm border border-primary-background px-4 font-medium text-primary-background transition-all duration-200 hover:shadow-lg hover:scale-105 lg:flex">
|
||||
className="hidden items-center gap-1.5 rounded-md py-2 text-sm border border-primary-background px-4 text-primary-background transition-all duration-200 hover:shadow-lg hover:scale-105 lg:flex">
|
||||
Log in
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP_HOME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex">
|
||||
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex">
|
||||
Sign Up
|
||||
</a>
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex whitespace-nowrap">
|
||||
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex whitespace-nowrap">
|
||||
<VscVscode className="-mr-[2px] mt-[1px] h-4 w-4" />
|
||||
<span>
|
||||
Install <span className="font-black max-lg:text-xs">·</span>
|
||||
|
|
@ -147,18 +164,6 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
<nav className="flex flex-col justify-between h-full pb-16 overflow-y-auto bg-background pointer-events-auto">
|
||||
{/* Main navigation items */}
|
||||
<div className="grow-1 py-4 font-semibold text-lg">
|
||||
<ScrollButton
|
||||
targetId="product"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Extension
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/cloud"
|
||||
className="block w-full p-5 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Cloud
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
|
|
@ -173,6 +178,31 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
Pricing
|
||||
</Link>
|
||||
|
||||
{/* Product Section */}
|
||||
<div className="mt-4 w-full">
|
||||
<div className="px-5 pb-2 pt-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Product
|
||||
</div>
|
||||
<Link
|
||||
href="/extension"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Roo Code VS Code Extension
|
||||
</Link>
|
||||
<Link
|
||||
href="/cloud"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Roo Code Cloud
|
||||
</Link>
|
||||
<Link
|
||||
href="/provider"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Roo Code Cloud Provider
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Resources Section */}
|
||||
<div className="mt-4 w-full">
|
||||
<div className="px-5 pb-2 pt-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
|
|
@ -215,7 +245,7 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
<Link
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
className="inline-flex items-center gap-2 rounded-md p-3 text-sm transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<RxGithubLogo className="h-6 w-6" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
|
|
@ -226,7 +256,7 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
className="inline-flex items-center gap-2 rounded-md p-3 text-sm transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<VscVscode className="h-6 w-6" />
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function CompanyLogos() {
|
|||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||
className="text-xs text-muted-foreground text-center mb-2 ">
|
||||
className="text-xs text-muted-foreground mb-2 ">
|
||||
Helping teams ship more at
|
||||
</motion.p>
|
||||
<div className="mt-4 flex flex-wrap items-center gap-6 justify-center sm:justify-start">
|
||||
|
|
@ -25,7 +25,7 @@ export function CompanyLogos() {
|
|||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
className="h-[22px] w-auto overflow-clip opacity-70 dark:invert"
|
||||
className="h-[20px] w-auto overflow-clip opacity-70 dark:invert"
|
||||
src={`/logos/${logo.toLowerCase().replace(/\s+/g, "-")}.svg`}
|
||||
alt={`${logo} Logo`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
"use client"
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import { Shield, Users2, ReplaceAll, Keyboard, LucideIcon, CheckCheck, GitPullRequest } from "lucide-react"
|
||||
import {
|
||||
Shield,
|
||||
Users2,
|
||||
ReplaceAll,
|
||||
Keyboard,
|
||||
LucideIcon,
|
||||
CheckCheck,
|
||||
GitPullRequest,
|
||||
Boxes,
|
||||
TextSearch,
|
||||
} from "lucide-react"
|
||||
import Image from "next/image"
|
||||
|
||||
export interface Feature {
|
||||
|
|
@ -21,7 +31,8 @@ export const features: Feature[] = [
|
|||
{
|
||||
icon: ReplaceAll,
|
||||
title: "Model-agnostic",
|
||||
description: "Bring your own provider key or even run local inference — no markup, lock-in, no restrictions.",
|
||||
description:
|
||||
"Use the Roo Code cloud Provider, bring your own provider key or even run local inference — no markup, lock-in, no restrictions.",
|
||||
logos: ["Anthropic", "OpenAI", "Gemini", "Grok", "Qwen", "Kimi", "Mistral", "Ollama"],
|
||||
},
|
||||
{
|
||||
|
|
@ -31,16 +42,27 @@ export const features: Feature[] = [
|
|||
"Control each action and make Roo as autonomous as you want as you build confidence. Or go YOLO and let it rip.",
|
||||
},
|
||||
{
|
||||
icon: GitPullRequest,
|
||||
title: "Proudly open source",
|
||||
icon: Boxes,
|
||||
title: "Large task coordination",
|
||||
description:
|
||||
"Community-driven and fully auditable: no throttling or surprises about what's happening behind the scenes.",
|
||||
"Orchestrator mode handles large tasks by coordinating tasks for other agents, running for hours and delivering.",
|
||||
},
|
||||
{
|
||||
icon: TextSearch,
|
||||
title: "Performant with large codebases",
|
||||
description: "Configurable integrated semantic search for quicker retrieval in large codebases.",
|
||||
},
|
||||
{
|
||||
icon: Keyboard,
|
||||
title: "Highly customizable",
|
||||
description:
|
||||
"Fine-tune settings for Roo to work for you, like inference context, model properties, slash commands and more.",
|
||||
"Fine-tune settings for Roo to work for you, like inference context, model properties, slash commands and more. Most settings can be global or serialized in your repository.",
|
||||
},
|
||||
{
|
||||
icon: GitPullRequest,
|
||||
title: "Proudly open source",
|
||||
description:
|
||||
"Community-driven and fully auditable: no throttling or surprises about what's happening behind the scenes.",
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ export function PillarsSection() {
|
|||
<div className="text-muted-foreground my-4 space-y-1">
|
||||
<p>
|
||||
"The best model in the world" changes every other week. Providers
|
||||
throttle models with no warning. 1st-party coding agents only work with their own
|
||||
models.
|
||||
throttle models with no warning. 1st-party coding agents only work with their
|
||||
own models.
|
||||
</p>
|
||||
<p>Roo doesn't care.</p>
|
||||
<p>
|
||||
It works great with 10s of models, from frontier to open weight. Choose from{" "}
|
||||
<Link href="/provider/pricing">the curated selection we offer at-cost</Link> or
|
||||
bring your own key.
|
||||
<Link href="/provider">the curated selection we offer at-cost</Link> or bring
|
||||
your own key.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
|
|
@ -164,7 +164,7 @@ export function PillarsSection() {
|
|||
Developer tools need to fit like gloves. Highly tweakable,
|
||||
keyboard-shortcut-heavy gloves.
|
||||
</p>
|
||||
<p>We made Roo thoughtfully configurable to fit your workflow as best it can.</p>
|
||||
<p>We made Roo thoughtfully configurable to fit your workflow as best it can.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -188,7 +188,7 @@ export function PillarsSection() {
|
|||
your data for training.
|
||||
</p>
|
||||
<p>
|
||||
Plus we're fully SOC2 Type 2 compliant and follow industry-standard
|
||||
Plus we're fully SOC2 Type 2 compliant and follow industry-standard
|
||||
security practices.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ function DesktopUseCaseCard({ item }: { item: PositionedUseCase }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function UseExamplesSection() {
|
||||
export function UseExamplesSection({ agentTitle = false }: { agentTitle?: boolean }) {
|
||||
const positionedItems = useMemo(() => distributeItems(USE_CASES), [])
|
||||
const [showAllMobile, setShowAllMobile] = useState(false)
|
||||
|
||||
|
|
@ -384,7 +384,15 @@ export function UseExamplesSection() {
|
|||
<div className="container px-4 mx-auto sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold tracking-tight mb-4">
|
||||
The AI team to help your <em>entire</em> human team
|
||||
{agentTitle ? (
|
||||
<>
|
||||
Part of the AI team to help your <em>entire</em> human team
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
The AI team to help your <em>entire</em> human team
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
<p className="text-xl font-light text-muted-foreground max-w-2xl mx-auto">
|
||||
Developers, PMs, Designers, Customer Success: everyone moves faster and more independently with
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
import useEmblaCarousel from "embla-carousel-react"
|
||||
import AutoPlay from "embla-carousel-autoplay"
|
||||
import {
|
||||
Bug,
|
||||
FileText,
|
||||
Gauge,
|
||||
GitPullRequest,
|
||||
Languages,
|
||||
Microscope,
|
||||
PocketKnife,
|
||||
TestTube,
|
||||
Wrench,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
// AI Agent types for the carousel
|
||||
interface AIAgent {
|
||||
icon: LucideIcon
|
||||
name: string
|
||||
page?: string
|
||||
}
|
||||
|
||||
const aiAgents: AIAgent[] = [
|
||||
{ icon: GitPullRequest, name: "PR Reviewer", page: "/reviewer" },
|
||||
{ icon: Wrench, name: "PR Fixer", page: "/pr-fixer" },
|
||||
{ icon: PocketKnife, name: "Generalist" },
|
||||
{ icon: Bug, name: "Bug Fixer" },
|
||||
{ icon: TestTube, name: "Test Engineer" },
|
||||
{ icon: Microscope, name: "Security Auditor" },
|
||||
{ icon: Gauge, name: "Performance Optimizer" },
|
||||
{ icon: FileText, name: "Documentation Writer" },
|
||||
{ icon: Languages, name: "String Translator" },
|
||||
]
|
||||
|
||||
export function AgentCarousel({ currentAgent = "" }: { currentAgent?: string } = {}) {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel(
|
||||
{
|
||||
loop: true,
|
||||
align: "start",
|
||||
watchDrag: true,
|
||||
dragFree: false,
|
||||
containScroll: false,
|
||||
duration: 10000,
|
||||
},
|
||||
[
|
||||
AutoPlay({
|
||||
playOnInit: true,
|
||||
delay: 0,
|
||||
stopOnInteraction: false,
|
||||
stopOnMouseEnter: false,
|
||||
stopOnFocusIn: false,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
// Continuous scrolling effect
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return
|
||||
|
||||
const autoPlay = emblaApi?.plugins()?.autoPlay as
|
||||
| {
|
||||
play?: () => void
|
||||
}
|
||||
| undefined
|
||||
|
||||
if (autoPlay?.play) {
|
||||
autoPlay.play()
|
||||
}
|
||||
|
||||
// Set up continuous scrolling
|
||||
const interval = setInterval(() => {
|
||||
if (emblaApi) {
|
||||
emblaApi.scrollNext()
|
||||
}
|
||||
}, 30) // Smooth continuous scroll
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [emblaApi])
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Duplicate the agents array for seamless infinite scroll
|
||||
const displayAgents = [...aiAgents, ...aiAgents]
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden border-t border-border py-32">
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-12 max-w-4xl text-center">
|
||||
<div>
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
|
||||
The first members of a whole new team
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Architecture, coding, reviewing, testing, debugging, documenting, designing – almost
|
||||
everything we do today is mostly through our agents. Now we're bringing them to you.
|
||||
</p>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Roo's {currentAgent} isn't yet another single-purpose tool to add to your already
|
||||
complicated stack. It's the first member of your AI-powered development team. More
|
||||
agents are shipping soon.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto md:max-w-[1200px]">
|
||||
<motion.div
|
||||
className="relative -mx-4 md:mx-auto max-w-[1400px]"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}>
|
||||
{/* Gradient Overlays */}
|
||||
<div className="absolute inset-y-0 left-0 z-10 w-[10%] bg-gradient-to-r from-background to-transparent pointer-events-none md:w-[15%]" />
|
||||
<div className="absolute inset-y-0 right-0 z-10 w-[10%] bg-gradient-to-l from-background to-transparent pointer-events-none md:w-[15%]" />
|
||||
|
||||
{/* Embla Carousel Container */}
|
||||
<div className="overflow-hidden" ref={emblaRef}>
|
||||
<div className="flex pb-4">
|
||||
{displayAgents.map((agent, index) => {
|
||||
const Icon = agent.icon
|
||||
return (
|
||||
<div
|
||||
key={`${agent.name}-${index}`}
|
||||
className="relative min-w-0 flex-[0_0_45%] px-2 md:flex-[0_0_30%] md:px-4 lg:flex-[0_0_15%]">
|
||||
<div className="group relative py-6 cursor-default">
|
||||
<div
|
||||
className="relative flex flex-col items-center justify-center rounded-full w-[150px] h-[150px] border border-border bg-background p-6 transition-all duration-500 ease-out shadow-xl
|
||||
hover:scale-110 hover:-translate-y-2
|
||||
hover:shadow-[0_20px_50px_rgba(39,110,226,0.25)] dark:hover:shadow-[0_20px_50px_rgba(59,130,246,0.25)]">
|
||||
<Icon
|
||||
strokeWidth={1}
|
||||
className="size-9 mb-2 text-foreground transition-colors duration-300"
|
||||
/>
|
||||
<h3 className="text-center leading-tight tracking-tight transition-colors duration-300 dark:text-foreground">
|
||||
{agent.page ? (
|
||||
<a
|
||||
href={agent.page}
|
||||
className="text-foreground/90 font-semibold">
|
||||
{agent.name}
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-foreground/60 font-medium">
|
||||
{agent.name}
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
12
packages/evals/src/db/migrations/0005_strong_skrulls.sql
Normal file
12
packages/evals/src/db/migrations/0005_strong_skrulls.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
ALTER TABLE "tasks" DROP CONSTRAINT "tasks_run_id_runs_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "tasks" DROP CONSTRAINT "tasks_task_metrics_id_taskMetrics_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "toolErrors" DROP CONSTRAINT "toolErrors_run_id_runs_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "toolErrors" DROP CONSTRAINT "toolErrors_task_id_tasks_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_task_metrics_id_taskMetrics_id_fk" FOREIGN KEY ("task_metrics_id") REFERENCES "public"."taskMetrics"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "toolErrors" ADD CONSTRAINT "toolErrors_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "toolErrors" ADD CONSTRAINT "toolErrors_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;
|
||||
472
packages/evals/src/db/migrations/meta/0005_snapshot.json
Normal file
472
packages/evals/src/db/migrations/meta/0005_snapshot.json
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
{
|
||||
"id": "71b54967-86df-42ec-a200-bfd8dad85069",
|
||||
"prevId": "9caa4487-e146-4084-907d-fbf9cc3e03b9",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.runs": {
|
||||
"name": "runs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "runs_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"task_metrics_id": {
|
||||
"name": "task_metrics_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"contextWindow": {
|
||||
"name": "contextWindow",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"inputPrice": {
|
||||
"name": "inputPrice",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"outputPrice": {
|
||||
"name": "outputPrice",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cacheWritesPrice": {
|
||||
"name": "cacheWritesPrice",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cacheReadsPrice": {
|
||||
"name": "cacheReadsPrice",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"settings": {
|
||||
"name": "settings",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"jobToken": {
|
||||
"name": "jobToken",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"pid": {
|
||||
"name": "pid",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"socket_path": {
|
||||
"name": "socket_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"concurrency": {
|
||||
"name": "concurrency",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 2
|
||||
},
|
||||
"timeout": {
|
||||
"name": "timeout",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 5
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"runs_task_metrics_id_taskMetrics_id_fk": {
|
||||
"name": "runs_task_metrics_id_taskMetrics_id_fk",
|
||||
"tableFrom": "runs",
|
||||
"tableTo": "taskMetrics",
|
||||
"columnsFrom": ["task_metrics_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.taskMetrics": {
|
||||
"name": "taskMetrics",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "taskMetrics_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"tokens_in": {
|
||||
"name": "tokens_in",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens_out": {
|
||||
"name": "tokens_out",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens_context": {
|
||||
"name": "tokens_context",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"cache_writes": {
|
||||
"name": "cache_writes",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"cache_reads": {
|
||||
"name": "cache_reads",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"cost": {
|
||||
"name": "cost",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"duration": {
|
||||
"name": "duration",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tool_usage": {
|
||||
"name": "tool_usage",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tasks": {
|
||||
"name": "tasks",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "tasks_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"run_id": {
|
||||
"name": "run_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"task_metrics_id": {
|
||||
"name": "task_metrics_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"exercise": {
|
||||
"name": "exercise",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"iteration": {
|
||||
"name": "iteration",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"finished_at": {
|
||||
"name": "finished_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tasks_language_exercise_iteration_idx": {
|
||||
"name": "tasks_language_exercise_iteration_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "run_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "language",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "exercise",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "iteration",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tasks_run_id_runs_id_fk": {
|
||||
"name": "tasks_run_id_runs_id_fk",
|
||||
"tableFrom": "tasks",
|
||||
"tableTo": "runs",
|
||||
"columnsFrom": ["run_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tasks_task_metrics_id_taskMetrics_id_fk": {
|
||||
"name": "tasks_task_metrics_id_taskMetrics_id_fk",
|
||||
"tableFrom": "tasks",
|
||||
"tableTo": "taskMetrics",
|
||||
"columnsFrom": ["task_metrics_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.toolErrors": {
|
||||
"name": "toolErrors",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "toolErrors_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"run_id": {
|
||||
"name": "run_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"task_id": {
|
||||
"name": "task_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"tool_name": {
|
||||
"name": "tool_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"toolErrors_run_id_runs_id_fk": {
|
||||
"name": "toolErrors_run_id_runs_id_fk",
|
||||
"tableFrom": "toolErrors",
|
||||
"tableTo": "runs",
|
||||
"columnsFrom": ["run_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"toolErrors_task_id_tasks_id_fk": {
|
||||
"name": "toolErrors_task_id_tasks_id_fk",
|
||||
"tableFrom": "toolErrors",
|
||||
"tableTo": "tasks",
|
||||
"columnsFrom": ["task_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,13 @@
|
|||
"when": 1764201678953,
|
||||
"tag": "0004_sloppy_black_knight",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1765167049182,
|
||||
"tag": "0005_strong_skrulls",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,3 +134,67 @@ export const deleteRun = async (runId: number) => {
|
|||
|
||||
await db.delete(schema.taskMetrics).where(inArray(schema.taskMetrics.id, taskMetricsIds))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all runs without a taskMetricsId (incomplete runs)
|
||||
*/
|
||||
export const getIncompleteRuns = async () => {
|
||||
return db.query.runs.findMany({
|
||||
where: sql`${schema.runs.taskMetricsId} IS NULL`,
|
||||
columns: { id: true },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple runs by their IDs
|
||||
*/
|
||||
export const deleteRunsByIds = async (runIds: number[]) => {
|
||||
if (runIds.length === 0) return
|
||||
|
||||
// Get all tasks for these runs
|
||||
const tasks = await db.query.tasks.findMany({
|
||||
where: inArray(schema.tasks.runId, runIds),
|
||||
columns: { id: true, taskMetricsId: true },
|
||||
})
|
||||
|
||||
const taskIds = tasks.map(({ id }) => id)
|
||||
|
||||
// Get run taskMetricsIds
|
||||
const runs = await db.query.runs.findMany({
|
||||
where: inArray(schema.runs.id, runIds),
|
||||
columns: { taskMetricsId: true },
|
||||
})
|
||||
|
||||
// Delete tool errors for tasks
|
||||
if (taskIds.length > 0) {
|
||||
await db.delete(schema.toolErrors).where(inArray(schema.toolErrors.taskId, taskIds))
|
||||
}
|
||||
|
||||
// Delete tasks
|
||||
await db.delete(schema.tasks).where(inArray(schema.tasks.runId, runIds))
|
||||
|
||||
// Delete tool errors for runs
|
||||
await db.delete(schema.toolErrors).where(inArray(schema.toolErrors.runId, runIds))
|
||||
|
||||
// Delete from tables that exist in DB but not in drizzle schema
|
||||
// Using individual deletes since drizzle's sql template doesn't support custom table schemas
|
||||
for (const runId of runIds) {
|
||||
await db.execute(sql`DELETE FROM "cpuMetrics" WHERE run_id = ${runId}`)
|
||||
await db.execute(sql`DELETE FROM "notes" WHERE run_id = ${runId}`)
|
||||
}
|
||||
|
||||
// Delete runs
|
||||
await db.delete(schema.runs).where(inArray(schema.runs.id, runIds))
|
||||
|
||||
// Delete task metrics
|
||||
const taskMetricsIds = [
|
||||
...tasks
|
||||
.map(({ taskMetricsId }) => taskMetricsId)
|
||||
.filter((id): id is number => id !== null && id !== undefined),
|
||||
...runs.map(({ taskMetricsId }) => taskMetricsId).filter((id): id is number => id !== null && id !== undefined),
|
||||
]
|
||||
|
||||
if (taskMetricsIds.length > 0) {
|
||||
await db.delete(schema.taskMetrics).where(inArray(schema.taskMetrics.id, taskMetricsIds))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ export const tasks = pgTable(
|
|||
{
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
runId: integer("run_id")
|
||||
.references(() => runs.id)
|
||||
.references(() => runs.id, { onDelete: "cascade" })
|
||||
.notNull(),
|
||||
taskMetricsId: integer("task_metrics_id").references(() => taskMetrics.id),
|
||||
taskMetricsId: integer("task_metrics_id").references(() => taskMetrics.id, { onDelete: "set null" }),
|
||||
language: text().notNull().$type<ExerciseLanguage>(),
|
||||
exercise: text().notNull(),
|
||||
iteration: integer().default(1).notNull(),
|
||||
|
|
@ -111,8 +111,8 @@ export type UpdateTaskMetrics = Partial<Omit<TaskMetrics, "id" | "createdAt">>
|
|||
|
||||
export const toolErrors = pgTable("toolErrors", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
runId: integer("run_id").references(() => runs.id),
|
||||
taskId: integer("task_id").references(() => tasks.id),
|
||||
runId: integer("run_id").references(() => runs.id, { onDelete: "cascade" }),
|
||||
taskId: integer("task_id").references(() => tasks.id, { onDelete: "cascade" }),
|
||||
toolName: text("tool_name").notNull().$type<ToolName>(),
|
||||
error: text().notNull(),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
|
|
|
|||
28
packages/types/src/__tests__/context-management.test.ts
Normal file
28
packages/types/src/__tests__/context-management.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { describe, it, expect } from "vitest"
|
||||
import { CONTEXT_MANAGEMENT_EVENTS, isContextManagementEvent } from "../context-management.js"
|
||||
|
||||
describe("context-management", () => {
|
||||
describe("CONTEXT_MANAGEMENT_EVENTS", () => {
|
||||
it("should contain all expected event types", () => {
|
||||
expect(CONTEXT_MANAGEMENT_EVENTS).toContain("condense_context")
|
||||
expect(CONTEXT_MANAGEMENT_EVENTS).toContain("condense_context_error")
|
||||
expect(CONTEXT_MANAGEMENT_EVENTS).toContain("sliding_window_truncation")
|
||||
expect(CONTEXT_MANAGEMENT_EVENTS).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isContextManagementEvent", () => {
|
||||
it("should return true for valid context management events", () => {
|
||||
expect(isContextManagementEvent("condense_context")).toBe(true)
|
||||
expect(isContextManagementEvent("condense_context_error")).toBe(true)
|
||||
expect(isContextManagementEvent("sliding_window_truncation")).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false for non-context-management events", () => {
|
||||
expect(isContextManagementEvent("text")).toBe(false)
|
||||
expect(isContextManagementEvent("error")).toBe(false)
|
||||
expect(isContextManagementEvent(null)).toBe(false)
|
||||
expect(isContextManagementEvent(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
34
packages/types/src/context-management.ts
Normal file
34
packages/types/src/context-management.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Context Management Types
|
||||
*
|
||||
* This module provides type definitions for context management events.
|
||||
* These events are used to handle different strategies for managing conversation context
|
||||
* when approaching token limits.
|
||||
*
|
||||
* Event Types:
|
||||
* - `condense_context`: Context was condensed using AI summarization
|
||||
* - `condense_context_error`: An error occurred during context condensation
|
||||
* - `sliding_window_truncation`: Context was truncated using sliding window strategy
|
||||
*/
|
||||
|
||||
/**
|
||||
* Array of all context management event types.
|
||||
* Used for runtime type checking.
|
||||
*/
|
||||
export const CONTEXT_MANAGEMENT_EVENTS = [
|
||||
"condense_context",
|
||||
"condense_context_error",
|
||||
"sliding_window_truncation",
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Union type representing all possible context management event types.
|
||||
*/
|
||||
export type ContextManagementEvent = (typeof CONTEXT_MANAGEMENT_EVENTS)[number]
|
||||
|
||||
/**
|
||||
* Type guard function to check if a value is a valid context management event.
|
||||
*/
|
||||
export function isContextManagementEvent(value: unknown): value is ContextManagementEvent {
|
||||
return typeof value === "string" && (CONTEXT_MANAGEMENT_EVENTS as readonly string[]).includes(value)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
export * from "./api.js"
|
||||
export * from "./cloud.js"
|
||||
export * from "./codebase-index.js"
|
||||
export * from "./context-management.js"
|
||||
export * from "./cookie-consent.js"
|
||||
export * from "./events.js"
|
||||
export * from "./experiment.js"
|
||||
|
|
|
|||
|
|
@ -197,8 +197,17 @@ export type ToolProgressStatus = z.infer<typeof toolProgressStatusSchema>
|
|||
|
||||
/**
|
||||
* ContextCondense
|
||||
*
|
||||
* Data associated with a successful context condensation event.
|
||||
* This is attached to messages with `say: "condense_context"` when
|
||||
* the condensation operation completes successfully.
|
||||
*
|
||||
* @property cost - The API cost incurred for the condensation operation
|
||||
* @property prevContextTokens - Token count before condensation
|
||||
* @property newContextTokens - Token count after condensation
|
||||
* @property summary - The condensed summary that replaced the original context
|
||||
* @property condenseId - Optional unique identifier for this condensation operation
|
||||
*/
|
||||
|
||||
export const contextCondenseSchema = z.object({
|
||||
cost: z.number(),
|
||||
prevContextTokens: z.number(),
|
||||
|
|
@ -212,21 +221,39 @@ export type ContextCondense = z.infer<typeof contextCondenseSchema>
|
|||
/**
|
||||
* ContextTruncation
|
||||
*
|
||||
* Used to track sliding window truncation events for the UI.
|
||||
* Data associated with a sliding window truncation event.
|
||||
* This is attached to messages with `say: "sliding_window_truncation"` when
|
||||
* messages are removed from the conversation history to stay within token limits.
|
||||
*
|
||||
* Unlike condensation, truncation simply removes older messages without
|
||||
* summarizing them. This is a faster but less context-preserving approach.
|
||||
*
|
||||
* @property truncationId - Unique identifier for this truncation operation
|
||||
* @property messagesRemoved - Number of conversation messages that were removed
|
||||
* @property prevContextTokens - Token count before truncation occurred
|
||||
* @property newContextTokens - Token count after truncation occurred
|
||||
*/
|
||||
|
||||
export const contextTruncationSchema = z.object({
|
||||
truncationId: z.string(),
|
||||
messagesRemoved: z.number(),
|
||||
prevContextTokens: z.number(),
|
||||
newContextTokens: z.number(),
|
||||
})
|
||||
|
||||
export type ContextTruncation = z.infer<typeof contextTruncationSchema>
|
||||
|
||||
/**
|
||||
* ClineMessage
|
||||
*
|
||||
* The main message type used for communication between the extension and webview.
|
||||
* Messages can either be "ask" (requiring user response) or "say" (informational).
|
||||
*
|
||||
* Context Management Fields:
|
||||
* - `contextCondense`: Present when `say: "condense_context"` and condensation succeeded
|
||||
* - `contextTruncation`: Present when `say: "sliding_window_truncation"` and truncation occurred
|
||||
*
|
||||
* Note: These fields are mutually exclusive - a message will have at most one of them.
|
||||
*/
|
||||
|
||||
export const clineMessageSchema = z.object({
|
||||
ts: z.number(),
|
||||
type: z.union([z.literal("ask"), z.literal("say")]),
|
||||
|
|
@ -239,7 +266,15 @@ export const clineMessageSchema = z.object({
|
|||
conversationHistoryIndex: z.number().optional(),
|
||||
checkpoint: z.record(z.string(), z.unknown()).optional(),
|
||||
progressStatus: toolProgressStatusSchema.optional(),
|
||||
/**
|
||||
* Data for successful context condensation.
|
||||
* Present when `say: "condense_context"` and `partial: false`.
|
||||
*/
|
||||
contextCondense: contextCondenseSchema.optional(),
|
||||
/**
|
||||
* Data for sliding window truncation.
|
||||
* Present when `say: "sliding_window_truncation"`.
|
||||
*/
|
||||
contextTruncation: contextTruncationSchema.optional(),
|
||||
isProtected: z.boolean().optional(),
|
||||
apiProtocol: z.union([z.literal("openai"), z.literal("anthropic")]).optional(),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export type ReasoningEffortWithMinimal = z.infer<typeof reasoningEffortWithMinim
|
|||
* Extended Reasoning Effort (includes "none" and "minimal")
|
||||
* Note: "disable" is a UI/control value, not a value sent as effort
|
||||
*/
|
||||
export const reasoningEffortsExtended = ["none", "minimal", "low", "medium", "high"] as const
|
||||
export const reasoningEffortsExtended = ["none", "minimal", "low", "medium", "high", "xhigh"] as const
|
||||
|
||||
export const reasoningEffortExtendedSchema = z.enum(reasoningEffortsExtended)
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ export type ReasoningEffortExtended = z.infer<typeof reasoningEffortExtendedSche
|
|||
/**
|
||||
* Reasoning Effort user setting (includes "disable")
|
||||
*/
|
||||
export const reasoningEffortSettingValues = ["disable", "none", "minimal", "low", "medium", "high"] as const
|
||||
export const reasoningEffortSettingValues = ["disable", "none", "minimal", "low", "medium", "high", "xhigh"] as const
|
||||
export const reasoningEffortSettingSchema = z.enum(reasoningEffortSettingValues)
|
||||
|
||||
/**
|
||||
|
|
@ -88,7 +88,7 @@ export const modelInfoSchema = z.object({
|
|||
defaultTemperature: z.number().optional(),
|
||||
requiredReasoningBudget: z.boolean().optional(),
|
||||
supportsReasoningEffort: z
|
||||
.union([z.boolean(), z.array(z.enum(["disable", "none", "minimal", "low", "medium", "high"]))])
|
||||
.union([z.boolean(), z.array(z.enum(["disable", "none", "minimal", "low", "medium", "high", "xhigh"]))])
|
||||
.optional(),
|
||||
requiredReasoningEffort: z.boolean().optional(),
|
||||
preserveReasoning: z.boolean().optional(),
|
||||
|
|
|
|||
|
|
@ -73,6 +73,18 @@ export const basetenModels = {
|
|||
description:
|
||||
"Extremely capable general-purpose LLM with hybrid reasoning capabilities and advanced tool calling",
|
||||
},
|
||||
"deepseek-ai/DeepSeek-V3.2": {
|
||||
maxTokens: 131_072,
|
||||
contextWindow: 163_840,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.3,
|
||||
outputPrice: 0.45,
|
||||
cacheWritesPrice: 0,
|
||||
cacheReadsPrice: 0,
|
||||
description:
|
||||
"DeepSeek's hybrid reasoning model with efficient long context scaling with GPT-5 level performance",
|
||||
},
|
||||
"Qwen/Qwen3-235B-A22B-Instruct-2507": {
|
||||
maxTokens: 262_144,
|
||||
contextWindow: 262_144,
|
||||
|
|
|
|||
|
|
@ -439,6 +439,52 @@ export const bedrockModels = {
|
|||
inputPrice: 0.02,
|
||||
description: "Amazon Titan Text Embeddings V2",
|
||||
},
|
||||
"moonshot.kimi-k2-thinking": {
|
||||
maxTokens: 32_000,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.6,
|
||||
outputPrice: 2.5,
|
||||
description: "Kimi K2 Thinking (1T parameter MoE model with 32B active parameters)",
|
||||
},
|
||||
"minimax.minimax-m2": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 196_608,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.3,
|
||||
outputPrice: 1.2,
|
||||
description: "MiniMax M2 (230B parameter MoE model with 10B active parameters)",
|
||||
},
|
||||
"qwen.qwen3-next-80b-a3b": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
inputPrice: 0.15,
|
||||
outputPrice: 1.2,
|
||||
description: "Qwen3 Next 80B (MoE model with 3B active parameters)",
|
||||
},
|
||||
"qwen.qwen3-coder-480b-a35b-v1:0": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
inputPrice: 0.45,
|
||||
outputPrice: 1.8,
|
||||
description: "Qwen3 Coder 480B (MoE model with 35B active parameters)",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
export const BEDROCK_DEFAULT_TEMPERATURE = 0.3
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const cerebrasDefaultModelId: CerebrasModelId = "gpt-oss-120b"
|
|||
|
||||
export const cerebrasModels = {
|
||||
"zai-glm-4.6": {
|
||||
maxTokens: 16384, // consistent with their other models
|
||||
maxTokens: 8192, // Conservative default to avoid premature rate limiting (Cerebras reserves quota upfront)
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
|
|
@ -17,7 +17,7 @@ export const cerebrasModels = {
|
|||
description: "Highly intelligent general purpose model with up to 1,000 tokens/s",
|
||||
},
|
||||
"qwen-3-235b-a22b-instruct-2507": {
|
||||
maxTokens: 64000,
|
||||
maxTokens: 8192, // Conservative default to avoid premature rate limiting
|
||||
contextWindow: 64000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
|
|
@ -27,7 +27,7 @@ export const cerebrasModels = {
|
|||
description: "Intelligent model with ~1400 tokens/s",
|
||||
},
|
||||
"llama-3.3-70b": {
|
||||
maxTokens: 64000,
|
||||
maxTokens: 8192, // Conservative default to avoid premature rate limiting
|
||||
contextWindow: 64000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
|
|
@ -37,7 +37,7 @@ export const cerebrasModels = {
|
|||
description: "Powerful model with ~2600 tokens/s",
|
||||
},
|
||||
"qwen-3-32b": {
|
||||
maxTokens: 64000,
|
||||
maxTokens: 8192, // Conservative default to avoid premature rate limiting
|
||||
contextWindow: 64000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
|
|
@ -47,7 +47,7 @@ export const cerebrasModels = {
|
|||
description: "SOTA coding performance with ~2500 tokens/s",
|
||||
},
|
||||
"gpt-oss-120b": {
|
||||
maxTokens: 8000,
|
||||
maxTokens: 8192, // Conservative default to avoid premature rate limiting
|
||||
contextWindow: 64000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,31 @@ export type OpenAiNativeModelId = keyof typeof openAiNativeModels
|
|||
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.1"
|
||||
|
||||
export const openAiNativeModels = {
|
||||
"gpt-5.1-codex-max": {
|
||||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
promptCacheRetention: "24h",
|
||||
supportsReasoningEffort: ["low", "medium", "high", "xhigh"],
|
||||
reasoningEffort: "medium",
|
||||
inputPrice: 1.25,
|
||||
outputPrice: 10.0,
|
||||
cacheReadsPrice: 0.125,
|
||||
supportsTemperature: false,
|
||||
tiers: [{ name: "priority", contextWindow: 400000, inputPrice: 2.5, outputPrice: 20.0, cacheReadsPrice: 0.25 }],
|
||||
description:
|
||||
"GPT-5.1 Codex Max: Our most intelligent coding model optimized for long-horizon, agentic coding tasks",
|
||||
},
|
||||
"gpt-5.1": {
|
||||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
promptCacheRetention: "24h",
|
||||
|
|
@ -30,6 +51,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
promptCacheRetention: "24h",
|
||||
|
|
@ -46,6 +69,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
promptCacheRetention: "24h",
|
||||
|
|
@ -61,6 +86,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
|
||||
|
|
@ -80,6 +107,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
|
||||
|
|
@ -99,6 +128,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["low", "medium", "high"],
|
||||
|
|
@ -114,6 +145,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
|
||||
|
|
@ -130,6 +163,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 1.25,
|
||||
|
|
@ -141,6 +176,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 32_768,
|
||||
contextWindow: 1_047_576,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 2,
|
||||
|
|
@ -155,6 +192,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 32_768,
|
||||
contextWindow: 1_047_576,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0.4,
|
||||
|
|
@ -169,6 +208,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 32_768,
|
||||
contextWindow: 1_047_576,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0.1,
|
||||
|
|
@ -377,6 +418,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
|
||||
|
|
@ -396,6 +439,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
|
||||
|
|
@ -415,6 +460,8 @@ export const openAiNativeModels = {
|
|||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsNativeTools: true,
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ export const RooModelSchema = z.object({
|
|||
pricing: RooPricingSchema,
|
||||
deprecated: z.boolean().optional(),
|
||||
default_temperature: z.number().optional(),
|
||||
// Dynamic settings that map directly to ModelInfo properties
|
||||
// Allows the API to configure model-specific defaults like includedTools, excludedTools, reasoningEffort, etc.
|
||||
settings: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
|
||||
export const RooModelsResponseSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ export const xaiDefaultModelId: XAIModelId = "grok-code-fast-1"
|
|||
export const xaiModels = {
|
||||
"grok-code-fast-1": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
contextWindow: 256_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 0.2,
|
||||
|
|
@ -17,6 +17,8 @@ export const xaiModels = {
|
|||
cacheWritesPrice: 0.02,
|
||||
cacheReadsPrice: 0.02,
|
||||
description: "xAI's Grok Code Fast model with 256K context window",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-4-1-fast-reasoning": {
|
||||
maxTokens: 65_536,
|
||||
|
|
@ -30,6 +32,8 @@ export const xaiModels = {
|
|||
cacheReadsPrice: 0.05,
|
||||
description:
|
||||
"xAI's Grok 4.1 Fast model with 2M context window, optimized for high-performance agentic tool calling with reasoning",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-4-1-fast-non-reasoning": {
|
||||
maxTokens: 65_536,
|
||||
|
|
@ -43,6 +47,8 @@ export const xaiModels = {
|
|||
cacheReadsPrice: 0.05,
|
||||
description:
|
||||
"xAI's Grok 4.1 Fast model with 2M context window, optimized for high-performance agentic tool calling",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-4-fast-reasoning": {
|
||||
maxTokens: 65_536,
|
||||
|
|
@ -56,6 +62,8 @@ export const xaiModels = {
|
|||
cacheReadsPrice: 0.05,
|
||||
description:
|
||||
"xAI's Grok 4 Fast model with 2M context window, optimized for high-performance agentic tool calling with reasoning",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-4-fast-non-reasoning": {
|
||||
maxTokens: 65_536,
|
||||
|
|
@ -69,10 +77,12 @@ export const xaiModels = {
|
|||
cacheReadsPrice: 0.05,
|
||||
description:
|
||||
"xAI's Grok 4 Fast model with 2M context window, optimized for high-performance agentic tool calling",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-4": {
|
||||
"grok-4-0709": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 256000,
|
||||
contextWindow: 256_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
|
|
@ -81,35 +91,13 @@ export const xaiModels = {
|
|||
cacheWritesPrice: 0.75,
|
||||
cacheReadsPrice: 0.75,
|
||||
description: "xAI's Grok-4 model with 256K context window",
|
||||
},
|
||||
"grok-3": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 0.75,
|
||||
cacheReadsPrice: 0.75,
|
||||
description: "xAI's Grok-3 model with 128K context window",
|
||||
},
|
||||
"grok-3-fast": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 5.0,
|
||||
outputPrice: 25.0,
|
||||
cacheWritesPrice: 1.25,
|
||||
cacheReadsPrice: 1.25,
|
||||
description: "xAI's Grok-3 fast model with 128K context window",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-3-mini": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 0.3,
|
||||
|
|
@ -117,39 +105,23 @@ export const xaiModels = {
|
|||
cacheWritesPrice: 0.07,
|
||||
cacheReadsPrice: 0.07,
|
||||
description: "xAI's Grok-3 mini model with 128K context window",
|
||||
supportsReasoningEffort: true,
|
||||
supportsReasoningEffort: ["low", "high"],
|
||||
reasoningEffort: "low",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-3-mini-fast": {
|
||||
"grok-3": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 0.6,
|
||||
outputPrice: 4.0,
|
||||
cacheWritesPrice: 0.15,
|
||||
cacheReadsPrice: 0.15,
|
||||
description: "xAI's Grok-3 mini fast model with 128K context window",
|
||||
supportsReasoningEffort: true,
|
||||
},
|
||||
"grok-2-1212": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 2.0,
|
||||
outputPrice: 10.0,
|
||||
description: "xAI's Grok-2 model (version 1212) with 128K context window",
|
||||
},
|
||||
"grok-2-vision-1212": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 32768,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 2.0,
|
||||
outputPrice: 10.0,
|
||||
description: "xAI's Grok-2 Vision model (version 1212) with image support and 32K context window",
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 0.75,
|
||||
cacheReadsPrice: 0.75,
|
||||
description: "xAI's Grok-3 model with 128K context window",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const toolNames = [
|
|||
"write_to_file",
|
||||
"apply_diff",
|
||||
"search_and_replace",
|
||||
"search_replace",
|
||||
"apply_patch",
|
||||
"search_files",
|
||||
"list_files",
|
||||
|
|
|
|||
BIN
releases/3.36.0-release.png
Normal file
BIN
releases/3.36.0-release.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
BIN
releases/3.36.1-release.png
Normal file
BIN
releases/3.36.1-release.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
BIN
releases/3.36.2-release.png
Normal file
BIN
releases/3.36.2-release.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
|
|
@ -16,12 +16,10 @@ import { MiniMaxHandler } from "../minimax"
|
|||
|
||||
vitest.mock("@anthropic-ai/sdk", () => {
|
||||
const mockCreate = vitest.fn()
|
||||
const mockCountTokens = vitest.fn()
|
||||
return {
|
||||
Anthropic: vitest.fn(() => ({
|
||||
messages: {
|
||||
create: mockCreate,
|
||||
countTokens: mockCountTokens,
|
||||
},
|
||||
})),
|
||||
}
|
||||
|
|
@ -30,13 +28,11 @@ vitest.mock("@anthropic-ai/sdk", () => {
|
|||
describe("MiniMaxHandler", () => {
|
||||
let handler: MiniMaxHandler
|
||||
let mockCreate: any
|
||||
let mockCountTokens: any
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
const anthropicInstance = (Anthropic as unknown as any)()
|
||||
mockCreate = anthropicInstance.messages.create
|
||||
mockCountTokens = anthropicInstance.messages.countTokens
|
||||
})
|
||||
|
||||
describe("International MiniMax (default)", () => {
|
||||
|
|
|
|||
|
|
@ -464,6 +464,46 @@ describe("OpenAiNativeHandler", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should support xhigh reasoning effort for GPT-5.1 Codex Max", async () => {
|
||||
// Mock fetch for Responses API
|
||||
const mockFetch = vitest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode(
|
||||
'data: {"type":"response.output_item.added","item":{"type":"text","text":"XHigh effort"}}\n\n',
|
||||
),
|
||||
)
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
})
|
||||
global.fetch = mockFetch as any
|
||||
|
||||
// Mock SDK to fail
|
||||
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
|
||||
|
||||
handler = new OpenAiNativeHandler({
|
||||
...mockOptions,
|
||||
apiModelId: "gpt-5.1-codex-max",
|
||||
reasoningEffort: "xhigh",
|
||||
})
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// drain
|
||||
}
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"https://api.openai.com/v1/responses",
|
||||
expect.objectContaining({
|
||||
body: expect.stringContaining('"effort":"xhigh"'),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should omit reasoning when selection is 'disable'", async () => {
|
||||
// Mock fetch for Responses API
|
||||
const mockFetch = vitest.fn().mockResolvedValue({
|
||||
|
|
|
|||
|
|
@ -283,6 +283,79 @@ describe("OpenRouterHandler", () => {
|
|||
const generator = handler.createMessage("test", [])
|
||||
await expect(generator.next()).rejects.toThrow("OpenRouter API Error 500: API Error")
|
||||
})
|
||||
|
||||
it("yields tool_call_end events when finish_reason is tool_calls", async () => {
|
||||
// Import NativeToolCallParser to set up state
|
||||
const { NativeToolCallParser } = await import("../../../core/assistant-message/NativeToolCallParser")
|
||||
|
||||
// Clear any previous state
|
||||
NativeToolCallParser.clearRawChunkState()
|
||||
|
||||
const handler = new OpenRouterHandler(mockOptions)
|
||||
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
id: "test-id",
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
id: "call_openrouter_test",
|
||||
function: { name: "read_file", arguments: '{"path":"test.ts"}' },
|
||||
},
|
||||
],
|
||||
},
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
yield {
|
||||
id: "test-id",
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
finish_reason: "tool_calls",
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
const generator = handler.createMessage("test", [])
|
||||
const chunks = []
|
||||
|
||||
for await (const chunk of generator) {
|
||||
// Simulate what Task.ts does: when we receive tool_call_partial,
|
||||
// process it through NativeToolCallParser to populate rawChunkTracker
|
||||
if (chunk.type === "tool_call_partial") {
|
||||
NativeToolCallParser.processRawChunk({
|
||||
index: chunk.index,
|
||||
id: chunk.id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
})
|
||||
}
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Should have tool_call_partial and tool_call_end
|
||||
const partialChunks = chunks.filter((chunk) => chunk.type === "tool_call_partial")
|
||||
const endChunks = chunks.filter((chunk) => chunk.type === "tool_call_end")
|
||||
|
||||
expect(partialChunks).toHaveLength(1)
|
||||
expect(endChunks).toHaveLength(1)
|
||||
expect(endChunks[0].id).toBe("call_openrouter_test")
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ vitest.mock("../../providers/fetchers/modelCache", () => ({
|
|||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
defaultToolProtocol: "native",
|
||||
},
|
||||
"minimax/minimax-m2:free": {
|
||||
maxTokens: 32_768,
|
||||
|
|
@ -110,6 +111,7 @@ vitest.mock("../../providers/fetchers/modelCache", () => ({
|
|||
supportsNativeTools: true,
|
||||
inputPrice: 0.15,
|
||||
outputPrice: 0.6,
|
||||
defaultToolProtocol: "native",
|
||||
},
|
||||
"anthropic/claude-haiku-4.5": {
|
||||
maxTokens: 8_192,
|
||||
|
|
@ -119,6 +121,7 @@ vitest.mock("../../providers/fetchers/modelCache", () => ({
|
|||
supportsNativeTools: true,
|
||||
inputPrice: 0.8,
|
||||
outputPrice: 4,
|
||||
defaultToolProtocol: "native",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -425,36 +428,23 @@ describe("RooHandler", () => {
|
|||
}
|
||||
})
|
||||
|
||||
it("should apply defaultToolProtocol: native for minimax/minimax-m2:free", () => {
|
||||
it("should have defaultToolProtocol: native for all roo provider models", () => {
|
||||
// Test that all models have defaultToolProtocol: native
|
||||
const testModels = ["minimax/minimax-m2:free", "anthropic/claude-haiku-4.5", "xai/grok-code-fast-1"]
|
||||
for (const modelId of testModels) {
|
||||
const handlerWithModel = new RooHandler({ apiModelId: modelId })
|
||||
const modelInfo = handlerWithModel.getModel()
|
||||
expect(modelInfo.id).toBe(modelId)
|
||||
expect((modelInfo.info as any).defaultToolProtocol).toBe("native")
|
||||
}
|
||||
})
|
||||
|
||||
it("should return cached model info with settings applied from API", () => {
|
||||
const handlerWithMinimax = new RooHandler({
|
||||
apiModelId: "minimax/minimax-m2:free",
|
||||
})
|
||||
const modelInfo = handlerWithMinimax.getModel()
|
||||
expect(modelInfo.id).toBe("minimax/minimax-m2:free")
|
||||
expect((modelInfo.info as any).defaultToolProtocol).toBe("native")
|
||||
// Verify cached model info is preserved
|
||||
expect(modelInfo.info.maxTokens).toBe(32_768)
|
||||
expect(modelInfo.info.contextWindow).toBe(1_000_000)
|
||||
})
|
||||
|
||||
it("should apply defaultToolProtocol: native for anthropic/claude-haiku-4.5", () => {
|
||||
const handlerWithHaiku = new RooHandler({
|
||||
apiModelId: "anthropic/claude-haiku-4.5",
|
||||
})
|
||||
const modelInfo = handlerWithHaiku.getModel()
|
||||
expect(modelInfo.id).toBe("anthropic/claude-haiku-4.5")
|
||||
expect((modelInfo.info as any).defaultToolProtocol).toBe("native")
|
||||
// Verify cached model info is preserved
|
||||
expect(modelInfo.info.maxTokens).toBe(8_192)
|
||||
expect(modelInfo.info.contextWindow).toBe(200_000)
|
||||
})
|
||||
|
||||
it("should not override existing properties when applying MODEL_DEFAULTS", () => {
|
||||
const handlerWithMinimax = new RooHandler({
|
||||
apiModelId: "minimax/minimax-m2:free",
|
||||
})
|
||||
const modelInfo = handlerWithMinimax.getModel()
|
||||
// The defaults should be merged, but not overwrite existing cached values
|
||||
// The settings from API should already be applied in the cached model info
|
||||
expect(modelInfo.info.supportsNativeTools).toBe(true)
|
||||
expect(modelInfo.info.inputPrice).toBe(0.15)
|
||||
expect(modelInfo.info.outputPrice).toBe(0.6)
|
||||
|
|
@ -1012,5 +1002,68 @@ describe("RooHandler", () => {
|
|||
const rawChunks = chunks.filter((chunk) => chunk.type === "tool_call_partial")
|
||||
expect(rawChunks).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("should yield tool_call_end events when finish_reason is tool_calls", async () => {
|
||||
// Import NativeToolCallParser to set up state
|
||||
const { NativeToolCallParser } = await import("../../../core/assistant-message/NativeToolCallParser")
|
||||
|
||||
// Clear any previous state
|
||||
NativeToolCallParser.clearRawChunkState()
|
||||
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
id: "call_finish_test",
|
||||
function: { name: "read_file", arguments: '{"path":"test.ts"}' },
|
||||
},
|
||||
],
|
||||
},
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
finish_reason: "tool_calls",
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
// Simulate what Task.ts does: when we receive tool_call_partial,
|
||||
// process it through NativeToolCallParser to populate rawChunkTracker
|
||||
if (chunk.type === "tool_call_partial") {
|
||||
NativeToolCallParser.processRawChunk({
|
||||
index: chunk.index,
|
||||
id: chunk.id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
})
|
||||
}
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Should have tool_call_partial and tool_call_end
|
||||
const partialChunks = chunks.filter((chunk) => chunk.type === "tool_call_partial")
|
||||
const endChunks = chunks.filter((chunk) => chunk.type === "tool_call_end")
|
||||
|
||||
expect(partialChunks).toHaveLength(1)
|
||||
expect(endChunks).toHaveLength(1)
|
||||
expect(endChunks[0].id).toBe("call_finish_test")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -402,30 +402,4 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
|||
const content = message.content.find(({ type }) => type === "text")
|
||||
return content?.type === "text" ? content.text : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts tokens for the given content using Anthropic's API
|
||||
*
|
||||
* @param content The content blocks to count tokens for
|
||||
* @returns A promise resolving to the token count
|
||||
*/
|
||||
override async countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number> {
|
||||
try {
|
||||
// Use the current model
|
||||
const { id: model } = this.getModel()
|
||||
|
||||
const response = await this.client.messages.countTokens({
|
||||
model,
|
||||
messages: [{ role: "user", content: content }],
|
||||
})
|
||||
|
||||
return response.input_tokens
|
||||
} catch (error) {
|
||||
// Log error but fallback to tiktoken estimation
|
||||
console.warn("Anthropic token counting failed, using fallback", error)
|
||||
|
||||
// Use the base provider's implementation as fallback
|
||||
return super.countTokens(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import { t } from "../../i18n"
|
|||
const CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1"
|
||||
const CEREBRAS_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
const CEREBRAS_INTEGRATION_HEADER = "X-Cerebras-3rd-Party-Integration"
|
||||
const CEREBRAS_INTEGRATION_NAME = "roocode"
|
||||
|
||||
export class CerebrasHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
private apiKey: string
|
||||
private providerModels: typeof cerebrasModels
|
||||
|
|
@ -36,11 +39,12 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan
|
|||
}
|
||||
|
||||
getModel(): { id: CerebrasModelId; info: (typeof cerebrasModels)[CerebrasModelId] } {
|
||||
const modelId = (this.options.apiModelId as CerebrasModelId) || this.defaultProviderModelId
|
||||
const modelId = this.options.apiModelId as CerebrasModelId
|
||||
const validModelId = modelId && this.providerModels[modelId] ? modelId : this.defaultProviderModelId
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
info: this.providerModels[modelId],
|
||||
id: validModelId,
|
||||
info: this.providerModels[validModelId],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +134,7 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan
|
|||
...DEFAULT_HEADERS,
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
[CEREBRAS_INTEGRATION_HEADER]: CEREBRAS_INTEGRATION_NAME,
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
|
@ -291,6 +296,7 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan
|
|||
...DEFAULT_HEADERS,
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
[CEREBRAS_INTEGRATION_HEADER]: CEREBRAS_INTEGRATION_NAME,
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ describe("OpenRouter API", () => {
|
|||
supportsReasoningEffort: false,
|
||||
supportsNativeTools: true,
|
||||
supportedParameters: ["max_tokens", "temperature", "reasoning", "include_reasoning"],
|
||||
defaultToolProtocol: "native",
|
||||
})
|
||||
|
||||
expect(models["anthropic/claude-3.7-sonnet:thinking"]).toEqual({
|
||||
|
|
@ -47,6 +48,7 @@ describe("OpenRouter API", () => {
|
|||
supportsReasoningEffort: true,
|
||||
supportsNativeTools: true,
|
||||
supportedParameters: ["max_tokens", "temperature", "reasoning", "include_reasoning"],
|
||||
defaultToolProtocol: "native",
|
||||
})
|
||||
|
||||
expect(models["google/gemini-2.5-flash-preview-05-20"].maxTokens).toEqual(65535)
|
||||
|
|
@ -390,5 +392,55 @@ describe("OpenRouter API", () => {
|
|||
expect(textResult.maxTokens).toBe(64000)
|
||||
expect(imageResult.maxTokens).toBe(64000)
|
||||
})
|
||||
|
||||
it("sets defaultToolProtocol to native when model supports native tools", () => {
|
||||
const mockModel = {
|
||||
name: "Tools Model",
|
||||
description: "Model with native tool support",
|
||||
context_length: 128000,
|
||||
max_completion_tokens: 8192,
|
||||
pricing: {
|
||||
prompt: "0.000003",
|
||||
completion: "0.000015",
|
||||
},
|
||||
}
|
||||
|
||||
const resultWithTools = parseOpenRouterModel({
|
||||
id: "test/tools-model",
|
||||
model: mockModel,
|
||||
inputModality: ["text"],
|
||||
outputModality: ["text"],
|
||||
maxTokens: 8192,
|
||||
supportedParameters: ["tools", "max_tokens", "temperature"],
|
||||
})
|
||||
|
||||
expect(resultWithTools.supportsNativeTools).toBe(true)
|
||||
expect(resultWithTools.defaultToolProtocol).toBe("native")
|
||||
})
|
||||
|
||||
it("does not set defaultToolProtocol when model does not support native tools", () => {
|
||||
const mockModel = {
|
||||
name: "No Tools Model",
|
||||
description: "Model without native tool support",
|
||||
context_length: 128000,
|
||||
max_completion_tokens: 8192,
|
||||
pricing: {
|
||||
prompt: "0.000003",
|
||||
completion: "0.000015",
|
||||
},
|
||||
}
|
||||
|
||||
const resultWithoutTools = parseOpenRouterModel({
|
||||
id: "test/no-tools-model",
|
||||
model: mockModel,
|
||||
inputModality: ["text"],
|
||||
outputModality: ["text"],
|
||||
maxTokens: 8192,
|
||||
supportedParameters: ["max_tokens", "temperature"],
|
||||
})
|
||||
|
||||
expect(resultWithoutTools.supportsNativeTools).toBe(false)
|
||||
expect(resultWithoutTools.defaultToolProtocol).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ describe("getRooModels", () => {
|
|||
description: "Fast coding model",
|
||||
deprecated: false,
|
||||
isFree: false,
|
||||
defaultToolProtocol: "native", // Applied from MODEL_DEFAULTS
|
||||
defaultToolProtocol: "native",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
|
@ -127,6 +127,9 @@ describe("getRooModels", () => {
|
|||
description: "Model that requires reasoning",
|
||||
deprecated: false,
|
||||
isFree: false,
|
||||
defaultTemperature: undefined,
|
||||
defaultToolProtocol: "native",
|
||||
isStealthModel: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -174,6 +177,9 @@ describe("getRooModels", () => {
|
|||
description: "Normal model without reasoning",
|
||||
deprecated: false,
|
||||
isFree: false,
|
||||
defaultTemperature: undefined,
|
||||
defaultToolProtocol: "native",
|
||||
isStealthModel: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -578,21 +584,21 @@ describe("getRooModels", () => {
|
|||
expect(models["test/native-tools-model"].defaultToolProtocol).toBe("native")
|
||||
})
|
||||
|
||||
it("should imply supportsNativeTools when default-native-tools tag is present without tool-use tag", async () => {
|
||||
it("should set defaultToolProtocol to native for all models regardless of tags", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/implicit-native-tools",
|
||||
id: "test/model-without-tool-tags",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Implicit Native Tools Model",
|
||||
description: "Model with default-native-tools but no tool-use tag",
|
||||
name: "Model Without Tool Tags",
|
||||
description: "Model without any tool-related tags",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
tags: ["default-native-tools"], // Only default-native-tools, no tool-use
|
||||
tags: [], // No tool-related tags
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
|
|
@ -608,21 +614,22 @@ describe("getRooModels", () => {
|
|||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/implicit-native-tools"].supportsNativeTools).toBe(true)
|
||||
expect(models["test/implicit-native-tools"].defaultToolProtocol).toBe("native")
|
||||
// All Roo provider models now default to native tool protocol
|
||||
expect(models["test/model-without-tool-tags"].supportsNativeTools).toBe(false)
|
||||
expect(models["test/model-without-tool-tags"].defaultToolProtocol).toBe("native")
|
||||
})
|
||||
|
||||
it("should not set defaultToolProtocol when default-native-tools tag is not present", async () => {
|
||||
it("should set supportsNativeTools from tool-use tag and always set defaultToolProtocol to native", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/non-native-model",
|
||||
id: "test/tool-use-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Non-Native Tools Model",
|
||||
description: "Model without native tool calling default",
|
||||
name: "Tool Use Model",
|
||||
description: "Model with tool-use tag",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
|
|
@ -642,8 +649,9 @@ describe("getRooModels", () => {
|
|||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/non-native-model"].supportsNativeTools).toBe(true)
|
||||
expect(models["test/non-native-model"].defaultToolProtocol).toBeUndefined()
|
||||
// tool-use tag sets supportsNativeTools, and all models get defaultToolProtocol: native
|
||||
expect(models["test/tool-use-model"].supportsNativeTools).toBe(true)
|
||||
expect(models["test/tool-use-model"].defaultToolProtocol).toBe("native")
|
||||
})
|
||||
|
||||
it("should detect stealth mode from tags", async () => {
|
||||
|
|
@ -711,4 +719,86 @@ describe("getRooModels", () => {
|
|||
|
||||
expect(models["test/non-stealth-model"].isStealthModel).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should apply API-provided settings to model info", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/model-with-settings",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Model with Settings",
|
||||
description: "Model with API-provided settings",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
tags: ["tool-use"],
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
settings: {
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
reasoningEffort: "high",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/model-with-settings"].includedTools).toEqual(["apply_patch"])
|
||||
expect(models["test/model-with-settings"].excludedTools).toEqual(["apply_diff", "write_to_file"])
|
||||
expect(models["test/model-with-settings"].reasoningEffort).toBe("high")
|
||||
})
|
||||
|
||||
it("should handle arbitrary settings properties dynamically", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/dynamic-settings-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Dynamic Settings Model",
|
||||
description: "Model with arbitrary settings",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
tags: [],
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
settings: {
|
||||
customProperty: "custom-value",
|
||||
anotherSetting: 42,
|
||||
nestedConfig: { key: "value" },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
const model = models["test/dynamic-settings-model"] as any
|
||||
|
||||
// Arbitrary settings should be passed through
|
||||
expect(model.customProperty).toBe("custom-value")
|
||||
expect(model.anotherSetting).toBe(42)
|
||||
expect(model.nestedConfig).toEqual({ key: "value" })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import axios from "axios"
|
||||
import { z } from "zod"
|
||||
|
||||
import { type ModelInfo, TOOL_PROTOCOL, chutesModels } from "@roo-code/types"
|
||||
import { type ModelInfo, chutesModels } from "@roo-code/types"
|
||||
|
||||
import { DEFAULT_HEADERS } from "../constants"
|
||||
|
||||
// Chutes models endpoint follows OpenAI /models shape with additional fields
|
||||
// Chutes models endpoint follows OpenAI /models shape with additional fields.
|
||||
const ChutesModelSchema = z.object({
|
||||
id: z.string(),
|
||||
object: z.literal("model").optional(),
|
||||
owned_by: z.string().optional(),
|
||||
created: z.number().optional(),
|
||||
context_length: z.number(),
|
||||
context_length: z.number().optional(),
|
||||
max_model_len: z.number(),
|
||||
input_modalities: z.array(z.string()),
|
||||
input_modalities: z.array(z.string()).optional(),
|
||||
supported_features: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
|
|
@ -21,42 +21,49 @@ const ChutesModelsResponseSchema = z.object({ data: z.array(ChutesModelSchema) }
|
|||
|
||||
export async function getChutesModels(apiKey?: string): Promise<Record<string, ModelInfo>> {
|
||||
const headers: Record<string, string> = { ...DEFAULT_HEADERS }
|
||||
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`
|
||||
|
||||
if (apiKey) {
|
||||
headers["Authorization"] = `Bearer ${apiKey}`
|
||||
}
|
||||
|
||||
const url = "https://llm.chutes.ai/v1/models"
|
||||
|
||||
// Start with hardcoded models as the base
|
||||
// Start with hardcoded models as the base.
|
||||
const models: Record<string, ModelInfo> = { ...chutesModels }
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, { headers })
|
||||
const parsed = ChutesModelsResponseSchema.safeParse(response.data)
|
||||
const data = parsed.success ? parsed.data.data : response.data?.data || []
|
||||
|
||||
for (const m of data as Array<z.infer<typeof ChutesModelSchema>>) {
|
||||
// Extract from API response (all fields are required)
|
||||
const contextWindow = m.context_length
|
||||
const maxTokens = m.max_model_len
|
||||
const supportsImages = m.input_modalities.includes("image")
|
||||
const supportsNativeTools = m.supported_features?.includes("tools") ?? false
|
||||
if (parsed.success) {
|
||||
for (const m of parsed.data.data) {
|
||||
const contextWindow = m.context_length
|
||||
|
||||
const info: ModelInfo = {
|
||||
maxTokens,
|
||||
contextWindow,
|
||||
supportsImages,
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: `Chutes AI model: ${m.id}`,
|
||||
if (!contextWindow) {
|
||||
console.error(`Context length is required for Chutes model: ${m.id}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const info: ModelInfo = {
|
||||
maxTokens: m.max_model_len,
|
||||
contextWindow,
|
||||
supportsImages: (m.input_modalities || []).includes("image"),
|
||||
supportsPromptCache: false,
|
||||
supportsNativeTools: (m.supported_features || []).includes("tools"),
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: `Chutes AI model: ${m.id}`,
|
||||
}
|
||||
|
||||
// Union: dynamic models override hardcoded ones if they have the same ID.
|
||||
models[m.id] = info
|
||||
}
|
||||
|
||||
// Union: dynamic models override hardcoded ones if they have the same ID
|
||||
models[m.id] = info
|
||||
} else {
|
||||
console.error(`Error parsing Chutes models: ${JSON.stringify(parsed.error.format(), null, 2)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching Chutes models: ${error instanceof Error ? error.message : String(error)}`)
|
||||
// On error, still return hardcoded models
|
||||
// On error, still return hardcoded models.
|
||||
}
|
||||
|
||||
return models
|
||||
|
|
|
|||
|
|
@ -207,6 +207,8 @@ export const parseOpenRouterModel = ({
|
|||
|
||||
const supportsPromptCache = typeof cacheReadsPrice !== "undefined" // some models support caching but don't charge a cacheWritesPrice, e.g. GPT-5
|
||||
|
||||
const supportsNativeTools = supportedParameters ? supportedParameters.includes("tools") : undefined
|
||||
|
||||
const modelInfo: ModelInfo = {
|
||||
maxTokens: maxTokens || Math.ceil(model.context_length * 0.2),
|
||||
contextWindow: model.context_length,
|
||||
|
|
@ -218,8 +220,10 @@ export const parseOpenRouterModel = ({
|
|||
cacheReadsPrice,
|
||||
description: model.description,
|
||||
supportsReasoningEffort: supportedParameters ? supportedParameters.includes("reasoning") : undefined,
|
||||
supportsNativeTools: supportedParameters ? supportedParameters.includes("tools") : undefined,
|
||||
supportsNativeTools,
|
||||
supportedParameters: supportedParameters ? supportedParameters.filter(isModelParameter) : undefined,
|
||||
// Default to native tool protocol when native tools are supported
|
||||
defaultToolProtocol: supportsNativeTools ? ("native" as const) : undefined,
|
||||
}
|
||||
|
||||
if (OPEN_ROUTER_REASONING_BUDGET_MODELS.has(id)) {
|
||||
|
|
|
|||
|
|
@ -5,23 +5,6 @@ import { parseApiPrice } from "../../../shared/cost"
|
|||
|
||||
import { DEFAULT_HEADERS } from "../constants"
|
||||
|
||||
// Model-specific defaults that should be applied even when models come from API cache
|
||||
// These override API-provided values for specific models
|
||||
// Exported so RooHandler.getModel() can also apply these for fallback cases
|
||||
export const MODEL_DEFAULTS: Record<string, Partial<ModelInfo>> = {
|
||||
"minimax/minimax-m2:free": {
|
||||
defaultToolProtocol: "native",
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"anthropic/claude-haiku-4.5": {
|
||||
defaultToolProtocol: "native",
|
||||
},
|
||||
"xai/grok-code-fast-1": {
|
||||
defaultToolProtocol: "native",
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches available models from the Roo Code Cloud provider
|
||||
*
|
||||
|
|
@ -109,13 +92,8 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
|
|||
// Determine if the model requires reasoning effort based on tags
|
||||
const requiredReasoningEffort = tags.includes("reasoning-required")
|
||||
|
||||
// Determine if native tool calling should be the default protocol for this model
|
||||
const hasDefaultNativeTools = tags.includes("default-native-tools")
|
||||
const defaultToolProtocol = hasDefaultNativeTools ? ("native" as const) : undefined
|
||||
|
||||
// Determine if the model supports native tool calling based on tags
|
||||
// default-native-tools implies tool-use support
|
||||
const supportsNativeTools = tags.includes("tool-use") || hasDefaultNativeTools
|
||||
const supportsNativeTools = tags.includes("tool-use")
|
||||
|
||||
// Determine if the model should hide vendor/company identity (stealth mode)
|
||||
const isStealthModel = tags.includes("stealth")
|
||||
|
|
@ -143,13 +121,16 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
|
|||
deprecated: model.deprecated || false,
|
||||
isFree: tags.includes("free"),
|
||||
defaultTemperature: model.default_temperature,
|
||||
defaultToolProtocol,
|
||||
defaultToolProtocol: "native" as const,
|
||||
isStealthModel: isStealthModel || undefined,
|
||||
}
|
||||
|
||||
// Apply model-specific defaults (e.g., defaultToolProtocol)
|
||||
const modelDefaults = MODEL_DEFAULTS[modelId]
|
||||
models[modelId] = modelDefaults ? { ...baseModelInfo, ...modelDefaults } : baseModelInfo
|
||||
// Apply API-provided settings on top of base model info
|
||||
// Settings allow the proxy to dynamically configure model-specific options
|
||||
// like includedTools, excludedTools, reasoningEffort, etc.
|
||||
const apiSettings = model.settings as Partial<ModelInfo> | undefined
|
||||
|
||||
models[modelId] = apiSettings ? { ...baseModelInfo, ...apiSettings } : baseModelInfo
|
||||
}
|
||||
|
||||
return models
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
type GenerateContentConfig,
|
||||
type GroundingMetadata,
|
||||
FunctionCallingConfigMode,
|
||||
Content,
|
||||
} from "@google/genai"
|
||||
import type { JWTInput } from "google-auth-library"
|
||||
|
||||
|
|
@ -15,7 +14,7 @@ import { type ModelInfo, type GeminiModelId, geminiDefaultModelId, geminiModels
|
|||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { safeJsonParse } from "../../shared/safeJsonParse"
|
||||
|
||||
import { convertAnthropicContentToGemini, convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { t } from "i18next"
|
||||
import type { ApiStream, GroundingSource } from "../transform/stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
|
|
@ -431,30 +430,6 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
}
|
||||
}
|
||||
|
||||
override async countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number> {
|
||||
try {
|
||||
const { id: model } = this.getModel()
|
||||
|
||||
const countTokensRequest = {
|
||||
model,
|
||||
// Token counting does not need encrypted continuation; always drop thoughtSignature.
|
||||
contents: convertAnthropicContentToGemini(content, { includeThoughtSignatures: false }),
|
||||
}
|
||||
|
||||
const response = await this.client.models.countTokens(countTokensRequest)
|
||||
|
||||
if (response.totalTokens === undefined) {
|
||||
console.warn("Gemini token counting returned undefined, using fallback")
|
||||
return super.countTokens(content)
|
||||
}
|
||||
|
||||
return response.totalTokens
|
||||
} catch (error) {
|
||||
console.warn("Gemini token counting failed, using fallback", error)
|
||||
return super.countTokens(content)
|
||||
}
|
||||
}
|
||||
|
||||
public getThoughtSignature(): string | undefined {
|
||||
return this.lastThoughtSignature
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,27 +303,4 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand
|
|||
const content = message.content.find(({ type }) => type === "text")
|
||||
return content?.type === "text" ? content.text : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts tokens for the given content using Anthropic's token counting
|
||||
* Falls back to base provider's tiktoken estimation if counting fails
|
||||
*/
|
||||
override async countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number> {
|
||||
try {
|
||||
const { id: model } = this.getModel()
|
||||
|
||||
const response = await this.client.messages.countTokens({
|
||||
model,
|
||||
messages: [{ role: "user", content: content }],
|
||||
})
|
||||
|
||||
return response.input_tokens
|
||||
} catch (error) {
|
||||
// Log error but fallback to tiktoken estimation
|
||||
console.warn("MiniMax token counting failed, using fallback", error)
|
||||
|
||||
// Use the base provider's implementation as fallback
|
||||
return super.countTokens(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
DEEP_SEEK_DEFAULT_TEMPERATURE,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
|
||||
|
||||
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
|
||||
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
|
|
@ -341,6 +343,15 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
}
|
||||
}
|
||||
|
||||
// Process finish_reason to emit tool_call_end events
|
||||
// This ensures tool calls are finalized even if the stream doesn't properly close
|
||||
if (finishReason) {
|
||||
const endEvents = NativeToolCallParser.processFinishReason(finishReason)
|
||||
for (const event of endEvents) {
|
||||
yield event
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
lastUsage = chunk.usage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
|||
import OpenAI from "openai"
|
||||
|
||||
import { rooDefaultModelId, getApiProtocol, type ImageGenerationApiMethod } from "@roo-code/types"
|
||||
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
||||
import { Package } from "../../shared/package"
|
||||
|
|
@ -15,7 +16,6 @@ import { getRooReasoning } from "../transform/reasoning"
|
|||
import type { ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
import { getModels, getModelsFromCache } from "../providers/fetchers/modelCache"
|
||||
import { MODEL_DEFAULTS } from "../providers/fetchers/roo"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import { generateImageWithProvider, generateImageWithImagesApi, ImageGenerationResult } from "./utils/image-generation"
|
||||
import { t } from "../../i18n"
|
||||
|
|
@ -158,6 +158,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
const finishReason = chunk.choices[0]?.finish_reason
|
||||
|
||||
if (delta) {
|
||||
// Handle reasoning_details array format (used by Gemini 3, Claude, OpenAI o-series, etc.)
|
||||
|
|
@ -259,6 +260,13 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
}
|
||||
}
|
||||
|
||||
if (finishReason) {
|
||||
const endEvents = NativeToolCallParser.processFinishReason(finishReason)
|
||||
for (const event of endEvents) {
|
||||
yield event
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
lastUsage = chunk.usage as RooUsage
|
||||
}
|
||||
|
|
@ -297,13 +305,15 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Log streaming errors with context
|
||||
console.error("[RooHandler] Error during message streaming:", {
|
||||
const errorContext = {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
modelId: this.options.apiModelId,
|
||||
hasTaskId: Boolean(metadata?.taskId),
|
||||
})
|
||||
}
|
||||
|
||||
console.error(`[RooHandler] Error during message streaming: ${JSON.stringify(errorContext)}`)
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
@ -335,17 +345,12 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
override getModel() {
|
||||
const modelId = this.options.apiModelId || rooDefaultModelId
|
||||
|
||||
// Get models from shared cache
|
||||
// Get models from shared cache (settings are already applied by the fetcher)
|
||||
const models = getModelsFromCache("roo") || {}
|
||||
const modelInfo = models[modelId]
|
||||
|
||||
// Get model-specific defaults if they exist
|
||||
const modelDefaults = MODEL_DEFAULTS[modelId]
|
||||
|
||||
if (modelInfo) {
|
||||
// Merge model-specific defaults with cached model info
|
||||
const mergedInfo = modelDefaults ? { ...modelInfo, ...modelDefaults } : modelInfo
|
||||
return { id: modelId, info: mergedInfo }
|
||||
return { id: modelId, info: modelInfo }
|
||||
}
|
||||
|
||||
// Return the requested model ID even if not found, with fallback info.
|
||||
|
|
|
|||
|
|
@ -58,20 +58,25 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
|
|||
supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml"
|
||||
|
||||
// Use the OpenAI-compatible API.
|
||||
const requestOptions = {
|
||||
model: modelId,
|
||||
max_tokens: modelInfo.maxTokens,
|
||||
temperature: this.options.modelTemperature ?? XAI_DEFAULT_TEMPERATURE,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
] as OpenAI.Chat.ChatCompletionMessageParam[],
|
||||
stream: true as const,
|
||||
stream_options: { include_usage: true },
|
||||
...(reasoning && reasoning),
|
||||
...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }),
|
||||
...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }),
|
||||
...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }),
|
||||
}
|
||||
|
||||
let stream
|
||||
try {
|
||||
stream = await this.client.chat.completions.create({
|
||||
model: modelId,
|
||||
max_tokens: modelInfo.maxTokens,
|
||||
temperature: this.options.modelTemperature ?? XAI_DEFAULT_TEMPERATURE,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
...(reasoning && reasoning),
|
||||
...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }),
|
||||
...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }),
|
||||
...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }),
|
||||
})
|
||||
stream = await this.client.chat.completions.create(requestOptions)
|
||||
} catch (error) {
|
||||
throw handleOpenAIError(error, this.providerName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,18 @@ export const getRooReasoning = ({
|
|||
settings,
|
||||
}: GetModelReasoningOptions): RooReasoningParams | undefined => {
|
||||
// Check if model supports reasoning effort
|
||||
if (!model.supportsReasoningEffort) return undefined
|
||||
if (!model.supportsReasoningEffort) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (model.requiredReasoningEffort) {
|
||||
// Honor the provided effort if it's valid, otherwise let the model choose.
|
||||
if (reasoningEffort && reasoningEffort !== "disable" && reasoningEffort !== "minimal") {
|
||||
return { enabled: true, effort: reasoningEffort }
|
||||
} else {
|
||||
return { enabled: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit off switch from settings: always send disabled for back-compat and to
|
||||
// prevent automatic reasoning when the toggle is turned off.
|
||||
|
|
|
|||
|
|
@ -499,6 +499,20 @@ export class NativeToolCallParser {
|
|||
}
|
||||
break
|
||||
|
||||
case "search_replace":
|
||||
if (
|
||||
partialArgs.file_path !== undefined ||
|
||||
partialArgs.old_string !== undefined ||
|
||||
partialArgs.new_string !== undefined
|
||||
) {
|
||||
nativeArgs = {
|
||||
file_path: partialArgs.file_path,
|
||||
old_string: partialArgs.old_string,
|
||||
new_string: partialArgs.new_string,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
// Add other tools as needed
|
||||
default:
|
||||
break
|
||||
|
|
@ -742,6 +756,20 @@ export class NativeToolCallParser {
|
|||
}
|
||||
break
|
||||
|
||||
case "search_replace":
|
||||
if (
|
||||
args.file_path !== undefined &&
|
||||
args.old_string !== undefined &&
|
||||
args.new_string !== undefined
|
||||
) {
|
||||
nativeArgs = {
|
||||
file_path: args.file_path,
|
||||
old_string: args.old_string,
|
||||
new_string: args.new_string,
|
||||
} as NativeArgsFor<TName>
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -756,8 +784,11 @@ export class NativeToolCallParser {
|
|||
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error(`Failed to parse tool call arguments:`, error)
|
||||
console.error(`Error details:`, error instanceof Error ? error.message : String(error))
|
||||
console.error(
|
||||
`Failed to parse tool call arguments: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
|
||||
console.error(`Tool call: ${JSON.stringify(toolCall, null, 2)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,264 @@
|
|||
// npx vitest src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest"
|
||||
import { presentAssistantMessage } from "../presentAssistantMessage"
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("../../task/Task")
|
||||
vi.mock("../../tools/validateToolUse", () => ({
|
||||
validateToolUse: vi.fn(),
|
||||
}))
|
||||
vi.mock("@roo-code/telemetry", () => ({
|
||||
TelemetryService: {
|
||||
instance: {
|
||||
captureToolUsage: vi.fn(),
|
||||
captureConsecutiveMistakeError: vi.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
describe("presentAssistantMessage - Unknown Tool Handling", () => {
|
||||
let mockTask: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock Task with minimal properties needed for testing
|
||||
mockTask = {
|
||||
taskId: "test-task-id",
|
||||
instanceId: "test-instance",
|
||||
abort: false,
|
||||
presentAssistantMessageLocked: false,
|
||||
presentAssistantMessageHasPendingUpdates: false,
|
||||
currentStreamingContentIndex: 0,
|
||||
assistantMessageContent: [],
|
||||
userMessageContent: [],
|
||||
didCompleteReadingStream: false,
|
||||
didRejectTool: false,
|
||||
didAlreadyUseTool: false,
|
||||
diffEnabled: false,
|
||||
consecutiveMistakeCount: 0,
|
||||
clineMessages: [],
|
||||
api: {
|
||||
getModel: () => ({ id: "test-model", info: {} }),
|
||||
},
|
||||
browserSession: {
|
||||
closeBrowser: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
recordToolUsage: vi.fn(),
|
||||
recordToolError: vi.fn(),
|
||||
toolRepetitionDetector: {
|
||||
check: vi.fn().mockReturnValue({ allowExecution: true }),
|
||||
},
|
||||
providerRef: {
|
||||
deref: () => ({
|
||||
getState: vi.fn().mockResolvedValue({
|
||||
mode: "code",
|
||||
customModes: [],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
say: vi.fn().mockResolvedValue(undefined),
|
||||
ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked" }),
|
||||
}
|
||||
})
|
||||
|
||||
it("should return error for unknown tool in native protocol", async () => {
|
||||
// Set up a tool_use block with an unknown tool name and an ID (native protocol)
|
||||
const toolCallId = "tool_call_unknown_123"
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: toolCallId, // ID indicates native protocol
|
||||
name: "nonexistent_tool",
|
||||
params: { some: "param" },
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
// Execute presentAssistantMessage
|
||||
await presentAssistantMessage(mockTask)
|
||||
|
||||
// Verify that a tool_result with error was pushed
|
||||
const toolResult = mockTask.userMessageContent.find(
|
||||
(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
|
||||
)
|
||||
|
||||
expect(toolResult).toBeDefined()
|
||||
expect(toolResult.tool_use_id).toBe(toolCallId)
|
||||
// The error is wrapped in JSON by formatResponse.toolError
|
||||
expect(toolResult.content).toContain("nonexistent_tool")
|
||||
expect(toolResult.content).toContain("does not exist")
|
||||
expect(toolResult.content).toContain("error")
|
||||
|
||||
// Verify consecutiveMistakeCount was incremented
|
||||
expect(mockTask.consecutiveMistakeCount).toBe(1)
|
||||
|
||||
// Verify recordToolError was called
|
||||
expect(mockTask.recordToolError).toHaveBeenCalledWith(
|
||||
"nonexistent_tool",
|
||||
expect.stringContaining("Unknown tool"),
|
||||
)
|
||||
|
||||
// Verify error message was shown to user (uses i18n key)
|
||||
expect(mockTask.say).toHaveBeenCalledWith("error", "unknownToolError")
|
||||
})
|
||||
|
||||
it("should return error for unknown tool in XML protocol", async () => {
|
||||
// Set up a tool_use block with an unknown tool name WITHOUT an ID (XML protocol)
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
// No ID = XML protocol
|
||||
name: "fake_tool_that_does_not_exist",
|
||||
params: { param1: "value1" },
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
// Execute presentAssistantMessage
|
||||
await presentAssistantMessage(mockTask)
|
||||
|
||||
// For XML protocol, error is pushed as text blocks
|
||||
const textBlocks = mockTask.userMessageContent.filter((item: any) => item.type === "text")
|
||||
|
||||
// There should be text blocks with error message
|
||||
expect(textBlocks.length).toBeGreaterThan(0)
|
||||
const hasErrorMessage = textBlocks.some(
|
||||
(block: any) =>
|
||||
block.text?.includes("fake_tool_that_does_not_exist") && block.text?.includes("does not exist"),
|
||||
)
|
||||
expect(hasErrorMessage).toBe(true)
|
||||
|
||||
// Verify consecutiveMistakeCount was incremented
|
||||
expect(mockTask.consecutiveMistakeCount).toBe(1)
|
||||
|
||||
// Verify recordToolError was called
|
||||
expect(mockTask.recordToolError).toHaveBeenCalled()
|
||||
|
||||
// Verify error message was shown to user (uses i18n key)
|
||||
expect(mockTask.say).toHaveBeenCalledWith("error", "unknownToolError")
|
||||
})
|
||||
|
||||
it("should handle unknown tool without freezing (native protocol)", async () => {
|
||||
// This test ensures the extension doesn't freeze when an unknown tool is called
|
||||
const toolCallId = "tool_call_freeze_test"
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: toolCallId, // Native protocol
|
||||
name: "this_tool_definitely_does_not_exist",
|
||||
params: {},
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
// The test will timeout if the extension freezes
|
||||
const timeoutPromise = new Promise<boolean>((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Test timed out - extension likely froze")), 5000)
|
||||
})
|
||||
|
||||
const resultPromise = presentAssistantMessage(mockTask).then(() => true)
|
||||
|
||||
// Race between the function completing and the timeout
|
||||
const completed = await Promise.race([resultPromise, timeoutPromise])
|
||||
expect(completed).toBe(true)
|
||||
|
||||
// Verify a tool_result was pushed (critical for API not to freeze)
|
||||
const toolResult = mockTask.userMessageContent.find(
|
||||
(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
|
||||
)
|
||||
expect(toolResult).toBeDefined()
|
||||
})
|
||||
|
||||
it("should increment consecutiveMistakeCount for unknown tools", async () => {
|
||||
// Test with multiple unknown tools to ensure mistake count increments
|
||||
const toolCallId = "tool_call_mistake_test"
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: toolCallId,
|
||||
name: "unknown_tool_1",
|
||||
params: {},
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
expect(mockTask.consecutiveMistakeCount).toBe(0)
|
||||
|
||||
await presentAssistantMessage(mockTask)
|
||||
|
||||
expect(mockTask.consecutiveMistakeCount).toBe(1)
|
||||
})
|
||||
|
||||
it("should set userMessageContentReady after handling unknown tool", async () => {
|
||||
const toolCallId = "tool_call_ready_test"
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: toolCallId,
|
||||
name: "unknown_tool",
|
||||
params: {},
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
mockTask.didCompleteReadingStream = true
|
||||
mockTask.userMessageContentReady = false
|
||||
|
||||
await presentAssistantMessage(mockTask)
|
||||
|
||||
// userMessageContentReady should be set after processing
|
||||
expect(mockTask.userMessageContentReady).toBe(true)
|
||||
})
|
||||
|
||||
it("should still work with didAlreadyUseTool flag for unknown tool", async () => {
|
||||
const toolCallId = "tool_call_already_used_test"
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: toolCallId,
|
||||
name: "unknown_tool",
|
||||
params: {},
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
mockTask.didAlreadyUseTool = true
|
||||
|
||||
await presentAssistantMessage(mockTask)
|
||||
|
||||
// When didAlreadyUseTool is true, should send error tool_result
|
||||
const toolResult = mockTask.userMessageContent.find(
|
||||
(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
|
||||
)
|
||||
|
||||
expect(toolResult).toBeDefined()
|
||||
expect(toolResult.is_error).toBe(true)
|
||||
expect(toolResult.content).toContain("was not executed because a tool has already been used")
|
||||
})
|
||||
|
||||
it("should still work with didRejectTool flag for unknown tool", async () => {
|
||||
const toolCallId = "tool_call_rejected_test"
|
||||
mockTask.assistantMessageContent = [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: toolCallId,
|
||||
name: "unknown_tool",
|
||||
params: {},
|
||||
partial: false,
|
||||
},
|
||||
]
|
||||
|
||||
mockTask.didRejectTool = true
|
||||
|
||||
await presentAssistantMessage(mockTask)
|
||||
|
||||
// When didRejectTool is true, should send error tool_result
|
||||
const toolResult = mockTask.userMessageContent.find(
|
||||
(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
|
||||
)
|
||||
|
||||
expect(toolResult).toBeDefined()
|
||||
expect(toolResult.is_error).toBe(true)
|
||||
expect(toolResult.content).toContain("due to user rejecting a previous tool")
|
||||
})
|
||||
})
|
||||
|
|
@ -8,6 +8,7 @@ import { TelemetryService } from "@roo-code/telemetry"
|
|||
import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
|
||||
import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../shared/tools"
|
||||
import { Package } from "../../shared/package"
|
||||
import { t } from "../../i18n"
|
||||
|
||||
import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
|
||||
import { listFilesTool } from "../tools/ListFilesTool"
|
||||
|
|
@ -17,6 +18,7 @@ import { shouldUseSingleFileRead, TOOL_PROTOCOL } from "@roo-code/types"
|
|||
import { writeToFileTool } from "../tools/WriteToFileTool"
|
||||
import { applyDiffTool } from "../tools/MultiApplyDiffTool"
|
||||
import { searchAndReplaceTool } from "../tools/SearchAndReplaceTool"
|
||||
import { searchReplaceTool } from "../tools/SearchReplaceTool"
|
||||
import { applyPatchTool } from "../tools/ApplyPatchTool"
|
||||
import { listCodeDefinitionNamesTool } from "../tools/ListCodeDefinitionNamesTool"
|
||||
import { searchFilesTool } from "../tools/SearchFilesTool"
|
||||
|
|
@ -333,7 +335,11 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
await cline.say("text", content, undefined, block.partial)
|
||||
break
|
||||
}
|
||||
case "tool_use":
|
||||
case "tool_use": {
|
||||
// Fetch state early so it's available for toolDescription and validation
|
||||
const state = await cline.providerRef.deref()?.getState()
|
||||
const { mode, customModes, experiments: stateExperiments, apiConfiguration } = state ?? {}
|
||||
|
||||
const toolDescription = (): string => {
|
||||
switch (block.name) {
|
||||
case "execute_command":
|
||||
|
|
@ -380,6 +386,8 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
}]`
|
||||
case "search_and_replace":
|
||||
return `[${block.name} for '${block.params.path}']`
|
||||
case "search_replace":
|
||||
return `[${block.name} for '${block.params.file_path}']`
|
||||
case "apply_patch":
|
||||
return `[${block.name}]`
|
||||
case "list_files":
|
||||
|
|
@ -675,30 +683,46 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
TelemetryService.instance.captureToolUsage(cline.taskId, block.name, toolProtocol)
|
||||
}
|
||||
|
||||
// Validate tool use before execution.
|
||||
const {
|
||||
mode,
|
||||
customModes,
|
||||
experiments: stateExperiments,
|
||||
apiConfiguration,
|
||||
} = (await cline.providerRef.deref()?.getState()) ?? {}
|
||||
const modelInfo = cline.api.getModel()
|
||||
const includedTools = modelInfo?.info?.includedTools
|
||||
// Validate tool use before execution - ONLY for complete (non-partial) blocks.
|
||||
// Validating partial blocks would cause validation errors to be thrown repeatedly
|
||||
// during streaming, pushing multiple tool_results for the same tool_use_id and
|
||||
// potentially causing the stream to appear frozen.
|
||||
if (!block.partial) {
|
||||
const modelInfo = cline.api.getModel()
|
||||
const includedTools = modelInfo?.info?.includedTools
|
||||
|
||||
try {
|
||||
validateToolUse(
|
||||
block.name as ToolName,
|
||||
mode ?? defaultModeSlug,
|
||||
customModes ?? [],
|
||||
{ apply_diff: cline.diffEnabled },
|
||||
block.params,
|
||||
stateExperiments,
|
||||
includedTools,
|
||||
)
|
||||
} catch (error) {
|
||||
cline.consecutiveMistakeCount++
|
||||
pushToolResult(formatResponse.toolError(error.message, toolProtocol))
|
||||
break
|
||||
try {
|
||||
validateToolUse(
|
||||
block.name as ToolName,
|
||||
mode ?? defaultModeSlug,
|
||||
customModes ?? [],
|
||||
{ apply_diff: cline.diffEnabled },
|
||||
block.params,
|
||||
stateExperiments,
|
||||
includedTools,
|
||||
)
|
||||
} catch (error) {
|
||||
cline.consecutiveMistakeCount++
|
||||
// For validation errors (unknown tool, tool not allowed for mode), we need to:
|
||||
// 1. Send a tool_result with the error (required for native protocol)
|
||||
// 2. NOT set didAlreadyUseTool = true (the tool was never executed, just failed validation)
|
||||
// This prevents the stream from being interrupted with "Response interrupted by tool use result"
|
||||
// which would cause the extension to appear to hang
|
||||
const errorContent = formatResponse.toolError(error.message, toolProtocol)
|
||||
if (toolProtocol === TOOL_PROTOCOL.NATIVE && toolCallId) {
|
||||
// For native protocol, push tool_result directly without setting didAlreadyUseTool
|
||||
cline.userMessageContent.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: toolCallId,
|
||||
content: typeof errorContent === "string" ? errorContent : "(validation error)",
|
||||
is_error: true,
|
||||
} as Anthropic.ToolResultBlockParam)
|
||||
} else {
|
||||
// For XML protocol, use the standard pushToolResult
|
||||
pushToolResult(errorContent)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check for identical consecutive tool calls.
|
||||
|
|
@ -814,6 +838,16 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
toolProtocol,
|
||||
})
|
||||
break
|
||||
case "search_replace":
|
||||
await checkpointSaveAndMark(cline)
|
||||
await searchReplaceTool.handle(cline, block as ToolUse<"search_replace">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
toolProtocol,
|
||||
})
|
||||
break
|
||||
case "apply_patch":
|
||||
await checkpointSaveAndMark(cline)
|
||||
await applyPatchTool.handle(cline, block as ToolUse<"apply_patch">, {
|
||||
|
|
@ -995,9 +1029,40 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
toolProtocol,
|
||||
})
|
||||
break
|
||||
default: {
|
||||
// Handle unknown/invalid tool names
|
||||
// This is critical for native protocol where every tool_use MUST have a tool_result
|
||||
// Note: This case should rarely be reached since validateToolUse now checks for unknown tools
|
||||
|
||||
// CRITICAL: Don't process partial blocks for unknown tools - just let them stream in.
|
||||
// If we try to show errors for partial blocks, we'd show the error on every streaming chunk,
|
||||
// creating a loop that appears to freeze the extension. Only handle complete blocks.
|
||||
if (block.partial) {
|
||||
break
|
||||
}
|
||||
|
||||
const errorMessage = `Unknown tool "${block.name}". This tool does not exist. Please use one of the available tools.`
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError(block.name as ToolName, errorMessage)
|
||||
await cline.say("error", t("tools:unknownToolError", { toolName: block.name }))
|
||||
// Push tool_result directly for native protocol WITHOUT setting didAlreadyUseTool
|
||||
// This prevents the stream from being interrupted with "Response interrupted by tool use result"
|
||||
if (toolProtocol === TOOL_PROTOCOL.NATIVE && toolCallId) {
|
||||
cline.userMessageContent.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: toolCallId,
|
||||
content: formatResponse.toolError(errorMessage, toolProtocol),
|
||||
is_error: true,
|
||||
} as Anthropic.ToolResultBlockParam)
|
||||
} else {
|
||||
pushToolResult(formatResponse.toolError(errorMessage, toolProtocol))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Seeing out of bounds is fine, it means that the next too call is being
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest"
|
|||
import { Task } from "../../task/Task"
|
||||
import { ClineProvider } from "../../webview/ClineProvider"
|
||||
import { checkpointSave, checkpointRestore, checkpointDiff, getCheckpointService } from "../index"
|
||||
import { MessageManager } from "../../message-manager"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
// Mock vscode
|
||||
|
|
@ -102,6 +103,7 @@ describe("Checkpoint functionality", () => {
|
|||
overwriteApiConversationHistory: vi.fn(),
|
||||
combineMessages: vi.fn().mockReturnValue([]),
|
||||
}
|
||||
mockTask.messageManager = new MessageManager(mockTask)
|
||||
|
||||
// Update the mock to return our mockCheckpointService
|
||||
const checkpointsModule = await import("../../../services/checkpoints")
|
||||
|
|
|
|||
|
|
@ -258,20 +258,20 @@ export async function checkpointRestore(
|
|||
await provider?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash })
|
||||
|
||||
if (mode === "restore") {
|
||||
await task.overwriteApiConversationHistory(task.apiConversationHistory.filter((m) => !m.ts || m.ts < ts))
|
||||
|
||||
// Calculate metrics from messages that will be deleted (must be done before rewind)
|
||||
const deletedMessages = task.clineMessages.slice(index + 1)
|
||||
|
||||
const { totalTokensIn, totalTokensOut, totalCacheWrites, totalCacheReads, totalCost } = getApiMetrics(
|
||||
task.combineMessages(deletedMessages),
|
||||
)
|
||||
|
||||
// For delete operations, exclude the checkpoint message itself
|
||||
// For edit operations, include the checkpoint message (to be edited)
|
||||
const endIndex = operation === "edit" ? index + 1 : index
|
||||
await task.overwriteClineMessages(task.clineMessages.slice(0, endIndex))
|
||||
// Use MessageManager to properly handle context-management events
|
||||
// This ensures orphaned Summary messages and truncation markers are cleaned up
|
||||
await task.messageManager.rewindToTimestamp(ts, {
|
||||
includeTargetMessage: operation === "edit",
|
||||
})
|
||||
|
||||
// TODO: Verify that this is working as expected.
|
||||
// Report the deleted API request metrics
|
||||
await task.say(
|
||||
"api_req_deleted",
|
||||
JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
providerSettingsSchema,
|
||||
globalSettingsSchema,
|
||||
isSecretStateKey,
|
||||
isProviderName,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
|
|
@ -88,9 +89,33 @@ export class ContextProxy {
|
|||
// Migration: Check for old nested image generation settings and migrate them
|
||||
await this.migrateImageGenerationSettings()
|
||||
|
||||
// Migration: Sanitize invalid/removed API providers
|
||||
await this.migrateInvalidApiProvider()
|
||||
|
||||
this._isInitialized = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates invalid/removed apiProvider values by clearing them from storage.
|
||||
* This handles cases where a user had a provider selected that was later removed
|
||||
* from the extension (e.g., "glama").
|
||||
*/
|
||||
private async migrateInvalidApiProvider() {
|
||||
try {
|
||||
const apiProvider = this.stateCache.apiProvider
|
||||
if (apiProvider !== undefined && !isProviderName(apiProvider)) {
|
||||
logger.info(`[ContextProxy] Found invalid provider "${apiProvider}" in storage - clearing it`)
|
||||
// Clear the invalid provider from both cache and storage
|
||||
this.stateCache.apiProvider = undefined
|
||||
await this.originalContext.globalState.update("apiProvider", undefined)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error during invalid API provider migration: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates old nested openRouterImageGenerationSettings to the new flattened structure
|
||||
*/
|
||||
|
|
@ -266,17 +291,40 @@ export class ContextProxy {
|
|||
public getProviderSettings(): ProviderSettings {
|
||||
const values = this.getValues()
|
||||
|
||||
// Sanitize invalid/removed apiProvider values before parsing
|
||||
// This handles cases where a user had a provider selected that was later removed
|
||||
// from the extension (e.g., "glama"). We sanitize here to avoid repeated
|
||||
// schema validation errors that can cause infinite loops in telemetry.
|
||||
const sanitizedValues = this.sanitizeProviderValues(values)
|
||||
|
||||
try {
|
||||
return providerSettingsSchema.parse(values)
|
||||
return providerSettingsSchema.parse(sanitizedValues)
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
TelemetryService.instance.captureSchemaValidationError({ schemaName: "ProviderSettings", error })
|
||||
}
|
||||
|
||||
return PROVIDER_SETTINGS_KEYS.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {} as ProviderSettings)
|
||||
return PROVIDER_SETTINGS_KEYS.reduce(
|
||||
(acc, key) => ({ ...acc, [key]: sanitizedValues[key] }),
|
||||
{} as ProviderSettings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes provider values by resetting invalid/removed apiProvider values.
|
||||
* This prevents schema validation errors for removed providers.
|
||||
*/
|
||||
private sanitizeProviderValues(values: RooCodeSettings): RooCodeSettings {
|
||||
if (values.apiProvider !== undefined && !isProviderName(values.apiProvider)) {
|
||||
logger.info(`[ContextProxy] Sanitizing invalid provider "${values.apiProvider}" - resetting to undefined`)
|
||||
// Return a new values object without the invalid apiProvider
|
||||
const { apiProvider, ...restValues } = values
|
||||
return restValues as RooCodeSettings
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
public async setProviderSettings(values: ProviderSettings) {
|
||||
// Explicitly clear out any old API configuration values before that
|
||||
// might not be present in the new configuration.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
getModelId,
|
||||
type ProviderName,
|
||||
isProviderName,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
|
|
@ -598,7 +599,10 @@ export class ProviderSettingsManager {
|
|||
|
||||
const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce(
|
||||
(acc, [key, apiConfig]) => {
|
||||
const result = providerSettingsWithIdSchema.safeParse(apiConfig)
|
||||
// First, sanitize invalid apiProvider values before parsing
|
||||
// This handles removed providers (like "glama") gracefully
|
||||
const sanitizedConfig = this.sanitizeProviderConfig(apiConfig)
|
||||
const result = providerSettingsWithIdSchema.safeParse(sanitizedConfig)
|
||||
return result.success ? { ...acc, [key]: result.data } : acc
|
||||
},
|
||||
{} as Record<string, ProviderSettingsWithId>,
|
||||
|
|
@ -622,6 +626,32 @@ export class ProviderSettingsManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a provider config by resetting invalid/removed apiProvider values.
|
||||
* This handles cases where a user had a provider selected that was later removed
|
||||
* from the extension (e.g., "glama").
|
||||
*/
|
||||
private sanitizeProviderConfig(apiConfig: unknown): unknown {
|
||||
if (typeof apiConfig !== "object" || apiConfig === null) {
|
||||
return apiConfig
|
||||
}
|
||||
|
||||
const config = apiConfig as Record<string, unknown>
|
||||
|
||||
// Check if apiProvider is set and if it's still valid
|
||||
if (config.apiProvider !== undefined && !isProviderName(config.apiProvider)) {
|
||||
console.log(
|
||||
`[ProviderSettingsManager] Sanitizing invalid provider "${config.apiProvider}" - resetting to undefined`,
|
||||
)
|
||||
// Return a new config object without the invalid apiProvider
|
||||
// This effectively resets the profile so the user can select a valid provider
|
||||
const { apiProvider, ...restConfig } = config
|
||||
return restConfig
|
||||
}
|
||||
|
||||
return apiConfig
|
||||
}
|
||||
|
||||
private async store(providerProfiles: ProviderProfiles) {
|
||||
try {
|
||||
await this.context.secrets.store(this.secretsKey, JSON.stringify(providerProfiles, null, 2))
|
||||
|
|
|
|||
|
|
@ -428,4 +428,79 @@ describe("ContextProxy", () => {
|
|||
expect(initializeSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("invalid apiProvider migration", () => {
|
||||
it("should clear invalid apiProvider from storage during initialization", async () => {
|
||||
// Reset and create a new proxy with invalid provider in state
|
||||
vi.clearAllMocks()
|
||||
mockGlobalState.get.mockImplementation((key: string) => {
|
||||
if (key === "apiProvider") {
|
||||
return "invalid-removed-provider" // Invalid/removed provider
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
const proxyWithInvalidProvider = new ContextProxy(mockContext)
|
||||
await proxyWithInvalidProvider.initialize()
|
||||
|
||||
// Should have cleared the invalid apiProvider
|
||||
expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", undefined)
|
||||
})
|
||||
|
||||
it("should not modify valid apiProvider during initialization", async () => {
|
||||
// Reset and create a new proxy with valid provider in state
|
||||
vi.clearAllMocks()
|
||||
mockGlobalState.get.mockImplementation((key: string) => {
|
||||
if (key === "apiProvider") {
|
||||
return "anthropic" // Valid provider
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
const proxyWithValidProvider = new ContextProxy(mockContext)
|
||||
await proxyWithValidProvider.initialize()
|
||||
|
||||
// Should NOT have called update for apiProvider (it's valid)
|
||||
const updateCalls = mockGlobalState.update.mock.calls
|
||||
const apiProviderUpdateCalls = updateCalls.filter((call: any[]) => call[0] === "apiProvider")
|
||||
expect(apiProviderUpdateCalls.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getProviderSettings", () => {
|
||||
it("should sanitize invalid apiProvider before parsing", async () => {
|
||||
// Set an invalid provider in state
|
||||
await proxy.updateGlobalState("apiProvider", "invalid-removed-provider" as any)
|
||||
await proxy.updateGlobalState("apiModelId", "some-model")
|
||||
|
||||
const settings = proxy.getProviderSettings()
|
||||
|
||||
// The invalid apiProvider should be sanitized (removed)
|
||||
expect(settings.apiProvider).toBeUndefined()
|
||||
// Other settings should still be present
|
||||
expect(settings.apiModelId).toBe("some-model")
|
||||
})
|
||||
|
||||
it("should pass through valid apiProvider", async () => {
|
||||
// Set a valid provider in state
|
||||
await proxy.updateGlobalState("apiProvider", "anthropic")
|
||||
await proxy.updateGlobalState("apiModelId", "claude-3-opus-20240229")
|
||||
|
||||
const settings = proxy.getProviderSettings()
|
||||
|
||||
// Valid provider should be returned
|
||||
expect(settings.apiProvider).toBe("anthropic")
|
||||
expect(settings.apiModelId).toBe("claude-3-opus-20240229")
|
||||
})
|
||||
|
||||
it("should handle undefined apiProvider gracefully", async () => {
|
||||
// Ensure no provider is set
|
||||
await proxy.updateGlobalState("apiProvider", undefined)
|
||||
|
||||
const settings = proxy.getProviderSettings()
|
||||
|
||||
// Should not throw and should return undefined
|
||||
expect(settings.apiProvider).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -705,7 +705,55 @@ describe("ProviderSettingsManager", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should remove invalid profiles during load", async () => {
|
||||
it("should sanitize invalid/removed providers by resetting apiProvider to undefined", async () => {
|
||||
// This tests the fix for the infinite loop issue when a provider is removed
|
||||
const configWithRemovedProvider = {
|
||||
currentApiConfigName: "valid",
|
||||
apiConfigs: {
|
||||
valid: {
|
||||
apiProvider: "anthropic",
|
||||
apiKey: "valid-key",
|
||||
apiModelId: "claude-3-opus-20240229",
|
||||
id: "valid-id",
|
||||
},
|
||||
removedProvider: {
|
||||
// Provider that was removed from the extension (e.g., "invalid-removed-provider")
|
||||
id: "removed-id",
|
||||
apiProvider: "invalid-removed-provider",
|
||||
apiKey: "some-key",
|
||||
apiModelId: "some-model",
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
rateLimitSecondsMigrated: true,
|
||||
diffSettingsMigrated: true,
|
||||
openAiHeadersMigrated: true,
|
||||
consecutiveMistakeLimitMigrated: true,
|
||||
todoListEnabledMigrated: true,
|
||||
},
|
||||
}
|
||||
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify(configWithRemovedProvider))
|
||||
|
||||
await providerSettingsManager.initialize()
|
||||
|
||||
const storeCalls = mockSecrets.store.mock.calls
|
||||
expect(storeCalls.length).toBeGreaterThan(0)
|
||||
const finalStoredConfigJson = storeCalls[storeCalls.length - 1][1]
|
||||
|
||||
const storedConfig = JSON.parse(finalStoredConfigJson)
|
||||
// The valid provider should be untouched
|
||||
expect(storedConfig.apiConfigs.valid).toBeDefined()
|
||||
expect(storedConfig.apiConfigs.valid.apiProvider).toBe("anthropic")
|
||||
|
||||
// The config with the removed provider should have its apiProvider reset to undefined
|
||||
// but still be present (not filtered out entirely)
|
||||
expect(storedConfig.apiConfigs.removedProvider).toBeDefined()
|
||||
expect(storedConfig.apiConfigs.removedProvider.apiProvider).toBeUndefined()
|
||||
expect(storedConfig.apiConfigs.removedProvider.id).toBe("removed-id")
|
||||
})
|
||||
|
||||
it("should sanitize invalid providers and remove non-object profiles during load", async () => {
|
||||
const invalidConfig = {
|
||||
currentApiConfigName: "valid",
|
||||
apiConfigs: {
|
||||
|
|
@ -715,12 +763,12 @@ describe("ProviderSettingsManager", () => {
|
|||
apiModelId: "claude-3-opus-20240229",
|
||||
rateLimitSeconds: 0,
|
||||
},
|
||||
invalid: {
|
||||
// Invalid API provider.
|
||||
invalidProvider: {
|
||||
// Invalid API provider - should be sanitized (kept but apiProvider reset to undefined)
|
||||
id: "x.ai",
|
||||
apiProvider: "x.ai",
|
||||
},
|
||||
// Incorrect type.
|
||||
// Incorrect type - should be completely removed
|
||||
anotherInvalid: "not an object",
|
||||
},
|
||||
migrations: {
|
||||
|
|
@ -737,10 +785,19 @@ describe("ProviderSettingsManager", () => {
|
|||
const finalStoredConfigJson = storeCalls[storeCalls.length - 1][1]
|
||||
|
||||
const storedConfig = JSON.parse(finalStoredConfigJson)
|
||||
// Valid config should be untouched
|
||||
expect(storedConfig.apiConfigs.valid).toBeDefined()
|
||||
expect(storedConfig.apiConfigs.invalid).toBeUndefined()
|
||||
expect(storedConfig.apiConfigs.valid.apiProvider).toBe("anthropic")
|
||||
|
||||
// Invalid provider config should be sanitized - kept but apiProvider reset to undefined
|
||||
expect(storedConfig.apiConfigs.invalidProvider).toBeDefined()
|
||||
expect(storedConfig.apiConfigs.invalidProvider.apiProvider).toBeUndefined()
|
||||
expect(storedConfig.apiConfigs.invalidProvider.id).toBe("x.ai")
|
||||
|
||||
// Non-object config should be completely removed
|
||||
expect(storedConfig.apiConfigs.anotherInvalid).toBeUndefined()
|
||||
expect(Object.keys(storedConfig.apiConfigs)).toEqual(["valid"])
|
||||
|
||||
expect(Object.keys(storedConfig.apiConfigs)).toEqual(["valid", "invalidProvider"])
|
||||
expect(storedConfig.currentApiConfigName).toBe("valid")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ import { BaseProvider } from "../../../api/providers/base-provider"
|
|||
import { ApiMessage } from "../../task-persistence/apiMessages"
|
||||
import * as condenseModule from "../../condense"
|
||||
|
||||
import { TOKEN_BUFFER_PERCENTAGE, estimateTokenCount, truncateConversation, manageContext } from "../index"
|
||||
import {
|
||||
TOKEN_BUFFER_PERCENTAGE,
|
||||
estimateTokenCount,
|
||||
truncateConversation,
|
||||
manageContext,
|
||||
willManageContext,
|
||||
} from "../index"
|
||||
|
||||
// Create a mock ApiHandler for testing
|
||||
class MockApiHandler extends BaseProvider {
|
||||
|
|
@ -65,14 +71,12 @@ describe("Context Management", () => {
|
|||
|
||||
// With 2 messages after the first, 0.5 fraction means remove 1 message
|
||||
// But 1 is odd, so it rounds down to 0 (to make it even)
|
||||
// Result should have messages + truncation marker
|
||||
expect(result.messages.length).toBe(4) // First message + truncation marker + 2 remaining messages
|
||||
// No truncation happens, so no marker is inserted
|
||||
expect(result.messages.length).toBe(3) // Original messages unchanged
|
||||
expect(result.messagesRemoved).toBe(0)
|
||||
expect(result.messages[0]).toEqual(messages[0])
|
||||
// messages[1] is the truncation marker
|
||||
expect(result.messages[1].isTruncationMarker).toBe(true)
|
||||
// Original messages[1] and messages[2] are at indices 2 and 3 now
|
||||
expect(result.messages[2].content).toEqual(messages[1].content)
|
||||
expect(result.messages[3].content).toEqual(messages[2].content)
|
||||
expect(result.messages[1]).toEqual(messages[1])
|
||||
expect(result.messages[2]).toEqual(messages[2])
|
||||
})
|
||||
|
||||
it("should remove the specified fraction of messages (rounded to even number)", () => {
|
||||
|
|
@ -92,11 +96,16 @@ describe("Context Management", () => {
|
|||
expect(result.messages.length).toBe(6) // 5 original + 1 marker
|
||||
expect(result.messagesRemoved).toBe(2)
|
||||
expect(result.messages[0]).toEqual(messages[0])
|
||||
expect(result.messages[1].isTruncationMarker).toBe(true)
|
||||
// Messages 2 and 3 (indices 1 and 2 from original) should be tagged
|
||||
|
||||
// Messages at indices 1 and 2 from original should be tagged
|
||||
expect(result.messages[1].truncationParent).toBe(result.truncationId)
|
||||
expect(result.messages[2].truncationParent).toBe(result.truncationId)
|
||||
expect(result.messages[3].truncationParent).toBe(result.truncationId)
|
||||
// Messages 4 and 5 (indices 3 and 4 from original) should NOT be tagged
|
||||
|
||||
// Marker should be at index 3 (at the boundary, after truncated messages)
|
||||
expect(result.messages[3].isTruncationMarker).toBe(true)
|
||||
expect(result.messages[3].role).toBe("user")
|
||||
|
||||
// Messages at indices 3 and 4 from original should NOT be tagged (now at indices 4 and 5)
|
||||
expect(result.messages[4].truncationParent).toBeUndefined()
|
||||
expect(result.messages[5].truncationParent).toBeUndefined()
|
||||
})
|
||||
|
|
@ -117,9 +126,8 @@ describe("Context Management", () => {
|
|||
const result = truncateConversation(messages, 0.3, taskId)
|
||||
|
||||
expect(result.messagesRemoved).toBe(0) // No messages removed
|
||||
// Should still have truncation marker inserted
|
||||
expect(result.messages.length).toBe(8) // 7 original + 1 marker
|
||||
expect(result.messages[1].isTruncationMarker).toBe(true)
|
||||
// When nothing is truncated, no marker is inserted
|
||||
expect(result.messages.length).toBe(7) // Original messages unchanged
|
||||
})
|
||||
|
||||
it("should handle edge case with fracToRemove = 0", () => {
|
||||
|
|
@ -132,9 +140,8 @@ describe("Context Management", () => {
|
|||
const result = truncateConversation(messages, 0, taskId)
|
||||
|
||||
expect(result.messagesRemoved).toBe(0)
|
||||
// Should have original messages + truncation marker
|
||||
expect(result.messages.length).toBe(4)
|
||||
expect(result.messages[1].isTruncationMarker).toBe(true)
|
||||
// When nothing is truncated, no marker is inserted
|
||||
expect(result.messages.length).toBe(3) // Original messages unchanged
|
||||
})
|
||||
|
||||
it("should handle edge case with fracToRemove = 1", () => {
|
||||
|
|
@ -153,11 +160,16 @@ describe("Context Management", () => {
|
|||
// Should have all original messages + truncation marker
|
||||
expect(result.messages.length).toBe(5) // 4 original + 1 marker
|
||||
expect(result.messages[0]).toEqual(messages[0])
|
||||
expect(result.messages[1].isTruncationMarker).toBe(true)
|
||||
// Messages at indices 2 and 3 should be tagged (original indices 1 and 2)
|
||||
|
||||
// Messages at indices 1 and 2 should be tagged
|
||||
expect(result.messages[1].truncationParent).toBe(result.truncationId)
|
||||
expect(result.messages[2].truncationParent).toBe(result.truncationId)
|
||||
expect(result.messages[3].truncationParent).toBe(result.truncationId)
|
||||
// Last message should NOT be tagged
|
||||
|
||||
// Marker should be at index 3 (at the boundary)
|
||||
expect(result.messages[3].isTruncationMarker).toBe(true)
|
||||
expect(result.messages[3].role).toBe("user")
|
||||
|
||||
// Last message should NOT be tagged (now at index 4)
|
||||
expect(result.messages[4].truncationParent).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
@ -1274,4 +1286,125 @@ describe("Context Management", () => {
|
|||
expect(result2.truncationId).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Tests for the willManageContext helper function
|
||||
*/
|
||||
describe("willManageContext", () => {
|
||||
it("should return true when context percent exceeds threshold", () => {
|
||||
const result = willManageContext({
|
||||
totalTokens: 60000,
|
||||
contextWindow: 100000, // 60% of context window
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: true,
|
||||
autoCondenseContextPercent: 50, // 50% threshold
|
||||
profileThresholds: {},
|
||||
currentProfileId: "default",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when context percent is below threshold", () => {
|
||||
const result = willManageContext({
|
||||
totalTokens: 40000,
|
||||
contextWindow: 100000, // 40% of context window
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: true,
|
||||
autoCondenseContextPercent: 50, // 50% threshold
|
||||
profileThresholds: {},
|
||||
currentProfileId: "default",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when tokens exceed allowedTokens even if autoCondenseContext is false", () => {
|
||||
// allowedTokens = contextWindow * (1 - 0.1) - reservedTokens = 100000 * 0.9 - 30000 = 60000
|
||||
const result = willManageContext({
|
||||
totalTokens: 60001, // Exceeds allowedTokens
|
||||
contextWindow: 100000,
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: false, // Even with auto-condense disabled
|
||||
autoCondenseContextPercent: 50,
|
||||
profileThresholds: {},
|
||||
currentProfileId: "default",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when autoCondenseContext is false and tokens are below allowedTokens", () => {
|
||||
// allowedTokens = contextWindow * (1 - 0.1) - reservedTokens = 100000 * 0.9 - 30000 = 60000
|
||||
const result = willManageContext({
|
||||
totalTokens: 59999, // Below allowedTokens
|
||||
contextWindow: 100000,
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: false,
|
||||
autoCondenseContextPercent: 50, // This shouldn't matter since autoCondenseContext is false
|
||||
profileThresholds: {},
|
||||
currentProfileId: "default",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should use profile-specific threshold when available", () => {
|
||||
const result = willManageContext({
|
||||
totalTokens: 55000,
|
||||
contextWindow: 100000, // 55% of context window
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: true,
|
||||
autoCondenseContextPercent: 80, // Global threshold 80%
|
||||
profileThresholds: { "test-profile": 50 }, // Profile threshold 50%
|
||||
currentProfileId: "test-profile",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
// Should trigger because 55% > 50% (profile threshold)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("should fall back to global threshold when profile threshold is -1", () => {
|
||||
const result = willManageContext({
|
||||
totalTokens: 55000,
|
||||
contextWindow: 100000, // 55% of context window
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: true,
|
||||
autoCondenseContextPercent: 80, // Global threshold 80%
|
||||
profileThresholds: { "test-profile": -1 }, // Profile uses global
|
||||
currentProfileId: "test-profile",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
// Should NOT trigger because 55% < 80% (global threshold)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should include lastMessageTokens in the calculation", () => {
|
||||
// Without lastMessageTokens: 49000 tokens = 49%
|
||||
// With lastMessageTokens: 49000 + 2000 = 51000 tokens = 51%
|
||||
const resultWithoutLastMessage = willManageContext({
|
||||
totalTokens: 49000,
|
||||
contextWindow: 100000,
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: true,
|
||||
autoCondenseContextPercent: 50, // 50% threshold
|
||||
profileThresholds: {},
|
||||
currentProfileId: "default",
|
||||
lastMessageTokens: 0,
|
||||
})
|
||||
expect(resultWithoutLastMessage).toBe(false)
|
||||
|
||||
const resultWithLastMessage = willManageContext({
|
||||
totalTokens: 49000,
|
||||
contextWindow: 100000,
|
||||
maxTokens: 30000,
|
||||
autoCondenseContext: true,
|
||||
autoCondenseContextPercent: 50, // 50% threshold
|
||||
profileThresholds: {},
|
||||
currentProfileId: "default",
|
||||
lastMessageTokens: 2000, // Pushes total to 51%
|
||||
})
|
||||
expect(resultWithLastMessage).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -33,39 +33,41 @@ describe("Non-Destructive Sliding Window Truncation", () => {
|
|||
it("should tag messages with truncationParent instead of deleting", () => {
|
||||
const result = truncateConversation(messages, 0.5, "test-task-id")
|
||||
|
||||
// All messages should still be present
|
||||
// All messages should still be present plus the truncation marker
|
||||
expect(result.messages.length).toBe(messages.length + 1) // +1 for truncation marker
|
||||
|
||||
// Calculate expected messages to remove: floor((11-1) * 0.5) = 5, rounded to even = 4
|
||||
const expectedMessagesToRemove = 4
|
||||
|
||||
// Messages 1-4 should be tagged with truncationParent
|
||||
for (let i = 1; i <= expectedMessagesToRemove; i++) {
|
||||
// Account for truncation marker inserted at position 1
|
||||
const msgIndex = i < 1 ? i : i + 1
|
||||
expect(result.messages[msgIndex].truncationParent).toBeDefined()
|
||||
expect(result.messages[msgIndex].truncationParent).toBe(result.truncationId)
|
||||
// Find which messages have truncationParent set
|
||||
const taggedMessages = result.messages.filter((msg) => msg.truncationParent)
|
||||
expect(taggedMessages.length).toBe(expectedMessagesToRemove)
|
||||
|
||||
// All tagged messages should point to the truncationId
|
||||
for (const msg of taggedMessages) {
|
||||
expect(msg.truncationParent).toBe(result.truncationId)
|
||||
}
|
||||
|
||||
// First message should not be tagged
|
||||
expect(result.messages[0].truncationParent).toBeUndefined()
|
||||
|
||||
// Remaining messages should not be tagged
|
||||
for (let i = expectedMessagesToRemove + 2; i < result.messages.length; i++) {
|
||||
expect(result.messages[i].truncationParent).toBeUndefined()
|
||||
}
|
||||
// Marker should not have truncationParent
|
||||
const marker = result.messages.find((msg) => msg.isTruncationMarker)
|
||||
expect(marker?.truncationParent).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should insert truncation marker with truncationId", () => {
|
||||
const result = truncateConversation(messages, 0.5, "test-task-id")
|
||||
|
||||
// Truncation marker should be at index 1 (after first message)
|
||||
const marker = result.messages[1]
|
||||
expect(marker.isTruncationMarker).toBe(true)
|
||||
expect(marker.truncationId).toBeDefined()
|
||||
expect(marker.truncationId).toBe(result.truncationId)
|
||||
expect(marker.role).toBe("assistant")
|
||||
expect(marker.content).toContain("Sliding window truncation")
|
||||
// Truncation marker should be at the boundary (after truncated messages)
|
||||
// With 4 messages truncated (indices 1-4), marker should be at index 5
|
||||
const marker = result.messages.find((msg) => msg.isTruncationMarker)
|
||||
expect(marker).toBeDefined()
|
||||
expect(marker!.isTruncationMarker).toBe(true)
|
||||
expect(marker!.truncationId).toBeDefined()
|
||||
expect(marker!.truncationId).toBe(result.truncationId)
|
||||
expect(marker!.role).toBe("user")
|
||||
expect(marker!.content).toContain("Sliding window truncation")
|
||||
})
|
||||
|
||||
it("should return truncationId and messagesRemoved", () => {
|
||||
|
|
@ -367,10 +369,10 @@ describe("Non-Destructive Sliding Window Truncation", () => {
|
|||
// No messages should be tagged (messagesToRemove = 0)
|
||||
const taggedMessages = result.messages.filter((msg) => msg.truncationParent)
|
||||
expect(taggedMessages.length).toBe(0)
|
||||
expect(result.messagesRemoved).toBe(0)
|
||||
|
||||
// Should still have truncation marker
|
||||
const marker = result.messages.find((msg) => msg.isTruncationMarker)
|
||||
expect(marker).toBeDefined()
|
||||
// When nothing is truncated, no marker is inserted
|
||||
expect(result.messages).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should handle truncateConversation with very few messages", () => {
|
||||
|
|
@ -381,10 +383,43 @@ describe("Non-Destructive Sliding Window Truncation", () => {
|
|||
|
||||
const result = truncateConversation(fewMessages, 0.5, "test-task-id")
|
||||
|
||||
// Should not crash and should still create marker
|
||||
expect(result.messages.length).toBeGreaterThan(0)
|
||||
const marker = result.messages.find((msg) => msg.isTruncationMarker)
|
||||
expect(marker).toBeDefined()
|
||||
// With only 1 message after first, 0.5 fraction = 0.5, floored to 0, rounded to even = 0
|
||||
// So no messages should be removed and no marker inserted
|
||||
expect(result.messages.length).toBe(2)
|
||||
expect(result.messagesRemoved).toBe(0)
|
||||
})
|
||||
|
||||
it("should handle truncating all visible messages except first", () => {
|
||||
// This tests the edge case where visibleIndices[messagesToRemove + 1] would be undefined
|
||||
// 3 messages total: first is preserved, 2 others can be truncated
|
||||
const threeMessages: ApiMessage[] = [
|
||||
{ role: "user", content: "Initial", ts: 1000 },
|
||||
{ role: "assistant", content: "Response 1", ts: 1100 },
|
||||
{ role: "user", content: "Message 2", ts: 1200 },
|
||||
]
|
||||
|
||||
// With fracToRemove = 1.0:
|
||||
// visibleCount = 3
|
||||
// rawMessagesToRemove = floor((3-1) * 1.0) = 2
|
||||
// messagesToRemove = 2 (already even)
|
||||
// This truncates ALL messages except the first
|
||||
const result = truncateConversation(threeMessages, 1.0, "test-task-id")
|
||||
|
||||
expect(result.messagesRemoved).toBe(2)
|
||||
// Should have 3 original messages + 1 marker = 4
|
||||
expect(result.messages.length).toBe(4)
|
||||
|
||||
// First message should be untouched
|
||||
expect(result.messages[0].truncationParent).toBeUndefined()
|
||||
expect(result.messages[0].content).toBe("Initial")
|
||||
|
||||
// Messages at indices 1 and 2 should be tagged
|
||||
expect(result.messages[1].truncationParent).toBe(result.truncationId)
|
||||
expect(result.messages[2].truncationParent).toBe(result.truncationId)
|
||||
|
||||
// Marker should be at the end (index 3)
|
||||
expect(result.messages[3].isTruncationMarker).toBe(true)
|
||||
expect(result.messages[3].role).toBe("user")
|
||||
})
|
||||
|
||||
it("should handle empty condenseParent and truncationParent gracefully", () => {
|
||||
|
|
|
|||
|
|
@ -67,29 +67,64 @@ export function truncateConversation(messages: ApiMessage[], fracToRemove: numbe
|
|||
TelemetryService.instance.captureSlidingWindowTruncation(taskId)
|
||||
|
||||
const truncationId = crypto.randomUUID()
|
||||
const rawMessagesToRemove = Math.floor((messages.length - 1) * fracToRemove)
|
||||
|
||||
// Filter to only visible messages (those not already truncated)
|
||||
// We need to track original indices to correctly tag messages in the full array
|
||||
const visibleIndices: number[] = []
|
||||
messages.forEach((msg, index) => {
|
||||
if (!msg.truncationParent && !msg.isTruncationMarker) {
|
||||
visibleIndices.push(index)
|
||||
}
|
||||
})
|
||||
|
||||
// Calculate how many visible messages to truncate (excluding first visible message)
|
||||
const visibleCount = visibleIndices.length
|
||||
const rawMessagesToRemove = Math.floor((visibleCount - 1) * fracToRemove)
|
||||
const messagesToRemove = rawMessagesToRemove - (rawMessagesToRemove % 2)
|
||||
|
||||
if (messagesToRemove <= 0) {
|
||||
// Nothing to truncate
|
||||
return {
|
||||
messages,
|
||||
truncationId,
|
||||
messagesRemoved: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Get the indices of visible messages to truncate (skip first visible, take next N)
|
||||
const indicesToTruncate = new Set(visibleIndices.slice(1, messagesToRemove + 1))
|
||||
|
||||
// Tag messages that are being "truncated" (hidden from API calls)
|
||||
const taggedMessages = messages.map((msg, index) => {
|
||||
if (index > 0 && index <= messagesToRemove) {
|
||||
if (indicesToTruncate.has(index)) {
|
||||
return { ...msg, truncationParent: truncationId }
|
||||
}
|
||||
return msg
|
||||
})
|
||||
|
||||
// Insert truncation marker after first message (so we know a truncation happened)
|
||||
const firstKeptTs = messages[messagesToRemove + 1]?.ts ?? Date.now()
|
||||
// Find the actual boundary - the index right after the last truncated message
|
||||
const lastTruncatedVisibleIndex = visibleIndices[messagesToRemove] // Last visible message being truncated
|
||||
// If all visible messages except the first are truncated, insert marker at the end
|
||||
const firstKeptVisibleIndex = visibleIndices[messagesToRemove + 1] ?? taggedMessages.length
|
||||
|
||||
// Insert truncation marker at the actual boundary (between last truncated and first kept)
|
||||
const firstKeptTs = messages[firstKeptVisibleIndex]?.ts ?? Date.now()
|
||||
const truncationMarker: ApiMessage = {
|
||||
role: "assistant",
|
||||
role: "user",
|
||||
content: `[Sliding window truncation: ${messagesToRemove} messages hidden to reduce context]`,
|
||||
ts: firstKeptTs - 1,
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
}
|
||||
|
||||
// Insert marker after first message
|
||||
const result = [taggedMessages[0], truncationMarker, ...taggedMessages.slice(1)]
|
||||
// Insert marker at the boundary position
|
||||
// Find where to insert: right before the first kept visible message
|
||||
const insertPosition = firstKeptVisibleIndex
|
||||
const result = [
|
||||
...taggedMessages.slice(0, insertPosition),
|
||||
truncationMarker,
|
||||
...taggedMessages.slice(insertPosition),
|
||||
]
|
||||
|
||||
return {
|
||||
messages: result,
|
||||
|
|
@ -98,6 +133,68 @@ export function truncateConversation(messages: ApiMessage[], fracToRemove: numbe
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for checking if context management will likely run.
|
||||
* A subset of ContextManagementOptions with only the fields needed for threshold calculation.
|
||||
*/
|
||||
export type WillManageContextOptions = {
|
||||
totalTokens: number
|
||||
contextWindow: number
|
||||
maxTokens?: number | null
|
||||
autoCondenseContext: boolean
|
||||
autoCondenseContextPercent: number
|
||||
profileThresholds: Record<string, number>
|
||||
currentProfileId: string
|
||||
lastMessageTokens: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether context management (condensation or truncation) will likely run based on current token usage.
|
||||
*
|
||||
* This is useful for showing UI indicators before `manageContext` is actually called,
|
||||
* without duplicating the threshold calculation logic.
|
||||
*
|
||||
* @param {WillManageContextOptions} options - The options for threshold calculation
|
||||
* @returns {boolean} True if context management will likely run, false otherwise
|
||||
*/
|
||||
export function willManageContext({
|
||||
totalTokens,
|
||||
contextWindow,
|
||||
maxTokens,
|
||||
autoCondenseContext,
|
||||
autoCondenseContextPercent,
|
||||
profileThresholds,
|
||||
currentProfileId,
|
||||
lastMessageTokens,
|
||||
}: WillManageContextOptions): boolean {
|
||||
if (!autoCondenseContext) {
|
||||
// When auto-condense is disabled, only truncation can occur
|
||||
const reservedTokens = maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS
|
||||
const prevContextTokens = totalTokens + lastMessageTokens
|
||||
const allowedTokens = contextWindow * (1 - TOKEN_BUFFER_PERCENTAGE) - reservedTokens
|
||||
return prevContextTokens > allowedTokens
|
||||
}
|
||||
|
||||
const reservedTokens = maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS
|
||||
const prevContextTokens = totalTokens + lastMessageTokens
|
||||
const allowedTokens = contextWindow * (1 - TOKEN_BUFFER_PERCENTAGE) - reservedTokens
|
||||
|
||||
// Determine the effective threshold to use
|
||||
let effectiveThreshold = autoCondenseContextPercent
|
||||
const profileThreshold = profileThresholds[currentProfileId]
|
||||
if (profileThreshold !== undefined) {
|
||||
if (profileThreshold === -1) {
|
||||
effectiveThreshold = autoCondenseContextPercent
|
||||
} else if (profileThreshold >= MIN_CONDENSE_THRESHOLD && profileThreshold <= MAX_CONDENSE_THRESHOLD) {
|
||||
effectiveThreshold = profileThreshold
|
||||
}
|
||||
// Invalid values fall back to global setting (effectiveThreshold already set)
|
||||
}
|
||||
|
||||
const contextPercent = (100 * prevContextTokens) / contextWindow
|
||||
return contextPercent >= effectiveThreshold || prevContextTokens > allowedTokens
|
||||
}
|
||||
|
||||
/**
|
||||
* Context Management: Conditionally manages the conversation context when approaching limits.
|
||||
*
|
||||
|
|
@ -129,6 +226,7 @@ export type ContextManagementResult = SummarizeResponse & {
|
|||
prevContextTokens: number
|
||||
truncationId?: string
|
||||
messagesRemoved?: number
|
||||
newContextTokensAfterTruncation?: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -219,6 +317,25 @@ export async function manageContext({
|
|||
// Fall back to sliding window truncation if needed
|
||||
if (prevContextTokens > allowedTokens) {
|
||||
const truncationResult = truncateConversation(messages, 0.5, taskId)
|
||||
|
||||
// Calculate new context tokens after truncation by counting non-truncated messages
|
||||
// Messages with truncationParent are hidden, so we count only those without it
|
||||
const effectiveMessages = truncationResult.messages.filter(
|
||||
(msg) => !msg.truncationParent && !msg.isTruncationMarker,
|
||||
)
|
||||
let newContextTokensAfterTruncation = 0
|
||||
for (const msg of effectiveMessages) {
|
||||
const content = msg.content
|
||||
if (Array.isArray(content)) {
|
||||
newContextTokensAfterTruncation += await estimateTokenCount(content, apiHandler)
|
||||
} else if (typeof content === "string") {
|
||||
newContextTokensAfterTruncation += await estimateTokenCount(
|
||||
[{ type: "text", text: content }],
|
||||
apiHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
messages: truncationResult.messages,
|
||||
prevContextTokens,
|
||||
|
|
@ -227,6 +344,7 @@ export async function manageContext({
|
|||
error,
|
||||
truncationId: truncationResult.truncationId,
|
||||
messagesRemoved: truncationResult.messagesRemoved,
|
||||
newContextTokensAfterTruncation,
|
||||
}
|
||||
}
|
||||
// No truncation or condensation needed
|
||||
|
|
|
|||
731
src/core/message-manager/index.spec.ts
Normal file
731
src/core/message-manager/index.spec.ts
Normal file
|
|
@ -0,0 +1,731 @@
|
|||
import { MessageManager } from "./index"
|
||||
import * as condenseModule from "../condense"
|
||||
|
||||
describe("MessageManager", () => {
|
||||
let mockTask: any
|
||||
let manager: MessageManager
|
||||
let cleanupAfterTruncationSpy: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockTask = {
|
||||
clineMessages: [],
|
||||
apiConversationHistory: [],
|
||||
overwriteClineMessages: vi.fn(),
|
||||
overwriteApiConversationHistory: vi.fn(),
|
||||
}
|
||||
manager = new MessageManager(mockTask)
|
||||
|
||||
// Mock cleanupAfterTruncation to track calls and return input by default
|
||||
cleanupAfterTruncationSpy = vi.spyOn(condenseModule, "cleanupAfterTruncation")
|
||||
cleanupAfterTruncationSpy.mockImplementation((messages: any[]) => messages)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupAfterTruncationSpy.mockRestore()
|
||||
})
|
||||
|
||||
describe("Basic rewind operations", () => {
|
||||
it("should remove messages at and after the target timestamp", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
{ ts: 400, say: "assistant", text: "Response 2" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ ts: 200, role: "assistant", content: [{ type: "text", text: "Response" }] },
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
{ ts: 400, role: "assistant", content: [{ type: "text", text: "Response 2" }] },
|
||||
]
|
||||
|
||||
await manager.rewindToTimestamp(300)
|
||||
|
||||
// Should keep messages before ts=300
|
||||
expect(mockTask.overwriteClineMessages).toHaveBeenCalledWith([
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
])
|
||||
|
||||
// Should keep API messages before ts=300
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
expect(apiCall).toHaveLength(2)
|
||||
expect(apiCall[0].ts).toBe(100)
|
||||
expect(apiCall[1].ts).toBe(200)
|
||||
})
|
||||
|
||||
it("should keep target message when includeTargetMessage is true", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ ts: 200, role: "assistant", content: [{ type: "text", text: "Response" }] },
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
]
|
||||
|
||||
await manager.rewindToTimestamp(300, { includeTargetMessage: true })
|
||||
|
||||
// Should keep messages up to and including ts=300 in clineMessages
|
||||
expect(mockTask.overwriteClineMessages).toHaveBeenCalledWith([
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
])
|
||||
|
||||
// API history uses ts < cutoffTs, so excludes the message at ts=300
|
||||
// This is correct for edit scenarios - keep UI message but truncate API before it
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
expect(apiCall).toHaveLength(2)
|
||||
expect(apiCall[0].ts).toBe(100)
|
||||
expect(apiCall[1].ts).toBe(200)
|
||||
})
|
||||
|
||||
it("should throw error when timestamp not found", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
]
|
||||
|
||||
await expect(manager.rewindToTimestamp(999)).rejects.toThrow(
|
||||
"Message with timestamp 999 not found in clineMessages",
|
||||
)
|
||||
})
|
||||
|
||||
it("should remove messages at and after the target index", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
{ ts: 400, say: "assistant", text: "Response 2" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ ts: 200, role: "assistant", content: [{ type: "text", text: "Response" }] },
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
{ ts: 400, role: "assistant", content: [{ type: "text", text: "Response 2" }] },
|
||||
]
|
||||
|
||||
await manager.rewindToIndex(2)
|
||||
|
||||
// Should keep messages [0, 2) - index 0 and 1
|
||||
expect(mockTask.overwriteClineMessages).toHaveBeenCalledWith([
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
])
|
||||
|
||||
// Should keep API messages before ts=300
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
expect(apiCall).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Condense handling", () => {
|
||||
it("should preserve Summary when condense_context is preserved", async () => {
|
||||
const condenseId = "summary-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
{ ts: 300, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
{ ts: 400, say: "user", text: "After condense" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 200,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Response" }],
|
||||
condenseParent: condenseId,
|
||||
},
|
||||
{
|
||||
ts: 299,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
{ ts: 400, role: "user", content: [{ type: "text", text: "After condense" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=400, which preserves condense_context at ts=300
|
||||
await manager.rewindToTimestamp(400)
|
||||
|
||||
// Summary should still exist
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasSummary = apiCall.some((m: any) => m.isSummary && m.condenseId === condenseId)
|
||||
expect(hasSummary).toBe(true)
|
||||
})
|
||||
|
||||
it("should remove Summary when condense_context is removed", async () => {
|
||||
const condenseId = "summary-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "assistant", text: "Response" },
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
{ ts: 400, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
{ ts: 500, say: "user", text: "Third" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 200,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Response" }],
|
||||
condenseParent: condenseId,
|
||||
},
|
||||
{
|
||||
ts: 299,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
{ ts: 500, role: "user", content: [{ type: "text", text: "Third" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=300, which removes condense_context at ts=400
|
||||
await manager.rewindToTimestamp(300)
|
||||
|
||||
// Summary should be removed
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasSummary = apiCall.some((m: any) => m.isSummary)
|
||||
expect(hasSummary).toBe(false)
|
||||
})
|
||||
|
||||
it("should clear orphaned condenseParent tags via cleanup", async () => {
|
||||
const condenseId = "summary-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 150,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Response" }],
|
||||
condenseParent: condenseId,
|
||||
},
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
]
|
||||
|
||||
// Rewind to ts=100, which removes condense_context
|
||||
await manager.rewindToTimestamp(100)
|
||||
|
||||
// cleanupAfterTruncation should be called to remove orphaned tags
|
||||
expect(cleanupAfterTruncationSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle multiple condense_context removals", async () => {
|
||||
const condenseId1 = "summary-1"
|
||||
const condenseId2 = "summary-2"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{
|
||||
ts: 200,
|
||||
say: "condense_context",
|
||||
contextCondense: { condenseId: condenseId1, summary: "Summary 1" },
|
||||
},
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
{
|
||||
ts: 400,
|
||||
say: "condense_context",
|
||||
contextCondense: { condenseId: condenseId2, summary: "Summary 2" },
|
||||
},
|
||||
{ ts: 500, say: "user", text: "Third" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary 1" }],
|
||||
isSummary: true,
|
||||
condenseId: condenseId1,
|
||||
},
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
{
|
||||
ts: 399,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary 2" }],
|
||||
isSummary: true,
|
||||
condenseId: condenseId2,
|
||||
},
|
||||
{ ts: 500, role: "user", content: [{ type: "text", text: "Third" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=200, which removes both condense_context messages
|
||||
await manager.rewindToTimestamp(200)
|
||||
|
||||
// Both summaries should be removed
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasSummary1 = apiCall.some((m: any) => m.condenseId === condenseId1)
|
||||
const hasSummary2 = apiCall.some((m: any) => m.condenseId === condenseId2)
|
||||
expect(hasSummary1).toBe(false)
|
||||
expect(hasSummary2).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Truncation handling", () => {
|
||||
it("should preserve truncation marker when sliding_window_truncation is preserved", async () => {
|
||||
const truncationId = "trunc-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "sliding_window_truncation", contextTruncation: { truncationId, reason: "window" } },
|
||||
{ ts: 300, say: "user", text: "After truncation" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }], truncationParent: truncationId },
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
},
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "After truncation" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=300, which preserves sliding_window_truncation at ts=200
|
||||
await manager.rewindToTimestamp(300)
|
||||
|
||||
// Truncation marker should still exist
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasMarker = apiCall.some((m: any) => m.isTruncationMarker && m.truncationId === truncationId)
|
||||
expect(hasMarker).toBe(true)
|
||||
})
|
||||
|
||||
it("should remove truncation marker when sliding_window_truncation is removed", async () => {
|
||||
const truncationId = "trunc-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "user", text: "Second" },
|
||||
{ ts: 300, say: "sliding_window_truncation", contextTruncation: { truncationId, reason: "window" } },
|
||||
{ ts: 400, say: "user", text: "Third" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }], truncationParent: truncationId },
|
||||
{ ts: 200, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
{
|
||||
ts: 299,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
},
|
||||
{ ts: 400, role: "user", content: [{ type: "text", text: "Third" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=200, which removes sliding_window_truncation at ts=300
|
||||
await manager.rewindToTimestamp(200)
|
||||
|
||||
// Truncation marker should be removed
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasMarker = apiCall.some((m: any) => m.isTruncationMarker)
|
||||
expect(hasMarker).toBe(false)
|
||||
})
|
||||
|
||||
it("should clear orphaned truncationParent tags via cleanup", async () => {
|
||||
const truncationId = "trunc-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "sliding_window_truncation", contextTruncation: { truncationId, reason: "window" } },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }], truncationParent: truncationId },
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
},
|
||||
]
|
||||
|
||||
// Rewind to ts=100, which removes sliding_window_truncation
|
||||
await manager.rewindToTimestamp(100)
|
||||
|
||||
// cleanupAfterTruncation should be called to remove orphaned tags
|
||||
expect(cleanupAfterTruncationSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle multiple truncation removals", async () => {
|
||||
const truncationId1 = "trunc-1"
|
||||
const truncationId2 = "trunc-2"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{
|
||||
ts: 200,
|
||||
say: "sliding_window_truncation",
|
||||
contextTruncation: { truncationId: truncationId1, reason: "window" },
|
||||
},
|
||||
{ ts: 300, say: "user", text: "Second" },
|
||||
{
|
||||
ts: 400,
|
||||
say: "sliding_window_truncation",
|
||||
contextTruncation: { truncationId: truncationId2, reason: "window" },
|
||||
},
|
||||
{ ts: 500, say: "user", text: "Third" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId: truncationId1,
|
||||
},
|
||||
{ ts: 300, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
{
|
||||
ts: 399,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId: truncationId2,
|
||||
},
|
||||
{ ts: 500, role: "user", content: [{ type: "text", text: "Third" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=200, which removes both truncation messages
|
||||
await manager.rewindToTimestamp(200)
|
||||
|
||||
// Both markers should be removed
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasMarker1 = apiCall.some((m: any) => m.truncationId === truncationId1)
|
||||
const hasMarker2 = apiCall.some((m: any) => m.truncationId === truncationId2)
|
||||
expect(hasMarker1).toBe(false)
|
||||
expect(hasMarker2).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Checkpoint scenarios", () => {
|
||||
it("should preserve Summary when checkpoint restore is BEFORE condense", async () => {
|
||||
const condenseId = "summary-abc"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "Task" },
|
||||
{ ts: 500, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
{ ts: 600, say: "checkpoint_saved", text: "checkpoint-hash" },
|
||||
{ ts: 700, say: "user", text: "After checkpoint" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "Task" }] },
|
||||
{
|
||||
ts: 200,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Response 1" }],
|
||||
condenseParent: condenseId,
|
||||
},
|
||||
{
|
||||
ts: 499,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
{ ts: 700, role: "user", content: [{ type: "text", text: "After checkpoint" }] },
|
||||
]
|
||||
|
||||
// Restore checkpoint at ts=600 (like checkpoint restore does)
|
||||
await manager.rewindToTimestamp(600, { includeTargetMessage: true })
|
||||
|
||||
// Since condense_context (ts=500) is BEFORE checkpoint, it should be preserved
|
||||
const clineCall = mockTask.overwriteClineMessages.mock.calls[0][0]
|
||||
const hasCondenseContext = clineCall.some((m: any) => m.say === "condense_context")
|
||||
expect(hasCondenseContext).toBe(true)
|
||||
|
||||
// And the Summary should still exist
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasSummary = apiCall.some((m: any) => m.isSummary)
|
||||
expect(hasSummary).toBe(true)
|
||||
})
|
||||
|
||||
it("should remove Summary when checkpoint restore is AFTER condense", async () => {
|
||||
const condenseId = "summary-xyz"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "Task" },
|
||||
{ ts: 200, say: "checkpoint_saved", text: "checkpoint-hash" },
|
||||
{ ts: 300, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
{ ts: 400, say: "user", text: "After condense" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "Task" }] },
|
||||
{
|
||||
ts: 150,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Response" }],
|
||||
condenseParent: condenseId,
|
||||
},
|
||||
{
|
||||
ts: 299,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
{ ts: 400, role: "user", content: [{ type: "text", text: "After condense" }] },
|
||||
]
|
||||
|
||||
// Restore checkpoint at ts=200 (before the condense happened)
|
||||
await manager.rewindToTimestamp(200, { includeTargetMessage: true })
|
||||
|
||||
// condense_context (ts=300) is AFTER checkpoint, so it should be removed
|
||||
const clineCall = mockTask.overwriteClineMessages.mock.calls[0][0]
|
||||
const hasCondenseContext = clineCall.some((m: any) => m.say === "condense_context")
|
||||
expect(hasCondenseContext).toBe(false)
|
||||
|
||||
// And the Summary should be removed too
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasSummary = apiCall.some((m: any) => m.isSummary)
|
||||
expect(hasSummary).toBe(false)
|
||||
})
|
||||
|
||||
it("should preserve truncation marker when checkpoint restore is BEFORE truncation", async () => {
|
||||
const truncationId = "trunc-abc"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "Task" },
|
||||
{ ts: 500, say: "sliding_window_truncation", contextTruncation: { truncationId, reason: "window" } },
|
||||
{ ts: 600, say: "checkpoint_saved", text: "checkpoint-hash" },
|
||||
{ ts: 700, say: "user", text: "After checkpoint" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "Task" }], truncationParent: truncationId },
|
||||
{
|
||||
ts: 499,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
},
|
||||
{ ts: 700, role: "user", content: [{ type: "text", text: "After checkpoint" }] },
|
||||
]
|
||||
|
||||
// Restore checkpoint at ts=600
|
||||
await manager.rewindToTimestamp(600, { includeTargetMessage: true })
|
||||
|
||||
// Truncation should be preserved
|
||||
const clineCall = mockTask.overwriteClineMessages.mock.calls[0][0]
|
||||
const hasTruncation = clineCall.some((m: any) => m.say === "sliding_window_truncation")
|
||||
expect(hasTruncation).toBe(true)
|
||||
|
||||
// Marker should still exist
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasMarker = apiCall.some((m: any) => m.isTruncationMarker)
|
||||
expect(hasMarker).toBe(true)
|
||||
})
|
||||
|
||||
it("should remove truncation marker when checkpoint restore is AFTER truncation", async () => {
|
||||
const truncationId = "trunc-xyz"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "Task" },
|
||||
{ ts: 200, say: "checkpoint_saved", text: "checkpoint-hash" },
|
||||
{ ts: 300, say: "sliding_window_truncation", contextTruncation: { truncationId, reason: "window" } },
|
||||
{ ts: 400, say: "user", text: "After truncation" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "Task" }], truncationParent: truncationId },
|
||||
{
|
||||
ts: 299,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
},
|
||||
{ ts: 400, role: "user", content: [{ type: "text", text: "After truncation" }] },
|
||||
]
|
||||
|
||||
// Restore checkpoint at ts=200 (before truncation happened)
|
||||
await manager.rewindToTimestamp(200, { includeTargetMessage: true })
|
||||
|
||||
// Truncation should be removed
|
||||
const clineCall = mockTask.overwriteClineMessages.mock.calls[0][0]
|
||||
const hasTruncation = clineCall.some((m: any) => m.say === "sliding_window_truncation")
|
||||
expect(hasTruncation).toBe(false)
|
||||
|
||||
// Marker should be removed
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasMarker = apiCall.some((m: any) => m.isTruncationMarker)
|
||||
expect(hasMarker).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Skip cleanup option", () => {
|
||||
it("should NOT call cleanupAfterTruncation when skipCleanup is true", async () => {
|
||||
const condenseId = "summary-123"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 150,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Response" }],
|
||||
condenseParent: condenseId,
|
||||
},
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
]
|
||||
|
||||
// Rewind with skipCleanup
|
||||
await manager.rewindToTimestamp(100, { skipCleanup: true })
|
||||
|
||||
// cleanupAfterTruncation should NOT be called
|
||||
expect(cleanupAfterTruncationSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should call cleanupAfterTruncation by default", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "user", text: "Second" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ ts: 200, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
]
|
||||
|
||||
// Rewind without options (skipCleanup defaults to false)
|
||||
await manager.rewindToTimestamp(100)
|
||||
|
||||
// cleanupAfterTruncation should be called
|
||||
expect(cleanupAfterTruncationSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should call cleanupAfterTruncation when skipCleanup is explicitly false", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "user", text: "Second" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ ts: 200, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
]
|
||||
|
||||
// Rewind with skipCleanup explicitly false
|
||||
await manager.rewindToTimestamp(100, { skipCleanup: false })
|
||||
|
||||
// cleanupAfterTruncation should be called
|
||||
expect(cleanupAfterTruncationSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Combined scenarios", () => {
|
||||
it("should handle both condense and truncation removal in the same rewind", async () => {
|
||||
const condenseId = "summary-123"
|
||||
const truncationId = "trunc-456"
|
||||
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "condense_context", contextCondense: { condenseId, summary: "Summary" } },
|
||||
{ ts: 300, say: "sliding_window_truncation", contextTruncation: { truncationId, reason: "window" } },
|
||||
{ ts: 400, say: "user", text: "After both" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{
|
||||
ts: 199,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Summary" }],
|
||||
isSummary: true,
|
||||
condenseId,
|
||||
},
|
||||
{
|
||||
ts: 299,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "..." }],
|
||||
isTruncationMarker: true,
|
||||
truncationId,
|
||||
},
|
||||
{ ts: 400, role: "user", content: [{ type: "text", text: "After both" }] },
|
||||
]
|
||||
|
||||
// Rewind to ts=100, which removes both
|
||||
await manager.rewindToTimestamp(100)
|
||||
|
||||
// Both Summary and marker should be removed
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
const hasSummary = apiCall.some((m: any) => m.isSummary)
|
||||
const hasMarker = apiCall.some((m: any) => m.isTruncationMarker)
|
||||
expect(hasSummary).toBe(false)
|
||||
expect(hasMarker).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle empty clineMessages array", async () => {
|
||||
mockTask.clineMessages = []
|
||||
mockTask.apiConversationHistory = []
|
||||
|
||||
await manager.rewindToIndex(0)
|
||||
|
||||
expect(mockTask.overwriteClineMessages).toHaveBeenCalledWith([])
|
||||
// API history write is skipped when nothing changed (optimization)
|
||||
expect(mockTask.overwriteApiConversationHistory).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle messages without timestamps in API history", async () => {
|
||||
mockTask.clineMessages = [
|
||||
{ ts: 100, say: "user", text: "First" },
|
||||
{ ts: 200, say: "user", text: "Second" },
|
||||
]
|
||||
|
||||
mockTask.apiConversationHistory = [
|
||||
{ role: "system", content: [{ type: "text", text: "System message" }] }, // No ts
|
||||
{ ts: 100, role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ ts: 200, role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
]
|
||||
|
||||
await manager.rewindToTimestamp(100)
|
||||
|
||||
// Should keep system message (no ts) and message at ts=100
|
||||
const apiCall = mockTask.overwriteApiConversationHistory.mock.calls[0][0]
|
||||
expect(apiCall).toHaveLength(1)
|
||||
expect(apiCall[0].role).toBe("system")
|
||||
})
|
||||
})
|
||||
})
|
||||
185
src/core/message-manager/index.ts
Normal file
185
src/core/message-manager/index.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import { Task } from "../task/Task"
|
||||
import { ClineMessage } from "@roo-code/types"
|
||||
import { ApiMessage } from "../task-persistence/apiMessages"
|
||||
import { cleanupAfterTruncation } from "../condense"
|
||||
|
||||
export interface RewindOptions {
|
||||
/** Whether to include the target message in deletion (edit=true, delete=false) */
|
||||
includeTargetMessage?: boolean
|
||||
/** Skip cleanup for special cases (default: false) */
|
||||
skipCleanup?: boolean
|
||||
}
|
||||
|
||||
interface ContextEventIds {
|
||||
condenseIds: Set<string>
|
||||
truncationIds: Set<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* MessageManager provides centralized handling for all conversation rewind operations.
|
||||
*
|
||||
* This ensures that whenever UI chat history is rewound (delete, edit, checkpoint restore, etc.),
|
||||
* the API conversation history is properly maintained, including:
|
||||
* - Removing orphaned Summary messages when their condense_context is removed
|
||||
* - Removing orphaned truncation markers when their sliding_window_truncation is removed
|
||||
* - Cleaning up orphaned condenseParent/truncationParent tags
|
||||
*
|
||||
* Usage (always access via Task.messageManager getter):
|
||||
* ```typescript
|
||||
* await task.messageManager.rewindToTimestamp(messageTs, { includeTargetMessage: false })
|
||||
* ```
|
||||
*
|
||||
* @see Task.messageManager - The getter that provides lazy-initialized access to this manager
|
||||
*/
|
||||
export class MessageManager {
|
||||
constructor(private task: Task) {}
|
||||
|
||||
/**
|
||||
* Rewind conversation to a specific timestamp.
|
||||
* This is the SINGLE entry point for all message deletion operations.
|
||||
*
|
||||
* @param ts - The timestamp to rewind to
|
||||
* @param options - Rewind options
|
||||
* @throws Error if timestamp not found in clineMessages
|
||||
*/
|
||||
async rewindToTimestamp(ts: number, options: RewindOptions = {}): Promise<void> {
|
||||
const { includeTargetMessage = false, skipCleanup = false } = options
|
||||
|
||||
// Find the index in clineMessages
|
||||
const clineIndex = this.task.clineMessages.findIndex((m) => m.ts === ts)
|
||||
if (clineIndex === -1) {
|
||||
throw new Error(`Message with timestamp ${ts} not found in clineMessages`)
|
||||
}
|
||||
|
||||
// Calculate the actual cutoff index
|
||||
const cutoffIndex = includeTargetMessage ? clineIndex + 1 : clineIndex
|
||||
|
||||
await this.performRewind(cutoffIndex, ts, { skipCleanup })
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind conversation to a specific index in clineMessages.
|
||||
* Keeps messages [0, toIndex) and removes [toIndex, end].
|
||||
*
|
||||
* @param toIndex - The index to rewind to (exclusive)
|
||||
* @param options - Rewind options
|
||||
*/
|
||||
async rewindToIndex(toIndex: number, options: RewindOptions = {}): Promise<void> {
|
||||
const cutoffTs = this.task.clineMessages[toIndex]?.ts ?? Date.now()
|
||||
await this.performRewind(toIndex, cutoffTs, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that performs the actual rewind operation.
|
||||
*/
|
||||
private async performRewind(toIndex: number, cutoffTs: number, options: RewindOptions): Promise<void> {
|
||||
const { skipCleanup = false } = options
|
||||
|
||||
// Step 1: Collect context event IDs from messages being removed
|
||||
const removedIds = this.collectRemovedContextEventIds(toIndex)
|
||||
|
||||
// Step 2: Truncate clineMessages
|
||||
await this.truncateClineMessages(toIndex)
|
||||
|
||||
// Step 3: Truncate and clean API history (combined with cleanup for efficiency)
|
||||
await this.truncateApiHistoryWithCleanup(cutoffTs, removedIds, skipCleanup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect condenseIds and truncationIds from context-management events
|
||||
* that will be removed during the rewind.
|
||||
*
|
||||
* This is critical for maintaining the linkage between:
|
||||
* - condense_context (clineMessage) ↔ Summary (apiMessage)
|
||||
* - sliding_window_truncation (clineMessage) ↔ Truncation marker (apiMessage)
|
||||
*/
|
||||
private collectRemovedContextEventIds(fromIndex: number): ContextEventIds {
|
||||
const condenseIds = new Set<string>()
|
||||
const truncationIds = new Set<string>()
|
||||
|
||||
for (let i = fromIndex; i < this.task.clineMessages.length; i++) {
|
||||
const msg = this.task.clineMessages[i]
|
||||
|
||||
// Collect condenseIds from condense_context events
|
||||
if (msg.say === "condense_context" && msg.contextCondense?.condenseId) {
|
||||
condenseIds.add(msg.contextCondense.condenseId)
|
||||
console.log(`[MessageManager] Found condense_context to remove: ${msg.contextCondense.condenseId}`)
|
||||
}
|
||||
|
||||
// Collect truncationIds from sliding_window_truncation events
|
||||
if (msg.say === "sliding_window_truncation" && msg.contextTruncation?.truncationId) {
|
||||
truncationIds.add(msg.contextTruncation.truncationId)
|
||||
console.log(
|
||||
`[MessageManager] Found sliding_window_truncation to remove: ${msg.contextTruncation.truncationId}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return { condenseIds, truncationIds }
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate clineMessages to the specified index.
|
||||
*/
|
||||
private async truncateClineMessages(toIndex: number): Promise<void> {
|
||||
await this.task.overwriteClineMessages(this.task.clineMessages.slice(0, toIndex))
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate API history by timestamp, remove orphaned summaries/markers,
|
||||
* and clean up orphaned tags - all in a single write operation.
|
||||
*
|
||||
* This combined approach:
|
||||
* 1. Avoids multiple writes to API history
|
||||
* 2. Only writes if the history actually changed
|
||||
* 3. Handles both truncation and cleanup atomically
|
||||
*/
|
||||
private async truncateApiHistoryWithCleanup(
|
||||
cutoffTs: number,
|
||||
removedIds: ContextEventIds,
|
||||
skipCleanup: boolean,
|
||||
): Promise<void> {
|
||||
const originalHistory = this.task.apiConversationHistory
|
||||
let apiHistory = [...originalHistory]
|
||||
|
||||
// Step 1: Filter by timestamp
|
||||
apiHistory = apiHistory.filter((m) => !m.ts || m.ts < cutoffTs)
|
||||
|
||||
// Step 2: Remove Summaries whose condense_context was removed
|
||||
if (removedIds.condenseIds.size > 0) {
|
||||
apiHistory = apiHistory.filter((msg) => {
|
||||
if (msg.isSummary && msg.condenseId && removedIds.condenseIds.has(msg.condenseId)) {
|
||||
console.log(`[MessageManager] Removing orphaned Summary with condenseId: ${msg.condenseId}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Step 3: Remove truncation markers whose sliding_window_truncation was removed
|
||||
if (removedIds.truncationIds.size > 0) {
|
||||
apiHistory = apiHistory.filter((msg) => {
|
||||
if (msg.isTruncationMarker && msg.truncationId && removedIds.truncationIds.has(msg.truncationId)) {
|
||||
console.log(
|
||||
`[MessageManager] Removing orphaned truncation marker with truncationId: ${msg.truncationId}`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Step 4: Cleanup orphaned tags (unless skipped)
|
||||
if (!skipCleanup) {
|
||||
apiHistory = cleanupAfterTruncation(apiHistory)
|
||||
}
|
||||
|
||||
// Only write if the history actually changed
|
||||
const historyChanged =
|
||||
apiHistory.length !== originalHistory.length || apiHistory.some((msg, i) => msg !== originalHistory[i])
|
||||
|
||||
if (historyChanged) {
|
||||
await this.task.overwriteApiConversationHistory(apiHistory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -374,9 +393,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -394,11 +410,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -340,8 +351,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -359,9 +368,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches.
|
||||
|
||||
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -102,10 +102,14 @@ Example: Requesting instructions to create a Mode
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -113,13 +117,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -160,9 +171,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -171,7 +190,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -373,9 +392,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -393,11 +409,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -440,9 +459,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
|
||||
|
||||
|
|
@ -462,11 +478,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -108,10 +108,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -119,13 +123,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -166,9 +177,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -177,7 +196,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -379,9 +398,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -399,11 +415,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -374,9 +393,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -394,11 +410,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -196,6 +215,10 @@ Example: Requesting to write to frontend-config.json
|
|||
## browser_action
|
||||
Description: Request to interact with a Puppeteer-controlled browser. Every action, except `close`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action.
|
||||
|
||||
This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. Use it at key stages of web development tasks - such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. Analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.
|
||||
|
||||
The user may ask generic non-development tasks (such as "what's the latest news" or "look up the weather"), in which case you might use this tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.
|
||||
|
||||
**Browser Session Lifecycle:**
|
||||
- Browser sessions **start** with `launch` and **end** with `close`
|
||||
- The session remains active across multiple messages and tool uses
|
||||
|
|
@ -440,14 +463,9 @@ By waiting for and carefully considering the user's response after each tool use
|
|||
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, use the browser, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.
|
||||
- For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser.
|
||||
|
||||
====
|
||||
|
||||
|
|
@ -464,11 +482,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
@ -478,14 +491,13 @@ RULES
|
|||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.
|
||||
- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.
|
||||
- The user may ask generic non-development tasks, such as "what's the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.
|
||||
- NEVER end attempt_completion result with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.
|
||||
- You are STRICTLY FORBIDDEN from starting your messages with "Great", "Certainly", "Okay", "Sure". You should NOT be conversational in your responses, but rather direct and to the point. For example you should NOT say "Great, I've updated the CSS" but instead something like "I've updated the CSS". It is important you be clear and technical in your messages.
|
||||
- When presented with images, utilize your vision capabilities to thoroughly examine them and extract meaningful information. Incorporate these insights into your thought process as you accomplish the user's task.
|
||||
- At the end of each user message, you will automatically receive environment_details. This information is not written by the user themselves, but is auto-generated to provide potentially relevant context about the project structure and environment. While this information can be valuable for understanding the project context, do not treat it as a direct part of the user's request or response. Use it to inform your actions and decisions, but don't assume the user is explicitly asking about or referring to this information unless they clearly do so in their message. When using environment_details, explain your actions clearly to ensure the user understands, as they may not be aware of these details.
|
||||
- Before executing commands, check the "Actively Running Terminals" section in environment_details. If present, consider how these active processes might impact your task. For example, if a local development server is already running, you wouldn't need to start it again. If no active terminals are listed, proceed with command execution as normal.
|
||||
- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations.
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc. Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser.
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.
|
||||
|
||||
====
|
||||
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -374,9 +393,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -394,11 +410,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -249,9 +260,17 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -260,7 +279,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -462,9 +481,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the apply_diff or write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -482,12 +498,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using apply_diff or write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: apply_diff (for surgical edits - targeted changes to specific lines or functions), write_to_file (for creating new files or complete file rewrites).
|
||||
- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files.
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -374,9 +393,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -394,11 +410,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -374,9 +393,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -394,11 +410,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -440,9 +459,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
|
||||
|
||||
|
|
@ -462,11 +478,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ Example: Requesting instructions to create an MCP Server
|
|||
|
||||
## search_files
|
||||
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
|
||||
|
||||
Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched.
|
||||
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
|
||||
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
|
||||
|
||||
Usage:
|
||||
<search_files>
|
||||
<path>Directory path here</path>
|
||||
|
|
@ -114,13 +118,20 @@ Usage:
|
|||
<file_pattern>file pattern here (optional)</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Requesting to search for all .ts files in the current directory
|
||||
Example: Searching for all .ts files in the current directory
|
||||
<search_files>
|
||||
<path>.</path>
|
||||
<regex>.*</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
|
||||
Example: Searching for function definitions in JavaScript files
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>function\s+\w+</regex>
|
||||
<file_pattern>*.js</file_pattern>
|
||||
</search_files>
|
||||
|
||||
## list_files
|
||||
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
|
||||
Parameters:
|
||||
|
|
@ -161,9 +172,17 @@ Examples:
|
|||
|
||||
## write_to_file
|
||||
Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
||||
When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code.
|
||||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current workspace directory /test/path)
|
||||
- content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.
|
||||
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
|
|
@ -172,7 +191,7 @@ Your file content here
|
|||
</content>
|
||||
</write_to_file>
|
||||
|
||||
Example: Requesting to write to frontend-config.json
|
||||
Example: Writing a configuration file
|
||||
<write_to_file>
|
||||
<path>frontend-config.json</path>
|
||||
<content>
|
||||
|
|
@ -374,9 +393,6 @@ CAPABILITIES
|
|||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
|
||||
|
||||
====
|
||||
|
|
@ -394,11 +410,6 @@ RULES
|
|||
- You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`.
|
||||
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
|
||||
- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites).
|
||||
- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\.md$"
|
||||
|
|
|
|||
|
|
@ -1,225 +0,0 @@
|
|||
import { getCapabilitiesSection } from "../sections/capabilities"
|
||||
import { getRulesSection } from "../sections/rules"
|
||||
import type { DiffStrategy, DiffResult, DiffItem } from "../../../shared/tools"
|
||||
|
||||
describe("Mode-aware system prompt sections", () => {
|
||||
const cwd = "/test/path"
|
||||
const mcpHub = undefined
|
||||
const mockDiffStrategy: DiffStrategy = {
|
||||
getName: () => "MockStrategy",
|
||||
getToolDescription: () => "apply_diff tool description",
|
||||
async applyDiff(_originalContent: string, _diffContents: string | DiffItem[]): Promise<DiffResult> {
|
||||
return { success: true, content: "mock result" }
|
||||
},
|
||||
}
|
||||
|
||||
describe("getCapabilitiesSection", () => {
|
||||
it('should include editing tools in "code" mode', () => {
|
||||
const result = getCapabilitiesSection(cwd, false, "code", undefined, undefined, mcpHub, mockDiffStrategy)
|
||||
|
||||
expect(result).toContain("apply_diff")
|
||||
expect(result).toContain("write_to_file")
|
||||
})
|
||||
|
||||
it('should NOT include editing tools in "ask" mode', () => {
|
||||
const result = getCapabilitiesSection(cwd, false, "ask", undefined, undefined, mcpHub, mockDiffStrategy)
|
||||
|
||||
// Ask mode doesn't have the "edit" group, so editing tools shouldn't be mentioned
|
||||
expect(result).not.toContain("apply_diff")
|
||||
expect(result).not.toContain("write_to_file")
|
||||
})
|
||||
|
||||
it('should include editing tools in "architect" mode', () => {
|
||||
const result = getCapabilitiesSection(
|
||||
cwd,
|
||||
false,
|
||||
"architect",
|
||||
undefined,
|
||||
undefined,
|
||||
mcpHub,
|
||||
mockDiffStrategy,
|
||||
)
|
||||
|
||||
// Architect mode has write_to_file (for markdown files)
|
||||
expect(result).toContain("write_to_file")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getRulesSection", () => {
|
||||
it('should include editing instructions in "code" mode', () => {
|
||||
const result = getRulesSection(
|
||||
cwd,
|
||||
false,
|
||||
"code",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
expect(result).toContain("For editing files")
|
||||
expect(result).toContain("apply_diff")
|
||||
expect(result).toContain("write_to_file")
|
||||
})
|
||||
|
||||
it('should NOT include editing instructions in "ask" mode', () => {
|
||||
const result = getRulesSection(
|
||||
cwd,
|
||||
false,
|
||||
"ask",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
// Ask mode has no editing tools, so shouldn't mention them
|
||||
expect(result).not.toContain("For editing files")
|
||||
expect(result).not.toContain("apply_diff")
|
||||
expect(result).not.toContain("write_to_file")
|
||||
})
|
||||
|
||||
it('should include editing instructions in "debug" mode', () => {
|
||||
const result = getRulesSection(
|
||||
cwd,
|
||||
false,
|
||||
"debug",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
// Debug mode has editing tools
|
||||
expect(result).toContain("For editing files")
|
||||
expect(result).toContain("write_to_file")
|
||||
})
|
||||
|
||||
it("should filter editing tools from search_files description in ask mode", () => {
|
||||
const result = getRulesSection(
|
||||
cwd,
|
||||
false,
|
||||
"ask",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
// In ask mode, the search_files description shouldn't mention editing tools
|
||||
expect(result).toContain("When using the search_files tool")
|
||||
expect(result).not.toContain("before using apply_diff")
|
||||
expect(result).not.toContain("before using write_to_file")
|
||||
})
|
||||
|
||||
it("should include editing tools in search_files description in code mode", () => {
|
||||
const result = getRulesSection(
|
||||
cwd,
|
||||
false,
|
||||
"code",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
// In code mode, the search_files description should mention editing tools
|
||||
expect(result).toContain("When using the search_files tool")
|
||||
expect(result).toContain("before using apply_diff or write_to_file")
|
||||
})
|
||||
})
|
||||
|
||||
describe("browser_action filtering", () => {
|
||||
it("should include browser_action mentions when enabled and mode supports it", () => {
|
||||
const capabilities = getCapabilitiesSection(
|
||||
cwd,
|
||||
true, // supportsComputerUse
|
||||
"code",
|
||||
undefined,
|
||||
undefined,
|
||||
mcpHub,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
{ browserToolEnabled: true } as any,
|
||||
)
|
||||
|
||||
const rules = getRulesSection(
|
||||
cwd,
|
||||
true, // supportsComputerUse
|
||||
"code",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
{ browserToolEnabled: true } as any,
|
||||
)
|
||||
|
||||
expect(capabilities).toContain("use the browser")
|
||||
expect(capabilities).toContain("browser_action tool")
|
||||
expect(rules).toContain("browser_action")
|
||||
})
|
||||
|
||||
it("should NOT include browser_action mentions when disabled in settings", () => {
|
||||
const capabilities = getCapabilitiesSection(
|
||||
cwd,
|
||||
true, // supportsComputerUse
|
||||
"code",
|
||||
undefined,
|
||||
undefined,
|
||||
mcpHub,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
{ browserToolEnabled: false } as any,
|
||||
)
|
||||
|
||||
const rules = getRulesSection(
|
||||
cwd,
|
||||
true, // supportsComputerUse
|
||||
"code",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
{ browserToolEnabled: false } as any,
|
||||
)
|
||||
|
||||
expect(capabilities).not.toContain("use the browser")
|
||||
expect(capabilities).not.toContain("browser_action tool")
|
||||
expect(rules).not.toContain("browser_action")
|
||||
})
|
||||
|
||||
it("should NOT include browser_action mentions when mode doesn't support browser", () => {
|
||||
const capabilities = getCapabilitiesSection(
|
||||
cwd,
|
||||
true, // supportsComputerUse
|
||||
"orchestrator", // orchestrator mode has no groups, including browser
|
||||
undefined,
|
||||
undefined,
|
||||
mcpHub,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
{ browserToolEnabled: true } as any,
|
||||
)
|
||||
|
||||
const rules = getRulesSection(
|
||||
cwd,
|
||||
true, // supportsComputerUse
|
||||
"orchestrator",
|
||||
undefined,
|
||||
undefined,
|
||||
mockDiffStrategy,
|
||||
undefined,
|
||||
{ browserToolEnabled: true } as any,
|
||||
)
|
||||
|
||||
expect(capabilities).not.toContain("use the browser")
|
||||
expect(capabilities).not.toContain("browser_action tool")
|
||||
expect(rules).not.toContain("browser_action")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { addCustomInstructions } from "../sections/custom-instructions"
|
||||
import { getCapabilitiesSection } from "../sections/capabilities"
|
||||
import { getRulesSection } from "../sections/rules"
|
||||
import type { DiffStrategy, DiffResult, DiffItem } from "../../../shared/tools"
|
||||
import { McpHub } from "../../../services/mcp/McpHub"
|
||||
|
||||
describe("addCustomInstructions", () => {
|
||||
it("adds vscode language to custom instructions", async () => {
|
||||
|
|
@ -32,33 +32,41 @@ describe("addCustomInstructions", () => {
|
|||
|
||||
describe("getCapabilitiesSection", () => {
|
||||
const cwd = "/test/path"
|
||||
const mcpHub = undefined
|
||||
const mockDiffStrategy: DiffStrategy = {
|
||||
getName: () => "MockStrategy",
|
||||
getToolDescription: () => "apply_diff tool description",
|
||||
async applyDiff(_originalContent: string, _diffContents: string | DiffItem[]): Promise<DiffResult> {
|
||||
return { success: true, content: "mock result" }
|
||||
},
|
||||
}
|
||||
|
||||
it("includes apply_diff in capabilities when diffStrategy is provided", () => {
|
||||
const result = getCapabilitiesSection(cwd, false, "code", undefined, undefined, mcpHub, mockDiffStrategy)
|
||||
it("includes standard capabilities", () => {
|
||||
const result = getCapabilitiesSection(cwd)
|
||||
|
||||
expect(result).toContain("apply_diff")
|
||||
expect(result).toContain("write_to_file")
|
||||
expect(result).toContain("CAPABILITIES")
|
||||
expect(result).toContain("execute CLI commands")
|
||||
expect(result).toContain("list files")
|
||||
expect(result).toContain("read and write files")
|
||||
})
|
||||
|
||||
it("excludes apply_diff from capabilities when diffStrategy is undefined", () => {
|
||||
const result = getCapabilitiesSection(cwd, false, "code", undefined, undefined, mcpHub, undefined)
|
||||
it("includes MCP reference when mcpHub is provided", () => {
|
||||
const mockMcpHub = {} as McpHub
|
||||
const result = getCapabilitiesSection(cwd, mockMcpHub)
|
||||
|
||||
expect(result).not.toContain("apply_diff")
|
||||
expect(result).toContain("write_to_file")
|
||||
expect(result).toContain("MCP servers")
|
||||
})
|
||||
|
||||
it("excludes MCP reference when mcpHub is undefined", () => {
|
||||
const result = getCapabilitiesSection(cwd, undefined)
|
||||
|
||||
expect(result).not.toContain("MCP servers")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getRulesSection", () => {
|
||||
const cwd = "/test/path"
|
||||
|
||||
it("includes standard rules", () => {
|
||||
const result = getRulesSection(cwd)
|
||||
|
||||
expect(result).toContain("RULES")
|
||||
expect(result).toContain("project base directory")
|
||||
expect(result).toContain(cwd)
|
||||
})
|
||||
|
||||
it("includes vendor confidentiality section when isStealthModel is true", () => {
|
||||
const settings = {
|
||||
maxConcurrentFileReads: 5,
|
||||
|
|
@ -68,7 +76,7 @@ describe("getRulesSection", () => {
|
|||
isStealthModel: true,
|
||||
}
|
||||
|
||||
const result = getRulesSection(cwd, false, "code", undefined, undefined, undefined, undefined, settings)
|
||||
const result = getRulesSection(cwd, settings)
|
||||
|
||||
expect(result).toContain("VENDOR CONFIDENTIALITY")
|
||||
expect(result).toContain("Never reveal the vendor or company that created you")
|
||||
|
|
@ -86,7 +94,7 @@ describe("getRulesSection", () => {
|
|||
isStealthModel: false,
|
||||
}
|
||||
|
||||
const result = getRulesSection(cwd, false, "code", undefined, undefined, undefined, undefined, settings)
|
||||
const result = getRulesSection(cwd, settings)
|
||||
|
||||
expect(result).not.toContain("VENDOR CONFIDENTIALITY")
|
||||
expect(result).not.toContain("Never reveal the vendor or company")
|
||||
|
|
@ -100,7 +108,7 @@ describe("getRulesSection", () => {
|
|||
newTaskRequireTodos: false,
|
||||
}
|
||||
|
||||
const result = getRulesSection(cwd, false, "code", undefined, undefined, undefined, undefined, settings)
|
||||
const result = getRulesSection(cwd, settings)
|
||||
|
||||
expect(result).not.toContain("VENDOR CONFIDENTIALITY")
|
||||
expect(result).not.toContain("Never reveal the vendor or company")
|
||||
|
|
|
|||
|
|
@ -1,82 +1,47 @@
|
|||
import { getObjectiveSection } from "../objective"
|
||||
import type { CodeIndexManager } from "../../../../services/code-index/manager"
|
||||
|
||||
describe("getObjectiveSection", () => {
|
||||
// Mock CodeIndexManager with codebase search available
|
||||
const mockCodeIndexManagerEnabled = {
|
||||
isFeatureEnabled: true,
|
||||
isFeatureConfigured: true,
|
||||
isInitialized: true,
|
||||
} as CodeIndexManager
|
||||
it("should include proper numbered structure", () => {
|
||||
const objective = getObjectiveSection()
|
||||
|
||||
// Mock CodeIndexManager with codebase search unavailable
|
||||
const mockCodeIndexManagerDisabled = {
|
||||
isFeatureEnabled: false,
|
||||
isFeatureConfigured: false,
|
||||
isInitialized: false,
|
||||
} as CodeIndexManager
|
||||
|
||||
describe("when codebase_search is available", () => {
|
||||
it("should include codebase_search first enforcement in thinking process", () => {
|
||||
const objective = getObjectiveSection(mockCodeIndexManagerEnabled)
|
||||
|
||||
// Check that the objective includes the codebase_search enforcement
|
||||
expect(objective).toContain(
|
||||
"for ANY exploration of code you haven't examined yet in this conversation, you MUST use the `codebase_search` tool",
|
||||
)
|
||||
expect(objective).toContain("BEFORE using any other search or file exploration tools")
|
||||
expect(objective).toContain("This applies throughout the entire task, not just at the beginning")
|
||||
})
|
||||
// Check that all numbered items are present
|
||||
expect(objective).toContain("1. Analyze the user's task")
|
||||
expect(objective).toContain("2. Work through these goals sequentially")
|
||||
expect(objective).toContain("3. Remember, you have extensive capabilities")
|
||||
expect(objective).toContain("4. Once you've completed the user's task")
|
||||
expect(objective).toContain("5. The user may provide feedback")
|
||||
})
|
||||
|
||||
describe("when codebase_search is not available", () => {
|
||||
it("should not include codebase_search enforcement", () => {
|
||||
const objective = getObjectiveSection(mockCodeIndexManagerDisabled)
|
||||
it("should include analysis guidance", () => {
|
||||
const objective = getObjectiveSection()
|
||||
|
||||
// Check that the objective does not include the codebase_search enforcement
|
||||
expect(objective).not.toContain("you MUST use the `codebase_search` tool")
|
||||
expect(objective).not.toContain("BEFORE using any other search or file exploration tools")
|
||||
})
|
||||
expect(objective).toContain("Before calling a tool, do some analysis")
|
||||
expect(objective).toContain("analyze the file structure provided in environment_details")
|
||||
expect(objective).toContain("think about which of the provided tools is the most relevant")
|
||||
})
|
||||
|
||||
it("should maintain proper structure regardless of codebase_search availability", () => {
|
||||
const objectiveEnabled = getObjectiveSection(mockCodeIndexManagerEnabled)
|
||||
const objectiveDisabled = getObjectiveSection(mockCodeIndexManagerDisabled)
|
||||
it("should include parameter inference guidance", () => {
|
||||
const objective = getObjectiveSection()
|
||||
|
||||
// Check that all numbered items are present in both cases
|
||||
for (const objective of [objectiveEnabled, objectiveDisabled]) {
|
||||
expect(objective).toContain("1. Analyze the user's task")
|
||||
expect(objective).toContain("2. Work through these goals sequentially")
|
||||
expect(objective).toContain("3. Remember, you have extensive capabilities")
|
||||
expect(objective).toContain("4. Once you've completed the user's task")
|
||||
expect(objective).toContain("5. The user may provide feedback")
|
||||
}
|
||||
expect(objective).toContain("Go through each of the required parameters")
|
||||
expect(objective).toContain(
|
||||
"determine if the user has directly provided or given enough information to infer a value",
|
||||
)
|
||||
expect(objective).toContain("DO NOT invoke the tool (not even with fillers for the missing params)")
|
||||
expect(objective).toContain("ask_followup_question tool")
|
||||
})
|
||||
|
||||
it("should include analysis guidance regardless of codebase_search availability", () => {
|
||||
const objectiveEnabled = getObjectiveSection(mockCodeIndexManagerEnabled)
|
||||
const objectiveDisabled = getObjectiveSection(mockCodeIndexManagerDisabled)
|
||||
it("should include guidance about not engaging in back and forth conversations", () => {
|
||||
const objective = getObjectiveSection()
|
||||
|
||||
// Check that analysis guidance is included in both cases
|
||||
for (const objective of [objectiveEnabled, objectiveDisabled]) {
|
||||
expect(objective).toContain("Before calling a tool, do some analysis")
|
||||
expect(objective).toContain("analyze the file structure provided in environment_details")
|
||||
expect(objective).toContain("think about which of the provided tools is the most relevant")
|
||||
}
|
||||
expect(objective).toContain("DO NOT continue in pointless back and forth conversations")
|
||||
expect(objective).toContain("don't end your responses with questions or offers for further assistance")
|
||||
})
|
||||
|
||||
it("should include parameter inference guidance regardless of codebase_search availability", () => {
|
||||
const objectiveEnabled = getObjectiveSection(mockCodeIndexManagerEnabled)
|
||||
const objectiveDisabled = getObjectiveSection(mockCodeIndexManagerDisabled)
|
||||
it("should include the OBJECTIVE header", () => {
|
||||
const objective = getObjectiveSection()
|
||||
|
||||
// Check parameter inference guidance in both cases
|
||||
for (const objective of [objectiveEnabled, objectiveDisabled]) {
|
||||
expect(objective).toContain("Go through each of the required parameters")
|
||||
expect(objective).toContain(
|
||||
"determine if the user has directly provided or given enough information to infer a value",
|
||||
)
|
||||
expect(objective).toContain("DO NOT invoke the tool (not even with fillers for the missing params)")
|
||||
expect(objective).toContain("ask_followup_question tool")
|
||||
}
|
||||
expect(objective).toContain("OBJECTIVE")
|
||||
expect(objective).toContain("You accomplish a given task iteratively")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,62 +1,10 @@
|
|||
import { getToolUseGuidelinesSection } from "../tool-use-guidelines"
|
||||
import type { CodeIndexManager } from "../../../../services/code-index/manager"
|
||||
import { TOOL_PROTOCOL } from "@roo-code/types"
|
||||
|
||||
describe("getToolUseGuidelinesSection", () => {
|
||||
// Mock CodeIndexManager with codebase search available
|
||||
const mockCodeIndexManagerEnabled = {
|
||||
isFeatureEnabled: true,
|
||||
isFeatureConfigured: true,
|
||||
isInitialized: true,
|
||||
} as CodeIndexManager
|
||||
|
||||
// Mock CodeIndexManager with codebase search unavailable
|
||||
const mockCodeIndexManagerDisabled = {
|
||||
isFeatureEnabled: false,
|
||||
isFeatureConfigured: false,
|
||||
isInitialized: false,
|
||||
} as CodeIndexManager
|
||||
|
||||
describe("when codebase_search is available", () => {
|
||||
it("should include codebase_search first enforcement", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerEnabled)
|
||||
|
||||
// Check that the guidelines include the codebase_search enforcement
|
||||
expect(guidelines).toContain(
|
||||
"CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use the `codebase_search` tool FIRST",
|
||||
)
|
||||
expect(guidelines).toContain("before any other search or file exploration tools")
|
||||
expect(guidelines).toContain(
|
||||
"semantic search to find relevant code based on meaning rather than just keywords",
|
||||
)
|
||||
})
|
||||
|
||||
it("should maintain proper numbering with codebase_search", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerEnabled)
|
||||
|
||||
// Check that all numbered items are present
|
||||
expect(guidelines).toContain("1. Assess what information")
|
||||
expect(guidelines).toContain("2. **CRITICAL:")
|
||||
expect(guidelines).toContain("3. Choose the most appropriate tool")
|
||||
expect(guidelines).toContain("4. If multiple actions are needed")
|
||||
expect(guidelines).toContain("5. Formulate your tool use")
|
||||
expect(guidelines).toContain("6. After each tool use")
|
||||
expect(guidelines).toContain("7. ALWAYS wait for user confirmation")
|
||||
})
|
||||
})
|
||||
|
||||
describe("when codebase_search is not available", () => {
|
||||
it("should not include codebase_search enforcement", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerDisabled)
|
||||
|
||||
// Check that the guidelines do not include the codebase_search enforcement
|
||||
expect(guidelines).not.toContain(
|
||||
"CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use the `codebase_search` tool FIRST",
|
||||
)
|
||||
expect(guidelines).not.toContain("semantic search to find relevant code based on meaning")
|
||||
})
|
||||
|
||||
it("should maintain proper numbering without codebase_search", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerDisabled)
|
||||
describe("XML protocol", () => {
|
||||
it("should include proper numbered guidelines", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML)
|
||||
|
||||
// Check that all numbered items are present with correct numbering
|
||||
expect(guidelines).toContain("1. Assess what information")
|
||||
|
|
@ -66,19 +14,62 @@ describe("getToolUseGuidelinesSection", () => {
|
|||
expect(guidelines).toContain("5. After each tool use")
|
||||
expect(guidelines).toContain("6. ALWAYS wait for user confirmation")
|
||||
})
|
||||
})
|
||||
|
||||
it("should include iterative process guidelines regardless of codebase_search availability", () => {
|
||||
const guidelinesEnabled = getToolUseGuidelinesSection(mockCodeIndexManagerEnabled)
|
||||
const guidelinesDisabled = getToolUseGuidelinesSection(mockCodeIndexManagerDisabled)
|
||||
it("should include XML-specific guidelines", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML)
|
||||
|
||||
expect(guidelines).toContain("Formulate your tool use using the XML format specified for each tool")
|
||||
expect(guidelines).toContain("use one tool at a time per message")
|
||||
expect(guidelines).toContain("ALWAYS wait for user confirmation")
|
||||
})
|
||||
|
||||
it("should include iterative process guidelines", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML)
|
||||
|
||||
// Check that the iterative process section is included in both cases
|
||||
for (const guidelines of [guidelinesEnabled, guidelinesDisabled]) {
|
||||
expect(guidelines).toContain("It is crucial to proceed step-by-step")
|
||||
expect(guidelines).toContain("1. Confirm the success of each step before proceeding")
|
||||
expect(guidelines).toContain("2. Address any issues or errors that arise immediately")
|
||||
expect(guidelines).toContain("3. Adapt your approach based on new information")
|
||||
expect(guidelines).toContain("4. Ensure that each action builds correctly")
|
||||
})
|
||||
})
|
||||
|
||||
describe("native protocol", () => {
|
||||
it("should include proper numbered guidelines", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE)
|
||||
|
||||
// Check that all numbered items are present with correct numbering
|
||||
expect(guidelines).toContain("1. Assess what information")
|
||||
expect(guidelines).toContain("2. Choose the most appropriate tool")
|
||||
expect(guidelines).toContain("3. If multiple actions are needed")
|
||||
expect(guidelines).toContain("4. After each tool use")
|
||||
})
|
||||
|
||||
it("should include native protocol-specific guidelines", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE)
|
||||
|
||||
expect(guidelines).toContain("you may use multiple tools in a single message")
|
||||
expect(guidelines).not.toContain("Formulate your tool use using the XML format")
|
||||
expect(guidelines).not.toContain("ALWAYS wait for user confirmation")
|
||||
})
|
||||
|
||||
it("should include simplified iterative process guidelines", () => {
|
||||
const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE)
|
||||
|
||||
expect(guidelines).toContain("carefully considering the user's response after tool executions")
|
||||
// Native protocol doesn't have the step-by-step list
|
||||
expect(guidelines).not.toContain("It is crucial to proceed step-by-step")
|
||||
})
|
||||
})
|
||||
|
||||
it("should include common guidance regardless of protocol", () => {
|
||||
const guidelinesXml = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML)
|
||||
const guidelinesNative = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE)
|
||||
|
||||
for (const guidelines of [guidelinesXml, guidelinesNative]) {
|
||||
expect(guidelines).toContain("Assess what information you already have")
|
||||
expect(guidelines).toContain("Choose the most appropriate tool")
|
||||
expect(guidelines).toContain("After each tool use, the user will respond")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,82 +1,13 @@
|
|||
import { DiffStrategy } from "../../../shared/tools"
|
||||
import { McpHub } from "../../../services/mcp/McpHub"
|
||||
import { CodeIndexManager } from "../../../services/code-index/manager"
|
||||
import type { ModeConfig, ToolName } from "@roo-code/types"
|
||||
import { getAvailableToolsInGroup } from "../tools/filter-tools-for-mode"
|
||||
import type { SystemPromptSettings } from "../types"
|
||||
|
||||
export function getCapabilitiesSection(
|
||||
cwd: string,
|
||||
supportsComputerUse: boolean,
|
||||
mode: string,
|
||||
customModes: ModeConfig[] | undefined,
|
||||
experiments: Record<string, boolean> | undefined,
|
||||
mcpHub?: McpHub,
|
||||
diffStrategy?: DiffStrategy,
|
||||
codeIndexManager?: CodeIndexManager,
|
||||
settings?: SystemPromptSettings,
|
||||
): string {
|
||||
// Get available tools from relevant groups
|
||||
const availableEditTools = getAvailableToolsInGroup(
|
||||
"edit",
|
||||
mode,
|
||||
customModes,
|
||||
experiments,
|
||||
codeIndexManager,
|
||||
settings,
|
||||
)
|
||||
const availableBrowserTools = getAvailableToolsInGroup(
|
||||
"browser",
|
||||
mode,
|
||||
customModes,
|
||||
experiments,
|
||||
codeIndexManager,
|
||||
settings,
|
||||
)
|
||||
|
||||
// Build the tool list for the example, filtering for main editing tools
|
||||
const editingToolsExample = (["apply_diff", "write_to_file"] as const).filter((tool) => {
|
||||
if (tool === "apply_diff") return diffStrategy && availableEditTools.includes(tool as ToolName)
|
||||
return availableEditTools.includes(tool as ToolName)
|
||||
})
|
||||
|
||||
const editingToolsText =
|
||||
editingToolsExample.length === 1
|
||||
? `the ${editingToolsExample[0]}`
|
||||
: editingToolsExample.length === 2
|
||||
? `the ${editingToolsExample[0]} or ${editingToolsExample[1]}`
|
||||
: `the ${editingToolsExample.slice(0, -1).join(", ")}, or ${editingToolsExample[editingToolsExample.length - 1]}`
|
||||
|
||||
const hasBrowserAction = supportsComputerUse && availableBrowserTools.includes("browser_action")
|
||||
|
||||
export function getCapabilitiesSection(cwd: string, mcpHub?: McpHub): string {
|
||||
return `====
|
||||
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
|
||||
hasBrowserAction ? ", use the browser" : ""
|
||||
}, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('${cwd}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.${
|
||||
codeIndexManager &&
|
||||
codeIndexManager.isFeatureEnabled &&
|
||||
codeIndexManager.isFeatureConfigured &&
|
||||
codeIndexManager.isInitialized
|
||||
? `
|
||||
- You can use the \`codebase_search\` tool to perform semantic searches across your entire codebase. This tool is powerful for finding functionally relevant code, even if you don't know the exact keywords or file names. It's particularly useful for understanding how features are implemented across multiple files, discovering usages of a particular API, or finding code examples related to a concept. This capability relies on a pre-built index of your code.`
|
||||
: ""
|
||||
}
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.${
|
||||
editingToolsExample.length > 0
|
||||
? `
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use ${editingToolsText} tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.`
|
||||
: ""
|
||||
}
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('${cwd}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
|
||||
hasBrowserAction
|
||||
? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser."
|
||||
: ""
|
||||
}${
|
||||
mcpHub
|
||||
? `
|
||||
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,4 @@
|
|||
import { CodeIndexManager } from "../../../services/code-index/manager"
|
||||
|
||||
export function getObjectiveSection(
|
||||
codeIndexManager?: CodeIndexManager,
|
||||
experimentsConfig?: Record<string, boolean>,
|
||||
): string {
|
||||
const isCodebaseSearchAvailable =
|
||||
codeIndexManager &&
|
||||
codeIndexManager.isFeatureEnabled &&
|
||||
codeIndexManager.isFeatureConfigured &&
|
||||
codeIndexManager.isInitialized
|
||||
|
||||
const codebaseSearchInstruction = isCodebaseSearchAvailable
|
||||
? "First, for ANY exploration of code you haven't examined yet in this conversation, you MUST use the `codebase_search` tool to search for relevant code based on the task's intent BEFORE using any other search or file exploration tools. This applies throughout the entire task, not just at the beginning - whenever you need to explore a new area of code, codebase_search must come first. Then, "
|
||||
: "First, "
|
||||
|
||||
export function getObjectiveSection(): string {
|
||||
return `====
|
||||
|
||||
OBJECTIVE
|
||||
|
|
@ -22,7 +7,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
|
|||
|
||||
1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
|
||||
2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
|
||||
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis. ${codebaseSearchInstruction}analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Next, think about which of the provided tools is the most relevant tool to accomplish the user's task. Go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
|
||||
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Next, think about which of the provided tools is the most relevant tool to accomplish the user's task. Go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
|
||||
4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user.
|
||||
5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +1,5 @@
|
|||
import { DiffStrategy } from "../../../shared/tools"
|
||||
import { CodeIndexManager } from "../../../services/code-index/manager"
|
||||
import type { SystemPromptSettings } from "../types"
|
||||
import { getEffectiveProtocol, isNativeProtocol } from "@roo-code/types"
|
||||
import type { ModeConfig, ToolName } from "@roo-code/types"
|
||||
import { getAvailableToolsInGroup } from "../tools/filter-tools-for-mode"
|
||||
|
||||
function getEditingInstructions(
|
||||
mode: string,
|
||||
customModes: ModeConfig[] | undefined,
|
||||
experiments: Record<string, boolean> | undefined,
|
||||
codeIndexManager: CodeIndexManager | undefined,
|
||||
settings: SystemPromptSettings | undefined,
|
||||
diffStrategy?: DiffStrategy,
|
||||
): string {
|
||||
// Get available editing tools from the edit group
|
||||
const availableEditTools = getAvailableToolsInGroup(
|
||||
"edit",
|
||||
mode,
|
||||
customModes,
|
||||
experiments,
|
||||
codeIndexManager,
|
||||
settings,
|
||||
)
|
||||
|
||||
// Filter for the main editing tools we care about
|
||||
const hasApplyDiff = diffStrategy && availableEditTools.includes("apply_diff" as ToolName)
|
||||
const hasWriteToFile = availableEditTools.includes("write_to_file" as ToolName)
|
||||
|
||||
// If no editing tools are available, return empty string
|
||||
if (availableEditTools.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const instructions: string[] = []
|
||||
const availableTools: string[] = []
|
||||
|
||||
// Collect available editing tools
|
||||
if (hasApplyDiff) {
|
||||
availableTools.push("apply_diff (for surgical edits - targeted changes to specific lines or functions)")
|
||||
}
|
||||
if (hasWriteToFile) {
|
||||
availableTools.push("write_to_file (for creating new files or complete file rewrites)")
|
||||
}
|
||||
|
||||
// Base editing instruction mentioning all available tools
|
||||
if (availableTools.length > 0) {
|
||||
instructions.push(`- For editing files, you have access to these tools: ${availableTools.join(", ")}.`)
|
||||
}
|
||||
|
||||
// Preference instruction if multiple tools are available
|
||||
if (availableTools.length > 1 && hasWriteToFile) {
|
||||
instructions.push(
|
||||
"- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files.",
|
||||
)
|
||||
}
|
||||
|
||||
// Write to file instructions
|
||||
if (hasWriteToFile) {
|
||||
instructions.push(
|
||||
"- When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project.",
|
||||
)
|
||||
}
|
||||
|
||||
return instructions.join("\n")
|
||||
}
|
||||
|
||||
function getVendorConfidentialitySection(): string {
|
||||
return `
|
||||
|
|
@ -80,59 +16,7 @@ When asked about your creator, vendor, or company, respond with:
|
|||
- "I don't have information about specific vendors"`
|
||||
}
|
||||
|
||||
export function getRulesSection(
|
||||
cwd: string,
|
||||
supportsComputerUse: boolean,
|
||||
mode: string,
|
||||
customModes: ModeConfig[] | undefined,
|
||||
experiments: Record<string, boolean> | undefined,
|
||||
diffStrategy?: DiffStrategy,
|
||||
codeIndexManager?: CodeIndexManager,
|
||||
settings?: SystemPromptSettings,
|
||||
): string {
|
||||
const isCodebaseSearchAvailable =
|
||||
codeIndexManager &&
|
||||
codeIndexManager.isFeatureEnabled &&
|
||||
codeIndexManager.isFeatureConfigured &&
|
||||
codeIndexManager.isInitialized
|
||||
|
||||
const codebaseSearchRule = isCodebaseSearchAvailable
|
||||
? "- **CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use the `codebase_search` tool FIRST before using search_files or other file exploration tools.** This requirement applies throughout the entire conversation, not just when starting a task. The codebase_search tool uses semantic search to find relevant code based on meaning, not just keywords, making it much more effective for understanding how features are implemented. Even if you've already explored some parts of the codebase, any new area or functionality you need to understand requires using codebase_search first.\n"
|
||||
: ""
|
||||
|
||||
// Get available tools from relevant groups
|
||||
const availableEditTools = getAvailableToolsInGroup(
|
||||
"edit",
|
||||
mode,
|
||||
customModes,
|
||||
experiments,
|
||||
codeIndexManager,
|
||||
settings,
|
||||
)
|
||||
const availableBrowserTools = getAvailableToolsInGroup(
|
||||
"browser",
|
||||
mode,
|
||||
customModes,
|
||||
experiments,
|
||||
codeIndexManager,
|
||||
settings,
|
||||
)
|
||||
|
||||
// Check which editing tools are available for the search_files tool description
|
||||
const hasApplyDiff = diffStrategy && availableEditTools.includes("apply_diff" as ToolName)
|
||||
const hasWriteToFile = availableEditTools.includes("write_to_file" as ToolName)
|
||||
const hasBrowserAction = supportsComputerUse && availableBrowserTools.includes("browser_action" as ToolName)
|
||||
|
||||
// Build editing tools reference for search_files description
|
||||
let editingToolsRef = ""
|
||||
if (hasApplyDiff && hasWriteToFile) {
|
||||
editingToolsRef = "apply_diff or write_to_file"
|
||||
} else if (hasApplyDiff) {
|
||||
editingToolsRef = "apply_diff"
|
||||
} else if (hasWriteToFile) {
|
||||
editingToolsRef = "write_to_file"
|
||||
}
|
||||
|
||||
export function getRulesSection(cwd: string, settings?: SystemPromptSettings): string {
|
||||
// Determine whether to use XML tool references based on protocol
|
||||
const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol)
|
||||
|
||||
|
|
@ -145,19 +29,6 @@ RULES
|
|||
- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
|
||||
${codebaseSearchRule}${
|
||||
editingToolsRef
|
||||
? `- When using the search_files tool${isCodebaseSearchAvailable ? " (after codebase_search)" : ""}, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using ${editingToolsRef} to make informed changes.
|
||||
`
|
||||
: `- When using the search_files tool${isCodebaseSearchAvailable ? " (after codebase_search)" : ""}, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches.
|
||||
`
|
||||
}${
|
||||
hasWriteToFile
|
||||
? `- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${getEditingInstructions(mode, customModes, experiments, codeIndexManager, settings, diffStrategy)}
|
||||
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
|
||||
|
|
@ -166,20 +37,12 @@ ${getEditingInstructions(mode, customModes, experiments, codeIndexManager, setti
|
|||
- You are only allowed to ask the user questions using the ask_followup_question tool. Use this tool only when you need additional details to complete a task, and be sure to use a clear and concise question that will help you move forward with the task. When you ask a question, provide the user with 2-4 suggested answers based on your question so they don't need to do so much typing. The suggestions should be specific, actionable, and directly related to the completed task. They should be ordered by priority or logical sequence. However if you can use the available tools to avoid having to ask the user questions, you should do so. For example, if the user mentions a file that may be in an outside directory like the Desktop, you should use the list_files tool to list the files in the Desktop and check if the file they are talking about is there, rather than asking the user to provide the file path themselves.
|
||||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.
|
||||
- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${
|
||||
hasBrowserAction
|
||||
? '\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.'
|
||||
: ""
|
||||
}
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.
|
||||
- NEVER end attempt_completion result with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.
|
||||
- You are STRICTLY FORBIDDEN from starting your messages with "Great", "Certainly", "Okay", "Sure". You should NOT be conversational in your responses, but rather direct and to the point. For example you should NOT say "Great, I've updated the CSS" but instead something like "I've updated the CSS". It is important you be clear and technical in your messages.
|
||||
- When presented with images, utilize your vision capabilities to thoroughly examine them and extract meaningful information. Incorporate these insights into your thought process as you accomplish the user's task.
|
||||
- At the end of each user message, you will automatically receive environment_details. This information is not written by the user themselves, but is auto-generated to provide potentially relevant context about the project structure and environment. While this information can be valuable for understanding the project context, do not treat it as a direct part of the user's request or response. Use it to inform your actions and decisions, but don't assume the user is explicitly asking about or referring to this information unless they clearly do so in their message. When using environment_details, explain your actions clearly to ensure the user understands, as they may not be aware of these details.
|
||||
- Before executing commands, check the "Actively Running Terminals" section in environment_details. If present, consider how these active processes might impact your task. For example, if a local development server is already running, you wouldn't need to start it again. If no active terminals are listed, proceed with command execution as normal.
|
||||
- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations.
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${
|
||||
hasBrowserAction
|
||||
? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser."
|
||||
: ""
|
||||
}${settings?.isStealthModel ? getVendorConfidentialitySection() : ""}`
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${settings?.isStealthModel ? getVendorConfidentialitySection() : ""}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
import { ToolProtocol, TOOL_PROTOCOL } from "@roo-code/types"
|
||||
import { CodeIndexManager } from "../../../services/code-index/manager"
|
||||
import { isNativeProtocol } from "@roo-code/types"
|
||||
|
||||
export function getToolUseGuidelinesSection(
|
||||
codeIndexManager?: CodeIndexManager,
|
||||
protocol: ToolProtocol = TOOL_PROTOCOL.XML,
|
||||
): string {
|
||||
const isCodebaseSearchAvailable =
|
||||
codeIndexManager &&
|
||||
codeIndexManager.isFeatureEnabled &&
|
||||
codeIndexManager.isFeatureConfigured &&
|
||||
codeIndexManager.isInitialized
|
||||
|
||||
export function getToolUseGuidelinesSection(protocol: ToolProtocol = TOOL_PROTOCOL.XML): string {
|
||||
// Build guidelines array with automatic numbering
|
||||
let itemNumber = 1
|
||||
const guidelinesList: string[] = []
|
||||
|
|
@ -21,19 +11,9 @@ export function getToolUseGuidelinesSection(
|
|||
`${itemNumber++}. Assess what information you already have and what information you need to proceed with the task.`,
|
||||
)
|
||||
|
||||
// Conditional codebase search guideline
|
||||
if (isCodebaseSearchAvailable) {
|
||||
guidelinesList.push(
|
||||
`${itemNumber++}. **CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use the \`codebase_search\` tool FIRST before any other search or file exploration tools.** This applies throughout the entire conversation, not just at the beginning. The codebase_search tool uses semantic search to find relevant code based on meaning rather than just keywords, making it far more effective than regex-based search_files for understanding implementations. Even if you've already explored some code, any new area of exploration requires codebase_search first.`,
|
||||
)
|
||||
guidelinesList.push(
|
||||
`${itemNumber++}. Choose the most appropriate tool based on the task and the tool descriptions provided. After using codebase_search for initial exploration of any new code area, you may then use more specific tools like search_files (for regex patterns), list_files, or read_file for detailed examination. For example, using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task.`,
|
||||
)
|
||||
} else {
|
||||
guidelinesList.push(
|
||||
`${itemNumber++}. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task.`,
|
||||
)
|
||||
}
|
||||
guidelinesList.push(
|
||||
`${itemNumber++}. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task.`,
|
||||
)
|
||||
|
||||
// Remaining guidelines - different for native vs XML protocol
|
||||
if (isNativeProtocol(protocol)) {
|
||||
|
|
|
|||
|
|
@ -122,19 +122,19 @@ ${markdownFormattingSection()}
|
|||
|
||||
${getSharedToolUseSection(effectiveProtocol)}${toolsCatalog}
|
||||
|
||||
${getToolUseGuidelinesSection(codeIndexManager, effectiveProtocol)}
|
||||
${getToolUseGuidelinesSection(effectiveProtocol)}
|
||||
|
||||
${mcpServersSection}
|
||||
|
||||
${getCapabilitiesSection(cwd, supportsComputerUse, mode, customModeConfigs, experiments, shouldIncludeMcp ? mcpHub : undefined, effectiveDiffStrategy, codeIndexManager, settings)}
|
||||
${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}
|
||||
|
||||
${modesSection}
|
||||
|
||||
${getRulesSection(cwd, supportsComputerUse, mode, customModeConfigs, experiments, effectiveDiffStrategy, codeIndexManager, settings)}
|
||||
${getRulesSection(cwd, settings)}
|
||||
|
||||
${getSystemInfoSection(cwd)}
|
||||
|
||||
${getObjectiveSection(codeIndexManager, experiments)}
|
||||
${getObjectiveSection()}
|
||||
|
||||
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, {
|
||||
language: language ?? formatLanguage(vscode.env.language),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ export function getBrowserActionDescription(args: ToolArgs): string | undefined
|
|||
return `## browser_action
|
||||
Description: Request to interact with a Puppeteer-controlled browser. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action.
|
||||
|
||||
This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. Use it at key stages of web development tasks - such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. Analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.
|
||||
|
||||
The user may ask generic non-development tasks (such as "what's the latest news" or "look up the weather"), in which case you might use this tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.
|
||||
|
||||
**Browser Session Lifecycle:**
|
||||
- Browser sessions **start** with \`launch\` and **end** with \`close\`
|
||||
- The session remains active across multiple messages and tool uses
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export function getCodebaseSearchDescription(args: ToolArgs): string {
|
|||
return `## codebase_search
|
||||
Description: Find files most relevant to the search query using semantic search. Searches based on meaning rather than exact text matches. By default searches entire workspace. Reuse the user's exact wording unless there's a clear reason not to - their phrasing often helps semantic search. Queries MUST be in English (translate if needed).
|
||||
|
||||
**CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use this tool FIRST before any other search or file exploration tools.** This applies throughout the entire conversation, not just at the beginning. This tool uses semantic search to find relevant code based on meaning rather than just keywords, making it far more effective than regex-based search_files for understanding implementations. Even if you've already explored some code, any new area of exploration requires codebase_search first.
|
||||
|
||||
Parameters:
|
||||
- query: (required) The search query. Reuse the user's exact wording/question format unless there's a clear reason not to.
|
||||
- path: (optional) Limit search to specific subdirectory (relative to the current workspace directory ${args.cwd}). Leave empty for entire workspace.
|
||||
|
|
@ -14,10 +16,16 @@ Usage:
|
|||
<path>Optional subdirectory path</path>
|
||||
</codebase_search>
|
||||
|
||||
Example:
|
||||
Example: Searching for user authentication code
|
||||
<codebase_search>
|
||||
<query>User login and password hashing</query>
|
||||
<path>src/auth</path>
|
||||
</codebase_search>
|
||||
|
||||
Example: Searching entire workspace
|
||||
<codebase_search>
|
||||
<query>database connection pooling</query>
|
||||
<path></path>
|
||||
</codebase_search>
|
||||
`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import type OpenAI from "openai"
|
|||
|
||||
const BROWSER_ACTION_DESCRIPTION = `Request to interact with a Puppeteer-controlled browser. Every action, except close, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action.
|
||||
|
||||
This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. Use it at key stages of web development tasks - such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. Analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.
|
||||
|
||||
The user may ask generic non-development tasks (such as "what's the latest news" or "look up the weather"), in which case you might use this tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.
|
||||
|
||||
Browser Session Lifecycle:
|
||||
- Browser sessions start with launch and end with close
|
||||
- The session remains active across multiple messages and tool uses
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue