Merge main to ollama_improvements: resolve conflicts in Ollama i18n implementation

This commit is contained in:
randomizedcoder dave.seddon.ca@gmail.com 2026-01-29 10:24:22 -08:00
commit 8a6b4b58ad
619 changed files with 27836 additions and 27165 deletions

View file

@ -8,6 +8,10 @@ on:
- 'apps/web-roo-code/**'
workflow_dispatch:
concurrency:
group: deploy-roocode-com
cancel-in-progress: true
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
@ -36,8 +40,17 @@ jobs:
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run lint
run: pnpm lint
working-directory: apps/web-roo-code
- name: Run type check
run: pnpm check-types
working-directory: apps/web-roo-code
- name: Run build
run: pnpm build
working-directory: apps/web-roo-code
- name: Install Vercel CLI
run: npm install --global vercel@canary
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information
run: npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts

View file

@ -11,6 +11,10 @@ on:
- "apps/web-roo-code/**"
workflow_dispatch:
concurrency:
group: preview-roocode-com-${{ github.ref }}
cancel-in-progress: true
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
@ -39,8 +43,17 @@ jobs:
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run lint
run: pnpm lint
working-directory: apps/web-roo-code
- name: Run type check
run: pnpm check-types
working-directory: apps/web-roo-code
- name: Run build
run: pnpm build
working-directory: apps/web-roo-code
- name: Install Vercel CLI
run: npm install --global vercel@canary
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information
run: npx vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
@ -70,15 +83,20 @@ jobs:
comment.body.includes(commentIdentifier)
);
if (existingComment) {
return;
}
const comment = commentIdentifier + '\n🚀 **Preview deployed!**\n\nYour changes have been deployed to Vercel:\n\n**Preview URL:** ' + deploymentUrl + '\n\nThis preview will be updated automatically when you push new commits to this PR.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}

5
AGENTS.md Normal file
View file

@ -0,0 +1,5 @@
# AGENTS.md
This file provides guidance to agents when working with code in this repository.
- Settings View Pattern: When working on `SettingsView`, inputs must bind to the local `cachedState`, NOT the live `useExtensionState()`. The `cachedState` acts as a buffer for user edits, isolating them from the `ContextProxy` source-of-truth until the user explicitly clicks "Save". Wiring inputs directly to the live state causes race conditions.

View file

@ -1,5 +1,98 @@
# Roo Code Changelog
## [3.45.0] - 2026-01-27
![3.45.0 Release - Smart Code Folding](/releases/3.45.0-release.png)
- Smart Code Folding: Context condensation now intelligently preserves a lightweight map of files you worked on—function signatures, class declarations, and type definitions—so Roo can continue referencing them accurately after condensing. Files are prioritized by most recent access, with a ~50k character budget ensuring your latest work is always preserved. (Idea by @shariqriazz, PR #10942 by @hannesrudolph)
## [3.44.2] - 2026-01-27
- Re-enable parallel tool calling with new_task isolation safeguards (PR #11006 by @mrubens)
- Fix worktree indexing by using relative paths in isPathInIgnoredDirectory (PR #11009 by @daniel-lxs)
- Fix local model validation error for Ollama models (PR #10893 by @roomote)
- Fix duplicate tool_call emission from Responses API providers (PR #11008 by @daniel-lxs)
## [3.44.1] - 2026-01-27
- Fix LiteLLM tool ID validation errors for Bedrock proxy (PR #10990 by @daniel-lxs)
- Add temperature=0.9 and top_p=0.95 to zai-glm-4.7 model for better generation quality (PR #10945 by @sebastiand-cerebras)
- Add quality checks to marketing site deployment workflows (PR #10959 by @mp-roocode)
## [3.44.0] - 2026-01-26
![3.44.0 Release - Worktrees](/releases/3.44.0-release.png)
- Add worktree selector and creation UX (PR #10940 by @brunobergher, thanks Cline!)
- Improve subtask visibility and navigation in history and chat views (PR #10864 by @brunobergher)
- Add wildcard support for MCP alwaysAllow configuration (PR #10948 by @app/roomote)
- Fix: Prevent nested condensing from including previously-condensed content (PR #10985 by @hannesrudolph)
- Fix: VS Code LM token counting returns 0 outside requests, breaking context condensing (#10968 by @srulyt, PR #10983 by @daniel-lxs)
- Fix: Record truncation event when condensation fails but truncation succeeds (PR #10984 by @hannesrudolph)
- Replace hyphen encoding with fuzzy matching for MCP tool names (PR #10775 by @daniel-lxs)
- Remove MCP SERVERS section from system prompt for cleaner prompts (PR #10895 by @daniel-lxs)
- new_task tool creates checkpoint the same way write_to_file does (PR #10982 by @daniel-lxs)
- Update Fireworks provider with new models (#10674 by @hannesrudolph, PR #10679 by @ThanhNguyxn)
- Fix: Truncate AWS Bedrock toolUseId to 64 characters (PR #10902 by @daniel-lxs)
- Fix: Restore opaque background to settings section headers (PR #10951 by @app/roomote)
- Fix: Remove unsupported Fireworks model tool fields (PR #10937 by @app/roomote)
- Update and improve zh-TW Traditional Chinese locale and docs (PR #10953 by @PeterDaveHello)
- Chore: Remove POWER_STEERING experiment remnants (PR #10980 by @hannesrudolph)
## [3.43.0] - 2026-01-23
![3.43.0 Release - Intelligent Context Condensation](/releases/3.43.0-release.png)
- Intelligent Context Condensation v2: New context condensation system that intelligently summarizes conversation history when approaching context limits, preserving important information while reducing token usage (PR #10873 by @hannesrudolph)
- Improved context condensation with environment details, accurate token counts, and lazy evaluation for better performance (PR #10920 by @hannesrudolph)
- Move condense prompt editor to Context Management tab for better discoverability and organization (PR #10909 by @hannesrudolph)
- Update Z.AI models with new variants and pricing (#10859 by @ErdemGKSL, PR #10860 by @ErdemGKSL)
- Add pnpm install:vsix:nightly command for easier nightly build installation (PR #10912 by @hannesrudolph)
- Fix: Convert orphaned tool_results to text blocks after condensing to prevent API errors (PR #10927 by @daniel-lxs)
- Fix: Auto-migrate v1 condensing prompt and handle invalid providers on import (PR #10931 by @hannesrudolph)
- Fix: Use json-stream-stringify for pretty-printing MCP config files to prevent memory issues with large configs (#9862 by @Michaelzag, PR #9864 by @Michaelzag)
- Fix: Correct Gemini 3 pricing for Flash and Pro models (#10432 by @rossdonald, PR #10487 by @roomote)
- Fix: Skip thoughtSignature blocks during markdown export for cleaner output (#10199 by @rossdonald, PR #10932 by @rossdonald)
- Fix: Duplicate model display for OpenAI Codex provider (PR #10930 by @roomote)
- Remove diffEnabled and fuzzyMatchThreshold settings as they are no longer needed (#10648 by @hannesrudolph, PR #10298 by @hannesrudolph)
- Remove MULTI_FILE_APPLY_DIFF experiment (PR #10925 by @hannesrudolph)
- Remove POWER_STEERING experimental feature (PR #10926 by @hannesrudolph)
- Remove legacy XML tool calling code (getToolDescription) for cleaner codebase (PR #10929 by @hannesrudolph)
## [3.42.0] - 2026-01-22
![3.42.0 Release - ChatGPT Usage Tracking](/releases/3.42.0-release.png)
- Added UI to track your ChatGPT usage limits in the OpenAI Codex provider (PR #10813 by @hannesrudolph)
- Removed deprecated Claude Code provider (PR #10883 by @daniel-lxs)
- Streamlined codebase by removing legacy XML tool calling functionality (#10848 by @hannesrudolph, PR #10841 by @hannesrudolph)
- Standardize model selectors across all providers: Improved consistency of model selection UI (#10650 by @hannesrudolph, PR #10294 by @hannesrudolph)
- Enable prompt caching for Cerebras zai-glm-4.7 model (#10601 by @jahanson, PR #10670 by @app/roomote)
- Add Kimi K2 thinking model to VertexAI provider (#9268 by @diwakar-s-maurya, PR #9269 by @app/roomote)
- Warn users when too many MCP tools are enabled (PR #10772 by @app/roomote)
- Migrate context condensing prompt to customSupportPrompts (PR #10881 by @hannesrudolph)
- Unify export path logic and default to Downloads folder (PR #10882 by @hannesrudolph)
- Performance improvements for webview state synchronization (PR #10842 by @hannesrudolph)
- Fix: Handle mode selector empty state on workspace switch (#10660 by @hannesrudolph, PR #9674 by @app/roomote)
- Fix: Resolve race condition in context condensing prompt input (PR #10876 by @hannesrudolph)
- Fix: Prevent double emission of text/reasoning in OpenAI native and codex handlers (PR #10888 by @hannesrudolph)
- Fix: Prevent task abortion when resuming via IPC/bridge (PR #10892 by @cte)
- Fix: Enforce file restrictions for all editing tools (PR #10896 by @app/roomote)
- Fix: Remove custom condensing model option (PR #10901 by @hannesrudolph)
- Unify user content tags to <user_message> for consistent prompt formatting (#10658 by @hannesrudolph, PR #10723 by @app/roomote)
- Clarify linked SKILL.md file handling in prompts (PR #10907 by @hannesrudolph)
- Fix: Padding on Roo Code Cloud teaser (PR #10889 by @app/roomote)
## [3.41.3] - 2026-01-18
- Fix: Thinking block word-breaking to prevent horizontal scroll in the chat UI (PR #10806 by @roomote)
- Add Claude-like CLI flags and authentication fixes for the Roo Code CLI (PR #10797 by @cte)
- Improve CLI authentication by using a redirect instead of a fetch (PR #10799 by @cte)
- Fix: Roo Code Router fixes for the CLI (PR #10789 by @cte)
- Release CLI v0.0.48 with latest improvements (PR #10800 by @cte)
- Release CLI v0.0.47 (PR #10798 by @cte)
- Revert E2E tests enablement to address stability issues (PR #10794 by @cte)
## [3.41.2] - 2026-01-16
- Add button to open markdown in VSCode preview for easier reading of formatted content (PR #10773 by @brunobergher)

View file

@ -5,6 +5,17 @@ All notable changes to the `@roo-code/cli` package will be documented in this fi
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.49] - 2026-01-18
### Added
- **Output Format Options**: New `--output-format` flag to control CLI output format for scripting and automation:
- `text` (default) - Human-readable interactive output
- `json` - Single JSON object with all events and final result at task completion
- `stream-json` - NDJSON (newline-delimited JSON) for real-time streaming of events
- See [`json-events.ts`](src/types/json-events.ts) for the complete event schema
- New [`JsonEventEmitter`](src/agent/json-event-emitter.ts) for structured output generation
## [0.0.48] - 2026-01-17
### Changed

View file

@ -71,7 +71,7 @@ By default, the CLI prompts for approval before executing actions:
```bash
export OPENROUTER_API_KEY=sk-or-v1-...
roo ~/Documents/my-project -P "What is this project?"
roo "What is this project?" -w ~/Documents/my-project
```
You can also run without a prompt and enter it interactively in TUI mode:
@ -92,7 +92,7 @@ In interactive mode:
For automation and scripts, use `-y` to auto-approve all actions:
```bash
roo ~/Documents/my-project -y -P "Refactor the utils.ts file"
roo "Refactor the utils.ts file" -y -w ~/Documents/my-project
```
In non-interactive mode:
@ -149,8 +149,8 @@ Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when yo
| Option | Description | Default |
| --------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------- |
| `[workspace]` | Workspace path to operate in (positional argument) | Current directory |
| `-P, --prompt <prompt>` | The prompt/task to execute (optional in TUI mode) | None |
| `[prompt]` | Your prompt (positional argument, optional) | None |
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
| `-e, --extension <path>` | Path to the extension bundle directory | Auto-detected |
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
| `-x, --exit-on-complete` | Exit the process when task completes (useful for testing) | `false` |
@ -249,7 +249,7 @@ pnpm lint
To create a new release, execute the /cli-release slash command:
```bash
roo ~/Documents/Roo-Code -P "/cli-release" -y
roo "/cli-release" -w ~/Documents/Roo-Code -y
```
The workflow will:

View file

@ -1,6 +1,6 @@
{
"name": "@roo-code/cli",
"version": "0.0.48",
"version": "0.0.49",
"description": "Roo Code CLI - Run the Roo Code agent from the command line",
"private": true,
"type": "module",
@ -30,6 +30,8 @@
"@trpc/client": "^11.8.1",
"@vscode/ripgrep": "^1.15.9",
"commander": "^12.1.0",
"cross-spawn": "^7.0.6",
"execa": "^9.5.2",
"fuzzysort": "^3.1.0",
"ink": "^6.6.0",
"p-wait-for": "^5.0.2",

View file

@ -100,16 +100,17 @@ describe("ExtensionHost", () => {
ephemeral: false,
debug: false,
exitOnComplete: false,
integrationTest: true, // Set explicitly for testing
}
const host = new ExtensionHost(options)
// Options are stored but integrationTest is set to true
// Options are stored as-is
const storedOptions = getPrivate<ExtensionHostOptions>(host, "options")
expect(storedOptions.mode).toBe(options.mode)
expect(storedOptions.workspacePath).toBe(options.workspacePath)
expect(storedOptions.extensionPath).toBe(options.extensionPath)
expect(storedOptions.integrationTest).toBe(true) // Always set to true in constructor
expect(storedOptions.integrationTest).toBe(true)
})
it("should be an EventEmitter instance", () => {
@ -298,16 +299,19 @@ describe("ExtensionHost", () => {
})
it("should suppress console when integrationTest is false", () => {
const host = createTestHost()
// Capture the real console.log before any host is created
const originalLog = console.log
// Override integrationTest to false
// Create host with integrationTest: true to prevent constructor from suppressing
const host = createTestHost({ integrationTest: true })
// Override integrationTest to false to test suppression
const options = getPrivate<ExtensionHostOptions>(host, "options")
options.integrationTest = false
callPrivate(host, "setupQuietMode")
// Console should be modified
// Console should be modified (suppressed)
expect(console.log).not.toBe(originalLog)
// Restore for other tests
@ -332,9 +336,12 @@ describe("ExtensionHost", () => {
describe("restoreConsole", () => {
it("should restore original console methods when suppressed", () => {
const host = createTestHost()
// Capture the real console.log before any host is created
const originalLog = console.log
// Create host with integrationTest: true to prevent constructor from suppressing
const host = createTestHost({ integrationTest: true })
// Override integrationTest to false to actually suppress
const options = getPrivate<ExtensionHostOptions>(host, "options")
options.integrationTest = false

View file

@ -153,7 +153,10 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
super()
this.options = options
this.options.integrationTest = true
// Set up quiet mode early, before any extension code runs.
// This suppresses console output from the extension during load.
this.setupQuietMode()
// Initialize client - single source of truth for agent state (including mode).
this.client = new ExtensionClient({
@ -162,9 +165,7 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
})
// Initialize output manager.
this.outputManager = new OutputManager({
disabled: options.disableOutput,
})
this.outputManager = new OutputManager({ disabled: options.disableOutput })
// Initialize prompt manager with console mode callbacks.
this.promptManager = new PromptManager({
@ -222,8 +223,6 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
this.initialSettings.reasoningEffort = this.options.reasoningEffort
}
}
this.setupQuietMode()
}
// ==========================================================================
@ -267,7 +266,8 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
// ==========================================================================
private setupQuietMode(): void {
if (this.options.integrationTest) {
// Skip if already set up or if integrationTest mode
if (this.originalConsole || this.options.integrationTest) {
return
}
@ -292,18 +292,16 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
}
private restoreConsole(): void {
if (this.options.integrationTest) {
if (!this.originalConsole) {
return
}
if (this.originalConsole) {
console.log = this.originalConsole.log
console.warn = this.originalConsole.warn
console.error = this.originalConsole.error
console.debug = this.originalConsole.debug
console.info = this.originalConsole.info
this.originalConsole = null
}
console.log = this.originalConsole.log
console.warn = this.originalConsole.warn
console.error = this.originalConsole.error
console.debug = this.originalConsole.debug
console.info = this.originalConsole.info
this.originalConsole = null
if (this.originalProcessEmitWarning) {
process.emitWarning = this.originalProcessEmitWarning

View file

@ -1 +1,2 @@
export * from "./extension-host.js"
export * from "./json-event-emitter.js"

View file

@ -0,0 +1,464 @@
/**
* JsonEventEmitter - Handles structured JSON output for the CLI
*
* This class transforms internal CLI events (ClineMessage, state changes, etc.)
* into structured JSON events and outputs them to stdout.
*
* Supports two output modes:
* - "stream-json": NDJSON format (one JSON object per line) for real-time streaming
* - "json": Single JSON object at the end with accumulated events
*
* Schema is optimized for efficiency with high message volume:
* - Minimal fields per event
* - No redundant wrappers
* - `done` flag instead of partial:false
*/
import type { ClineMessage } from "@roo-code/types"
import type { JsonEvent, JsonEventCost, JsonFinalOutput } from "@/types/json-events.js"
import type { ExtensionClient } from "./extension-client.js"
import type { TaskCompletedEvent } from "./events.js"
/**
* Options for JsonEventEmitter.
*/
export interface JsonEventEmitterOptions {
/** Output mode: "json" or "stream-json" */
mode: "json" | "stream-json"
/** Output stream (defaults to process.stdout) */
stdout?: NodeJS.WriteStream
}
/**
* Parse tool information from a ClineMessage text field.
* Tool messages are JSON with a `tool` field containing the tool name.
*/
function parseToolInfo(text: string | undefined): { name: string; input: Record<string, unknown> } | null {
if (!text) return null
try {
const parsed = JSON.parse(text)
return parsed.tool ? { name: parsed.tool, input: parsed } : null
} catch {
return null
}
}
/**
* Parse API request cost information from api_req_started message text.
*/
function parseApiReqCost(text: string | undefined): JsonEventCost | undefined {
if (!text) return undefined
try {
const parsed = JSON.parse(text)
return parsed.cost !== undefined
? {
totalCost: parsed.cost,
inputTokens: parsed.tokensIn,
outputTokens: parsed.tokensOut,
cacheWrites: parsed.cacheWrites,
cacheReads: parsed.cacheReads,
}
: undefined
} catch {
return undefined
}
}
/** Internal events that should not be emitted */
const SKIP_SAY_TYPES = new Set([
"api_req_finished",
"api_req_retried",
"api_req_retry_delayed",
"api_req_rate_limit_wait",
"api_req_deleted",
"checkpoint_saved",
"condense_context",
"condense_context_error",
"sliding_window_truncation",
])
/** Key offset for reasoning content to avoid collision with text content delta tracking */
const REASONING_KEY_OFFSET = 1_000_000_000
export class JsonEventEmitter {
private mode: "json" | "stream-json"
private stdout: NodeJS.WriteStream
private events: JsonEvent[] = []
private unsubscribers: (() => void)[] = []
private lastCost: JsonEventCost | undefined
private seenMessageIds = new Set<number>()
// Track previous content for delta computation
private previousContent = new Map<number, string>()
// Track the completion result content
private completionResultContent: string | undefined
constructor(options: JsonEventEmitterOptions) {
this.mode = options.mode
this.stdout = options.stdout ?? process.stdout
}
/**
* Attach to an ExtensionClient and subscribe to its events.
*/
attachToClient(client: ExtensionClient): void {
// Subscribe to message events
const unsubMessage = client.on("message", (msg) => this.handleMessage(msg, false))
const unsubMessageUpdated = client.on("messageUpdated", (msg) => this.handleMessage(msg, true))
const unsubTaskCompleted = client.on("taskCompleted", (event) => this.handleTaskCompleted(event))
const unsubError = client.on("error", (error) => this.handleError(error))
this.unsubscribers.push(unsubMessage, unsubMessageUpdated, unsubTaskCompleted, unsubError)
// Emit init event
this.emitEvent({
type: "system",
subtype: "init",
content: "Task started",
})
}
/**
* Detach from the client and clean up subscriptions.
*/
detach(): void {
for (const unsub of this.unsubscribers) {
unsub()
}
this.unsubscribers = []
}
/**
* Compute the delta (new content) for a streaming message.
* Returns null if there's no new content.
*/
private computeDelta(msgId: number, fullContent: string | undefined): string | null {
if (!fullContent) return null
const previous = this.previousContent.get(msgId) || ""
if (fullContent === previous) return null
this.previousContent.set(msgId, fullContent)
// If content is appended, return only the new part
return fullContent.startsWith(previous) ? fullContent.slice(previous.length) : fullContent
}
/**
* Check if this is a streaming partial message with no new content.
*/
private isEmptyStreamingDelta(content: string | null): boolean {
return this.mode === "stream-json" && content === null
}
/**
* Get content to send for a message (delta for streaming, full for json mode).
*/
private getContentToSend(msgId: number, text: string | undefined, isPartial: boolean): string | null {
if (this.mode === "stream-json" && isPartial) {
return this.computeDelta(msgId, text)
}
return text ?? null
}
/**
* Build a base event with optional done flag.
*/
private buildTextEvent(
type: "assistant" | "thinking" | "user",
id: number,
content: string | null,
isDone: boolean,
subtype?: string,
): JsonEvent {
const event: JsonEvent = { type, id }
if (content !== null) {
event.content = content
}
if (subtype) {
event.subtype = subtype
}
if (isDone) {
event.done = true
}
return event
}
/**
* Handle a ClineMessage and emit the appropriate JSON event.
*/
private handleMessage(msg: ClineMessage, _isUpdate: boolean): void {
const isDone = !msg.partial
// In json mode, only emit complete (non-partial) messages
if (this.mode === "json" && msg.partial) {
return
}
// Skip duplicate complete messages
if (isDone && this.seenMessageIds.has(msg.ts)) {
return
}
if (isDone) {
this.seenMessageIds.add(msg.ts)
this.previousContent.delete(msg.ts)
}
const contentToSend = this.getContentToSend(msg.ts, msg.text, msg.partial ?? false)
// Skip if no new content for streaming partial messages
if (msg.partial && this.isEmptyStreamingDelta(contentToSend)) {
return
}
if (msg.type === "say" && msg.say) {
this.handleSayMessage(msg, contentToSend, isDone)
}
if (msg.type === "ask" && msg.ask) {
this.handleAskMessage(msg, contentToSend, isDone)
}
}
/**
* Handle "say" type messages.
*/
private handleSayMessage(msg: ClineMessage, contentToSend: string | null, isDone: boolean): void {
switch (msg.say) {
case "text":
this.emitEvent(this.buildTextEvent("assistant", msg.ts, contentToSend, isDone))
break
case "reasoning":
this.handleReasoningMessage(msg, isDone)
break
case "error":
this.emitEvent({ type: "error", id: msg.ts, content: contentToSend ?? undefined })
break
case "command_output":
this.emitEvent({
type: "tool_result",
tool_result: { name: "execute_command", output: msg.text },
})
break
case "user_feedback":
case "user_feedback_diff":
this.emitEvent(this.buildTextEvent("user", msg.ts, contentToSend, isDone))
break
case "api_req_started": {
const cost = parseApiReqCost(msg.text)
if (cost) {
this.lastCost = cost
}
break
}
case "browser_action":
case "browser_action_result":
this.emitEvent({
type: "tool_result",
subtype: "browser",
tool_result: { name: "browser_action", output: msg.text },
})
break
case "mcp_server_response":
this.emitEvent({
type: "tool_result",
subtype: "mcp",
tool_result: { name: "mcp_server", output: msg.text },
})
break
case "completion_result":
if (msg.text && !msg.partial) {
this.completionResultContent = msg.text
}
break
default:
if (SKIP_SAY_TYPES.has(msg.say!)) {
break
}
if (msg.text) {
this.emitEvent(this.buildTextEvent("assistant", msg.ts, contentToSend, isDone, msg.say))
}
break
}
}
/**
* Handle reasoning/thinking messages with separate delta tracking.
*/
private handleReasoningMessage(msg: ClineMessage, isDone: boolean): void {
const reasoningContent = msg.reasoning || msg.text
const reasoningKey = msg.ts + REASONING_KEY_OFFSET
const reasoningDelta = this.getContentToSend(reasoningKey, reasoningContent, msg.partial ?? false)
if (msg.partial && this.isEmptyStreamingDelta(reasoningDelta)) {
return
}
if (!msg.partial) {
this.previousContent.delete(reasoningKey)
}
this.emitEvent(this.buildTextEvent("thinking", msg.ts, reasoningDelta, isDone))
}
/**
* Handle "ask" type messages.
*/
private handleAskMessage(msg: ClineMessage, contentToSend: string | null, isDone: boolean): void {
switch (msg.ask) {
case "tool": {
const toolInfo = parseToolInfo(msg.text)
this.emitEvent({
type: "tool_use",
id: msg.ts,
subtype: "tool",
tool_use: toolInfo ?? { name: "unknown_tool", input: { raw: msg.text } },
})
break
}
case "command":
this.emitEvent({
type: "tool_use",
id: msg.ts,
subtype: "command",
tool_use: { name: "execute_command", input: { command: msg.text } },
})
break
case "browser_action_launch":
this.emitEvent({
type: "tool_use",
id: msg.ts,
subtype: "browser",
tool_use: { name: "browser_action", input: { raw: msg.text } },
})
break
case "use_mcp_server":
this.emitEvent({
type: "tool_use",
id: msg.ts,
subtype: "mcp",
tool_use: { name: "mcp_server", input: { raw: msg.text } },
})
break
case "followup":
this.emitEvent(this.buildTextEvent("assistant", msg.ts, contentToSend, isDone, "followup"))
break
case "command_output":
// Handled in say type
break
case "completion_result":
if (msg.text && !msg.partial) {
this.completionResultContent = msg.text
}
break
default:
if (msg.text) {
this.emitEvent(this.buildTextEvent("assistant", msg.ts, contentToSend, isDone, msg.ask))
}
break
}
}
/**
* Handle task completion and emit result event.
*/
private handleTaskCompleted(event: TaskCompletedEvent): void {
// Use tracked completion result content, falling back to event message
const resultContent = this.completionResultContent || event.message?.text
this.emitEvent({
type: "result",
id: event.message?.ts ?? Date.now(),
content: resultContent,
done: true,
success: event.success,
cost: this.lastCost,
})
// For "json" mode, output the final accumulated result
if (this.mode === "json") {
this.outputFinalResult(event.success, resultContent)
}
}
/**
* Handle errors and emit error event.
*/
private handleError(error: Error): void {
this.emitEvent({
type: "error",
id: Date.now(),
content: error.message,
})
}
/**
* Emit a JSON event.
* For stream-json mode: immediately output to stdout
* For json mode: accumulate for final output
*/
private emitEvent(event: JsonEvent): void {
this.events.push(event)
if (this.mode === "stream-json") {
this.outputLine(event)
}
}
/**
* Output a single JSON line (NDJSON format).
*/
private outputLine(data: unknown): void {
this.stdout.write(JSON.stringify(data) + "\n")
}
/**
* Output the final accumulated result (for "json" mode).
*/
private outputFinalResult(success: boolean, content?: string): void {
const output: JsonFinalOutput = {
type: "result",
success,
content,
cost: this.lastCost,
events: this.events.filter((e) => e.type !== "result"), // Exclude the result event itself
}
this.stdout.write(JSON.stringify(output, null, 2) + "\n")
}
/**
* Get accumulated events (for testing or external use).
*/
getEvents(): JsonEvent[] {
return [...this.events]
}
/**
* Clear accumulated events and state.
*/
clear(): void {
this.events = []
this.lastCost = undefined
this.seenMessageIds.clear()
this.previousContent.clear()
this.completionResultContent = undefined
}
}

View file

@ -11,11 +11,13 @@ import {
isSupportedProvider,
OnboardingProviderChoice,
supportedProviders,
ASCII_ROO,
DEFAULT_FLAGS,
REASONING_EFFORTS,
SDK_BASE_URL,
OutputFormat,
} from "@/types/index.js"
import { isValidOutputFormat } from "@/types/json-events.js"
import { JsonEventEmitter } from "@/agent/json-event-emitter.js"
import { createClient } from "@/lib/sdk/index.js"
import { loadToken, loadSettings } from "@/lib/storage/index.js"
@ -164,6 +166,23 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
process.exit(1)
}
// Validate output format
const outputFormat: OutputFormat = (flagOptions.outputFormat as OutputFormat) || "text"
if (!isValidOutputFormat(outputFormat)) {
console.error(
`[CLI] Error: Invalid output format: ${flagOptions.outputFormat}; must be one of: text, json, stream-json`,
)
process.exit(1)
}
// Output format only works with --print mode
if (outputFormat !== "text" && !flagOptions.print && isTuiSupported) {
console.error("[CLI] Error: --output-format requires --print mode")
console.error("[CLI] Usage: roo <prompt> --print --output-format json")
process.exit(1)
}
if (!isTuiEnabled) {
if (!prompt) {
console.error("[CLI] Error: prompt is required in print mode")
@ -204,38 +223,53 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
process.exit(1)
}
} else {
console.log(ASCII_ROO)
console.log()
console.log(
`[roo] Running ${extensionHostOptions.model || "default"} (${extensionHostOptions.reasoningEffort || "default"}) on ${extensionHostOptions.provider} in ${extensionHostOptions.mode || "default"} mode in ${extensionHostOptions.workspacePath} [debug = ${extensionHostOptions.debug}]`,
)
const useJsonOutput = outputFormat === "json" || outputFormat === "stream-json"
extensionHostOptions.disableOutput = useJsonOutput
const host = new ExtensionHost(extensionHostOptions)
process.on("SIGINT", async () => {
console.log("\n[CLI] Received SIGINT, shutting down...")
await host.dispose()
process.exit(130)
})
const jsonEmitter = useJsonOutput
? new JsonEventEmitter({ mode: outputFormat as "json" | "stream-json" })
: null
process.on("SIGTERM", async () => {
console.log("\n[CLI] Received SIGTERM, shutting down...")
async function shutdown(signal: string, exitCode: number): Promise<void> {
if (!useJsonOutput) {
console.log(`\n[CLI] Received ${signal}, shutting down...`)
}
jsonEmitter?.detach()
await host.dispose()
process.exit(143)
})
process.exit(exitCode)
}
process.on("SIGINT", () => shutdown("SIGINT", 130))
process.on("SIGTERM", () => shutdown("SIGTERM", 143))
try {
await host.activate()
if (jsonEmitter) {
jsonEmitter.attachToClient(host.client)
}
await host.runTask(prompt!)
jsonEmitter?.detach()
await host.dispose()
process.exit(0)
} catch (error) {
console.error("[CLI] Error:", error instanceof Error ? error.message : String(error))
const errorMessage = error instanceof Error ? error.message : String(error)
if (error instanceof Error) {
console.error(error.stack)
if (useJsonOutput) {
const errorEvent = { type: "error", id: Date.now(), content: errorMessage }
process.stdout.write(JSON.stringify(errorEvent) + "\n")
} else {
console.error("[CLI] Error:", errorMessage)
if (error instanceof Error) {
console.error(error.stack)
}
}
jsonEmitter?.detach()
await host.dispose()
process.exit(1)
}

View file

@ -30,6 +30,11 @@ program
)
.option("--ephemeral", "Run without persisting state (uses temporary storage)", false)
.option("--oneshot", "Exit upon task completion", false)
.option(
"--output-format <format>",
'Output format (only works with --print): "text" (default), "json" (single result), or "stream-json" (realtime streaming)',
"text",
)
.action(run)
const authCommand = program.command("auth").description("Manage authentication for Roo Code Cloud")

View file

@ -1,2 +1,3 @@
export * from "./types.js"
export * from "./constants.js"
export * from "./json-events.js"

View file

@ -0,0 +1,120 @@
/**
* JSON Event Types for Structured CLI Output
*
* This module defines the types for structured JSON output from the CLI.
* The output format is NDJSON (newline-delimited JSON) for stream-json mode,
* or a single JSON object for json mode.
*
* Schema is optimized for efficiency with high message volume:
* - Minimal fields per event
* - No redundant wrappers
* - `done` flag instead of partial:false
*/
/**
* Output format options for the CLI.
*/
export const OUTPUT_FORMATS = ["text", "json", "stream-json"] as const
export type OutputFormat = (typeof OUTPUT_FORMATS)[number]
export function isValidOutputFormat(format: string): format is OutputFormat {
return (OUTPUT_FORMATS as readonly string[]).includes(format)
}
/**
* Event type discriminators for JSON output.
*/
export type JsonEventType =
| "system" // System messages (init, ready, shutdown)
| "assistant" // Assistant text messages
| "user" // User messages (echoed input)
| "tool_use" // Tool invocations (file ops, commands, browser, MCP)
| "tool_result" // Results from tool execution
| "thinking" // Reasoning/thinking content
| "error" // Errors
| "result" // Final task result
/**
* Tool use information for tool_use events.
*/
export interface JsonEventToolUse {
/** Tool name (e.g., "read_file", "write_to_file", "execute_command") */
name: string
/** Tool input parameters */
input?: Record<string, unknown>
}
/**
* Tool result information for tool_result events.
*/
export interface JsonEventToolResult {
/** Tool name that produced this result */
name: string
/** Tool output (for successful execution) */
output?: string
/** Error message (for failed execution) */
error?: string
}
/**
* Cost and token usage information.
*/
export interface JsonEventCost {
/** Total cost in USD */
totalCost?: number
/** Input tokens used */
inputTokens?: number
/** Output tokens generated */
outputTokens?: number
/** Cache write tokens */
cacheWrites?: number
/** Cache read tokens */
cacheReads?: number
}
/**
* Base JSON event structure.
* Optimized for minimal payload size.
*
* For streaming deltas:
* - Each delta includes `id` for easy correlation
* - Final message has `done: true`
*/
export interface JsonEvent {
/** Event type discriminator */
type: JsonEventType
/** Message ID - included on first delta and final message */
id?: number
/** Content text (for text-based events) */
content?: string
/** True when this is the final message (stream complete) */
done?: boolean
/** Optional subtype for more specific categorization */
subtype?: string
/** Tool use information (for tool_use events) */
tool_use?: JsonEventToolUse
/** Tool result information (for tool_result events) */
tool_result?: JsonEventToolResult
/** Whether the task succeeded (for result events) */
success?: boolean
/** Cost and token usage (for result events) */
cost?: JsonEventCost
}
/**
* Final JSON output for "json" mode (single object at end).
* Contains the result and accumulated messages.
*/
export interface JsonFinalOutput {
/** Final result type */
type: "result"
/** Whether the task succeeded */
success: boolean
/** Result content/message */
content?: string
/** Cost and token usage */
cost?: JsonEventCost
/** All events that occurred during the task */
events: JsonEvent[]
}

View file

@ -1,4 +1,5 @@
import type { ProviderName, ReasoningEffortExtended } from "@roo-code/types"
import type { OutputFormat } from "./json-events.js"
export const supportedProviders = [
"anthropic",
@ -32,6 +33,7 @@ export type FlagOptions = {
reasoningEffort?: ReasoningEffortFlagOptions
ephemeral: boolean
oneshot: boolean
outputFormat?: OutputFormat
}
export enum OnboardingProviderChoice {

View file

@ -16,15 +16,7 @@ export type ToolCategory =
| "other"
export function getToolCategory(toolName: string): ToolCategory {
const fileReadTools = [
"readFile",
"read_file",
"fetchInstructions",
"fetch_instructions",
"listFilesTopLevel",
"listFilesRecursive",
"list_files",
]
const fileReadTools = ["readFile", "read_file", "skill", "listFilesTopLevel", "listFilesRecursive", "list_files"]
const fileWriteTools = [
"editedExistingFile",

View file

@ -50,8 +50,7 @@ export function getToolDisplayName(toolName: string): string {
// File read operations
readFile: "Read",
read_file: "Read",
fetchInstructions: "Fetch Instructions",
fetch_instructions: "Fetch Instructions",
skill: "Load Skill",
listFilesTopLevel: "List Files",
listFilesRecursive: "List Files (Recursive)",
list_files: "List Files",
@ -107,8 +106,7 @@ export function getToolIconName(toolName: string): IconName {
// File read operations
readFile: "file",
read_file: "file",
fetchInstructions: "file",
fetch_instructions: "file",
skill: "file",
listFilesTopLevel: "folder",
listFilesRecursive: "folder",
list_files: "folder",

View file

@ -56,7 +56,6 @@ import { useRooCodeCloudModels } from "@/hooks/use-roo-code-cloud-models"
import {
Button,
Checkbox,
FormControl,
FormField,
FormItem,
@ -111,7 +110,6 @@ export function NewRun() {
const [provider, setModelSource] = useState<"roo" | "openrouter" | "other">("other")
const [executionMethod, setExecutionMethod] = useState<ExecutionMethod>("vscode")
const [useNativeToolProtocol, setUseNativeToolProtocol] = useState(true)
const [commandExecutionTimeout, setCommandExecutionTimeout] = useState(20)
const [terminalShellIntegrationTimeout, setTerminalShellIntegrationTimeout] = useState(30) // seconds
@ -464,7 +462,6 @@ export function NewRun() {
...(runValues.settings || {}),
apiProvider: "openrouter",
openRouterModelId: selection.model,
toolProtocol: useNativeToolProtocol ? "native" : "xml",
commandExecutionTimeout,
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000,
}
@ -474,7 +471,6 @@ export function NewRun() {
...(runValues.settings || {}),
apiProvider: "roo",
apiModelId: selection.model,
toolProtocol: useNativeToolProtocol ? "native" : "xml",
commandExecutionTimeout,
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000,
}
@ -485,7 +481,6 @@ export function NewRun() {
...EVALS_SETTINGS,
...providerSettings,
...importedSettings.globalSettings,
toolProtocol: useNativeToolProtocol ? "native" : "xml",
commandExecutionTimeout,
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000,
}
@ -512,7 +507,6 @@ export function NewRun() {
configSelections,
importedSettings,
router,
useNativeToolProtocol,
commandExecutionTimeout,
terminalShellIntegrationTimeout,
],
@ -688,26 +682,6 @@ export function NewRun() {
</div>
)}
<div className="mt-4 p-4 rounded-md bg-muted/30 border border-border space-y-3">
<Label className="text-sm font-medium text-muted-foreground">
Tool Protocol Options
</Label>
<div className="flex flex-col gap-2.5 pl-1">
<label
htmlFor="native-other"
className="flex items-center gap-2 cursor-pointer">
<Checkbox
id="native-other"
checked={useNativeToolProtocol}
onCheckedChange={(checked: boolean) =>
setUseNativeToolProtocol(checked)
}
/>
<span className="text-sm">Use Native Tool Calls</span>
</label>
</div>
</div>
{settings && (
<SettingsDiff defaultSettings={EVALS_SETTINGS} customSettings={settings} />
)}
@ -792,26 +766,6 @@ export function NewRun() {
</div>
))}
</div>
<div className="mt-4 p-4 rounded-md bg-muted/30 border border-border space-y-3">
<Label className="text-sm font-medium text-muted-foreground">
Tool Protocol Options
</Label>
<div className="flex flex-col gap-2.5 pl-1">
<label
htmlFor="native"
className="flex items-center gap-2 cursor-pointer">
<Checkbox
id="native"
checked={useNativeToolProtocol}
onCheckedChange={(checked: boolean) =>
setUseNativeToolProtocol(checked)
}
/>
<span className="text-sm">Use Native Tool Calls</span>
</label>
</div>
</div>
</>
)}

View file

@ -100,6 +100,19 @@ export default function CookiePolicy() {
<td className="border border-border px-4 py-3">1 year</td>
<td className="border border-border px-4 py-3 font-mono text-sm">ph_*</td>
</tr>
<tr>
<td className="border border-border px-4 py-3 font-medium">HubSpot</td>
<td className="border border-border px-4 py-3">
Marketing automation and visitor tracking
</td>
<td className="border border-border px-4 py-3">
Analytics (only with your consent)
</td>
<td className="border border-border px-4 py-3">13 months</td>
<td className="border border-border px-4 py-3 font-mono text-sm">
hubspotutk, __hstc, __hssrc, __hssc
</td>
</tr>
</tbody>
</table>
</div>
@ -122,6 +135,15 @@ export default function CookiePolicy() {
PostHog Privacy Policy
</a>
</p>
<p>
<a
href="https://legal.hubspot.com/privacy-policy"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline">
HubSpot Privacy Policy
</a>
</p>
<h2 className="mt-12 text-2xl font-bold">Essential cookies</h2>
<p>
@ -133,10 +155,10 @@ export default function CookiePolicy() {
<h2 className="mt-12 text-2xl font-bold">Analytics cookies</h2>
<p>
We use PostHog analytics cookies to understand how visitors interact with our website. This
helps us improve our services and user experience. Analytics cookies are placed only if you give
consent through our cookie banner. The lawful basis for processing these cookies is your
consent, which you can withdraw at any time.
We use PostHog and HubSpot analytics cookies to understand how visitors interact with our
website. This helps us improve our services, user experience, and marketing efforts. Analytics
cookies are placed only if you give consent through our cookie banner. The lawful basis for
processing these cookies is your consent, which you can withdraw at any time.
</p>
<h2 className="mt-12 text-2xl font-bold">Third-party services</h2>

View file

@ -291,11 +291,7 @@ export default function PricingPage() {
<li>To pay for Cloud Agents running time (${PRICE_CREDITS}/hour)</li>
<li>
To pay for AI model inference costs (
<a
href="https://app.roocode.com/provider/pricing"
target="_blank"
rel="noopener noreferrer"
className="underline">
<a href="/provider" target="_blank" rel="noopener noreferrer" className="underline">
varies by model
</a>
)

View file

@ -0,0 +1,401 @@
import {
ArrowRight,
Brain,
CreditCard,
GitBranch,
GraduationCap,
Link2,
MessageSquare,
Settings,
Shield,
Slack,
Users,
Zap,
} from "lucide-react"
import type { LucideIcon } from "lucide-react"
import type { Metadata } from "next"
import { AnimatedBackground } from "@/components/homepage"
import { SlackThreadDemo } from "@/components/slack/slack-thread-demo"
import { Button } from "@/components/ui"
import { EXTERNAL_LINKS } from "@/lib/constants"
import { SEO } from "@/lib/seo"
import { ogImageUrl } from "@/lib/og"
const TITLE = "Roo Code for Slack"
const DESCRIPTION =
"Mention @Roomote in any channel to explain code, plan features, or ship a PR, all without leaving the conversation."
const OG_DESCRIPTION = "Your AI Team in Slack"
const PATH = "/slack"
export const metadata: Metadata = {
title: TITLE,
description: DESCRIPTION,
alternates: {
canonical: `${SEO.url}${PATH}`,
},
openGraph: {
title: TITLE,
description: DESCRIPTION,
url: `${SEO.url}${PATH}`,
siteName: SEO.name,
images: [
{
url: ogImageUrl(TITLE, OG_DESCRIPTION),
width: 1200,
height: 630,
alt: TITLE,
},
],
locale: SEO.locale,
type: "website",
},
twitter: {
card: SEO.twitterCard,
title: TITLE,
description: DESCRIPTION,
images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
},
keywords: [
...SEO.keywords,
"slack integration",
"slack bot",
"AI in slack",
"code assistant slack",
"@Roomote",
"team collaboration",
],
}
// Invalidate cache when a request comes in, at most once every hour.
export const revalidate = 3600
type ValueProp = {
icon: LucideIcon
title: string
description: string
}
const VALUE_PROPS: ValueProp[] = [
{
icon: GitBranch,
title: "Discussion to PR.",
description:
"Your team discusses a feature in Slack. @Roomote turns the discussion into a plan. Then builds it. All without leaving the conversation.",
},
{
icon: Brain,
title: "Thread-aware.",
description:
'@Roomote reads the full thread before responding. Ask "Can we add caching here?" and it knows exactly what code you mean.',
},
{
icon: Link2,
title: "Chain agents.",
description:
"Start with a Planner to spec it out. Then call the Coder to build it. Multi-step workflows, one Slack thread.",
},
{
icon: Users,
title: "Open to all.",
description:
"Anyone on your team can ask @Roomote to fix bugs, build features, or investigate issues. Engineering gets looped in only when needed.",
},
{
icon: GraduationCap,
title: "Built-in learning.",
description: "Public channel mentions show everyone how to leverage agents. Learn by watching.",
},
{
icon: Shield,
title: "Safe by design.",
description: "Agents never touch main/master directly. They produce branches and PRs. You approve.",
},
]
type WorkflowStep = {
step: number
title: string
description: string
}
const WORKFLOW_STEPS: WorkflowStep[] = [
{
step: 1,
title: "Turn the discussion into a plan",
description: "Your team discusses a feature. When it gets complex, summon the Planner agent.",
},
{
step: 2,
title: "Refine the plan in the thread",
description:
"The team reviews the spec in the thread, suggests changes, asks questions. Mention @Roomote again to refine.",
},
{
step: 3,
title: "Build the plan",
description: "Once the plan looks good, hand it off to the Coder agent to implement.",
},
{
step: 4,
title: "Review and ship",
description: "The Coder creates a branch and opens a PR. The team reviews, and the feature ships.",
},
]
type OnboardingStep = {
icon: LucideIcon
title: string
description: string
link?: {
href: string
text: string
}
}
const ONBOARDING_STEPS: OnboardingStep[] = [
{
icon: CreditCard,
title: "1. Team Plan",
description: "Slack requires a Team plan.",
link: {
href: EXTERNAL_LINKS.CLOUD_APP_TEAM_TRIAL,
text: "Start a free trial",
},
},
{
icon: Settings,
title: "2. Connect",
description: 'Sign in to Roo Code Cloud and go to Settings. Click "Connect" next to Slack.',
},
{
icon: Slack,
title: "3. Authorize",
description: "Authorize the Roo Code app to access your Slack workspace.",
},
{
icon: MessageSquare,
title: "4. Add to channels",
description: "Add @Roomote to the channels where you want it available.",
},
]
export default function SlackPage(): JSX.Element {
return (
<>
{/* Hero Section */}
<section className="relative flex pt-32 pb-20 items-center overflow-hidden">
<AnimatedBackground />
<div className="container relative flex flex-col items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid w-full max-w-6xl grid-cols-1 items-center gap-10 lg:grid-cols-2 lg:gap-12">
<div className="text-center lg:text-left">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 text-sm font-medium mb-6">
<Slack className="size-4" />
Powered by Roo Code Cloud
</div>
<h1 className="text-4xl font-bold tracking-tight mb-6 md:text-5xl lg:text-6xl">
<span className="text-violet-500">@Roomote:</span> Your AI Team in&nbsp;Slack
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto lg:mx-0">
Mention @Roomote in any channel to explain code, plan features, or ship a PR, all
without leaving the conversation.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
<Button
size="xl"
className="bg-violet-600 hover:bg-violet-700 text-white transition-all duration-300 shadow-lg hover:shadow-violet-500/25"
asChild>
<a
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center">
Get Started
<ArrowRight className="ml-2 size-5" />
</a>
</Button>
<Button variant="outline" size="xl" className="backdrop-blur-sm" asChild>
<a
href={EXTERNAL_LINKS.SLACK_DOCS}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center">
Read the Docs
</a>
</Button>
</div>
</div>
<div className="flex justify-center lg:justify-end">
<SlackThreadDemo />
</div>
</div>
</div>
</section>
{/* Value Props Section */}
<section className="py-24 bg-muted/30">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative">
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-violet-500/10 dark:bg-violet-700/20 blur-[140px]" />
</div>
<div className="text-center mb-16">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-4">
Why your team will love using Roo Code in&nbsp;Slack
</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
AI agents that understand context, chain together for complex work, and keep your team in
control.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto relative">
{VALUE_PROPS.map((prop, index) => {
const Icon = prop.icon
return (
<div
key={index}
className="bg-background p-8 rounded-2xl border border-border hover:shadow-lg transition-all duration-300">
<div className="bg-violet-100 dark:bg-violet-900/20 w-12 h-12 rounded-lg flex items-center justify-center mb-6">
<Icon className="size-6 text-violet-600 dark:text-violet-400" />
</div>
<h3 className="text-xl font-semibold mb-3">{prop.title}</h3>
<p className="text-muted-foreground leading-relaxed">{prop.description}</p>
</div>
)
})}
</div>
</div>
</section>
{/* Featured Workflow Section */}
<section className="relative overflow-hidden border-t border-border py-24 lg:py-32">
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-500/10 dark:bg-blue-700/20 blur-[140px]" />
</div>
<div className="mx-auto mb-12 max-w-5xl text-center">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-sm font-medium mb-6">
<Zap className="size-4" />
Featured Workflow
</div>
<h2 className="text-3xl font-bold tracking-tight sm:text-5xl mb-4">
Thread to Shipped Feature
</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Turn Slack discussions into working code. No context lost, no meetings needed.
</p>
</div>
<div className="relative mx-auto max-w-6xl">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-10 items-center">
{/* YouTube Video Embed */}
<div className="lg:col-span-3 overflow-hidden rounded-2xl border border-border bg-background shadow-lg">
<iframe
className="aspect-video w-full"
src="https://www.youtube-nocookie.com/embed/dJM_8HHGe1E?rel=0"
title="Roo Code Slack Integration Demo"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
/>
</div>
{/* Workflow Steps */}
<div className="lg:col-span-2 space-y-3">
{WORKFLOW_STEPS.map((step) => (
<div
key={step.step}
className="relative border border-border rounded-xl bg-background p-4 transition-all duration-300 hover:shadow-md hover:border-blue-500/30">
<div className="flex items-start gap-3">
<div className="bg-blue-100 dark:bg-blue-900/30 w-7 h-7 rounded-full flex items-center justify-center text-blue-700 dark:text-blue-300 font-bold text-xs shrink-0 mt-0.5">
{step.step}
</div>
<div className="min-w-0">
<h3 className="text-base font-semibold text-foreground mb-0.5">
{step.title}
</h3>
<p className="text-sm leading-snug text-muted-foreground">
{step.description}
</p>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* Onboarding Section */}
<section className="py-24 bg-muted/30">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-4">Get started in minutes</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Connect your Slack workspace and start working with AI agents.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-5xl mx-auto">
{ONBOARDING_STEPS.map((step, index) => {
const Icon = step.icon
return (
<div key={index} className="text-center">
<div className="bg-violet-100 dark:bg-violet-900/20 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6">
<Icon className="size-8 text-violet-600 dark:text-violet-400" />
</div>
<h3 className="text-lg font-semibold mb-2">{step.title}</h3>
<p className="text-muted-foreground">
{step.description}
{step.link && (
<>
{" "}
<a
href={step.link.href}
target="_blank"
rel="noopener noreferrer"
className="text-violet-600 dark:text-violet-400 hover:underline">
{step.link.text}
</a>
</>
)}
</p>
</div>
)
})}
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-24">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-violet-500/10 via-purple-500/5 to-blue-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/10 sm:p-16">
<h2 className="mb-6 text-3xl font-bold tracking-tight sm:text-4xl">
Start using Roo Code in Slack
</h2>
<p className="mx-auto mb-10 max-w-2xl text-lg text-muted-foreground">
Start a free 14 day Team trial.
</p>
<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<Button
size="lg"
className="bg-foreground text-background hover:bg-foreground/90 transition-all duration-300"
asChild>
<a
href={EXTERNAL_LINKS.CLOUD_APP_TEAM_TRIAL}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center">
Start free trial
<ArrowRight className="ml-2 h-4 w-4" />
</a>
</Button>
</div>
</div>
</div>
</section>
</>
)
}

View file

@ -13,7 +13,7 @@ import { EXTERNAL_LINKS } from "@/lib/constants"
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
import { ScrollButton } from "@/components/ui"
import ThemeToggle from "@/components/chromes/theme-toggle"
import { Brain, ChevronDown, Cloud, Puzzle, X } from "lucide-react"
import { Brain, ChevronDown, Cloud, Puzzle, Slack, X } from "lucide-react"
interface NavBarProps {
stars: string | null
@ -54,6 +54,12 @@ export function NavBar({ stars, downloads }: NavBarProps) {
<Cloud className="size-3 inline mr-2 -mt-0.5" />
Roo Code Cloud
</Link>
<Link
href="/slack"
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
<Slack className="size-3 inline mr-2 -mt-0.5" />
Roo Code for Slack
</Link>
<Link
href="/provider"
className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
@ -190,6 +196,12 @@ export function NavBar({ stars, downloads }: NavBarProps) {
onClick={() => setIsMenuOpen(false)}>
Roo Code Cloud
</Link>
<Link
href="/slack"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Roo Code for Slack
</Link>
<Link
href="/provider"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"

View file

@ -0,0 +1,50 @@
"use client"
import { useEffect, useState } from "react"
import Script from "next/script"
import { hasConsent, onConsentChange } from "@/lib/analytics/consent-manager"
// HubSpot Account ID
const HUBSPOT_ID = "243714031"
/**
* HubSpot Tracking Provider
* Loads HubSpot tracking script only after user consent is given, following GDPR requirements
*/
export function HubSpotProvider({ children }: { children: React.ReactNode }) {
const [shouldLoad, setShouldLoad] = useState(false)
useEffect(() => {
// Check initial consent status
if (hasConsent()) {
setShouldLoad(true)
}
// Listen for consent changes
const unsubscribe = onConsentChange((consented) => {
if (consented) {
setShouldLoad(true)
}
})
return unsubscribe
}, [])
return (
<>
{shouldLoad && (
<>
{/* HubSpot Embed Code */}
<Script
id="hs-script-loader"
src={`//js-na2.hs-scripts.com/${HUBSPOT_ID}.js`}
strategy="afterInteractive"
async
defer
/>
</>
)}
{children}
</>
)
}

View file

@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ThemeProvider } from "next-themes"
import { GoogleTagManagerProvider } from "./google-tag-manager-provider"
import { HubSpotProvider } from "./hubspot-provider"
import { PostHogProvider } from "./posthog-provider"
const queryClient = new QueryClient()
@ -12,11 +13,13 @@ export const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={queryClient}>
<GoogleTagManagerProvider>
<PostHogProvider>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}>
{children}
</ThemeProvider>
</PostHogProvider>
<HubSpotProvider>
<PostHogProvider>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}>
{children}
</ThemeProvider>
</PostHogProvider>
</HubSpotProvider>
</GoogleTagManagerProvider>
</QueryClientProvider>
)

View file

@ -0,0 +1,548 @@
"use client"
import type { ReactNode } from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import { CheckCircle2, Paperclip } from "lucide-react"
import { cn } from "@/lib/utils"
type SlackMessage = {
id: string
author: string
timeLabel: string
body: ReactNode
avatarText: string
avatarClassName: string
kind: "human" | "bot"
}
function usePrefersReducedMotion(): boolean {
const [reduced, setReduced] = useState(false)
useEffect(() => {
const media = window.matchMedia("(prefers-reduced-motion: reduce)")
const onChange = () => setReduced(media.matches)
onChange()
if (typeof media.addEventListener === "function") {
media.addEventListener("change", onChange)
return () => media.removeEventListener("change", onChange)
}
media.addListener?.(onChange)
return () => media.removeListener?.(onChange)
}, [])
return reduced
}
type TypingDotsProps = {
className?: string
}
function TypingDots({ className }: TypingDotsProps): JSX.Element {
return (
<span className={cn("inline-flex items-center gap-1", className)} aria-hidden="true">
<span className="h-1.5 w-1.5 rounded-full bg-[#8B8D91] animate-pulse [animation-delay:0ms]" />
<span className="h-1.5 w-1.5 rounded-full bg-[#8B8D91] animate-pulse [animation-delay:180ms]" />
<span className="h-1.5 w-1.5 rounded-full bg-[#8B8D91] animate-pulse [animation-delay:360ms]" />
</span>
)
}
type FakeLinkProps = {
children: ReactNode
className?: string
}
function FakeLink({ children, className }: FakeLinkProps): JSX.Element {
return (
<span className={cn("text-violet-300 underline underline-offset-2", "cursor-default", className)}>
{children}
</span>
)
}
type SlackMessageRowProps = {
message: SlackMessage
isNew: boolean
reduceMotion: boolean
}
function SlackMessageRow({ message, isNew, reduceMotion }: SlackMessageRowProps): JSX.Element {
let animation = ""
if (!reduceMotion && isNew) {
animation = "animate-in fade-in slide-in-from-bottom-2 duration-500"
}
return (
<div className={cn("flex gap-3", animation)}>
<div
className={cn(
"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-xs font-semibold",
message.avatarClassName,
)}>
{message.avatarText}
</div>
<div className="min-w-0">
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span className="text-[13px] font-semibold text-[#F8F8F9]">{message.author}</span>
<span className="text-[11px] text-[#8B8D91]">{message.timeLabel}</span>
{message.kind === "bot" && (
<span className="inline-flex items-center gap-1 rounded-full bg-violet-500/20 px-2 py-0.5 text-[10px] font-medium text-violet-200">
<CheckCircle2 className="h-3 w-3" />
App
</span>
)}
</div>
<div className="mt-1 text-[13px] leading-relaxed text-[#D1D2D3]">{message.body}</div>
</div>
</div>
)
}
export type SlackThreadDemoProps = {
className?: string
}
export function SlackThreadDemo({ className }: SlackThreadDemoProps): JSX.Element {
const reduceMotion = usePrefersReducedMotion()
const [stepIndex, setStepIndex] = useState(0)
const scrollViewportRef = useRef<HTMLDivElement>(null)
const messages: SlackMessage[] = useMemo(
() => [
{
id: "m1",
author: "Avery Lee",
timeLabel: "Monday at 2:56 PM",
avatarText: "AL",
avatarClassName: "bg-[#2B2D31] text-[#F8F8F9] ring-1 ring-white/10",
kind: "human",
body: (
<span>We need to add a page to our Marketing site that highlights using Roo Code from Slack.</span>
),
},
{
id: "m2",
author: "Avery Lee",
timeLabel: "Monday at 2:58 PM",
avatarText: "AL",
avatarClassName: "bg-[#2B2D31] text-[#F8F8F9] ring-1 ring-white/10",
kind: "human",
body: (
<div className="space-y-2">
<div>
The documentation for using Roo Code from Slack is here:{" "}
<FakeLink className="hover:text-violet-200">
https://docs.roocode.com/roo-code-cloud/slack-integration
</FakeLink>
</div>
<div className="text-[#B8BBC0]">Here are some pages from our site we can use for guidance:</div>
<ol className="list-decimal pl-5 text-[#D1D2D3]">
<li>
<FakeLink className="hover:text-violet-200">https://roocode.com</FakeLink>
</li>
<li>
<FakeLink className="hover:text-violet-200">https://roocode.com/extension</FakeLink>
</li>
<li>
<FakeLink className="hover:text-violet-200">https://roocode.com/cloud</FakeLink>
</li>
</ol>
</div>
),
},
{
id: "m3",
author: "Avery Lee",
timeLabel: "Monday at 3:08 PM",
avatarText: "AL",
avatarClassName: "bg-[#2B2D31] text-[#F8F8F9] ring-1 ring-white/10",
kind: "human",
body: (
<div className="space-y-3">
<div>This is the start of a wireframe I have in mind for this page</div>
<div className="w-full max-w-[420px] rounded-lg border border-white/10 bg-black/20 p-3">
<div className="flex items-center gap-2 text-[12px] text-[#B8BBC0]">
<Paperclip className="h-4 w-4" />
IMG_9721.heic
</div>
<div className="mt-3 h-24 w-full rounded-md bg-gradient-to-br from-white/10 via-white/5 to-white/0" />
</div>
</div>
),
},
{
id: "m4",
author: "Avery Lee",
timeLabel: "Monday at 3:09 PM",
avatarText: "AL",
avatarClassName: "bg-[#2B2D31] text-[#F8F8F9] ring-1 ring-white/10",
kind: "human",
body: (
<span>
<FakeLink className="no-underline hover:text-violet-200">@Roomote</FakeLink> let&apos;s create
the plan to deliver this
</span>
),
},
{
id: "m5",
author: "Roomote",
timeLabel: "Monday at 3:09 PM",
avatarText: "R",
avatarClassName: "bg-violet-500/20 text-violet-200 ring-1 ring-violet-500/30",
kind: "bot",
body: (
<div className="space-y-3">
<div className="rounded-lg border border-white/10 bg-black/20 px-3 py-2 text-[#D1D2D3]">
Calling <span className="font-semibold text-[#F8F8F9]">Planneroo</span> to get started on
your task on{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">
RooCodeInc/Roo-Code
</code>
</div>
<div>
<button
type="button"
className="inline-flex items-center rounded-md border border-white/10 bg-transparent px-2 py-1 text-[12px] font-medium text-[#D1D2D3] hover:bg-white/5">
Cancel
</button>
</div>
</div>
),
},
{
id: "m6",
author: "Roomote",
timeLabel: "Monday at 3:10 PM",
avatarText: "R",
avatarClassName: "bg-violet-500/20 text-violet-200 ring-1 ring-violet-500/30",
kind: "bot",
body: (
<div className="space-x-2">
<span>Cool, I&apos;ll knock this out real quick.</span>
<FakeLink className="hover:text-violet-200">Follow along</FakeLink>
</div>
),
},
{
id: "m7",
author: "Roomote",
timeLabel: "Monday at 3:12 PM",
avatarText: "R",
avatarClassName: "bg-violet-500/20 text-violet-200 ring-1 ring-violet-500/30",
kind: "bot",
body: (
<div className="space-y-2">
<div className="font-semibold text-[#F8F8F9]">Todo List:</div>
<div className="rounded-lg border border-white/10 bg-black/20 px-3 py-2">
<ul className="space-y-1">
{[
"Analyze existing page structures and component patterns",
"Review marketing content requirements and wireframe details",
"Create detailed component architecture plan",
"Design page structure and section breakdown",
"Plan navigation updates and integration points",
"Test the page and verify all sections work",
].map((item) => (
<li key={item} className="text-[#D1D2D3]">
<span className="mr-2"></span>
<span className="line-through opacity-80">{item}</span>
</li>
))}
</ul>
</div>
<div className="text-[12px] text-[#8B8D91]">(edited)</div>
</div>
),
},
{
id: "m8",
author: "Roomote",
timeLabel: "Monday at 3:16 PM",
avatarText: "R",
avatarClassName: "bg-violet-500/20 text-violet-200 ring-1 ring-violet-500/30",
kind: "bot",
body: (
<div className="space-y-3">
<p>
I&apos;ve created a comprehensive implementation plan for the Roo Code Slack integration
marketing page at{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">
plans/slack-marketing-page-plan.md
</code>
.
</p>
<div className="rounded-lg border border-white/10 bg-black/20 px-3 py-2">
<div className="text-[12px] font-semibold text-[#F8F8F9]">Plan Overview</div>
<ul className="mt-2 space-y-1 text-[#D1D2D3]">
<li>
<span className="mr-2"></span>Hero + dual CTAs
</li>
<li>
<span className="mr-2"></span>Value props grid
</li>
<li>
<span className="mr-2"></span>Thread to Shipped Feature workflow
</li>
<li>
<span className="mr-2"></span>Onboarding steps + CTA
</li>
</ul>
</div>
<div className="space-x-2">
<span className="text-[#B8BBC0]">Full document:</span>
<FakeLink className="hover:text-violet-200">View artifact</FakeLink>
</div>
<div className="text-[12px] italic text-[#8B8D91]">
Want to follow up? Just @-mention me in your response.
</div>
</div>
),
},
{
id: "m9",
author: "Avery Lee",
timeLabel: "Monday at 3:17 PM",
avatarText: "AL",
avatarClassName: "bg-[#2B2D31] text-[#F8F8F9] ring-1 ring-white/10",
kind: "human",
body: (
<div className="space-x-2">
<FakeLink className="no-underline hover:text-violet-200">@Roomote</FakeLink>
<span>this looks great, let&apos;s use Coderoo to build this</span>
</div>
),
},
{
id: "m10",
author: "Roomote",
timeLabel: "Monday at 3:23 PM",
avatarText: "R",
avatarClassName: "bg-violet-500/20 text-violet-200 ring-1 ring-violet-500/30",
kind: "bot",
body: (
<div className="space-y-3">
<p>
I&apos;ve built the Roo Code Slack integration marketing page. Here&apos;s what was
implemented:
</p>
<div className="rounded-lg border border-white/10 bg-black/20 px-3 py-2">
<div className="text-[12px] font-semibold text-[#F8F8F9]">Files</div>
<ul className="mt-2 space-y-1 text-[#D1D2D3]">
<li>
<span className="mr-2"></span>
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">
apps/web-roo-code/src/app/slack/page.tsx
</code>{" "}
Slack marketing page
</li>
<li>
<span className="mr-2"></span>
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">
apps/web-roo-code/src/lib/constants.ts
</code>{" "}
added{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">
SLACK_DOCS
</code>
</li>
<li>
<span className="mr-2"></span>
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">
apps/web-roo-code/src/components/chromes/nav-bar.tsx
</code>{" "}
added Slack to Product dropdown
</li>
</ul>
</div>
<div className="rounded-lg border border-white/10 bg-black/20 px-3 py-2">
<div className="text-[12px] font-semibold text-[#F8F8F9]">Pull Request</div>
<div className="mt-2">
<span className="font-semibold text-[#F8F8F9]">PR #10853</span>:{" "}
<FakeLink className="hover:text-violet-200">
https://github.com/RooCodeInc/Roo-Code/pull/10853
</FakeLink>
</div>
</div>
<p className="text-[#B8BBC0]">
The page is accessible at{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 text-[12px] text-[#F8F8F9]">/slack</code>{" "}
and includes navigation links in desktop and mobile.
</p>
</div>
),
},
],
[],
)
type DemoPhase =
| { kind: "show"; messageIndex: number }
| { kind: "typing"; messageIndex: number }
| { kind: "reset" }
const phases: DemoPhase[] = useMemo(() => {
const next: DemoPhase[] = []
if (messages.length === 0) return [{ kind: "reset" }]
next.push({ kind: "typing", messageIndex: 0 })
next.push({ kind: "show", messageIndex: 0 })
for (let messageIndex = 1; messageIndex < messages.length; messageIndex += 1) {
next.push({ kind: "typing", messageIndex })
next.push({ kind: "show", messageIndex })
}
next.push({ kind: "reset" })
return next
}, [messages])
const lastShowPhaseIndex = useMemo(() => {
let lastIndex = -1
for (let idx = 0; idx < phases.length; idx += 1) {
if (phases[idx]?.kind === "show") lastIndex = idx
}
return lastIndex
}, [phases])
useEffect(() => {
if (reduceMotion) {
setStepIndex(lastShowPhaseIndex >= 0 ? lastShowPhaseIndex : 0)
return
}
const active = phases[stepIndex] ?? phases.at(0)
const isLastMessageShow = active?.kind === "show" && stepIndex === lastShowPhaseIndex
const durationMs = (() => {
const base = 2200
if (active?.kind === "reset") return 500
if (active?.kind === "typing") return 900
return isLastMessageShow ? base * 2 : base
})()
const timer = window.setTimeout(() => {
setStepIndex((prev) => (prev + 1) % phases.length)
}, durationMs)
return () => window.clearTimeout(timer)
}, [lastShowPhaseIndex, phases, reduceMotion, stepIndex])
const activePhase = phases[stepIndex] ?? phases.at(0) ?? { kind: "reset" }
function getVisibleCount(phase: DemoPhase): number {
if (phase.kind === "reset") return 0
if (phase.kind === "typing") return phase.messageIndex
return phase.messageIndex + 1
}
const visibleCount = getVisibleCount(activePhase)
const visibleMessages = messages.slice(0, visibleCount)
const typingTarget = activePhase.kind === "typing" ? messages[activePhase.messageIndex] : undefined
useEffect(() => {
const viewport = scrollViewportRef.current
if (!viewport) return
if (activePhase.kind === "reset" || visibleCount <= 1) {
viewport.scrollTo({ top: 0, behavior: "auto" })
return
}
viewport.scrollTo({
top: viewport.scrollHeight,
behavior: reduceMotion ? "auto" : "smooth",
})
}, [activePhase.kind, reduceMotion, visibleCount])
return (
<div
className={cn("w-full max-w-[620px] h-[520px] sm:h-[560px]", className)}
role="img"
aria-label="Animated Slack thread showing Roo Code responding as @Roomote">
<div
aria-hidden="true"
className="relative flex h-full flex-col overflow-hidden rounded-2xl border border-white/10 bg-[#1A1D21] shadow-2xl shadow-black/30">
<div className="flex items-center justify-between border-b border-white/10 px-4 py-3">
<div className="flex items-center gap-2">
<div className="h-2.5 w-2.5 rounded-full bg-[#F24A4A]" />
<div className="h-2.5 w-2.5 rounded-full bg-[#F2C94C]" />
<div className="h-2.5 w-2.5 rounded-full bg-[#27AE60]" />
<div className="ml-3 text-sm font-semibold text-[#F8F8F9]">Thread</div>
</div>
<div className="flex items-center gap-2 text-[11px] text-[#8B8D91]">
<span className="h-2 w-2 rounded-full bg-[#27AE60]" />
Live demo
</div>
</div>
<div
ref={scrollViewportRef}
className="flex-1 overflow-y-auto px-4 py-5 [scrollbar-width:thin] [scrollbar-color:rgba(255,255,255,0.18)_transparent]">
<div
className={cn(
"space-y-5 transition-opacity duration-300 will-change-opacity",
activePhase.kind === "reset" ? "opacity-0" : "opacity-100",
)}>
{visibleMessages.map((message) => (
<SlackMessageRow
key={message.id}
message={message}
reduceMotion={reduceMotion}
isNew={
activePhase.kind === "show" && messages[activePhase.messageIndex]?.id === message.id
}
/>
))}
{typingTarget && (
<div className={cn(reduceMotion ? "" : "animate-in fade-in duration-300", "flex gap-3")}>
<div
className={cn(
"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-xs font-semibold",
typingTarget.avatarClassName,
)}>
{typingTarget.avatarText}
</div>
<div className="min-w-0">
<div className="flex items-baseline gap-x-2">
<span className="text-[13px] font-semibold text-[#F8F8F9]">
{typingTarget.author}
</span>
{typingTarget.kind === "bot" && (
<span className="inline-flex items-center gap-1 rounded-full bg-violet-500/20 px-2 py-0.5 text-[10px] font-medium text-violet-200">
<CheckCircle2 className="h-3 w-3" />
App
</span>
)}
<span className="text-[11px] text-[#8B8D91]">typing</span>
</div>
<div className="mt-2">
<TypingDots />
</div>
</div>
</div>
)}
</div>
</div>
<div className="flex items-center justify-between border-t border-white/10 px-4 py-3">
<div className="flex items-center gap-1.5">
{messages.map((message, idx) => (
<span
key={message.id}
className={cn(
"h-1.5 w-5 rounded-full transition-colors duration-300",
Math.max(0, visibleCount - 1) === idx ? "bg-violet-300" : "bg-white/10",
)}
/>
))}
</div>
</div>
</div>
</div>
)
}

View file

@ -9,6 +9,7 @@ export const EXTERNAL_LINKS = {
BLUESKY: "https://bsky.app/profile/roocode.bsky.social",
YOUTUBE: "https://www.youtube.com/@RooCodeYT",
DOCUMENTATION: "https://docs.roocode.com",
SLACK_DOCS: "https://docs.roocode.com/roo-code-cloud/slack-integration",
CAREERS: "https://careers.roocode.com",
ISSUES: "https://github.com/RooCodeInc/Roo-Code/issues",
FEATURE_REQUESTS: "https://github.com/RooCodeInc/Roo-Code/discussions/categories/feature-requests",
@ -28,6 +29,7 @@ export const EXTERNAL_LINKS = {
CLOUD_APP_SIGNUP: "https://app.roocode.com/sign-up",
CLOUD_APP_SIGNUP_HOME: "https://app.roocode.com/sign-up?redirect_url=/cloud-agents/setup",
CLOUD_APP_SIGNUP_PRO: "https://app.roocode.com/sign-up?redirect_url=/cloud-agents/setup",
CLOUD_APP_TEAM_TRIAL: "https://app.roocode.com/checkout/team",
SUPPORT: "mailto:support@roocode.com",
}

View file

@ -15,7 +15,7 @@
## 我們的承諾
為了營造開放且友善的環境,我們身為貢獻者與維護者,承諾讓參與本專案及社群的體驗,對每個人都不帶有騷擾,不論其年齡、體型、身心障礙、族裔、性徵、性別認同與表現、經驗程度、教育程度、社經地位、國籍、個人外表、種族、宗教信仰、或性傾向。
為了營造開放且友善的環境,我們身為貢獻者與維護者,承諾讓參與本專案及社群的體驗,對每個人都不帶有騷擾,不論其年齡、體型、身心障礙、族裔、性徵、性別認同與氣質、經驗程度、教育程度、社經地位、國籍、個人外表、種族、宗教信仰、或性傾向。
## 我們的準則
@ -32,14 +32,14 @@
- 使用帶有性暗示的言語或影像,以及不受歡迎的性關注或騷擾
- 挑釁、羞辱/貶低他人的評論,以及人身或政治攻擊
- 公開或私下的騷擾行為
- 未經他人明確許可,公開他人的私人資料,如實體或電子郵件地址
- 未經他人明確許可,公開他人的私人資訊,如實體地址或電子郵件信箱
- 其他在專業環境中可被合理認定為不恰當的行為
## 我們的責任
專案維護者有責任釐清可接受行為的標準,並應對任何不可接受的行為採取適當且公平的糾正措施。
專案維護者有責任釐清可接受行為的標準,並應對任何不可接受的行為採取適當且公平的處置措施。
專案維護者有權利和責任移除、編輯或拒絕不符合本行為準則的評論、提交、程式碼、維基編輯、議題和其他貢獻,或暫時或永久封鎖任何他們認為有不當、威脅、冒犯或有害行為的貢獻者。
專案維護者有權利和責任移除、編輯或拒絕不符合本行為準則的留言、Commit、程式碼、Wiki 編輯、Issue 和其他貢獻,或暫時或永久封鎖任何他們認為有不當、威脅、冒犯或有害行為的貢獻者。
## 範疇
@ -49,7 +49,7 @@
如發生辱罵、騷擾或其他不可接受的行為,請透過 support@roocode.com 聯絡專案團隊回報。所有申訴都將被審查和調查,並做出必要且合適的回應。專案團隊有義務為事件回報者保密。具體執行政策的更多細節可能另行公佈。
未遵守或未切實執行本行為準則的專案維護者,可能會面臨由專案領導團隊其他成員所決定的暫時或永久的處置
未遵守或未切實執行本行為準則的專案維護者,可能會面臨由專案領導團隊其他成員所決定的暫時或永久處分
## 來源說明

View file

@ -13,7 +13,7 @@
# 為 Roo Code 做出貢獻
Roo Code 是一個由社群驅動的專案,我們非常重視每一份貢獻。為了簡化協作,我們採用 [「問題優先」的方法](#問題優先方法),這意味著所有的 [拉取請求 (PR)](#提交拉取請求) 都必須先連結到一個 GitHub 問題。請仔細閱讀本指南。
Roo Code 是一個由社群驅動的專案,我們非常重視每一份貢獻。為了簡化協作流程,我們採用 [「Issue 優先」的方法](#issue-優先方法),這意味著所有的 [Pull Request (PR)](#提交-pull-request) 都必須先連結到一個 GitHub Issue。請仔細閱讀本指南。
## 目錄
@ -30,32 +30,32 @@ Roo Code 是一個由社群驅動的專案,我們非常重視每一份貢獻
### 2. 專案路線圖
我們的路線圖指著專案的方向。請將您的貢獻與這些關鍵目標保持一致:
我們的路線圖指著專案的方向。請將您的貢獻與這些關鍵目標保持一致:
### 可靠性第一
### 可靠性第一 (Reliability First)
- 確保差異編輯和命令執行始終可靠。
- 減少阻礙常規使用的摩擦
- 保證在所有地區和平台上的流暢操作
- 擴大對各種人工智慧提供商和模型的強大支援。
- 減少阻礙常規使用的摩擦。
- 保證在所有語系和平台上的操作流暢
- 擴大對各種 AI 供應商和模型的強大支援。
### 增強的使用者體驗
### 增強的使用者體驗 (Enhanced User Experience)
- 簡化使用者介面/使用者體驗,以提高清晰度和直觀性。
- 不斷改進工作流程,以滿足開發人員對日常使用工具的高期望。
- 簡化 UI/UX提高清晰度和直覺性。
- 持續改進工作流程,以滿足開發者對日常使用工具的高期望。
### 在代理效能上領先
### 在 Agent 效能上領先 (Leading on Agent Performance)
- 建立全面的評估基準 (evals) 來衡量真實世界的生產力。
- 讓每個人都能輕鬆執行和解釋這些評估
- 發布能顯示評估分數明顯提高的改進。
- 建立全面的評估基準 (Evals) 來衡量實際應用的生產力。
- 讓每個人都能輕鬆執行和解讀這些評估結果
- 發布能顯示評估分數有明顯提升的改進。
在您的 PR 中提及與這些領域的一致性。
### 3. 加入 Roo Code 社群
- **主要方式:** 加入我們的 [Discord](https://discord.gg/roocode) 並私訊 **Hannes Rudolph (`hrudolph`)**
- **替代方式:** 經驗豐富的貢獻者可以透過 [GitHub 專案](https://github.com/orgs/RooCodeInc/projects/1) 直接參與。
- **替代方式:** 經驗豐富的貢獻者可以透過 [GitHub Project](https://github.com/orgs/RooCodeInc/projects/1) 直接參與。
## 尋找和規劃您的貢獻
@ -65,42 +65,42 @@ Roo Code 是一個由社群驅動的專案,我們非常重視每一份貢獻
- **新功能:** 新增功能。
- **文件:** 改進指南和清晰度。
### 問題優先方法
### Issue 優先方法
所有貢獻都始於使用我們精簡範本的 GitHub 問題
所有貢獻都始於使用我們精簡範本的 GitHub Issue
- **檢查現有問題**:在 [GitHub 問題](https://github.com/RooCodeInc/Roo-Code/issues) 中搜尋。
- **使用以下範本建立問題**
- **增強功能:**增強請求」範本(著重於使用者利益的簡單語言)。
- **錯誤** 「錯誤報告」範本(最少的重現步驟 + 預期與實際 + 版本)。
- **想參與其中嗎?**問題上評論“領取”,並在[Discord](https://discord.gg/roocode)上私訊 **Hannes Rudolph (`hrudolph`)** 以獲得分配。分配將在帖子中確認。
- **PR 必須連結到問題。** 未連結的 PR 可能會被關閉。
- **檢查現有 Issue**:在 [GitHub Issues](https://github.com/RooCodeInc/Roo-Code/issues) 中搜尋。
- **使用以下範本建立 Issue**
- **增強功能:**Enhancement Request」範本著重於使用者利益的淺顯描述)。
- **錯誤** 「Bug Report」範本(最少的重現步驟 + 預期與實際結果 + 版本)。
- **想參與其中嗎?** Issue 上留言「Claiming」並在 [Discord](https://discord.gg/roocode) 上私訊 **Hannes Rudolph (`hrudolph`)** 以獲得分配。分配結果將在討論串中確認。
- **PR 必須連結到 Issue。** 未連結的 PR 可能會被關閉。
### 決定做什麼
- 查看 [GitHub 專案](https://github.com/orgs/RooCodeInc/projects/1) 中的「問題 [未分配]」問題
- 如需文件,請造訪 [Roo Code 文件](https://github.com/RooCodeInc/Roo-Code-Docs)。
- 查看 [GitHub 專案](https://github.com/orgs/RooCodeInc/projects/1) 中的「Issue [Unassigned]」
- 如需文件,請造訪 [Roo Code Docs](https://github.com/RooCodeInc/Roo-Code-Docs)。
### 報錯誤
### 報錯誤
- 首先檢查現有的報告。
- 使用 [錯誤報告」範本](https://github.com/RooCodeInc/Roo-Code/issues/new/choose) 建立一個新錯誤,並提供:
- 使用 [Bug Report」範本](https://github.com/RooCodeInc/Roo-Code/issues/new/choose) 建立一個新錯誤回報,並提供:
- 清晰、編號的重現步驟
- 預期與實際結果
- Roo Code 版本(必需);如果相關,還需提供 API 提供商/模型
- **安全問題**:透過 [安全公告](https://github.com/RooCodeInc/Roo-Code/security/advisories/new) 私下報
- Roo Code 版本(必填);如果相關,還需提供 API 供應商/模型
- **安全問題**:透過 [安全公告 (Security Advisories)](https://github.com/RooCodeInc/Roo-Code/security/advisories/new) 私下報。
## 開發和提交流程
### 開發設定
1. **複製和克隆**
1. **Fork 與 Clone**
```
git clone https://github.com/您的使用者名稱/Roo-Code.git
git clone https://github.com/YOUR_USERNAME/Roo-Code.git
```
2. **安裝依賴項**
2. **安裝相依套件**
```
pnpm install
@ -108,34 +108,34 @@ pnpm install
3. **偵錯:** 使用 VS Code 開啟(`F5`)。
### 編碼指南
### 程式碼撰寫指南
- 每個功能或修復一個集中的 PR。
- 遵循 ESLint 和 TypeScript 的最佳實
- 編寫清晰、描述性的提交,並引用問題(例如,`修復 #123`)。
- 每個功能或修復使用一個單一目的的 PR。
- 遵循 ESLint 和 TypeScript 的最佳實
- 撰寫清晰、描述性的 Commit 訊息,並引用 Issue例如`Fixes #123`)。
- 提供全面的測試(`npm test`)。
- 在提交前變基到最新的 `main` 分支。
- 在提交前 Rebase 到最新的 `main` 分支。
### 提交拉取請求
### 提交 Pull Request
- 如果希望獲得早期回饋,請以 **草稿 PR** 開始。
- 遵循拉取請求範本,清晰地描述您的變更。
- 在 PR 描述/標題中連結問題(例如,“修復 #123)。
- 如果希望獲得早期回饋,請以 **Draft PR** 開始。
- 遵循 Pull Request 範本,清晰地描述您的變更。
- 在 PR 描述/標題中連結 Issue例如「Fixes #123)。
- 為使用者介面變更提供螢幕截圖/影片。
- 指明是否需要更新文件。
### 拉取請求政策
### Pull Request 政策
- 必須引用一個已分配的 GitHub 問題。要獲得分配:在問題上評論“領取”,並在[Discord](https://discord.gg/roocode)上私訊 **Hannes Rudolph (`hrudolph`)**分配將在帖子中確認。
- 未連結的 PR 可能會被關閉。
- 必須引用一個已指派的 GitHub Issue。如要被指派請在 Issue 上留言「Claiming」並在 [Discord](https://discord.gg/roocode) 上私訊 **Hannes Rudolph (`hrudolph`)**指派結果將可在討論串中確認。
- 未連結 Issue 的 PR 可能會被關閉。
- PR 必須通過 CI 測試,與路線圖保持一致,並有清晰的文件。
### 審查流程
- **每日分類:** 維護人員進行快速檢查。
- **每日 Triage** 維護者進行快速檢查。
- **每週深入審查:** 全面評估。
- **根據回饋及時迭代**
## 法律
## 法律資訊
透過貢獻,您同意您的貢獻將根據 Apache 2.0 授權進行授權,這與 Roo Code 的授權一致。
透過貢獻,您同意您的貢獻將根據 Apache 2.0 授權條款進行授權,這與 Roo Code 的授權一致。

View file

@ -6,15 +6,15 @@
<a href="https://www.reddit.com/r/RooCode/"><img src="https://img.shields.io/badge/Join%20r%2FRooCode-FF4500?style=flat&logo=reddit&logoColor=white" alt="Join r/RooCode"></a>
</p>
<p align="center">
<em>快速取得協助 → <a href="https://discord.gg/roocode">加入 Discord</a> • 偏好非同步?→ <a href="https://www.reddit.com/r/RooCode/">加入 r/RooCode</a></em>
<em>快速取得協助 → <a href="https://discord.gg/roocode">加入 Discord</a> • 偏好非同步溝通?→ <a href="https://www.reddit.com/r/RooCode/">加入 r/RooCode</a></em>
</p>
# Roo Code
> 你的 AI 驅動開發團隊,就在你的編輯器裡
> 您的 AI 驅動開發團隊,就在您的編輯器中
<details>
<summary>🌐 可用語言</summary>
<summary>🌐 支援語言</summary>
- [English](../../README.md)
- [Català](../ca/README.md)
@ -51,14 +51,14 @@
## 模式
Roo Code 適應您的工作方式,而不是相反
Roo Code 會配合您的工作方式,而非要您配合它
- 程式碼模式:日常編碼、編輯和檔案操作
- 程式碼模式:日常開發、編輯和檔案操作
- 架構師模式:規劃系統、規格和遷移
- 詢問模式:快速回答、解釋和文件
- 偵錯模式:追蹤問題、新增日誌、隔離根本原因
- 偵錯模式:追蹤問題、新增日誌、鎖定根本原因
- 自訂模式:為您的團隊或工作流程建置專門的模式
- Roomote ControlRoomote Control 讓能遠端控制在本機 VS Code 執行個體中運行的工作。
- Roomote ControlRoomote Control 讓能遠端控制在本機 VS Code 執行個體中運行的工作。
更多資訊:[使用模式](https://docs.roocode.com/basic-usage/using-modes) • [自訂模式](https://docs.roocode.com/advanced-usage/custom-modes) • [Roomote Control](https://docs.roocode.com/roo-code-cloud/roomote-control)
@ -69,7 +69,7 @@ Roo Code 適應您的工作方式,而不是相反:
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <a href="https://www.youtube.com/watch?v=Mcq3r1EPZ-4"><img src="https://img.youtube.com/vi/Mcq3r1EPZ-4/maxresdefault.jpg" width="100%"></a><br><b>安裝 Roo Code</b> | <a href="https://www.youtube.com/watch?v=ZBML8h5cCgo"><img src="https://img.youtube.com/vi/ZBML8h5cCgo/maxresdefault.jpg" width="100%"></a><br><b>設定設定檔</b> | <a href="https://www.youtube.com/watch?v=r1bpod1VWhg"><img src="https://img.youtube.com/vi/r1bpod1VWhg/maxresdefault.jpg" width="100%"></a><br><b>程式碼庫索引</b> |
| <a href="https://www.youtube.com/watch?v=iiAv1eKOaxk"><img src="https://img.youtube.com/vi/iiAv1eKOaxk/maxresdefault.jpg" width="100%"></a><br><b>自訂模式</b> | <a href="https://www.youtube.com/watch?v=Ho30nyY332E"><img src="https://img.youtube.com/vi/Ho30nyY332E/maxresdefault.jpg" width="100%"></a><br><b>檢查點</b> | <a href="https://www.youtube.com/watch?v=HmnNSasv7T8"><img src="https://img.youtube.com/vi/HmnNSasv7T8/maxresdefault.jpg" width="100%"></a><br><b>上下文管理</b> |
| <a href="https://www.youtube.com/watch?v=iiAv1eKOaxk"><img src="https://img.youtube.com/vi/iiAv1eKOaxk/maxresdefault.jpg" width="100%"></a><br><b>自訂模式</b> | <a href="https://www.youtube.com/watch?v=Ho30nyY332E"><img src="https://img.youtube.com/vi/Ho30nyY332E/maxresdefault.jpg" width="100%"></a><br><b>檢查點</b> | <a href="https://www.youtube.com/watch?v=HmnNSasv7T8"><img src="https://img.youtube.com/vi/HmnNSasv7T8/maxresdefault.jpg" width="100%"></a><br><b>上下文管理</b> |
</div>
<p align="center">
@ -82,12 +82,12 @@ Roo Code 適應您的工作方式,而不是相反:
- **[YouTube 頻道](https://youtube.com/@roocodeyt?feature=shared):** 觀看教學和功能實際操作。
- **[Discord 伺服器](https://discord.gg/roocode):** 加入社群以獲得即時協助和討論。
- **[Reddit 社群](https://www.reddit.com/r/RooCode):** 分享您的經驗,看看其他人正在建立什麼。
- **[GitHub 問題](https://github.com/RooCodeInc/Roo-Code/issues):** 回報錯誤並追蹤開發進度。
- **[GitHub Issues](https://github.com/RooCodeInc/Roo-Code/issues):** 回報問題並追蹤開發進度。
- **[功能請求](https://github.com/RooCodeInc/Roo-Code/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop):** 有想法嗎?與開發人員分享。
---
## 本設定與開發
## 本設定與開發
1. **複製**儲存庫:
@ -95,7 +95,7 @@ Roo Code 適應您的工作方式,而不是相反:
git clone https://github.com/RooCodeInc/Roo-Code.git
```
2. **安裝套件**:
2. **安裝依套件**:
```sh
pnpm install
@ -107,7 +107,7 @@ pnpm install
### 開發模式F5
對於積極的開發,請使用 VSCode 的內建偵錯功能:
若要進行開發,請使用 VSCode 的內建偵錯功能:
在 VSCode 中按 `F5`(或前往 **執行****開始偵錯**)。這將在執行 Roo Code 擴充功能的新 VSCode 視窗中開啟。
@ -127,7 +127,7 @@ pnpm install:vsix [-y] [--editor=<command>]
- 詢問要使用的編輯器命令code/cursor/code-insiders - 預設為“code”
- 解除安裝任何現有版本的擴充功能。
- 建置最新的 VSIX 套件。
- 安裝新建的 VSIX。
- 安裝新建的 VSIX。
- 提示您重新啟動 VS Code 以使變更生效。
選項:
@ -144,7 +144,7 @@ pnpm install:vsix [-y] [--editor=<command>]
pnpm vsix
```
2. 將在 `bin/` 目錄中產生一個 `.vsix` 檔案(例如 `bin/roo-cline-<version>.vsix`)。
3. 使用 VSCode CLI 手動安裝
3. 使用 VSCode CLI 手動安裝
```sh
code --install-extension bin/roo-cline-<version>.vsix
```
@ -163,7 +163,7 @@ pnpm install:vsix [-y] [--editor=<command>]
## 貢獻
我們歡迎社群貢獻!請閱讀我們的 [CONTRIBUTING.md](CONTRIBUTING.md) 開始。
我們歡迎社群貢獻!請閱讀我們的 [CONTRIBUTING.md](CONTRIBUTING.md) 開始。
---
@ -173,4 +173,4 @@ pnpm install:vsix [-y] [--editor=<command>]
---
**享受 Roo Code** 無論您是將它拴在短繩上還是讓它自主漫遊,我們迫不及待地想看看您會建構什麼。如果您有問題或功能想法,請造訪我們的 [Reddit 社群](https://www.reddit.com/r/RooCode/)或 [Discord](https://discord.gg/roocode)。祝您開發愉快!
**享受 Roo Code** 無論您是想嚴格控管它,還是讓它自主運作,我們都迫不及待地想看看您會打造些什麼。如果您有問題或功能想法,請造訪我們的 [Reddit 社群](https://www.reddit.com/r/RooCode/)或 [Discord](https://discord.gg/roocode)。祝您開發愉快!

View file

@ -20,6 +20,7 @@
"vsix:nightly": "turbo vsix:nightly --log-order grouped --output-logs new-only",
"clean": "turbo clean --log-order grouped --output-logs new-only && rimraf dist out bin .vite-port .turbo",
"install:vsix": "pnpm install --frozen-lockfile && pnpm clean && pnpm vsix && node scripts/install-vsix.js",
"install:vsix:nightly": "pnpm install --frozen-lockfile && pnpm clean && pnpm vsix:nightly && node scripts/install-vsix.js --nightly",
"changeset:version": "cp CHANGELOG.md src/CHANGELOG.md && changeset version && cp -vf src/CHANGELOG.md .",
"knip": "knip --include files",
"evals": "dotenvx run -f packages/evals/.env.development packages/evals/.env.local -- docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0",
@ -65,7 +66,8 @@
"bluebird": ">=3.7.2",
"glob": ">=11.1.0",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.5"
"@types/react-dom": "^18.3.5",
"zod": "3.25.76"
}
}
}

View file

@ -18,6 +18,7 @@
"@roo-code/types": "workspace:^",
"esbuild": "^0.25.0",
"execa": "^9.5.2",
"ignore": "^7.0.3",
"openai": "^5.12.2",
"zod": "^3.25.61"
},

View file

@ -1,129 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`XML Protocol snapshots > should generate correct XML description for all fixtures combined 1`] = `
"# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
## simple
Description: Simple tool
Parameters:
- value: (required) The input value (type: string)
Usage:
<simple>
<value>value value here</value>
</simple>
## cached
Description: Cached tool
Parameters:
Usage:
<cached>
</cached>
## legacy
Description: Legacy tool using args
Parameters:
- input: (required) The input string (type: string)
Usage:
<legacy>
<input>input value here</input>
</legacy>
## multi_toolA
Description: Tool A
Parameters:
Usage:
<multi_toolA>
</multi_toolA>
## multi_toolB
Description: Tool B
Parameters:
Usage:
<multi_toolB>
</multi_toolB>
## mixed_validTool
Description: Valid
Parameters:
Usage:
<mixed_validTool>
</mixed_validTool>"
`;
exports[`XML Protocol snapshots > should generate correct XML description for cached tool 1`] = `
"# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
## cached
Description: Cached tool
Parameters:
Usage:
<cached>
</cached>"
`;
exports[`XML Protocol snapshots > should generate correct XML description for legacy tool (using args) 1`] = `
"# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
## legacy
Description: Legacy tool using args
Parameters:
- input: (required) The input string (type: string)
Usage:
<legacy>
<input>input value here</input>
</legacy>"
`;
exports[`XML Protocol snapshots > should generate correct XML description for mixed export tool 1`] = `
"# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
## mixed_validTool
Description: Valid
Parameters:
Usage:
<mixed_validTool>
</mixed_validTool>"
`;
exports[`XML Protocol snapshots > should generate correct XML description for multi export tools 1`] = `
"# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
## multi_toolA
Description: Tool A
Parameters:
Usage:
<multi_toolA>
</multi_toolA>
## multi_toolB
Description: Tool B
Parameters:
Usage:
<multi_toolB>
</multi_toolB>"
`;
exports[`XML Protocol snapshots > should generate correct XML description for simple tool 1`] = `
"# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
## simple
Description: Simple tool
Parameters:
- value: (required) The input value (type: string)
Usage:
<simple>
<value>value value here</value>
</simple>"
`;

View file

@ -1,192 +0,0 @@
// pnpm --filter @roo-code/core test src/custom-tools/__tests__/format-xml.spec.ts
import { type SerializedCustomToolDefinition, parametersSchema as z, defineCustomTool } from "@roo-code/types"
import { serializeCustomTool, serializeCustomTools } from "../serialize.js"
import { formatXml } from "../format-xml.js"
import simpleTool from "./fixtures/simple.js"
import cachedTool from "./fixtures/cached.js"
import legacyTool from "./fixtures/legacy.js"
import { toolA, toolB } from "./fixtures/multi.js"
import { validTool as mixedValidTool } from "./fixtures/mixed.js"
const fixtureTools = {
simple: simpleTool,
cached: cachedTool,
legacy: legacyTool,
multi_toolA: toolA,
multi_toolB: toolB,
mixed_validTool: mixedValidTool,
}
describe("formatXml", () => {
it("should return empty string for empty tools array", () => {
expect(formatXml([])).toBe("")
})
it("should throw for undefined tools", () => {
expect(() => formatXml(undefined as unknown as SerializedCustomToolDefinition[])).toThrow()
})
it("should generate description for a single tool without args", () => {
const tool = defineCustomTool({
name: "my_tool",
description: "A simple tool that does something",
async execute() {
return "done"
},
})
const serialized = serializeCustomTool(tool)
const result = formatXml([serialized])
expect(result).toContain("# Custom Tools")
expect(result).toContain("## my_tool")
expect(result).toContain("Description: A simple tool that does something")
expect(result).toContain("Parameters: None")
expect(result).toContain("<my_tool>")
expect(result).toContain("</my_tool>")
})
it("should generate description for a tool with required args", () => {
const tool = defineCustomTool({
name: "greeter",
description: "Greets a person by name",
parameters: z.object({
name: z.string().describe("The name of the person to greet"),
}),
async execute({ name }) {
return `Hello, ${name}!`
},
})
const serialized = serializeCustomTool(tool)
const result = formatXml([serialized])
expect(result).toContain("## greeter")
expect(result).toContain("Description: Greets a person by name")
expect(result).toContain("Parameters:")
expect(result).toContain("- name: (required) The name of the person to greet (type: string)")
expect(result).toContain("<greeter>")
expect(result).toContain("<name>name value here</name>")
expect(result).toContain("</greeter>")
})
it("should generate description for a tool with optional args", () => {
const tool = defineCustomTool({
name: "configurable_tool",
description: "A tool with optional configuration",
parameters: z.object({
input: z.string().describe("The input to process"),
format: z.string().optional().describe("Output format"),
}),
async execute({ input, format }) {
return format ? `${input} (${format})` : input
},
})
const serialized = serializeCustomTool(tool)
const result = formatXml([serialized])
expect(result).toContain("- input: (required) The input to process (type: string)")
expect(result).toContain("- format: (optional) Output format (type: string)")
expect(result).toContain("<input>input value here</input>")
expect(result).toContain("<format>optional format value</format>")
})
it("should generate descriptions for multiple tools", () => {
const tools = [
defineCustomTool({
name: "tool_a",
description: "First tool",
async execute() {
return "a"
},
}),
defineCustomTool({
name: "tool_b",
description: "Second tool",
parameters: z.object({
value: z.number().describe("A numeric value"),
}),
async execute() {
return "b"
},
}),
]
const serialized = serializeCustomTools(tools)
const result = formatXml(serialized)
expect(result).toContain("## tool_a")
expect(result).toContain("Description: First tool")
expect(result).toContain("## tool_b")
expect(result).toContain("Description: Second tool")
expect(result).toContain("- value: (required) A numeric value (type: number)")
})
it("should treat args in required array as required", () => {
// Using a raw SerializedToolDefinition to test the required behavior.
const tools: SerializedCustomToolDefinition[] = [
{
name: "test_tool",
description: "Test tool",
parameters: {
type: "object",
properties: {
data: {
type: "object",
description: "Some data",
},
},
required: ["data"],
},
},
]
const result = formatXml(tools)
expect(result).toContain("- data: (required) Some data (type: object)")
expect(result).toContain("<data>data value here</data>")
})
})
describe("XML Protocol snapshots", () => {
it("should generate correct XML description for simple tool", () => {
const serialized = serializeCustomTool(fixtureTools.simple)
const result = formatXml([serialized])
expect(result).toMatchSnapshot()
})
it("should generate correct XML description for cached tool", () => {
const serialized = serializeCustomTool(fixtureTools.cached)
const result = formatXml([serialized])
expect(result).toMatchSnapshot()
})
it("should generate correct XML description for legacy tool (using args)", () => {
const serialized = serializeCustomTool(fixtureTools.legacy)
const result = formatXml([serialized])
expect(result).toMatchSnapshot()
})
it("should generate correct XML description for multi export tools", () => {
const serializedA = serializeCustomTool(fixtureTools.multi_toolA)
const serializedB = serializeCustomTool(fixtureTools.multi_toolB)
const result = formatXml([serializedA, serializedB])
expect(result).toMatchSnapshot()
})
it("should generate correct XML description for mixed export tool", () => {
const serialized = serializeCustomTool(fixtureTools.mixed_validTool)
const result = formatXml([serialized])
expect(result).toMatchSnapshot()
})
it("should generate correct XML description for all fixtures combined", () => {
const allSerialized = Object.values(fixtureTools).map(serializeCustomTool)
const result = formatXml(allSerialized)
expect(result).toMatchSnapshot()
})
})

View file

@ -1,89 +0,0 @@
import type { SerializedCustomToolDefinition, SerializedCustomToolParameters } from "@roo-code/types"
/**
* Extract the type string from a parameter schema.
* Handles both direct `type` property and `anyOf` schemas (used for nullable types).
*/
function getParameterType(parameter: SerializedCustomToolParameters): string {
// Direct type property
if (parameter.type) {
return String(parameter.type)
}
// Handle anyOf schema (used for nullable types like `string | null`)
if (parameter.anyOf && Array.isArray(parameter.anyOf)) {
const types = parameter.anyOf
.map((schema) => (typeof schema === "object" && schema.type ? String(schema.type) : null))
.filter((t): t is string => t !== null && t !== "null")
if (types.length > 0) {
return types.join(" | ")
}
}
return "unknown"
}
function getParameterDescription(name: string, parameter: SerializedCustomToolParameters, required: string[]): string {
const requiredText = required.includes(name) ? "(required)" : "(optional)"
const typeText = getParameterType(parameter)
return `- ${name}: ${requiredText} ${parameter.description ?? ""} (type: ${typeText})`
}
function getUsage(tool: SerializedCustomToolDefinition): string {
const lines: string[] = [`<${tool.name}>`]
if (tool.parameters) {
const required = tool.parameters.required ?? []
for (const [argName, _argType] of Object.entries(tool.parameters.properties ?? {})) {
const placeholder = required.includes(argName) ? `${argName} value here` : `optional ${argName} value`
lines.push(`<${argName}>${placeholder}</${argName}>`)
}
}
lines.push(`</${tool.name}>`)
return lines.join("\n")
}
function getDescription(tool: SerializedCustomToolDefinition): string {
const parts: string[] = []
parts.push(`## ${tool.name}`)
parts.push(`Description: ${tool.description}`)
if (tool.parameters?.properties) {
const required = tool.parameters?.required ?? []
parts.push("Parameters:")
for (const [name, parameter] of Object.entries(tool.parameters.properties)) {
// What should we do with `boolean` values for `parameter`?
if (typeof parameter !== "object") {
continue
}
parts.push(getParameterDescription(name, parameter, required))
}
} else {
parts.push("Parameters: None")
}
parts.push("Usage:")
parts.push(getUsage(tool))
return parts.join("\n")
}
export function formatXml(tools: SerializedCustomToolDefinition[]): string {
if (tools.length === 0) {
return ""
}
const descriptions = tools.map((tool) => getDescription(tool))
return `# Custom Tools
The following custom tools are available for this mode. Use them in the same way as built-in tools.
${descriptions.join("\n\n")}`
}

View file

@ -1,4 +1,3 @@
export * from "./custom-tool-registry.js"
export * from "./serialize.js"
export * from "./format-xml.js"
export * from "./format-native.js"

View file

@ -1,3 +1,4 @@
export * from "./custom-tools/index.js"
export * from "./debug-log/index.js"
export * from "./message-utils/index.js"
export * from "./worktree/index.js"

View file

@ -0,0 +1,306 @@
import * as fs from "fs/promises"
import * as path from "path"
import * as os from "os"
import { execFile } from "child_process"
import { promisify } from "util"
import { WorktreeIncludeService } from "../worktree-include.js"
const execFileAsync = promisify(execFile)
async function execGit(cwd: string, args: string[]): Promise<string> {
const { stdout } = await execFileAsync("git", args, { cwd, encoding: "utf8" })
return stdout
}
describe("WorktreeIncludeService", () => {
let service: WorktreeIncludeService
let tempDir: string
beforeEach(async () => {
service = new WorktreeIncludeService()
// Create a temp directory for each test
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "worktree-test-"))
})
afterEach(async () => {
// Clean up temp directory
try {
await fs.rm(tempDir, { recursive: true })
} catch {
// Ignore cleanup errors
}
})
describe("hasWorktreeInclude", () => {
it("should return true when .worktreeinclude exists", async () => {
await fs.writeFile(path.join(tempDir, ".worktreeinclude"), "node_modules")
const result = await service.hasWorktreeInclude(tempDir)
expect(result).toBe(true)
})
it("should return false when .worktreeinclude does not exist", async () => {
const result = await service.hasWorktreeInclude(tempDir)
expect(result).toBe(false)
})
it("should return false for non-existent directory", async () => {
const result = await service.hasWorktreeInclude("/non/existent/path")
expect(result).toBe(false)
})
})
describe("branchHasWorktreeInclude", () => {
it("should detect .worktreeinclude on the specified branch", async () => {
const repoDir = path.join(tempDir, "repo")
await fs.mkdir(repoDir, { recursive: true })
await execGit(repoDir, ["init"])
await execGit(repoDir, ["config", "user.name", "Test User"])
await execGit(repoDir, ["config", "user.email", "test@example.com"])
await fs.writeFile(path.join(repoDir, "README.md"), "test")
await execGit(repoDir, ["add", "README.md"])
await execGit(repoDir, ["commit", "-m", "init"])
const baseBranch = (await execGit(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"])).trim()
expect(await service.branchHasWorktreeInclude(repoDir, baseBranch)).toBe(false)
await execGit(repoDir, ["checkout", "-b", "with-include"])
await fs.writeFile(path.join(repoDir, ".worktreeinclude"), "node_modules")
await execGit(repoDir, ["add", ".worktreeinclude"])
await execGit(repoDir, ["commit", "-m", "add include"])
expect(await service.branchHasWorktreeInclude(repoDir, "with-include")).toBe(true)
}, 30_000)
})
describe("getStatus", () => {
it("should return correct status when both files exist", async () => {
const gitignoreContent = "node_modules\n.env\ndist"
await fs.writeFile(path.join(tempDir, ".worktreeinclude"), "node_modules")
await fs.writeFile(path.join(tempDir, ".gitignore"), gitignoreContent)
const result = await service.getStatus(tempDir)
expect(result.exists).toBe(true)
expect(result.hasGitignore).toBe(true)
expect(result.gitignoreContent).toBe(gitignoreContent)
})
it("should return correct status when only .gitignore exists", async () => {
const gitignoreContent = "node_modules\n.env"
await fs.writeFile(path.join(tempDir, ".gitignore"), gitignoreContent)
const result = await service.getStatus(tempDir)
expect(result.exists).toBe(false)
expect(result.hasGitignore).toBe(true)
expect(result.gitignoreContent).toBe(gitignoreContent)
})
it("should return correct status when only .worktreeinclude exists", async () => {
await fs.writeFile(path.join(tempDir, ".worktreeinclude"), "node_modules")
const result = await service.getStatus(tempDir)
expect(result.exists).toBe(true)
expect(result.hasGitignore).toBe(false)
expect(result.gitignoreContent).toBeUndefined()
})
it("should return correct status when neither file exists", async () => {
const result = await service.getStatus(tempDir)
expect(result.exists).toBe(false)
expect(result.hasGitignore).toBe(false)
expect(result.gitignoreContent).toBeUndefined()
})
})
describe("createWorktreeInclude", () => {
it("should create .worktreeinclude file with specified content", async () => {
const content = "node_modules\n.env\ndist"
await service.createWorktreeInclude(tempDir, content)
const fileContent = await fs.readFile(path.join(tempDir, ".worktreeinclude"), "utf-8")
expect(fileContent).toBe(content)
})
it("should overwrite existing .worktreeinclude file", async () => {
await fs.writeFile(path.join(tempDir, ".worktreeinclude"), "old content")
const newContent = "new content"
await service.createWorktreeInclude(tempDir, newContent)
const fileContent = await fs.readFile(path.join(tempDir, ".worktreeinclude"), "utf-8")
expect(fileContent).toBe(newContent)
})
})
describe("copyWorktreeIncludeFiles", () => {
let sourceDir: string
let targetDir: string
beforeEach(async () => {
sourceDir = path.join(tempDir, "source")
targetDir = path.join(tempDir, "target")
await fs.mkdir(sourceDir, { recursive: true })
await fs.mkdir(targetDir, { recursive: true })
})
it("should return empty array when no .worktreeinclude exists", async () => {
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules")
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toEqual([])
})
it("should return empty array when no .gitignore exists", async () => {
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules")
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toEqual([])
})
it("should return empty array when patterns do not match", async () => {
// .worktreeinclude wants node_modules, .gitignore only ignores .env
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules")
await fs.writeFile(path.join(sourceDir, ".gitignore"), ".env")
await fs.mkdir(path.join(sourceDir, "node_modules"), { recursive: true })
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toEqual([])
})
it("should copy files that match both patterns", async () => {
// Both files include node_modules
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules")
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules")
// Create a file in node_modules
await fs.mkdir(path.join(sourceDir, "node_modules"), { recursive: true })
await fs.writeFile(path.join(sourceDir, "node_modules", "package.json"), '{"name": "test"}')
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toContain("node_modules")
// Verify the file was copied
const copiedContent = await fs.readFile(path.join(targetDir, "node_modules", "package.json"), "utf-8")
expect(copiedContent).toBe('{"name": "test"}')
})
it("should only copy intersection of patterns", async () => {
// .worktreeinclude: node_modules, dist
// .gitignore: node_modules, .env
// Only node_modules should be copied (intersection)
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules\ndist")
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules\n.env")
await fs.mkdir(path.join(sourceDir, "node_modules"), { recursive: true })
await fs.mkdir(path.join(sourceDir, "dist"), { recursive: true })
await fs.writeFile(path.join(sourceDir, ".env"), "SECRET=123")
await fs.writeFile(path.join(sourceDir, "node_modules", "test.txt"), "test")
await fs.writeFile(path.join(sourceDir, "dist", "main.js"), "console.log('dist')")
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
// Only node_modules should be in the result (matches both)
expect(result).toContain("node_modules")
expect(result).not.toContain("dist") // only in .worktreeinclude
expect(result).not.toContain(".env") // only in .gitignore
// Verify node_modules was copied
const nodeModulesExists = await fs
.access(path.join(targetDir, "node_modules"))
.then(() => true)
.catch(() => false)
expect(nodeModulesExists).toBe(true)
// Verify dist was NOT copied
const distExists = await fs
.access(path.join(targetDir, "dist"))
.then(() => true)
.catch(() => false)
expect(distExists).toBe(false)
})
it("should skip .git directory", async () => {
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), ".git")
await fs.writeFile(path.join(sourceDir, ".gitignore"), ".git")
await fs.mkdir(path.join(sourceDir, ".git"), { recursive: true })
await fs.writeFile(path.join(sourceDir, ".git", "config"), "[core]")
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).not.toContain(".git")
})
it("should copy single files", async () => {
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), ".env.local")
await fs.writeFile(path.join(sourceDir, ".gitignore"), ".env.local")
await fs.writeFile(path.join(sourceDir, ".env.local"), "LOCAL_VAR=value")
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toContain(".env.local")
const copiedContent = await fs.readFile(path.join(targetDir, ".env.local"), "utf-8")
expect(copiedContent).toBe("LOCAL_VAR=value")
})
it("should ignore comment lines in pattern files", async () => {
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "# comment\nnode_modules\n# another comment")
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules")
await fs.mkdir(path.join(sourceDir, "node_modules"), { recursive: true })
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toContain("node_modules")
})
it("should call progress callback with bytesCopied progress", async () => {
// Set up files to copy
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules\n.env.local")
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules\n.env.local")
await fs.mkdir(path.join(sourceDir, "node_modules"), { recursive: true })
await fs.writeFile(path.join(sourceDir, "node_modules", "test.txt"), "test")
await fs.writeFile(path.join(sourceDir, ".env.local"), "LOCAL_VAR=value")
const progressCalls: Array<{ bytesCopied: number; itemName: string }> = []
const onProgress = vi.fn((progress: { bytesCopied: number; itemName: string }) => {
progressCalls.push({ ...progress })
})
await service.copyWorktreeIncludeFiles(sourceDir, targetDir, onProgress)
// Should be called multiple times (initial + after each copy)
expect(onProgress).toHaveBeenCalled()
// bytesCopied should increase over time
expect(progressCalls.length).toBeGreaterThan(0)
const finalCall = progressCalls[progressCalls.length - 1]
expect(finalCall?.bytesCopied).toBeGreaterThan(0)
// Each call should have an item name
expect(progressCalls.every((p) => typeof p.itemName === "string")).toBe(true)
})
it("should not fail when progress callback is not provided", async () => {
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules")
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules")
await fs.mkdir(path.join(sourceDir, "node_modules"), { recursive: true })
// Should not throw when no callback is provided
const result = await service.copyWorktreeIncludeFiles(sourceDir, targetDir)
expect(result).toContain("node_modules")
})
})
})

View file

@ -0,0 +1,146 @@
import * as path from "path"
import { WorktreeService } from "../worktree-service.js"
describe("WorktreeService", () => {
describe("normalizePath", () => {
let service: WorktreeService
beforeEach(() => {
service = new WorktreeService()
})
// Access private method for testing
const callNormalizePath = (service: WorktreeService, p: string): string => {
// @ts-expect-error - accessing private method for testing
return service.normalizePath(p)
}
it("should normalize paths with trailing slashes", () => {
const result = callNormalizePath(service, "/home/user/project/")
expect(result).toBe(path.normalize("/home/user/project"))
})
it("should normalize paths with multiple trailing slashes", () => {
const result = callNormalizePath(service, "/home/user/project///")
// path.normalize already handles multiple slashes
expect(result).toBe(path.normalize("/home/user/project"))
})
it("should preserve root path /", () => {
// This is a critical test - the old regex would turn "/" into ""
// On Windows, path.normalize("/") returns "\", on Unix it returns "/"
const result = callNormalizePath(service, "/")
expect(result).toBe(path.sep)
})
it("should handle paths without trailing slashes", () => {
const result = callNormalizePath(service, "/home/user/project")
expect(result).toBe(path.normalize("/home/user/project"))
})
it("should handle relative paths", () => {
const result = callNormalizePath(service, "./some/path/")
expect(result).toBe(path.normalize("./some/path"))
})
it("should handle empty string", () => {
const result = callNormalizePath(service, "")
expect(result).toBe(".")
})
it("should handle Windows-style paths on non-Windows", () => {
// path.normalize will convert separators appropriately
const result = callNormalizePath(service, "C:\\Users\\test\\project")
// On Unix, this stays as-is; on Windows it would normalize
expect(result).toBeTruthy()
})
})
describe("parseWorktreeOutput", () => {
let service: WorktreeService
beforeEach(() => {
service = new WorktreeService()
})
// Access private method for testing
const callParseWorktreeOutput = (
service: WorktreeService,
output: string,
currentCwd: string,
): ReturnType<WorktreeService["parseWorktreeOutput"]> => {
// @ts-expect-error - accessing private method for testing
return service.parseWorktreeOutput(output, currentCwd)
}
it("should parse porcelain output correctly", () => {
const output = `worktree /home/user/repo
HEAD abc123def456
branch refs/heads/main
worktree /home/user/repo-feature
HEAD def456abc123
branch refs/heads/feature/test
`
const result = callParseWorktreeOutput(service, output, "/home/user/repo")
expect(result).toHaveLength(2)
expect(result[0]).toMatchObject({
path: "/home/user/repo",
branch: "main",
commitHash: "abc123def456",
isCurrent: true,
})
expect(result[1]).toMatchObject({
path: "/home/user/repo-feature",
branch: "feature/test",
commitHash: "def456abc123",
isCurrent: false,
})
})
it("should handle detached HEAD worktrees", () => {
const output = `worktree /home/user/repo-detached
HEAD abc123def456
detached
`
const result = callParseWorktreeOutput(service, output, "/home/user/other")
expect(result).toHaveLength(1)
expect(result[0]).toMatchObject({
path: "/home/user/repo-detached",
isDetached: true,
branch: "",
})
})
it("should handle locked worktrees", () => {
const output = `worktree /home/user/repo-locked
HEAD abc123def456
branch refs/heads/locked-branch
locked some reason here
`
const result = callParseWorktreeOutput(service, output, "/home/user/other")
expect(result).toHaveLength(1)
expect(result[0]).toMatchObject({
isLocked: true,
lockReason: "some reason here",
})
})
it("should handle bare worktrees", () => {
const output = `worktree /home/user/repo.git
bare
`
const result = callParseWorktreeOutput(service, output, "/home/user/other")
expect(result).toHaveLength(1)
expect(result[0]).toMatchObject({
path: "/home/user/repo.git",
isBare: true,
})
})
})
})

View file

@ -0,0 +1,13 @@
/**
* Worktree Module
*
* Platform-agnostic git worktree management functionality.
* These exports are decoupled from VSCode and can be used by any consumer.
*/
// Types
export * from "./types.js"
// Services
export { WorktreeService, worktreeService } from "./worktree-service.js"
export { WorktreeIncludeService, worktreeIncludeService, type CopyProgressCallback } from "./worktree-include.js"

View file

@ -0,0 +1,15 @@
/**
* Worktree Types
*
* Re-exports platform-agnostic type definitions from @roo-code/types.
*/
export type {
Worktree,
WorktreeResult,
BranchInfo,
CreateWorktreeOptions,
WorktreeIncludeStatus,
WorktreeListResponse,
WorktreeDefaultsResponse,
} from "@roo-code/types"

View file

@ -0,0 +1,428 @@
/**
* WorktreeIncludeService
*
* Platform-agnostic service for handling .worktreeinclude files.
* Used to copy untracked files (like node_modules) when creating worktrees.
*/
import { execFile, spawn } from "child_process"
import * as fs from "fs/promises"
import * as path from "path"
import { promisify } from "util"
import ignore, { type Ignore } from "ignore"
import type { WorktreeIncludeStatus } from "./types.js"
/**
* Progress info for copy tracking.
* Shows activity without trying to predict total size (which is inaccurate).
*/
export interface CopyProgress {
/** Current bytes copied */
bytesCopied: number
/** Name of current item being copied */
itemName: string
}
/**
* Callback for reporting copy progress during worktree file copying.
*/
export type CopyProgressCallback = (progress: CopyProgress) => void
const execFileAsync = promisify(execFile)
/**
* Service for managing .worktreeinclude files and copying files to new worktrees.
* All methods are platform-agnostic and don't depend on VSCode APIs.
*/
export class WorktreeIncludeService {
/**
* Check if .worktreeinclude exists in a directory
*/
async hasWorktreeInclude(dir: string): Promise<boolean> {
try {
await fs.access(path.join(dir, ".worktreeinclude"))
return true
} catch {
return false
}
}
/**
* Check if a specific branch has .worktreeinclude file (in git, not local filesystem)
* @param cwd - Current working directory (git repo)
* @param branch - Branch name to check
*/
async branchHasWorktreeInclude(cwd: string, branch: string): Promise<boolean> {
try {
const ref = `${branch}:.worktreeinclude`
// Use git cat-file -e to check if the file exists on the branch (without printing contents)
await execFileAsync("git", ["cat-file", "-e", "--", ref], { cwd })
return true
} catch {
// File doesn't exist on this branch
return false
}
}
/**
* Get the status of .worktreeinclude and .gitignore
*/
async getStatus(dir: string): Promise<WorktreeIncludeStatus> {
const worktreeIncludePath = path.join(dir, ".worktreeinclude")
const gitignorePath = path.join(dir, ".gitignore")
let exists = false
let hasGitignore = false
let gitignoreContent: string | undefined
try {
await fs.access(worktreeIncludePath)
exists = true
} catch {
exists = false
}
try {
gitignoreContent = await fs.readFile(gitignorePath, "utf-8")
hasGitignore = true
} catch {
hasGitignore = false
}
return {
exists,
hasGitignore,
gitignoreContent,
}
}
/**
* Create a .worktreeinclude file with the specified content
*/
async createWorktreeInclude(dir: string, content: string): Promise<void> {
await fs.writeFile(path.join(dir, ".worktreeinclude"), content, "utf-8")
}
/**
* Copy files matching .worktreeinclude patterns from source to target.
* Only copies files that are ALSO in .gitignore (to avoid copying tracked files).
*
* @param sourceDir - The source directory containing the files to copy
* @param targetDir - The target directory where files will be copied
* @param onProgress - Optional callback to report copy progress (size-based)
* @returns Array of copied file/directory paths
*/
async copyWorktreeIncludeFiles(
sourceDir: string,
targetDir: string,
onProgress?: CopyProgressCallback,
): Promise<string[]> {
const worktreeIncludePath = path.join(sourceDir, ".worktreeinclude")
const gitignorePath = path.join(sourceDir, ".gitignore")
// Check if both files exist
let hasWorktreeInclude = false
let hasGitignore = false
try {
await fs.access(worktreeIncludePath)
hasWorktreeInclude = true
} catch {
hasWorktreeInclude = false
}
try {
await fs.access(gitignorePath)
hasGitignore = true
} catch {
hasGitignore = false
}
if (!hasWorktreeInclude || !hasGitignore) {
return []
}
// Parse both files
const worktreeIncludePatterns = await this.parseIgnoreFile(worktreeIncludePath)
const gitignorePatterns = await this.parseIgnoreFile(gitignorePath)
if (worktreeIncludePatterns.length === 0 || gitignorePatterns.length === 0) {
return []
}
// Create ignore matchers
const worktreeIncludeMatcher = ignore().add(worktreeIncludePatterns)
const gitignoreMatcher = ignore().add(gitignorePatterns)
// Find items that match BOTH patterns (intersection)
const itemsToCopy = await this.findMatchingItems(sourceDir, worktreeIncludeMatcher, gitignoreMatcher)
if (itemsToCopy.length === 0) {
return []
}
let bytesCopied = 0
// Report initial progress
if (onProgress && itemsToCopy.length > 0) {
onProgress({ bytesCopied: 0, itemName: itemsToCopy[0]! })
}
// Copy the items with progress tracking (no total size calculation)
const copiedItems: string[] = []
for (const item of itemsToCopy) {
const sourcePath = path.join(sourceDir, item)
const targetPath = path.join(targetDir, item)
try {
const stats = await fs.stat(sourcePath)
if (stats.isDirectory()) {
// Copy directory with progress tracking
bytesCopied = await this.copyDirectoryWithProgress(
sourcePath,
targetPath,
item,
bytesCopied,
onProgress,
)
} else {
// Report progress before copying
onProgress?.({ bytesCopied, itemName: item })
// Ensure parent directory exists
await fs.mkdir(path.dirname(targetPath), { recursive: true })
await fs.copyFile(sourcePath, targetPath)
// Update bytes copied
bytesCopied += this.getSizeOnDisk(stats)
}
copiedItems.push(item)
// Report progress after copying
onProgress?.({ bytesCopied, itemName: item })
} catch (error) {
// Log but don't fail on individual copy errors
console.error(`Failed to copy ${item}:`, error)
}
}
return copiedItems
}
/**
* Get the size on disk of a file (accounts for filesystem block allocation).
* Uses blksize to calculate actual disk usage including block overhead.
*/
private getSizeOnDisk(stats: { size: number; blksize?: number }): number {
// Calculate size on disk using filesystem block size
if (stats.blksize !== undefined && stats.blksize > 0) {
return stats.blksize * Math.ceil(stats.size / stats.blksize)
}
// Fallback to logical size when blksize not available
return stats.size
}
/**
* Get the total size on disk of a file or directory (recursively).
* Uses native Node.js fs operations for cross-platform compatibility.
*/
private async getPathSize(targetPath: string): Promise<number> {
try {
const stats = await fs.stat(targetPath)
if (stats.isFile()) {
return this.getSizeOnDisk(stats)
}
if (stats.isDirectory()) {
return await this.getDirectorySizeRecursive(targetPath)
}
return 0
} catch {
return 0
}
}
/**
* Recursively calculate directory size on disk using Node.js fs.
* Uses parallel processing for better performance on large directories.
*/
private async getDirectorySizeRecursive(dirPath: string): Promise<number> {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true })
const sizes = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(dirPath, entry.name)
try {
if (entry.isFile()) {
const stats = await fs.stat(entryPath)
return this.getSizeOnDisk(stats)
} else if (entry.isDirectory()) {
return await this.getDirectorySizeRecursive(entryPath)
}
return 0
} catch {
return 0 // Skip inaccessible files
}
}),
)
return sizes.reduce((sum, size) => sum + size, 0)
} catch {
return 0
}
}
/**
* Get the current size of a directory (for progress tracking).
*/
private async getCurrentDirectorySize(dirPath: string): Promise<number> {
try {
await fs.access(dirPath)
return await this.getDirectorySizeRecursive(dirPath)
} catch {
return 0
}
}
/**
* Copy directory with progress polling using native cp command.
* Starts native copy and polls target directory size to report progress.
* Returns the updated bytesCopied count.
*/
private async copyDirectoryWithProgress(
source: string,
target: string,
itemName: string,
bytesCopiedBefore: number,
onProgress?: CopyProgressCallback,
): Promise<number> {
// Ensure parent directory exists
await fs.mkdir(path.dirname(target), { recursive: true })
const isWindows = process.platform === "win32"
// Start the copy process
const copyPromise = new Promise<void>((resolve, reject) => {
let proc: ReturnType<typeof spawn>
if (isWindows) {
proc = spawn("robocopy", [source, target, "/E", "/NFL", "/NDL", "/NJH", "/NJS", "/NC", "/NS", "/NP"], {
windowsHide: true,
})
} else {
proc = spawn("cp", ["-r", "--", source, target])
}
proc.on("close", (code) => {
if (isWindows) {
// robocopy returns non-zero for success (values < 8)
if (code !== null && code < 8) {
resolve()
} else {
reject(new Error(`robocopy failed with code ${code}`))
}
} else {
if (code === 0) {
resolve()
} else {
reject(new Error(`cp failed with code ${code}`))
}
}
})
proc.on("error", reject)
})
// Poll progress while copying
const pollInterval = 500 // Poll every 500ms
let polling = true
const pollProgress = async () => {
while (polling) {
const currentSize = await this.getCurrentDirectorySize(target)
const totalCopied = bytesCopiedBefore + currentSize
onProgress?.({
bytesCopied: totalCopied,
itemName,
})
await new Promise((resolve) => setTimeout(resolve, pollInterval))
}
}
// Start polling and wait for copy to complete
const pollPromise = pollProgress()
try {
await copyPromise
} finally {
polling = false
// Wait for final poll iteration to complete
await pollPromise.catch(() => {})
}
// Get the final size of the copied directory
const finalSize = await this.getPathSize(target)
return bytesCopiedBefore + finalSize
}
/**
* Parse a .gitignore-style file and return the patterns
*/
private async parseIgnoreFile(filePath: string): Promise<string[]> {
try {
const content = await fs.readFile(filePath, "utf-8")
return content
.split("\n")
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#"))
} catch {
return []
}
}
/**
* Find items in sourceDir that match both matchers
*/
private async findMatchingItems(
sourceDir: string,
includeMatcher: Ignore,
gitignoreMatcher: Ignore,
): Promise<string[]> {
const matchingItems: string[] = []
try {
const entries = await fs.readdir(sourceDir, { withFileTypes: true })
for (const entry of entries) {
const relativePath = entry.name
// Skip .git directory
if (relativePath === ".git") continue
// Check if this path matches both patterns
// For .worktreeinclude, we want items that are "ignored" (matched)
// For .gitignore, we want items that are "ignored" (matched)
const matchesWorktreeInclude = includeMatcher.ignores(relativePath)
const matchesGitignore = gitignoreMatcher.ignores(relativePath)
if (matchesWorktreeInclude && matchesGitignore) {
matchingItems.push(relativePath)
}
}
} catch {
return []
}
return matchingItems
}
}
// Export singleton instance for convenience
export const worktreeIncludeService = new WorktreeIncludeService()

View file

@ -0,0 +1,315 @@
/**
* WorktreeService
*
* Platform-agnostic service for git worktree operations.
* Uses simple-git and native CLI commands - no VSCode dependencies.
*/
import { exec, execFile } from "child_process"
import * as path from "path"
import { promisify } from "util"
import type { BranchInfo, CreateWorktreeOptions, Worktree, WorktreeResult } from "./types.js"
const execAsync = promisify(exec)
const execFileAsync = promisify(execFile)
/**
* Service for managing git worktrees.
* All methods are platform-agnostic and don't depend on VSCode APIs.
*/
export class WorktreeService {
/**
* Check if git is installed on the system
*/
async checkGitInstalled(): Promise<boolean> {
try {
await execAsync("git --version")
return true
} catch {
return false
}
}
/**
* Check if a directory is a git repository.
*/
async checkGitRepo(cwd: string): Promise<boolean> {
try {
await execAsync("git rev-parse --git-dir", { cwd })
return true
} catch {
return false
}
}
/**
* Get the git repository root path.
*/
async getGitRootPath(cwd: string): Promise<string | null> {
try {
const { stdout } = await execAsync("git rev-parse --show-toplevel", { cwd })
return stdout.trim()
} catch {
return null
}
}
/**
* Get the current worktree path.
*/
async getCurrentWorktreePath(cwd: string): Promise<string | null> {
try {
const { stdout } = await execAsync("git rev-parse --show-toplevel", { cwd })
return stdout.trim()
} catch {
return null
}
}
/**
* Get the current branch name.
*/
async getCurrentBranch(cwd: string): Promise<string | null> {
try {
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", { cwd })
const branch = stdout.trim()
return branch === "HEAD" ? null : branch
} catch {
return null
}
}
/**
* List all worktrees in the repository
*/
async listWorktrees(cwd: string): Promise<Worktree[]> {
try {
const { stdout } = await execAsync("git worktree list --porcelain", { cwd })
return this.parseWorktreeOutput(stdout, cwd)
} catch {
return []
}
}
/**
* Create a new worktree
*/
async createWorktree(cwd: string, options: CreateWorktreeOptions): Promise<WorktreeResult> {
try {
const { path: worktreePath, branch, baseBranch, createNewBranch } = options
// Build the git worktree add command arguments
const args: string[] = ["worktree", "add"]
if (createNewBranch && branch) {
// Create new branch: git worktree add -b <branch> <path> [<base>]
args.push("-b", branch, worktreePath)
if (baseBranch) {
args.push(baseBranch)
}
} else if (branch) {
// Checkout existing branch: git worktree add <path> <branch>
args.push(worktreePath, branch)
} else {
// Detached HEAD at current commit
args.push("--detach", worktreePath)
}
await execFileAsync("git", args, { cwd })
// Get the created worktree info
const worktrees = await this.listWorktrees(cwd)
const createdWorktree = worktrees.find(
(wt) => this.normalizePath(wt.path) === this.normalizePath(worktreePath),
)
return {
success: true,
message: `Worktree created at ${worktreePath}`,
worktree: createdWorktree,
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
success: false,
message: `Failed to create worktree: ${errorMessage}`,
}
}
}
/**
* Delete a worktree
*/
async deleteWorktree(cwd: string, worktreePath: string, force = false): Promise<WorktreeResult> {
try {
// Get worktree info BEFORE deletion to capture the branch name
const worktrees = await this.listWorktrees(cwd)
const worktreeToDelete = worktrees.find(
(wt) => this.normalizePath(wt.path) === this.normalizePath(worktreePath),
)
const args = ["worktree", "remove"]
if (force) {
args.push("--force")
}
args.push(worktreePath)
await execFileAsync("git", args, { cwd })
// Also try to delete the branch if it exists
if (worktreeToDelete?.branch) {
try {
await execFileAsync("git", ["branch", "-d", worktreeToDelete.branch], { cwd })
} catch {
// Branch deletion is best-effort
}
}
return {
success: true,
message: `Worktree removed from ${worktreePath}`,
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
success: false,
message: `Failed to delete worktree: ${errorMessage}`,
}
}
}
/**
* Get available branches
* @param cwd - Current working directory
* @param includeWorktreeBranches - If true, include branches already checked out in worktrees (useful for base branch selection)
*/
async getAvailableBranches(cwd: string, includeWorktreeBranches = false): Promise<BranchInfo> {
try {
// Run all git commands in parallel for better performance
const [worktrees, localResult, remoteResult, currentBranch] = await Promise.all([
this.listWorktrees(cwd),
execAsync('git branch --format="%(refname:short)"', { cwd }),
execAsync('git branch -r --format="%(refname:short)"', { cwd }),
this.getCurrentBranch(cwd),
])
const branchesInWorktrees = new Set(worktrees.map((wt) => wt.branch).filter(Boolean))
// Filter local branches
const localBranches = localResult.stdout
.trim()
.split("\n")
.filter((b) => b && (includeWorktreeBranches || !branchesInWorktrees.has(b)))
// Filter remote branches
const remoteBranches = remoteResult.stdout
.trim()
.split("\n")
.filter(
(b) =>
b &&
!b.includes("HEAD") &&
(includeWorktreeBranches || !branchesInWorktrees.has(b.replace(/^origin\//, ""))),
)
return {
localBranches,
remoteBranches,
currentBranch: currentBranch || "",
}
} catch {
return {
localBranches: [],
remoteBranches: [],
currentBranch: "",
}
}
}
/**
* Checkout a branch in the current worktree
*/
async checkoutBranch(cwd: string, branch: string): Promise<WorktreeResult> {
try {
await execFileAsync("git", ["checkout", branch], { cwd })
return {
success: true,
message: `Checked out branch ${branch}`,
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
success: false,
message: `Failed to checkout branch: ${errorMessage}`,
}
}
}
/**
* Parse git worktree list --porcelain output
*/
private parseWorktreeOutput(output: string, currentCwd: string): Worktree[] {
const worktrees: Worktree[] = []
const entries = output.trim().split("\n\n")
for (const entry of entries) {
if (!entry.trim()) continue
const lines = entry.trim().split("\n")
const worktree: Partial<Worktree> = {
path: "",
branch: "",
commitHash: "",
isCurrent: false,
isBare: false,
isDetached: false,
isLocked: false,
}
for (const line of lines) {
if (line.startsWith("worktree ")) {
worktree.path = line.substring(9).trim()
} else if (line.startsWith("HEAD ")) {
worktree.commitHash = line.substring(5).trim()
} else if (line.startsWith("branch ")) {
// branch refs/heads/main -> main
const branchRef = line.substring(7).trim()
worktree.branch = branchRef.replace(/^refs\/heads\//, "")
} else if (line === "bare") {
worktree.isBare = true
} else if (line === "detached") {
worktree.isDetached = true
} else if (line === "locked") {
worktree.isLocked = true
} else if (line.startsWith("locked ")) {
worktree.isLocked = true
worktree.lockReason = line.substring(7).trim()
}
}
if (worktree.path) {
worktree.isCurrent = this.normalizePath(worktree.path) === this.normalizePath(currentCwd)
worktrees.push(worktree as Worktree)
}
}
return worktrees
}
/**
* Normalize a path for comparison (handle trailing slashes, etc.)
*/
private normalizePath(p: string): string {
// normalize resolves ./.. segments, removes duplicate slashes, and standardizes path separators
let normalized = path.normalize(p)
// however it doesn't remove trailing slashes
// remove trailing slash, except for root paths (handles both / and \)
if (normalized.length > 1 && (normalized.endsWith("/") || normalized.endsWith("\\"))) {
normalized = normalized.slice(0, -1)
}
return normalized
}
}
// Export singleton instance for convenience
export const worktreeService = new WorktreeService()

View file

@ -111,8 +111,8 @@ export class TelemetryService {
this.captureEvent(TelemetryEventName.MODE_SWITCH, { taskId, newMode })
}
public captureToolUsage(taskId: string, tool: string, toolProtocol: string): void {
this.captureEvent(TelemetryEventName.TOOL_USED, { taskId, tool, toolProtocol })
public captureToolUsage(taskId: string, tool: string): void {
this.captureEvent(TelemetryEventName.TOOL_USED, { taskId, tool })
}
public captureCheckpointCreated(taskId: string): void {
@ -127,17 +127,11 @@ export class TelemetryService {
this.captureEvent(TelemetryEventName.CHECKPOINT_RESTORED, { taskId })
}
public captureContextCondensed(
taskId: string,
isAutomaticTrigger: boolean,
usedCustomPrompt?: boolean,
usedCustomApiHandler?: boolean,
): void {
public captureContextCondensed(taskId: string, isAutomaticTrigger: boolean, usedCustomPrompt?: boolean): void {
this.captureEvent(TelemetryEventName.CONTEXT_CONDENSED, {
taskId,
isAutomaticTrigger,
...(usedCustomPrompt !== undefined && { usedCustomPrompt }),
...(usedCustomApiHandler !== undefined && { usedCustomApiHandler }),
})
}

View file

@ -23,7 +23,7 @@
"clean": "rimraf dist .turbo"
},
"dependencies": {
"zod": "^3.25.61"
"zod": "3.25.76"
},
"devDependencies": {
"@roo-code/config-eslint": "workspace:^",

View file

@ -90,11 +90,6 @@ describe("getApiProtocol", () => {
expect(getApiProtocol("anthropic", "gpt-4")).toBe("anthropic")
})
it("should return 'anthropic' for claude-code provider", () => {
expect(getApiProtocol("claude-code")).toBe("anthropic")
expect(getApiProtocol("claude-code", "some-model")).toBe("anthropic")
})
it("should return 'anthropic' for bedrock provider", () => {
expect(getApiProtocol("bedrock")).toBe("anthropic")
expect(getApiProtocol("bedrock", "gpt-4")).toBe("anthropic")

View file

@ -94,14 +94,11 @@ export type OrganizationAllowList = z.infer<typeof organizationAllowListSchema>
export const organizationDefaultSettingsSchema = globalSettingsSchema
.pick({
enableCheckpoints: true,
fuzzyMatchThreshold: true,
maxOpenTabsContext: true,
maxReadFileLine: true,
maxWorkspaceFiles: true,
showRooIgnoredFiles: true,
terminalCommandDelay: true,
terminalCompressProgressBar: true,
terminalOutputLineLimit: true,
terminalShellIntegrationDisabled: true,
terminalShellIntegrationTimeout: true,
terminalZshClearEolMark: true,
@ -113,7 +110,6 @@ export const organizationDefaultSettingsSchema = globalSettingsSchema
maxReadFileLine: z.number().int().gte(-1).optional(),
maxWorkspaceFiles: z.number().int().nonnegative().optional(),
terminalCommandDelay: z.number().int().nonnegative().optional(),
terminalOutputLineLimit: z.number().int().nonnegative().optional(),
terminalShellIntegrationTimeout: z.number().int().nonnegative().optional(),
}),
)

View file

@ -6,15 +6,7 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js"
* ExperimentId
*/
export const experimentIds = [
"powerSteering",
"multiFileApplyDiff",
"preventFocusDisruption",
"imageGeneration",
"runSlashCommand",
"multipleNativeToolCalls",
"customTools",
] as const
export const experimentIds = ["preventFocusDisruption", "imageGeneration", "runSlashCommand", "customTools"] as const
export const experimentIdsSchema = z.enum(experimentIds)
@ -25,12 +17,9 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
*/
export const experimentsSchema = z.object({
powerSteering: z.boolean().optional(),
multiFileApplyDiff: z.boolean().optional(),
preventFocusDisruption: z.boolean().optional(),
imageGeneration: z.boolean().optional(),
runSlashCommand: z.boolean().optional(),
multipleNativeToolCalls: z.boolean().optional(),
customTools: z.boolean().optional(),
})

View file

@ -23,11 +23,40 @@ import { languagesSchema } from "./vscode.js"
export const DEFAULT_WRITE_DELAY_MS = 1000
/**
* Default terminal output character limit constant.
* This provides a reasonable default that aligns with typical terminal usage
* while preventing context window explosions from extremely long lines.
* Terminal output preview size options for persisted command output.
*
* Controls how much command output is kept in memory as a "preview" before
* the LLM decides to retrieve more via `read_command_output`. Larger previews
* mean more immediate context but consume more of the context window.
*
* - `small`: 5KB preview - Best for long-running commands with verbose output
* - `medium`: 10KB preview - Balanced default for most use cases
* - `large`: 20KB preview - Best when commands produce critical info early
*
* @see OutputInterceptor - Uses this setting to determine when to spill to disk
* @see PersistedCommandOutput - Contains the resulting preview and artifact reference
*/
export const DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT = 50_000
export type TerminalOutputPreviewSize = "small" | "medium" | "large"
/**
* Byte limits for each terminal output preview size.
*
* Maps preview size names to their corresponding byte thresholds.
* When command output exceeds these thresholds, the excess is persisted
* to disk and made available via the `read_command_output` tool.
*/
export const TERMINAL_PREVIEW_BYTES: Record<TerminalOutputPreviewSize, number> = {
small: 5 * 1024, // 5KB
medium: 10 * 1024, // 10KB
large: 20 * 1024, // 20KB
}
/**
* Default terminal output preview size.
* The "medium" (10KB) setting provides a good balance between immediate
* visibility and context window conservation for most use cases.
*/
export const DEFAULT_TERMINAL_OUTPUT_PREVIEW_SIZE: TerminalOutputPreviewSize = "medium"
/**
* Minimum checkpoint timeout in seconds.
@ -63,7 +92,6 @@ export const globalSettingsSchema = z.object({
openRouterImageApiKey: z.string().optional(),
openRouterImageGenerationSelectedModel: z.string().optional(),
condensingApiConfigId: z.string().optional(),
customCondensingPrompt: z.string().optional(),
autoApprovalEnabled: z.boolean().optional(),
@ -148,8 +176,7 @@ export const globalSettingsSchema = z.object({
maxImageFileSize: z.number().optional(),
maxTotalImageSize: z.number().optional(),
terminalOutputLineLimit: z.number().optional(),
terminalOutputCharacterLimit: z.number().optional(),
terminalOutputPreviewSize: z.enum(["small", "medium", "large"]).optional(),
terminalShellIntegrationTimeout: z.number().optional(),
terminalShellIntegrationDisabled: z.boolean().optional(),
terminalCommandDelay: z.number().optional(),
@ -158,13 +185,10 @@ export const globalSettingsSchema = z.object({
terminalZshOhMy: z.boolean().optional(),
terminalZshP10k: z.boolean().optional(),
terminalZdotdir: z.boolean().optional(),
terminalCompressProgressBar: z.boolean().optional(),
diagnosticsEnabled: z.boolean().optional(),
rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
experiments: experimentsSchema.optional(),
codebaseIndexModels: codebaseIndexModelsSchema.optional(),
@ -175,7 +199,6 @@ export const globalSettingsSchema = z.object({
telemetrySetting: telemetrySettingsSchema.optional(),
mcpEnabled: z.boolean().optional(),
enableMcpServerCreation: z.boolean().optional(),
mode: z.string().optional(),
modeApiConfigs: z.record(z.string(), z.string()).optional(),
@ -197,6 +220,20 @@ export const globalSettingsSchema = z.object({
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
lastModeImportPath: z.string().optional(),
lastSettingsExportPath: z.string().optional(),
lastTaskExportPath: z.string().optional(),
lastImageSavePath: z.string().optional(),
/**
* Path to worktree to auto-open after switching workspaces.
* Used by the worktree feature to open the Roo Code sidebar in a new window.
*/
worktreeAutoOpenPath: z.string().optional(),
/**
* Whether to show the worktree selector in the home screen.
* @default true
*/
showWorktreesInHomeScreen: z.boolean().optional(),
})
export type GlobalSettings = z.infer<typeof globalSettingsSchema>
@ -327,8 +364,6 @@ export const EVALS_SETTINGS: RooCodeSettings = {
soundEnabled: false,
soundVolume: 0.5,
terminalOutputLineLimit: 500,
terminalOutputCharacterLimit: DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
terminalShellIntegrationTimeout: 30000,
terminalCommandDelay: 0,
terminalPowershellCounter: false,
@ -336,14 +371,10 @@ export const EVALS_SETTINGS: RooCodeSettings = {
terminalZshClearEolMark: true,
terminalZshP10k: false,
terminalZdotdir: true,
terminalCompressProgressBar: true,
terminalShellIntegrationDisabled: true,
diagnosticsEnabled: true,
diffEnabled: true,
fuzzyMatchThreshold: 1,
enableCheckpoints: false,
rateLimitSeconds: 0,

View file

@ -19,16 +19,6 @@ export const historyItemSchema = z.object({
size: z.number().optional(),
workspace: z.string().optional(),
mode: z.string().optional(),
/**
* The tool protocol used by this task. Once a task uses tools with a specific
* protocol (XML or Native), it is permanently locked to that protocol.
*
* - "xml": Tool calls are parsed from XML text (no tool IDs)
* - "native": Tool calls come as tool_call chunks with IDs
*
* This ensures task resumption works correctly even when NTC settings change.
*/
toolProtocol: z.enum(["xml", "native"]).optional(),
apiConfigName: z.string().optional(), // Provider profile name for sticky profile feature
status: z.enum(["active", "completed", "delegated"]).optional(),
delegatedToId: z.string().optional(), // Last child this parent delegated to

View file

@ -28,5 +28,6 @@ export * from "./tool-params.js"
export * from "./type-fu.js"
export * from "./vscode-extension-host.js"
export * from "./vscode.js"
export * from "./worktree.js"
export * from "./providers/index.js"

View file

@ -1,5 +1,11 @@
import { z } from "zod"
/**
* Maximum number of MCP tools that can be enabled before showing a warning.
* LLMs tend to perform poorly when given too many tools to choose from.
*/
export const MAX_MCP_TOOLS_THRESHOLD = 60
/**
* McpServerUse
*/
@ -128,3 +134,53 @@ export type McpErrorEntry = {
timestamp: number
level: "error" | "warn" | "info"
}
/**
* Result of counting enabled MCP tools across servers.
*/
export interface EnabledMcpToolsCount {
/** Number of enabled and connected MCP servers */
enabledServerCount: number
/** Total number of enabled tools across all enabled servers */
enabledToolCount: number
}
/**
* Count the number of enabled MCP tools across all enabled and connected servers.
* This is a pure function that can be used in both backend and frontend contexts.
*
* @param servers - Array of MCP server objects
* @returns Object with enabledToolCount and enabledServerCount
*
* @example
* const { enabledToolCount, enabledServerCount } = countEnabledMcpTools(mcpServers)
* if (enabledToolCount > MAX_MCP_TOOLS_THRESHOLD) {
* // Show warning
* }
*/
export function countEnabledMcpTools(servers: McpServer[]): EnabledMcpToolsCount {
let serverCount = 0
let toolCount = 0
for (const server of servers) {
// Skip disabled servers
if (server.disabled) continue
// Skip servers that are not connected
if (server.status !== "connected") continue
serverCount++
// Count enabled tools on this server
if (server.tools) {
for (const tool of server.tools) {
// Tool is enabled if enabledForPrompt is undefined (default) or true
if (tool.enabledForPrompt !== false) {
toolCount++
}
}
}
}
return { enabledToolCount: toolCount, enabledServerCount: serverCount }
}

View file

@ -149,6 +149,7 @@ export function isNonBlockingAsk(ask: ClineAsk): ask is NonBlockingAsk {
* - `condense_context`: Context condensation/summarization has started
* - `condense_context_error`: Error occurred during context condensation
* - `codebase_search_result`: Results from searching the codebase
* - `too_many_tools_warning`: Warning that too many MCP tools are enabled, which may confuse the LLM
*/
export const clineSays = [
"error",
@ -180,6 +181,8 @@ export const clineSays = [
"sliding_window_truncation",
"codebase_search_result",
"user_edit_todos",
"too_many_tools_warning",
"tool",
] as const
export const clineSaySchema = z.enum(clineSays)

View file

@ -110,10 +110,6 @@ export const modelInfoSchema = z.object({
isStealthModel: z.boolean().optional(),
// Flag to indicate if the model is free (no cost)
isFree: z.boolean().optional(),
// Flag to indicate if the model supports native tool calling (OpenAI-style function calling)
supportsNativeTools: z.boolean().optional(),
// Default tool protocol preferred by this model (if not specified, falls back to capability/provider defaults)
defaultToolProtocol: z.enum(["xml", "native"]).optional(),
// Exclude specific native tools from being available (only applies to native protocol)
// These tools will be removed from the set of tools available to the model
excludedTools: z.array(z.string()).optional(),

View file

@ -7,7 +7,6 @@ import {
basetenModels,
bedrockModels,
cerebrasModels,
claudeCodeModels,
deepSeekModels,
doubaoModels,
featherlessModels,
@ -123,7 +122,6 @@ export const providerNames = [
"bedrock",
"baseten",
"cerebras",
"claude-code",
"doubao",
"deepseek",
"featherless",
@ -170,9 +168,7 @@ export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
const baseProviderSettingsSchema = z.object({
includeMaxTokens: z.boolean().optional(),
diffEnabled: z.boolean().optional(),
todoListEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
modelTemperature: z.number().nullish(),
rateLimitSeconds: z.number().optional(),
consecutiveMistakeLimit: z.number().min(0).optional(),
@ -185,9 +181,6 @@ const baseProviderSettingsSchema = z.object({
// Model verbosity.
verbosity: verbosityLevelsSchema.optional(),
// Tool protocol override for this profile.
toolProtocol: z.enum(["xml", "native"]).optional(),
})
// Several of the providers share common model config properties.
@ -202,8 +195,6 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
})
const claudeCodeSchema = apiModelIdProviderModelSchema.extend({})
const openRouterSchema = baseProviderSettingsSchema.extend({
openRouterApiKey: z.string().optional(),
openRouterModelId: z.string().optional(),
@ -437,7 +428,6 @@ const defaultSchema = z.object({
export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProvider", [
anthropicSchema.merge(z.object({ apiProvider: z.literal("anthropic") })),
claudeCodeSchema.merge(z.object({ apiProvider: z.literal("claude-code") })),
openRouterSchema.merge(z.object({ apiProvider: z.literal("openrouter") })),
bedrockSchema.merge(z.object({ apiProvider: z.literal("bedrock") })),
vertexSchema.merge(z.object({ apiProvider: z.literal("vertex") })),
@ -479,7 +469,6 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
export const providerSettingsSchema = z.object({
apiProvider: providerNamesSchema.optional(),
...anthropicSchema.shape,
...claudeCodeSchema.shape,
...openRouterSchema.shape,
...bedrockSchema.shape,
...vertexSchema.shape,
@ -568,7 +557,6 @@ export const isTypicalProvider = (key: unknown): key is TypicalProvider =>
export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
anthropic: "apiModelId",
"claude-code": "apiModelId",
openrouter: "openRouterModelId",
bedrock: "apiModelId",
vertex: "apiModelId",
@ -608,7 +596,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
*/
// Providers that use Anthropic-style API protocol.
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock", "minimax"]
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "bedrock", "minimax"]
export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => {
if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) {
@ -655,7 +643,6 @@ export const MODELS_BY_PROVIDER: Record<
label: "Cerebras",
models: Object.keys(cerebrasModels),
},
"claude-code": { id: "claude-code", label: "Claude Code", models: Object.keys(claudeCodeModels) },
deepseek: {
id: "deepseek",
label: "DeepSeek",

View file

@ -1,46 +0,0 @@
import { normalizeClaudeCodeModelId } from "../claude-code.js"
describe("normalizeClaudeCodeModelId", () => {
test("should return valid model IDs unchanged", () => {
expect(normalizeClaudeCodeModelId("claude-sonnet-4-5")).toBe("claude-sonnet-4-5")
expect(normalizeClaudeCodeModelId("claude-opus-4-5")).toBe("claude-opus-4-5")
expect(normalizeClaudeCodeModelId("claude-haiku-4-5")).toBe("claude-haiku-4-5")
})
test("should normalize sonnet models with date suffix to claude-sonnet-4-5", () => {
// Sonnet 4.5 with date
expect(normalizeClaudeCodeModelId("claude-sonnet-4-5-20250929")).toBe("claude-sonnet-4-5")
// Sonnet 4 (legacy)
expect(normalizeClaudeCodeModelId("claude-sonnet-4-20250514")).toBe("claude-sonnet-4-5")
// Claude 3.7 Sonnet
expect(normalizeClaudeCodeModelId("claude-3-7-sonnet-20250219")).toBe("claude-sonnet-4-5")
// Claude 3.5 Sonnet
expect(normalizeClaudeCodeModelId("claude-3-5-sonnet-20241022")).toBe("claude-sonnet-4-5")
})
test("should normalize opus models with date suffix to claude-opus-4-5", () => {
// Opus 4.5 with date
expect(normalizeClaudeCodeModelId("claude-opus-4-5-20251101")).toBe("claude-opus-4-5")
// Opus 4.1 (legacy)
expect(normalizeClaudeCodeModelId("claude-opus-4-1-20250805")).toBe("claude-opus-4-5")
// Opus 4 (legacy)
expect(normalizeClaudeCodeModelId("claude-opus-4-20250514")).toBe("claude-opus-4-5")
})
test("should normalize haiku models with date suffix to claude-haiku-4-5", () => {
// Haiku 4.5 with date
expect(normalizeClaudeCodeModelId("claude-haiku-4-5-20251001")).toBe("claude-haiku-4-5")
// Claude 3.5 Haiku
expect(normalizeClaudeCodeModelId("claude-3-5-haiku-20241022")).toBe("claude-haiku-4-5")
})
test("should handle case-insensitive model family matching", () => {
expect(normalizeClaudeCodeModelId("Claude-Sonnet-4-5-20250929")).toBe("claude-sonnet-4-5")
expect(normalizeClaudeCodeModelId("CLAUDE-OPUS-4-5-20251101")).toBe("claude-opus-4-5")
})
test("should fallback to default for unrecognized models", () => {
expect(normalizeClaudeCodeModelId("unknown-model")).toBe("claude-sonnet-4-5")
expect(normalizeClaudeCodeModelId("gpt-4")).toBe("claude-sonnet-4-5")
})
})

View file

@ -11,8 +11,6 @@ export const anthropicModels = {
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens (≤200K context)
outputPrice: 15.0, // $15 per million output tokens (≤200K context)
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -34,8 +32,6 @@ export const anthropicModels = {
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens (≤200K context)
outputPrice: 15.0, // $15 per million output tokens (≤200K context)
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -57,8 +53,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 5.0, // $5 per million input tokens
outputPrice: 25.0, // $25 per million output tokens
cacheWritesPrice: 6.25, // $6.25 per million tokens
@ -70,8 +64,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0, // $15 per million input tokens
outputPrice: 75.0, // $75 per million output tokens
cacheWritesPrice: 18.75, // $18.75 per million tokens
@ -83,8 +75,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0, // $15 per million input tokens
outputPrice: 75.0, // $75 per million output tokens
cacheWritesPrice: 18.75, // $18.75 per million tokens
@ -96,8 +86,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens
outputPrice: 15.0, // $15 per million output tokens
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -110,8 +98,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens
outputPrice: 15.0, // $15 per million output tokens
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -122,8 +108,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens
outputPrice: 15.0, // $15 per million output tokens
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -134,8 +118,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.0,
outputPrice: 5.0,
cacheWritesPrice: 1.25,
@ -146,8 +128,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
@ -158,8 +138,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.25,
outputPrice: 1.25,
cacheWritesPrice: 0.3,
@ -170,8 +148,6 @@ export const anthropicModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.0,
outputPrice: 5.0,
cacheWritesPrice: 1.25,

View file

@ -9,7 +9,6 @@ export const basetenModels = {
contextWindow: 262_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.6,
outputPrice: 2.5,
cacheWritesPrice: 0,
@ -21,7 +20,6 @@ export const basetenModels = {
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.6,
outputPrice: 2.2,
cacheWritesPrice: 0,
@ -33,7 +31,6 @@ export const basetenModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 2.55,
outputPrice: 5.95,
cacheWritesPrice: 0,
@ -45,7 +42,6 @@ export const basetenModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 2.55,
outputPrice: 5.95,
cacheWritesPrice: 0,
@ -57,7 +53,6 @@ export const basetenModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.77,
outputPrice: 0.77,
cacheWritesPrice: 0,
@ -69,7 +64,6 @@ export const basetenModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.5,
outputPrice: 1.5,
cacheWritesPrice: 0,
@ -82,7 +76,6 @@ export const basetenModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.3,
outputPrice: 0.45,
cacheWritesPrice: 0,
@ -95,7 +88,6 @@ export const basetenModels = {
contextWindow: 128_072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.1,
outputPrice: 0.5,
cacheWritesPrice: 0,
@ -107,7 +99,6 @@ export const basetenModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.22,
outputPrice: 0.8,
cacheWritesPrice: 0,
@ -119,7 +110,6 @@ export const basetenModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.38,
outputPrice: 1.53,
cacheWritesPrice: 0,
@ -131,7 +121,6 @@ export const basetenModels = {
contextWindow: 262_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.6,
outputPrice: 2.5,
cacheWritesPrice: 0,

View file

@ -19,8 +19,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -34,7 +32,6 @@ export const bedrockModels = {
contextWindow: 300_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 0.8,
outputPrice: 3.2,
cacheWritesPrice: 0.8, // per million tokens
@ -48,7 +45,6 @@ export const bedrockModels = {
contextWindow: 300_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 1.0,
outputPrice: 4.0,
cacheWritesPrice: 1.0, // per million tokens
@ -60,7 +56,6 @@ export const bedrockModels = {
contextWindow: 300_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 0.06,
outputPrice: 0.24,
cacheWritesPrice: 0.06, // per million tokens
@ -74,7 +69,6 @@ export const bedrockModels = {
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 0.33,
outputPrice: 2.75,
cacheWritesPrice: 0,
@ -89,7 +83,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 0.035,
outputPrice: 0.14,
cacheWritesPrice: 0.035, // per million tokens
@ -104,8 +97,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -120,8 +111,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
@ -136,8 +125,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 5.0,
outputPrice: 25.0,
cacheWritesPrice: 6.25,
@ -152,8 +139,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
@ -168,8 +153,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -183,8 +166,6 @@ export const bedrockModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -198,8 +179,6 @@ export const bedrockModels = {
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.8,
outputPrice: 4.0,
cacheWritesPrice: 1.0,
@ -214,8 +193,6 @@ export const bedrockModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.0,
outputPrice: 5.0,
cacheWritesPrice: 1.25, // 5m cache writes
@ -229,8 +206,6 @@ export const bedrockModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
},
@ -239,8 +214,6 @@ export const bedrockModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
},
@ -249,8 +222,6 @@ export const bedrockModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
},
@ -259,8 +230,6 @@ export const bedrockModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.25,
outputPrice: 1.25,
},
@ -269,7 +238,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 1.35,
outputPrice: 5.4,
},
@ -278,7 +246,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.5,
outputPrice: 1.5,
description: "GPT-OSS 20B - Optimized for low latency and local/specialized use cases",
@ -288,7 +255,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 2.0,
outputPrice: 6.0,
description: "GPT-OSS 120B - Production-ready, general-purpose, high-reasoning model",
@ -298,7 +264,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.72,
outputPrice: 0.72,
description: "Llama 3.3 Instruct (70B)",
@ -308,7 +273,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.72,
outputPrice: 0.72,
description: "Llama 3.2 Instruct (90B)",
@ -318,7 +282,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.16,
outputPrice: 0.16,
description: "Llama 3.2 Instruct (11B)",
@ -328,7 +291,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.15,
outputPrice: 0.15,
description: "Llama 3.2 Instruct (3B)",
@ -338,7 +300,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.1,
outputPrice: 0.1,
description: "Llama 3.2 Instruct (1B)",
@ -348,7 +309,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 2.4,
outputPrice: 2.4,
description: "Llama 3.1 Instruct (405B)",
@ -358,7 +318,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.72,
outputPrice: 0.72,
description: "Llama 3.1 Instruct (70B)",
@ -368,7 +327,6 @@ export const bedrockModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.9,
outputPrice: 0.9,
description: "Llama 3.1 Instruct (70B) (w/ latency optimized inference)",
@ -378,7 +336,6 @@ export const bedrockModels = {
contextWindow: 8_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.22,
outputPrice: 0.22,
description: "Llama 3.1 Instruct (8B)",
@ -388,7 +345,6 @@ export const bedrockModels = {
contextWindow: 8_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 2.65,
outputPrice: 3.5,
},
@ -397,7 +353,6 @@ export const bedrockModels = {
contextWindow: 4_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.3,
outputPrice: 0.6,
},
@ -406,7 +361,6 @@ export const bedrockModels = {
contextWindow: 8_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.15,
outputPrice: 0.2,
description: "Amazon Titan Text Lite",
@ -416,7 +370,6 @@ export const bedrockModels = {
contextWindow: 8_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.2,
outputPrice: 0.6,
description: "Amazon Titan Text Express",
@ -426,8 +379,6 @@ export const bedrockModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
preserveReasoning: true,
inputPrice: 0.6,
outputPrice: 2.5,
@ -438,8 +389,6 @@ export const bedrockModels = {
contextWindow: 196_608,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
preserveReasoning: true,
inputPrice: 0.3,
outputPrice: 1.2,
@ -450,8 +399,6 @@ export const bedrockModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.15,
outputPrice: 1.2,
description: "Qwen3 Next 80B (MoE model with 3B active parameters)",
@ -461,8 +408,6 @@ export const bedrockModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.45,
outputPrice: 1.8,
description: "Qwen3 Coder 480B (MoE model with 35B active parameters)",

View file

@ -6,24 +6,13 @@ export type CerebrasModelId = keyof typeof cerebrasModels
export const cerebrasDefaultModelId: CerebrasModelId = "gpt-oss-120b"
export const cerebrasModels = {
"zai-glm-4.6": {
maxTokens: 16384, // Conservative default to avoid premature rate limiting (Cerebras reserves quota upfront)
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Fast general-purpose model on Cerebras (up to 1,000 tokens/s). To be deprecated soon.",
},
"zai-glm-4.7": {
maxTokens: 16384, // Conservative default to avoid premature rate limiting (Cerebras reserves quota upfront)
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
supportsTemperature: true,
defaultTemperature: 1.0,
inputPrice: 0,
outputPrice: 0,
description:
@ -34,8 +23,6 @@ export const cerebrasModels = {
contextWindow: 64000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Intelligent model with ~1400 tokens/s",
@ -45,8 +32,6 @@ export const cerebrasModels = {
contextWindow: 64000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Powerful model with ~2600 tokens/s",
@ -56,8 +41,6 @@ export const cerebrasModels = {
contextWindow: 64000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "SOTA coding performance with ~2500 tokens/s",
@ -67,8 +50,6 @@ export const cerebrasModels = {
contextWindow: 64000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:

View file

@ -51,8 +51,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek R1 0528 model.",
@ -62,8 +60,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek R1 model.",
@ -73,8 +69,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek V3 model.",
@ -84,8 +78,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek V3.1 model.",
@ -95,8 +87,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.23,
outputPrice: 0.9,
description:
@ -107,8 +97,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.0,
outputPrice: 3.0,
description:
@ -119,8 +107,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.25,
outputPrice: 0.35,
description:
@ -131,8 +117,6 @@ export const chutesModels = {
contextWindow: 131072, // From Groq
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Unsloth Llama 3.3 70B Instruct model.",
@ -142,8 +126,6 @@ export const chutesModels = {
contextWindow: 512000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "ChutesAI Llama 4 Scout 17B Instruct model, 512K context.",
@ -153,8 +135,6 @@ export const chutesModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Unsloth Mistral Nemo Instruct model.",
@ -164,8 +144,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Unsloth Gemma 3 12B IT model.",
@ -175,8 +153,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Nous DeepHermes 3 Llama 3 8B Preview model.",
@ -186,8 +162,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Unsloth Gemma 3 4B IT model.",
@ -197,8 +171,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Nvidia Llama 3.3 Nemotron Super 49B model.",
@ -208,8 +180,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Nvidia Llama 3.1 Nemotron Ultra 253B model.",
@ -219,8 +189,6 @@ export const chutesModels = {
contextWindow: 256000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "ChutesAI Llama 4 Maverick 17B Instruct FP8 model.",
@ -230,8 +198,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek V3 Base model.",
@ -241,8 +207,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek R1 Zero model.",
@ -252,8 +216,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek V3 (0324) model.",
@ -263,8 +225,6 @@ export const chutesModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 235B A22B Instruct 2507 model with 262K context window.",
@ -274,8 +234,6 @@ export const chutesModels = {
contextWindow: 40960,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 235B A22B model.",
@ -285,8 +243,6 @@ export const chutesModels = {
contextWindow: 40960,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 32B model.",
@ -296,8 +252,6 @@ export const chutesModels = {
contextWindow: 40960,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 30B A3B model.",
@ -307,8 +261,6 @@ export const chutesModels = {
contextWindow: 40960,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 14B model.",
@ -318,8 +270,6 @@ export const chutesModels = {
contextWindow: 40960,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 8B model.",
@ -329,8 +279,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Microsoft MAI-DS-R1 FP8 model.",
@ -340,8 +288,6 @@ export const chutesModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "TNGTech DeepSeek R1T Chimera model.",
@ -351,8 +297,6 @@ export const chutesModels = {
contextWindow: 151329,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:
@ -363,8 +307,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:
@ -375,8 +317,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1,
outputPrice: 3,
description: "GLM-4.5-turbo model with 128K token context window, optimized for fast inference.",
@ -386,8 +326,6 @@ export const chutesModels = {
contextWindow: 202752,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:
@ -398,8 +336,6 @@ export const chutesModels = {
contextWindow: 202752,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.15,
outputPrice: 3.25,
description: "GLM-4.6-turbo model with 200K-token context window, optimized for fast inference.",
@ -409,8 +345,6 @@ export const chutesModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:
@ -421,8 +355,6 @@ export const chutesModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 Coder 480B A35B Instruct FP8 model, optimized for coding tasks.",
@ -432,8 +364,6 @@ export const chutesModels = {
contextWindow: 75000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1481,
outputPrice: 0.5926,
description: "Moonshot AI Kimi K2 Instruct model with 75k context window.",
@ -443,8 +373,6 @@ export const chutesModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1999,
outputPrice: 0.8001,
description: "Moonshot AI Kimi K2 Instruct 0905 model with 256k context window.",
@ -454,8 +382,6 @@ export const chutesModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.077968332,
outputPrice: 0.31202496,
description: "Qwen3 235B A22B Thinking 2507 model with 262K context window.",
@ -465,8 +391,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:
@ -477,8 +401,6 @@ export const chutesModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
description:
@ -489,8 +411,6 @@ export const chutesModels = {
contextWindow: 262144,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.16,
outputPrice: 0.65,
description:

View file

@ -1,160 +0,0 @@
import type { ModelInfo } from "../model.js"
/**
* Rate limit information from Claude Code API
*/
export interface ClaudeCodeRateLimitInfo {
// 5-hour limit info
fiveHour: {
status: string
utilization: number
resetTime: number // Unix timestamp
}
// 7-day (weekly) limit info (Sonnet-specific)
weekly?: {
status: string
utilization: number
resetTime: number // Unix timestamp
}
// 7-day unified limit info
weeklyUnified?: {
status: string
utilization: number
resetTime: number // Unix timestamp
}
// Representative claim type
representativeClaim?: string
// Overage status
overage?: {
status: string
disabledReason?: string
}
// Fallback percentage
fallbackPercentage?: number
// Organization ID
organizationId?: string
// Timestamp when this was fetched
fetchedAt: number
}
// Regex pattern to strip date suffix from model names
const DATE_SUFFIX_PATTERN = /-\d{8}$/
// Models that work with Claude Code OAuth tokens
// See: https://docs.anthropic.com/en/docs/claude-code
// NOTE: Claude Code is subscription-based with no per-token cost - pricing fields are 0
export const claudeCodeModels = {
"claude-haiku-4-5": {
maxTokens: 32768,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsReasoningEffort: ["disable", "low", "medium", "high"],
reasoningEffort: "medium",
description: "Claude Haiku 4.5 - Fast and efficient with thinking",
},
"claude-sonnet-4-5": {
maxTokens: 32768,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsReasoningEffort: ["disable", "low", "medium", "high"],
reasoningEffort: "medium",
description: "Claude Sonnet 4.5 - Balanced performance with thinking",
},
"claude-opus-4-5": {
maxTokens: 32768,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsReasoningEffort: ["disable", "low", "medium", "high"],
reasoningEffort: "medium",
description: "Claude Opus 4.5 - Most capable with thinking",
},
} as const satisfies Record<string, ModelInfo>
// Claude Code - Only models that work with Claude Code OAuth tokens
export type ClaudeCodeModelId = keyof typeof claudeCodeModels
export const claudeCodeDefaultModelId: ClaudeCodeModelId = "claude-sonnet-4-5"
/**
* Model family patterns for normalization.
* Maps regex patterns to their canonical Claude Code model IDs.
*
* Order matters - more specific patterns should come first.
*/
const MODEL_FAMILY_PATTERNS: Array<{ pattern: RegExp; target: ClaudeCodeModelId }> = [
// Opus models (any version) → claude-opus-4-5
{ pattern: /opus/i, target: "claude-opus-4-5" },
// Haiku models (any version) → claude-haiku-4-5
{ pattern: /haiku/i, target: "claude-haiku-4-5" },
// Sonnet models (any version) → claude-sonnet-4-5
{ pattern: /sonnet/i, target: "claude-sonnet-4-5" },
]
/**
* Normalizes a Claude model ID to a valid Claude Code model ID.
*
* This function handles backward compatibility for legacy model names
* that may include version numbers or date suffixes. It maps:
* - claude-sonnet-4-5-20250929, claude-sonnet-4-20250514, claude-3-7-sonnet-20250219, claude-3-5-sonnet-20241022 claude-sonnet-4-5
* - claude-opus-4-5-20251101, claude-opus-4-1-20250805, claude-opus-4-20250514 claude-opus-4-5
* - claude-haiku-4-5-20251001, claude-3-5-haiku-20241022 claude-haiku-4-5
*
* @param modelId - The model ID to normalize (may be a legacy format)
* @returns A valid ClaudeCodeModelId, or the original ID if already valid
*
* @example
* normalizeClaudeCodeModelId("claude-sonnet-4-5") // returns "claude-sonnet-4-5"
* normalizeClaudeCodeModelId("claude-3-5-sonnet-20241022") // returns "claude-sonnet-4-5"
* normalizeClaudeCodeModelId("claude-opus-4-1-20250805") // returns "claude-opus-4-5"
*/
export function normalizeClaudeCodeModelId(modelId: string): ClaudeCodeModelId {
// If already a valid model ID, return as-is
// Use Object.hasOwn() instead of 'in' operator to avoid matching inherited properties like 'toString'
if (Object.hasOwn(claudeCodeModels, modelId)) {
return modelId as ClaudeCodeModelId
}
// Strip date suffix if present (e.g., -20250514)
const withoutDate = modelId.replace(DATE_SUFFIX_PATTERN, "")
// Check if stripping the date makes it valid
if (Object.hasOwn(claudeCodeModels, withoutDate)) {
return withoutDate as ClaudeCodeModelId
}
// Match by model family
for (const { pattern, target } of MODEL_FAMILY_PATTERNS) {
if (pattern.test(modelId)) {
return target
}
}
// Fallback to default if no match (shouldn't happen with valid Claude models)
return claudeCodeDefaultModelId
}
/**
* Reasoning effort configuration for Claude Code thinking mode.
* Maps reasoning effort level to budget_tokens for the thinking process.
*
* Note: With interleaved thinking (enabled via beta header), budget_tokens
* can exceed max_tokens as the token limit becomes the entire context window.
* The max_tokens is drawn from the model's maxTokens definition.
*
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#interleaved-thinking
*/
export const claudeCodeReasoningConfig = {
low: { budgetTokens: 16_000 },
medium: { budgetTokens: 32_000 },
high: { budgetTokens: 64_000 },
} as const
export type ClaudeCodeReasoningLevel = keyof typeof claudeCodeReasoningConfig

View file

@ -8,7 +8,6 @@ export const deepInfraDefaultModelInfo: ModelInfo = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.3,
outputPrice: 1.2,
description: "Qwen 3 Coder 480B A35B Instruct Turbo model, 256K context.",

View file

@ -14,8 +14,6 @@ export const deepSeekModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
outputPrice: 0.42, // $0.42 per million tokens - Updated Dec 9, 2025
cacheWritesPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
@ -27,8 +25,6 @@ export const deepSeekModels = {
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
preserveReasoning: true,
inputPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
outputPrice: 0.42, // $0.42 per million tokens - Updated Dec 9, 2025

View file

@ -8,8 +8,6 @@ export const doubaoModels = {
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.0001, // $0.0001 per million tokens (cache miss)
outputPrice: 0.0004, // $0.0004 per million tokens
cacheWritesPrice: 0.0001, // $0.0001 per million tokens (cache miss)
@ -21,8 +19,6 @@ export const doubaoModels = {
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.0002, // $0.0002 per million tokens
outputPrice: 0.0008, // $0.0008 per million tokens
cacheWritesPrice: 0.0002, // $0.0002 per million
@ -34,8 +30,6 @@ export const doubaoModels = {
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.00015, // $0.00015 per million tokens
outputPrice: 0.0006, // $0.0006 per million tokens
cacheWritesPrice: 0.00015, // $0.00015 per million

View file

@ -13,7 +13,6 @@ export const featherlessModels = {
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek V3 0324 model.",
@ -23,7 +22,6 @@ export const featherlessModels = {
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek R1 0528 model.",
@ -33,7 +31,6 @@ export const featherlessModels = {
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0,
outputPrice: 0,
description: "Kimi K2 Instruct model.",
@ -43,7 +40,6 @@ export const featherlessModels = {
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0,
outputPrice: 0,
description: "GPT-OSS 120B model.",
@ -53,7 +49,6 @@ export const featherlessModels = {
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 Coder 480B A35B Instruct model.",

View file

@ -5,16 +5,22 @@ export type FireworksModelId =
| "accounts/fireworks/models/kimi-k2-instruct-0905"
| "accounts/fireworks/models/kimi-k2-thinking"
| "accounts/fireworks/models/minimax-m2"
| "accounts/fireworks/models/minimax-m2p1"
| "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
| "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct"
| "accounts/fireworks/models/deepseek-r1-0528"
| "accounts/fireworks/models/deepseek-v3"
| "accounts/fireworks/models/deepseek-v3p1"
| "accounts/fireworks/models/deepseek-v3p2"
| "accounts/fireworks/models/glm-4p5"
| "accounts/fireworks/models/glm-4p5-air"
| "accounts/fireworks/models/glm-4p6"
| "accounts/fireworks/models/glm-4p7"
| "accounts/fireworks/models/gpt-oss-20b"
| "accounts/fireworks/models/gpt-oss-120b"
| "accounts/fireworks/models/llama-v3p3-70b-instruct"
| "accounts/fireworks/models/llama4-maverick-instruct-basic"
| "accounts/fireworks/models/llama4-scout-instruct-basic"
export const fireworksDefaultModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct-0905"
@ -24,8 +30,6 @@ export const fireworksModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 2.5,
cacheReadsPrice: 0.15,
@ -37,8 +41,6 @@ export const fireworksModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 2.5,
description:
@ -49,7 +51,6 @@ export const fireworksModels = {
contextWindow: 256000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
supportsTemperature: true,
preserveReasoning: true,
defaultTemperature: 1.0,
@ -64,8 +65,6 @@ export const fireworksModels = {
contextWindow: 204800,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.3,
outputPrice: 1.2,
description:
@ -76,8 +75,6 @@ export const fireworksModels = {
contextWindow: 256000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.22,
outputPrice: 0.88,
description: "Latest Qwen3 thinking model, competitive against the best closed source models in Jul 2025.",
@ -87,8 +84,6 @@ export const fireworksModels = {
contextWindow: 256000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.45,
outputPrice: 1.8,
description: "Qwen3's most agentic code model to date.",
@ -98,8 +93,6 @@ export const fireworksModels = {
contextWindow: 160000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3,
outputPrice: 8,
description:
@ -110,8 +103,6 @@ export const fireworksModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.9,
outputPrice: 0.9,
description:
@ -122,8 +113,6 @@ export const fireworksModels = {
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.56,
outputPrice: 1.68,
description:
@ -134,8 +123,6 @@ export const fireworksModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.55,
outputPrice: 2.19,
description:
@ -146,8 +133,6 @@ export const fireworksModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.55,
outputPrice: 2.19,
description:
@ -158,8 +143,6 @@ export const fireworksModels = {
contextWindow: 198000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.55,
outputPrice: 2.19,
description:
@ -170,8 +153,6 @@ export const fireworksModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.07,
outputPrice: 0.3,
description:
@ -182,11 +163,69 @@ export const fireworksModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.15,
outputPrice: 0.6,
description:
"OpenAI gpt-oss-120b: Production-grade, general-purpose model that fits on a single H100 GPU. Features complex reasoning, configurable effort, full chain-of-thought transparency, and supports function calling, tool use, and structured outputs.",
},
"accounts/fireworks/models/minimax-m2p1": {
maxTokens: 4096,
contextWindow: 204800,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.3,
outputPrice: 1.2,
description:
"MiniMax M2.1 is an upgraded version of M2 with improved performance on complex reasoning, coding, and long-context understanding tasks.",
},
"accounts/fireworks/models/deepseek-v3p2": {
maxTokens: 16384,
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.56,
outputPrice: 1.68,
description:
"DeepSeek V3.2 is the latest iteration of the V3 model family with enhanced reasoning capabilities, improved code generation, and better instruction following.",
},
"accounts/fireworks/models/glm-4p7": {
maxTokens: 25344,
contextWindow: 198000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.55,
outputPrice: 2.19,
description:
"Z.ai GLM-4.7 is the latest coding model with exceptional performance on complex programming tasks. Features improved reasoning capabilities and enhanced code generation quality.",
},
"accounts/fireworks/models/llama-v3p3-70b-instruct": {
maxTokens: 16384,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.9,
outputPrice: 0.9,
description:
"Meta Llama 3.3 70B Instruct is a highly capable instruction-tuned model with strong reasoning, coding, and general task performance.",
},
"accounts/fireworks/models/llama4-maverick-instruct-basic": {
maxTokens: 16384,
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.22,
outputPrice: 0.88,
description:
"Llama 4 Maverick is Meta's latest multimodal model with vision capabilities, optimized for instruction following and coding tasks.",
},
"accounts/fireworks/models/llama4-scout-instruct-basic": {
maxTokens: 16384,
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.15,
outputPrice: 0.6,
description:
"Llama 4 Scout is a smaller, faster variant of Llama 4 with multimodal capabilities, ideal for quick iterations and cost-effective deployments.",
},
} as const satisfies Record<string, ModelInfo>

View file

@ -10,8 +10,6 @@ export const geminiModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
supportsReasoningEffort: ["low", "high"],
reasoningEffort: "low",
@ -20,16 +18,19 @@ export const geminiModels = {
defaultTemperature: 1,
inputPrice: 4.0,
outputPrice: 18.0,
cacheReadsPrice: 0.4,
tiers: [
{
contextWindow: 200_000,
inputPrice: 2.0,
outputPrice: 12.0,
cacheReadsPrice: 0.2,
},
{
contextWindow: Infinity,
inputPrice: 4.0,
outputPrice: 18.0,
cacheReadsPrice: 0.4,
},
],
},
@ -37,26 +38,21 @@ export const geminiModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
reasoningEffort: "medium",
supportsTemperature: true,
defaultTemperature: 1,
inputPrice: 0.3,
outputPrice: 2.5,
cacheReadsPrice: 0.075,
cacheWritesPrice: 1.0,
inputPrice: 0.5,
outputPrice: 3.0,
cacheReadsPrice: 0.05,
},
// 2.5 Pro models
"gemini-2.5-pro": {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
@ -85,8 +81,6 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
@ -114,8 +108,6 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
@ -141,8 +133,6 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
@ -172,8 +162,6 @@ export const geminiModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.3,
@ -187,8 +175,6 @@ export const geminiModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.3,
@ -202,8 +188,6 @@ export const geminiModels = {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.3,
@ -219,8 +203,6 @@ export const geminiModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.1,
@ -234,8 +216,6 @@ export const geminiModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.1,

View file

@ -19,8 +19,6 @@ export const groqModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.05,
outputPrice: 0.08,
description: "Meta Llama 3.1 8B Instant model, 128K context.",
@ -30,8 +28,6 @@ export const groqModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.59,
outputPrice: 0.79,
description: "Meta Llama 3.3 70B Versatile model, 128K context.",
@ -41,8 +37,6 @@ export const groqModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.11,
outputPrice: 0.34,
description: "Meta Llama 4 Scout 17B Instruct model, 128K context.",
@ -52,8 +46,6 @@ export const groqModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.29,
outputPrice: 0.59,
description: "Alibaba Qwen 3 32B model, 128K context.",
@ -63,8 +55,6 @@ export const groqModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 2.5,
cacheReadsPrice: 0.15,
@ -76,8 +66,6 @@ export const groqModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.15,
outputPrice: 0.75,
description:
@ -88,8 +76,6 @@ export const groqModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1,
outputPrice: 0.5,
description:

View file

@ -3,7 +3,6 @@ export * from "./baseten.js"
export * from "./bedrock.js"
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"
@ -19,6 +18,7 @@ export * from "./moonshot.js"
export * from "./ollama.js"
export * from "./openai.js"
export * from "./openai-codex.js"
export * from "./openai-codex-rate-limits.js"
export * from "./openrouter.js"
export * from "./qwen-code.js"
export * from "./requesty.js"
@ -38,7 +38,6 @@ import { basetenDefaultModelId } from "./baseten.js"
import { bedrockDefaultModelId } from "./bedrock.js"
import { cerebrasDefaultModelId } from "./cerebras.js"
import { chutesDefaultModelId } from "./chutes.js"
import { claudeCodeDefaultModelId } from "./claude-code.js"
import { deepSeekDefaultModelId } from "./deepseek.js"
import { doubaoDefaultModelId } from "./doubao.js"
import { featherlessDefaultModelId } from "./featherless.js"
@ -127,8 +126,6 @@ export function getProviderDefaultModelId(
return deepInfraDefaultModelId
case "vscode-lm":
return vscodeLlmDefaultModelId
case "claude-code":
return claudeCodeDefaultModelId
case "cerebras":
return cerebrasDefaultModelId
case "sambanova":

View file

@ -18,7 +18,6 @@ export const ioIntelligenceModels = {
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
description: "DeepSeek R1 reasoning model",
},
"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": {
@ -26,7 +25,6 @@ export const ioIntelligenceModels = {
contextWindow: 430000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
description: "Llama 4 Maverick 17B model",
},
"Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": {
@ -34,7 +32,6 @@ export const ioIntelligenceModels = {
contextWindow: 106000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
description: "Qwen3 Coder 480B specialized for coding",
},
"openai/gpt-oss-120b": {
@ -42,7 +39,6 @@ export const ioIntelligenceModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
description: "OpenAI GPT-OSS 120B model",
},
} as const satisfies Record<string, ModelInfo>

View file

@ -8,8 +8,6 @@ export const litellmDefaultModelInfo: ModelInfo = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,

View file

@ -10,8 +10,6 @@ export const lMStudioDefaultModelInfo: ModelInfo = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,

View file

@ -13,8 +13,6 @@ export const minimaxModels = {
contextWindow: 192_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["search_and_replace"],
excludedTools: ["apply_diff"],
preserveReasoning: true,
@ -30,8 +28,6 @@ export const minimaxModels = {
contextWindow: 192_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["search_and_replace"],
excludedTools: ["apply_diff"],
preserveReasoning: true,
@ -47,8 +43,6 @@ export const minimaxModels = {
contextWindow: 192_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["search_and_replace"],
excludedTools: ["apply_diff"],
preserveReasoning: true,

View file

@ -11,8 +11,6 @@ export const mistralModels = {
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 2.0,
outputPrice: 5.0,
},
@ -21,8 +19,6 @@ export const mistralModels = {
contextWindow: 131_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.4,
outputPrice: 2.0,
},
@ -31,8 +27,6 @@ export const mistralModels = {
contextWindow: 131_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.4,
outputPrice: 2.0,
},
@ -41,8 +35,6 @@ export const mistralModels = {
contextWindow: 256_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.3,
outputPrice: 0.9,
},
@ -51,8 +43,6 @@ export const mistralModels = {
contextWindow: 131_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 2.0,
outputPrice: 6.0,
},
@ -61,8 +51,6 @@ export const mistralModels = {
contextWindow: 131_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1,
outputPrice: 0.1,
},
@ -71,8 +59,6 @@ export const mistralModels = {
contextWindow: 131_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.04,
outputPrice: 0.04,
},
@ -81,8 +67,6 @@ export const mistralModels = {
contextWindow: 32_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 0.6,
},
@ -91,8 +75,6 @@ export const mistralModels = {
contextWindow: 131_000,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 2.0,
outputPrice: 6.0,
},

View file

@ -11,8 +11,6 @@ export const moonshotModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6, // $0.60 per million tokens (cache miss)
outputPrice: 2.5, // $2.50 per million tokens
cacheWritesPrice: 0, // $0 per million tokens (cache miss)
@ -24,8 +22,6 @@ export const moonshotModels = {
contextWindow: 262144,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 2.5,
cacheReadsPrice: 0.15,
@ -37,8 +33,6 @@ export const moonshotModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 2.4, // $2.40 per million tokens (cache miss)
outputPrice: 10, // $10.00 per million tokens
cacheWritesPrice: 0, // $0 per million tokens (cache miss)
@ -50,8 +44,6 @@ export const moonshotModels = {
contextWindow: 262_144, // 262,144 tokens
supportsImages: false, // Text-only (no image/vision support)
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6, // $0.60 per million tokens (cache miss)
outputPrice: 2.5, // $2.50 per million tokens
cacheWritesPrice: 0, // $0 per million tokens (cache miss)
@ -61,6 +53,19 @@ export const moonshotModels = {
defaultTemperature: 1.0,
description: `The kimi-k2-thinking model is a general-purpose agentic reasoning model developed by Moonshot AI. Thanks to its strength in deep reasoning and multi-turn tool use, it can solve even the hardest problems.`,
},
"kimi-k2.5": {
maxTokens: 16_384,
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.6, // $0.60 per million tokens (cache miss)
outputPrice: 3.0, // $3.00 per million tokens
cacheReadsPrice: 0.1, // $0.10 per million tokens (cache hit)
supportsTemperature: true,
defaultTemperature: 1.0,
description:
"Kimi K2.5 is the latest generation of Moonshot AI's Kimi series, featuring improved reasoning capabilities and enhanced performance across diverse tasks.",
},
} as const satisfies Record<string, ModelInfo>
export const MOONSHOT_DEFAULT_TEMPERATURE = 0.6

View file

@ -8,7 +8,6 @@ export const ollamaDefaultModelInfo: ModelInfo = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,

View file

@ -0,0 +1,29 @@
/**
* OpenAI Codex usage/rate limit information (ChatGPT subscription)
*/
export interface OpenAiCodexRateLimitInfo {
primary?: {
/** Used percent in 0100 */
usedPercent: number
/** Window length in minutes, when provided */
windowMinutes?: number
/** Reset time (unix ms since epoch), when provided */
resetsAt?: number
}
secondary?: {
/** Used percent in 0100 */
usedPercent: number
/** Window length in minutes, when provided */
windowMinutes?: number
/** Reset time (unix ms since epoch), when provided */
resetsAt?: number
}
credits?: {
hasCredits: boolean
unlimited: boolean
balance?: string
}
planType?: string
/** Timestamp when this was fetched (unix ms since epoch) */
fetchedAt: number
}

View file

@ -27,8 +27,6 @@ export const openAiCodexModels = {
"gpt-5.1-codex-max": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -44,8 +42,6 @@ export const openAiCodexModels = {
"gpt-5.1-codex": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -61,8 +57,6 @@ export const openAiCodexModels = {
"gpt-5.2-codex": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -77,8 +71,6 @@ export const openAiCodexModels = {
"gpt-5.1": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -95,8 +87,6 @@ export const openAiCodexModels = {
"gpt-5": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -113,8 +103,6 @@ export const openAiCodexModels = {
"gpt-5-codex": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -130,8 +118,6 @@ export const openAiCodexModels = {
"gpt-5-codex-mini": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -147,8 +133,6 @@ export const openAiCodexModels = {
"gpt-5.1-codex-mini": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -163,8 +147,6 @@ export const openAiCodexModels = {
"gpt-5.2": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,

View file

@ -9,8 +9,6 @@ export const openAiNativeModels = {
"gpt-5.1-codex-max": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -29,8 +27,6 @@ export const openAiNativeModels = {
"gpt-5.2": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -52,8 +48,6 @@ export const openAiNativeModels = {
"gpt-5.2-codex": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -72,8 +66,6 @@ export const openAiNativeModels = {
"gpt-5.2-chat-latest": {
maxTokens: 16_384,
contextWindow: 128_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -86,8 +78,6 @@ export const openAiNativeModels = {
"gpt-5.1": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -109,8 +99,6 @@ export const openAiNativeModels = {
"gpt-5.1-codex": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -128,8 +116,6 @@ export const openAiNativeModels = {
"gpt-5.1-codex-mini": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -146,8 +132,6 @@ export const openAiNativeModels = {
"gpt-5": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -168,8 +152,6 @@ export const openAiNativeModels = {
"gpt-5-mini": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -190,8 +172,6 @@ export const openAiNativeModels = {
"gpt-5-codex": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -208,8 +188,6 @@ export const openAiNativeModels = {
"gpt-5-nano": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -227,8 +205,6 @@ export const openAiNativeModels = {
"gpt-5-chat-latest": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -241,8 +217,6 @@ export const openAiNativeModels = {
"gpt-4.1": {
maxTokens: 32_768,
contextWindow: 1_047_576,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -258,8 +232,6 @@ export const openAiNativeModels = {
"gpt-4.1-mini": {
maxTokens: 32_768,
contextWindow: 1_047_576,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -275,8 +247,6 @@ export const openAiNativeModels = {
"gpt-4.1-nano": {
maxTokens: 32_768,
contextWindow: 1_047_576,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -292,8 +262,6 @@ export const openAiNativeModels = {
o3: {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2.0,
@ -310,8 +278,6 @@ export const openAiNativeModels = {
"o3-high": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2.0,
@ -323,8 +289,6 @@ export const openAiNativeModels = {
"o3-low": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2.0,
@ -336,8 +300,6 @@ export const openAiNativeModels = {
"o4-mini": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 1.1,
@ -354,8 +316,6 @@ export const openAiNativeModels = {
"o4-mini-high": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 1.1,
@ -367,8 +327,6 @@ export const openAiNativeModels = {
"o4-mini-low": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 1.1,
@ -380,8 +338,6 @@ export const openAiNativeModels = {
"o3-mini": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: false,
supportsPromptCache: true,
inputPrice: 1.1,
@ -394,8 +350,6 @@ export const openAiNativeModels = {
"o3-mini-high": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: false,
supportsPromptCache: true,
inputPrice: 1.1,
@ -407,8 +361,6 @@ export const openAiNativeModels = {
"o3-mini-low": {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: false,
supportsPromptCache: true,
inputPrice: 1.1,
@ -420,8 +372,6 @@ export const openAiNativeModels = {
o1: {
maxTokens: 100_000,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 15,
@ -432,8 +382,6 @@ export const openAiNativeModels = {
"o1-preview": {
maxTokens: 32_768,
contextWindow: 128_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 15,
@ -444,8 +392,6 @@ export const openAiNativeModels = {
"o1-mini": {
maxTokens: 65_536,
contextWindow: 128_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 1.1,
@ -456,8 +402,6 @@ export const openAiNativeModels = {
"gpt-4o": {
maxTokens: 16_384,
contextWindow: 128_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2.5,
@ -471,8 +415,6 @@ export const openAiNativeModels = {
"gpt-4o-mini": {
maxTokens: 16_384,
contextWindow: 128_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.15,
@ -486,8 +428,6 @@ export const openAiNativeModels = {
"codex-mini-latest": {
maxTokens: 16_384,
contextWindow: 200_000,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsImages: false,
supportsPromptCache: false,
inputPrice: 1.5,
@ -501,8 +441,6 @@ export const openAiNativeModels = {
"gpt-5-2025-08-07": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -523,8 +461,6 @@ export const openAiNativeModels = {
"gpt-5-mini-2025-08-07": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -545,8 +481,6 @@ export const openAiNativeModels = {
"gpt-5-nano-2025-08-07": {
maxTokens: 128000,
contextWindow: 400000,
supportsNativeTools: true,
defaultToolProtocol: "native",
includedTools: ["apply_patch"],
excludedTools: ["apply_diff", "write_to_file"],
supportsImages: true,
@ -570,8 +504,6 @@ export const openAiModelInfoSaneDefaults: ModelInfo = {
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
supportsNativeTools: true,
defaultToolProtocol: "native",
}
// https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation

View file

@ -8,7 +8,6 @@ export const openRouterDefaultModelInfo: ModelInfo = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,

View file

@ -10,8 +10,6 @@ export const qwenCodeModels = {
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
@ -23,8 +21,6 @@ export const qwenCodeModels = {
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,

View file

@ -9,8 +9,6 @@ export const requestyDefaultModelInfo: ModelInfo = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,

View file

@ -19,8 +19,6 @@ export const sambaNovaModels = {
contextWindow: 16384,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1,
outputPrice: 0.2,
description: "Meta Llama 3.1 8B Instruct model with 16K context window.",
@ -30,8 +28,6 @@ export const sambaNovaModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 1.2,
description: "Meta Llama 3.3 70B Instruct model with 128K context window.",
@ -42,8 +38,6 @@ export const sambaNovaModels = {
supportsImages: false,
supportsPromptCache: false,
supportsReasoningBudget: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 5.0,
outputPrice: 7.0,
description: "DeepSeek R1 reasoning model with 32K context window.",
@ -53,8 +47,6 @@ export const sambaNovaModels = {
contextWindow: 32768,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 4.5,
description: "DeepSeek V3 model with 32K context window.",
@ -64,8 +56,6 @@ export const sambaNovaModels = {
contextWindow: 32768,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 4.5,
description: "DeepSeek V3.1 model with 32K context window.",
@ -75,8 +65,6 @@ export const sambaNovaModels = {
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.63,
outputPrice: 1.8,
description: "Meta Llama 4 Maverick 17B 128E Instruct model with 128K context window.",
@ -86,8 +74,6 @@ export const sambaNovaModels = {
contextWindow: 8192,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.4,
outputPrice: 0.8,
description: "Alibaba Qwen 3 32B model with 8K context window.",
@ -97,8 +83,6 @@ export const sambaNovaModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.22,
outputPrice: 0.59,
description: "OpenAI gpt oss 120b model with 128k context window.",

View file

@ -7,7 +7,6 @@ export const unboundDefaultModelInfo: ModelInfo = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,

View file

@ -90,7 +90,6 @@ export const vercelAiGatewayDefaultModelInfo: ModelInfo = {
contextWindow: 200000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
inputPrice: 3,
outputPrice: 15,
cacheWritesPrice: 3.75,

View file

@ -10,8 +10,6 @@ export const vertexModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
supportsReasoningEffort: ["low", "high"],
reasoningEffort: "low",
@ -20,16 +18,19 @@ export const vertexModels = {
defaultTemperature: 1,
inputPrice: 4.0,
outputPrice: 18.0,
cacheReadsPrice: 0.4,
tiers: [
{
contextWindow: 200_000,
inputPrice: 2.0,
outputPrice: 12.0,
cacheReadsPrice: 0.2,
},
{
contextWindow: Infinity,
inputPrice: 4.0,
outputPrice: 18.0,
cacheReadsPrice: 0.4,
},
],
},
@ -37,25 +38,20 @@ export const vertexModels = {
maxTokens: 65_536,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
reasoningEffort: "medium",
supportsTemperature: true,
defaultTemperature: 1,
inputPrice: 0.3,
outputPrice: 2.5,
cacheReadsPrice: 0.075,
cacheWritesPrice: 1.0,
inputPrice: 0.5,
outputPrice: 3.0,
cacheReadsPrice: 0.05,
},
"gemini-2.5-flash-preview-05-20:thinking": {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.15,
@ -68,8 +64,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.15,
@ -79,8 +73,6 @@ export const vertexModels = {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.3,
@ -94,8 +86,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 0.15,
@ -108,8 +98,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 0.15,
@ -119,8 +107,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5,
@ -130,8 +116,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5,
@ -141,8 +125,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5,
@ -154,8 +136,6 @@ export const vertexModels = {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 2.5,
@ -182,8 +162,6 @@ export const vertexModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 0,
@ -193,8 +171,6 @@ export const vertexModels = {
maxTokens: 8192,
contextWindow: 2_097_152,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 0,
@ -204,8 +180,6 @@ export const vertexModels = {
maxTokens: 8192,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.15,
@ -215,8 +189,6 @@ export const vertexModels = {
maxTokens: 8192,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 0.075,
@ -226,8 +198,6 @@ export const vertexModels = {
maxTokens: 8192,
contextWindow: 32_768,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 0,
@ -237,8 +207,6 @@ export const vertexModels = {
maxTokens: 8192,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.075,
@ -248,8 +216,6 @@ export const vertexModels = {
maxTokens: 8192,
contextWindow: 2_097_152,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: false,
inputPrice: 1.25,
@ -260,8 +226,6 @@ export const vertexModels = {
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens (≤200K context)
outputPrice: 15.0, // $15 per million output tokens (≤200K context)
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -283,8 +247,6 @@ export const vertexModels = {
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0, // $3 per million input tokens (≤200K context)
outputPrice: 15.0, // $15 per million output tokens (≤200K context)
cacheWritesPrice: 3.75, // $3.75 per million tokens
@ -306,8 +268,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.0,
outputPrice: 5.0,
cacheWritesPrice: 1.25,
@ -319,8 +279,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 5.0,
outputPrice: 25.0,
cacheWritesPrice: 6.25,
@ -332,8 +290,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
@ -345,8 +301,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
@ -357,8 +311,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -371,8 +323,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -383,8 +333,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -395,8 +343,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
@ -407,8 +353,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.0,
outputPrice: 5.0,
cacheWritesPrice: 1.25,
@ -419,8 +363,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
@ -431,8 +373,6 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.25,
outputPrice: 1.25,
cacheWritesPrice: 0.3,
@ -442,8 +382,6 @@ export const vertexModels = {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsPromptCache: true,
inputPrice: 0.1,
@ -458,7 +396,6 @@ export const vertexModels = {
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.35,
outputPrice: 1.15,
description: "Meta Llama 4 Maverick 17B Instruct model, 128K context.",
@ -468,7 +405,6 @@ export const vertexModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 1.35,
outputPrice: 5.4,
description: "DeepSeek R1 (0528). Available in us-central1",
@ -478,7 +414,6 @@ export const vertexModels = {
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.6,
outputPrice: 1.7,
description: "DeepSeek V3.1. Available in us-west2",
@ -488,7 +423,6 @@ export const vertexModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.15,
outputPrice: 0.6,
description: "OpenAI gpt-oss 120B. Available in us-central1",
@ -498,7 +432,6 @@ export const vertexModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.075,
outputPrice: 0.3,
description: "OpenAI gpt-oss 20B. Available in us-central1",
@ -508,7 +441,6 @@ export const vertexModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 1.0,
outputPrice: 4.0,
description: "Qwen3 Coder 480B A35B Instruct. Available in us-south1",
@ -518,11 +450,19 @@ export const vertexModels = {
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
inputPrice: 0.25,
outputPrice: 1.0,
description: "Qwen3 235B A22B Instruct. Available in us-south1",
},
"moonshotai/kimi-k2-thinking-maas": {
maxTokens: 16_384,
contextWindow: 262_144,
supportsPromptCache: false,
supportsImages: false,
inputPrice: 0.6,
outputPrice: 2.5,
description: "Kimi K2 Thinking Model with 256K context window.",
},
} as const satisfies Record<string, ModelInfo>
// Vertex AI models that support 1M context window beta

View file

@ -11,8 +11,6 @@ export const xaiModels = {
contextWindow: 256_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 1.5,
cacheWritesPrice: 0.02,
@ -26,8 +24,6 @@ export const xaiModels = {
contextWindow: 2_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 0.5,
cacheWritesPrice: 0.05,
@ -42,8 +38,6 @@ export const xaiModels = {
contextWindow: 2_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 0.5,
cacheWritesPrice: 0.05,
@ -58,8 +52,6 @@ export const xaiModels = {
contextWindow: 2_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 0.5,
cacheWritesPrice: 0.05,
@ -74,8 +66,6 @@ export const xaiModels = {
contextWindow: 2_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 0.5,
cacheWritesPrice: 0.05,
@ -90,8 +80,6 @@ export const xaiModels = {
contextWindow: 256_000,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 0.75,
@ -105,8 +93,6 @@ export const xaiModels = {
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.3,
outputPrice: 0.5,
cacheWritesPrice: 0.07,
@ -122,8 +108,6 @@ export const xaiModels = {
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 0.75,

View file

@ -16,8 +16,6 @@ export const internationalZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 2.2,
cacheWritesPrice: 0,
@ -30,8 +28,6 @@ export const internationalZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.2,
outputPrice: 1.1,
cacheWritesPrice: 0,
@ -44,8 +40,6 @@ export const internationalZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 2.2,
outputPrice: 8.9,
cacheWritesPrice: 0,
@ -58,8 +52,6 @@ export const internationalZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 1.1,
outputPrice: 4.5,
cacheWritesPrice: 0,
@ -71,8 +63,6 @@ export const internationalZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
@ -84,8 +74,6 @@ export const internationalZAiModels = {
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 1.8,
cacheWritesPrice: 0,
@ -93,13 +81,23 @@ export const internationalZAiModels = {
description:
"GLM-4.5V is Z.AI's multimodal visual reasoning model (image/video/text/file input), optimized for GUI tasks, grounding, and document/video understanding.",
},
"glm-4.6v": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.3,
outputPrice: 0.9,
cacheWritesPrice: 0,
cacheReadsPrice: 0.05,
description:
"GLM-4.6V is an advanced multimodal vision model with improved performance and cost-efficiency for visual understanding tasks.",
},
"glm-4.6": {
maxTokens: 16_384,
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.6,
outputPrice: 2.2,
cacheWritesPrice: 0,
@ -112,8 +110,6 @@ export const internationalZAiModels = {
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsReasoningEffort: ["disable", "medium"],
reasoningEffort: "medium",
preserveReasoning: true,
@ -124,13 +120,59 @@ export const internationalZAiModels = {
description:
"GLM-4.7 is Zhipu's latest model with built-in thinking capabilities enabled by default. It provides enhanced reasoning for complex tasks while maintaining fast response times.",
},
"glm-4.7-flash": {
maxTokens: 16_384,
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description:
"GLM-4.7-Flash is a free, high-speed variant of GLM-4.7 offering fast responses for reasoning and coding tasks.",
},
"glm-4.7-flashx": {
maxTokens: 16_384,
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.07,
outputPrice: 0.4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.01,
description:
"GLM-4.7-FlashX is an ultra-fast variant of GLM-4.7 with exceptional speed and cost-effectiveness for high-throughput applications.",
},
"glm-4.6v-flash": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description:
"GLM-4.6V-Flash is a free, high-speed multimodal vision model for rapid image understanding and visual reasoning tasks.",
},
"glm-4.6v-flashx": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.04,
outputPrice: 0.4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.004,
description:
"GLM-4.6V-FlashX is an ultra-fast multimodal vision model optimized for high-speed visual processing at low cost.",
},
"glm-4-32b-0414-128k": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1,
outputPrice: 0.1,
cacheWritesPrice: 0,
@ -147,8 +189,6 @@ export const mainlandZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.29,
outputPrice: 1.14,
cacheWritesPrice: 0,
@ -161,8 +201,6 @@ export const mainlandZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1,
outputPrice: 0.6,
cacheWritesPrice: 0,
@ -175,8 +213,6 @@ export const mainlandZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.29,
outputPrice: 1.14,
cacheWritesPrice: 0,
@ -189,8 +225,6 @@ export const mainlandZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.1,
outputPrice: 0.6,
cacheWritesPrice: 0,
@ -202,8 +236,6 @@ export const mainlandZAiModels = {
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
@ -215,8 +247,6 @@ export const mainlandZAiModels = {
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.29,
outputPrice: 0.93,
cacheWritesPrice: 0,
@ -229,8 +259,6 @@ export const mainlandZAiModels = {
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
inputPrice: 0.29,
outputPrice: 1.14,
cacheWritesPrice: 0,
@ -243,8 +271,6 @@ export const mainlandZAiModels = {
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
supportsNativeTools: true,
defaultToolProtocol: "native",
supportsReasoningEffort: ["disable", "medium"],
reasoningEffort: "medium",
preserveReasoning: true,
@ -255,6 +281,66 @@ export const mainlandZAiModels = {
description:
"GLM-4.7 is Zhipu's latest model with built-in thinking capabilities enabled by default. It provides enhanced reasoning for complex tasks while maintaining fast response times.",
},
"glm-4.7-flash": {
maxTokens: 16_384,
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description:
"GLM-4.7-Flash is a free, high-speed variant of GLM-4.7 offering fast responses for reasoning and coding tasks.",
},
"glm-4.7-flashx": {
maxTokens: 16_384,
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.035,
outputPrice: 0.2,
cacheWritesPrice: 0,
cacheReadsPrice: 0.005,
description:
"GLM-4.7-FlashX is an ultra-fast variant of GLM-4.7 with exceptional speed and cost-effectiveness for high-throughput applications.",
},
"glm-4.6v": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.15,
outputPrice: 0.45,
cacheWritesPrice: 0,
cacheReadsPrice: 0.025,
description:
"GLM-4.6V is an advanced multimodal vision model with improved performance and cost-efficiency for visual understanding tasks.",
},
"glm-4.6v-flash": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0,
outputPrice: 0,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description:
"GLM-4.6V-Flash is a free, high-speed multimodal vision model for rapid image understanding and visual reasoning tasks.",
},
"glm-4.6v-flashx": {
maxTokens: 16_384,
contextWindow: 131_072,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.02,
outputPrice: 0.2,
cacheWritesPrice: 0,
cacheReadsPrice: 0.002,
description:
"GLM-4.6V-FlashX is an ultra-fast multimodal vision model optimized for high-speed visual processing at low cost.",
},
} as const satisfies Record<string, ModelInfo>
export const ZAI_DEFAULT_TEMPERATURE = 0.6

View file

@ -89,9 +89,7 @@ export type TaskProviderEvents = {
*/
export interface CreateTaskOptions {
enableDiff?: boolean
enableCheckpoints?: boolean
fuzzyMatchThreshold?: number
consecutiveMistakeLimit?: number
experiments?: Record<string, boolean>
initialTodos?: TodoItem[]

View file

@ -32,3 +32,69 @@ export const commandExecutionStatusSchema = z.discriminatedUnion("status", [
])
export type CommandExecutionStatus = z.infer<typeof commandExecutionStatusSchema>
/**
* PersistedCommandOutput
*
* Represents the result of a terminal command execution that may have been
* truncated and persisted to disk.
*
* When command output exceeds the configured preview threshold, the full
* output is saved to a disk artifact file. The LLM receives this structure
* which contains:
* - A preview of the output (for immediate display in context)
* - Metadata about the full output (size, truncation status)
* - A path to the artifact file for later retrieval via `read_command_output`
*
* ## Usage in execute_command Response
*
* The response format depends on whether truncation occurred:
*
* **Not truncated** (output fits in preview):
* ```json
* {
* "preview": "full output here...",
* "totalBytes": 1234,
* "artifactPath": null,
* "truncated": false
* }
* ```
*
* **Truncated** (output exceeded threshold):
* ```json
* {
* "preview": "first 4KB of output...",
* "totalBytes": 1048576,
* "artifactPath": "/path/to/tasks/123/command-output/cmd-1706119234567.txt",
* "truncated": true
* }
* ```
*
* @see OutputInterceptor - Creates these results during command execution
* @see ReadCommandOutputTool - Retrieves full content from artifact files
*/
export interface PersistedCommandOutput {
/**
* Preview of the command output, truncated to the preview threshold.
* Always contains the beginning of the output, even if truncated.
*/
preview: string
/**
* Total size of the command output in bytes.
* Useful for determining if additional reads are needed.
*/
totalBytes: number
/**
* Absolute path to the artifact file containing full output.
* `null` if output wasn't truncated (no artifact was created).
*/
artifactPath: string | null
/**
* Whether the output was truncated (exceeded preview threshold).
* When `true`, use `read_command_output` to retrieve full content.
*/
truncated: boolean
}

View file

@ -17,6 +17,7 @@ export type ToolGroup = z.infer<typeof toolGroupsSchema>
export const toolNames = [
"execute_command",
"read_file",
"read_command_output",
"write_to_file",
"apply_diff",
"search_and_replace",
@ -32,10 +33,10 @@ export const toolNames = [
"attempt_completion",
"switch_mode",
"new_task",
"fetch_instructions",
"codebase_search",
"update_todo_list",
"run_slash_command",
"skill",
"generate_image",
"custom_tool",
] as const
@ -57,48 +58,3 @@ export const toolUsageSchema = z.record(
)
export type ToolUsage = z.infer<typeof toolUsageSchema>
/**
* Tool protocol constants
*/
export const TOOL_PROTOCOL = {
XML: "xml",
NATIVE: "native",
} as const
/**
* Tool protocol type for system prompt generation
* Derived from TOOL_PROTOCOL constants to ensure type safety
*/
export type ToolProtocol = (typeof TOOL_PROTOCOL)[keyof typeof TOOL_PROTOCOL]
/**
* Default model info properties for native tool support.
* Used to merge with cached model info that may lack these fields.
* Router providers (Requesty, Unbound, LiteLLM) assume all models support native tools.
*/
export const NATIVE_TOOL_DEFAULTS = {
supportsNativeTools: true,
defaultToolProtocol: TOOL_PROTOCOL.NATIVE,
} as const
/**
* Checks if the protocol is native (non-XML).
*
* @param protocol - The tool protocol to check
* @returns True if protocol is native
*/
export function isNativeProtocol(protocol: ToolProtocol): boolean {
return protocol === TOOL_PROTOCOL.NATIVE
}
/**
* Gets the effective protocol from settings or falls back to the default XML.
* This function is safe to use in webview-accessible code as it doesn't depend on vscode module.
*
* @param toolProtocol - Optional tool protocol from settings
* @returns The effective tool protocol (defaults to "xml")
*/
export function getEffectiveProtocol(toolProtocol?: ToolProtocol): ToolProtocol {
return toolProtocol || TOOL_PROTOCOL.XML
}

View file

@ -19,6 +19,8 @@ import type { SerializedCustomToolDefinition } from "./custom-tool.js"
import type { GitCommit } from "./git.js"
import type { McpServer } from "./mcp.js"
import type { ModelRecord, RouterModels } from "./model.js"
import type { OpenAiCodexRateLimitInfo } from "./providers/openai-codex-rate-limits.js"
import type { WorktreeIncludeStatus } from "./worktree.js"
/**
* ExtensionMessage
@ -28,6 +30,8 @@ export interface ExtensionMessage {
type:
| "action"
| "state"
| "taskHistoryUpdated"
| "taskHistoryItemUpdated"
| "selectedImages"
| "theme"
| "workspaceUpdated"
@ -93,10 +97,19 @@ export interface ExtensionMessage {
| "interactionRequired"
| "browserSessionUpdate"
| "browserSessionNavigate"
| "claudeCodeRateLimits"
| "customToolsResult"
| "modes"
| "taskWithAggregatedCosts"
| "openAiCodexRateLimits"
// Worktree response types
| "worktreeList"
| "worktreeResult"
| "worktreeCopyProgress"
| "branchList"
| "worktreeDefaults"
| "worktreeIncludeStatus"
| "branchWorktreeIncludeResult"
| "folderSelected"
text?: string
payload?: any // eslint-disable-line @typescript-eslint/no-explicit-any
checkpointWarning?: {
@ -114,7 +127,11 @@ export interface ExtensionMessage {
| "switchTab"
| "toggleAutoApprove"
invoke?: "newChat" | "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
state?: ExtensionState
/**
* Partial state updates are allowed to reduce message size (e.g. omit large fields like taskHistory).
* The webview is responsible for merging.
*/
state?: Partial<ExtensionState>
images?: string[]
filePaths?: string[]
openedTabs?: Array<{
@ -164,7 +181,9 @@ export interface ExtensionMessage {
customMode?: ModeConfig
slug?: string
success?: boolean
values?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
/** Generic payload for extension messages that use `values` */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
values?: Record<string, any>
requestId?: string
promptText?: string
results?:
@ -204,6 +223,62 @@ export interface ExtensionMessage {
childrenCost: number
}
historyItem?: HistoryItem
taskHistory?: HistoryItem[] // For taskHistoryUpdated: full sorted task history
/** For taskHistoryItemUpdated: single updated/added history item */
taskHistoryItem?: HistoryItem
// Worktree response properties
worktrees?: Array<{
path: string
branch: string
commitHash: string
isCurrent: boolean
isBare: boolean
isDetached: boolean
isLocked: boolean
lockReason?: string
}>
isGitRepo?: boolean
isMultiRoot?: boolean
isSubfolder?: boolean
gitRootPath?: string
worktreeResult?: {
success: boolean
message: string
worktree?: {
path: string
branch: string
commitHash: string
isCurrent: boolean
isBare: boolean
isDetached: boolean
isLocked: boolean
lockReason?: string
}
}
localBranches?: string[]
remoteBranches?: string[]
currentBranch?: string
suggestedBranch?: string
suggestedPath?: string
worktreeIncludeExists?: boolean
worktreeIncludeStatus?: WorktreeIncludeStatus
hasGitignore?: boolean
gitignoreContent?: string
// branchWorktreeIncludeResult
branch?: string
hasWorktreeInclude?: boolean
// worktreeCopyProgress (size-based)
copyProgressBytesCopied?: number
copyProgressTotalBytes?: number
copyProgressItemName?: string
// folderSelected
path?: string
}
export interface OpenAiCodexRateLimitsMessage {
type: "openAiCodexRateLimits"
values?: OpenAiCodexRateLimitInfo
error?: string
}
export type ExtensionState = Pick<
@ -241,8 +316,7 @@ export type ExtensionState = Pick<
| "soundEnabled"
| "soundVolume"
| "maxConcurrentFileReads"
| "terminalOutputLineLimit"
| "terminalOutputCharacterLimit"
| "terminalOutputPreviewSize"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
| "terminalCommandDelay"
@ -251,16 +325,12 @@ export type ExtensionState = Pick<
| "terminalZshOhMy"
| "terminalZshP10k"
| "terminalZdotdir"
| "terminalCompressProgressBar"
| "diagnosticsEnabled"
| "diffEnabled"
| "fuzzyMatchThreshold"
| "language"
| "modeApiConfigs"
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "customCondensingPrompt"
| "codebaseIndexConfig"
| "codebaseIndexModels"
@ -276,6 +346,7 @@ export type ExtensionState = Pick<
| "includeCurrentCost"
| "maxGitStatusFiles"
| "requestDelaySeconds"
| "showWorktreesInHomeScreen"
> & {
version: string
clineMessages: ClineMessage[]
@ -302,11 +373,10 @@ export type ExtensionState = Pick<
experiments: Experiments // Map of experiment IDs to their enabled state
mcpEnabled: boolean
enableMcpServerCreation: boolean
mode: string
customModes: ModeConfig[]
toolRequirements?: Record<string, boolean> // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled)
toolRequirements?: Record<string, boolean> // Map of tool names to their requirements (e.g. {"apply_diff": true})
cwd?: string // Current working directory
telemetrySetting: TelemetrySetting
@ -346,7 +416,6 @@ export type ExtensionState = Pick<
remoteControlEnabled: boolean
taskSyncEnabled: boolean
featureRoomoteControlEnabled: boolean
claudeCodeIsAuthenticated?: boolean
openAiCodexIsAuthenticated?: boolean
debug?: boolean
}
@ -445,7 +514,6 @@ export interface WebviewMessage {
| "deleteMessageConfirm"
| "submitEditedMessage"
| "editMessageConfirm"
| "enableMcpServerCreation"
| "remoteControlEnabled"
| "taskSyncEnabled"
| "searchCommits"
@ -477,8 +545,6 @@ export interface WebviewMessage {
| "cloudLandingPageSignIn"
| "rooCloudSignOut"
| "rooCloudManualUrl"
| "claudeCodeSignIn"
| "claudeCodeSignOut"
| "openAiCodexSignIn"
| "openAiCodexSignOut"
| "switchOrganization"
@ -533,11 +599,23 @@ export interface WebviewMessage {
| "openDebugApiHistory"
| "openDebugUiHistory"
| "downloadErrorDiagnostics"
| "requestClaudeCodeRateLimits"
| "requestOpenAiCodexRateLimits"
| "refreshCustomTools"
| "requestModes"
| "switchMode"
| "debugSetting"
// Worktree messages
| "listWorktrees"
| "createWorktree"
| "deleteWorktree"
| "switchWorktree"
| "getAvailableBranches"
| "getWorktreeDefaults"
| "getWorktreeIncludeStatus"
| "checkBranchWorktreeInclude"
| "createWorktreeInclude"
| "checkoutBranch"
| "browseForWorktreePath"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
@ -562,6 +640,7 @@ export interface WebviewMessage {
promptMode?: string | "enhance"
customPrompt?: PromptComponent
dataUrls?: string[]
/** Generic payload for webview messages that use `values` */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
values?: Record<string, any>
query?: string
@ -628,6 +707,18 @@ export interface WebviewMessage {
codebaseIndexOpenRouterApiKey?: string
}
updatedSettings?: RooCodeSettings
// Worktree properties
worktreePath?: string
worktreeBranch?: string
worktreeBaseBranch?: string
worktreeCreateNewBranch?: boolean
worktreeForce?: boolean
worktreeNewWindow?: boolean
worktreeIncludeContent?: string
}
export interface RequestOpenAiCodexRateLimitsMessage {
type: "requestOpenAiCodexRateLimits"
}
export const checkoutDiffPayloadSchema = z.object({
@ -703,7 +794,7 @@ export interface ClineSayTool {
| "newFileCreated"
| "codebaseSearch"
| "readFile"
| "fetchInstructions"
| "readCommandOutput"
| "listFilesTopLevel"
| "listFilesRecursive"
| "searchFiles"
@ -714,7 +805,14 @@ export interface ClineSayTool {
| "imageGenerated"
| "runSlashCommand"
| "updateTodoList"
| "skill"
path?: string
// For readCommandOutput
readStart?: number
readEnd?: number
totalBytes?: number
searchPattern?: string
matchCount?: number
diff?: string
content?: string
// Unified diff statistics computed by the extension
@ -754,6 +852,8 @@ export interface ClineSayTool {
args?: string
source?: string
description?: string
// Properties for skill tool
skill?: string
}
// Must keep in sync with system prompt.

View file

@ -0,0 +1,99 @@
/**
* Worktree Types
*
* Platform-agnostic type definitions for git worktree operations.
* These types are decoupled from VSCode and can be used by any consumer.
*/
/**
* Represents a git worktree
*/
export interface Worktree {
/** Absolute path to the worktree directory */
path: string
/** Branch name - empty string if detached HEAD */
branch: string
/** Current commit hash */
commitHash: string
/** Whether this is the current worktree (matches cwd) */
isCurrent: boolean
/** Whether this is the bare/main repository */
isBare: boolean
/** Whether HEAD is detached (not on a branch) */
isDetached: boolean
/** Whether the worktree is locked */
isLocked: boolean
/** Reason for lock if locked */
lockReason?: string
}
/**
* Result of a worktree operation (create, delete, etc.)
*/
export interface WorktreeResult {
/** Whether the operation succeeded */
success: boolean
/** Human-readable message describing the result */
message: string
/** The worktree that was affected (if applicable) */
worktree?: Worktree
}
/**
* Branch information for worktree creation
*/
export interface BranchInfo {
/** Local branches available */
localBranches: string[]
/** Remote branches available */
remoteBranches: string[]
/** Currently checked out branch */
currentBranch: string
}
/**
* Options for creating a worktree
*/
export interface CreateWorktreeOptions {
/** Path where the worktree will be created */
path: string
/** Branch name to checkout or create */
branch?: string
/** Base branch to create new branch from */
baseBranch?: string
/** If true, create a new branch; if false, checkout existing branch */
createNewBranch?: boolean
}
/**
* Status of .worktreeinclude file
*/
export interface WorktreeIncludeStatus {
/** Whether .worktreeinclude exists in the directory */
exists: boolean
/** Whether .gitignore exists in the directory */
hasGitignore: boolean
/** Content of .gitignore (for creating .worktreeinclude) */
gitignoreContent?: string
}
/**
* Response for listWorktrees handler
*/
export interface WorktreeListResponse {
worktrees: Worktree[]
isGitRepo: boolean
error?: string
isMultiRoot: boolean
isSubfolder: boolean
gitRootPath: string
}
/**
* Response for worktree defaults
*/
export interface WorktreeDefaultsResponse {
suggestedBranch: string
suggestedPath: string
error?: string
}

337
pnpm-lock.yaml generated
View file

@ -14,6 +14,7 @@ overrides:
glob: '>=11.1.0'
'@types/react': ^18.3.23
'@types/react-dom': ^18.3.5
zod: 3.25.76
importers:
@ -103,6 +104,12 @@ importers:
commander:
specifier: ^12.1.0
version: 12.1.0
cross-spawn:
specifier: ^7.0.6
version: 7.0.6
execa:
specifier: ^9.5.2
version: 9.6.0
fuzzysort:
specifier: ^3.1.0
version: 3.1.0
@ -262,7 +269,7 @@ importers:
version: 0.518.0(react@18.3.1)
next:
specifier: ~15.2.8
version: 15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 15.2.8(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -297,8 +304,8 @@ importers:
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -371,7 +378,7 @@ importers:
version: 0.518.0(react@18.3.1)
next:
specifier: ~15.2.8
version: 15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 15.2.8(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -412,8 +419,8 @@ importers:
specifier: ^6.1.86
version: 6.1.86
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -438,7 +445,7 @@ importers:
version: 10.4.21(postcss@8.5.4)
next-sitemap:
specifier: ^4.2.3
version: 4.2.3(next@15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
version: 4.2.3(next@15.2.8(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
postcss:
specifier: ^8.5.4
version: 8.5.4
@ -449,8 +456,8 @@ importers:
packages/build:
dependencies:
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -483,7 +490,7 @@ importers:
specifier: ^4.8.1
version: 4.8.1
zod:
specifier: ^3.25.76
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
@ -551,11 +558,14 @@ importers:
execa:
specifier: ^9.5.2
version: 9.6.0
ignore:
specifier: ^7.0.3
version: 7.0.5
openai:
specifier: ^5.12.2
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
zod:
specifier: ^3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
@ -584,7 +594,7 @@ importers:
version: 0.13.0
drizzle-orm:
specifier: ^0.44.1
version: 0.44.1(@libsql/client@0.15.8)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7)
version: 0.44.1(@libsql/client@0.15.8)(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7)
execa:
specifier: ^9.6.0
version: 9.6.0
@ -610,8 +620,8 @@ importers:
specifier: ^5.5.5
version: 5.5.5
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -672,8 +682,8 @@ importers:
specifier: ^5.0.0
version: 5.1.1
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -694,8 +704,8 @@ importers:
packages/types:
dependencies:
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -756,7 +766,7 @@ importers:
version: 1.2.0
'@mistralai/mistralai':
specifier: ^1.9.18
version: 1.9.18(zod@3.25.61)
version: 1.9.18(zod@3.25.76)
'@modelcontextprotocol/sdk':
specifier: 1.12.0
version: 1.12.0
@ -844,6 +854,9 @@ importers:
isbinaryfile:
specifier: ^5.0.2
version: 5.0.4
json-stream-stringify:
specifier: ^3.1.6
version: 3.1.6
jwt-decode:
specifier: ^4.0.0
version: 4.0.0
@ -867,7 +880,7 @@ importers:
version: 0.5.17
openai:
specifier: ^5.12.2
version: 5.12.2(ws@8.18.3)(zod@3.25.61)
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
os-name:
specifier: ^6.0.0
version: 6.1.0
@ -974,9 +987,15 @@ importers:
specifier: ^2.8.0
version: 2.8.0
zod:
specifier: 3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@ai-sdk/openai-compatible':
specifier: ^1.0.0
version: 1.0.31(zod@3.25.76)
'@openrouter/ai-sdk-provider':
specifier: ^2.0.4
version: 2.1.1(ai@6.0.57(zod@3.25.76))(zod@3.25.76)
'@roo-code/build':
specifier: workspace:^
version: link:../packages/build
@ -1049,6 +1068,9 @@ importers:
'@vscode/vsce':
specifier: 3.3.2
version: 3.3.2
ai:
specifier: ^6.0.0
version: 6.0.57(zod@3.25.76)
esbuild-wasm:
specifier: ^0.25.0
version: 0.25.12
@ -1084,7 +1106,7 @@ importers:
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
zod-to-ts:
specifier: ^1.2.0
version: 1.2.0(typescript@5.8.3)(zod@3.25.61)
version: 1.2.0(typescript@5.8.3)(zod@3.25.76)
webview-ui:
dependencies:
@ -1115,6 +1137,9 @@ importers:
'@radix-ui/react-progress':
specifier: ^1.1.2
version: 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-radio-group':
specifier: ^1.3.8
version: 1.3.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-select':
specifier: ^2.1.6
version: 2.2.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -1211,6 +1236,9 @@ importers:
react:
specifier: ^18.3.1
version: 18.3.1
react-compiler-runtime:
specifier: ^1.0.0
version: 1.0.0(react@18.3.1)
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
@ -1287,8 +1315,8 @@ importers:
specifier: ^0.2.2
version: 0.2.2(@types/react@18.3.23)(react@18.3.1)
zod:
specifier: ^3.25.61
version: 3.25.61
specifier: 3.25.76
version: 3.25.76
devDependencies:
'@roo-code/config-eslint':
specifier: workspace:^
@ -1338,6 +1366,9 @@ importers:
'@vitest/ui':
specifier: ^3.2.3
version: 3.2.4(vitest@3.2.4)
babel-plugin-react-compiler:
specifier: ^1.0.0
version: 1.0.0
identity-obj-proxy:
specifier: ^3.0.0
version: 3.0.0
@ -1356,6 +1387,38 @@ packages:
'@adobe/css-tools@4.4.2':
resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==}
'@ai-sdk/gateway@3.0.25':
resolution: {integrity: sha512-j0AQeA7hOVqwImykQlganf/Euj3uEXf0h3G0O4qKTDpEwE+EZGIPnVimCWht5W91lAetPZSfavDyvfpuPDd2PQ==}
engines: {node: '>=18'}
peerDependencies:
zod: 3.25.76
'@ai-sdk/openai-compatible@1.0.31':
resolution: {integrity: sha512-znBvaVHM0M6yWNerIEy3hR+O8ZK2sPcE7e2cxfb6kYLEX3k//JH5VDnRnajseVofg7LXtTCFFdjsB7WLf1BdeQ==}
engines: {node: '>=18'}
peerDependencies:
zod: 3.25.76
'@ai-sdk/provider-utils@3.0.20':
resolution: {integrity: sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ==}
engines: {node: '>=18'}
peerDependencies:
zod: 3.25.76
'@ai-sdk/provider-utils@4.0.10':
resolution: {integrity: sha512-VeDAiCH+ZK8Xs4hb9Cw7pHlujWNL52RKe8TExOkrw6Ir1AmfajBZTb9XUdKOZO08RwQElIKA8+Ltm+Gqfo8djQ==}
engines: {node: '>=18'}
peerDependencies:
zod: 3.25.76
'@ai-sdk/provider@2.0.1':
resolution: {integrity: sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng==}
engines: {node: '>=18'}
'@ai-sdk/provider@3.0.5':
resolution: {integrity: sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w==}
engines: {node: '>=18'}
'@alcalzone/ansi-tokenize@0.2.3':
resolution: {integrity: sha512-jsElTJ0sQ4wHRz+C45tfect76BwbTbgkgKByOzpCN9xG61N5V6u/glvg1CsNJhq2xJIFpKHSwG3D2wPPuEYOrQ==}
engines: {node: '>=18'}
@ -2374,7 +2437,7 @@ packages:
'@mistralai/mistralai@1.9.18':
resolution: {integrity: sha512-D/vNAGEvWMsg95tzgLTg7pPnW9leOPyH+nh1Os05NwxVPbUykoYgMAwOEX7J46msahWdvZ4NQQuxUXIUV2P6dg==}
peerDependencies:
zod: '>= 3'
zod: 3.25.76
'@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
@ -2573,6 +2636,17 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
'@openrouter/ai-sdk-provider@2.1.1':
resolution: {integrity: sha512-UypPbVnSExxmG/4Zg0usRiit3auvQVrjUXSyEhm0sZ9GQnW/d8p/bKgCk2neh1W5YyRSo7PNQvCrAEBHZnqQkQ==}
engines: {node: '>=18'}
peerDependencies:
ai: ^6.0.0
zod: 3.25.76
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@oxc-resolver/binding-darwin-arm64@11.2.0':
resolution: {integrity: sha512-ruKLkS+Dm/YIJaUhzEB7zPI+jh3EXxu0QnNV8I7t9jf0lpD2VnltuyRbhrbJEkksklZj//xCMyFFsILGjiU2Mg==}
cpu: [arm64]
@ -3074,6 +3148,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-radio-group@1.3.8':
resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
peerDependencies:
'@types/react': ^18.3.23
'@types/react-dom': ^18.3.5
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.10':
resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
peerDependencies:
@ -3087,6 +3174,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.11':
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
peerDependencies:
'@types/react': ^18.3.23
'@types/react-dom': ^18.3.5
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.9':
resolution: {integrity: sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ==}
peerDependencies:
@ -3801,6 +3901,9 @@ packages:
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
@ -4421,6 +4524,10 @@ packages:
resolution: {integrity: sha512-e4kQK9mP8ntpo3dACWirGod/hHv4qO5JMj9a/0a2AZto7b4persj5YP7t1Er372gTtYFTYxNhMx34jRvHooglw==}
engines: {node: '>=16'}
'@vercel/oidc@3.1.0':
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
engines: {node: '>= 20'}
'@vitejs/plugin-react@4.4.1':
resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -4573,6 +4680,12 @@ packages:
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
engines: {node: '>= 8.0.0'}
ai@6.0.57:
resolution: {integrity: sha512-5wYcMQmOaNU71wGv4XX1db3zvn4uLjLbTKIo6cQZPWOJElA0882XI7Eawx6TCd5jbjOvKMIP+KLWbpVomAFT2g==}
engines: {node: '>=18'}
peerDependencies:
zod: 3.25.76
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@ -4747,6 +4860,9 @@ packages:
b4a@1.6.7:
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
babel-plugin-react-compiler@1.0.0:
resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==}
bail@1.0.5:
resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==}
@ -6152,6 +6268,10 @@ packages:
resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==}
engines: {node: '>=18.0.0'}
eventsource-parser@3.0.6:
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
engines: {node: '>=18.0.0'}
eventsource@3.0.7:
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
engines: {node: '>=18.0.0'}
@ -7271,9 +7391,16 @@ packages:
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
json-stream-stringify@3.1.6:
resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==}
engines: {node: '>=7.10.1'}
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
@ -8265,7 +8392,7 @@ packages:
hasBin: true
peerDependencies:
ws: ^8.18.0
zod: ^3.23.8
zod: 3.25.76
peerDependenciesMeta:
ws:
optional: true
@ -8759,6 +8886,11 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-compiler-runtime@1.0.0:
resolution: {integrity: sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental
react-cookie-consent@9.0.0:
resolution: {integrity: sha512-Blyj+m+Zz7SFHYqT18p16EANgnSg2sIyU6Yp3vk83AnOnSW7qnehPkUe4+8+qxztJrNmCH5GP+VHsWzAKVOoZA==}
engines: {node: '>=10'}
@ -10636,25 +10768,19 @@ packages:
zod-to-json-schema@3.24.5:
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
peerDependencies:
zod: ^3.24.1
zod: 3.25.76
zod-to-ts@1.2.0:
resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==}
peerDependencies:
typescript: ^4.9.4 || ^5.0.2
zod: ^3
zod: 3.25.76
zod-validation-error@3.4.1:
resolution: {integrity: sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.24.4
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
zod@3.25.61:
resolution: {integrity: sha512-fzfJgUw78LTNnHujj9re1Ov/JJQkRZZGDMcYqSx7Hp4rPOkKywaFHq0S6GoHeXs0wGNE/sIOutkXgnwzrVOGCQ==}
zod: 3.25.76
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
@ -10684,6 +10810,41 @@ snapshots:
'@adobe/css-tools@4.4.2': {}
'@ai-sdk/gateway@3.0.25(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 3.0.5
'@ai-sdk/provider-utils': 4.0.10(zod@3.25.76)
'@vercel/oidc': 3.1.0
zod: 3.25.76
'@ai-sdk/openai-compatible@1.0.31(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.1
'@ai-sdk/provider-utils': 3.0.20(zod@3.25.76)
zod: 3.25.76
'@ai-sdk/provider-utils@3.0.20(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.1
'@standard-schema/spec': 1.1.0
eventsource-parser: 3.0.6
zod: 3.25.76
'@ai-sdk/provider-utils@4.0.10(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 3.0.5
'@standard-schema/spec': 1.1.0
eventsource-parser: 3.0.6
zod: 3.25.76
'@ai-sdk/provider@2.0.1':
dependencies:
json-schema: 0.4.0
'@ai-sdk/provider@3.0.5':
dependencies:
json-schema: 0.4.0
'@alcalzone/ansi-tokenize@0.2.3':
dependencies:
ansi-styles: 6.2.3
@ -12198,10 +12359,10 @@ snapshots:
dependencies:
exenv-es6: 1.1.1
'@mistralai/mistralai@1.9.18(zod@3.25.61)':
'@mistralai/mistralai@1.9.18(zod@3.25.76)':
dependencies:
zod: 3.25.61
zod-to-json-schema: 3.24.5(zod@3.25.61)
zod: 3.25.76
zod-to-json-schema: 3.24.5(zod@3.25.76)
'@mixmark-io/domino@2.2.0': {}
@ -12369,6 +12530,13 @@ snapshots:
'@open-draft/until@2.1.0': {}
'@openrouter/ai-sdk-provider@2.1.1(ai@6.0.57(zod@3.25.76))(zod@3.25.76)':
dependencies:
ai: 6.0.57(zod@3.25.76)
zod: 3.25.76
'@opentelemetry/api@1.9.0': {}
'@oxc-resolver/binding-darwin-arm64@11.2.0':
optional: true
@ -12862,6 +13030,24 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@ -12879,6 +13065,23 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-roving-focus@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@ -13735,6 +13938,8 @@ snapshots:
'@socket.io/component-emitter@3.1.2': {}
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
'@swc/counter@0.1.3': {}
@ -14392,6 +14597,8 @@ snapshots:
satori: 0.12.2
yoga-wasm-web: 0.3.3
'@vercel/oidc@3.1.0': {}
'@vitejs/plugin-react@4.4.1(vite@6.3.6(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
'@babel/core': 7.27.1
@ -14614,6 +14821,14 @@ snapshots:
dependencies:
humanize-ms: 1.2.1
ai@6.0.57(zod@3.25.76):
dependencies:
'@ai-sdk/gateway': 3.0.25(zod@3.25.76)
'@ai-sdk/provider': 3.0.5
'@ai-sdk/provider-utils': 4.0.10(zod@3.25.76)
'@opentelemetry/api': 1.9.0
zod: 3.25.76
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@ -14838,6 +15053,10 @@ snapshots:
b4a@1.6.7: {}
babel-plugin-react-compiler@1.0.0:
dependencies:
'@babel/types': 7.27.1
bail@1.0.5: {}
bail@2.0.2: {}
@ -15141,7 +15360,7 @@ snapshots:
dependencies:
devtools-protocol: 0.0.1367902
mitt: 3.0.1
zod: 3.23.8
zod: 3.25.76
chromium-bidi@5.1.0(devtools-protocol@0.0.1452169):
dependencies:
@ -15819,9 +16038,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.44.1(@libsql/client@0.15.8)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7):
drizzle-orm@0.44.1(@libsql/client@0.15.8)(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7):
optionalDependencies:
'@libsql/client': 0.15.8
'@opentelemetry/api': 1.9.0
better-sqlite3: 11.10.0
gel: 2.1.0
postgres: 3.4.7
@ -16296,6 +16516,8 @@ snapshots:
eventsource-parser@3.0.2: {}
eventsource-parser@3.0.6: {}
eventsource@3.0.7:
dependencies:
eventsource-parser: 3.0.2
@ -17598,8 +17820,12 @@ snapshots:
json-schema-traverse@0.4.1: {}
json-schema@0.4.0: {}
json-stable-stringify-without-jsonify@1.0.1: {}
json-stream-stringify@3.1.6: {}
json-stringify-safe@5.0.1: {}
json5@2.2.3: {}
@ -18647,20 +18873,20 @@ snapshots:
netmask@2.0.2: {}
next-sitemap@4.2.3(next@15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
next-sitemap@4.2.3(next@15.2.8(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
dependencies:
'@corex/deepmerge': 4.0.43
'@next/env': 13.5.11
fast-glob: 3.3.3
minimist: 1.2.8
next: 15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next: 15.2.8(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next@15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next@15.2.8(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 15.2.8
'@swc/counter': 0.1.3
@ -18680,6 +18906,8 @@ snapshots:
'@next/swc-linux-x64-musl': 15.2.5
'@next/swc-win32-arm64-msvc': 15.2.5
'@next/swc-win32-x64-msvc': 15.2.5
'@opentelemetry/api': 1.9.0
babel-plugin-react-compiler: 1.0.0
sharp: 0.33.5
transitivePeerDependencies:
- '@babel/core'
@ -18852,11 +19080,6 @@ snapshots:
is-inside-container: 1.0.0
is-wsl: 3.1.0
openai@5.12.2(ws@8.18.3)(zod@3.25.61):
optionalDependencies:
ws: 8.18.3
zod: 3.25.61
openai@5.12.2(ws@8.18.3)(zod@3.25.76):
optionalDependencies:
ws: 8.18.3
@ -19396,6 +19619,10 @@ snapshots:
strip-json-comments: 2.0.1
optional: true
react-compiler-runtime@1.0.0(react@18.3.1):
dependencies:
react: 18.3.1
react-cookie-consent@9.0.0(react@18.3.1):
dependencies:
js-cookie: 2.2.1
@ -21692,27 +21919,19 @@ snapshots:
compress-commons: 6.0.2
readable-stream: 4.7.0
zod-to-json-schema@3.24.5(zod@3.25.61):
dependencies:
zod: 3.25.61
zod-to-json-schema@3.24.5(zod@3.25.76):
dependencies:
zod: 3.25.76
zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.61):
zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.76):
dependencies:
typescript: 5.8.3
zod: 3.25.61
zod: 3.25.76
zod-validation-error@3.4.1(zod@3.25.76):
dependencies:
zod: 3.25.76
zod@3.23.8: {}
zod@3.25.61: {}
zod@3.25.76: {}
zustand@5.0.9(@types/react@18.3.23)(react@19.2.3):

BIN
releases/3.42.0-release.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
releases/3.43.0-release.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
releases/3.44.0-release.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
releases/3.45.0-release.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -5,6 +5,9 @@ const readline = require("readline")
// detect "yes" flags
const autoYes = process.argv.includes("-y")
// detect nightly flag
const isNightly = process.argv.includes("--nightly")
// detect editor command from args or default to "code"
const editorArg = process.argv.find((arg) => arg.startsWith("--editor="))
const defaultEditor = editorArg ? editorArg.split("=")[1] : "code"
@ -24,14 +27,29 @@ const askQuestion = (question) => {
async function main() {
try {
const packageJson = JSON.parse(fs.readFileSync("./src/package.json", "utf-8"))
const name = packageJson.name
const version = packageJson.version
const vsixFileName = `./bin/${name}-${version}.vsix`
const publisher = packageJson.publisher
const extensionId = `${publisher}.${name}`
let name, version, publisher
console.log("\n🚀 Roo Code VSIX Installer")
if (isNightly) {
// For nightly, read the nightly-specific package.json and get publisher from src
const nightlyPackageJson = JSON.parse(
fs.readFileSync("./apps/vscode-nightly/package.nightly.json", "utf-8"),
)
const srcPackageJson = JSON.parse(fs.readFileSync("./src/package.json", "utf-8"))
name = nightlyPackageJson.name
version = nightlyPackageJson.version
publisher = srcPackageJson.publisher
} else {
const packageJson = JSON.parse(fs.readFileSync("./src/package.json", "utf-8"))
name = packageJson.name
version = packageJson.version
publisher = packageJson.publisher
}
const vsixFileName = `./bin/${name}-${version}.vsix`
const extensionId = `${publisher}.${name}`
const buildType = isNightly ? "Nightly" : "Regular"
console.log(`\n🚀 Roo Code VSIX Installer (${buildType})`)
console.log("========================")
console.log("\nThis script will:")
console.log("1. Uninstall any existing version of the Roo Code extension")

Some files were not shown because too many files have changed in this diff Show more