mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Merge branch 'main' into will/mode-plumbing - Resolved conflicts by combining mode plumbing with approve/deny functionality
This commit is contained in:
commit
de40233890
133 changed files with 3590 additions and 635 deletions
|
|
@ -3,3 +3,4 @@ POSTHOG_API_KEY=key-goes-here
|
|||
# Roo Code Cloud / Local Development
|
||||
CLERK_BASE_URL=https://epic-chamois-85.clerk.accounts.dev
|
||||
ROO_CODE_API_URL=http://localhost:3000
|
||||
ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy/v1
|
||||
|
|
|
|||
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -25,6 +25,7 @@ body:
|
|||
- AWS Bedrock
|
||||
- Chutes AI
|
||||
- DeepSeek
|
||||
- Featherless AI
|
||||
- Fireworks AI
|
||||
- Glama
|
||||
- Google Gemini
|
||||
|
|
|
|||
36
CHANGELOG.md
36
CHANGELOG.md
|
|
@ -1,5 +1,41 @@
|
|||
# Roo Code Changelog
|
||||
|
||||
## [3.25.20] - 2025-08-19
|
||||
|
||||
- Add announcement for Sonic model
|
||||
|
||||
## [3.25.19] - 2025-08-19
|
||||
|
||||
- Fix issue where new users couldn't select the Roo Code Cloud provider (thanks @daniel-lxs!)
|
||||
|
||||
## [3.25.18] - 2025-08-19
|
||||
|
||||
- Add new stealth Sonic model through the Roo Code Cloud provider
|
||||
- Fix: respect enableReasoningEffort setting when determining reasoning usage (#7048 by @ikbencasdoei, PR by @app/roomote)
|
||||
- Fix: prevent duplicate LM Studio models with case-insensitive deduplication (#6954 by @fbuechler, PR by @daniel-lxs)
|
||||
- Feat: simplify ask_followup_question prompt documentation (thanks @daniel-lxs!)
|
||||
- Feat: simple read_file tool for single-file-only models (thanks @daniel-lxs!)
|
||||
- Fix: Add missing zaiApiKey and doubaoApiKey to SECRET_STATE_KEYS (#7082 by @app/roomote)
|
||||
- Feat: Add new models and update configurations for vscode-lm (thanks @NaccOll!)
|
||||
|
||||
## [3.25.17] - 2025-08-17
|
||||
|
||||
- Fix: Resolve terminal reuse logic issues
|
||||
|
||||
## [3.25.16] - 2025-08-16
|
||||
|
||||
- Add support for OpenAI gpt-5-chat-latest model (#7057 by @PeterDaveHello, PR by @app/roomote)
|
||||
- Fix: Use native Ollama API instead of OpenAI compatibility layer (#7070 by @LivioGama, PR by @daniel-lxs)
|
||||
- Fix: Prevent XML entity decoding in diff tools (#7107 by @indiesewell, PR by @app/roomote)
|
||||
- Fix: Add type check before calling .match() on diffItem.content (#6905 by @pwilkin, PR by @app/roomote)
|
||||
- Refactor task execution system: improve call stack management (thanks @catrielmuller!)
|
||||
- Fix: Enable save button for provider dropdown and checkbox changes (thanks @daniel-lxs!)
|
||||
- Add an API for resuming tasks by ID (thanks @mrubens!)
|
||||
- Emit event when a task ask requires interaction (thanks @cte!)
|
||||
- Make enhance with task history default to true (thanks @liwilliam2021!)
|
||||
- Fix: Use cline.cwd as primary source for workspace path in codebaseSearchTool (thanks @NaccOll!)
|
||||
- Hotfix multiple folder workspace checkpoint (thanks @NaccOll!)
|
||||
|
||||
## [3.25.15] - 2025-08-14
|
||||
|
||||
- Fix: Remove 500-message limit to prevent scrollbar jumping in long conversations (#7052, #7063 by @daniel-lxs, PR by @app/roomote)
|
||||
|
|
|
|||
90
packages/ipc/README.md
Normal file
90
packages/ipc/README.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# IPC (Inter-Process Communication)
|
||||
|
||||
This package provides IPC functionality for Roo Code, allowing external applications to communicate with the extension through a socket-based interface.
|
||||
|
||||
## Available Commands
|
||||
|
||||
The IPC interface supports the following task commands:
|
||||
|
||||
### StartNewTask
|
||||
|
||||
Starts a new task with optional configuration and initial message.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `configuration`: RooCode settings object
|
||||
- `text`: Initial task message (string)
|
||||
- `images`: Array of image data URIs (optional)
|
||||
- `newTab`: Whether to open in a new tab (boolean, optional)
|
||||
|
||||
### CancelTask
|
||||
|
||||
Cancels a running task.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `data`: Task ID to cancel (string)
|
||||
|
||||
### CloseTask
|
||||
|
||||
Closes a task and performs cleanup.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `data`: Task ID to close (string)
|
||||
|
||||
### ResumeTask
|
||||
|
||||
Resumes a task from history.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `data`: Task ID to resume (string)
|
||||
|
||||
**Error Handling:**
|
||||
|
||||
- If the task ID is not found in history, the command will fail gracefully without crashing the IPC server
|
||||
- Errors are logged for debugging purposes but do not propagate to the client
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { IpcClient } from "@roo-code/ipc"
|
||||
|
||||
const client = new IpcClient("/path/to/socket")
|
||||
|
||||
// Resume a task
|
||||
client.sendCommand({
|
||||
commandName: "ResumeTask",
|
||||
data: "task-123",
|
||||
})
|
||||
|
||||
// Start a new task
|
||||
client.sendCommand({
|
||||
commandName: "StartNewTask",
|
||||
data: {
|
||||
configuration: {
|
||||
/* RooCode settings */
|
||||
},
|
||||
text: "Hello, world!",
|
||||
images: [],
|
||||
newTab: false,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
The IPC interface also emits task events that clients can listen to:
|
||||
|
||||
- `TaskStarted`: When a task begins
|
||||
- `TaskCompleted`: When a task finishes
|
||||
- `TaskAborted`: When a task is cancelled
|
||||
- `Message`: When a task sends a message
|
||||
|
||||
## Socket Path
|
||||
|
||||
The socket path is typically located in the system's temporary directory and follows the pattern:
|
||||
|
||||
- Unix/Linux/macOS: `/tmp/roo-code-{id}.sock`
|
||||
- Windows: `\\.\pipe\roo-code-{id}`
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@roo-code/types",
|
||||
"version": "1.49.0",
|
||||
"version": "1.59.0",
|
||||
"description": "TypeScript type definitions for Roo Code.",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
|
|
|||
75
packages/types/src/__tests__/ipc.test.ts
Normal file
75
packages/types/src/__tests__/ipc.test.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { describe, it, expect } from "vitest"
|
||||
import { TaskCommandName, taskCommandSchema } from "../ipc.js"
|
||||
|
||||
describe("IPC Types", () => {
|
||||
describe("TaskCommandName", () => {
|
||||
it("should include ResumeTask command", () => {
|
||||
expect(TaskCommandName.ResumeTask).toBe("ResumeTask")
|
||||
})
|
||||
|
||||
it("should have all expected task commands", () => {
|
||||
const expectedCommands = ["StartNewTask", "CancelTask", "CloseTask", "ResumeTask"]
|
||||
const actualCommands = Object.values(TaskCommandName)
|
||||
|
||||
expectedCommands.forEach((command) => {
|
||||
expect(actualCommands).toContain(command)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Error Handling", () => {
|
||||
it("should handle ResumeTask command gracefully when task not found", () => {
|
||||
// This test verifies the schema validation - the actual error handling
|
||||
// for invalid task IDs is tested at the API level, not the schema level
|
||||
const resumeTaskCommand = {
|
||||
commandName: TaskCommandName.ResumeTask,
|
||||
data: "non-existent-task-id",
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(resumeTaskCommand)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.commandName).toBe("ResumeTask")
|
||||
expect(result.data.data).toBe("non-existent-task-id")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("taskCommandSchema", () => {
|
||||
it("should validate ResumeTask command with taskId", () => {
|
||||
const resumeTaskCommand = {
|
||||
commandName: TaskCommandName.ResumeTask,
|
||||
data: "task-123",
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(resumeTaskCommand)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.commandName).toBe("ResumeTask")
|
||||
expect(result.data.data).toBe("task-123")
|
||||
}
|
||||
})
|
||||
|
||||
it("should reject ResumeTask command with invalid data", () => {
|
||||
const invalidCommand = {
|
||||
commandName: TaskCommandName.ResumeTask,
|
||||
data: 123, // Should be string
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(invalidCommand)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject ResumeTask command without data", () => {
|
||||
const invalidCommand = {
|
||||
commandName: TaskCommandName.ResumeTask,
|
||||
// Missing data field
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(invalidCommand)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -18,6 +18,8 @@ export enum RooCodeEventName {
|
|||
TaskFocused = "taskFocused",
|
||||
TaskUnfocused = "taskUnfocused",
|
||||
TaskActive = "taskActive",
|
||||
TaskInteractive = "taskInteractive",
|
||||
TaskResumable = "taskResumable",
|
||||
TaskIdle = "taskIdle",
|
||||
|
||||
// Subtask Lifecycle
|
||||
|
|
@ -59,6 +61,8 @@ export const rooCodeEventsSchema = z.object({
|
|||
[RooCodeEventName.TaskFocused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskUnfocused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskActive]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskInteractive]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskResumable]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskIdle]: z.tuple([z.string()]),
|
||||
|
||||
[RooCodeEventName.TaskPaused]: z.tuple([z.string()]),
|
||||
|
|
@ -124,6 +128,16 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [
|
|||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskActive],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskInteractive),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskInteractive],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskResumable),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskResumable],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskIdle),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskIdle],
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ export const SECRET_STATE_KEYS = [
|
|||
"openAiNativeApiKey",
|
||||
"cerebrasApiKey",
|
||||
"deepSeekApiKey",
|
||||
"doubaoApiKey",
|
||||
"moonshotApiKey",
|
||||
"mistralApiKey",
|
||||
"unboundApiKey",
|
||||
|
|
@ -193,7 +194,9 @@ export const SECRET_STATE_KEYS = [
|
|||
"codebaseIndexMistralApiKey",
|
||||
"huggingFaceApiKey",
|
||||
"sambaNovaApiKey",
|
||||
"zaiApiKey",
|
||||
"fireworksApiKey",
|
||||
"featherlessApiKey",
|
||||
"ioIntelligenceApiKey",
|
||||
] as const satisfies readonly (keyof ProviderSettings)[]
|
||||
export type SecretState = Pick<ProviderSettings, (typeof SECRET_STATE_KEYS)[number]>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export * from "./message.js"
|
|||
export * from "./mode.js"
|
||||
export * from "./model.js"
|
||||
export * from "./provider-settings.js"
|
||||
export * from "./single-file-read-models.js"
|
||||
export * from "./task.js"
|
||||
export * from "./todo.js"
|
||||
export * from "./telemetry.js"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export enum TaskCommandName {
|
|||
StartNewTask = "StartNewTask",
|
||||
CancelTask = "CancelTask",
|
||||
CloseTask = "CloseTask",
|
||||
ResumeTask = "ResumeTask",
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,6 +69,10 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
|
|||
commandName: z.literal(TaskCommandName.CloseTask),
|
||||
data: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
commandName: z.literal(TaskCommandName.ResumeTask),
|
||||
data: z.string(),
|
||||
}),
|
||||
])
|
||||
|
||||
export type TaskCommand = z.infer<typeof taskCommandSchema>
|
||||
|
|
|
|||
|
|
@ -44,24 +44,61 @@ export const clineAskSchema = z.enum(clineAsks)
|
|||
|
||||
export type ClineAsk = z.infer<typeof clineAskSchema>
|
||||
|
||||
// Needs classification:
|
||||
// - `followup`
|
||||
// - `command_output
|
||||
|
||||
/**
|
||||
* BlockingAsk
|
||||
* IdleAsk
|
||||
*
|
||||
* Asks that put the task into an "idle" state.
|
||||
*/
|
||||
|
||||
export const blockingAsks: ClineAsk[] = [
|
||||
"api_req_failed",
|
||||
"mistake_limit_reached",
|
||||
export const idleAsks = [
|
||||
"completion_result",
|
||||
"resume_task",
|
||||
"api_req_failed",
|
||||
"resume_completed_task",
|
||||
"command_output",
|
||||
"mistake_limit_reached",
|
||||
"auto_approval_max_req_reached",
|
||||
] as const
|
||||
] as const satisfies readonly ClineAsk[]
|
||||
|
||||
export type BlockingAsk = (typeof blockingAsks)[number]
|
||||
export type IdleAsk = (typeof idleAsks)[number]
|
||||
|
||||
export function isBlockingAsk(ask: ClineAsk): ask is BlockingAsk {
|
||||
return blockingAsks.includes(ask)
|
||||
export function isIdleAsk(ask: ClineAsk): ask is IdleAsk {
|
||||
return (idleAsks as readonly ClineAsk[]).includes(ask)
|
||||
}
|
||||
|
||||
/**
|
||||
* ResumableAsk
|
||||
*
|
||||
* Asks that put the task into an "resumable" state.
|
||||
*/
|
||||
|
||||
export const resumableAsks = ["resume_task"] as const satisfies readonly ClineAsk[]
|
||||
|
||||
export type ResumableAsk = (typeof resumableAsks)[number]
|
||||
|
||||
export function isResumableAsk(ask: ClineAsk): ask is ResumableAsk {
|
||||
return (resumableAsks as readonly ClineAsk[]).includes(ask)
|
||||
}
|
||||
|
||||
/**
|
||||
* InteractiveAsk
|
||||
*
|
||||
* Asks that put the task into an "user interaction required" state.
|
||||
*/
|
||||
|
||||
export const interactiveAsks = [
|
||||
"command",
|
||||
"tool",
|
||||
"browser_action_launch",
|
||||
"use_mcp_server",
|
||||
] as const satisfies readonly ClineAsk[]
|
||||
|
||||
export type InteractiveAsk = (typeof interactiveAsks)[number]
|
||||
|
||||
export function isInteractiveAsk(ask: ClineAsk): ask is InteractiveAsk {
|
||||
return (interactiveAsks as readonly ClineAsk[]).includes(ask)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ export const reasoningEffortsSchema = z.enum(reasoningEfforts)
|
|||
|
||||
export type ReasoningEffort = z.infer<typeof reasoningEffortsSchema>
|
||||
|
||||
/**
|
||||
* ReasoningEffortWithMinimal
|
||||
*/
|
||||
|
||||
export const reasoningEffortWithMinimalSchema = z.union([reasoningEffortsSchema, z.literal("minimal")])
|
||||
|
||||
export type ReasoningEffortWithMinimal = z.infer<typeof reasoningEffortWithMinimalSchema>
|
||||
|
||||
/**
|
||||
* Verbosity
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,15 +1,30 @@
|
|||
import { z } from "zod"
|
||||
|
||||
import { reasoningEffortsSchema, verbosityLevelsSchema, modelInfoSchema } from "./model.js"
|
||||
import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema } from "./model.js"
|
||||
import { codebaseIndexProviderSchema } from "./codebase-index.js"
|
||||
|
||||
// Bedrock Claude Sonnet 4 model ID that supports 1M context
|
||||
export const BEDROCK_CLAUDE_SONNET_4_MODEL_ID = "anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
|
||||
// Extended schema that includes "minimal" for GPT-5 models
|
||||
export const extendedReasoningEffortsSchema = z.union([reasoningEffortsSchema, z.literal("minimal")])
|
||||
|
||||
export type ReasoningEffortWithMinimal = z.infer<typeof extendedReasoningEffortsSchema>
|
||||
import {
|
||||
anthropicModels,
|
||||
bedrockModels,
|
||||
cerebrasModels,
|
||||
chutesModels,
|
||||
claudeCodeModels,
|
||||
deepSeekModels,
|
||||
doubaoModels,
|
||||
featherlessModels,
|
||||
fireworksModels,
|
||||
geminiModels,
|
||||
groqModels,
|
||||
ioIntelligenceModels,
|
||||
mistralModels,
|
||||
moonshotModels,
|
||||
openAiNativeModels,
|
||||
rooModels,
|
||||
sambaNovaModels,
|
||||
vertexModels,
|
||||
vscodeLlmModels,
|
||||
xaiModels,
|
||||
internationalZAiModels,
|
||||
} from "./providers/index.js"
|
||||
|
||||
/**
|
||||
* ProviderName
|
||||
|
|
@ -46,7 +61,9 @@ export const providerNames = [
|
|||
"sambanova",
|
||||
"zai",
|
||||
"fireworks",
|
||||
"featherless",
|
||||
"io-intelligence",
|
||||
"roo",
|
||||
] as const
|
||||
|
||||
export const providerNamesSchema = z.enum(providerNames)
|
||||
|
|
@ -85,7 +102,7 @@ const baseProviderSettingsSchema = z.object({
|
|||
|
||||
// Model reasoning.
|
||||
enableReasoningEffort: z.boolean().optional(),
|
||||
reasoningEffort: extendedReasoningEffortsSchema.optional(),
|
||||
reasoningEffort: reasoningEffortWithMinimalSchema.optional(),
|
||||
modelMaxTokens: z.number().optional(),
|
||||
modelMaxThinkingTokens: z.number().optional(),
|
||||
|
||||
|
|
@ -283,11 +300,19 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({
|
|||
fireworksApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const featherlessSchema = apiModelIdProviderModelSchema.extend({
|
||||
featherlessApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({
|
||||
ioIntelligenceModelId: z.string().optional(),
|
||||
ioIntelligenceApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const rooSchema = apiModelIdProviderModelSchema.extend({
|
||||
// No additional fields needed - uses cloud authentication
|
||||
})
|
||||
|
||||
const defaultSchema = z.object({
|
||||
apiProvider: z.undefined(),
|
||||
})
|
||||
|
|
@ -323,7 +348,9 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
|
|||
sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
|
||||
zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
|
||||
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
|
||||
featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })),
|
||||
ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })),
|
||||
rooSchema.merge(z.object({ apiProvider: z.literal("roo") })),
|
||||
defaultSchema,
|
||||
])
|
||||
|
||||
|
|
@ -359,7 +386,9 @@ export const providerSettingsSchema = z.object({
|
|||
...sambaNovaSchema.shape,
|
||||
...zaiSchema.shape,
|
||||
...fireworksSchema.shape,
|
||||
...featherlessSchema.shape,
|
||||
...ioIntelligenceSchema.shape,
|
||||
...rooSchema.shape,
|
||||
...codebaseIndexProviderSchema.shape,
|
||||
})
|
||||
|
||||
|
|
@ -393,21 +422,126 @@ export const getModelId = (settings: ProviderSettings): string | undefined => {
|
|||
return modelIdKey ? (settings[modelIdKey] as string) : undefined
|
||||
}
|
||||
|
||||
// Providers that use Anthropic-style API protocol
|
||||
// Providers that use Anthropic-style API protocol.
|
||||
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock"]
|
||||
|
||||
// Helper function to determine API protocol for a provider and model
|
||||
export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => {
|
||||
// First check if the provider is an Anthropic-style provider
|
||||
if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) {
|
||||
return "anthropic"
|
||||
}
|
||||
|
||||
// For vertex provider, check if the model ID contains "claude" (case-insensitive)
|
||||
if (provider && provider === "vertex" && modelId && modelId.toLowerCase().includes("claude")) {
|
||||
return "anthropic"
|
||||
}
|
||||
|
||||
// Default to OpenAI protocol
|
||||
return "openai"
|
||||
}
|
||||
|
||||
export const MODELS_BY_PROVIDER: Record<
|
||||
Exclude<ProviderName, "fake-ai" | "human-relay" | "gemini-cli" | "lmstudio" | "openai" | "ollama">,
|
||||
{ id: ProviderName; label: string; models: string[] }
|
||||
> = {
|
||||
anthropic: {
|
||||
id: "anthropic",
|
||||
label: "Anthropic",
|
||||
models: Object.keys(anthropicModels),
|
||||
},
|
||||
bedrock: {
|
||||
id: "bedrock",
|
||||
label: "Amazon Bedrock",
|
||||
models: Object.keys(bedrockModels),
|
||||
},
|
||||
cerebras: {
|
||||
id: "cerebras",
|
||||
label: "Cerebras",
|
||||
models: Object.keys(cerebrasModels),
|
||||
},
|
||||
chutes: {
|
||||
id: "chutes",
|
||||
label: "Chutes AI",
|
||||
models: Object.keys(chutesModels),
|
||||
},
|
||||
"claude-code": { id: "claude-code", label: "Claude Code", models: Object.keys(claudeCodeModels) },
|
||||
deepseek: {
|
||||
id: "deepseek",
|
||||
label: "DeepSeek",
|
||||
models: Object.keys(deepSeekModels),
|
||||
},
|
||||
doubao: { id: "doubao", label: "Doubao", models: Object.keys(doubaoModels) },
|
||||
featherless: {
|
||||
id: "featherless",
|
||||
label: "Featherless",
|
||||
models: Object.keys(featherlessModels),
|
||||
},
|
||||
fireworks: {
|
||||
id: "fireworks",
|
||||
label: "Fireworks",
|
||||
models: Object.keys(fireworksModels),
|
||||
},
|
||||
gemini: {
|
||||
id: "gemini",
|
||||
label: "Google Gemini",
|
||||
models: Object.keys(geminiModels),
|
||||
},
|
||||
groq: { id: "groq", label: "Groq", models: Object.keys(groqModels) },
|
||||
"io-intelligence": {
|
||||
id: "io-intelligence",
|
||||
label: "IO Intelligence",
|
||||
models: Object.keys(ioIntelligenceModels),
|
||||
},
|
||||
mistral: {
|
||||
id: "mistral",
|
||||
label: "Mistral",
|
||||
models: Object.keys(mistralModels),
|
||||
},
|
||||
moonshot: {
|
||||
id: "moonshot",
|
||||
label: "Moonshot",
|
||||
models: Object.keys(moonshotModels),
|
||||
},
|
||||
"openai-native": {
|
||||
id: "openai-native",
|
||||
label: "OpenAI",
|
||||
models: Object.keys(openAiNativeModels),
|
||||
},
|
||||
roo: { id: "roo", label: "Roo", models: Object.keys(rooModels) },
|
||||
sambanova: {
|
||||
id: "sambanova",
|
||||
label: "SambaNova",
|
||||
models: Object.keys(sambaNovaModels),
|
||||
},
|
||||
vertex: {
|
||||
id: "vertex",
|
||||
label: "GCP Vertex AI",
|
||||
models: Object.keys(vertexModels),
|
||||
},
|
||||
"vscode-lm": {
|
||||
id: "vscode-lm",
|
||||
label: "VS Code LM API",
|
||||
models: Object.keys(vscodeLlmModels),
|
||||
},
|
||||
xai: { id: "xai", label: "xAI (Grok)", models: Object.keys(xaiModels) },
|
||||
zai: { id: "zai", label: "Zai", models: Object.keys(internationalZAiModels) },
|
||||
|
||||
// Dynamic providers; models pulled from the respective APIs.
|
||||
glama: { id: "glama", label: "Glama", models: [] },
|
||||
huggingface: { id: "huggingface", label: "Hugging Face", models: [] },
|
||||
litellm: { id: "litellm", label: "LiteLLM", models: [] },
|
||||
openrouter: { id: "openrouter", label: "OpenRouter", models: [] },
|
||||
requesty: { id: "requesty", label: "Requesty", models: [] },
|
||||
unbound: { id: "unbound", label: "Unbound", models: [] },
|
||||
}
|
||||
|
||||
export const dynamicProviders = [
|
||||
"glama",
|
||||
"huggingface",
|
||||
"litellm",
|
||||
"openrouter",
|
||||
"requesty",
|
||||
"unbound",
|
||||
] as const satisfies readonly ProviderName[]
|
||||
|
||||
export type DynamicProvider = (typeof dynamicProviders)[number]
|
||||
|
||||
export const isDynamicProvider = (key: string): key is DynamicProvider =>
|
||||
dynamicProviders.includes(key as DynamicProvider)
|
||||
|
|
|
|||
|
|
@ -441,3 +441,5 @@ export const BEDROCK_REGIONS = [
|
|||
{ value: "us-gov-east-1", label: "us-gov-east-1" },
|
||||
{ value: "us-gov-west-1", label: "us-gov-west-1" },
|
||||
].sort((a, b) => a.value.localeCompare(b.value))
|
||||
|
||||
export const BEDROCK_CLAUDE_SONNET_4_MODEL_ID = "anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
|
|
|
|||
58
packages/types/src/providers/featherless.ts
Normal file
58
packages/types/src/providers/featherless.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
|
||||
export type FeatherlessModelId =
|
||||
| "deepseek-ai/DeepSeek-V3-0324"
|
||||
| "deepseek-ai/DeepSeek-R1-0528"
|
||||
| "moonshotai/Kimi-K2-Instruct"
|
||||
| "openai/gpt-oss-120b"
|
||||
| "Qwen/Qwen3-Coder-480B-A35B-Instruct"
|
||||
|
||||
export const featherlessModels = {
|
||||
"deepseek-ai/DeepSeek-V3-0324": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 32678,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "DeepSeek V3 0324 model.",
|
||||
},
|
||||
"deepseek-ai/DeepSeek-R1-0528": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 32678,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "DeepSeek R1 0528 model.",
|
||||
},
|
||||
"moonshotai/Kimi-K2-Instruct": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 32678,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Kimi K2 Instruct model.",
|
||||
},
|
||||
"openai/gpt-oss-120b": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 32678,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "GPT-OSS 120B model.",
|
||||
},
|
||||
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 32678,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Qwen3 Coder 480B A35B Instruct model.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
export const featherlessDefaultModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
|
||||
|
|
@ -4,6 +4,9 @@ export * from "./cerebras.js"
|
|||
export * from "./chutes.js"
|
||||
export * from "./claude-code.js"
|
||||
export * from "./deepseek.js"
|
||||
export * from "./doubao.js"
|
||||
export * from "./featherless.js"
|
||||
export * from "./fireworks.js"
|
||||
export * from "./gemini.js"
|
||||
export * from "./glama.js"
|
||||
export * from "./groq.js"
|
||||
|
|
@ -17,11 +20,10 @@ export * from "./ollama.js"
|
|||
export * from "./openai.js"
|
||||
export * from "./openrouter.js"
|
||||
export * from "./requesty.js"
|
||||
export * from "./roo.js"
|
||||
export * from "./sambanova.js"
|
||||
export * from "./unbound.js"
|
||||
export * from "./vertex.js"
|
||||
export * from "./vscode-llm.js"
|
||||
export * from "./xai.js"
|
||||
export * from "./doubao.js"
|
||||
export * from "./zai.js"
|
||||
export * from "./fireworks.js"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,18 @@ export type OpenAiNativeModelId = keyof typeof openAiNativeModels
|
|||
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5-2025-08-07"
|
||||
|
||||
export const openAiNativeModels = {
|
||||
"gpt-5-chat-latest": {
|
||||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: false,
|
||||
inputPrice: 1.25,
|
||||
outputPrice: 10.0,
|
||||
cacheReadsPrice: 0.13,
|
||||
description: "GPT-5 Chat Latest: Optimized for conversational AI and non-reasoning tasks",
|
||||
supportsVerbosity: true,
|
||||
},
|
||||
"gpt-5-2025-08-07": {
|
||||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
|
|
|
|||
19
packages/types/src/providers/roo.ts
Normal file
19
packages/types/src/providers/roo.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
|
||||
// Roo provider with single model
|
||||
export type RooModelId = "roo/sonic"
|
||||
|
||||
export const rooDefaultModelId: RooModelId = "roo/sonic"
|
||||
|
||||
export const rooModels = {
|
||||
"roo/sonic": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description:
|
||||
"A stealth reasoning model that is blazing fast and excels at agentic coding, accessible for free through Roo Code Cloud for a limited time. (Note: prompts and completions are logged by the model creator and used to improve the model.)",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
|
@ -4,6 +4,7 @@ export type VscodeLlmModelId = keyof typeof vscodeLlmModels
|
|||
|
||||
export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-3.5-sonnet"
|
||||
|
||||
// https://docs.cline.bot/provider-config/vscode-language-model-api
|
||||
export const vscodeLlmModels = {
|
||||
"gpt-3.5-turbo": {
|
||||
contextWindow: 12114,
|
||||
|
|
@ -101,6 +102,18 @@ export const vscodeLlmModels = {
|
|||
supportsToolCalling: true,
|
||||
maxInputTokens: 81638,
|
||||
},
|
||||
"claude-4-sonnet": {
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
family: "claude-sonnet-4",
|
||||
version: "claude-sonnet-4",
|
||||
name: "Claude Sonnet 4",
|
||||
supportsToolCalling: true,
|
||||
maxInputTokens: 111836,
|
||||
},
|
||||
"gemini-2.0-flash-001": {
|
||||
contextWindow: 127827,
|
||||
supportsImages: true,
|
||||
|
|
@ -114,7 +127,7 @@ export const vscodeLlmModels = {
|
|||
maxInputTokens: 127827,
|
||||
},
|
||||
"gemini-2.5-pro": {
|
||||
contextWindow: 63830,
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
|
|
@ -123,10 +136,10 @@ export const vscodeLlmModels = {
|
|||
version: "gemini-2.5-pro-preview-03-25",
|
||||
name: "Gemini 2.5 Pro (Preview)",
|
||||
supportsToolCalling: true,
|
||||
maxInputTokens: 63830,
|
||||
maxInputTokens: 108637,
|
||||
},
|
||||
"o4-mini": {
|
||||
contextWindow: 111446,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
|
|
@ -135,10 +148,10 @@ export const vscodeLlmModels = {
|
|||
version: "o4-mini-2025-04-16",
|
||||
name: "o4-mini (Preview)",
|
||||
supportsToolCalling: true,
|
||||
maxInputTokens: 111446,
|
||||
maxInputTokens: 111452,
|
||||
},
|
||||
"gpt-4.1": {
|
||||
contextWindow: 111446,
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
|
|
@ -147,7 +160,31 @@ export const vscodeLlmModels = {
|
|||
version: "gpt-4.1-2025-04-14",
|
||||
name: "GPT-4.1 (Preview)",
|
||||
supportsToolCalling: true,
|
||||
maxInputTokens: 111446,
|
||||
maxInputTokens: 111452,
|
||||
},
|
||||
"gpt-5-mini": {
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
family: "gpt-5-mini",
|
||||
version: "gpt-5-mini",
|
||||
name: "GPT-5 mini (Preview)",
|
||||
supportsToolCalling: true,
|
||||
maxInputTokens: 108637,
|
||||
},
|
||||
"gpt-5": {
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
family: "gpt-5",
|
||||
version: "gpt-5",
|
||||
name: "GPT-5 (Preview)",
|
||||
supportsToolCalling: true,
|
||||
maxInputTokens: 108637,
|
||||
},
|
||||
} as const satisfies Record<
|
||||
string,
|
||||
|
|
|
|||
32
packages/types/src/single-file-read-models.ts
Normal file
32
packages/types/src/single-file-read-models.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Configuration for models that should use simplified single-file read_file tool
|
||||
* These models will use the simpler <read_file><path>...</path></read_file> format
|
||||
* instead of the more complex multi-file args format
|
||||
*/
|
||||
|
||||
// List of model IDs (or patterns) that should use single file reads only
|
||||
export const SINGLE_FILE_READ_MODELS = new Set<string>(["roo/sonic"])
|
||||
|
||||
/**
|
||||
* Check if a model should use single file read format
|
||||
* @param modelId The model ID to check
|
||||
* @returns true if the model should use single file reads
|
||||
*/
|
||||
export function shouldUseSingleFileRead(modelId: string): boolean {
|
||||
// Direct match
|
||||
if (SINGLE_FILE_READ_MODELS.has(modelId)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Pattern matching for model families
|
||||
// Check if model ID starts with any configured pattern
|
||||
// Using Array.from for compatibility with older TypeScript targets
|
||||
const patterns = Array.from(SINGLE_FILE_READ_MODELS)
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.endsWith("*") && modelId.startsWith(pattern.slice(0, -1))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from "zod"
|
||||
|
||||
import { RooCodeEventName } from "./events.js"
|
||||
import { type ClineMessage, type BlockingAsk, type TokenUsage } from "./message.js"
|
||||
import { type ClineMessage, type TokenUsage } from "./message.js"
|
||||
import { type ToolUsage, type ToolName } from "./tool.js"
|
||||
import { type Experiments } from "./experiment.js"
|
||||
import type { StaticAppProperties, GitProperties, TelemetryProperties } from "./telemetry.js"
|
||||
|
|
@ -34,6 +34,7 @@ export interface TaskProviderLike {
|
|||
createTask(text?: string, images?: string[], parentTask?: TaskLike, options?: CreateTaskOptions): Promise<TaskLike>
|
||||
cancelTask(): Promise<void>
|
||||
clearTask(): Promise<void>
|
||||
resumeTask(taskId: string): void
|
||||
|
||||
getState(): Promise<TaskProviderState>
|
||||
postStateToWebview(): Promise<void>
|
||||
|
|
@ -62,6 +63,8 @@ export type TaskProviderEvents = {
|
|||
[RooCodeEventName.TaskFocused]: [taskId: string]
|
||||
[RooCodeEventName.TaskUnfocused]: [taskId: string]
|
||||
[RooCodeEventName.TaskActive]: [taskId: string]
|
||||
[RooCodeEventName.TaskInteractive]: [taskId: string]
|
||||
[RooCodeEventName.TaskResumable]: [taskId: string]
|
||||
[RooCodeEventName.TaskIdle]: [taskId: string]
|
||||
}
|
||||
|
||||
|
|
@ -69,8 +72,15 @@ export type TaskProviderEvents = {
|
|||
* TaskLike
|
||||
*/
|
||||
|
||||
export enum TaskStatus {
|
||||
Running = "running",
|
||||
Interactive = "interactive",
|
||||
Resumable = "resumable",
|
||||
Idle = "idle",
|
||||
None = "none",
|
||||
}
|
||||
|
||||
export const taskMetadataSchema = z.object({
|
||||
taskId: z.string(),
|
||||
task: z.string().optional(),
|
||||
images: z.array(z.string()).optional(),
|
||||
})
|
||||
|
|
@ -79,15 +89,20 @@ export type TaskMetadata = z.infer<typeof taskMetadataSchema>
|
|||
|
||||
export interface TaskLike {
|
||||
readonly taskId: string
|
||||
readonly rootTask?: TaskLike
|
||||
readonly blockingAsk?: BlockingAsk
|
||||
readonly taskStatus: TaskStatus
|
||||
readonly taskAsk: ClineMessage | undefined
|
||||
readonly metadata: TaskMetadata
|
||||
|
||||
readonly rootTask?: TaskLike
|
||||
|
||||
on<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
|
||||
off<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
|
||||
|
||||
setMessageResponse(text: string, images?: string[]): void
|
||||
approveAsk(options?: { text?: string; images?: string[] }): void
|
||||
denyAsk(options?: { text?: string; images?: string[] }): void
|
||||
submitUserMessage(text: string, images?: string[], modeSlug?: string): void
|
||||
abortTask(): void
|
||||
}
|
||||
|
||||
export type TaskEvents = {
|
||||
|
|
@ -98,6 +113,8 @@ export type TaskEvents = {
|
|||
[RooCodeEventName.TaskFocused]: []
|
||||
[RooCodeEventName.TaskUnfocused]: []
|
||||
[RooCodeEventName.TaskActive]: [taskId: string]
|
||||
[RooCodeEventName.TaskInteractive]: [taskId: string]
|
||||
[RooCodeEventName.TaskResumable]: [taskId: string]
|
||||
[RooCodeEventName.TaskIdle]: [taskId: string]
|
||||
|
||||
// Subtask Lifecycle
|
||||
|
|
|
|||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
|
|
@ -584,8 +584,8 @@ importers:
|
|||
specifier: ^1.14.0
|
||||
version: 1.14.0(typescript@5.8.3)
|
||||
'@roo-code/cloud':
|
||||
specifier: ^0.15.0
|
||||
version: 0.15.0
|
||||
specifier: ^0.19.0
|
||||
version: 0.19.0
|
||||
'@roo-code/ipc':
|
||||
specifier: workspace:^
|
||||
version: link:../packages/ipc
|
||||
|
|
@ -676,6 +676,9 @@ importers:
|
|||
node-ipc:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
ollama:
|
||||
specifier: ^0.5.17
|
||||
version: 0.5.17
|
||||
openai:
|
||||
specifier: ^5.0.0
|
||||
version: 5.5.1(ws@8.18.3)(zod@3.25.61)
|
||||
|
|
@ -3103,11 +3106,11 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@roo-code/cloud@0.15.0':
|
||||
resolution: {integrity: sha512-0DivOP5uUS9U6UKSxzoxZ4NxMCYUxA7wG72y3PBP91JhGzHaTqxi9WrvF61bo134dCnCktU9oTKIAD9AvFigzg==}
|
||||
'@roo-code/cloud@0.19.0':
|
||||
resolution: {integrity: sha512-alZ3X4+TPqRr0xSs9v/UDo3eTlcHaI8ZW8AbWPDtgqf86P8govnyM2hVUMhGXete3AlbYIPRE/9w3/7MrcIjsA==}
|
||||
|
||||
'@roo-code/types@1.49.0':
|
||||
resolution: {integrity: sha512-h7gbjfIxBN+fgFecQiZs3W+vjdUhZOvtjh4OqcpPGG1w8B1DlXPDV4L/+ARUeNzZxSOK5S5rNPy57jC35EaD/w==}
|
||||
'@roo-code/types@1.55.0':
|
||||
resolution: {integrity: sha512-+T5MP8IQcDp7htnGDnk3M4n7S5eYk6jNkw3VBSUBZRhS4EE2GuPDI+CcdmhnDiMb6NMV6yseL+CT4G4QV5ktUw==}
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
|
@ -7645,6 +7648,9 @@ packages:
|
|||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
ollama@0.5.17:
|
||||
resolution: {integrity: sha512-q5LmPtk6GLFouS+3aURIVl+qcAOPC4+Msmx7uBb3pd+fxI55WnGjmLZ0yijI/CYy79x0QPGx3BwC3u5zv9fBvQ==}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -9655,6 +9661,9 @@ packages:
|
|||
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
whatwg-fetch@3.6.20:
|
||||
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
|
||||
|
||||
whatwg-mimetype@4.0.0:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -12305,9 +12314,9 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc@4.40.2':
|
||||
optional: true
|
||||
|
||||
'@roo-code/cloud@0.15.0':
|
||||
'@roo-code/cloud@0.19.0':
|
||||
dependencies:
|
||||
'@roo-code/types': 1.49.0
|
||||
'@roo-code/types': 1.55.0
|
||||
ioredis: 5.6.1
|
||||
p-wait-for: 5.0.2
|
||||
socket.io-client: 4.8.1
|
||||
|
|
@ -12317,7 +12326,7 @@ snapshots:
|
|||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@roo-code/types@1.49.0':
|
||||
'@roo-code/types@1.55.0':
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
|
|
@ -14176,7 +14185,7 @@ snapshots:
|
|||
dependencies:
|
||||
devtools-protocol: 0.0.1452169
|
||||
mitt: 3.0.1
|
||||
zod: 3.25.61
|
||||
zod: 3.25.76
|
||||
|
||||
ci-info@2.0.0: {}
|
||||
|
||||
|
|
@ -17683,6 +17692,10 @@ snapshots:
|
|||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
ollama@0.5.17:
|
||||
dependencies:
|
||||
whatwg-fetch: 3.6.20
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
|
@ -20155,6 +20168,8 @@ snapshots:
|
|||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
whatwg-fetch@3.6.20: {}
|
||||
|
||||
whatwg-mimetype@4.0.0: {}
|
||||
|
||||
whatwg-url@14.2.0:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
VertexHandler,
|
||||
AnthropicVertexHandler,
|
||||
OpenAiHandler,
|
||||
OllamaHandler,
|
||||
LmStudioHandler,
|
||||
GeminiHandler,
|
||||
OpenAiNativeHandler,
|
||||
|
|
@ -36,7 +35,10 @@ import {
|
|||
DoubaoHandler,
|
||||
ZAiHandler,
|
||||
FireworksHandler,
|
||||
RooHandler,
|
||||
FeatherlessHandler,
|
||||
} from "./providers"
|
||||
import { NativeOllamaHandler } from "./providers/native-ollama"
|
||||
|
||||
export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
|
|
@ -95,7 +97,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
|
|||
case "openai":
|
||||
return new OpenAiHandler(options)
|
||||
case "ollama":
|
||||
return new OllamaHandler(options)
|
||||
return new NativeOllamaHandler(options)
|
||||
case "lmstudio":
|
||||
return new LmStudioHandler(options)
|
||||
case "gemini":
|
||||
|
|
@ -140,6 +142,10 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
|
|||
return new FireworksHandler(options)
|
||||
case "io-intelligence":
|
||||
return new IOIntelligenceHandler(options)
|
||||
case "roo":
|
||||
return new RooHandler(options)
|
||||
case "featherless":
|
||||
return new FeatherlessHandler(options)
|
||||
default:
|
||||
apiProvider satisfies "gemini-cli" | undefined
|
||||
return new AnthropicHandler(options)
|
||||
|
|
|
|||
286
src/api/providers/__tests__/featherless.spec.ts
Normal file
286
src/api/providers/__tests__/featherless.spec.ts
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
// npx vitest run api/providers/__tests__/featherless.spec.ts
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import {
|
||||
type FeatherlessModelId,
|
||||
featherlessDefaultModelId,
|
||||
featherlessModels,
|
||||
DEEP_SEEK_DEFAULT_TEMPERATURE,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { FeatherlessHandler } from "../featherless"
|
||||
|
||||
// Create mock functions
|
||||
const mockCreate = vi.fn()
|
||||
|
||||
// Mock OpenAI module
|
||||
vi.mock("openai", () => ({
|
||||
default: vi.fn(() => ({
|
||||
chat: {
|
||||
completions: {
|
||||
create: mockCreate,
|
||||
},
|
||||
},
|
||||
})),
|
||||
}))
|
||||
|
||||
describe("FeatherlessHandler", () => {
|
||||
let handler: FeatherlessHandler
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Set up default mock implementation
|
||||
mockCreate.mockImplementation(async () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: { content: "Test response" },
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 5,
|
||||
total_tokens: 15,
|
||||
},
|
||||
}
|
||||
},
|
||||
}))
|
||||
handler = new FeatherlessHandler({ featherlessApiKey: "test-key" })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("should use the correct Featherless base URL", () => {
|
||||
new FeatherlessHandler({ featherlessApiKey: "test-featherless-api-key" })
|
||||
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.featherless.ai/v1" }))
|
||||
})
|
||||
|
||||
it("should use the provided API key", () => {
|
||||
const featherlessApiKey = "test-featherless-api-key"
|
||||
new FeatherlessHandler({ featherlessApiKey })
|
||||
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: featherlessApiKey }))
|
||||
})
|
||||
|
||||
it("should handle DeepSeek R1 reasoning format", async () => {
|
||||
// Override the mock for this specific test
|
||||
mockCreate.mockImplementationOnce(async () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: { content: "<think>Thinking..." },
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: { content: "</think>Hello" },
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
|
||||
vi.spyOn(handler, "getModel").mockReturnValue({
|
||||
id: "deepseek-ai/DeepSeek-R1-0528",
|
||||
info: { maxTokens: 1024, temperature: 0.7 },
|
||||
} as any)
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const chunks = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{ type: "reasoning", text: "Thinking..." },
|
||||
{ type: "text", text: "Hello" },
|
||||
{ type: "usage", inputTokens: 10, outputTokens: 5 },
|
||||
])
|
||||
})
|
||||
|
||||
it("should fall back to base provider for non-DeepSeek models", async () => {
|
||||
// Use default mock implementation which returns text content
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
|
||||
vi.spyOn(handler, "getModel").mockReturnValue({
|
||||
id: "some-other-model",
|
||||
info: { maxTokens: 1024, temperature: 0.7 },
|
||||
} as any)
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const chunks = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{ type: "text", text: "Test response" },
|
||||
{ type: "usage", inputTokens: 10, outputTokens: 5 },
|
||||
])
|
||||
})
|
||||
|
||||
it("should return default model when no model is specified", () => {
|
||||
const model = handler.getModel()
|
||||
expect(model.id).toBe(featherlessDefaultModelId)
|
||||
expect(model.info).toEqual(expect.objectContaining(featherlessModels[featherlessDefaultModelId]))
|
||||
})
|
||||
|
||||
it("should return specified model when valid model is provided", () => {
|
||||
const testModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
|
||||
const handlerWithModel = new FeatherlessHandler({
|
||||
apiModelId: testModelId,
|
||||
featherlessApiKey: "test-featherless-api-key",
|
||||
})
|
||||
const model = handlerWithModel.getModel()
|
||||
expect(model.id).toBe(testModelId)
|
||||
expect(model.info).toEqual(expect.objectContaining(featherlessModels[testModelId]))
|
||||
})
|
||||
|
||||
it("completePrompt method should return text from Featherless API", async () => {
|
||||
const expectedResponse = "This is a test response from Featherless"
|
||||
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
|
||||
const result = await handler.completePrompt("test prompt")
|
||||
expect(result).toBe(expectedResponse)
|
||||
})
|
||||
|
||||
it("should handle errors in completePrompt", async () => {
|
||||
const errorMessage = "Featherless API error"
|
||||
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
|
||||
await expect(handler.completePrompt("test prompt")).rejects.toThrow(
|
||||
`Featherless completion error: ${errorMessage}`,
|
||||
)
|
||||
})
|
||||
|
||||
it("createMessage should yield text content from stream", async () => {
|
||||
const testContent = "This is test content from Featherless stream"
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: { choices: [{ delta: { content: testContent } }] },
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const stream = handler.createMessage("system prompt", [])
|
||||
const firstChunk = await stream.next()
|
||||
|
||||
expect(firstChunk.done).toBe(false)
|
||||
expect(firstChunk.value).toEqual({ type: "text", text: testContent })
|
||||
})
|
||||
|
||||
it("createMessage should yield usage data from stream", async () => {
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } },
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const stream = handler.createMessage("system prompt", [])
|
||||
const firstChunk = await stream.next()
|
||||
|
||||
expect(firstChunk.done).toBe(false)
|
||||
expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 })
|
||||
})
|
||||
|
||||
it("createMessage should pass correct parameters to Featherless client for DeepSeek R1", async () => {
|
||||
const modelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
|
||||
|
||||
// Clear previous mocks and set up new implementation
|
||||
mockCreate.mockClear()
|
||||
mockCreate.mockImplementationOnce(async () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
// Empty stream for this test
|
||||
},
|
||||
}))
|
||||
|
||||
const handlerWithModel = new FeatherlessHandler({
|
||||
apiModelId: modelId,
|
||||
featherlessApiKey: "test-featherless-api-key",
|
||||
})
|
||||
|
||||
const systemPrompt = "Test system prompt for Featherless"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Featherless" }]
|
||||
|
||||
const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages)
|
||||
await messageGenerator.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: modelId,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `${systemPrompt}\n${messages[0].content}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should apply DeepSeek default temperature for R1 models", () => {
|
||||
const testModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
|
||||
const handlerWithModel = new FeatherlessHandler({
|
||||
apiModelId: testModelId,
|
||||
featherlessApiKey: "test-featherless-api-key",
|
||||
})
|
||||
const model = handlerWithModel.getModel()
|
||||
expect(model.info.temperature).toBe(DEEP_SEEK_DEFAULT_TEMPERATURE)
|
||||
})
|
||||
|
||||
it("should use default temperature for non-DeepSeek models", () => {
|
||||
const testModelId: FeatherlessModelId = "moonshotai/Kimi-K2-Instruct"
|
||||
const handlerWithModel = new FeatherlessHandler({
|
||||
apiModelId: testModelId,
|
||||
featherlessApiKey: "test-featherless-api-key",
|
||||
})
|
||||
const model = handlerWithModel.getModel()
|
||||
expect(model.info.temperature).toBe(0.5)
|
||||
})
|
||||
})
|
||||
162
src/api/providers/__tests__/native-ollama.spec.ts
Normal file
162
src/api/providers/__tests__/native-ollama.spec.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// npx vitest run api/providers/__tests__/native-ollama.spec.ts
|
||||
|
||||
import { NativeOllamaHandler } from "../native-ollama"
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
|
||||
// Mock the ollama package
|
||||
const mockChat = vitest.fn()
|
||||
vitest.mock("ollama", () => {
|
||||
return {
|
||||
Ollama: vitest.fn().mockImplementation(() => ({
|
||||
chat: mockChat,
|
||||
})),
|
||||
Message: vitest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
// Mock the getOllamaModels function
|
||||
vitest.mock("../fetchers/ollama", () => ({
|
||||
getOllamaModels: vitest.fn().mockResolvedValue({
|
||||
llama2: {
|
||||
contextWindow: 4096,
|
||||
maxTokens: 4096,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
describe("NativeOllamaHandler", () => {
|
||||
let handler: NativeOllamaHandler
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
|
||||
const options: ApiHandlerOptions = {
|
||||
apiModelId: "llama2",
|
||||
ollamaModelId: "llama2",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
}
|
||||
|
||||
handler = new NativeOllamaHandler(options)
|
||||
})
|
||||
|
||||
describe("createMessage", () => {
|
||||
it("should stream messages from Ollama", async () => {
|
||||
// Mock the chat response as an async generator
|
||||
mockChat.mockImplementation(async function* () {
|
||||
yield {
|
||||
message: { content: "Hello" },
|
||||
eval_count: undefined,
|
||||
prompt_eval_count: undefined,
|
||||
}
|
||||
yield {
|
||||
message: { content: " world" },
|
||||
eval_count: 2,
|
||||
prompt_eval_count: 10,
|
||||
}
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hi there" }]
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const results = []
|
||||
|
||||
for await (const chunk of stream) {
|
||||
results.push(chunk)
|
||||
}
|
||||
|
||||
expect(results).toHaveLength(3)
|
||||
expect(results[0]).toEqual({ type: "text", text: "Hello" })
|
||||
expect(results[1]).toEqual({ type: "text", text: " world" })
|
||||
expect(results[2]).toEqual({ type: "usage", inputTokens: 10, outputTokens: 2 })
|
||||
})
|
||||
|
||||
it("should handle DeepSeek R1 models with reasoning detection", async () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
apiModelId: "deepseek-r1",
|
||||
ollamaModelId: "deepseek-r1",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
}
|
||||
|
||||
handler = new NativeOllamaHandler(options)
|
||||
|
||||
// Mock response with thinking tags
|
||||
mockChat.mockImplementation(async function* () {
|
||||
yield { message: { content: "<think>Let me think" } }
|
||||
yield { message: { content: " about this</think>" } }
|
||||
yield { message: { content: "The answer is 42" } }
|
||||
})
|
||||
|
||||
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Question?" }])
|
||||
const results = []
|
||||
|
||||
for await (const chunk of stream) {
|
||||
results.push(chunk)
|
||||
}
|
||||
|
||||
// Should detect reasoning vs regular text
|
||||
expect(results.some((r) => r.type === "reasoning")).toBe(true)
|
||||
expect(results.some((r) => r.type === "text")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
it("should complete a prompt without streaming", async () => {
|
||||
mockChat.mockResolvedValue({
|
||||
message: { content: "This is the response" },
|
||||
})
|
||||
|
||||
const result = await handler.completePrompt("Tell me a joke")
|
||||
|
||||
expect(mockChat).toHaveBeenCalledWith({
|
||||
model: "llama2",
|
||||
messages: [{ role: "user", content: "Tell me a joke" }],
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: 0,
|
||||
},
|
||||
})
|
||||
expect(result).toBe("This is the response")
|
||||
})
|
||||
})
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should handle connection refused errors", async () => {
|
||||
const error = new Error("ECONNREFUSED") as any
|
||||
error.code = "ECONNREFUSED"
|
||||
mockChat.mockRejectedValue(error)
|
||||
|
||||
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])
|
||||
|
||||
await expect(async () => {
|
||||
for await (const _ of stream) {
|
||||
// consume stream
|
||||
}
|
||||
}).rejects.toThrow("Ollama service is not running")
|
||||
})
|
||||
|
||||
it("should handle model not found errors", async () => {
|
||||
const error = new Error("Not found") as any
|
||||
error.status = 404
|
||||
mockChat.mockRejectedValue(error)
|
||||
|
||||
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])
|
||||
|
||||
await expect(async () => {
|
||||
for await (const _ of stream) {
|
||||
// consume stream
|
||||
}
|
||||
}).rejects.toThrow("Model llama2 not found in Ollama")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
it("should return the configured model", () => {
|
||||
const model = handler.getModel()
|
||||
expect(model.id).toBe("llama2")
|
||||
expect(model.info).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
436
src/api/providers/__tests__/roo.spec.ts
Normal file
436
src/api/providers/__tests__/roo.spec.ts
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
// npx vitest run api/providers/__tests__/roo.spec.ts
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { rooDefaultModelId, rooModels } from "@roo-code/types"
|
||||
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
|
||||
// Mock OpenAI client
|
||||
const mockCreate = vitest.fn()
|
||||
|
||||
vitest.mock("openai", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: vitest.fn().mockImplementation(() => ({
|
||||
chat: {
|
||||
completions: {
|
||||
create: mockCreate.mockImplementation(async (options) => {
|
||||
if (!options.stream) {
|
||||
return {
|
||||
id: "test-completion",
|
||||
choices: [
|
||||
{
|
||||
message: { role: "assistant", content: "Test response" },
|
||||
finish_reason: "stop",
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 5,
|
||||
total_tokens: 15,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: { content: "Test response" },
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 5,
|
||||
total_tokens: 15,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
},
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
// Mock CloudService - Define functions outside to avoid initialization issues
|
||||
const mockGetSessionToken = vitest.fn()
|
||||
const mockHasInstance = vitest.fn()
|
||||
|
||||
// Create mock functions that we can control
|
||||
const mockGetSessionTokenFn = vitest.fn()
|
||||
const mockHasInstanceFn = vitest.fn()
|
||||
|
||||
vitest.mock("@roo-code/cloud", () => ({
|
||||
CloudService: {
|
||||
hasInstance: () => mockHasInstanceFn(),
|
||||
get instance() {
|
||||
return {
|
||||
authService: {
|
||||
getSessionToken: () => mockGetSessionTokenFn(),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock i18n
|
||||
vitest.mock("../../../i18n", () => ({
|
||||
t: vitest.fn((key: string) => {
|
||||
if (key === "common:errors.roo.authenticationRequired") {
|
||||
return "Authentication required for Roo Code Cloud"
|
||||
}
|
||||
return key
|
||||
}),
|
||||
}))
|
||||
|
||||
// Import after mocks are set up
|
||||
import { RooHandler } from "../roo"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
import { t } from "../../../i18n"
|
||||
|
||||
describe("RooHandler", () => {
|
||||
let handler: RooHandler
|
||||
let mockOptions: ApiHandlerOptions
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(() => {
|
||||
mockOptions = {
|
||||
apiModelId: "roo/sonic",
|
||||
}
|
||||
// Set up CloudService mocks for successful authentication
|
||||
mockHasInstanceFn.mockReturnValue(true)
|
||||
mockGetSessionTokenFn.mockReturnValue("test-session-token")
|
||||
mockCreate.mockClear()
|
||||
vitest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
it("should initialize with valid session token", () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
expect(handler.getModel().id).toBe(mockOptions.apiModelId)
|
||||
})
|
||||
|
||||
it("should throw error if CloudService is not available", () => {
|
||||
mockHasInstanceFn.mockReturnValue(false)
|
||||
expect(() => {
|
||||
new RooHandler(mockOptions)
|
||||
}).toThrow("Authentication required for Roo Code Cloud")
|
||||
expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired")
|
||||
})
|
||||
|
||||
it("should throw error if session token is not available", () => {
|
||||
mockHasInstanceFn.mockReturnValue(true)
|
||||
mockGetSessionTokenFn.mockReturnValue(null)
|
||||
expect(() => {
|
||||
new RooHandler(mockOptions)
|
||||
}).toThrow("Authentication required for Roo Code Cloud")
|
||||
expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired")
|
||||
})
|
||||
|
||||
it("should initialize with default model if no model specified", () => {
|
||||
handler = new RooHandler({})
|
||||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
expect(handler.getModel().id).toBe(rooDefaultModelId)
|
||||
})
|
||||
|
||||
it("should pass correct configuration to base class", () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
// The handler should be initialized with correct base URL and API key
|
||||
// We can't directly test the parent class constructor, but we can verify the handler works
|
||||
expect(handler).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("createMessage", () => {
|
||||
beforeEach(() => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
})
|
||||
|
||||
it("should handle streaming responses", async () => {
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks.length).toBeGreaterThan(0)
|
||||
const textChunks = chunks.filter((chunk) => chunk.type === "text")
|
||||
expect(textChunks).toHaveLength(1)
|
||||
expect(textChunks[0].text).toBe("Test response")
|
||||
})
|
||||
|
||||
it("should include usage information", async () => {
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
const usageChunks = chunks.filter((chunk) => chunk.type === "usage")
|
||||
expect(usageChunks).toHaveLength(1)
|
||||
expect(usageChunks[0].inputTokens).toBe(10)
|
||||
expect(usageChunks[0].outputTokens).toBe(5)
|
||||
})
|
||||
|
||||
it("should handle API errors", async () => {
|
||||
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
await expect(async () => {
|
||||
for await (const _chunk of stream) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow("API Error")
|
||||
})
|
||||
|
||||
it("should handle empty response content", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: { content: null },
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 10,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
const textChunks = chunks.filter((chunk) => chunk.type === "text")
|
||||
expect(textChunks).toHaveLength(0)
|
||||
const usageChunks = chunks.filter((chunk) => chunk.type === "usage")
|
||||
expect(usageChunks).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("should handle multiple messages in conversation", async () => {
|
||||
const multipleMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "First message" },
|
||||
{ role: "assistant", content: "First response" },
|
||||
{ role: "user", content: "Second message" },
|
||||
]
|
||||
|
||||
const stream = handler.createMessage(systemPrompt, multipleMessages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({ role: "system", content: systemPrompt }),
|
||||
expect.objectContaining({ role: "user", content: "First message" }),
|
||||
expect.objectContaining({ role: "assistant", content: "First response" }),
|
||||
expect.objectContaining({ role: "user", content: "Second message" }),
|
||||
]),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
beforeEach(() => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
})
|
||||
|
||||
it("should complete prompt successfully", async () => {
|
||||
const result = await handler.completePrompt("Test prompt")
|
||||
expect(result).toBe("Test response")
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
model: mockOptions.apiModelId,
|
||||
messages: [{ role: "user", content: "Test prompt" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle API errors", async () => {
|
||||
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
||||
await expect(handler.completePrompt("Test prompt")).rejects.toThrow(
|
||||
"Roo Code Cloud completion error: API Error",
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle empty response", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [{ message: { content: "" } }],
|
||||
})
|
||||
const result = await handler.completePrompt("Test prompt")
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should handle missing response content", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [{ message: {} }],
|
||||
})
|
||||
const result = await handler.completePrompt("Test prompt")
|
||||
expect(result).toBe("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
beforeEach(() => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
})
|
||||
|
||||
it("should return model info for specified model", () => {
|
||||
const modelInfo = handler.getModel()
|
||||
expect(modelInfo.id).toBe(mockOptions.apiModelId)
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
// roo/sonic is a valid model in rooModels
|
||||
expect(modelInfo.info).toBe(rooModels["roo/sonic"])
|
||||
})
|
||||
|
||||
it("should return default model when no model specified", () => {
|
||||
const handlerWithoutModel = new RooHandler({})
|
||||
const modelInfo = handlerWithoutModel.getModel()
|
||||
expect(modelInfo.id).toBe(rooDefaultModelId)
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
expect(modelInfo.info).toBe(rooModels[rooDefaultModelId])
|
||||
})
|
||||
|
||||
it("should handle unknown model ID with fallback info", () => {
|
||||
const handlerWithUnknownModel = new RooHandler({
|
||||
apiModelId: "unknown-model-id",
|
||||
})
|
||||
const modelInfo = handlerWithUnknownModel.getModel()
|
||||
expect(modelInfo.id).toBe("unknown-model-id")
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
// Should return fallback info for unknown models
|
||||
expect(modelInfo.info.maxTokens).toBe(16_384)
|
||||
expect(modelInfo.info.contextWindow).toBe(262_144)
|
||||
expect(modelInfo.info.supportsImages).toBe(false)
|
||||
expect(modelInfo.info.supportsPromptCache).toBe(true)
|
||||
expect(modelInfo.info.inputPrice).toBe(0)
|
||||
expect(modelInfo.info.outputPrice).toBe(0)
|
||||
})
|
||||
|
||||
it("should return correct model info for all Roo models", () => {
|
||||
// Test each model in rooModels
|
||||
const modelIds = Object.keys(rooModels) as Array<keyof typeof rooModels>
|
||||
|
||||
for (const modelId of modelIds) {
|
||||
const handlerWithModel = new RooHandler({ apiModelId: modelId })
|
||||
const modelInfo = handlerWithModel.getModel()
|
||||
expect(modelInfo.id).toBe(modelId)
|
||||
expect(modelInfo.info).toBe(rooModels[modelId])
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("temperature and model configuration", () => {
|
||||
it("should use default temperature of 0.7", async () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
temperature: 0.7,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should respect custom temperature setting", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
modelTemperature: 0.9,
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
temperature: 0.9,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should use correct API endpoint", () => {
|
||||
// The base URL should be set to Roo's API endpoint
|
||||
// We can't directly test the OpenAI client configuration, but we can verify the handler initializes
|
||||
handler = new RooHandler(mockOptions)
|
||||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
// The handler should work with the Roo API endpoint
|
||||
})
|
||||
})
|
||||
|
||||
describe("authentication flow", () => {
|
||||
it("should use session token as API key", () => {
|
||||
const testToken = "test-session-token-123"
|
||||
mockGetSessionTokenFn.mockReturnValue(testToken)
|
||||
|
||||
handler = new RooHandler(mockOptions)
|
||||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
expect(mockGetSessionTokenFn).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle undefined auth service", () => {
|
||||
mockHasInstanceFn.mockReturnValue(true)
|
||||
// Mock CloudService with undefined authService
|
||||
const originalGetter = Object.getOwnPropertyDescriptor(CloudService, "instance")?.get
|
||||
|
||||
try {
|
||||
Object.defineProperty(CloudService, "instance", {
|
||||
get: () => ({ authService: undefined }),
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
expect(() => {
|
||||
new RooHandler(mockOptions)
|
||||
}).toThrow("Authentication required for Roo Code Cloud")
|
||||
} finally {
|
||||
// Always restore original getter, even if test fails
|
||||
if (originalGetter) {
|
||||
Object.defineProperty(CloudService, "instance", {
|
||||
get: originalGetter,
|
||||
configurable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle empty session token", () => {
|
||||
mockGetSessionTokenFn.mockReturnValue("")
|
||||
|
||||
expect(() => {
|
||||
new RooHandler(mockOptions)
|
||||
}).toThrow("Authentication required for Roo Code Cloud")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -62,11 +62,11 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
|
|||
})
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
protected createStream(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
) {
|
||||
const {
|
||||
id: model,
|
||||
info: { maxTokens: max_tokens },
|
||||
|
|
@ -83,7 +83,15 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
|
|||
stream_options: { include_usage: true },
|
||||
}
|
||||
|
||||
const stream = await this.client.chat.completions.create(params)
|
||||
return this.client.chat.completions.create(params)
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const stream = await this.createStream(systemPrompt, messages, metadata)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
|
|
|||
108
src/api/providers/featherless.ts
Normal file
108
src/api/providers/featherless.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import {
|
||||
DEEP_SEEK_DEFAULT_TEMPERATURE,
|
||||
type FeatherlessModelId,
|
||||
featherlessDefaultModelId,
|
||||
featherlessModels,
|
||||
} from "@roo-code/types"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { XmlMatcher } from "../../utils/xml-matcher"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
|
||||
export class FeatherlessHandler extends BaseOpenAiCompatibleProvider<FeatherlessModelId> {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super({
|
||||
...options,
|
||||
providerName: "Featherless",
|
||||
baseURL: "https://api.featherless.ai/v1",
|
||||
apiKey: options.featherlessApiKey,
|
||||
defaultProviderModelId: featherlessDefaultModelId,
|
||||
providerModels: featherlessModels,
|
||||
defaultTemperature: 0.5,
|
||||
})
|
||||
}
|
||||
|
||||
private getCompletionParams(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
): OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming {
|
||||
const {
|
||||
id: model,
|
||||
info: { maxTokens: max_tokens },
|
||||
} = this.getModel()
|
||||
|
||||
const temperature = this.options.modelTemperature ?? this.getModel().info.temperature
|
||||
|
||||
return {
|
||||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
}
|
||||
}
|
||||
|
||||
override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
|
||||
if (model.id.includes("DeepSeek-R1")) {
|
||||
const stream = await this.client.chat.completions.create({
|
||||
...this.getCompletionParams(systemPrompt, messages),
|
||||
messages: convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]),
|
||||
})
|
||||
|
||||
const matcher = new XmlMatcher(
|
||||
"think",
|
||||
(chunk) =>
|
||||
({
|
||||
type: chunk.matched ? "reasoning" : "text",
|
||||
text: chunk.data,
|
||||
}) as const,
|
||||
)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
for (const processedChunk of matcher.update(delta.content)) {
|
||||
yield processedChunk
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process any remaining content
|
||||
for (const processedChunk of matcher.final()) {
|
||||
yield processedChunk
|
||||
}
|
||||
} else {
|
||||
yield* super.createMessage(systemPrompt, messages)
|
||||
}
|
||||
}
|
||||
|
||||
override getModel() {
|
||||
const model = super.getModel()
|
||||
const isDeepSeekR1 = model.id.includes("DeepSeek-R1")
|
||||
return {
|
||||
...model,
|
||||
info: {
|
||||
...model.info,
|
||||
temperature: isDeepSeekR1 ? DEEP_SEEK_DEFAULT_TEMPERATURE : this.defaultTemperature,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,6 +143,228 @@ describe("LMStudio Fetcher", () => {
|
|||
expect(result).toEqual({ [mockRawModel.modelKey]: expectedParsedModel })
|
||||
})
|
||||
|
||||
it("should deduplicate models when both downloaded and loaded", async () => {
|
||||
const mockDownloadedModel: LLMInfo = {
|
||||
type: "llm" as const,
|
||||
modelKey: "mistralai/devstral-small-2505",
|
||||
format: "safetensors",
|
||||
displayName: "Devstral Small 2505",
|
||||
path: "mistralai/devstral-small-2505",
|
||||
sizeBytes: 13277565112,
|
||||
architecture: "mistral",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 131072,
|
||||
}
|
||||
|
||||
const mockLoadedModel: LLMInstanceInfo = {
|
||||
type: "llm",
|
||||
modelKey: "devstral-small-2505", // Different key but should match case-insensitively
|
||||
format: "safetensors",
|
||||
displayName: "Devstral Small 2505",
|
||||
path: "mistralai/devstral-small-2505",
|
||||
sizeBytes: 13277565112,
|
||||
architecture: "mistral",
|
||||
identifier: "mistralai/devstral-small-2505",
|
||||
instanceReference: "RAP5qbeHVjJgBiGFQ6STCuTJ",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 131072,
|
||||
contextLength: 7161, // Runtime context info
|
||||
}
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } })
|
||||
mockListDownloadedModels.mockResolvedValueOnce([mockDownloadedModel])
|
||||
mockListLoaded.mockResolvedValueOnce([{ getModelInfo: vi.fn().mockResolvedValueOnce(mockLoadedModel) }])
|
||||
|
||||
const result = await getLMStudioModels(baseUrl)
|
||||
|
||||
// Should only have one model, with the loaded model replacing the downloaded one
|
||||
expect(Object.keys(result)).toHaveLength(1)
|
||||
|
||||
// The loaded model's key should be used, with loaded model's data
|
||||
const expectedParsedModel = parseLMStudioModel(mockLoadedModel)
|
||||
expect(result[mockLoadedModel.modelKey]).toEqual(expectedParsedModel)
|
||||
|
||||
// The downloaded model should have been removed
|
||||
expect(result[mockDownloadedModel.path]).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle deduplication with path-based matching", async () => {
|
||||
const mockDownloadedModel: LLMInfo = {
|
||||
type: "llm" as const,
|
||||
modelKey: "Meta/Llama-3.1/8B-Instruct",
|
||||
format: "gguf",
|
||||
displayName: "Llama 3.1 8B Instruct",
|
||||
path: "Meta/Llama-3.1/8B-Instruct",
|
||||
sizeBytes: 8000000000,
|
||||
architecture: "llama",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 8192,
|
||||
}
|
||||
|
||||
const mockLoadedModel: LLMInstanceInfo = {
|
||||
type: "llm",
|
||||
modelKey: "Llama-3.1", // Should match the path segment
|
||||
format: "gguf",
|
||||
displayName: "Llama 3.1",
|
||||
path: "Meta/Llama-3.1/8B-Instruct",
|
||||
sizeBytes: 8000000000,
|
||||
architecture: "llama",
|
||||
identifier: "Meta/Llama-3.1/8B-Instruct",
|
||||
instanceReference: "ABC123",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 8192,
|
||||
contextLength: 4096,
|
||||
}
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } })
|
||||
mockListDownloadedModels.mockResolvedValueOnce([mockDownloadedModel])
|
||||
mockListLoaded.mockResolvedValueOnce([{ getModelInfo: vi.fn().mockResolvedValueOnce(mockLoadedModel) }])
|
||||
|
||||
const result = await getLMStudioModels(baseUrl)
|
||||
|
||||
expect(Object.keys(result)).toHaveLength(1)
|
||||
expect(result[mockLoadedModel.modelKey]).toBeDefined()
|
||||
expect(result[mockDownloadedModel.path]).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should not deduplicate models with similar but distinct names", async () => {
|
||||
const mockDownloadedModels: LLMInfo[] = [
|
||||
{
|
||||
type: "llm" as const,
|
||||
modelKey: "mistral-7b",
|
||||
format: "gguf",
|
||||
displayName: "Mistral 7B",
|
||||
path: "mistralai/mistral-7b-instruct",
|
||||
sizeBytes: 7000000000,
|
||||
architecture: "mistral",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 4096,
|
||||
},
|
||||
{
|
||||
type: "llm" as const,
|
||||
modelKey: "codellama",
|
||||
format: "gguf",
|
||||
displayName: "Code Llama",
|
||||
path: "meta/codellama/7b",
|
||||
sizeBytes: 7000000000,
|
||||
architecture: "llama",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 4096,
|
||||
},
|
||||
]
|
||||
|
||||
const mockLoadedModel: LLMInstanceInfo = {
|
||||
type: "llm",
|
||||
modelKey: "llama", // Should not match "codellama" or "mistral-7b"
|
||||
format: "gguf",
|
||||
displayName: "Llama",
|
||||
path: "meta/llama/7b",
|
||||
sizeBytes: 7000000000,
|
||||
architecture: "llama",
|
||||
identifier: "meta/llama/7b",
|
||||
instanceReference: "XYZ789",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 4096,
|
||||
contextLength: 2048,
|
||||
}
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } })
|
||||
mockListDownloadedModels.mockResolvedValueOnce(mockDownloadedModels)
|
||||
mockListLoaded.mockResolvedValueOnce([{ getModelInfo: vi.fn().mockResolvedValueOnce(mockLoadedModel) }])
|
||||
|
||||
const result = await getLMStudioModels(baseUrl)
|
||||
|
||||
// Should have 3 models: mistral-7b (not deduped), codellama (not deduped), and llama (loaded)
|
||||
expect(Object.keys(result)).toHaveLength(3)
|
||||
expect(result["mistralai/mistral-7b-instruct"]).toBeDefined() // Should NOT be removed
|
||||
expect(result["meta/codellama/7b"]).toBeDefined() // Should NOT be removed (codellama != llama)
|
||||
expect(result[mockLoadedModel.modelKey]).toBeDefined()
|
||||
})
|
||||
|
||||
it("should handle multiple loaded models with various duplicate scenarios", async () => {
|
||||
const mockDownloadedModels: LLMInfo[] = [
|
||||
{
|
||||
type: "llm" as const,
|
||||
modelKey: "mistral-7b",
|
||||
format: "gguf",
|
||||
displayName: "Mistral 7B",
|
||||
path: "mistralai/mistral-7b/instruct",
|
||||
sizeBytes: 7000000000,
|
||||
architecture: "mistral",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 8192,
|
||||
},
|
||||
{
|
||||
type: "llm" as const,
|
||||
modelKey: "llama-3.1",
|
||||
format: "gguf",
|
||||
displayName: "Llama 3.1",
|
||||
path: "meta/llama-3.1/8b",
|
||||
sizeBytes: 8000000000,
|
||||
architecture: "llama",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 8192,
|
||||
},
|
||||
]
|
||||
|
||||
const mockLoadedModels: LLMInstanceInfo[] = [
|
||||
{
|
||||
type: "llm",
|
||||
modelKey: "mistral-7b", // Exact match with path segment
|
||||
format: "gguf",
|
||||
displayName: "Mistral 7B",
|
||||
path: "mistralai/mistral-7b/instruct",
|
||||
sizeBytes: 7000000000,
|
||||
architecture: "mistral",
|
||||
identifier: "mistralai/mistral-7b/instruct",
|
||||
instanceReference: "REF1",
|
||||
vision: false,
|
||||
trainedForToolUse: false,
|
||||
maxContextLength: 8192,
|
||||
contextLength: 4096,
|
||||
},
|
||||
{
|
||||
type: "llm",
|
||||
modelKey: "gpt-4", // No match, new model
|
||||
format: "gguf",
|
||||
displayName: "GPT-4",
|
||||
path: "openai/gpt-4",
|
||||
sizeBytes: 10000000000,
|
||||
architecture: "gpt",
|
||||
identifier: "openai/gpt-4",
|
||||
instanceReference: "REF2",
|
||||
vision: true,
|
||||
trainedForToolUse: true,
|
||||
maxContextLength: 32768,
|
||||
contextLength: 16384,
|
||||
},
|
||||
]
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } })
|
||||
mockListDownloadedModels.mockResolvedValueOnce(mockDownloadedModels)
|
||||
mockListLoaded.mockResolvedValueOnce(
|
||||
mockLoadedModels.map((model) => ({ getModelInfo: vi.fn().mockResolvedValueOnce(model) })),
|
||||
)
|
||||
|
||||
const result = await getLMStudioModels(baseUrl)
|
||||
|
||||
// Should have 3 models: llama-3.1 (downloaded), mistral-7b (loaded, replaced), gpt-4 (loaded, new)
|
||||
expect(Object.keys(result)).toHaveLength(3)
|
||||
expect(result["meta/llama-3.1/8b"]).toBeDefined() // Downloaded, not replaced
|
||||
expect(result["mistralai/mistral-7b/instruct"]).toBeUndefined() // Downloaded, replaced
|
||||
expect(result["mistral-7b"]).toBeDefined() // Loaded, replaced downloaded
|
||||
expect(result["gpt-4"]).toBeDefined() // Loaded, new
|
||||
})
|
||||
|
||||
it("should use default baseUrl if an empty string is provided", async () => {
|
||||
const defaultBaseUrl = "http://localhost:1234"
|
||||
const defaultLmsUrl = "ws://localhost:1234"
|
||||
|
|
|
|||
|
|
@ -81,12 +81,38 @@ export async function getLMStudioModels(baseUrl = "http://localhost:1234"): Prom
|
|||
} catch (error) {
|
||||
console.warn("Failed to list downloaded models, falling back to loaded models only")
|
||||
}
|
||||
// We want to list loaded models *anyway* since they provide valuable extra info (context size)
|
||||
|
||||
// Get loaded models for their runtime info (context size)
|
||||
const loadedModels = (await client.llm.listLoaded().then((models: LLM[]) => {
|
||||
return Promise.all(models.map((m) => m.getModelInfo()))
|
||||
})) as Array<LLMInstanceInfo>
|
||||
|
||||
// Deduplicate: For each loaded model, check if any downloaded model path contains the loaded model's key
|
||||
// This handles cases like loaded "llama-3.1" matching downloaded "Meta/Llama-3.1/Something"
|
||||
// If found, remove the downloaded version and add the loaded model (prefer loaded over downloaded for accurate runtime info)
|
||||
for (const lmstudioModel of loadedModels) {
|
||||
const loadedModelId = lmstudioModel.modelKey.toLowerCase()
|
||||
|
||||
// Find if any downloaded model path contains the loaded model's key as a path segment
|
||||
// Use word boundaries or path separators to avoid false matches like "llama" matching "codellama"
|
||||
const existingKey = Object.keys(models).find((key) => {
|
||||
const keyLower = key.toLowerCase()
|
||||
// Check if the loaded model ID appears as a distinct segment in the path
|
||||
// This matches "llama-3.1" in "Meta/Llama-3.1/Something" but not "llama" in "codellama"
|
||||
return (
|
||||
keyLower.includes(`/${loadedModelId}/`) ||
|
||||
keyLower.includes(`/${loadedModelId}`) ||
|
||||
keyLower.startsWith(`${loadedModelId}/`) ||
|
||||
keyLower === loadedModelId
|
||||
)
|
||||
})
|
||||
|
||||
if (existingKey) {
|
||||
// Remove the downloaded version
|
||||
delete models[existingKey]
|
||||
}
|
||||
|
||||
// Add the loaded model (either as replacement or new entry)
|
||||
models[lmstudioModel.modelKey] = parseLMStudioModel(lmstudioModel)
|
||||
modelsWithLoadedDetails.add(lmstudioModel.modelKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,3 +29,5 @@ export { VsCodeLmHandler } from "./vscode-lm"
|
|||
export { XAIHandler } from "./xai"
|
||||
export { ZAiHandler } from "./zai"
|
||||
export { FireworksHandler } from "./fireworks"
|
||||
export { RooHandler } from "./roo"
|
||||
export { FeatherlessHandler } from "./featherless"
|
||||
|
|
|
|||
285
src/api/providers/native-ollama.ts
Normal file
285
src/api/providers/native-ollama.ts
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Message, Ollama } from "ollama"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults, DEEP_SEEK_DEFAULT_TEMPERATURE } from "@roo-code/types"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { getOllamaModels } from "./fetchers/ollama"
|
||||
import { XmlMatcher } from "../../utils/xml-matcher"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
|
||||
function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
const ollamaMessages: Message[] = []
|
||||
|
||||
for (const anthropicMessage of anthropicMessages) {
|
||||
if (typeof anthropicMessage.content === "string") {
|
||||
ollamaMessages.push({
|
||||
role: anthropicMessage.role,
|
||||
content: anthropicMessage.content,
|
||||
})
|
||||
} else {
|
||||
if (anthropicMessage.role === "user") {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolResultBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_result") {
|
||||
acc.toolMessages.push(part)
|
||||
} else if (part.type === "text" || part.type === "image") {
|
||||
acc.nonToolMessages.push(part)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ nonToolMessages: [], toolMessages: [] },
|
||||
)
|
||||
|
||||
// Process tool result messages FIRST since they must follow the tool use messages
|
||||
const toolResultImages: string[] = []
|
||||
toolMessages.forEach((toolMessage) => {
|
||||
// The Anthropic SDK allows tool results to be a string or an array of text and image blocks, enabling rich and structured content. In contrast, the Ollama SDK only supports tool results as a single string, so we map the Anthropic tool result parts into one concatenated string to maintain compatibility.
|
||||
let content: string
|
||||
|
||||
if (typeof toolMessage.content === "string") {
|
||||
content = toolMessage.content
|
||||
} else {
|
||||
content =
|
||||
toolMessage.content
|
||||
?.map((part) => {
|
||||
if (part.type === "image") {
|
||||
// Handle base64 images only (Anthropic SDK uses base64)
|
||||
// Ollama expects raw base64 strings, not data URLs
|
||||
if ("source" in part && part.source.type === "base64") {
|
||||
toolResultImages.push(part.source.data)
|
||||
}
|
||||
return "(see following user message for image)"
|
||||
}
|
||||
return part.text
|
||||
})
|
||||
.join("\n") ?? ""
|
||||
}
|
||||
ollamaMessages.push({
|
||||
role: "user",
|
||||
images: toolResultImages.length > 0 ? toolResultImages : undefined,
|
||||
content: content,
|
||||
})
|
||||
})
|
||||
|
||||
// Process non-tool messages
|
||||
if (nonToolMessages.length > 0) {
|
||||
// Separate text and images for Ollama
|
||||
const textContent = nonToolMessages
|
||||
.filter((part) => part.type === "text")
|
||||
.map((part) => part.text)
|
||||
.join("\n")
|
||||
|
||||
const imageData: string[] = []
|
||||
nonToolMessages.forEach((part) => {
|
||||
if (part.type === "image" && "source" in part && part.source.type === "base64") {
|
||||
// Ollama expects raw base64 strings, not data URLs
|
||||
imageData.push(part.source.data)
|
||||
}
|
||||
})
|
||||
|
||||
ollamaMessages.push({
|
||||
role: "user",
|
||||
content: textContent,
|
||||
images: imageData.length > 0 ? imageData : undefined,
|
||||
})
|
||||
}
|
||||
} else if (anthropicMessage.role === "assistant") {
|
||||
const { nonToolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolUseBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_use") {
|
||||
acc.toolMessages.push(part)
|
||||
} else if (part.type === "text" || part.type === "image") {
|
||||
acc.nonToolMessages.push(part)
|
||||
} // assistant cannot send tool_result messages
|
||||
return acc
|
||||
},
|
||||
{ nonToolMessages: [], toolMessages: [] },
|
||||
)
|
||||
|
||||
// Process non-tool messages
|
||||
let content: string = ""
|
||||
if (nonToolMessages.length > 0) {
|
||||
content = nonToolMessages
|
||||
.map((part) => {
|
||||
if (part.type === "image") {
|
||||
return "" // impossible as the assistant cannot send images
|
||||
}
|
||||
return part.text
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
ollamaMessages.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ollamaMessages
|
||||
}
|
||||
|
||||
export class NativeOllamaHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
private client: Ollama | undefined
|
||||
protected models: Record<string, ModelInfo> = {}
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
this.options = options
|
||||
}
|
||||
|
||||
private ensureClient(): Ollama {
|
||||
if (!this.client) {
|
||||
try {
|
||||
this.client = new Ollama({
|
||||
host: this.options.ollamaBaseUrl || "http://localhost:11434",
|
||||
// Note: The ollama npm package handles timeouts internally
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Ollama client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const { id: modelId, info: modelInfo } = await this.fetchModel()
|
||||
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
|
||||
|
||||
const ollamaMessages: Message[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOllamaMessages(messages),
|
||||
]
|
||||
|
||||
const matcher = new XmlMatcher(
|
||||
"think",
|
||||
(chunk) =>
|
||||
({
|
||||
type: chunk.matched ? "reasoning" : "text",
|
||||
text: chunk.data,
|
||||
}) as const,
|
||||
)
|
||||
|
||||
try {
|
||||
// Create the actual API request promise
|
||||
const stream = await client.chat({
|
||||
model: modelId,
|
||||
messages: ollamaMessages,
|
||||
stream: true,
|
||||
options: {
|
||||
num_ctx: modelInfo.contextWindow,
|
||||
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
|
||||
},
|
||||
})
|
||||
|
||||
let totalInputTokens = 0
|
||||
let totalOutputTokens = 0
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
if (typeof chunk.message.content === "string") {
|
||||
// Process content through matcher for reasoning detection
|
||||
for (const matcherChunk of matcher.update(chunk.message.content)) {
|
||||
yield matcherChunk
|
||||
}
|
||||
}
|
||||
|
||||
// Handle token usage if available
|
||||
if (chunk.eval_count !== undefined || chunk.prompt_eval_count !== undefined) {
|
||||
if (chunk.prompt_eval_count) {
|
||||
totalInputTokens = chunk.prompt_eval_count
|
||||
}
|
||||
if (chunk.eval_count) {
|
||||
totalOutputTokens = chunk.eval_count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yield any remaining content from the matcher
|
||||
for (const chunk of matcher.final()) {
|
||||
yield chunk
|
||||
}
|
||||
|
||||
// Yield usage information if available
|
||||
if (totalInputTokens > 0 || totalOutputTokens > 0) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: totalInputTokens,
|
||||
outputTokens: totalOutputTokens,
|
||||
}
|
||||
}
|
||||
} catch (streamError: any) {
|
||||
console.error("Error processing Ollama stream:", streamError)
|
||||
throw new Error(`Ollama stream processing error: ${streamError.message || "Unknown error"}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Enhance error reporting
|
||||
const statusCode = error.status || error.statusCode
|
||||
const errorMessage = error.message || "Unknown error"
|
||||
|
||||
if (error.code === "ECONNREFUSED") {
|
||||
throw new Error(
|
||||
`Ollama service is not running at ${this.options.ollamaBaseUrl || "http://localhost:11434"}. Please start Ollama first.`,
|
||||
)
|
||||
} else if (statusCode === 404) {
|
||||
throw new Error(
|
||||
`Model ${this.getModel().id} not found in Ollama. Please pull the model first with: ollama pull ${this.getModel().id}`,
|
||||
)
|
||||
}
|
||||
|
||||
console.error(`Ollama API error (${statusCode || "unknown"}): ${errorMessage}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async fetchModel() {
|
||||
this.models = await getOllamaModels(this.options.ollamaBaseUrl)
|
||||
return this.getModel()
|
||||
}
|
||||
|
||||
override getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.ollamaModelId || ""
|
||||
return {
|
||||
id: modelId,
|
||||
info: this.models[modelId] || openAiModelInfoSaneDefaults,
|
||||
}
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const { id: modelId } = await this.fetchModel()
|
||||
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
|
||||
|
||||
const response = await client.chat({
|
||||
model: modelId,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
|
||||
},
|
||||
})
|
||||
|
||||
return response.message?.content || ""
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`Ollama completion error: ${error.message}`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/api/providers/roo.ts
Normal file
93
src/api/providers/roo.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { t } from "../../i18n"
|
||||
|
||||
import type { ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
|
||||
export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
// Check if CloudService is available and get the session token.
|
||||
if (!CloudService.hasInstance()) {
|
||||
throw new Error(t("common:errors.roo.authenticationRequired"))
|
||||
}
|
||||
|
||||
const sessionToken = CloudService.instance.authService?.getSessionToken()
|
||||
|
||||
if (!sessionToken) {
|
||||
throw new Error(t("common:errors.roo.authenticationRequired"))
|
||||
}
|
||||
|
||||
super({
|
||||
...options,
|
||||
providerName: "Roo Code Cloud",
|
||||
baseURL: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy/v1",
|
||||
apiKey: sessionToken,
|
||||
defaultProviderModelId: rooDefaultModelId,
|
||||
providerModels: rooModels,
|
||||
defaultTemperature: 0.7,
|
||||
})
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const stream = await this.createStream(systemPrompt, messages, metadata)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if (delta) {
|
||||
if (delta.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: delta.reasoning_content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override getModel() {
|
||||
const modelId = this.options.apiModelId || rooDefaultModelId
|
||||
const modelInfo = this.providerModels[modelId as RooModelId] ?? this.providerModels[rooDefaultModelId]
|
||||
|
||||
if (modelInfo) {
|
||||
return { id: modelId as RooModelId, info: modelInfo }
|
||||
}
|
||||
|
||||
// Return the requested model ID even if not found, with fallback info.
|
||||
return {
|
||||
id: modelId as RooModelId,
|
||||
info: {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ import type { ToolParamName, ToolResponse } from "../../shared/tools"
|
|||
import { fetchInstructionsTool } from "../tools/fetchInstructionsTool"
|
||||
import { listFilesTool } from "../tools/listFilesTool"
|
||||
import { getReadFileToolDescription, readFileTool } from "../tools/readFileTool"
|
||||
import { getSimpleReadFileToolDescription, simpleReadFileTool } from "../tools/simpleReadFileTool"
|
||||
import { shouldUseSingleFileRead } from "@roo-code/types"
|
||||
import { writeToFileTool } from "../tools/writeToFileTool"
|
||||
import { applyDiffTool } from "../tools/multiApplyDiffTool"
|
||||
import { insertContentTool } from "../tools/insertContentTool"
|
||||
|
|
@ -155,7 +157,13 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
case "execute_command":
|
||||
return `[${block.name} for '${block.params.command}']`
|
||||
case "read_file":
|
||||
return getReadFileToolDescription(block.name, block.params)
|
||||
// Check if this model should use the simplified description
|
||||
const modelId = cline.api.getModel().id
|
||||
if (shouldUseSingleFileRead(modelId)) {
|
||||
return getSimpleReadFileToolDescription(block.name, block.params)
|
||||
} else {
|
||||
return getReadFileToolDescription(block.name, block.params)
|
||||
}
|
||||
case "fetch_instructions":
|
||||
return `[${block.name} for '${block.params.task}']`
|
||||
case "write_to_file":
|
||||
|
|
@ -454,8 +462,20 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
await searchAndReplaceTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
break
|
||||
case "read_file":
|
||||
await readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
|
||||
// Check if this model should use the simplified single-file read tool
|
||||
const modelId = cline.api.getModel().id
|
||||
if (shouldUseSingleFileRead(modelId)) {
|
||||
await simpleReadFileTool(
|
||||
cline,
|
||||
block,
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
)
|
||||
} else {
|
||||
await readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
}
|
||||
break
|
||||
case "fetch_instructions":
|
||||
await fetchInstructionsTool(cline, block, askApproval, handleError, pushToolResult)
|
||||
|
|
|
|||
|
|
@ -270,29 +270,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -167,29 +167,22 @@ Examples:
|
|||
</list_code_definition_names>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -199,16 +192,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -269,29 +269,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -301,16 +294,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -319,29 +319,22 @@ Example: Requesting to access an MCP resource
|
|||
</access_mcp_resource>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -351,16 +344,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -275,29 +275,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -307,16 +300,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -270,29 +270,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -323,29 +323,22 @@ Example: Requesting to click on the element at coordinates 450,300
|
|||
</browser_action>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -355,16 +348,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -270,29 +270,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -358,29 +358,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -390,16 +383,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -270,29 +270,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -323,29 +323,22 @@ Example: Requesting to click on the element at coordinates 450,300
|
|||
</browser_action>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -355,16 +348,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -319,29 +319,22 @@ Example: Requesting to access an MCP resource
|
|||
</access_mcp_resource>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -351,16 +344,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -270,29 +270,22 @@ Examples:
|
|||
</search_and_replace>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ async function generatePrompt(
|
|||
partialReadsEnabled?: boolean,
|
||||
settings?: SystemPromptSettings,
|
||||
todoList?: TodoItem[],
|
||||
modelId?: string,
|
||||
): Promise<string> {
|
||||
if (!context) {
|
||||
throw new Error("Extension context is required for generating system prompt")
|
||||
|
|
@ -106,6 +107,7 @@ ${getToolDescriptionsForMode(
|
|||
partialReadsEnabled,
|
||||
settings,
|
||||
enableMcpServerCreation,
|
||||
modelId,
|
||||
)}
|
||||
|
||||
${getToolUseGuidelinesSection(codeIndexManager)}
|
||||
|
|
@ -150,6 +152,7 @@ export const SYSTEM_PROMPT = async (
|
|||
partialReadsEnabled?: boolean,
|
||||
settings?: SystemPromptSettings,
|
||||
todoList?: TodoItem[],
|
||||
modelId?: string,
|
||||
): Promise<string> => {
|
||||
if (!context) {
|
||||
throw new Error("Extension context is required for generating system prompt")
|
||||
|
|
@ -221,5 +224,6 @@ ${customInstructions}`
|
|||
partialReadsEnabled,
|
||||
settings,
|
||||
todoList,
|
||||
modelId,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,21 @@
|
|||
export function getAskFollowupQuestionDescription(): string {
|
||||
return `## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
Parameters:
|
||||
- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
|
||||
- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must:
|
||||
1. Be provided in its own <suggest> tag
|
||||
2. Be specific, actionable, and directly related to the completed task
|
||||
3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses.
|
||||
4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: <suggest mode="mode-slug">suggestion text</suggest>
|
||||
- When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge
|
||||
- question: (required) A clear, specific question addressing the information needed
|
||||
- follow_up: (required) A list of 2-4 suggested answers, each in its own <suggest> tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.)
|
||||
|
||||
Usage:
|
||||
<ask_followup_question>
|
||||
<question>Your question here</question>
|
||||
<follow_up>
|
||||
<suggest>
|
||||
Your suggested answer here
|
||||
</suggest>
|
||||
<suggest mode="code">
|
||||
Implement the solution
|
||||
</suggest>
|
||||
<suggest>First suggestion</suggest>
|
||||
<suggest mode="code">Action with mode switch</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Requesting to ask the user for the path to the frontend-config.json file
|
||||
Example:
|
||||
<ask_followup_question>
|
||||
<question>What is the path to the frontend-config.json file?</question>
|
||||
<follow_up>
|
||||
|
|
@ -30,15 +23,5 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil
|
|||
<suggest>./config/frontend-config.json</suggest>
|
||||
<suggest>./frontend-config.json</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
Example: Asking a question with mode switching options
|
||||
<ask_followup_question>
|
||||
<question>How would you like to proceed with this task?</question>
|
||||
<follow_up>
|
||||
<suggest mode="code">Start implementing the solution</suggest>
|
||||
<suggest mode="architect">Plan the architecture first</suggest>
|
||||
<suggest>Continue with more details</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import { Mode, getModeConfig, isToolAllowedForMode, getGroupName } from "../../.
|
|||
import { ToolArgs } from "./types"
|
||||
import { getExecuteCommandDescription } from "./execute-command"
|
||||
import { getReadFileDescription } from "./read-file"
|
||||
import { getSimpleReadFileDescription } from "./simple-read-file"
|
||||
import { getFetchInstructionsDescription } from "./fetch-instructions"
|
||||
import { shouldUseSingleFileRead } from "@roo-code/types"
|
||||
import { getWriteToFileDescription } from "./write-to-file"
|
||||
import { getSearchFilesDescription } from "./search-files"
|
||||
import { getListFilesDescription } from "./list-files"
|
||||
|
|
@ -28,7 +30,14 @@ import { CodeIndexManager } from "../../../services/code-index/manager"
|
|||
// Map of tool names to their description functions
|
||||
const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined> = {
|
||||
execute_command: (args) => getExecuteCommandDescription(args),
|
||||
read_file: (args) => getReadFileDescription(args),
|
||||
read_file: (args) => {
|
||||
// Check if the current model should use the simplified read_file tool
|
||||
const modelId = args.settings?.modelId
|
||||
if (modelId && shouldUseSingleFileRead(modelId)) {
|
||||
return getSimpleReadFileDescription(args)
|
||||
}
|
||||
return getReadFileDescription(args)
|
||||
},
|
||||
fetch_instructions: (args) => getFetchInstructionsDescription(args.settings?.enableMcpServerCreation),
|
||||
write_to_file: (args) => getWriteToFileDescription(args),
|
||||
search_files: (args) => getSearchFilesDescription(args),
|
||||
|
|
@ -62,6 +71,7 @@ export function getToolDescriptionsForMode(
|
|||
partialReadsEnabled?: boolean,
|
||||
settings?: Record<string, any>,
|
||||
enableMcpServerCreation?: boolean,
|
||||
modelId?: string,
|
||||
): string {
|
||||
const config = getModeConfig(mode, customModes)
|
||||
const args: ToolArgs = {
|
||||
|
|
@ -74,6 +84,7 @@ export function getToolDescriptionsForMode(
|
|||
settings: {
|
||||
...settings,
|
||||
enableMcpServerCreation,
|
||||
modelId,
|
||||
},
|
||||
experiments,
|
||||
}
|
||||
|
|
@ -138,6 +149,7 @@ export function getToolDescriptionsForMode(
|
|||
export {
|
||||
getExecuteCommandDescription,
|
||||
getReadFileDescription,
|
||||
getSimpleReadFileDescription,
|
||||
getFetchInstructionsDescription,
|
||||
getWriteToFileDescription,
|
||||
getSearchFilesDescription,
|
||||
|
|
|
|||
35
src/core/prompts/tools/simple-read-file.ts
Normal file
35
src/core/prompts/tools/simple-read-file.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { ToolArgs } from "./types"
|
||||
|
||||
/**
|
||||
* Generate a simplified read_file tool description for models that only support single file reads
|
||||
* Uses the simpler format: <read_file><path>file/path.ext</path></read_file>
|
||||
*/
|
||||
export function getSimpleReadFileDescription(args: ToolArgs): string {
|
||||
return `## read_file
|
||||
Description: Request to read the contents of a file. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when discussing code.
|
||||
|
||||
Parameters:
|
||||
- path: (required) File path (relative to workspace directory ${args.cwd})
|
||||
|
||||
Usage:
|
||||
<read_file>
|
||||
<path>path/to/file</path>
|
||||
</read_file>
|
||||
|
||||
Examples:
|
||||
|
||||
1. Reading a TypeScript file:
|
||||
<read_file>
|
||||
<path>src/app.ts</path>
|
||||
</read_file>
|
||||
|
||||
2. Reading a configuration file:
|
||||
<read_file>
|
||||
<path>config.json</path>
|
||||
</read_file>
|
||||
|
||||
3. Reading a markdown file:
|
||||
<read_file>
|
||||
<path>README.md</path>
|
||||
</read_file>`
|
||||
}
|
||||
|
|
@ -21,16 +21,18 @@ import {
|
|||
type ClineMessage,
|
||||
type ClineSay,
|
||||
type ClineAsk,
|
||||
type BlockingAsk,
|
||||
type ToolProgressStatus,
|
||||
type HistoryItem,
|
||||
RooCodeEventName,
|
||||
TelemetryEventName,
|
||||
TaskStatus,
|
||||
TodoItem,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
getApiProtocol,
|
||||
getModelId,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
isBlockingAsk,
|
||||
isIdleAsk,
|
||||
isInteractiveAsk,
|
||||
isResumableAsk,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { CloudService, ExtensionBridgeService } from "@roo-code/cloud"
|
||||
|
|
@ -182,7 +184,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
providerRef: WeakRef<ClineProvider>
|
||||
private readonly globalStoragePath: string
|
||||
abort: boolean = false
|
||||
blockingAsk?: BlockingAsk
|
||||
|
||||
// TaskStatus
|
||||
idleAsk?: ClineMessage
|
||||
resumableAsk?: ClineMessage
|
||||
interactiveAsk?: ClineMessage
|
||||
|
||||
didFinishAbortingStream = false
|
||||
abandoned = false
|
||||
isInitialized = false
|
||||
|
|
@ -290,7 +297,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
|
||||
|
||||
this.metadata = {
|
||||
taskId: this.taskId,
|
||||
task: historyItem ? historyItem.task : task,
|
||||
images: historyItem ? [] : images,
|
||||
}
|
||||
|
|
@ -497,6 +503,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
if (this._taskMode === undefined) {
|
||||
throw new Error("Task mode accessed before initialization. Use getTaskMode() or wait for taskModeReady.")
|
||||
}
|
||||
|
||||
return this._taskMode
|
||||
}
|
||||
|
||||
|
|
@ -615,6 +622,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
}
|
||||
|
||||
private findMessageByTimestamp(ts: number): ClineMessage | undefined {
|
||||
for (let i = this.clineMessages.length - 1; i >= 0; i--) {
|
||||
if (this.clineMessages[i].ts === ts) {
|
||||
return this.clineMessages[i]
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Note that `partial` has three valid states true (partial message),
|
||||
// false (completion of partial message), undefined (individual complete
|
||||
// message).
|
||||
|
|
@ -713,16 +730,55 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, isProtected })
|
||||
}
|
||||
|
||||
// Detect if the task will enter an idle state.
|
||||
const isReady = this.askResponse !== undefined || this.lastMessageTs !== askTs
|
||||
// The state is mutable if the message is complete and the task will
|
||||
// block (via the `pWaitFor`).
|
||||
const isBlocking = !(this.askResponse !== undefined || this.lastMessageTs !== askTs)
|
||||
const isStatusMutable = !partial && isBlocking
|
||||
let statusMutationTimeouts: NodeJS.Timeout[] = []
|
||||
|
||||
if (!partial && !isReady && isBlockingAsk(type)) {
|
||||
this.blockingAsk = type
|
||||
this.emit(RooCodeEventName.TaskIdle, this.taskId)
|
||||
if (isStatusMutable) {
|
||||
if (isInteractiveAsk(type)) {
|
||||
statusMutationTimeouts.push(
|
||||
setTimeout(() => {
|
||||
const message = this.findMessageByTimestamp(askTs)
|
||||
|
||||
if (message) {
|
||||
this.interactiveAsk = message
|
||||
this.emit(RooCodeEventName.TaskInteractive, this.taskId)
|
||||
}
|
||||
}, 1_000),
|
||||
)
|
||||
} else if (isResumableAsk(type)) {
|
||||
statusMutationTimeouts.push(
|
||||
setTimeout(() => {
|
||||
const message = this.findMessageByTimestamp(askTs)
|
||||
|
||||
if (message) {
|
||||
this.resumableAsk = message
|
||||
this.emit(RooCodeEventName.TaskResumable, this.taskId)
|
||||
}
|
||||
}, 1_000),
|
||||
)
|
||||
} else if (isIdleAsk(type)) {
|
||||
statusMutationTimeouts.push(
|
||||
setTimeout(() => {
|
||||
const message = this.findMessageByTimestamp(askTs)
|
||||
|
||||
if (message) {
|
||||
this.idleAsk = message
|
||||
this.emit(RooCodeEventName.TaskIdle, this.taskId)
|
||||
}
|
||||
}, 1_000),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> blocking`)
|
||||
console.log(
|
||||
`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> blocking (isStatusMutable = ${isStatusMutable}, statusMutationTimeouts = ${statusMutationTimeouts.length})`,
|
||||
)
|
||||
|
||||
await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 })
|
||||
|
||||
console.log(`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> unblocked (${this.askResponse})`)
|
||||
|
||||
if (this.lastMessageTs !== askTs) {
|
||||
|
|
@ -737,9 +793,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
this.askResponseText = undefined
|
||||
this.askResponseImages = undefined
|
||||
|
||||
// Cancel the timeouts if they are still running.
|
||||
statusMutationTimeouts.forEach((timeout) => clearTimeout(timeout))
|
||||
|
||||
// Switch back to an active state.
|
||||
if (this.blockingAsk) {
|
||||
this.blockingAsk = undefined
|
||||
if (this.idleAsk || this.resumableAsk || this.interactiveAsk) {
|
||||
this.idleAsk = undefined
|
||||
this.resumableAsk = undefined
|
||||
this.interactiveAsk = undefined
|
||||
this.emit(RooCodeEventName.TaskActive, this.taskId)
|
||||
}
|
||||
|
||||
|
|
@ -757,23 +818,27 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
this.askResponseImages = images
|
||||
}
|
||||
|
||||
public approveAsk({ text, images }: { text?: string; images?: string[] } = {}) {
|
||||
this.handleWebviewAskResponse("yesButtonClicked", text, images)
|
||||
}
|
||||
|
||||
public denyAsk({ text, images }: { text?: string; images?: string[] } = {}) {
|
||||
this.handleWebviewAskResponse("noButtonClicked", text, images)
|
||||
}
|
||||
|
||||
public submitUserMessage(text: string, images?: string[], modeSlug?: string): void {
|
||||
try {
|
||||
const trimmed = (text ?? "").trim()
|
||||
const imgs = images ?? []
|
||||
text = (text ?? "").trim()
|
||||
images = images ?? []
|
||||
|
||||
const provider = this.providerRef.deref()
|
||||
if (!provider) {
|
||||
console.error("[Task#submitUserMessage] Provider reference lost")
|
||||
return
|
||||
}
|
||||
|
||||
// Run asynchronously to allow awaiting mode switch before sending the message
|
||||
void (async () => {
|
||||
// If a mode slug is provided, handle the mode switch first (same behavior as createTask)
|
||||
try {
|
||||
const modeSlugValue = (modeSlug ?? "").trim()
|
||||
if (modeSlugValue.length > 0) {
|
||||
if (modeSlugValue.length > 0 && provider) {
|
||||
const customModes = await provider.customModesManager.getCustomModes()
|
||||
const targetMode = getModeBySlug(modeSlugValue, customModes)
|
||||
if (targetMode) {
|
||||
|
|
@ -784,7 +849,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
}
|
||||
} catch (err) {
|
||||
provider.log(
|
||||
provider?.log(
|
||||
`[Task#submitUserMessage] Failed to apply modeSlug: ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
|
|
@ -792,16 +857,20 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
|
||||
// If there's no content to send, exit after potential mode switch
|
||||
if (!trimmed && imgs.length === 0) {
|
||||
if (text.length === 0 && images.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
await provider.postMessageToWebview({
|
||||
type: "invoke",
|
||||
invoke: "sendMessage",
|
||||
text: trimmed,
|
||||
images: imgs,
|
||||
})
|
||||
if (provider) {
|
||||
await provider.postMessageToWebview({
|
||||
type: "invoke",
|
||||
invoke: "sendMessage",
|
||||
text,
|
||||
images,
|
||||
})
|
||||
} else {
|
||||
console.error("[Task#submitUserMessage] Provider reference lost")
|
||||
}
|
||||
})()
|
||||
} catch (error) {
|
||||
console.error("[Task#submitUserMessage] Failed to submit user message:", error)
|
||||
|
|
@ -1055,12 +1124,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
|
||||
public async resumePausedTask(lastMessage: string) {
|
||||
// Release this Cline instance from paused state.
|
||||
this.isPaused = false
|
||||
this.emit(RooCodeEventName.TaskUnpaused)
|
||||
|
||||
// Fake an answer from the subtask that it has completed running and
|
||||
// this is the result of what it has done add the message to the chat
|
||||
// this is the result of what it has done add the message to the chat
|
||||
// history and to the webview ui.
|
||||
try {
|
||||
await this.say("subtask_result", lastMessage)
|
||||
|
|
@ -2188,6 +2256,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
|
||||
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
|
||||
},
|
||||
undefined, // todoList
|
||||
this.api.getModel().id,
|
||||
)
|
||||
})()
|
||||
}
|
||||
|
|
@ -2545,4 +2615,24 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
public get cwd() {
|
||||
return this.workspacePath
|
||||
}
|
||||
|
||||
public get taskStatus(): TaskStatus {
|
||||
if (this.interactiveAsk) {
|
||||
return TaskStatus.Interactive
|
||||
}
|
||||
|
||||
if (this.resumableAsk) {
|
||||
return TaskStatus.Resumable
|
||||
}
|
||||
|
||||
if (this.idleAsk) {
|
||||
return TaskStatus.Idle
|
||||
}
|
||||
|
||||
return TaskStatus.Running
|
||||
}
|
||||
|
||||
public get taskAsk(): ClineMessage | undefined {
|
||||
return this.idleAsk || this.resumableAsk || this.interactiveAsk
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1614,4 +1614,103 @@ describe("Cline", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("abortTask", () => {
|
||||
it("should set abort flag and emit TaskAborted event", async () => {
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// Spy on emit method
|
||||
const emitSpy = vi.spyOn(task, "emit")
|
||||
|
||||
// Mock the dispose method to avoid actual cleanup
|
||||
vi.spyOn(task, "dispose").mockImplementation(() => {})
|
||||
|
||||
// Call abortTask
|
||||
await task.abortTask()
|
||||
|
||||
// Verify abort flag is set
|
||||
expect(task.abort).toBe(true)
|
||||
|
||||
// Verify TaskAborted event was emitted
|
||||
expect(emitSpy).toHaveBeenCalledWith("taskAborted")
|
||||
})
|
||||
|
||||
it("should be equivalent to clicking Cancel button functionality", async () => {
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// Mock the dispose method to track cleanup
|
||||
const disposeSpy = vi.spyOn(task, "dispose").mockImplementation(() => {})
|
||||
|
||||
// Call abortTask
|
||||
await task.abortTask()
|
||||
|
||||
// Verify the same behavior as Cancel button
|
||||
expect(task.abort).toBe(true)
|
||||
expect(disposeSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should work with TaskLike interface", async () => {
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// Cast to TaskLike to ensure interface compliance
|
||||
const taskLike = task as any // TaskLike interface from types package
|
||||
|
||||
// Verify abortTask method exists and is callable
|
||||
expect(typeof taskLike.abortTask).toBe("function")
|
||||
|
||||
// Mock the dispose method to avoid actual cleanup
|
||||
vi.spyOn(task, "dispose").mockImplementation(() => {})
|
||||
|
||||
// Call abortTask through interface
|
||||
await taskLike.abortTask()
|
||||
|
||||
// Verify it works
|
||||
expect(task.abort).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle errors during disposal gracefully", async () => {
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// Mock dispose to throw an error
|
||||
const mockError = new Error("Disposal failed")
|
||||
vi.spyOn(task, "dispose").mockImplementation(() => {
|
||||
throw mockError
|
||||
})
|
||||
|
||||
// Spy on console.error to verify error is logged
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
|
||||
// abortTask should not throw even if dispose fails
|
||||
await expect(task.abortTask()).resolves.not.toThrow()
|
||||
|
||||
// Verify error was logged
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Error during task"), mockError)
|
||||
|
||||
// Verify abort flag is still set
|
||||
expect(task.abort).toBe(true)
|
||||
|
||||
// Restore console.error
|
||||
consoleErrorSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -213,12 +213,7 @@ describe("executeCommand", () => {
|
|||
|
||||
// Verify
|
||||
expect(rejected).toBe(false)
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(
|
||||
customCwd,
|
||||
true, // customCwd provided
|
||||
mockTask.taskId,
|
||||
"vscode",
|
||||
)
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(customCwd, mockTask.taskId, "vscode")
|
||||
expect(result).toContain(`within working directory '${customCwd}'`)
|
||||
})
|
||||
|
||||
|
|
@ -248,12 +243,7 @@ describe("executeCommand", () => {
|
|||
|
||||
// Verify
|
||||
expect(rejected).toBe(false)
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(
|
||||
resolvedCwd,
|
||||
true, // customCwd provided
|
||||
mockTask.taskId,
|
||||
"vscode",
|
||||
)
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(resolvedCwd, mockTask.taskId, "vscode")
|
||||
expect(result).toContain(`within working directory '${resolvedCwd.toPosix()}'`)
|
||||
})
|
||||
|
||||
|
|
@ -302,12 +292,7 @@ describe("executeCommand", () => {
|
|||
await executeCommand(mockTask, options)
|
||||
|
||||
// Verify
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(
|
||||
mockTask.cwd,
|
||||
false, // no customCwd
|
||||
mockTask.taskId,
|
||||
"vscode",
|
||||
)
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(mockTask.cwd, mockTask.taskId, "vscode")
|
||||
})
|
||||
|
||||
it("should use execa provider when shell integration is disabled", async () => {
|
||||
|
|
@ -330,12 +315,7 @@ describe("executeCommand", () => {
|
|||
await executeCommand(mockTask, options)
|
||||
|
||||
// Verify
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(
|
||||
mockTask.cwd,
|
||||
false, // no customCwd
|
||||
mockTask.taskId,
|
||||
"execa",
|
||||
)
|
||||
expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(mockTask.cwd, mockTask.taskId, "execa")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ export async function executeCommand(
|
|||
}
|
||||
}
|
||||
|
||||
const terminal = await TerminalRegistry.getOrCreateTerminal(workingDir, !!customCwd, task.taskId, terminalProvider)
|
||||
const terminal = await TerminalRegistry.getOrCreateTerminal(workingDir, task.taskId, terminalProvider)
|
||||
|
||||
if (terminal instanceof Terminal) {
|
||||
terminal.terminal.show(true)
|
||||
|
|
|
|||
287
src/core/tools/simpleReadFileTool.ts
Normal file
287
src/core/tools/simpleReadFileTool.ts
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
import path from "path"
|
||||
import { isBinaryFile } from "isbinaryfile"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { t } from "../../i18n"
|
||||
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { countFileLines } from "../../integrations/misc/line-counter"
|
||||
import { readLines } from "../../integrations/misc/read-lines"
|
||||
import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "../../integrations/misc/extract-text"
|
||||
import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
|
||||
import {
|
||||
DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
|
||||
DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
|
||||
isSupportedImageFormat,
|
||||
validateImageForProcessing,
|
||||
processImageFile,
|
||||
} from "./helpers/imageHelpers"
|
||||
|
||||
/**
|
||||
* Simplified read file tool for models that only support single file reads
|
||||
* Uses the format: <read_file><path>file/path.ext</path></read_file>
|
||||
*
|
||||
* This is a streamlined version of readFileTool that:
|
||||
* - Only accepts a single path parameter
|
||||
* - Does not support multiple files
|
||||
* - Does not support line ranges
|
||||
* - Has simpler XML parsing
|
||||
*/
|
||||
export async function simpleReadFileTool(
|
||||
cline: Task,
|
||||
block: ToolUse,
|
||||
askApproval: AskApproval,
|
||||
handleError: HandleError,
|
||||
pushToolResult: PushToolResult,
|
||||
_removeClosingTag: RemoveClosingTag,
|
||||
) {
|
||||
const filePath: string | undefined = block.params.path
|
||||
|
||||
// Check if the current model supports images
|
||||
const modelInfo = cline.api.getModel().info
|
||||
const supportsImages = modelInfo.supportsImages ?? false
|
||||
|
||||
// Handle partial message
|
||||
if (block.partial) {
|
||||
const fullPath = filePath ? path.resolve(cline.cwd, filePath) : ""
|
||||
const sharedMessageProps: ClineSayTool = {
|
||||
tool: "readFile",
|
||||
path: getReadablePath(cline.cwd, filePath || ""),
|
||||
isOutsideWorkspace: filePath ? isPathOutsideWorkspace(fullPath) : false,
|
||||
}
|
||||
const partialMessage = JSON.stringify({
|
||||
...sharedMessageProps,
|
||||
content: undefined,
|
||||
} satisfies ClineSayTool)
|
||||
await cline.ask("tool", partialMessage, block.partial).catch(() => {})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate path parameter
|
||||
if (!filePath) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("read_file")
|
||||
const errorMsg = await cline.sayAndCreateMissingParamError("read_file", "path")
|
||||
pushToolResult(`<file><error>${errorMsg}</error></file>`)
|
||||
return
|
||||
}
|
||||
|
||||
const relPath = filePath
|
||||
const fullPath = path.resolve(cline.cwd, relPath)
|
||||
|
||||
try {
|
||||
// Check RooIgnore validation
|
||||
const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath)
|
||||
if (!accessAllowed) {
|
||||
await cline.say("rooignore_error", relPath)
|
||||
const errorMsg = formatResponse.rooIgnoreError(relPath)
|
||||
pushToolResult(`<file><path>${relPath}</path><error>${errorMsg}</error></file>`)
|
||||
return
|
||||
}
|
||||
|
||||
// Get max read file line setting
|
||||
const { maxReadFileLine = -1 } = (await cline.providerRef.deref()?.getState()) ?? {}
|
||||
|
||||
// Create approval message
|
||||
const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)
|
||||
let lineSnippet = ""
|
||||
if (maxReadFileLine === 0) {
|
||||
lineSnippet = t("tools:readFile.definitionsOnly")
|
||||
} else if (maxReadFileLine > 0) {
|
||||
lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine })
|
||||
}
|
||||
|
||||
const completeMessage = JSON.stringify({
|
||||
tool: "readFile",
|
||||
path: getReadablePath(cline.cwd, relPath),
|
||||
isOutsideWorkspace,
|
||||
content: fullPath,
|
||||
reason: lineSnippet,
|
||||
} satisfies ClineSayTool)
|
||||
|
||||
const { response, text, images } = await cline.ask("tool", completeMessage, false)
|
||||
|
||||
if (response !== "yesButtonClicked") {
|
||||
// Handle denial
|
||||
if (text) {
|
||||
await cline.say("user_feedback", text, images)
|
||||
}
|
||||
cline.didRejectTool = true
|
||||
|
||||
const statusMessage = text ? formatResponse.toolDeniedWithFeedback(text) : formatResponse.toolDenied()
|
||||
|
||||
pushToolResult(`${statusMessage}\n<file><path>${relPath}</path><status>Denied by user</status></file>`)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle approval with feedback
|
||||
if (text) {
|
||||
await cline.say("user_feedback", text, images)
|
||||
}
|
||||
|
||||
// Process the file
|
||||
const [totalLines, isBinary] = await Promise.all([countFileLines(fullPath), isBinaryFile(fullPath)])
|
||||
|
||||
// Handle binary files
|
||||
if (isBinary) {
|
||||
const fileExtension = path.extname(relPath).toLowerCase()
|
||||
const supportedBinaryFormats = getSupportedBinaryFormats()
|
||||
|
||||
// Check if it's a supported image format
|
||||
if (isSupportedImageFormat(fileExtension)) {
|
||||
try {
|
||||
const {
|
||||
maxImageFileSize = DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
|
||||
maxTotalImageSize = DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
|
||||
} = (await cline.providerRef.deref()?.getState()) ?? {}
|
||||
|
||||
// Validate image for processing
|
||||
const validationResult = await validateImageForProcessing(
|
||||
fullPath,
|
||||
supportsImages,
|
||||
maxImageFileSize,
|
||||
maxTotalImageSize,
|
||||
0, // No cumulative memory for single file
|
||||
)
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
|
||||
pushToolResult(
|
||||
`<file><path>${relPath}</path>\n<notice>${validationResult.notice}</notice>\n</file>`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Process the image
|
||||
const imageResult = await processImageFile(fullPath)
|
||||
await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
|
||||
|
||||
// Return result with image data
|
||||
const result = formatResponse.toolResult(
|
||||
`<file><path>${relPath}</path>\n<notice>${imageResult.notice}</notice>\n</file>`,
|
||||
supportsImages ? [imageResult.dataUrl] : undefined,
|
||||
)
|
||||
|
||||
if (typeof result === "string") {
|
||||
pushToolResult(result)
|
||||
} else {
|
||||
pushToolResult(result)
|
||||
}
|
||||
return
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
pushToolResult(
|
||||
`<file><path>${relPath}</path><error>Error reading image file: ${errorMsg}</error></file>`,
|
||||
)
|
||||
await handleError(
|
||||
`reading image file ${relPath}`,
|
||||
error instanceof Error ? error : new Error(errorMsg),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a supported binary format that can be processed
|
||||
if (supportedBinaryFormats && supportedBinaryFormats.includes(fileExtension)) {
|
||||
// For supported binary formats (.pdf, .docx, .ipynb), continue to extractTextFromFile
|
||||
// Fall through to the normal extractTextFromFile processing below
|
||||
} else {
|
||||
// Handle unknown binary format
|
||||
const fileFormat = fileExtension.slice(1) || "bin"
|
||||
pushToolResult(
|
||||
`<file><path>${relPath}</path>\n<binary_file format="${fileFormat}">Binary file - content not displayed</binary_file>\n</file>`,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle definitions-only mode
|
||||
if (maxReadFileLine === 0) {
|
||||
try {
|
||||
const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController)
|
||||
if (defResult) {
|
||||
let xmlInfo = `<notice>Showing only definitions. Use standard read_file if you need to read actual content</notice>\n`
|
||||
pushToolResult(
|
||||
`<file><path>${relPath}</path>\n<list_code_definition_names>${defResult}</list_code_definition_names>\n${xmlInfo}</file>`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
|
||||
console.warn(`[simple_read_file] Warning: ${error.message}`)
|
||||
} else {
|
||||
console.error(
|
||||
`[simple_read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle files exceeding line threshold
|
||||
if (maxReadFileLine > 0 && totalLines > maxReadFileLine) {
|
||||
const content = addLineNumbers(await readLines(fullPath, maxReadFileLine - 1, 0))
|
||||
const lineRangeAttr = ` lines="1-${maxReadFileLine}"`
|
||||
let xmlInfo = `<content${lineRangeAttr}>\n${content}</content>\n`
|
||||
|
||||
try {
|
||||
const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController)
|
||||
if (defResult) {
|
||||
xmlInfo += `<list_code_definition_names>${defResult}</list_code_definition_names>\n`
|
||||
}
|
||||
xmlInfo += `<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. File is too large for complete display</notice>\n`
|
||||
pushToolResult(`<file><path>${relPath}</path>\n${xmlInfo}</file>`)
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
|
||||
console.warn(`[simple_read_file] Warning: ${error.message}`)
|
||||
} else {
|
||||
console.error(
|
||||
`[simple_read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle normal file read
|
||||
const content = await extractTextFromFile(fullPath)
|
||||
const lineRangeAttr = ` lines="1-${totalLines}"`
|
||||
let xmlInfo = totalLines > 0 ? `<content${lineRangeAttr}>\n${content}</content>\n` : `<content/>`
|
||||
|
||||
if (totalLines === 0) {
|
||||
xmlInfo += `<notice>File is empty</notice>\n`
|
||||
}
|
||||
|
||||
// Track file read
|
||||
await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
|
||||
|
||||
// Return the result
|
||||
if (text) {
|
||||
const statusMessage = formatResponse.toolApprovedWithFeedback(text)
|
||||
pushToolResult(`${statusMessage}\n<file><path>${relPath}</path>\n${xmlInfo}</file>`)
|
||||
} else {
|
||||
pushToolResult(`<file><path>${relPath}</path>\n${xmlInfo}</file>`)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
pushToolResult(`<file><path>${relPath}</path><error>Error reading file: ${errorMsg}</error></file>`)
|
||||
await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description for the simple read file tool
|
||||
* @param blockName The name of the tool block
|
||||
* @param blockParams The parameters passed to the tool
|
||||
* @returns A description string for the tool use
|
||||
*/
|
||||
export function getSimpleReadFileToolDescription(blockName: string, blockParams: any): string {
|
||||
if (blockParams.path) {
|
||||
return `[${blockName} for '${blockParams.path}']`
|
||||
} else {
|
||||
return `[${blockName} with missing path]`
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import {
|
|||
type TerminalActionPromptType,
|
||||
type HistoryItem,
|
||||
type CreateTaskOptions,
|
||||
type ClineAsk,
|
||||
RooCodeEventName,
|
||||
requestyDefaultModelId,
|
||||
openRouterDefaultModelId,
|
||||
|
|
@ -120,7 +121,7 @@ export class ClineProvider
|
|||
|
||||
public isViewLaunched = false
|
||||
public settingsImportedAt?: number
|
||||
public readonly latestAnnouncementId = "jul-29-2025-3-25-0" // Update for v3.25.0 announcement
|
||||
public readonly latestAnnouncementId = "aug-20-2025-stealth-model" // Update for stealth model announcement
|
||||
public readonly providerSettingsManager: ProviderSettingsManager
|
||||
public readonly customModesManager: CustomModesManager
|
||||
|
||||
|
|
@ -177,6 +178,8 @@ export class ClineProvider
|
|||
const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId)
|
||||
const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId)
|
||||
const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId)
|
||||
const onTaskInteractive = (taskId: string) => this.emit(RooCodeEventName.TaskInteractive, taskId)
|
||||
const onTaskResumable = (taskId: string) => this.emit(RooCodeEventName.TaskResumable, taskId)
|
||||
const onTaskIdle = (taskId: string) => this.emit(RooCodeEventName.TaskIdle, taskId)
|
||||
|
||||
// Attach the listeners.
|
||||
|
|
@ -186,6 +189,8 @@ export class ClineProvider
|
|||
instance.on(RooCodeEventName.TaskFocused, onTaskFocused)
|
||||
instance.on(RooCodeEventName.TaskUnfocused, onTaskUnfocused)
|
||||
instance.on(RooCodeEventName.TaskActive, onTaskActive)
|
||||
instance.on(RooCodeEventName.TaskInteractive, onTaskInteractive)
|
||||
instance.on(RooCodeEventName.TaskResumable, onTaskResumable)
|
||||
instance.on(RooCodeEventName.TaskIdle, onTaskIdle)
|
||||
|
||||
// Store the cleanup functions for later removal.
|
||||
|
|
@ -196,6 +201,8 @@ export class ClineProvider
|
|||
() => instance.off(RooCodeEventName.TaskFocused, onTaskFocused),
|
||||
() => instance.off(RooCodeEventName.TaskUnfocused, onTaskUnfocused),
|
||||
() => instance.off(RooCodeEventName.TaskActive, onTaskActive),
|
||||
() => instance.off(RooCodeEventName.TaskInteractive, onTaskInteractive),
|
||||
() => instance.off(RooCodeEventName.TaskResumable, onTaskResumable),
|
||||
() => instance.off(RooCodeEventName.TaskIdle, onTaskIdle),
|
||||
])
|
||||
}
|
||||
|
|
@ -404,6 +411,13 @@ export class ClineProvider
|
|||
await this.removeClineFromStack()
|
||||
}
|
||||
|
||||
resumeTask(taskId: string): void {
|
||||
// Use the existing showTaskWithId method which handles both current and historical tasks
|
||||
this.showTaskWithId(taskId).catch((error) => {
|
||||
this.log(`Failed to resume task ${taskId}: ${error.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
getRecentTasks(): string[] {
|
||||
if (this.recentTasksCache) {
|
||||
return this.recentTasksCache
|
||||
|
|
@ -1881,7 +1895,7 @@ export class ClineProvider
|
|||
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000,
|
||||
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
|
||||
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
|
||||
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? false,
|
||||
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
|
||||
remoteControlEnabled: remoteControlEnabled ?? false,
|
||||
}
|
||||
}
|
||||
|
|
@ -2069,7 +2083,7 @@ export class ClineProvider
|
|||
includeDiagnosticMessages: stateValues.includeDiagnosticMessages ?? true,
|
||||
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
|
||||
// Add includeTaskHistoryInEnhance setting
|
||||
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? false,
|
||||
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? true,
|
||||
// Add remoteControlEnabled setting
|
||||
remoteControlEnabled: stateValues.remoteControlEnabled ?? false,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1343,7 +1343,7 @@ export const webviewMessageHandler = async (
|
|||
await provider.postStateToWebview()
|
||||
break
|
||||
case "includeTaskHistoryInEnhance":
|
||||
await updateGlobalState("includeTaskHistoryInEnhance", message.bool ?? false)
|
||||
await updateGlobalState("includeTaskHistoryInEnhance", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "condensingApiConfigId":
|
||||
|
|
|
|||
|
|
@ -78,6 +78,17 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
|
|||
await vscode.commands.executeCommand("workbench.action.files.saveFiles")
|
||||
await vscode.commands.executeCommand("workbench.action.closeWindow")
|
||||
break
|
||||
case TaskCommandName.ResumeTask:
|
||||
this.log(`[API] ResumeTask -> ${data}`)
|
||||
try {
|
||||
await this.resumeTask(data)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
this.log(`[API] ResumeTask failed for taskId ${data}: ${errorMessage}`)
|
||||
// Don't rethrow - we want to prevent IPC server crashes
|
||||
// The error is logged for debugging purposes
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/ca/common.json
generated
3
src/i18n/locales/ca/common.json
generated
|
|
@ -104,6 +104,9 @@
|
|||
"noResponseBody": "Error de l'API de Cerebras: No hi ha cos de resposta",
|
||||
"completionError": "Error de finalització de Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "El proveïdor Roo requereix autenticació al núvol. Si us plau, inicieu sessió a Roo Code Cloud."
|
||||
},
|
||||
"mode_import_failed": "Ha fallat la importació del mode: {{error}}"
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/de/common.json
generated
3
src/i18n/locales/de/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras API-Fehler ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras API-Fehler: Kein Antworttext vorhanden",
|
||||
"completionError": "Cerebras-Vervollständigungsfehler: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo-Anbieter erfordert Cloud-Authentifizierung. Bitte melde dich bei Roo Code Cloud an."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras API Error ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras API Error: No response body",
|
||||
"completionError": "Cerebras completion error: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo provider requires cloud authentication. Please sign in to Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/es/common.json
generated
3
src/i18n/locales/es/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Error de la API de Cerebras ({{status}}): {{message}}",
|
||||
"noResponseBody": "Error de la API de Cerebras: Sin cuerpo de respuesta",
|
||||
"completionError": "Error de finalización de Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "El proveedor Roo requiere autenticación en la nube. Por favor, inicia sesión en Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/fr/common.json
generated
3
src/i18n/locales/fr/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Erreur de l'API Cerebras ({{status}}) : {{message}}",
|
||||
"noResponseBody": "Erreur de l'API Cerebras : Aucun corps de réponse",
|
||||
"completionError": "Erreur d'achèvement de Cerebras : {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Le fournisseur Roo nécessite une authentification cloud. Veuillez vous connecter à Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/hi/common.json
generated
3
src/i18n/locales/hi/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras API त्रुटि ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras API त्रुटि: कोई प्रतिक्रिया मुख्य भाग नहीं",
|
||||
"completionError": "Cerebras पूर्णता त्रुटि: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo प्रदाता को क्लाउड प्रमाणीकरण की आवश्यकता है। कृपया Roo Code Cloud में साइन इन करें।"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/id/common.json
generated
3
src/i18n/locales/id/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Kesalahan API Cerebras ({{status}}): {{message}}",
|
||||
"noResponseBody": "Kesalahan API Cerebras: Tidak ada isi respons",
|
||||
"completionError": "Kesalahan penyelesaian Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Penyedia Roo memerlukan autentikasi cloud. Silakan masuk ke Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/it/common.json
generated
3
src/i18n/locales/it/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Errore API Cerebras ({{status}}): {{message}}",
|
||||
"noResponseBody": "Errore API Cerebras: Nessun corpo di risposta",
|
||||
"completionError": "Errore di completamento Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Il provider Roo richiede l'autenticazione cloud. Accedi a Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/ja/common.json
generated
3
src/i18n/locales/ja/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras APIエラー ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras APIエラー: レスポンスボディなし",
|
||||
"completionError": "Cerebras完了エラー: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Rooプロバイダーはクラウド認証が必要です。Roo Code Cloudにサインインしてください。"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/ko/common.json
generated
3
src/i18n/locales/ko/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras API 오류 ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras API 오류: 응답 본문 없음",
|
||||
"completionError": "Cerebras 완료 오류: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo 제공업체는 클라우드 인증이 필요합니다. Roo Code Cloud에 로그인하세요."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/nl/common.json
generated
3
src/i18n/locales/nl/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras API-fout ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras API-fout: Geen responslichaam",
|
||||
"completionError": "Cerebras-voltooiingsfout: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo provider vereist cloud authenticatie. Log in bij Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/pl/common.json
generated
3
src/i18n/locales/pl/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Błąd API Cerebras ({{status}}): {{message}}",
|
||||
"noResponseBody": "Błąd API Cerebras: Brak treści odpowiedzi",
|
||||
"completionError": "Błąd uzupełniania Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Dostawca Roo wymaga uwierzytelnienia w chmurze. Zaloguj się do Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/pt-BR/common.json
generated
3
src/i18n/locales/pt-BR/common.json
generated
|
|
@ -104,6 +104,9 @@
|
|||
"genericError": "Erro da API Cerebras ({{status}}): {{message}}",
|
||||
"noResponseBody": "Erro da API Cerebras: Sem corpo de resposta",
|
||||
"completionError": "Erro de conclusão do Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "O provedor Roo requer autenticação na nuvem. Faça login no Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/ru/common.json
generated
3
src/i18n/locales/ru/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Ошибка Cerebras API ({{status}}): {{message}}",
|
||||
"noResponseBody": "Ошибка Cerebras API: Нет тела ответа",
|
||||
"completionError": "Ошибка завершения Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Провайдер Roo требует облачной аутентификации. Войдите в Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/tr/common.json
generated
3
src/i18n/locales/tr/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Cerebras API Hatası ({{status}}): {{message}}",
|
||||
"noResponseBody": "Cerebras API Hatası: Yanıt gövdesi yok",
|
||||
"completionError": "Cerebras tamamlama hatası: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo sağlayıcısı bulut kimlik doğrulaması gerektirir. Lütfen Roo Code Cloud'a giriş yapın."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/vi/common.json
generated
3
src/i18n/locales/vi/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"genericError": "Lỗi API Cerebras ({{status}}): {{message}}",
|
||||
"noResponseBody": "Lỗi API Cerebras: Không có nội dung phản hồi",
|
||||
"completionError": "Lỗi hoàn thành Cerebras: {{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Nhà cung cấp Roo yêu cầu xác thực đám mây. Vui lòng đăng nhập vào Roo Code Cloud."
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/zh-CN/common.json
generated
3
src/i18n/locales/zh-CN/common.json
generated
|
|
@ -105,6 +105,9 @@
|
|||
"genericError": "Cerebras API 错误 ({{status}}):{{message}}",
|
||||
"noResponseBody": "Cerebras API 错误:无响应主体",
|
||||
"completionError": "Cerebras 完成错误:{{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo 提供商需要云认证。请登录 Roo Code Cloud。"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/zh-TW/common.json
generated
3
src/i18n/locales/zh-TW/common.json
generated
|
|
@ -100,6 +100,9 @@
|
|||
"noResponseBody": "Cerebras API 錯誤:無回應主體",
|
||||
"completionError": "Cerebras 完成錯誤:{{error}}"
|
||||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo 提供者需要雲端認證。請登入 Roo Code Cloud。"
|
||||
},
|
||||
"mode_import_failed": "匯入模式失敗:{{error}}"
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
|
|
@ -146,13 +146,11 @@ export class TerminalRegistry {
|
|||
* directory.
|
||||
*
|
||||
* @param cwd The working directory path
|
||||
* @param requiredCwd Whether the working directory is required (if false, may reuse any non-busy terminal)
|
||||
* @param taskId Optional task ID to associate with the terminal
|
||||
* @returns A Terminal instance
|
||||
*/
|
||||
public static async getOrCreateTerminal(
|
||||
cwd: string,
|
||||
requiredCwd: boolean = false,
|
||||
taskId?: string,
|
||||
provider: RooTerminalProvider = "vscode",
|
||||
): Promise<RooTerminal> {
|
||||
|
|
@ -194,12 +192,6 @@ export class TerminalRegistry {
|
|||
})
|
||||
}
|
||||
|
||||
// Third priority: Find any non-busy terminal (only if directory is not
|
||||
// required).
|
||||
if (!terminal && !requiredCwd) {
|
||||
terminal = terminals.find((t) => !t.busy && t.provider === provider)
|
||||
}
|
||||
|
||||
// If no suitable terminal found, create a new one.
|
||||
if (!terminal) {
|
||||
terminal = this.createTerminal(cwd, provider)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"displayName": "%extension.displayName%",
|
||||
"description": "%extension.description%",
|
||||
"publisher": "RooVeterinaryInc",
|
||||
"version": "3.25.15",
|
||||
"version": "3.25.20",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
|
|
@ -427,7 +427,7 @@
|
|||
"@mistralai/mistralai": "^1.3.6",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@qdrant/js-client-rest": "^1.14.0",
|
||||
"@roo-code/cloud": "^0.15.0",
|
||||
"@roo-code/cloud": "^0.19.0",
|
||||
"@roo-code/ipc": "workspace:^",
|
||||
"@roo-code/telemetry": "workspace:^",
|
||||
"@roo-code/types": "workspace:^",
|
||||
|
|
@ -458,6 +458,7 @@
|
|||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-ipc": "^12.0.0",
|
||||
"ollama": "^0.5.17",
|
||||
"openai": "^5.0.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-limit": "^6.2.0",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export class ProfileValidator {
|
|||
case "sambanova":
|
||||
case "chutes":
|
||||
case "fireworks":
|
||||
case "featherless":
|
||||
return profile.apiModelId
|
||||
case "litellm":
|
||||
return profile.litellmModelId
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ describe("ProfileValidator", () => {
|
|||
"chutes",
|
||||
"sambanova",
|
||||
"fireworks",
|
||||
"featherless",
|
||||
]
|
||||
|
||||
apiModelProviders.forEach((provider) => {
|
||||
|
|
|
|||
|
|
@ -375,12 +375,41 @@ describe("shouldUseReasoningEffort", () => {
|
|||
reasoningEffort: "medium",
|
||||
}
|
||||
|
||||
// Should return true regardless of settings
|
||||
// Should return true regardless of settings (unless explicitly disabled)
|
||||
expect(shouldUseReasoningEffort({ model })).toBe(true)
|
||||
expect(shouldUseReasoningEffort({ model, settings: {} })).toBe(true)
|
||||
expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: undefined } })).toBe(true)
|
||||
})
|
||||
|
||||
test("should return false when enableReasoningEffort is false, even if reasoningEffort is set", () => {
|
||||
const model: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const settings: ProviderSettings = {
|
||||
enableReasoningEffort: false,
|
||||
reasoningEffort: "medium",
|
||||
}
|
||||
|
||||
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
|
||||
})
|
||||
|
||||
test("should return false when enableReasoningEffort is false, even if model has reasoningEffort property", () => {
|
||||
const model: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
supportsPromptCache: true,
|
||||
reasoningEffort: "medium",
|
||||
}
|
||||
|
||||
const settings: ProviderSettings = {
|
||||
enableReasoningEffort: false,
|
||||
}
|
||||
|
||||
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
|
||||
})
|
||||
|
||||
test("should return true when model supports reasoning effort and settings provide reasoning effort", () => {
|
||||
const model: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
|
|
|
|||
|
|
@ -63,7 +63,17 @@ export const shouldUseReasoningEffort = ({
|
|||
}: {
|
||||
model: ModelInfo
|
||||
settings?: ProviderSettings
|
||||
}): boolean => (!!model.supportsReasoningEffort && !!settings?.reasoningEffort) || !!model.reasoningEffort
|
||||
}): boolean => {
|
||||
// If enableReasoningEffort is explicitly set to false, reasoning should be disabled
|
||||
if (settings?.enableReasoningEffort === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, use reasoning if:
|
||||
// 1. Model supports reasoning effort AND settings provide reasoning effort, OR
|
||||
// 2. Model itself has a reasoningEffort property
|
||||
return (!!model.supportsReasoningEffort && !!settings?.reasoningEffort) || !!model.reasoningEffort
|
||||
}
|
||||
|
||||
export const DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS = 16_384
|
||||
export const DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS = 8_192
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ export function checkExistKey(config: ProviderSettings | undefined) {
|
|||
return false
|
||||
}
|
||||
|
||||
// Special case for human-relay, fake-ai, and claude-code providers which don't need any configuration.
|
||||
if (config.apiProvider && ["human-relay", "fake-ai", "claude-code"].includes(config.apiProvider)) {
|
||||
// Special case for human-relay, fake-ai, claude-code, and roo providers which don't need any configuration.
|
||||
if (config.apiProvider && ["human-relay", "fake-ai", "claude-code", "roo"].includes(config.apiProvider)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ import { Trans } from "react-i18next"
|
|||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { Package } from "@roo/package"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@src/components/ui"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@src/components/ui"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
interface AnnouncementProps {
|
||||
hideAnnouncement: () => void
|
||||
|
|
@ -23,6 +25,7 @@ interface AnnouncementProps {
|
|||
const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const [open, setOpen] = useState(true)
|
||||
const { cloudIsAuthenticated } = useExtensionState()
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
|
@ -37,98 +40,64 @@ const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
|
|||
<DialogContent className="max-w-96">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("chat:announcement.title", { version: Package.version })}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("chat:announcement.description", { version: Package.version })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<h3>{t("chat:announcement.whatsNew")}</h3>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans
|
||||
i18nKey="chat:announcement.feature1"
|
||||
i18nKey="chat:announcement.stealthModel.feature"
|
||||
components={{
|
||||
bold: <b />,
|
||||
code: <code />,
|
||||
settingsLink: (
|
||||
<VSCodeLink
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
setOpen(false)
|
||||
hideAnnouncement()
|
||||
window.postMessage(
|
||||
{
|
||||
type: "action",
|
||||
action: "settingsButtonClicked",
|
||||
values: { section: "codebaseIndexing" },
|
||||
},
|
||||
"*",
|
||||
)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans
|
||||
i18nKey="chat:announcement.feature2"
|
||||
components={{
|
||||
bold: <b />,
|
||||
code: <code />,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans
|
||||
i18nKey="chat:announcement.feature3"
|
||||
components={{
|
||||
bold: <b />,
|
||||
code: <code />,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<Trans
|
||||
i18nKey="chat:announcement.detailsDiscussLinks"
|
||||
components={{ discordLink: <DiscordLink />, redditLink: <RedditLink /> }}
|
||||
/>
|
||||
|
||||
<p className="text-xs text-muted-foreground mt-2">{t("chat:announcement.stealthModel.note")}</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{!cloudIsAuthenticated ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "rooCloudSignIn" })
|
||||
}}
|
||||
className="w-full">
|
||||
{t("chat:announcement.stealthModel.connectButton")}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="text-sm w-full">
|
||||
<Trans
|
||||
i18nKey="chat:announcement.stealthModel.selectModel"
|
||||
components={{
|
||||
code: <code className="px-1 py-0.5 bg-gray-100 dark:bg-gray-800 rounded" />,
|
||||
settingsLink: (
|
||||
<VSCodeLink
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
setOpen(false)
|
||||
hideAnnouncement()
|
||||
window.postMessage(
|
||||
{
|
||||
type: "action",
|
||||
action: "settingsButtonClicked",
|
||||
values: { section: "provider" },
|
||||
},
|
||||
"*",
|
||||
)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const DiscordLink = () => (
|
||||
<VSCodeLink
|
||||
href="https://discord.gg/roocode"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.postMessage(
|
||||
{ type: "action", action: "openExternal", data: { url: "https://discord.gg/roocode" } },
|
||||
"*",
|
||||
)
|
||||
}}>
|
||||
Discord
|
||||
</VSCodeLink>
|
||||
)
|
||||
|
||||
const RedditLink = () => (
|
||||
<VSCodeLink
|
||||
href="https://reddit.com/r/RooCode"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.postMessage(
|
||||
{ type: "action", action: "openExternal", data: { url: "https://reddit.com/r/RooCode" } },
|
||||
"*",
|
||||
)
|
||||
}}>
|
||||
Reddit
|
||||
</VSCodeLink>
|
||||
)
|
||||
|
||||
export default memo(Announcement)
|
||||
|
|
|
|||
|
|
@ -11,17 +11,27 @@ vi.mock("@src/components/ui", () => ({
|
|||
DialogDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
DialogHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
DialogFooter: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
Button: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => (
|
||||
<button onClick={onClick}>{children}</button>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock the useAppTranslation hook
|
||||
// Mock the useAppTranslation hook and Trans component
|
||||
vi.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string, options?: { version: string }) => {
|
||||
if (key === "chat:announcement.title") {
|
||||
return `🎉 Roo Code ${options?.version} Released`
|
||||
}
|
||||
if (key === "chat:announcement.description") {
|
||||
return `Roo Code ${options?.version} brings powerful new features and improvements based on your feedback.`
|
||||
if (key === "chat:announcement.stealthModel.feature") {
|
||||
return "Stealth reasoning model with advanced capabilities"
|
||||
}
|
||||
if (key === "chat:announcement.stealthModel.note") {
|
||||
return "Note: This is an experimental feature"
|
||||
}
|
||||
if (key === "chat:announcement.stealthModel.connectButton") {
|
||||
return "Connect to Roo Code Cloud"
|
||||
}
|
||||
// Return key for other translations not relevant to this test
|
||||
return key
|
||||
|
|
@ -29,6 +39,34 @@ vi.mock("@src/i18n/TranslationContext", () => ({
|
|||
}),
|
||||
}))
|
||||
|
||||
// Mock react-i18next Trans component
|
||||
vi.mock("react-i18next", () => ({
|
||||
Trans: ({ i18nKey, children }: { i18nKey?: string; children: React.ReactNode }) => {
|
||||
if (i18nKey === "chat:announcement.stealthModel.feature") {
|
||||
return <>Stealth reasoning model with advanced capabilities</>
|
||||
}
|
||||
if (i18nKey === "chat:announcement.stealthModel.selectModel") {
|
||||
return <>Please select the roo/sonic model in settings</>
|
||||
}
|
||||
return <>{children}</>
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock VSCodeLink
|
||||
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeLink: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => (
|
||||
<a onClick={onClick}>{children}</a>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock the useExtensionState hook
|
||||
vi.mock("@src/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
apiConfiguration: null,
|
||||
cloudIsAuthenticated: false,
|
||||
}),
|
||||
}))
|
||||
|
||||
describe("Announcement", () => {
|
||||
const mockHideAnnouncement = vi.fn()
|
||||
const expectedVersion = Package.version
|
||||
|
|
@ -36,12 +74,16 @@ describe("Announcement", () => {
|
|||
it("renders the announcement with the version number from package.json", () => {
|
||||
render(<Announcement hideAnnouncement={mockHideAnnouncement} />)
|
||||
|
||||
// Check if the mocked version number is present in the title and description
|
||||
// Check if the mocked version number is present in the title
|
||||
expect(screen.getByText(`🎉 Roo Code ${expectedVersion} Released`)).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(
|
||||
`Roo Code ${expectedVersion} brings powerful new features and improvements based on your feedback.`,
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
|
||||
// Check if the stealth model feature is displayed (using partial match due to bullet point)
|
||||
expect(screen.getByText(/Stealth reasoning model with advanced capabilities/)).toBeInTheDocument()
|
||||
|
||||
// Check if the note is displayed
|
||||
expect(screen.getByText("Note: This is an experimental feature")).toBeInTheDocument()
|
||||
|
||||
// Check if the connect button is displayed (since cloudIsAuthenticated is false in the mock)
|
||||
expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { convertHeadersToObject } from "./utils/headers"
|
||||
import { useDebounce } from "react-use"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeLink, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ExternalLinkIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import {
|
||||
|
|
@ -31,7 +31,9 @@ import {
|
|||
internationalZAiDefaultModelId,
|
||||
mainlandZAiDefaultModelId,
|
||||
fireworksDefaultModelId,
|
||||
featherlessDefaultModelId,
|
||||
ioIntelligenceDefaultModelId,
|
||||
rooDefaultModelId,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
|
@ -86,6 +88,7 @@ import {
|
|||
XAI,
|
||||
ZAi,
|
||||
Fireworks,
|
||||
Featherless,
|
||||
} from "./providers"
|
||||
|
||||
import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants"
|
||||
|
|
@ -124,7 +127,7 @@ const ApiOptions = ({
|
|||
setErrorMessage,
|
||||
}: ApiOptionsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const { organizationAllowList } = useExtensionState()
|
||||
const { organizationAllowList, cloudIsAuthenticated } = useExtensionState()
|
||||
|
||||
const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => {
|
||||
const headers = apiConfiguration?.openAiHeaders || {}
|
||||
|
|
@ -326,7 +329,9 @@ const ApiOptions = ({
|
|||
: internationalZAiDefaultModelId,
|
||||
},
|
||||
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
|
||||
featherless: { field: "apiModelId", default: featherlessDefaultModelId },
|
||||
"io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId },
|
||||
roo: { field: "apiModelId", default: rooDefaultModelId },
|
||||
openai: { field: "openAiModelId" },
|
||||
ollama: { field: "ollamaModelId" },
|
||||
lmstudio: { field: "lmStudioModelId" },
|
||||
|
|
@ -579,6 +584,29 @@ const ApiOptions = ({
|
|||
<Fireworks apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
||||
{selectedProvider === "roo" && (
|
||||
<div className="flex flex-col gap-3">
|
||||
{cloudIsAuthenticated ? (
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.roo.authenticatedMessage")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={() => vscode.postMessage({ type: "rooCloudSignIn" })}
|
||||
className="w-fit">
|
||||
{t("settings:providers.roo.connectButton")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "featherless" && (
|
||||
<Featherless apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
||||
{selectedProviderModels.length > 0 && (
|
||||
<>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export const ModelDescriptionMarkdown = memo(
|
|||
|
||||
return (
|
||||
<Collapsible open={isExpanded} onOpenChange={setIsExpanded} className="relative">
|
||||
<div ref={textContainerRef} className={cn({ "line-clamp-3": !isExpanded })}>
|
||||
<div ref={textContainerRef} className={cn({ "line-clamp-4": !isExpanded })}>
|
||||
<div ref={textRef}>
|
||||
<StyledMarkdown key={key}>{content}</StyledMarkdown>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,18 @@ export const ModelInfoView = ({
|
|||
const { t } = useAppTranslation()
|
||||
|
||||
const infoItems = [
|
||||
typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && (
|
||||
<>
|
||||
<span className="font-medium">{t("settings:modelInfo.contextWindow")}</span>{" "}
|
||||
{modelInfo.contextWindow?.toLocaleString()} tokens
|
||||
</>
|
||||
),
|
||||
typeof modelInfo?.maxTokens === "number" && modelInfo.maxTokens > 0 && (
|
||||
<>
|
||||
<span className="font-medium">{t("settings:modelInfo.maxOutput")}:</span>{" "}
|
||||
{modelInfo.maxTokens?.toLocaleString()} tokens
|
||||
</>
|
||||
),
|
||||
<ModelInfoSupportsItem
|
||||
isSupported={modelInfo?.supportsImages ?? false}
|
||||
supportsLabel={t("settings:modelInfo.supportsImages")}
|
||||
|
|
@ -41,18 +53,6 @@ export const ModelInfoView = ({
|
|||
supportsLabel={t("settings:modelInfo.supportsPromptCache")}
|
||||
doesNotSupportLabel={t("settings:modelInfo.noPromptCache")}
|
||||
/>,
|
||||
typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && (
|
||||
<>
|
||||
<span className="font-medium">{t("settings:modelInfo.contextWindow")}</span>{" "}
|
||||
{modelInfo.contextWindow?.toLocaleString()} tokens
|
||||
</>
|
||||
),
|
||||
typeof modelInfo?.maxTokens === "number" && modelInfo.maxTokens > 0 && (
|
||||
<>
|
||||
<span className="font-medium">{t("settings:modelInfo.maxOutput")}:</span>{" "}
|
||||
{modelInfo.maxTokens?.toLocaleString()} tokens
|
||||
</>
|
||||
),
|
||||
modelInfo?.inputPrice !== undefined && modelInfo.inputPrice > 0 && (
|
||||
<>
|
||||
<span className="font-medium">{t("settings:modelInfo.inputPrice")}:</span>{" "}
|
||||
|
|
@ -119,11 +119,7 @@ const ModelInfoSupportsItem = ({
|
|||
supportsLabel: string
|
||||
doesNotSupportLabel: string
|
||||
}) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1 font-medium",
|
||||
isSupported ? "text-vscode-charts-green" : "text-vscode-errorForeground",
|
||||
)}>
|
||||
<div className="flex items-center gap-1 font-medium">
|
||||
<span className={cn("codicon", isSupported ? "codicon-check" : "codicon-x")} />
|
||||
{isSupported ? supportsLabel : doesNotSupportLabel}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ const PromptsSettings = ({
|
|||
} = useExtensionState()
|
||||
|
||||
// Use props if provided, otherwise fall back to context
|
||||
const includeTaskHistoryInEnhance = propsIncludeTaskHistoryInEnhance ?? contextIncludeTaskHistoryInEnhance
|
||||
const includeTaskHistoryInEnhance = propsIncludeTaskHistoryInEnhance ?? contextIncludeTaskHistoryInEnhance ?? true
|
||||
const setIncludeTaskHistoryInEnhance = propsSetIncludeTaskHistoryInEnhance ?? contextSetIncludeTaskHistoryInEnhance
|
||||
|
||||
const [testPrompt, setTestPrompt] = useState("")
|
||||
|
|
@ -235,7 +235,7 @@ const PromptsSettings = ({
|
|||
<>
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={includeTaskHistoryInEnhance || false}
|
||||
checked={includeTaskHistoryInEnhance}
|
||||
onChange={(e: any) => {
|
||||
const value = e.target.checked
|
||||
setIncludeTaskHistoryInEnhance(value)
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
vscode.postMessage({ type: "condensingApiConfigId", text: condensingApiConfigId || "" })
|
||||
vscode.postMessage({ type: "updateCondensingPrompt", text: customCondensingPrompt || "" })
|
||||
vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} })
|
||||
vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? false })
|
||||
vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? true })
|
||||
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
|
||||
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
|
||||
vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import {
|
|||
doubaoModels,
|
||||
internationalZAiModels,
|
||||
fireworksModels,
|
||||
rooModels,
|
||||
featherlessModels,
|
||||
} from "@roo-code/types"
|
||||
|
||||
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
|
||||
|
|
@ -38,6 +40,8 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
|
|||
sambanova: sambaNovaModels,
|
||||
zai: internationalZAiModels,
|
||||
fireworks: fireworksModels,
|
||||
roo: rooModels,
|
||||
featherless: featherlessModels,
|
||||
}
|
||||
|
||||
export const PROVIDERS = [
|
||||
|
|
@ -69,5 +73,7 @@ export const PROVIDERS = [
|
|||
{ value: "sambanova", label: "SambaNova" },
|
||||
{ value: "zai", label: "Z AI" },
|
||||
{ value: "fireworks", label: "Fireworks AI" },
|
||||
{ value: "featherless", label: "Featherless AI" },
|
||||
{ value: "io-intelligence", label: "IO Intelligence" },
|
||||
{ value: "roo", label: "Roo Code Cloud" },
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
|
|
|||
50
webview-ui/src/components/settings/providers/Featherless.tsx
Normal file
50
webview-ui/src/components/settings/providers/Featherless.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { useCallback } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import type { ProviderSettings } from "@roo-code/types"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
|
||||
type FeatherlessProps = {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
}
|
||||
|
||||
export const Featherless = ({ apiConfiguration, setApiConfigurationField }: FeatherlessProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
field: K,
|
||||
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
|
||||
) =>
|
||||
(event: E | Event) => {
|
||||
setApiConfigurationField(field, transform(event as E))
|
||||
},
|
||||
[setApiConfigurationField],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.featherlessApiKey || ""}
|
||||
type="password"
|
||||
onInput={handleInputChange("featherlessApiKey")}
|
||||
placeholder={t("settings:placeholders.apiKey")}
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.featherlessApiKey")}</label>
|
||||
</VSCodeTextField>
|
||||
<div className="text-sm text-vscode-descriptionForeground -mt-2">
|
||||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.featherlessApiKey && (
|
||||
<VSCodeButtonLink href="https://featherless.ai/account/api-keys" appearance="secondary">
|
||||
{t("settings:providers.getFeatherlessApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -26,3 +26,4 @@ export { XAI } from "./XAI"
|
|||
export { ZAi } from "./ZAi"
|
||||
export { LiteLLM } from "./LiteLLM"
|
||||
export { Fireworks } from "./Fireworks"
|
||||
export { Featherless } from "./Featherless"
|
||||
|
|
|
|||
|
|
@ -46,8 +46,12 @@ import {
|
|||
mainlandZAiModels,
|
||||
fireworksModels,
|
||||
fireworksDefaultModelId,
|
||||
featherlessModels,
|
||||
featherlessDefaultModelId,
|
||||
ioIntelligenceDefaultModelId,
|
||||
ioIntelligenceModels,
|
||||
rooDefaultModelId,
|
||||
rooModels,
|
||||
BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
|
||||
} from "@roo-code/types"
|
||||
|
||||
|
|
@ -290,12 +294,22 @@ function getSelectedModel({
|
|||
const info = fireworksModels[id as keyof typeof fireworksModels]
|
||||
return { id, info }
|
||||
}
|
||||
case "featherless": {
|
||||
const id = apiConfiguration.apiModelId ?? featherlessDefaultModelId
|
||||
const info = featherlessModels[id as keyof typeof featherlessModels]
|
||||
return { id, info }
|
||||
}
|
||||
case "io-intelligence": {
|
||||
const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId
|
||||
const info =
|
||||
routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels]
|
||||
return { id, info }
|
||||
}
|
||||
case "roo": {
|
||||
const id = apiConfiguration.apiModelId ?? rooDefaultModelId
|
||||
const info = rooModels[id as keyof typeof rooModels]
|
||||
return { id, info }
|
||||
}
|
||||
// case "anthropic":
|
||||
// case "human-relay":
|
||||
// case "fake-ai":
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
project: {},
|
||||
global: {},
|
||||
})
|
||||
const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(false)
|
||||
const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true)
|
||||
|
||||
const setListApiConfigMeta = useCallback(
|
||||
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
|
||||
|
|
|
|||
10
webview-ui/src/i18n/locales/ca/chat.json
generated
10
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -265,6 +265,12 @@
|
|||
},
|
||||
"announcement": {
|
||||
"title": "🎉 Roo Code {{version}} Llançat",
|
||||
"stealthModel": {
|
||||
"feature": "<bold>Model stealth GRATUÏT per temps limitat</bold> - Un model de raonament ultraràpid que destaca en codificació agèntica amb una finestra de context de 262k, disponible a través de Roo Code Cloud.",
|
||||
"note": "(Nota: els prompts i completacions són registrats pel creador del model i utilitzats per millorar-lo)",
|
||||
"connectButton": "Connectar a Roo Code Cloud",
|
||||
"selectModel": "Selecciona <code>roo/sonic</code> del proveïdor Roo Code Cloud a<br/><settingsLink>Configuració</settingsLink> per començar"
|
||||
},
|
||||
"description": "Roo Code {{version}} porta noves funcions potents i millores significatives per millorar el vostre flux de treball de desenvolupament.",
|
||||
"whatsNew": "Novetats",
|
||||
"feature1": "<bold>Cua de Missatges</bold>: Posa en cua múltiples missatges mentre Roo està treballant, permetent-te continuar planificant el teu flux de treball sense interrupcions.",
|
||||
|
|
@ -349,9 +355,9 @@
|
|||
"ariaLabel": "Versió {{version}} - Feu clic per veure les notes de llançament"
|
||||
},
|
||||
"rooCloudCTA": {
|
||||
"title": "Roo Code Cloud arribarà aviat!",
|
||||
"title": "Roo Code Cloud està evolucionant!",
|
||||
"description": "Executa agents remots al núvol, accedeix a les teves tasques des de qualsevol lloc, col·labora amb altres i molt més.",
|
||||
"joinWaitlist": "Uneix-te a la llista d'espera per obtenir accés anticipat."
|
||||
"joinWaitlist": "Registra't per rebre les últimes actualitzacions."
|
||||
},
|
||||
"editMessage": {
|
||||
"placeholder": "Edita el teu missatge..."
|
||||
|
|
|
|||
6
webview-ui/src/i18n/locales/ca/settings.json
generated
6
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -269,6 +269,8 @@
|
|||
"getChutesApiKey": "Obtenir clau API de Chutes",
|
||||
"fireworksApiKey": "Clau API de Fireworks",
|
||||
"getFireworksApiKey": "Obtenir clau API de Fireworks",
|
||||
"featherlessApiKey": "Clau API de Featherless",
|
||||
"getFeatherlessApiKey": "Obtenir clau API de Featherless",
|
||||
"ioIntelligenceApiKey": "Clau API d'IO Intelligence",
|
||||
"ioIntelligenceApiKeyPlaceholder": "Introdueix la teva clau d'API de IO Intelligence",
|
||||
"getIoIntelligenceApiKey": "Obtenir clau API d'IO Intelligence",
|
||||
|
|
@ -377,6 +379,10 @@
|
|||
"description": "No es requereix clau API, però l'usuari necessita ajuda per copiar i enganxar informació al xat d'IA web.",
|
||||
"instructions": "Durant l'ús, apareixerà un diàleg i el missatge actual es copiarà automàticament al porta-retalls. Necessiteu enganxar-lo a les versions web d'IA (com ChatGPT o Claude), després copiar la resposta de l'IA de nou al diàleg i fer clic al botó de confirmació."
|
||||
},
|
||||
"roo": {
|
||||
"authenticatedMessage": "Autenticat de forma segura a través del teu compte de Roo Code Cloud.",
|
||||
"connectButton": "Connecta amb Roo Code Cloud"
|
||||
},
|
||||
"openRouter": {
|
||||
"providerRouting": {
|
||||
"title": "Encaminament de Proveïdors d'OpenRouter",
|
||||
|
|
|
|||
10
webview-ui/src/i18n/locales/de/chat.json
generated
10
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -265,6 +265,12 @@
|
|||
},
|
||||
"announcement": {
|
||||
"title": "🎉 Roo Code {{version}} veröffentlicht",
|
||||
"stealthModel": {
|
||||
"feature": "<bold>Zeitlich begrenztes KOSTENLOSES Stealth-Modell</bold> - Ein blitzschnelles Reasoning-Modell, das sich bei agentic coding mit einem 262k Kontextfenster auszeichnet, verfügbar über Roo Code Cloud.",
|
||||
"note": "(Hinweis: Prompts und Vervollständigungen werden vom Modellersteller protokolliert und zur Verbesserung des Modells verwendet)",
|
||||
"connectButton": "Mit Roo Code Cloud verbinden",
|
||||
"selectModel": "Wähle <code>roo/sonic</code> vom Roo Code Cloud Provider in<br/><settingsLink>Einstellungen</settingsLink> um zu beginnen"
|
||||
},
|
||||
"description": "Roo Code {{version}} bringt mächtige neue Funktionen und bedeutende Verbesserungen, um deinen Entwicklungsworkflow zu verbessern.",
|
||||
"whatsNew": "Was ist neu",
|
||||
"feature1": "<bold>Nachrichten-Warteschlange</bold>: Stelle mehrere Nachrichten in die Warteschlange, während Roo arbeitet, damit du deinen Workflow ohne Unterbrechung weiter planen kannst.",
|
||||
|
|
@ -349,9 +355,9 @@
|
|||
"ariaLabel": "Version {{version}} - Klicken Sie, um die Versionshinweise anzuzeigen"
|
||||
},
|
||||
"rooCloudCTA": {
|
||||
"title": "Roo Code Cloud kommt bald!",
|
||||
"title": "Roo Code Cloud entwickelt sich weiter!",
|
||||
"description": "Führe Remote-Agenten in der Cloud aus, greife von überall auf deine Aufgaben zu, arbeite mit anderen zusammen und vieles mehr.",
|
||||
"joinWaitlist": "Tritt der Warteliste bei, um frühen Zugang zu erhalten."
|
||||
"joinWaitlist": "Melde dich an, um die neuesten Updates zu erhalten."
|
||||
},
|
||||
"command": {
|
||||
"triggerDescription": "Starte den {{name}} Befehl"
|
||||
|
|
|
|||
6
webview-ui/src/i18n/locales/de/settings.json
generated
6
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -271,6 +271,8 @@
|
|||
"getChutesApiKey": "Chutes API-Schlüssel erhalten",
|
||||
"fireworksApiKey": "Fireworks API-Schlüssel",
|
||||
"getFireworksApiKey": "Fireworks API-Schlüssel erhalten",
|
||||
"featherlessApiKey": "Featherless API-Schlüssel",
|
||||
"getFeatherlessApiKey": "Featherless API-Schlüssel erhalten",
|
||||
"ioIntelligenceApiKey": "IO Intelligence API-Schlüssel",
|
||||
"ioIntelligenceApiKeyPlaceholder": "Gib deinen IO Intelligence API-Schlüssel ein",
|
||||
"getIoIntelligenceApiKey": "IO Intelligence API-Schlüssel erhalten",
|
||||
|
|
@ -377,6 +379,10 @@
|
|||
"description": "Es ist kein API-Schlüssel erforderlich, aber der Benutzer muss beim Kopieren und Einfügen der Informationen in den Web-Chat-KI helfen.",
|
||||
"instructions": "Während der Verwendung wird ein Dialogfeld angezeigt und die aktuelle Nachricht wird automatisch in die Zwischenablage kopiert. Du musst diese in Web-Versionen von KI (wie ChatGPT oder Claude) einfügen, dann die Antwort der KI zurück in das Dialogfeld kopieren und auf die Bestätigungsschaltfläche klicken."
|
||||
},
|
||||
"roo": {
|
||||
"authenticatedMessage": "Sicher authentifiziert über dein Roo Code Cloud-Konto.",
|
||||
"connectButton": "Mit Roo Code Cloud verbinden"
|
||||
},
|
||||
"openRouter": {
|
||||
"providerRouting": {
|
||||
"title": "OpenRouter Anbieter-Routing",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue