diff --git a/.clinerules b/.clinerules deleted file mode 100644 index 7cc6942a3f2..00000000000 --- a/.clinerules +++ /dev/null @@ -1,17 +0,0 @@ -# Code Quality Rules - -1. Test Coverage: - - Before attempting completion, always make sure that any code changes have test coverage - - Ensure all tests pass before submitting changes - -2. Lint Rules: - - Never disable any lint rules without explicit user approval - -3. Styling Guidelines: - - Use Tailwind CSS classes instead of inline style objects for new markup - - VSCode CSS variables must be added to webview-ui/src/index.css before using them in Tailwind classes - - Example: `
` instead of style objects - -# Adding a New Setting - -To add a new setting that persists its state, follow the steps in cline_docs/settings.md diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 8c316739202..00000000000 --- a/.dockerignore +++ /dev/null @@ -1,45 +0,0 @@ -# Version control -# .git/ -# .gitignore -# .gitattributes -# .git-blame-ignore-revs -# .gitconfig - -# Build artifacts -bin/ -dist/ -**/dist/ -out/ -**/out/ - -# Dependencies -node_modules/ -**/node_modules/ - -# Test and development files -coverage/ -**/.vscode-test/ - -# Configuration files -# .env* -knip.json -.husky/ - -# CI/CD -# .changeset/ -# .github/ -# ellipsis.yaml - -# OS specific -.DS_Store - -# Logs -logs/ -*.log - -# Nix -# flake.lock -# flake.nix - -# Monorepo -benchmark/exercises/ diff --git a/.eslintrc.json b/.eslintrc.json index e967b58a03f..aaf8f90e772 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -15,6 +15,8 @@ } ], "@typescript-eslint/semi": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "off", "eqeqeq": "warn", "no-throw-literal": "warn", "semi": "off" diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dc66b4f390b..5757f05ab1e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,68 +1,80 @@ name: Bug Report -description: File a bug report +description: Clearly report a bug with detailed repro steps labels: ["bug"] body: - - type: input - id: version - attributes: - label: Which version of the app are you using? - description: Please specify the app version you're using (e.g. v3.3.1) - validations: - required: true - - type: dropdown - id: provider - attributes: - label: Which API Provider are you using? - multiple: false - options: - - OpenRouter - - Anthropic - - Google Gemini - - DeepSeek - - OpenAI - - OpenAI Compatible - - GCP Vertex AI - - AWS Bedrock - - Glama - - VS Code LM API - - LM Studio - - Ollama - validations: - required: true - - type: input - id: model - attributes: - label: Which Model are you using? - description: Please specify the model you're using (e.g. Claude 3.7 Sonnet) - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - validations: - required: true - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: How do you trigger this bug? Please walk us through it step by step. - value: | - 1. - 2. - 3. - validations: - required: true - - type: textarea - id: logs - attributes: - label: Relevant API REQUEST output - description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks. - render: shell - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context about the problem here, such as screenshots or related issues. + - type: input + id: version + attributes: + label: App Version + description: Specify exactly which version you're using (e.g., v3.3.1) + validations: + required: true + + - type: dropdown + id: provider + attributes: + label: API Provider + description: Choose the API provider involved + multiple: false + options: + - OpenRouter + - Anthropic + - Google Gemini + - DeepSeek + - OpenAI + - OpenAI Compatible + - GCP Vertex AI + - Amazon Bedrock + - Requesty + - Glama + - VS Code LM API + - LM Studio + - Ollama + validations: + required: true + + - type: input + id: model + attributes: + label: Model Used + description: Clearly specify the exact model (e.g., Claude 3.7 Sonnet) + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: Actual vs. Expected Behavior + description: Clearly state what actually happened and what you expected instead. + placeholder: Provide precise details of the issue here. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Detailed Steps to Reproduce + description: | + List the exact steps someone must follow to reproduce this bug: + 1. Starting conditions (software state, settings, environment) + 2. Precise actions taken (every click, selection, input) + 3. Clearly observe and report outcomes + value: | + 1. + 2. + 3. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant API Request Output + description: Paste relevant API logs or outputs here (formatted automatically as code) + render: shell + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Include extra details, screenshots, or related issues. diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index 14f6d638038..7e027d0fc9a 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -62,7 +62,10 @@ jobs: run: npm run knip test-extension: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] steps: - name: Checkout code uses: actions/checkout@v4 @@ -73,11 +76,16 @@ jobs: cache: 'npm' - name: Install dependencies run: npm run install:all + - name: Compile (to build and copy WASM files) + run: npm run compile - name: Run unit tests run: npx jest --silent test-webview: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index de0a6152632..12777329698 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.pnpm-store dist out out-* @@ -31,7 +32,6 @@ docs/_site/ #Logging logs -.aider* -.env -# aider -.aider* + +# Vite development +.vite-port diff --git a/.husky/pre-commit b/.husky/pre-commit index d6393597652..b45f0f7bb3b 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -12,4 +12,12 @@ else npx_cmd="npx" fi -"$npx_cmd" lint-staged \ No newline at end of file +npm run generate-types + +if [ -n "$(git diff --name-only src/exports/roo-code.d.ts)" ]; then + echo "Error: There are unstaged changes to roo-code.d.ts after running 'npm run generate-types'." + echo "Please review and stage the changes before committing." + exit 1 +fi + +"$npx_cmd" lint-staged diff --git a/.roo/rules-translate/001-general-rules.md b/.roo/rules-translate/001-general-rules.md new file mode 100644 index 00000000000..61d232bbf70 --- /dev/null +++ b/.roo/rules-translate/001-general-rules.md @@ -0,0 +1,104 @@ +# 1. SUPPORTED LANGUAGES AND LOCATION + +- Localize all strings into the following locale files: ca, de, en, es, fr, hi, it, ja, ko, pl, pt-BR, ru, tr, vi, zh-CN, zh-TW +- The VSCode extension has two main areas that require localization: + - Core Extension: src/i18n/locales/ (extension backend) + - WebView UI: webview-ui/src/i18n/locales/ (user interface) + +# 2. VOICE, STYLE AND TONE + +- Always use informal speech (e.g., "du" instead of "Sie" in German) for all translations +- Maintain a direct and concise style that mirrors the tone of the original text +- Carefully account for colloquialisms and idiomatic expressions in both source and target languages +- Aim for culturally relevant and meaningful translations rather than literal translations +- Preserve the personality and voice of the original content +- Use natural-sounding language that feels native to speakers of the target language +- Don't translate the word "token" as it means something specific in English that all languages will understand +- Don't translate domain-specific words (especially technical terms like "Prompt") that are commonly used in English in the target language + +# 3. CORE EXTENSION LOCALIZATION (src/) + +- Located in src/i18n/locales/ +- NOT ALL strings in core source need internationalization - only user-facing messages +- Internal error messages, debugging logs, and developer-facing messages should remain in English +- The t() function is used with namespaces like 'core:errors.missingToolParameter' +- Be careful when modifying interpolation variables; they must remain consistent across all translations +- Some strings in formatResponse.ts are intentionally not internationalized since they're internal +- When updating strings in core.json, maintain all existing interpolation variables +- Check string usages in the codebase before making changes to ensure you're not breaking functionality + +# 4. WEBVIEW UI LOCALIZATION (webview-ui/src/) + +- Located in webview-ui/src/i18n/locales/ +- Uses standard React i18next patterns with the useTranslation hook +- All user interface strings should be internationalized +- Always use the Trans component with named components for text with embedded components + + example: + +`"changeSettings": "You can always change this at the bottom of the settings",` + +``` + + }} + /> +``` + +# 5. TECHNICAL IMPLEMENTATION + +- Use namespaces to organize translations logically +- Handle pluralization using i18next's built-in capabilities +- Implement proper interpolation for variables using {{variable}} syntax +- Don't include defaultValue. The `en` translations are the fallback +- Always use apply_diff instead of write_to_file when editing existing translation files (much faster and more reliable) +- When using apply_diff, carefully identify the exact JSON structure to edit to avoid syntax errors +- Placeholders (like {{variable}}) must remain exactly identical to the English source to maintain code integration and prevent syntax errors + +# 6. WORKFLOW AND APPROACH + +- First add or modify English strings, then ask for confirmation before translating to all other languages +- Use this process for each localization task: + 1. Identify where the string appears in the UI/codebase + 2. Understand the context and purpose of the string + 3. Update English translation first + 4. Create appropriate translations for all other supported languages + 5. Validate your changes with the missing translations script +- Flag or comment if an English source string is incomplete ("please see this...") to avoid truncated or unclear translations +- For UI elements, distinguish between: + - Button labels: Use short imperative commands ("Save", "Cancel") + - Tooltip text: Can be slightly more descriptive +- Preserve the original perspective: If text is a user command directed at the software, ensure the translation maintains this direction, avoiding language that makes it sound like an instruction from the system to the user + +# 7. COMMON PITFALLS TO AVOID + +- Switching between formal and informal addressing styles - always stay informal ("du" not "Sie") +- Translating or altering technical terms and brand names that should remain in English +- Modifying or removing placeholders like {{variable}} - these must remain identical +- Translating domain-specific terms that are commonly used in English in the target language +- Changing the meaning or nuance of instructions or error messages +- Forgetting to maintain consistent terminology throughout the translation + +# 8. QUALITY ASSURANCE + +- Maintain consistent terminology across all translations +- Respect the JSON structure of translation files +- Watch for placeholders and preserve them in translations +- Be mindful of text length in UI elements when translating to languages that might require more characters +- Use context-aware translations when the same string has different meanings +- Always validate your translation work by running the missing translations script: + ``` + node scripts/find-missing-translations.js + ``` +- Address any missing translations identified by the script to ensure complete coverage across all locales + +# 9. TRANSLATOR'S CHECKLIST + +- ✓ Used informal tone consistently ("du" not "Sie") +- ✓ Preserved all placeholders exactly as in the English source +- ✓ Maintained consistent terminology with existing translations +- ✓ Kept technical terms and brand names unchanged where appropriate +- ✓ Preserved the original perspective (user→system vs system→user) +- ✓ Adapted the text appropriately for UI context (buttons vs tooltips) diff --git a/.roo/rules-translate/instructions-de.md b/.roo/rules-translate/instructions-de.md new file mode 100644 index 00000000000..1268424832c --- /dev/null +++ b/.roo/rules-translate/instructions-de.md @@ -0,0 +1,14 @@ +# German (de) Translation Guidelines + +**Key Rule:** Always use informal speech ("du" form) in all German translations without exception. + +## Quick Reference + +| Category | Formal (Avoid) | Informal (Use) | Example | +| ----------- | ------------------------- | ------------------- | ----------------- | +| Pronouns | Sie | du | you | +| Possessives | Ihr/Ihre/Ihrem | dein/deine/deinem | your | +| Verbs | können Sie, müssen Sie | kannst du, musst du | you can, you must | +| Imperatives | Geben Sie ein, Wählen Sie | Gib ein, Wähle | Enter, Choose | + +**Technical terms** like "API", "token", "prompt" should not be translated. diff --git a/.roo/rules-translate/instructions-zh-cn.md b/.roo/rules-translate/instructions-zh-cn.md new file mode 100644 index 00000000000..241ae338dc1 --- /dev/null +++ b/.roo/rules-translate/instructions-zh-cn.md @@ -0,0 +1,278 @@ +# Simplified Chinese (zh-CN) Translation Guidelines + +## Key Terminology + +| English Term | Preferred (zh-CN) | Avoid | Context/Notes | +| --------------------- | ----------------- | ------------ | ------------- | +| API Cost | API 费用 | API 成本 | 财务相关术语 | +| Tokens | Token | Tokens/令牌 | 保留抽象术语 | +| Token Usage | Token 使用量 | Token 用量 | 技术计量单位 | +| Cache | 缓存 | 高速缓存 | 简洁优先 | +| Context | 上下文 | | 保留抽象术语 | +| Context Menu | 右键菜单 | 上下文菜单 | 技术术语准确 | +| Context Window | 上下文窗口 | | 技术术语准确 | +| Proceed While Running | 强制继续 | 运行时继续 | 操作命令 | +| Enhance Prompt | 增强提示词 | 优化提示 | AI相关功能 | +| Auto-approve | 自动批准 | 始终批准 | 权限相关术语 | +| Checkpoint | 存档点 | 检查点/快照 | 技术概念统一 | +| MCP Server | MCP 服务 | MCP 服务器 | 技术组件 | +| Human Relay | 人工辅助模式 | 人工中继 | 功能描述清晰 | +| Network Timeout | 请求超时 | 网络超时 | 更准确描述 | +| Terminal | 终端 | 命令行 | 技术术语统一 | +| diff | 差异更新 | 差分/补丁 | 代码变更 | +| prompt caching | 提示词缓存 | 提示缓存 | AI功能 | +| computer use | 计算机交互 | 计算机使用 | 技术能力 | +| rate limit | API 请求频率限制 | 速率限制 | API控制 | +| Browser Session | 浏览器会话 | 浏览器进程 | 技术概念 | +| Run Command | 运行命令 | 执行命令 | 操作动词 | +| power steering mode | 增强导向模式 | 动力转向模式 | 避免直译 | +| Boomerang Tasks | 任务拆分 | 回旋镖任务 | 避免直译 | + +## Formatting Rules + +1. **中英文混排** + + - 添加空格:在中文和英文/数字之间添加空格,如"API 费用"(不是"API费用") + - 单位格式:时间单位统一为"15秒"、"1分钟"(不是"15 seconds"、"1 minute") + - 数字范围:"已使用: {{used}} / {{total}}" + - 技术符号保留原样:"{{amount}} tokens"→"{{amount}}" + +2. **标点符号** + + - 使用中文全角标点 + - 列表项使用中文顿号:"创建、编辑文件" + +3. **UI文本优化** + + - 按钮文本:使用简洁动词,如"展开"优于"查看更多" + - 操作说明:使用步骤式说明(1. 2. 3.)替代长段落 + - 错误提示:使用"确认删除?此操作不可逆"替代"Are you sure...?" + - 操作说明要简洁:"Shift+拖拽文件"优于长描述 + - 按钮文本控制在2-4个汉字:"展开"优于"查看更多" + +4. **技术描述** + + - 保留英文缩写:如"MCP"不翻译 + - 统一术语:整个系统中相同概念使用相同译法 + - 长句拆分为短句 + - 被动语态转为主动语态 + - 功能名称统一:"计算机交互"优于"计算机使用" + - 参数说明:"差异更新"优于"差分/补丁" + +5. **变量占位符** + - 保持原格式:`{{variable}}` + - 中文说明放在变量外:"Token 使用量: {{used}}" + +## UI Element Translation Standards + +1. **按钮(Buttons)** + + - 确认类:确定/取消/应用/保存 + - 操作类:添加/删除/编辑/导出 + - 状态类:启用/禁用/展开/收起 + - 长度限制:2-4个汉字 + +2. **菜单(Menus)** + + - 主菜单:文件/编辑/视图/帮助 + - 子菜单:使用">"连接,如"文件>打开" + - 快捷键:保留英文,如"Ctrl+S" + +3. **标签(Labels)** + + - 设置项:描述功能,如"自动保存间隔" + - 状态提示:简洁明确,如"正在处理..." + - 单位说明:放在括号内,如"超时时间(秒)" + +4. **工具提示(Tooltips)** + + - 功能说明:简洁描述,如"复制选中内容" + - 操作指引:步骤明确,如"双击编辑单元格" + - 长度限制:不超过50个汉字 + +5. **对话框(Dialogs)** + - 标题:说明对话框用途 + - 正文:分段落说明 + - 按钮:使用动词,如"确认删除" + +## Contextual Translation Principles + +1. **根据UI位置调整** + + - 按钮文本:简洁动词 (如"展开", "收起") + - 设置项:描述性 (如"自动批准写入操作") + - 帮助文本:完整说明 (如"开启后自动创建任务存档点,方便回溯修改") + +2. **技术文档风格** + + - 使用主动语态:如"自动创建和编辑文件" + - 避免口语化表达 + - 复杂功能使用分点说明 + - 说明操作结果:如"无需二次确认" + - 参数说明清晰:如"延迟一段时间再自动批准写入" + +3. **品牌/产品名称** + + - 保留英文品牌名 + - 技术术语保持一致性 + - 保留英文专有名词:如"AWS Bedrock ARN" + +4. **用户操作** + - 操作动词统一: + - "Click"→"点击" + - "Type"→"输入" + - "Scroll"→"滚动" + - 按钮状态: + - "Enabled"→"已启用" + - "Disabled"→"已禁用" + +## Technical Documentation Guidelines + +1. **技术术语** + + - 统一使用"Token"而非"令牌" + - 保留英文专有名词:如"Model Context Protocol" + - 功能名称统一:如"计算机功能调用"优于"计算机使用" + +2. **API文档** + + - 端点(Endpoint):保留原始路径 + - 参数说明:表格形式展示 + - 示例:保留代码格式 + - 参数标签: + - 单位明确:如"最大输出 Token 数" + - 范围说明完整:如"模型可以处理的总 Token 数" + +3. **代码相关翻译** + + - 代码注释: + - 保留技术术语:如"// Initialize MCP client" + - 简短说明:如"检查文件是否存在" + - 错误信息: + - 包含错误代码:如"Error 404: 文件未找到" + - 提供解决方案:如"请检查文件权限" + - 命令行: + - 保留原生命令:如"git commit -m 'message'" + - 参数说明:如"-v: 显示详细输出" + +4. **配置指南** + - 设置项命名:如"Enable prompt caching"→"启用提示词缓存" + - 价格描述: + - 单位统一:如"每百万 Token 的成本" + - 说明影响:如"这会影响生成内容和补全的成本" + - 操作说明: + - 使用编号步骤:如"1. 注册Google Cloud账号" + - 步骤动词一致:如"安装配置Google Cloud CLI工具" + +## Common Patterns + +```markdown +<<<<<<< BEFORE +"dragFiles": "按住shift拖动文件" +======= +"dragFiles": "Shift+拖拽文件" + +> > > > > > > AFTER + +<<<<<<< BEFORE +"description": "启用后,Roo 将能够与 MCP 服务器交互以获取高级功能。" +======= +"description": "启用后 Roo 可与 MCP 服务交互获取高级功能。" + +> > > > > > > AFTER + +<<<<<<< BEFORE +"cannotUndo": "此操作无法撤消。" +======= +"cannotUndo": "此操作不可逆。" + +> > > > > > > AFTER + +<<<<<<< BEFORE +"hold shift to drag in files" → "按住shift拖动文件" +======= +"hold shift to drag in files" → "Shift+拖拽文件" + +> > > > > > > AFTER + +<<<<<<< BEFORE +"Double click to edit" → "双击进行编辑" +======= +"Double click to edit" → "双击编辑" + +> > > > > > > AFTER +``` + +## Common Pitfalls + +1. 避免过度直译导致生硬 + + - ✗ "Do more with Boomerang Tasks" → "使用回旋镖任务完成更多工作" + - ✓ "Do more with Boomerang Tasks" → "允许任务拆分" + +2. 保持功能描述准确 + + - ✗ "Enhance prompt with additional context" → "使用附加上下文增强提示" + - ✓ "Enhance prompt with additional context" → "增强提示词" + +3. 操作指引清晰 + + - ✗ "hold shift to drag in files" → "按住shift拖动文件" + - ✓ "hold shift to drag in files" → "Shift+拖拽文件" + +4. 确保术语一致性 + + - ✗ 同一文档中混用"Token"/"令牌"/"代币" + - ✓ 统一使用"Token"作为技术术语 + +5. 注意文化适应性 + + - ✗ "Kill the process" → "杀死进程"(过于暴力) + - ✓ "Kill the process" → "终止进程" + +6. 技术文档特殊处理 + - 代码示例中的注释: + ✗ 翻译后破坏代码结构 + ✓ 保持代码注释原样或仅翻译说明部分 + - 命令行参数: + ✗ 翻译参数名称导致无法使用 + ✓ 保持参数名称英文,仅翻译说明 + +## Best Practices + +1. **翻译工作流程** + + - 通读全文理解上下文 + - 标记并统一技术术语 + - 分段翻译并检查一致性 + - 最终整体审校 + +2. **质量检查要点** + + - 术语一致性 + - 功能描述准确性 + - UI元素长度适配性 + - 文化适应性 + +3. **工具使用建议** + + - 建立项目术语库 + - 使用翻译记忆工具 + - 维护风格指南 + - 定期更新翻译资源 + +4. **审校流程** + - 初翻 → 技术审校 → 语言润色 → 最终确认 + - 重点关注技术准确性、语言流畅度和UI显示效果 + +## Quality Checklist + +1. 术语是否全文一致? +2. 是否符合中文技术文档习惯? +3. UI控件文本是否简洁明确? +4. 长句是否已合理拆分? +5. 变量占位符是否保留原格式? +6. 技术描述是否准确无误? +7. 文化表达是否恰当? +8. 是否保持了原文的精确含义? +9. 特殊格式(如变量、代码)是否正确保留? diff --git a/.roo/rules-translate/instructions-zh-tw.md b/.roo/rules-translate/instructions-zh-tw.md new file mode 100644 index 00000000000..ee4d07a07a4 --- /dev/null +++ b/.roo/rules-translate/instructions-zh-tw.md @@ -0,0 +1,18 @@ +# Traditional Chinese (zh-TW) Translation Guidelines + +## Key Terminology + +| English Term | Use (zh-TW) | Avoid (Mainland) | +| ------------- | ----------- | ---------------- | +| file | 檔案 | 文件 | +| task | 工作 | 任務 | +| project | 專案 | 項目 | +| configuration | 設定 | 配置 | +| server | 伺服器 | 服務器 | +| import/export | 匯入/匯出 | 導入/導出 | + +## Formatting Rules + +- Add spaces between Chinese and English/numbers: "AI 驅動" (not "AI驅動") +- Use Traditional Chinese quotation marks: 「範例文字」(not "範例文字") +- Use Taiwanese computing conventions rather than mainland terminology diff --git a/.roo/rules/rules.md b/.roo/rules/rules.md new file mode 100644 index 00000000000..4351fc4b81f --- /dev/null +++ b/.roo/rules/rules.md @@ -0,0 +1,19 @@ +# Code Quality Rules + +1. Test Coverage: + + - Before attempting completion, always make sure that any code changes have test coverage + - Ensure all tests pass before submitting changes + +2. Lint Rules: + + - Never disable any lint rules without explicit user approval + +3. Styling Guidelines: + - Use Tailwind CSS classes instead of inline style objects for new markup + - VSCode CSS variables must be added to webview-ui/src/index.css before using them in Tailwind classes + - Example: `
` instead of style objects + +# Adding a New Setting + +To add a new setting that persists its state, follow the steps in cline_docs/settings.md diff --git a/.roomodes b/.roomodes index 9d1719fa31c..171c0fcc71c 100644 --- a/.roomodes +++ b/.roomodes @@ -22,7 +22,6 @@ "slug": "translate", "name": "Translate", "roleDefinition": "You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.", - "customInstructions": "# 1. SUPPORTED LANGUAGES AND LOCATION\n- Localize all strings into the following locale files: ca, de, en, es, fr, hi, it, ja, ko, pl, pt-BR, tr, vi, zh-CN, zh-TW\n- The VSCode extension has two main areas that require localization:\n * Core Extension: src/i18n/locales/ (extension backend)\n * WebView UI: webview-ui/src/i18n/locales/ (user interface)\n\n# 2. VOICE, STYLE AND TONE\n- Always use informal speech (e.g., \"du\" instead of \"Sie\" in German) for all translations\n- Maintain a direct and concise style that mirrors the tone of the original text\n- Carefully account for colloquialisms and idiomatic expressions in both source and target languages\n- Aim for culturally relevant and meaningful translations rather than literal translations\n- Preserve the personality and voice of the original content\n- Use natural-sounding language that feels native to speakers of the target language\n- Don't translate the word \"token\" as it means something specific in English that all languages will understand\n- Don't translate domain-specific words (especially technical terms like \"Prompt\") that are commonly used in English in the target language\n\n# 3. CORE EXTENSION LOCALIZATION (src/)\n- Located in src/i18n/locales/\n- NOT ALL strings in core source need internationalization - only user-facing messages\n- Internal error messages, debugging logs, and developer-facing messages should remain in English\n- The t() function is used with namespaces like 'core:errors.missingToolParameter'\n- Be careful when modifying interpolation variables; they must remain consistent across all translations\n- Some strings in formatResponse.ts are intentionally not internationalized since they're internal\n- When updating strings in core.json, maintain all existing interpolation variables\n- Check string usages in the codebase before making changes to ensure you're not breaking functionality\n\n# 4. WEBVIEW UI LOCALIZATION (webview-ui/src/)\n- Located in webview-ui/src/i18n/locales/\n- Uses standard React i18next patterns with the useTranslation hook\n- All user interface strings should be internationalized\n- Always use the Trans component with named components for text with embedded components\n\n example:\n\n`\"changeSettings\": \"You can always change this at the bottom of the settings\",`\n\n```\n \n }}\n />\n```\n\n# 5. TECHNICAL IMPLEMENTATION\n- Use namespaces to organize translations logically\n- Handle pluralization using i18next's built-in capabilities\n- Implement proper interpolation for variables using {{variable}} syntax\n- Don't include defaultValue. The `en` translations are the fallback\n- Always use apply_diff instead of write_to_file when editing existing translation files (much faster and more reliable)\n- When using apply_diff, carefully identify the exact JSON structure to edit to avoid syntax errors\n- Placeholders (like {{variable}}) must remain exactly identical to the English source to maintain code integration and prevent syntax errors\n\n# 6. WORKFLOW AND APPROACH\n- First add or modify English strings, then ask for confirmation before translating to all other languages\n- Use this process for each localization task:\n 1. Identify where the string appears in the UI/codebase\n 2. Understand the context and purpose of the string\n 3. Update English translation first\n 4. Create appropriate translations for all other supported languages\n 5. Validate your changes with the missing translations script\n- Flag or comment if an English source string is incomplete (\"please see this...\") to avoid truncated or unclear translations\n- For UI elements, distinguish between:\n * Button labels: Use short imperative commands (\"Save\", \"Cancel\")\n * Tooltip text: Can be slightly more descriptive\n- Preserve the original perspective: If text is a user command directed at the software, ensure the translation maintains this direction, avoiding language that makes it sound like an instruction from the system to the user\n\n# 7. COMMON PITFALLS TO AVOID\n- Switching between formal and informal addressing styles - always stay informal (\"du\" not \"Sie\")\n- Translating or altering technical terms and brand names that should remain in English\n- Modifying or removing placeholders like {{variable}} - these must remain identical\n- Translating domain-specific terms that are commonly used in English in the target language\n- Changing the meaning or nuance of instructions or error messages\n- Forgetting to maintain consistent terminology throughout the translation\n\n# 8. QUALITY ASSURANCE\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Watch for placeholders and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- Use context-aware translations when the same string has different meanings\n- Always validate your translation work by running the missing translations script:\n ```\n node scripts/find-missing-translations.js\n ```\n- Address any missing translations identified by the script to ensure complete coverage across all locales\n\n# 9. TRANSLATOR'S CHECKLIST\n- ✓ Used informal tone consistently (\"du\" not \"Sie\")\n- ✓ Preserved all placeholders exactly as in the English source\n- ✓ Maintained consistent terminology with existing translations\n- ✓ Kept technical terms and brand names unchanged where appropriate\n- ✓ Preserved the original perspective (user→system vs system→user)\n- ✓ Adapted the text appropriately for UI context (buttons vs tooltips)", "groups": [ "read", "command", diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000000..e8fc3f8ea0e --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 20.18.1 diff --git a/.vscodeignore b/.vscodeignore index 560897b9677..53fd3798c01 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,36 +1,45 @@ # Default +.changeset/** .github/** .husky/** .vscode/** -.vscode-test/** -out/** -out-integration/** -benchmark/** -e2e/** +coverage/** node_modules/** src/** +scripts/** .gitignore -.yarnrc esbuild.js -vsc-extension-quickstart.md +jest.* **/tsconfig.json **/.eslintrc.json +.prettierignore **/*.map **/*.ts -**/.vscode-test.* +**/.gitignore # Custom -demo.gif -.nvmrc +.env.sample +.git-blame-ignore-revs +.gitconfig .gitattributes -.prettierignore +.tool-versions +.vite-port +.nvmrc .clinerules* .roomodes +.rooignore +.roo/** +benchmark/** cline_docs/** -coverage/** +e2e/** +evals/** locales/** +out/** +ellipsis.yaml +knip.json -# Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore) +# Ignore all webview-ui files except the build directory. +# https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore webview-ui/src/** webview-ui/public/** webview-ui/scripts/** @@ -39,15 +48,20 @@ webview-ui/README.md webview-ui/package.json webview-ui/package-lock.json webview-ui/node_modules/** -**/.gitignore -# Fix issue where codicons don't get packaged (https://github.com/microsoft/vscode-extension-samples/issues/692) +# Include codicons !node_modules/@vscode/codicons/dist/codicon.css !node_modules/@vscode/codicons/dist/codicon.ttf +# Include material icons +!node_modules/vscode-material-icons/generated/** + # Include default themes JSON files used in getTheme !src/integrations/theme/default-themes/** +# Ignore doc assets +assets/docs/** + # Include icons and images !assets/icons/** !assets/images/** diff --git a/CHANGELOG.md b/CHANGELOG.md index e99bdb354f3..a489c618ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,310 @@ # Roo Code Changelog +## [3.14.3] - 2025-04-25 + +- Add Boomerang Orchestrator as a built-in mode +- Improve home screen UI +- Make token count estimation more efficient to reduce gray screens +- Revert change to automatically close files after edit until we figure out how to make it work well with diagnostics +- Clean up settings data model +- Omit reasoning params for non-reasoning models +- Clearer documentation for adding settings (thanks @shariqriazz!) +- Fix word wrapping in Roo message title (thanks @zhangtony239!) +- Update default model id for Unbound from claude 3.5 to 3.7 (thanks @pugazhendhi-m!) + +## [3.14.2] - 2025-04-24 + +- Enable prompt caching for Gemini (with some improvements) +- Allow users to turn prompt caching on / off for Gemini 2.5 on OpenRouter +- Compress terminal output with backspace characters (thanks @KJ7LNW) +- Add Russian language (Спасибо @asychin) + +## [3.14.1] - 2025-04-24 + +- Disable Gemini caching while we investigate issues reported by the community. + +## [3.14.0] - 2025-04-23 + +- Add prompt caching for `gemini-2.5-pro-preview-03-25` in the Gemini provider (Vertex and OpenRouter coming soon!) +- Improve the search_and_replace and insert_content tools and bring them out of experimental, and deprecate append_to_file (thanks @samhvw8!) +- Use material icons for files and folders in mentions (thanks @elianiva!) +- Make the list_files tool more efficient and smarter about excluding directories like .git/ +- Fix file drag and drop on Windows and when using SSH tunnels (thanks @NyxJae!) +- Correctly revert changes and suggest alternative tools when write_to_file fails on a missing line count +- Allow interpolation of `workspace`, `mode`, `language`, `shell`, and `operatingSystem` into custom system prompt overrides (thanks @daniel-lxs!) +- Fix interpolation bug in the “add to context” code action (thanks @elianiva!) +- Preserve editor state and prevent tab unpinning during diffs (thanks @seedlord!) +- Improvements to icon rendering on Linux (thanks @elianiva!) +- Improvements to Requesty model list fetching (thanks @dtrugman!) +- Fix user feedback not being added to conversation history in API error state, redundant ‘TASK RESUMPTION’ prompts, and error messages not showing after cancelling API requests (thanks @System233!) +- Track tool use errors in evals +- Fix MCP hub error when dragging extension to another sidebar +- Improve display of long MCP tool arguments +- Fix redundant ‘TASK RESUMPTION’ prompts (thanks @System233!) +- Fix bug opening files when editor has no workspace root +- Make the VS Code LM provider show the correct model information (thanks @QuinsZouls!) +- Fixes to make the focusInput command more reliable (thanks @hongzio!) +- Better handling of aftercursor content in context mentions (thanks @elianiva!) +- Support injecting environment variables in MCP config (thanks @NamesMT!) +- Better handling of FakeAI “controller” object (thanks @wkordalski) +- Remove unnecessary calculation from VS Code LM provider (thanks @d-oit!) +- Allow Amazon Bedrock Marketplace ARNs (thanks @mlopezr!) +- Give better loading feedback on chat rows (thanks @elianiva!) +- Performance improvements to task size calculations +- Don’t immediately show a model ID error when changing API providers +- Fix apply_diff edge cases +- Use a more sensible task export icon +- Use path aliases in webview source files +- Display a warning when the system prompt is overridden +- Better progress indicator for apply_diff tools (thanks @qdaxb!) +- Fix terminal carriage return handling for correct progress bar display (thanks @Yikai-Liao!) + +## [3.13.2] - 2025-04-18 + +- Allow custom URLs for Gemini provider + +## [3.13.1] - 2025-04-18 + +- Support Gemini 2.5 Flash thinking mode (thanks @monotykamary) +- Make auto-approval toggle on/off states more obvious (thanks @sachasayan) +- Add telemetry for shell integration errors +- Fix the path of files dragging into the chat textarea on Windows (thanks @NyxJae) + +## [3.13.0] - 2025-04-17 + +- UI improvements to task header, chat view, history preview, and welcome view (thanks @sachasayan!) +- Add append_to_file tool for appending content to files (thanks @samhvw8!) +- Add Gemini 2.5 Flash Preview to Gemini and Vertex providers (thanks @nbihan-mediware!) +- Fix image support in Bedrock (thanks @Smartsheet-JB-Brown!) +- Make diff edits more resilient to models passing in incorrect parameters + +## [3.12.3] - 2025-04-17 + +- Fix character escaping issues in Gemini diff edits +- Support dragging and dropping tabs into the chat box (thanks @NyxJae!) +- Make sure slash commands only fire at the beginning of the chat box (thanks @logosstone!) + +## [3.12.2] - 2025-04-16 + +- Add OpenAI o3 & 4o-mini (thanks @PeterDaveHello!) +- Improve file/folder context mention UI (thanks @elianiva!) +- Improve diff error telemetry + +## [3.12.1] - 2025-04-16 + +- Bugfix to Edit button visibility in the select dropdowns + +## [3.12.0] - 2025-04-15 + +- Add xAI provider and expose reasoning effort options for Grok on OpenRouter (thanks Cline!) +- Make diff editing config per-profile and improve pre-diff string normalization +- Make checkpoints faster and more reliable +- Add a search bar to mode and profile select dropdowns (thanks @samhvw8!) +- Add telemetry for code action usage, prompt enhancement usage, and consecutive mistake errors +- Suppress zero cost values in the task header (thanks @do-it!) +- Make JSON parsing safer to avoid crashing the webview on bad input +- Allow users to bind a keyboard shortcut for accepting suggestions or input in the chat view (thanks @axkirillov!) + +## [3.11.17] - 2025-04-14 + +- Improvements to OpenAI cache reporting and cost estimates (thanks @monotykamary and Cline!) +- Visual improvements to the auto-approve toggles (thanks @sachasayan!) +- Bugfix to diff apply logic (thanks @avtc for the test case!) and telemetry to track errors going forward +- Fix race condition in capturing short-running terminal commands (thanks @KJ7LNW!) +- Fix eslint error (thanks @nobu007!) + +## [3.11.16] - 2025-04-14 + +- Add gpt-4.1, gpt-4.1-mini, and gpt-4.1-nano to the OpenAI provider +- Include model ID in environment details and when exporting tasks (thanks @feifei325!) + +## [3.11.15] - 2025-04-13 + +- Add ability to filter task history by workspace (thanks @samhvw8!) +- Fix Node.js version in the .tool-versions file (thanks @bogdan0083!) +- Fix duplicate suggested mentions for open tabs (thanks @samhvw8!) +- Fix Bedrock ARN validation and token expiry issue when using profiles (thanks @vagadiya!) +- Add Anthropic option to pass API token as Authorization header instead of X-Api-Key (thanks @mecab!) +- Better documentation for adding new settings (thanks @KJ7LNW!) +- Localize package.json (thanks @samhvw8!) +- Add option to hide the welcome message and fix the background color for the new profile dialog (thanks @zhangtony239!) +- Restore the focus ring for the VSCodeButton component (thanks @pokutuna!) + +## [3.11.14] - 2025-04-11 + +- Support symbolic links in rules folders to directories and other symbolic links (thanks @taisukeoe!) +- Stronger enforcement of the setting to always read full files instead of doing partial reads + +## [3.11.13] - 2025-04-11 + +- Loads of terminal improvements: command delay, PowerShell counter, and ZSH EOL mark (thanks @KJ7LNW!) +- Add file context tracking system (thanks @samhvw8 and @canvrno!) +- Improved display of diff errors + easy copying for investigation +- Fixes to .vscodeignore (thanks @franekp!) +- Fix a zh-CN translation for model capabilities (thanks @zhangtony239!) +- Rename AWS Bedrock to Amazon Bedrock (thanks @ronyblum!) +- Update extension title and description (thanks @StevenTCramer!) + +## [3.11.12] - 2025-04-09 + +- Make Grok3 streaming work with OpenAI Compatible (thanks @amittell!) +- Tweak diff editing logic to make it more tolerant of model errors + +## [3.11.11] - 2025-04-09 + +- Fix highlighting interaction with mode/profile dropdowns (thanks @atlasgong!) +- Add the ability to set Host header and legacy OpenAI API in the OpenAI-compatible provider for better proxy support +- Improvements to TypeScript, C++, Go, Java, Python tree-sitter parsers (thanks @KJ7LNW!) +- Fixes to terminal working directory logic (thanks @KJ7LNW!) +- Improve readFileTool XML output format (thanks @KJ7LNW!) +- Add o1-pro support (thanks @arthurauffray!) +- Follow symlinked rules files/directories to allow for more flexible rule setups +- Focus Roo Code in the sidebar when running tasks in the sidebar via the API +- Improve subtasks UI + +## [3.11.10] - 2025-04-08 + +- Fix bug where nested .roo/rules directories are not respected properly (thanks @taisukeoe!) +- Handle long command output more efficiently in the chat row (thanks @samhvw8!) +- Fix cache usage tracking for OpenAI-compatible providers +- Add custom translation instructions for zh-CN (thanks @System233!) +- Code cleanup after making rate-limits per-profile (thanks @ross!) + +## [3.11.9] - 2025-04-07 + +- Rate-limit setting updated to be per-profile (thanks @ross and @olweraltuve!) +- You can now place multiple rules files in the .roo/rules/ and .roo/rules-{mode}/ folders (thanks @upamune!) +- Prevent unnecessary autoscroll when buttons appear (thanks @shtse8!) +- Add Gemini 2.5 Pro Preview to Vertex AI (thanks @nbihan-mediware!) +- Tidy up following ClineProvider refactor (thanks @diarmidmackenzie!) +- Clamp negative line numbers when reading files (thanks @KJ7LNW!) +- Enhance Rust tree-sitter parser with advanced language structures (thanks @KJ7LNW!) +- Persist settings on api.setConfiguration (thanks @gtaylor!) +- Add deep links to settings sections +- Add command to focus Roo Code input field (thanks @axkirillov!) +- Add resize and hover actions to the browser (thanks @SplittyDev!) +- Add resumeTask and isTaskInHistory to the API (thanks @franekp!) +- Fix bug displaying boolean/numeric suggested answers +- Dynamic Vite port detection for webview development (thanks @KJ7LNW!) + +## [3.11.8] - 2025-04-05 + +- Improve combineApiRequests performance to reduce gray screens of death (thanks @kyle-apex!) +- Add searchable dropdown to API config profiles on the settings screen (thanks @samhvw8!) +- Add workspace tracking to history items in preparation for future filtering (thanks @samhvw8!) +- Fix search highlighting UI in history search (thanks @samhvw8!) +- Add support for .roorules and give deprecation warning for .clinerules (thanks @upamune!) +- Fix nodejs version format in .tool-versions file (thanks @upamune!) + +## [3.11.7] - 2025-04-04 + +- Improve file tool context formatting and diff error guidance +- Improve zh-TW localization (thanks @PeterDaveHello!) +- Implement reference counting for McpHub disposal +- Update buttons to be more consistent (thanks @kyle-apex!) +- Improve zh-CN localization (thanks @System233!) + +## [3.11.6] - 2025-04-04 + +- Add the gemini 2.5 pro preview model with upper bound pricing + +## [3.11.5] - 2025-04-03 + +- Add prompt caching for Amazon Bedrock (thanks @Smartsheet-JB-Brown!) +- Add support for configuring the current working directory of MCP servers (thanks @shoopapa!) +- Add profile management functions to API (thanks @gtaylor!) +- Improvements to diff editing functionality, tests, and error messages (thanks @p12tic!) +- Fix for follow-up questions grabbing the focus (thanks @diarmidmackenzie!) +- Show menu buttons when popping the extension out into a new tab (thanks @benny123tw!) + +## [3.11.4] - 2025-04-02 + +- Correctly post state to webview when the current task is cleared (thanks @wkordalski!) +- Fix unit tests to run properly on Windows (thanks @StevenTCramer!) +- Tree-sitter enhancements: TSX, TypeScript, JSON, and Markdown support (thanks @KJ7LNW!) +- Fix issue with line number stripping for deletions in apply_diff +- Update history selection mode button spacing (thanks @kyle-apex!) +- Limit dropdown menu height to 80% of the viewport (thanks @axmo!) +- Update dependencies via `npm audit fix` (thanks @PeterDaveHello!) +- Enable model select when api fails (thanks @kyle-apex!) +- Fix issue where prompts and settings tabs were not scrollable when accessed from dropdown menus +- Update AWS region dropdown menu to the most recent data (thanks @Smartsheet-JB-Brown!) +- Fix prompt enhancement for Bedrock (thanks @Smartsheet-JB-Brown!) +- Allow processes to access the Roo Code API via a unix socket +- Improve zh-TW Traditional Chinese translations (thanks @PeterDaveHello!) +- Add support for Azure AI Inference Service with DeepSeek-V3 model (thanks @thomasjeung!) +- Fix off-by-one error in tree-sitter line numbers +- Remove the experimental unified diff +- Make extension icon more visible in different themes + +## [3.11.3] - 2025-03-31 + +- Revert mention changes in case they're causing performance issues/crashes + +## [3.11.2] - 2025-03-31 + +- Fix bug in loading Requesty key balance +- Fix bug with Bedrock inference profiles +- Update the webview when changing settings via the API +- Refactor webview messages code (thanks @diarmidmackenzie!) + +## [3.11.1] - 2025-03-30 + +- Relax provider profiles schema and add telemetry + +## [3.11.0] - 2025-03-30 + +- Replace single-block-diff with multi-block-diff fast editing strategy +- Support project-level MCP config in .roo/mcp.json (thanks @aheizi!) +- Show OpenRouter and Requesty key balance on the settings screen +- Support import/export of settings +- Add pinning and sorting for API configuration dropdown (thanks @jwcraig!) +- Add Gemini 2.5 Pro to GCP Vertex AI provider (thanks @nbihan-mediware!) +- Smarter retry logic for Gemini +- Fix Gemini command escaping +- Support @-mentions of files with spaces in the name (thanks @samhvw8!) +- Improvements to partial file reads (thanks @KJ7LNW!) +- Fix list_code_definition_names to support files (thanks @KJ7LNW!) +- Refactor tool-calling logic to make the code a lot easier to work with (thanks @diarmidmackenzie, @bramburn, @KJ7LNW, and everyone else who helped!) +- Prioritize “Add to Context” in the code actions and include line numbers (thanks @samhvw8!) +- Add an activation command that other extensions can use to interface with Roo Code (thanks @gtaylor!) +- Preserve language characters in file @-mentions (thanks @aheizi!) +- Browser tool improvements (thanks @afshawnlotfi!) +- Display info about partial reads in the chat row +- Link to the settings page from the auto-approve toolbar +- Link to provider docs from the API options +- Fix switching profiles to ensure only the selected profile is switched (thanks @feifei325!) +- Allow custom o3-mini- model from OpenAI-compatible providers (thanks @snoyiatk!) +- Edit suggested answers before accepting them (thanks @samhvw8!) + +## [3.10.5] - 2025-03-25 + +- Updated value of max tokens for gemini-2.5-pro-03-25 to 65,536 (thanks @linegel!) +- Fix logic around when we fire task completion events + +## [3.10.4] - 2025-03-25 + +- Dynamically fetch instructions for creating/editing custom modes and MCP servers (thanks @diarmidmackenzie!) +- Added Gemini 2.5 Pro model to Google Gemini provider (thanks @samsilveira!) +- Add settings to control whether to auto-approve reads and writes outside of the workspace +- Update UX for chat text area (thanks @chadgauth!) +- Support a custom storage path for tasks (thanks @Chenjiayuan195!) +- Add a New Task command in the Command Palette (thanks @qdaxb!) +- Add R1 support checkbox to Open AI compatible provider to support QWQ (thanks @teddyOOXX!) +- Support test declarations in TypeScript tree-sitter queries (thanks @KJ7LNW!) +- Add Bedrock support for application-inference-profile (thanks @maekawataiki!) +- Rename and migrate global MCP and modes files (thanks @StevenTCramer!) +- Add watchPaths option to McpHub for file change detection (thanks @01Rian!) +- Read image responses from MCP calls (thanks @nevermorec!) +- Add taskCreated event to API and subscribe to Cline events earlier (thanks @wkordalski!) +- Fixes to numeric formatting suffix internationalization (thanks @feifei325!) +- Fix open tab support in the context mention suggestions (thanks @aheizi!) +- Better display of OpenRouter “overloaded” error messages +- Fix browser tool visibility in system prompt preview (thanks @cannuri!) +- Fix the supportsPromptCache value for OpenAI models (thanks @PeterDaveHello!) +- Fix readme links to docs (thanks @kvokka!) +- Run ‘npm audit fix’ on all of our libraries + ## [3.10.3] - 2025-03-23 - Update the welcome page to provide 1-click OAuth flows with LLM routers (thanks @dtrugman!) diff --git a/LICENSE b/LICENSE index 194125d8550..302fa51c8a6 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Roo Veterinary Inc. + Copyright 2025 Roo Code, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/assets/docs/demo.gif b/assets/docs/demo.gif index 35eb8d0bbe1..c45e64bc31c 100644 --- a/assets/docs/demo.gif +++ b/assets/docs/demo.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d426d600fa80e9ac237cbad6b3f7f47a4aa2005d5218184e6b9565a8ee46d1ba -size 19108207 +oid sha256:a27ab29e2b5cf8ae65efd35222d456de4b9b1956b159705f9ead3d19426fabae +size 7456839 diff --git a/assets/icons/icon.png b/assets/icons/icon.png new file mode 100644 index 00000000000..b0bef29cc9c Binary files /dev/null and b/assets/icons/icon.png differ diff --git a/assets/icons/icon.svg b/assets/icons/icon.svg new file mode 100644 index 00000000000..a4e2d75e5b3 --- /dev/null +++ b/assets/icons/icon.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/assets/icons/panel_dark.png b/assets/icons/panel_dark.png new file mode 100644 index 00000000000..09a2d84ca42 Binary files /dev/null and b/assets/icons/panel_dark.png differ diff --git a/assets/icons/panel_light.png b/assets/icons/panel_light.png new file mode 100644 index 00000000000..221a8d3ab06 Binary files /dev/null and b/assets/icons/panel_light.png differ diff --git a/assets/icons/rocket.png b/assets/icons/rocket.png deleted file mode 100644 index 1ffd65e70e0..00000000000 Binary files a/assets/icons/rocket.png and /dev/null differ diff --git a/assets/images/roo-logo.svg b/assets/images/roo-logo.svg new file mode 100644 index 00000000000..d2af8edd7ad --- /dev/null +++ b/assets/images/roo-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/benchmark/.env.local.sample b/benchmark/.env.local.sample deleted file mode 100644 index 55a8a599eef..00000000000 --- a/benchmark/.env.local.sample +++ /dev/null @@ -1,2 +0,0 @@ -OPENROUTER_API_KEY=sk-or-v1-... -POSTHOG_API_KEY=phc_... diff --git a/benchmark/Dockerfile b/benchmark/Dockerfile deleted file mode 100644 index ab3c7d4f668..00000000000 --- a/benchmark/Dockerfile +++ /dev/null @@ -1,89 +0,0 @@ -# docker build -f Dockerfile.base -t roo-code-benchmark-base .. -# docker build -f Dockerfile -t roo-code-benchmark .. -# docker run -d -it -p 3000:3000 -v /tmp/benchmarks.db:/tmp/benchmarks.db roo-code-benchmark -# docker exec -it $(docker ps --filter "ancestor=roo-code-benchmark" -q) /bin/bash - -FROM ubuntu:latest - -# Install dependencies -RUN apt update && apt install -y sudo curl git vim jq - -# Create a `vscode` user -RUN useradd -m vscode -s /bin/bash && \ - echo "vscode ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/vscode && \ - chmod 0440 /etc/sudoers.d/vscode - -# Install VS Code -# https://code.visualstudio.com/docs/setup/linux -RUN apt install -y wget gpg apt-transport-https -RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg -RUN install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg -RUN echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" | tee /etc/apt/sources.list.d/vscode.list > /dev/null -RUN rm -f packages.microsoft.gpg -RUN apt update && apt install -y code - -# Install Xvfb -RUN apt install -y xvfb - -# [cpp] Install cmake 3.28.3 -RUN apt install -y cmake - -# [go] Install Go 1.22.2 -RUN apt install -y golang-go - -# [java] Install Java 21 -RUN apt install -y default-jre - -# [javascript] Install Node.js v18.20.6 -RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - -RUN apt update && apt install -y nodejs -RUN npm install -g corepack@latest - -# [python] Install Python 3.12.3 and uv 0.6.6 -RUN apt install -y python3 python3-venv python3-dev python3-pip - -# [rust] Install Rust 1.85 -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y -RUN echo 'source $HOME/.cargo/env' >> $HOME/.bashrc - -WORKDIR /home/vscode -USER vscode - -# Enable corepack and install pnpm for the vscode user -RUN corepack enable -RUN yes y | pnpm --version - -COPY benchmark/entrypoint.sh /usr/local/bin/entrypoint.sh - -# Copy and build dependencies -COPY --chown=vscode:vscode package*.json /home/vscode/repo/ -COPY --chown=vscode:vscode webview-ui/package*.json /home/vscode/repo/webview-ui/ -COPY --chown=vscode:vscode e2e/package*.json /home/vscode/repo/e2e/ -COPY --chown=vscode:vscode benchmark/package*.json /home/vscode/repo/benchmark/ -WORKDIR /home/vscode/repo -RUN npm run install:all - -# Copy and build benchmark runner -COPY --chown=vscode:vscode . /home/vscode/repo -WORKDIR /home/vscode/repo/benchmark -RUN npm run build - -# Copy exercises -WORKDIR /home/vscode -RUN git clone https://github.com/cte/Roo-Code-Benchmark.git exercises - -# Prepare exercises -WORKDIR /home/vscode/exercises/python -RUN curl -LsSf https://astral.sh/uv/install.sh | sh -RUN /home/vscode/.local/bin/uv sync - -# Build web-ui -WORKDIR /home/vscode/exercises/web-ui -RUN echo "DB_FILE_NAME=file:/tmp/benchmarks.db" > .env -RUN pnpm install -RUN npx drizzle-kit push - -# Run web-ui -EXPOSE 3000 -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] -CMD ["/usr/bin/pnpm", "dev"] diff --git a/benchmark/README.md b/benchmark/README.md deleted file mode 100644 index 350a089a110..00000000000 --- a/benchmark/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Benchmark Harness - -Configure ENV vars (OpenRouter, PostHog, etc): - -```sh -cp .env.local.sample .env.local -# Update ENV vars as needed. -``` - -Build and run a Docker image with the development environment needed to run the -benchmarks (C++, Go, Java, Node.js, Python & Rust): - -```sh -npm run docker:start -``` - -Run an exercise: - -```sh -npm run docker:benchmark -- -e exercises/javascript/binary -``` - -Select and run an exercise: - -```sh -npm run cli -``` - -Select and run an exercise for a specific language: - -```sh -npm run cli -- run rust -``` - -Run all exercises for a language: - -```sh -npm run cli -- run rust all -``` - -Run all exercises: - -```sh -npm run cli -- run all -``` - -Run all exercises using a specific runId (useful for re-trying when an unexpected error occurs): - -```sh -npm run cli -- run all --runId 1 -``` diff --git a/benchmark/entrypoint.sh b/benchmark/entrypoint.sh deleted file mode 100755 index ab24ab6bffe..00000000000 --- a/benchmark/entrypoint.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -npx drizzle-kit push -exec "$@" diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json deleted file mode 100644 index c506bba9e69..00000000000 --- a/benchmark/package-lock.json +++ /dev/null @@ -1,2493 +0,0 @@ -{ - "name": "benchmark", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "benchmark", - "version": "0.1.0", - "devDependencies": { - "@vscode/test-electron": "^2.4.0", - "gluegun": "^5.1.2", - "tsx": "^4.19.3", - "typescript": "^5.4.5", - "yargs": "^17.7.2" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^7.0.1", - "semver": "^7.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/apisauce": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/apisauce/-/apisauce-2.1.6.tgz", - "integrity": "sha512-MdxR391op/FucS2YQRfB/NMRyCnHEPDd4h17LRIuVYi0BpGmMhpxc0shbOpfs5ahABuBEffNCGal5EcsydbBWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^0.21.4" - } - }, - "node_modules/app-module-path": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", - "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", - "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs-jetpack": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-4.3.1.tgz", - "integrity": "sha512-dbeOK84F6BiQzk2yqqCVwCPWTxAvVGJ3fMQc6E2wuEohS28mR6yHngbrKuVCK1KHRx/ccByDylqu4H5PCP2urQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.2", - "rimraf": "^2.6.3" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gluegun": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/gluegun/-/gluegun-5.2.0.tgz", - "integrity": "sha512-jSUM5xUy2ztYFQANne17OUm/oAd7qSX7EBksS9bQDt9UvLPqcEkeWUebmaposb8Tx7eTTD8uJVWGRe6PYSsYkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "apisauce": "^2.1.5", - "app-module-path": "^2.2.0", - "cli-table3": "0.6.0", - "colors": "1.4.0", - "cosmiconfig": "7.0.1", - "cross-spawn": "7.0.3", - "ejs": "3.1.8", - "enquirer": "2.3.6", - "execa": "5.1.1", - "fs-jetpack": "4.3.1", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.lowercase": "^4.3.0", - "lodash.lowerfirst": "^4.3.1", - "lodash.pad": "^4.5.1", - "lodash.padend": "^4.6.1", - "lodash.padstart": "^4.6.1", - "lodash.repeat": "^4.1.0", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.trim": "^4.5.1", - "lodash.trimend": "^4.5.1", - "lodash.trimstart": "^4.5.1", - "lodash.uppercase": "^4.3.0", - "lodash.upperfirst": "^4.3.1", - "ora": "4.0.2", - "pluralize": "^8.0.0", - "semver": "7.3.5", - "which": "2.0.2", - "yargs-parser": "^21.0.0" - }, - "bin": { - "gluegun": "bin/gluegun" - } - }, - "node_modules/gluegun/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/gluegun/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gluegun/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gluegun/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gluegun/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/gluegun/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/gluegun/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/gluegun/node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/gluegun/node_modules/ora": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.2.tgz", - "integrity": "sha512-YUOZbamht5mfLxPmk4M35CD/5DuOkAacxlEUbStVXpBAt4fyhBf+vZHI/HRkI++QUp3sNoeA2Gw4C+hi4eGSig==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.2", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", - "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gluegun/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gluegun/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gluegun/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gluegun/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.lowercase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.lowercase/-/lodash.lowercase-4.3.0.tgz", - "integrity": "sha512-UcvP1IZYyDKyEL64mmrwoA1AbFu5ahojhTtkOUr1K9dbuxzS9ev8i4TxMMGCqRC9TE8uDaSoufNAXxRPNTseVA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.lowerfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.lowerfirst/-/lodash.lowerfirst-4.3.1.tgz", - "integrity": "sha512-UUKX7VhP1/JL54NXg2aq/E1Sfnjjes8fNYTNkPU8ZmsaVeBvPHKdbNaN79Re5XRL01u6wbq3j0cbYZj71Fcu5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.pad": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", - "integrity": "sha512-mvUHifnLqM+03YNzeTBS1/Gr6JRFjd3rRx88FHWUvamVaT9k2O/kXha3yBSOwB9/DTQrSTLJNHvLBBt2FdX7Mg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.padend": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.padstart": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", - "integrity": "sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.repeat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-4.1.0.tgz", - "integrity": "sha512-eWsgQW89IewS95ZOcr15HHCX6FVDxq3f2PNUIng3fyzsPev9imFQxIYdFZ6crl8L56UR6ZlGDLcEb3RZsCSSqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.trim": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.trim/-/lodash.trim-4.5.1.tgz", - "integrity": "sha512-nJAlRl/K+eiOehWKDzoBVrSMhK0K3A3YQsUNXHQa5yIrKBAhsZgSu3KoAFoFT+mEgiyBHddZ0pRk1ITpIp90Wg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.trimend": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.trimend/-/lodash.trimend-4.5.1.tgz", - "integrity": "sha512-lsD+k73XztDsMBKPKvzHXRKFNMohTjoTKIIo4ADLn5dA65LZ1BqlAvSXhR2rPEC3BgAUQnzMnorqDtqn2z4IHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.trimstart": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.trimstart/-/lodash.trimstart-4.5.1.tgz", - "integrity": "sha512-b/+D6La8tU76L/61/aN0jULWHkT0EeJCmVstPBn/K9MtD2qBW83AsBNrr63dKuWYwVMO7ucv13QNO/Ek/2RKaQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.uppercase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.uppercase/-/lodash.uppercase-4.3.0.tgz", - "integrity": "sha512-+Nbnxkj7s8K5U8z6KnEYPGUOGp3woZbB7Ecs7v3LkkjLQSm2kP9SKIILitN1ktn2mB/tmM9oSlku06I+/lH7QA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsx": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", - "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - } - } -} diff --git a/benchmark/package.json b/benchmark/package.json deleted file mode 100644 index 65874361cb1..00000000000 --- a/benchmark/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "benchmark", - "version": "0.1.0", - "private": true, - "main": "out/run.js", - "scripts": { - "build": "npm run compile && cd .. && npm run compile && npm run build:webview", - "lint": "eslint src --ext ts", - "check-types": "tsc --noEmit", - "compile": "rm -rf out && tsc -p tsconfig.json", - "cli": "npm run compile && npx dotenvx run -f .env.local -- tsx src/cli.ts", - "clean": "rimraf out", - "clean:exercises": "cd exercises && git checkout -f && git clean -fd", - "docker:build": "docker build -f Dockerfile -t roo-code-benchmark ..", - "docker:run": "touch /tmp/benchmarks.db && docker run -d -it -p 3000:3000 -v /tmp/benchmarks.db:/tmp/benchmarks.db roo-code-benchmark", - "docker:start": "npm run docker:build && npm run docker:run", - "docker:shell": "docker exec -it $(docker ps --filter \"ancestor=roo-code-benchmark\" -q) /bin/bash", - "docker:cli": "docker exec -it -w /home/vscode/repo/benchmark $(docker ps --filter \"ancestor=roo-code-benchmark\" -q) xvfb-run npm run cli --", - "docker:stop": "docker stop $(docker ps --filter \"ancestor=roo-code-benchmark\" -q)", - "docker:rm": "docker rm $(docker ps -a --filter \"ancestor=roo-code-benchmark\" -q)", - "docker:clean": "npm run docker:stop && npm run docker:rm" - }, - "devDependencies": { - "@vscode/test-electron": "^2.4.0", - "gluegun": "^5.1.2", - "tsx": "^4.19.3", - "typescript": "^5.4.5", - "yargs": "^17.7.2" - } -} diff --git a/benchmark/prompts/cpp.md b/benchmark/prompts/cpp.md deleted file mode 100644 index 19ffaf7803b..00000000000 --- a/benchmark/prompts/cpp.md +++ /dev/null @@ -1,17 +0,0 @@ -Your job is to complete a coding exercise described by `.docs/instructions.md`. - -A file with the implementation stubbed out has been created for you, along with a test file. - -To successfully complete the exercise, you must pass all the tests in the test file. - -To confirm that your solution is correct, you can compile your code and run the tests with: - -``` -mkdir -p build && cd build -cmake -G "Unix Makefiles" -DEXERCISM_RUN_ALL_TESTS=1 .. -make -``` - -Note that running `make` will compile the tests and generate compile time errors. Once the errors are fixed, running `make` will build and run the tests. - -Do not alter the test file; it should be run as-is. diff --git a/benchmark/prompts/go.md b/benchmark/prompts/go.md deleted file mode 100644 index 4b2edff6abd..00000000000 --- a/benchmark/prompts/go.md +++ /dev/null @@ -1,7 +0,0 @@ -Your job is to complete a coding exercise described by `.docs/instructions.md`. - -A file with the implementation stubbed out has been created for you, along with a test file. - -To successfully complete the exercise, you must pass all the tests in the test file. - -To confirm that your solution is correct, run the tests with `go test`. Do not alter the test file; it should be run as-is. diff --git a/benchmark/prompts/java.md b/benchmark/prompts/java.md deleted file mode 100644 index 4a7a0a74352..00000000000 --- a/benchmark/prompts/java.md +++ /dev/null @@ -1,7 +0,0 @@ -Your job is to complete a coding exercise described by `.docs/instructions.md`. - -A file with the implementation stubbed out has been created for you, along with a test file. - -To successfully complete the exercise, you must pass all the tests in the test file. - -To confirm that your solution is correct, run the tests with `./gradlew test`. Do not alter the test file; it should be run as-is. diff --git a/benchmark/prompts/javascript.md b/benchmark/prompts/javascript.md deleted file mode 100644 index dea54a94f93..00000000000 --- a/benchmark/prompts/javascript.md +++ /dev/null @@ -1,9 +0,0 @@ -Your job is to complete a coding exercise described by `.docs/instructions.md`. - -A file with the implementation stubbed out has been created for you, along with a test file. - -To successfully complete the exercise, you must pass all the tests in the test file. - -To confirm that your solution is correct, run the tests with `pnpm test`. Do not alter the test file; it should be run as-is. - -Before running the tests make sure your environment is set up by running `pnpm install` to install the dependencies. diff --git a/benchmark/prompts/python.md b/benchmark/prompts/python.md deleted file mode 100644 index 0a732ecf309..00000000000 --- a/benchmark/prompts/python.md +++ /dev/null @@ -1,7 +0,0 @@ -Your job is to complete a coding exercise described by `.docs/instructions.md`. - -A file with the implementation stubbed out has been created for you, along with a test file. - -To successfully complete the exercise, you must pass all the tests in the test file. - -To confirm that your solution is correct, run the tests with `uv run python3 -m pytest -o markers=task [name]_test.py`. Do not alter the test file; it should be run as-is. diff --git a/benchmark/prompts/rust.md b/benchmark/prompts/rust.md deleted file mode 100644 index b2697cba50a..00000000000 --- a/benchmark/prompts/rust.md +++ /dev/null @@ -1,7 +0,0 @@ -Your job is to complete a coding exercise described by `.docs/instructions.md`. - -A file with the implementation stubbed out has been created for you, along with a test file. - -To successfully complete the exercise, you must pass all the tests in the test file. - -To confirm that your solution is correct, run the tests with `cargo test`. Do not alter the test file; it should be run as-is. diff --git a/benchmark/src/cli.ts b/benchmark/src/cli.ts deleted file mode 100644 index 0a87aafd07f..00000000000 --- a/benchmark/src/cli.ts +++ /dev/null @@ -1,171 +0,0 @@ -import * as fs from "fs" -import * as path from "path" - -import { build, filesystem, GluegunPrompt } from "gluegun" -import { runTests } from "@vscode/test-electron" - -// console.log(__dirname) -// <...>/Roo-Code/benchmark/src - -const extensionDevelopmentPath = path.resolve(__dirname, "../../") -const extensionTestsPath = path.resolve(__dirname, "../out/runExercise") -const promptsPath = path.resolve(__dirname, "../prompts") -const exercisesPath = path.resolve(__dirname, "../../../exercises") -const languages = ["cpp", "go", "java", "javascript", "python", "rust"] - -async function runAll({ runId, model }: { runId: number; model: string }) { - for (const language of languages) { - await runLanguage({ runId, model, language }) - } -} - -async function runLanguage({ runId, model, language }: { runId: number; model: string; language: string }) { - const languagePath = path.resolve(exercisesPath, language) - - if (!fs.existsSync(languagePath)) { - console.error(`Language directory ${languagePath} does not exist`) - process.exit(1) - } - - const exercises = filesystem - .subdirectories(languagePath) - .map((exercise) => path.basename(exercise)) - .filter((exercise) => !exercise.startsWith(".")) - - for (const exercise of exercises) { - await runExercise({ runId, model, language, exercise }) - } -} - -async function runExercise({ - runId, - model, - language, - exercise, -}: { - runId: number - model: string - language: string - exercise: string -}) { - const workspacePath = path.resolve(exercisesPath, language, exercise) - const promptPath = path.resolve(promptsPath, `${language}.md`) - - const extensionTestsEnv = { - PROMPT_PATH: promptPath, - WORKSPACE_PATH: workspacePath, - OPENROUTER_MODEL_ID: model, - RUN_ID: runId.toString(), - } - - if (fs.existsSync(path.resolve(workspacePath, "usage.json"))) { - console.log(`Test result exists for ${language} / ${exercise}, skipping`) - return - } - - console.log(`Running ${language} / ${exercise}`) - - await runTests({ - extensionDevelopmentPath, - extensionTestsPath, - launchArgs: [workspacePath, "--disable-extensions"], - extensionTestsEnv, - }) -} - -async function askLanguage(prompt: GluegunPrompt) { - const languages = filesystem.subdirectories(exercisesPath) - - if (languages.length === 0) { - throw new Error(`No languages found in ${exercisesPath}`) - } - - const { language } = await prompt.ask<{ language: string }>({ - type: "select", - name: "language", - message: "Which language?", - choices: languages.map((language) => path.basename(language)).filter((language) => !language.startsWith(".")), - }) - - return language -} - -async function askExercise(prompt: GluegunPrompt, language: string) { - const exercises = filesystem.subdirectories(path.join(exercisesPath, language)) - - if (exercises.length === 0) { - throw new Error(`No exercises found for ${language}`) - } - - const { exercise } = await prompt.ask<{ exercise: string }>({ - type: "select", - name: "exercise", - message: "Which exercise?", - choices: exercises.map((exercise) => path.basename(exercise)), - }) - - return exercise -} - -async function createRun({ model }: { model: string }): Promise<{ id: number; model: string }> { - const response = await fetch("http://localhost:3000/api/runs", { - method: "POST", - body: JSON.stringify({ model }), - }) - - if (!response.ok) { - throw new Error(`Failed to create run: ${response.statusText}`) - } - - const { - run: [run], - } = await response.json() - return run -} - -async function main() { - const cli = build() - .brand("benchmark-runner") - .src(__dirname) - .help() - .version() - .command({ - name: "run", - run: ({ config, parameters }) => { - config.language = parameters.first - config.exercise = parameters.second - - if (parameters.options["runId"]) { - config.runId = parameters.options["runId"] - } - }, - }) - .defaultCommand() // Use the default command if no args. - .create() - - const { print, prompt, config } = await cli.run(process.argv) - - try { - const model = "anthropic/claude-3.7-sonnet" - const runId = config.runId ? Number(config.runId) : (await createRun({ model })).id - - if (config.language === "all") { - console.log("Running all exercises for all languages") - await runAll({ runId, model }) - } else if (config.exercise === "all") { - console.log(`Running all exercises for ${config.language}`) - await runLanguage({ runId, model, language: config.language }) - } else { - const language = config.language || (await askLanguage(prompt)) - const exercise = config.exercise || (await askExercise(prompt, language)) - await runExercise({ runId, model, language, exercise }) - } - - process.exit(0) - } catch (error) { - print.error(error) - process.exit(1) - } -} - -main() diff --git a/benchmark/src/runExercise.ts b/benchmark/src/runExercise.ts deleted file mode 100644 index 4b942e5fbb2..00000000000 --- a/benchmark/src/runExercise.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as fs from "fs/promises" -import * as path from "path" - -import * as vscode from "vscode" - -import { RooCodeAPI, TokenUsage } from "../../src/exports/roo-code" - -import { waitUntilReady, waitUntilCompleted, sleep } from "./utils" - -export async function run() { - /** - * Validate environment variables. - */ - - const runId = process.env.RUN_ID - const openRouterApiKey = process.env.OPENROUTER_API_KEY - const openRouterModelId = process.env.OPENROUTER_MODEL_ID - const promptPath = process.env.PROMPT_PATH - const workspacePath = process.env.WORKSPACE_PATH - - if (!runId || !openRouterApiKey || !openRouterModelId || !promptPath || !workspacePath) { - throw new Error("ENV not configured.") - } - - const prompt = await fs.readFile(promptPath, "utf-8") - - /** - * Activate the extension. - */ - - const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline") - - if (!extension) { - throw new Error("Extension not found.") - } - - const api = extension.isActive ? extension.exports : await extension.activate() - - /** - * Wait for the Roo Code to be ready to accept tasks. - */ - - await waitUntilReady({ api }) - - /** - * Configure Roo Code as needed. - * - * Use Claude 3.7 Sonnet via OpenRouter. - * Don't require approval for anything. - * Run any command without approval. - * Disable checkpoints (for performance). - */ - - await api.setConfiguration({ - apiProvider: "openrouter", - openRouterApiKey, - openRouterModelId, - autoApprovalEnabled: true, - alwaysAllowReadOnly: true, - alwaysAllowWrite: true, - alwaysAllowExecute: true, - alwaysAllowBrowser: true, - alwaysApproveResubmit: true, - alwaysAllowMcp: true, - alwaysAllowModeSwitch: true, - enableCheckpoints: false, - }) - - await vscode.workspace - .getConfiguration("roo-cline") - .update("allowedCommands", ["*"], vscode.ConfigurationTarget.Global) - - await sleep(2_000) - - /** - * Run the task and wait up to 10 minutes for it to complete. - */ - - const startTime = Date.now() - const taskId = await api.startNewTask(prompt) - - let usage: TokenUsage | undefined = undefined - - try { - usage = await waitUntilCompleted({ api, taskId, timeout: 5 * 60 * 1_000 }) // 5m - } catch (e) { - usage = api.getTokenUsage(taskId) - } - - if (usage) { - const content = JSON.stringify({ runId: parseInt(runId), ...usage, duration: Date.now() - startTime }, null, 2) - await fs.writeFile(path.resolve(workspacePath, "usage.json"), content) - } -} diff --git a/benchmark/src/utils.ts b/benchmark/src/utils.ts deleted file mode 100644 index ef89655d97d..00000000000 --- a/benchmark/src/utils.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as vscode from "vscode" - -import { RooCodeAPI, TokenUsage } from "../../src/exports/roo-code" - -type WaitForOptions = { - timeout?: number - interval?: number -} - -export const waitFor = ( - condition: (() => Promise) | (() => boolean), - { timeout = 30_000, interval = 250 }: WaitForOptions = {}, -) => { - let timeoutId: NodeJS.Timeout | undefined = undefined - - return Promise.race([ - new Promise((resolve) => { - const check = async () => { - const result = condition() - const isSatisfied = result instanceof Promise ? await result : result - - if (isSatisfied) { - if (timeoutId) { - clearTimeout(timeoutId) - timeoutId = undefined - } - - resolve() - } else { - setTimeout(check, interval) - } - } - - check() - }), - new Promise((_, reject) => { - timeoutId = setTimeout(() => { - reject(new Error(`Timeout after ${Math.floor(timeout / 1000)}s`)) - }, timeout) - }), - ]) -} - -type WaitUntilReadyOptions = WaitForOptions & { - api: RooCodeAPI -} - -export const waitUntilReady = async ({ api, ...options }: WaitUntilReadyOptions) => { - await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") - await waitFor(() => api.isReady(), options) -} - -type WaitUntilAbortedOptions = WaitForOptions & { - api: RooCodeAPI - taskId: string -} - -export const waitUntilAborted = async ({ api, taskId, ...options }: WaitUntilAbortedOptions) => { - const set = new Set() - api.on("taskAborted", (taskId) => set.add(taskId)) - await waitFor(() => set.has(taskId), options) -} - -type WaitUntilCompletedOptions = WaitForOptions & { - api: RooCodeAPI - taskId: string -} - -export const waitUntilCompleted = async ({ api, taskId, ...options }: WaitUntilCompletedOptions) => { - const map = new Map() - api.on("taskCompleted", (taskId, usage) => map.set(taskId, usage)) - await waitFor(() => map.has(taskId), options) - return map.get(taskId) -} - -export const waitForCompletion = async ({ - api, - taskId, - ...options -}: WaitUntilReadyOptions & { - taskId: string -}) => waitFor(() => !!getCompletion({ api, taskId }), options) - -export const getCompletion = ({ api, taskId }: { api: RooCodeAPI; taskId: string }) => - api.getMessages(taskId).find(({ say, partial }) => say === "completion_result" && partial === false) - -type WaitForMessageOptions = WaitUntilReadyOptions & { - taskId: string - include: string - exclude?: string -} - -export const waitForMessage = async ({ api, taskId, include, exclude, ...options }: WaitForMessageOptions) => - waitFor(() => !!getMessage({ api, taskId, include, exclude }), options) - -type GetMessageOptions = { - api: RooCodeAPI - taskId: string - include: string - exclude?: string -} - -export const getMessage = ({ api, taskId, include, exclude }: GetMessageOptions) => - api - .getMessages(taskId) - .find( - ({ type, text }) => - type === "say" && text && text.includes(include) && (!exclude || !text.includes(exclude)), - ) - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/cline_docs/bedrock/bedrock-cache-strategy-documentation.md b/cline_docs/bedrock/bedrock-cache-strategy-documentation.md new file mode 100644 index 00000000000..09d67fd996d --- /dev/null +++ b/cline_docs/bedrock/bedrock-cache-strategy-documentation.md @@ -0,0 +1,621 @@ +# Cache Strategy Documentation + +This document provides an overview of the cache strategy implementation for Amazon Bedrock in the Roo-Code project, including class relationships and sequence diagrams. + +## Class Relationship Diagram + +```mermaid +classDiagram + class CacheStrategy { + <> + #config: CacheStrategyConfig + #systemTokenCount: number + +determineOptimalCachePoints(): CacheResult + #initializeMessageGroups(): void + #calculateSystemTokens(): void + #createCachePoint(): ContentBlock + #messagesToContentBlocks(messages): Message[] + #meetsMinTokenThreshold(tokenCount): boolean + #estimateTokenCount(message): number + #applyCachePoints(messages, placements): Message[] + #formatResult(systemBlocks, messages): CacheResult + } + + class MultiPointStrategy { + +determineOptimalCachePoints(): CacheResult + -determineMessageCachePoints(minTokensPerPoint, remainingCachePoints): CachePointPlacement[] + -formatWithoutCachePoints(): CacheResult + -findOptimalPlacementForRange(startIndex, endIndex, minTokensPerPoint): CachePointPlacement + } + + class AwsBedrockHandler { + -client: BedrockRuntimeClient + -costModelConfig: object + -previousCachePointPlacements: Map + +createMessage(systemPrompt, messages): ApiStream + +completePrompt(prompt): Promise + -supportsAwsPromptCache(modelConfig): boolean + -getModelByName(modelName): object + +getModel(): object + -removeCachePoints(content): any + -convertToBedrockConverseMessages(anthropicMessages, systemMessage, usePromptCache, modelInfo, conversationId): object + } + + class CacheStrategyConfig { + +modelInfo: ModelInfo + +systemPrompt?: string + +messages: MessageParam[] + +usePromptCache: boolean + +previousCachePointPlacements?: CachePointPlacement[] + } + + class ModelInfo { + +maxTokens: number + +contextWindow: number + +supportsPromptCache: boolean + +maxCachePoints: number + +minTokensPerCachePoint: number + +cachableFields: Array + } + + class CacheResult { + +system: SystemContentBlock[] + +messages: Message[] + +messageCachePointPlacements?: CachePointPlacement[] + } + + class CachePointPlacement { + +index: number + +type: string + +tokensCovered: number + } + + CacheStrategy <|-- MultiPointStrategy : extends + CacheStrategy o-- CacheStrategyConfig : uses + CacheStrategyConfig o-- ModelInfo : contains + CacheStrategy ..> CacheResult : produces + CacheStrategy ..> CachePointPlacement : creates + AwsBedrockHandler ..> MultiPointStrategy : creates + AwsBedrockHandler ..> CachePointPlacement : tracks + MultiPointStrategy ..> CachePointPlacement : preserves +``` + +## Sequence Diagram: Multi-Point Strategy + +This diagram illustrates the process flow when using the MultiPointStrategy with multiple cache points in messages. + +```mermaid +sequenceDiagram + participant Client as Client Code + participant Bedrock as AwsBedrockHandler + participant Strategy as MultiPointStrategy + participant AWS as Amazon Bedrock Service + + Client->>Bedrock: createMessage(systemPrompt, messages) + Note over Bedrock: Generate conversationId to track cache points + Bedrock->>Bedrock: getModel() to get model info + Bedrock->>Bedrock: Check if model supports prompt cache + + Bedrock->>Strategy: new MultiPointStrategy(config) + Note over Strategy: config contains modelInfo, systemPrompt, messages, usePromptCache, previousCachePointPlacements + + Bedrock->>Strategy: determineOptimalCachePoints() + + alt usePromptCache is false or no messages + Strategy->>Strategy: formatWithoutCachePoints() + else + Strategy->>Strategy: Check if system cache is supported + alt supportsSystemCache and systemPrompt exists + Strategy->>Strategy: meetsMinTokenThreshold(systemTokenCount) + alt systemTokenCount >= minTokensPerCachePoint + Strategy->>Strategy: Add cache point after system prompt + Note over Strategy: Decrement remainingCachePoints + end + end + + Strategy->>Strategy: determineMessageCachePoints(minTokensPerPoint, remainingCachePoints) + alt previousCachePointPlacements exists + Note over Strategy: Analyze previous placements + Note over Strategy: Preserve N-1 cache points when possible + Note over Strategy: Determine which points to keep or combine + else + loop while currentIndex < messages.length and remainingCachePoints > 0 + Strategy->>Strategy: findOptimalPlacementForRange(currentIndex, totalMessages-1, minTokensPerPoint) + alt placement found + Strategy->>Strategy: Add placement to placements array + Strategy->>Strategy: Update currentIndex and decrement remainingCachePoints + end + end + end + Strategy->>Strategy: applyCachePoints(messages, placements) + Strategy->>Strategy: Store cache point placements in result + end + + Strategy-->>Bedrock: Return CacheResult with system blocks, messages, and messageCachePointPlacements + + Bedrock->>Bedrock: Store cache point placements for conversationId + Bedrock->>AWS: Send request with multiple cache points + AWS-->>Bedrock: Stream response + Bedrock-->>Client: Yield response chunks +``` + +## Key Concepts + +### Cache Strategy + +The cache strategy system is designed to optimize the placement of cache points in Amazon Bedrock API requests. Cache points allow the service to reuse previously processed parts of the prompt, reducing token usage and improving response times. + +- **MultiPointStrategy**: Upon first MR of Bedrock caching, this strategy is used for all cache point placement scenarios. It distributes cache points throughout the conversation to maximize caching efficiency, whether the model supports one or multiple cache points. + +### MultiPointStrategy Placement Logic + +- **System Prompt Caching**: If the system prompt is large enough (exceeds minTokensPerCachePoint), a cache point is placed after it. + +- **Message Caching**: The strategy uses a simplified approach for placing cache points in messages: + + 1. For new conversations (no previous cache points): + + - It iteratively finds the last user message in each range + - It ensures each placement covers at least the minimum token threshold + - It continues until all available cache points are used or no more valid placements can be found + + 2. For growing conversations (with previous cache points): + - It preserves previous cache points when possible + - It analyzes the token distribution between existing cache points + - It compares the token count of new messages with the smallest gap between existing cache points + - It only combines cache points if the new messages have more tokens than the smallest gap + +A key challenge in cache point placement is maintaining consistency across consecutive messages in a growing conversation. When new messages are added to a conversation, we want to ensure that: + +1. Cache points from previous messages are reused as much as possible to maximize cache hits +2. New cache points are placed optimally for the new messages + +The simplified approach ensures that: + +- Cache points are always placed after user messages, which are natural conversation boundaries +- Each cache point covers at least the minimum token threshold +- N-1 cache points remain in the same location when possible in growing conversations +- Cache points are combined only when it makes sense to do so (when the benefit outweighs the cost) +- New messages receive cache points only when they contain enough tokens to justify the reallocation + +The examples in this document reflect this optimized implementation. + +### Integration with Amazon Bedrock + +The AwsBedrockHandler class integrates with the cache strategies by: + +1. Determining if the model supports prompt caching +2. Creating the appropriate strategy based on model capabilities +3. Applying the strategy to format messages with optimal cache points +4. Sending the formatted request to Amazon Bedrock +5. Processing and returning the response + +## Usage Considerations + +- Cache points are only effective if the same content is reused across multiple requests +- The minimum token threshold ensures cache points are only placed where they provide meaningful benefits +- System prompt caching is prioritized when available, as it's typically static across requests +- Message caching is more complex and depends on conversation structure and token distribution + +## Examples: Multi-Point Strategy Cache Point Placement + +### Example 1: Initial Cache Point Placement + +In this example, we'll demonstrate how the `determineMessageCachePoints` method places cache points in a new conversation. + +**Input Configuration:** + +```javascript +const config = { + modelInfo: { + maxTokens: 4096, + contextWindow: 200000, + supportsPromptCache: true, + maxCachePoints: 3, + minTokensPerCachePoint: 100, + cachableFields: ["system", "messages"], + }, + systemPrompt: "You are a helpful assistant.", // ~10 tokens + messages: [ + { role: "user", content: "Tell me about machine learning." }, // ~50 tokens + { role: "assistant", content: "Machine learning is a field of study..." }, // ~150 tokens + { role: "user", content: "What about deep learning?" }, // ~40 tokens + { role: "assistant", content: "Deep learning is a subset of machine learning..." }, // ~160 tokens + ], + usePromptCache: true, +} +``` + +**Execution Process:** + +1. First, the system prompt is evaluated for caching (10 tokens < minTokensPerCachePoint of 100), so no cache point is used there. +2. The `determineMessageCachePoints` method is called with `minTokensPerPoint = 100` and `remainingCachePoints = 3`. +3. Since there are no previous cache point placements, it enters the special case for new conversations. +4. It calls `findOptimalPlacementForRange` with the entire message range. +5. The method finds the last user message in the range (index 2: "What about deep learning?"). +6. It calculates the total tokens covered (240) and verifies it exceeds the minimum threshold (100). +7. It adds this placement to the placements array and continues the process for the next range. +8. Since there are no more user messages after this point that would cover enough tokens, no more cache points are placed. + +**Output Cache Point Placements:** + +```javascript +;[ + { + index: 2, // After the second user message (What about deep learning?) + type: "message", + tokensCovered: 240, // ~240 tokens covered (first 3 messages) + }, +] +``` + +**Resulting Message Structure:** + +``` +[User]: Tell me about machine learning. +[Assistant]: Machine learning is a field of study... +[User]: What about deep learning? +[CACHE POINT] +[Assistant]: Deep learning is a subset of machine learning... +``` + +**Note**: The algorithm places a cache point after the second user message (the last user message in the range) because it's the optimal placement and the accumulated tokens (240) exceed the minimum threshold (100). + +### Example 2: Adding One Exchange with Cache Point Preservation + +Now, let's see what happens when we add one more exchange (user-assistant pair) to the conversation and use the cache point preservation logic: + +**Updated Input Configuration with Previous Cache Points:** + +```javascript +const config = { + // Same modelInfo and systemPrompt as before + messages: [ + // Previous 4 messages... + { role: "user", content: "How do neural networks work?" }, // ~50 tokens + { role: "assistant", content: "Neural networks are composed of layers of nodes..." }, // ~180 tokens + ], + usePromptCache: true, + // Pass the previous cache point placements from Example 1 + previousCachePointPlacements: [ + { + index: 2, // After the second user message (What about deep learning?) + type: "message", + tokensCovered: 240, + }, + ], +} +``` + +**Execution Process for Example 2 with Cache Point Preservation:** + +1. The system prompt evaluation remains the same (no cache point used). +2. The algorithm detects that `previousCachePointPlacements` is provided in the config. +3. It analyzes the previous cache point placements and the current message structure. +4. Since we have 3 total cache points available and used 1 in the previous conversation, we can preserve the previous cache point and still have 2 remaining for the new messages. +5. The algorithm preserves the cache point from the previous conversation: + - The cache point at index 2 (after "What about deep learning?") +6. It then calculates the optimal placement for the remaining cache points based on the new messages. +7. Since there are 2 new messages with significant token count (230 tokens), it places a second cache point after the new user message. + +**Output Cache Point Placements with Preservation:** + +```javascript +;[ + { + index: 2, // After the second user message (What about deep learning?) - PRESERVED + type: "message", + tokensCovered: 240, // ~240 tokens covered (first 3 messages) + }, + { + index: 4, // After the third user message (How do neural networks work?) - NEW PLACEMENT + type: "message", + tokensCovered: 230, // ~230 tokens covered (messages between cache points) + }, +] +``` + +**Resulting Message Structure with Preservation:** + +``` +[User]: Tell me about machine learning. +[Assistant]: Machine learning is a field of study... +[User]: What about deep learning? +[CACHE POINT 1] - PRESERVED FROM PREVIOUS +[Assistant]: Deep learning is a subset of machine learning... +[User]: How do neural networks work? +[CACHE POINT 2] - NEW PLACEMENT +[Assistant]: Neural networks are composed of layers of nodes... +``` + +**Note**: The algorithm preserved the cache point from the previous conversation and placed a new cache point for the new messages. This ensures maximum cache hit rates while still adapting to the growing conversation. + +### Example 3: Adding Another Exchange with Cache Point Preservation + +Let's add one more exchange to see how the cache strategy continues to adapt: + +**Updated Input Configuration with Previous Cache Points:** + +```javascript +const config = { + // Same modelInfo and systemPrompt as before + messages: [ + // Previous 6 messages... + { role: "user", content: "Can you explain backpropagation?" }, // ~40 tokens + { role: "assistant", content: "Backpropagation is an algorithm used to train neural networks..." }, // ~170 tokens + ], + usePromptCache: true, + // Pass the previous cache point placements from Example 2 + previousCachePointPlacements: [ + { + index: 2, // After the second user message (What about deep learning?) + type: "message", + tokensCovered: 240, + }, + { + index: 4, // After the third user message (How do neural networks work?) + type: "message", + tokensCovered: 230, + }, + ], +} +``` + +**Execution Process for Example 3 with Cache Point Preservation:** + +1. The system prompt evaluation remains the same (no cache point used). +2. The algorithm detects that `previousCachePointPlacements` is provided in the config. +3. It analyzes the previous cache point placements and the current message structure. +4. Following the N-1 preservation rule, it decides to keep both previous cache points (at indices 2 and 4) since there are 3 total cache points available. +5. It then calculates the optimal placement for the remaining cache point based on the new messages. +6. Since there are 2 new messages with significant token count (210 tokens), it places a new cache point after the new user message. + +**Output Cache Point Placements with Preservation:** + +```javascript +;[ + { + index: 2, // After the second user message (What about deep learning?) - PRESERVED + type: "message", + tokensCovered: 240, // ~240 tokens covered (first 3 messages) + }, + { + index: 4, // After the third user message (How do neural networks work?) - PRESERVED + type: "message", + tokensCovered: 230, // ~230 tokens covered (messages between cache points) + }, + { + index: 6, // After the fourth user message (Can you explain backpropagation?) - NEW PLACEMENT + type: "message", + tokensCovered: 210, // ~210 tokens covered (messages between cache points) + }, +] +``` + +**Resulting Message Structure with Preservation:** + +``` +[User]: Tell me about machine learning. +[Assistant]: Machine learning is a field of study... +[User]: What about deep learning? +[CACHE POINT 1] - PRESERVED FROM PREVIOUS +[Assistant]: Deep learning is a subset of machine learning... +[User]: How do neural networks work? +[CACHE POINT 2] - PRESERVED FROM PREVIOUS +[Assistant]: Neural networks are composed of layers of nodes... +[User]: Can you explain backpropagation? +[CACHE POINT 3] - NEW PLACEMENT +[Assistant]: Backpropagation is an algorithm used to train neural networks... +``` + +**Note**: The algorithm preserved both cache points from the previous conversation and placed a new cache point for the new messages. This ensures maximum cache hit rates while still adapting to the growing conversation. + +### Example 4: Adding Messages (With Token Comparison) + +In this example, we'll demonstrate how the algorithm handles the case when new messages have a token count small enough that cache points should not be changed: + +**Updated Input Configuration with Previous Cache Points:** + +```javascript +const config = { + // Same modelInfo and systemPrompt as before + messages: [ + // Previous 10 messages... + { + role: "user", + content: "Can you explain the difference between supervised and unsupervised learning in detail?", + }, // ~80 tokens + { + role: "assistant", + content: + "Certainly! Supervised learning and unsupervised learning are two fundamental paradigms in machine learning with..", + }, // ~130 tokens + ], + usePromptCache: true, + // Pass the previous cache point placements from Example 3 + previousCachePointPlacements: [ + { + index: 2, // After the second user message + type: "message", + tokensCovered: 240, + }, + { + index: 6, // After the fourth user message + type: "message", + tokensCovered: 440, + }, + { + index: 8, // After the fifth user message + type: "message", + tokensCovered: 260, + }, + ], +} +``` + +**Execution Process for Example 4 with Token Comparison:** + +1. The algorithm detects that all cache points are used and new messages have been added. +2. It calculates the token count of the new messages (210 tokens). +3. It analyzes the token distribution between existing cache points and finds the smallest gap (260 tokens). +4. It compares the token count of new messages (210) with the smallest gap (260). +5. Since the new messages have less tokens than the smallest gap (210 < 260), it decides not to re-allocate cache points +6. All existing cache points are preserved, and no cache point is allocated for the new messages. + +**Output Cache Point Placements (Unchanged):** + +```javascript +;[ + { + index: 2, // After the second user message - PRESERVED + type: "message", + tokensCovered: 240, + }, + { + index: 6, // After the fourth user message - PRESERVED + type: "message", + tokensCovered: 440, + }, + { + index: 8, // After the fifth user message - PRESERVED + type: "message", + tokensCovered: 260, + }, +] +``` + +**Resulting Message Structure:** + +``` +[User]: Tell me about machine learning. +[Assistant]: Machine learning is a field of study... +[User]: What about deep learning? +[CACHE POINT 1] - PRESERVED +[Assistant]: Deep learning is a subset of machine learning... +[User]: How do neural networks work? +[Assistant]: Neural networks are composed of layers of nodes... +[User]: Can you explain backpropagation? +[CACHE POINT 2] - PRESERVED +[Assistant]: Backpropagation is an algorithm used to train neural networks... +[User]: What are some applications of deep learning? +[CACHE POINT 3] - PRESERVED +[Assistant]: Deep learning has many applications including... +[User]: Can you explain the difference between supervised and unsupervised learning in detail? +[Assistant]: Certainly! Supervised learning and unsupervised learning are two fundamental paradigms in machine learning with... +``` + +**Note**: In this case, the algorithm determined that the new messages are the smallest portion of the message history in comparison to existing cache points. Restructuring the cache points to make room to cache the new messages would be a net negative since it would not make use of 2 previously cached blocks, would have to re-write those 2 as a single cache point, and would write a new small cache point that would be chosen to be merged in the next round of messages. + +### Example 5: Adding Messages that reallocate cache points + +Now let's see what happens when we add messages with a larger token count: + +**Updated Input Configuration with Previous Cache Points:** + +```javascript +const config = { + // Same modelInfo and systemPrompt as before + messages: [ + // Previous 10 messages... + { + role: "user", + content: "Can you provide a detailed example of implementing a neural network for image classification?", + }, // ~100 tokens + { + role: "assistant", + content: + "Certainly! Here's a detailed example of implementing a convolutional neural network (CNN) for image classification using TensorFlow and Keras...", + }, // ~300 tokens + ], + usePromptCache: true, + // Pass the previous cache point placements from Example 3 + previousCachePointPlacements: [ + { + index: 2, // After the second user message + type: "message", + tokensCovered: 240, + }, + { + index: 6, // After the fourth user message + type: "message", + tokensCovered: 440, + }, + { + index: 8, // After the fifth user message + type: "message", + tokensCovered: 260, + }, + ], +} +``` + +**Execution Process for Example 5 with Token Comparison:** + +1. The algorithm detects that all cache points are used and new messages have been added. +2. It calculates the token count of the new messages (400 tokens). +3. It analyzes the token distribution between existing cache points and finds the smallest gap (260 tokens). +4. It calculates the required token threshold by applying a 20% increase to the smallest gap (260 \* 1.2 = 312). +5. It compares the token count of new messages (400) with this threshold (312). +6. Since the new messages have significantly more tokens than the threshold (400 > 312), it decides to combine cache points. +7. It identifies that the cache point at index 8 has the smallest token coverage (260 tokens). +8. It removes this cache point and places a new one after the new user message. + +**Output Cache Point Placements with Reallocation:** + +```javascript +;[ + { + index: 2, // After the second user message - PRESERVED + type: "message", + tokensCovered: 240, + }, + { + index: 6, // After the fourth user message - PRESERVED + type: "message", + tokensCovered: 440, + }, + { + index: 10, // After the sixth user message - NEW PLACEMENT + type: "message", + tokensCovered: 660, // Tokens from messages 7 through 10 (260 + 400) + }, +] +``` + +**Resulting Message Structure:** + +``` +[User]: Tell me about machine learning. +[Assistant]: Machine learning is a field of study... +[User]: What about deep learning? +[CACHE POINT 1] - PRESERVED +[Assistant]: Deep learning is a subset of machine learning... +[User]: How do neural networks work? +[Assistant]: Neural networks are composed of layers of nodes... +[User]: Can you explain backpropagation? +[CACHE POINT 2] - PRESERVED +[Assistant]: Backpropagation is an algorithm used to train neural networks... +[User]: What are some applications of deep learning? +[Assistant]: Deep learning has many applications including... +[User]: Can you provide a detailed example of implementing a neural network for image classification? +[CACHE POINT 3] - NEW PLACEMENT +[Assistant]: Certainly! Here's a detailed example of implementing a convolutional neural network (CNN) for image classification using TensorFlow and Keras... +``` + +**Note**: In this case, the algorithm determined that it would be beneficial to reallocate a cache point for the new messages since they contain more tokens than the smallest gap between existing cache points. This optimization ensures that the most token-heavy parts of the conversation are cached. + +**Important**: The `tokensCovered` value for each cache point represents the total number of tokens from the previous cache point (or the beginning of the conversation for the first cache point) up to the current cache point. For example, the cache point at index 10 covers 660 tokens, which includes all tokens from messages 7 through 10 (after the cache point at index 6 up to and including the cache point at index 10). + +### Key Observations + +1. **Simple Initial Placement Logic**: The last user message in the range that meets the minimum token threshold is set as a cache point. + +2. **User Message Boundary Requirement**: Cache points are placed exclusively after user messages, not after assistant messages. This ensures cache points are placed at natural conversation boundaries where the user has provided input. + +3. **Token Threshold Enforcement**: Each segment between cache points must meet the minimum token threshold (100 tokens in our examples) to be considered for caching. This is enforced by a guard clause that checks if the total tokens covered by a placement meets the minimum threshold. + +4. **Adaptive Placement for Growing Conversations**: As the conversation grows, the strategy adapts by preserving previous cache points when possible and only reallocating them when beneficial. + +5. **Token Comparison Optimization with Required Increase**: When all cache points are used and new messages are added, the algorithm compares the token count of new messages with the smallest combined token count of contiguous existing cache points, applying a required percentage increase (20%) to ensure reallocation is worth it. Cache points are only combined if the new messages have significantly more tokens than this threshold, ensuring that reallocation is only done when it results in a substantial net positive effect on caching efficiency. + +This adaptive approach ensures that as conversations grow, the caching strategy continues to optimize token usage and response times by strategically placing cache points at the most effective positions, while avoiding inefficient reallocations that could result in a net negative effect on caching performance. diff --git a/cline_docs/bedrock/model-identification.md b/cline_docs/bedrock/model-identification.md new file mode 100644 index 00000000000..7d778b186a6 --- /dev/null +++ b/cline_docs/bedrock/model-identification.md @@ -0,0 +1,445 @@ +# Bedrock Model Identification + +This document explains how model information is identified and managed in the Amazon Bedrock provider implementation (`bedrock.ts`). It focuses on the sequence of operations that determine the `costModelConfig` property, which is crucial for token counting, pricing, and other features. + +## Model Identification Flow + +The `costModelConfig` property is set through different paths depending on the input configuration and response data from Bedrock. Below is a sequence diagram of how model identification works: + +```mermaid +sequenceDiagram + participant Constructor + participant parseArn + participant parseBaseModelId + participant getModelById + participant getModel + + Constructor->>parseArn: Initialize (if awsCustomArn provided) + parseArn->>parseBaseModelId: Extract region prefix from modelId + parseBaseModelId-->>parseArn: Return modelId without prefix + parseArn->>parseArn: Determine if cross-region inference + parseArn-->>Constructor: Return arnInfo with modelId and crossRegionInference flag + Constructor->>getModel: Call getModel() + getModel->>getModelById: Lookup model + getModelById-->>getModel: Return model info + getModel-->>Constructor: Return model config + Constructor->>Constructor: Set this.costModelConfig +``` + +### During Stream Processing (with Prompt Router) + +```mermaid +sequenceDiagram + participant createMessage + participant parseArn + participant getModelById + + createMessage->>parseArn: Process stream event with invokedModelId + parseArn->>parseArn: Extract modelId + parseArn-->>createMessage: Return invokedModelArn + createMessage->>getModelById: Call getModelById with invokedModelArn.modelId + getModelById-->>createMessage: Return invokedModel + createMessage->>createMessage: Set invokedModel.id = modelConfig.id + createMessage->>createMessage: Set this.costModelConfig = invokedModel +``` + +## Input Examples and Resulting Values + +### Example 1: Standard Model Selection + +**Input:** + +```javascript +const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "us-east-1", +}) +``` + +**Sequence:** + +1. Constructor initializes with options +2. Constructor calls `getModel()` +3. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")` +4. `getModelById()` looks up the model in `bedrockModels` +5. `this.costModelConfig` is set to: + ```javascript + { + id: "anthropic.claude-3-5-sonnet-20241022-v2:0", + info: { + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other model properties... + } + } + ``` + +### Example 2: Custom ARN for Foundation Model + +**Input:** + +```javascript +const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "us-east-1", + awsCustomArn: "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0", +}) +``` + +**Sequence:** + +1. Constructor initializes with options +2. Constructor calls `parseArn("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0")` +3. `parseArn()` extracts: + ```javascript + { + isValid: true, + region: "us-east-1", + modelType: "foundation-model", + modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + crossRegionInference: false + } + ``` +4. Constructor sets `this.arnInfo` to the result +5. Constructor calls `getModel()` +6. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")` +7. `getModelById()` looks up the model in `bedrockModels` +8. `this.costModelConfig` is set to: + ```javascript + { + id: "anthropic.claude-3-5-sonnet-20241022-v2:0", // Note: ID is not the ARN since it's a foundation-model + info: { + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other model properties... + } + } + ``` + +### Example 3: Custom ARN for Prompt Router + +**Input:** + +```javascript +const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "us-west-2", + awsCustomArn: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", +}) +``` + +**Sequence:** + +1. Constructor initializes with options +2. Constructor calls `parseArn("arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router")` +3. `parseArn()` extracts: + ```javascript + { + isValid: true, + region: "us-west-2", + modelType: "prompt-router", + modelId: "my-router", + crossRegionInference: false + } + ``` +4. Constructor sets `this.arnInfo` to the result +5. Constructor calls `getModel()` +6. `getModel()` calls `getModelById("my-router")` +7. `getModelById()` doesn't find "my-router" in `bedrockModels`, returns default model info +8. Since `this.arnInfo.modelType` is "prompt-router" (not "foundation-model"), `getModel()` sets the ID to the full ARN +9. `this.costModelConfig` is set to: + ```javascript + { + id: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", // Full ARN as ID + info: { + // Default model info for prompt routers + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other model properties... + } + } + ``` + +### Example 4: Cross-Region Inference + +**Input:** + +```javascript +const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "eu-west-1", + awsUseCrossRegionInference: true, +}) +``` + +**Sequence:** + +1. Constructor initializes with options +2. Constructor calls `getModel()` +3. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")` +4. `getModelById()` looks up the model in `bedrockModels` +5. Since `awsUseCrossRegionInference` is true, `getModel()` gets the prefix for "eu-west-1" (which is "eu.") +6. `getModel()` prepends "eu." to the model ID +7. `this.costModelConfig` is set to: + ```javascript + { + id: "eu.anthropic.claude-3-5-sonnet-20241022-v2:0", // Note the "eu." prefix + info: { + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other model properties... + } + } + ``` + +### Example 5: Prompt Router with invokedModelId in Stream + +**Initial Input:** + +```javascript +const handler = new AwsBedrockHandler({ + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "us-west-2", + awsCustomArn: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", +}) +``` + +**Initial Sequence (same as Example 3):** + +1. `this.costModelConfig` is initially set to: + ```javascript + { + id: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", + info: { + // Default model info for prompt routers + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other properties... + } + } + ``` + +**Stream Event with invokedModelId:** + +```javascript +{ + trace: { + promptRouter: { + invokedModelId: "arn:aws:bedrock:us-west-2:123456789012:inference-profile/anthropic.claude-3-5-sonnet-20241022-v2:0", + usage: { + inputTokens: 150, + outputTokens: 250 + } + } + } +} +``` + +**Stream Processing Sequence:** + +1. `createMessage()` encounters the stream event with `invokedModelId` +2. It calls `parseArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/anthropic.claude-3-5-sonnet-20241022-v2:0")` +3. `parseArn()` extracts: + ```javascript + { + isValid: true, + region: "us-west-2", + modelType: "inference-profile", + modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + crossRegionInference: false + } + ``` +4. `createMessage()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")` +5. `getModelById()` looks up the model in `bedrockModels` and returns the model info +6. `createMessage()` sets `invokedModel.id` to the original router ID +7. `this.costModelConfig` is updated to: + ```javascript + { + id: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", // Keeps router ID + info: { + // Claude 3.5 Sonnet model info + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other Claude-specific properties... + } + } + ``` + +This ensures that: + +1. Subsequent requests continue to use the prompt router +2. Token counting and pricing use the actual model's rates +3. Context window and other model-specific properties are correctly set + +### Example 6: Cross-Region ARN with Region Prefix + +**Input:** + +```javascript +const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "us-east-1", + awsCustomArn: + "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0", +}) +``` + +**Sequence:** + +1. Constructor initializes with options +2. Constructor calls `parseArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0")` +3. `parseArn()` extracts region and calls `parseBaseModelId("us.anthropic.claude-3-5-sonnet-20241022-v2:0")` +4. `parseBaseModelId()` recognizes "us." as a region prefix and removes it +5. `parseArn()` returns: + ```javascript + { + isValid: true, + region: "us-west-2", + modelType: "inference-profile", + modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", // Note: prefix removed + crossRegionInference: true // Detected cross-region + } + ``` +6. Constructor sets `this.arnInfo` to the result and updates `this.options.awsRegion` to "us-west-2" +7. Constructor calls `getModel()` +8. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")` +9. `getModelById()` looks up the model in `bedrockModels` +10. Since `this.arnInfo.modelType` is "inference-profile" (not "foundation-model"), `getModel()` sets the ID to the full ARN +11. `this.costModelConfig` is set to: + ```javascript + { + id: "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0", // Full ARN + info: { + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other model properties... + } + } + ``` + +### Example 7: Single-Region ARN with Region Prefix (apne3) + +**Input:** + +```javascript +const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", + awsAccessKey: "ACCESS_KEY", + awsSecretKey: "SECRET_KEY", + awsRegion: "ap-northeast-3", // Osaka region + awsCustomArn: + "arn:aws:bedrock:ap-northeast-3:123456789012:inference-profile/apne3.anthropic.claude-3-5-sonnet-20241022-v2:0", +}) +``` + +**Sequence:** + +1. Constructor initializes with options +2. Constructor calls `parseArn("arn:aws:bedrock:ap-northeast-3:123456789012:inference-profile/apne3.anthropic.claude-3-5-sonnet-20241022-v2:0")` +3. `parseArn()` extracts region and calls `parseBaseModelId("apne3.anthropic.claude-3-5-sonnet-20241022-v2:0")` +4. `parseBaseModelId()` recognizes "apne3." as a region prefix and removes it +5. `parseArn()` returns: + ```javascript + { + isValid: true, + region: "ap-northeast-3", + modelType: "inference-profile", + modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", // Note: prefix removed + crossRegionInference: false // Not a cross-region prefix since apne3 maps to a single region + } + ``` +6. Constructor sets `this.arnInfo` to the result +7. Constructor calls `getModel()` +8. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")` +9. `getModelById()` looks up the model in `bedrockModels` +10. Since `this.arnInfo.modelType` is "inference-profile" (not "foundation-model"), `getModel()` sets the ID to the full ARN +11. `this.costModelConfig` is set to: + ```javascript + { + id: "arn:aws:bedrock:ap-northeast-3:123456789012:inference-profile/apne3.anthropic.claude-3-5-sonnet-20241022-v2:0", // Full ARN + info: { + maxTokens: 4096, + contextWindow: 128000, + inputPrice: 3, + outputPrice: 15, + // other model properties... + } + } + ``` + +## Region Prefixes + +The system recognizes these region prefixes for cross-region inference: + +| Prefix | Region ID | Description | Multi-Region | +| -------- | ---------------- | ------------------------- | ------------ | +| "us." | "us-east-1" | US East (N. Virginia) | Yes | +| "use1." | "us-east-1" | US East (N. Virginia) | No | +| "use2." | "us-east-2" | US East (Ohio) | No | +| "usw2." | "us-west-2" | US West (Oregon) | No | +| "eu." | "eu-west-1" | Europe (Ireland) | Yes | +| "euw1." | "eu-west-1" | Europe (Ireland) | No | +| "ap." | "ap-southeast-1" | Asia Pacific (Singapore) | Yes | +| "apne1." | "ap-northeast-1" | Asia Pacific (Tokyo) | No | +| "apne3." | "ap-northeast-3" | Asia Pacific (Osaka) | No | +| "ca." | "ca-central-1" | Canada (Central) | Yes | +| "sa." | "sa-east-1" | South America (São Paulo) | Yes | +| "apac." | "ap-southeast-1" | Default APAC region | Yes | +| "emea." | "eu-west-1" | Default EMEA region | Yes | +| "amer." | "us-east-1" | Default Americas region | Yes | + +These prefixes are used to: + +1. Identify and strip region prefixes from model IDs in `parseBaseModelId()` +2. Add appropriate region prefixes when cross-region inference is enabled in `getModel()` + +**Note on Multi-Region Prefixes:** + +- Prefixes marked as "Multi-Region" (like "us.", "eu.", "ap.", etc.) set the `crossRegionInference` flag to `true` when detected in an ARN +- These prefixes typically represent a geographic area with multiple AWS regions +- Single-region prefixes (like "apne3.", "use1.", etc.) set the `crossRegionInference` flag to `false` +- The `crossRegionInference` flag affects how the system handles region-specific model configurations + +## Summary + +The Bedrock provider's model identification system follows these key principles: + +1. **ARN Parsing**: Extracts model ID, region, and resource type from ARNs +2. **Model Lookup**: Uses the extracted model ID to find model information +3. **ID Preservation**: + - For foundation models: Uses the model ID directly + - For other resources (prompt routers, inference profiles): Uses the full ARN as the ID +4. **Cross-Region Handling**: Adds or removes region prefixes as needed +5. **Dynamic Updates**: Updates model information when a prompt router provides an invokedModelId + +This system ensures that: + +- The correct model ID is used for API requests +- Accurate model information is used for token counting and pricing +- Cross-region inference works correctly +- Prompt routers can dynamically select models while maintaining proper tracking diff --git a/cline_docs/settings.md b/cline_docs/settings.md index f4b06826023..d1e98d0cc05 100644 --- a/cline_docs/settings.md +++ b/cline_docs/settings.md @@ -1,24 +1,32 @@ ## For All Settings -1. Add the setting to ExtensionMessage.ts: +1. Add the setting to schema definitions: - - Add the setting to the ExtensionState interface - - Make it required if it has a default value, optional if it can be undefined - - Example: `preferredLanguage: string` + - Add the item to `globalSettingsSchema` in `src/schemas/index.ts` + - Add the item to `globalSettingsRecord` in `src/schemas/index.ts` + - Example: `terminalCommandDelay: z.number().optional(),` -2. Add test coverage: - - Add the setting to mockState in ClineProvider.test.ts +2. Add the setting to type definitions: + + - Add the item to `src/exports/types.ts` + - Add the item to `src/exports/roo-code.d.ts` + - Add the setting to `src/shared/ExtensionMessage.ts` + - Add the setting to the WebviewMessage type in `src/shared/WebviewMessage.ts` + - Example: `terminalCommandDelay?: number | undefined` + +3. Add test coverage: + - Add the setting to mockState in src/core/webview/**tests**/ClineProvider.test.ts - Add test cases for setting persistence and state updates - Ensure all tests pass before submitting changes ## For Checkbox Settings -1. Add the message type to WebviewMessage.ts: +1. Add the message type to src/shared/WebviewMessage.ts: - Add the setting name to the WebviewMessage type's type union - Example: `| "multisearchDiffEnabled"` -2. Add the setting to ExtensionStateContext.tsx: +2. Add the setting to webview-ui/src/context/ExtensionStateContext.tsx: - Add the setting to the ExtensionStateContextType interface - Add the setter function to the interface @@ -32,7 +40,7 @@ } ``` -3. Add the setting to ClineProvider.ts: +3. Add the setting to src/core/webview/ClineProvider.ts: - Add the setting name to the GlobalStateKey type union - Add the setting to the Promise.all array in getState @@ -48,7 +56,7 @@ break ``` -4. Add the checkbox UI to SettingsView.tsx: +4. Add the checkbox UI to webview-ui/src/components/settings/SettingsView.tsx: - Import the setting and its setter from ExtensionStateContext - Add the VSCodeCheckbox component with the setting's state and onChange handler @@ -63,21 +71,44 @@ ``` -5. Add the setting to handleSubmit in SettingsView.tsx: - - Add a vscode.postMessage call to send the setting's value when clicking Done +5. Add the setting to handleSubmit in webview-ui/src/components/settings/SettingsView.tsx: + + - Add a vscode.postMessage call to send the setting's value when clicking Save + - This step is critical for persistence - without it, the setting will not be saved when the user clicks Save - Example: ```typescript vscode.postMessage({ type: "multisearchDiffEnabled", bool: multisearchDiffEnabled }) ``` +6. Style Considerations: + - Use the VSCodeCheckbox component from @vscode/webview-ui-toolkit/react instead of HTML input elements + - Wrap each checkbox in a div element for proper spacing + - Use a span with className="font-medium" for the checkbox label inside the VSCodeCheckbox component + - Place the description in a separate div with className="text-vscode-descriptionForeground text-sm mt-1" + - Maintain consistent spacing between configuration options + - Example: + ```typescript +
+ setCachedStateField("terminalPowershellCounter", e.target.checked)} + data-testid="terminal-powershell-counter-checkbox"> + {t("settings:terminal.powershellCounter.label")} + +
+ {t("settings:terminal.powershellCounter.description")} +
+
+ ``` + ## For Select/Dropdown Settings -1. Add the message type to WebviewMessage.ts: +1. Add the message type to src/shared/WebviewMessage.ts: - Add the setting name to the WebviewMessage type's type union - Example: `| "preferredLanguage"` -2. Add the setting to ExtensionStateContext.tsx: +2. Add the setting to webview-ui/src/context/ExtensionStateContext.tsx: - Add the setting to the ExtensionStateContextType interface - Add the setter function to the interface @@ -91,13 +122,14 @@ } ``` -3. Add the setting to ClineProvider.ts: +3. Add the setting to src/core/webview/ClineProvider.ts: - Add the setting name to the GlobalStateKey type union - Add the setting to the Promise.all array in getState - Add the setting to the return value in getState with a default value - Add the setting to the destructured variables in getStateToPostToWebview - Add the setting to the return value in getStateToPostToWebview + - This step is critical for UI display - without it, the setting will not be displayed in the UI - Add a case in setWebviewMessageListener to handle the setting's message type - Example: ```typescript @@ -107,7 +139,7 @@ break ``` -4. Add the select UI to SettingsView.tsx: +4. Add the select UI to webview-ui/src/components/settings/SettingsView.tsx: - Import the setting and its setter from ExtensionStateContext - Add the select element with appropriate styling to match VSCode's theme @@ -132,7 +164,7 @@ ``` -5. Add the setting to handleSubmit in SettingsView.tsx: +5. Add the setting to handleSubmit in webview-ui/src/components/settings/SettingsView.tsx: - Add a vscode.postMessage call to send the setting's value when clicking Done - Example: ```typescript @@ -146,3 +178,266 @@ These steps ensure that: - The setting's value is properly synchronized between the webview and extension - The setting has a proper UI representation in the settings view - Test coverage is maintained for the new setting + +## Adding a New Configuration Item: Summary of Required Changes + +To add a new configuration item to the system, the following changes are necessary: + +1. **Feature-Specific Class** (if applicable) + + - For settings that affect specific features (e.g., Terminal, Browser, etc.) + - Add a static property to store the value + - Add getter/setter methods to access and modify the value + +2. **Schema Definition** + + - Add the item to globalSettingsSchema in src/schemas/index.ts + - Add the item to globalSettingsRecord in src/schemas/index.ts + +3. **Type Definitions** + + - Add the item to src/exports/types.ts + - Add the item to src/exports/roo-code.d.ts + - Add the item to src/shared/ExtensionMessage.ts + - Add the item to src/shared/WebviewMessage.ts + +4. **UI Component** + + - Create or update a component in webview-ui/src/components/settings/ + - Add appropriate slider/input controls with min/max/step values + - Ensure the props are passed correctly to the component in webview-ui/src/components/settings/SettingsView.tsx + - Update the component's props interface to include the new settings + +5. **Translations** + + - Add label and description in webview-ui/src/i18n/locales/en/settings.json + - Update all other languages + - If any language content is changed, synchronize all other languages with that change + - Translations must be performed within "translation" mode so change modes for that purpose + +6. **State Management** + + - Add the item to the destructuring in SettingsView.tsx + - Add the item to the handleSubmit function in webview-ui/src/components/settings/SettingsView.tsx + - Add the item to getStateToPostToWebview in src/core/webview/ClineProvider.ts + - Add the item to getState in src/core/webview/ClineProvider.ts with appropriate default values + - Add the item to the initialization in resolveWebviewView in src/core/webview/ClineProvider.ts + +7. **Message Handling** + + - Add a case for the item in src/core/webview/webviewMessageHandler.ts + +8. **Implementation-Specific Logic** + + - Implement any feature-specific behavior triggered by the setting + - Examples: + - Environment variables for terminal settings + - API configuration changes for provider settings + - UI behavior modifications for display settings + +9. **Testing** + + - Add test cases for the new settings in appropriate test files + - Verify settings persistence and state updates + +10. **Ensuring Settings Persistence Across Reload** + + To ensure settings persist across application reload, several key components must be properly configured: + + 1. **Initial State in ExtensionStateContextProvider**: + + - Add the setting to the initial state in the useState call + - Example: + ```typescript + const [state, setState] = useState({ + // existing settings... + newSetting: false, // Default value for the new setting + }) + ``` + + 2. **State Loading in ClineProvider**: + + - Add the setting to the getState method to load it from storage + - Example: + ```typescript + return { + // existing settings... + newSetting: stateValues.newSetting ?? false, + } + ``` + + 3. **State Initialization in resolveWebviewView**: + + - Add the setting to the initialization in resolveWebviewView + - Example: + ```typescript + this.getState().then( + ({ + // existing settings... + newSetting, + }) => { + // Initialize the setting with its stored value or default + FeatureClass.setNewSetting(newSetting ?? false) + }, + ) + ``` + + 4. **State Transmission to Webview**: + + - Add the setting to the getStateToPostToWebview method + - Example: + ```typescript + return { + // existing settings... + newSetting: newSetting ?? false, + } + ``` + + 5. **Setter Method in ExtensionStateContext**: + - Add the setter method to the contextValue object + - Example: + ```typescript + const contextValue: ExtensionStateContextType = { + // existing properties and methods... + setNewSetting: (value) => setState((prevState) => ({ ...prevState, newSetting: value })), + } + ``` + +11. **Debugging Settings Persistence Issues** + + If a setting is not persisting across reload, check the following: + + 1. **Complete Chain of Persistence**: + + - Verify that the setting is added to all required locations: + - globalSettingsSchema and globalSettingsRecord in src/schemas/index.ts + - Initial state in ExtensionStateContextProvider + - getState method in src/core/webview/ClineProvider.ts + - getStateToPostToWebview method in src/core/webview/ClineProvider.ts + - resolveWebviewView method in src/core/webview/ClineProvider.ts (if feature-specific) + - A break in any part of this chain can prevent persistence + + 2. **Default Values Consistency**: + + - Ensure default values are consistent across all locations + - Inconsistent defaults can cause unexpected behavior + + 3. **Message Handling**: + + - Confirm the src/core/webview/webviewMessageHandler.ts has a case for the setting + - Verify the message type matches what's sent from the UI + + 4. **UI Integration**: + + - Check that the setting is included in the handleSubmit function in webview-ui/src/components/settings/SettingsView.tsx + - Ensure the UI component correctly updates the state + + 5. **Type Definitions**: + + - Verify the setting is properly typed in all relevant interfaces + - Check for typos in property names across different files + + 6. **Storage Mechanism**: + - For complex settings, ensure proper serialization/deserialization + - Check that the setting is being correctly stored in VSCode's globalState + + These checks help identify and resolve common issues with settings persistence. + +12. **Advanced Troubleshooting: The Complete Settings Persistence Chain** + +Settings persistence requires a complete chain of state management across multiple components. Understanding this chain is critical for both humans and AI to effectively troubleshoot persistence issues: + +1. **Schema Definition (Entry Point)**: + + - Settings must be properly defined in `globalSettingsSchema` and `globalSettingsRecord` + - Enum values should use proper zod schemas: `z.enum(["value1", "value2"])` + - Example: + + ```typescript + // In src/schemas/index.ts + export const globalSettingsSchema = z.object({ + // Existing settings... + commandRiskLevel: z.enum(["readOnly", "reversibleChanges", "complexChanges"]).optional(), + }) + + const globalSettingsRecord: GlobalSettingsRecord = { + // Existing settings... + commandRiskLevel: undefined, + } + ``` + +2. **UI Component (User Interaction)**: + + - Must use consistent components (Select vs. select) with other similar settings + - Must use `setCachedStateField` for state updates, not direct state setting + - Must generate the correct message type through `vscode.postMessage` + - Example: + ```tsx + // In a settings component + + ``` + +3. **Message Handler (State Saving)**: + + - Must use correct message type in `src/core/webview/webviewMessageHandler.ts` + - Must use `updateGlobalState` with properly typed values + - Must call `postStateToWebview` after updates + - Example: + ```typescript + // In src/core/webview/webviewMessageHandler.ts + case "commandRiskLevel": + await updateGlobalState( + "commandRiskLevel", + (message.text ?? "readOnly") as "readOnly" | "reversibleChanges" | "complexChanges" + ) + await provider.postStateToWebview() + break + ``` + +4. **State Retrieval (Reading State)**: + + - In `getState`, state must be properly retrieved from stateValues + - In `getStateToPostToWebview`, the setting must be in the destructured parameters + - The setting must be included in the return value + - Use `contextProxy.getGlobalState` for direct access when needed + - Example: + + ```typescript + // In src/core/webview/ClineProvider.ts getStateToPostToWebview + const { + // Other state properties... + commandRiskLevel, + } = await this.getState() + + return { + // Other state properties... + commandRiskLevel: commandRiskLevel ?? "readOnly", + } + ``` + +5. **Debugging Strategies**: + + - **Follow the State Flow**: Watch the setting's value at each step in the chain + - **Type Safety**: Ensure the same type is used throughout the chain + - **Component Consistency**: Use the same pattern as other working settings + - **Check Return Values**: Ensure the setting is included in all return objects + - **State vs. Configuration**: Understand when to use state vs. VSCode configuration + +6. **Common Pitfalls**: + - **Type Mismatch**: Using string where an enum is expected + - **Chain Breaks**: Missing the setting in return objects + - **UI Inconsistency**: Using different component patterns + - **DefaultValue Issues**: Inconsistent default values across components + - **Missing Schema**: Not adding to schema or record definitions + +Remember: A break at ANY point in this chain can cause persistence failures. When troubleshooting, systematically check each link in the chain to identify where the issue occurs. diff --git a/e2e/VSCODE_INTEGRATION_TESTS.md b/e2e/VSCODE_INTEGRATION_TESTS.md index 452c00c2268..51e3e0dade4 100644 --- a/e2e/VSCODE_INTEGRATION_TESTS.md +++ b/e2e/VSCODE_INTEGRATION_TESTS.md @@ -156,7 +156,7 @@ while (Date.now() - startTime < timeout) { 6. **Grading**: When grading tests, use the `Grade:` format to ensure the test is graded correctly (See modes.test.ts for an example). ```typescript -await globalThis.api.startNewTask( - `Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`, -) +await globalThis.api.startNewTask({ + text: `Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`, +}) ``` diff --git a/e2e/package.json b/e2e/package.json index d4932f8a076..aec42f93f1d 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -3,13 +3,12 @@ "version": "0.1.0", "private": true, "scripts": { - "build": "cd .. && npm run compile && npm run build:webview", - "compile": "rm -rf out && tsc -p tsconfig.json", - "lint": "eslint src --ext ts", + "lint": "eslint src/**/*.ts", "check-types": "tsc --noEmit", - "test": "npm run compile && npx dotenvx run -f .env.local -- node ./out/runTest.js", - "ci": "npm run build && npm run test", - "clean": "rimraf out" + "test": "npm run build && npx dotenvx run -f .env.local -- node ./out/runTest.js", + "ci": "npm run vscode-test && npm run test", + "build": "rimraf out && tsc -p tsconfig.json", + "vscode-test": "cd .. && npm run vscode-test" }, "dependencies": {}, "devDependencies": { diff --git a/e2e/src/suite/extension.test.ts b/e2e/src/suite/extension.test.ts index bc09f816f5e..e701993594c 100644 --- a/e2e/src/suite/extension.test.ts +++ b/e2e/src/suite/extension.test.ts @@ -1,13 +1,7 @@ import * as assert from "assert" import * as vscode from "vscode" -suite("Roo Code Extension", () => { - test("OPENROUTER_API_KEY environment variable is set", () => { - if (!process.env.OPENROUTER_API_KEY) { - assert.fail("OPENROUTER_API_KEY environment variable is not set") - } - }) - +suite("Agent Extension", () => { test("Commands should be registered", async () => { const expectedCommands = [ "roo-cline.plusButtonClicked", diff --git a/e2e/src/suite/index.ts b/e2e/src/suite/index.ts index 3a0fe27255a..1a3e2656623 100644 --- a/e2e/src/suite/index.ts +++ b/e2e/src/suite/index.ts @@ -3,9 +3,9 @@ import Mocha from "mocha" import { glob } from "glob" import * as vscode from "vscode" -import { RooCodeAPI } from "../../../src/exports/roo-code" +import type { RooCodeAPI } from "../../../src/exports/roo-code" -import { waitUntilReady } from "./utils" +import { waitFor } from "./utils" declare global { var api: RooCodeAPI @@ -18,18 +18,16 @@ export async function run() { throw new Error("Extension not found") } - // Activate the extension if it's not already active. const api = extension.isActive ? extension.exports : await extension.activate() - // TODO: We might want to support a "free" model out of the box so - // contributors can run the tests locally without having to pay. await api.setConfiguration({ - apiProvider: "openrouter", + apiProvider: "openrouter" as const, openRouterApiKey: process.env.OPENROUTER_API_KEY!, - openRouterModelId: "anthropic/claude-3.5-sonnet", + openRouterModelId: "google/gemini-2.0-flash-001", }) - await waitUntilReady({ api }) + await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") + await waitFor(() => api.isReady()) // Expose the API to the tests. globalThis.api = api diff --git a/e2e/src/suite/modes.test.ts b/e2e/src/suite/modes.test.ts index 6e130efd54f..0759fdbe4bd 100644 --- a/e2e/src/suite/modes.test.ts +++ b/e2e/src/suite/modes.test.ts @@ -1,9 +1,11 @@ import * as assert from "assert" -import { getCompletion, getMessage, sleep, waitForCompletion, waitUntilAborted } from "./utils" +import type { ClineMessage } from "../../../src/exports/roo-code" -suite("Roo Code Modes", () => { - test("Should handle switching modes correctly", async function () { +import { waitUntilCompleted } from "./utils" + +suite("Agent Modes", () => { + test("Should handle switching modes correctly", async () => { const api = globalThis.api /** @@ -11,34 +13,33 @@ suite("Roo Code Modes", () => { */ const switchModesPrompt = - "For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode. " + - "Do not start with the current mode." + "For each mode (Architect, Ask, Debug) respond with the mode name and what it specializes in after switching to that mode." - await api.setConfiguration({ mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }) - const switchModesTaskId = await api.startNewTask(switchModesPrompt) - await waitForCompletion({ api, taskId: switchModesTaskId, timeout: 60_000 }) + let messages: ClineMessage[] = [] - /** - * Grade the response. - */ + const modeSwitches: string[] = [] - const gradePrompt = - `Given this prompt: ${switchModesPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ` + - api - .getMessages(switchModesTaskId) - .filter(({ type }) => type === "say") - .map(({ text }) => text ?? "") - .join("\n") + api.on("taskModeSwitched", (_taskId, mode) => { + console.log("taskModeSwitched", mode) + modeSwitches.push(mode) + }) - await api.setConfiguration({ mode: "Ask" }) - const gradeTaskId = await api.startNewTask(gradePrompt) - await waitForCompletion({ api, taskId: gradeTaskId, timeout: 60_000 }) + api.on("message", ({ message }) => { + if (message.type === "say" && message.partial === false) { + messages.push(message) + } + }) - const completion = getCompletion({ api, taskId: gradeTaskId }) - const match = completion?.text?.match(/Grade: (\d+)/) - const score = parseInt(match?.[1] ?? "0") - assert.ok(score >= 7 && score <= 10, `Grade must be between 7 and 10 - ${completion?.text}`) + const switchModesTaskId = await api.startNewTask({ + configuration: { mode: "code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, + text: switchModesPrompt, + }) + await waitUntilCompleted({ api, taskId: switchModesTaskId }) await api.cancelCurrentTask() + + assert.ok(modeSwitches.includes("architect")) + assert.ok(modeSwitches.includes("ask")) + assert.ok(modeSwitches.includes("debug")) }) }) diff --git a/e2e/src/suite/subtasks.test.ts b/e2e/src/suite/subtasks.test.ts index 2a621979085..cb18401d34c 100644 --- a/e2e/src/suite/subtasks.test.ts +++ b/e2e/src/suite/subtasks.test.ts @@ -1,13 +1,24 @@ import * as assert from "assert" -import { sleep, waitFor, getMessage, waitForCompletion } from "./utils" +import type { ClineMessage } from "../../../src/exports/roo-code" -suite("Roo Code Subtasks", () => { - test("Should handle subtask cancellation and resumption correctly", async function () { +import { sleep, waitFor, waitUntilCompleted } from "./utils" + +suite("Agent Subtasks", () => { + test("Should handle subtask cancellation and resumption correctly", async () => { const api = globalThis.api + const messages: Record = {} + + api.on("message", ({ taskId, message }) => { + if (message.type === "say" && message.partial === false) { + messages[taskId] = messages[taskId] || [] + messages[taskId].push(message) + } + }) + await api.setConfiguration({ - mode: "Code", + mode: "ask", alwaysAllowModeSwitch: true, alwaysAllowSubtasks: true, autoApprovalEnabled: true, @@ -17,18 +28,19 @@ suite("Roo Code Subtasks", () => { const childPrompt = "You are a calculator. Respond only with numbers. What is the square root of 9?" // Start a parent task that will create a subtask. - const parentTaskId = await api.startNewTask( - "You are the parent task. " + + const parentTaskId = await api.startNewTask({ + text: + "You are the parent task. " + `Create a subtask by using the new_task tool with the message '${childPrompt}'.` + "After creating the subtask, wait for it to complete and then respond 'Parent task resumed'.", - ) + }) let spawnedTaskId: string | undefined = undefined // Wait for the subtask to be spawned and then cancel it. api.on("taskSpawned", (_, childTaskId) => (spawnedTaskId = childTaskId)) await waitFor(() => !!spawnedTaskId) - await sleep(2_000) // Give the task a chance to start and populate the history. + await sleep(1_000) // Give the task a chance to start and populate the history. await api.cancelCurrentTask() // Wait a bit to ensure any task resumption would have happened. @@ -37,35 +49,27 @@ suite("Roo Code Subtasks", () => { // The parent task should not have resumed yet, so we shouldn't see // "Parent task resumed". assert.ok( - getMessage({ - api, - taskId: parentTaskId, - include: "Parent task resumed", - exclude: "You are the parent task", - }) === undefined, + messages[parentTaskId].find(({ type, text }) => type === "say" && text === "Parent task resumed") === + undefined, "Parent task should not have resumed after subtask cancellation", ) // Start a new task with the same message as the subtask. - const anotherTaskId = await api.startNewTask(childPrompt) - await waitForCompletion({ api, taskId: anotherTaskId }) + const anotherTaskId = await api.startNewTask({ text: childPrompt }) + await waitUntilCompleted({ api, taskId: anotherTaskId }) // Wait a bit to ensure any task resumption would have happened. await sleep(2_000) // The parent task should still not have resumed. assert.ok( - getMessage({ - api, - taskId: parentTaskId, - include: "Parent task resumed", - exclude: "You are the parent task", - }) === undefined, + messages[parentTaskId].find(({ type, text }) => type === "say" && text === "Parent task resumed") === + undefined, "Parent task should not have resumed after subtask cancellation", ) // Clean up - cancel all tasks. await api.clearCurrentTask() - await waitForCompletion({ api, taskId: parentTaskId }) + await waitUntilCompleted({ api, taskId: parentTaskId }) }) }) diff --git a/e2e/src/suite/task.test.ts b/e2e/src/suite/task.test.ts index 840654a5082..d2add414c35 100644 --- a/e2e/src/suite/task.test.ts +++ b/e2e/src/suite/task.test.ts @@ -1,10 +1,33 @@ -import { waitForMessage } from "./utils" +import * as assert from "assert" -suite("Roo Code Task", () => { - test("Should handle prompt and response correctly", async function () { +import type { ClineMessage } from "../../../src/exports/roo-code" + +import { waitUntilCompleted } from "./utils" + +suite("Agent Task", () => { + test("Should handle prompt and response correctly", async () => { const api = globalThis.api - await api.setConfiguration({ mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }) - const taskId = await api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'") - await waitForMessage({ api, taskId, include: "My name is Roo" }) + + const messages: ClineMessage[] = [] + + api.on("message", ({ message }) => { + if (message.type === "say" && message.partial === false) { + messages.push(message) + } + }) + + const taskId = await api.startNewTask({ + configuration: { mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, + text: "Hello world, what is your name? Respond with 'My name is ...'", + }) + + await waitUntilCompleted({ api, taskId }) + + assert.ok( + !!messages.find( + ({ say, text }) => (say === "completion_result" || say === "text") && text?.includes("My name is Roo"), + ), + `Completion should include "My name is Roo"`, + ) }) }) diff --git a/e2e/src/suite/utils.ts b/e2e/src/suite/utils.ts index a84ddd814f9..784d299820c 100644 --- a/e2e/src/suite/utils.ts +++ b/e2e/src/suite/utils.ts @@ -1,6 +1,4 @@ -import * as vscode from "vscode" - -import { RooCodeAPI } from "../../../src/exports/roo-code" +import type { RooCodeAPI } from "../../../src/exports/roo-code" type WaitForOptions = { timeout?: number @@ -41,15 +39,6 @@ export const waitFor = ( ]) } -type WaitUntilReadyOptions = WaitForOptions & { - api: RooCodeAPI -} - -export const waitUntilReady = async ({ api, ...options }: WaitUntilReadyOptions) => { - await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") - await waitFor(() => api.isReady(), options) -} - type WaitUntilAbortedOptions = WaitForOptions & { api: RooCodeAPI taskId: string @@ -61,39 +50,15 @@ export const waitUntilAborted = async ({ api, taskId, ...options }: WaitUntilAbo await waitFor(() => set.has(taskId), options) } -export const waitForCompletion = async ({ - api, - taskId, - ...options -}: WaitUntilReadyOptions & { - taskId: string -}) => waitFor(() => !!getCompletion({ api, taskId }), options) - -export const getCompletion = ({ api, taskId }: { api: RooCodeAPI; taskId: string }) => - api.getMessages(taskId).find(({ say, partial }) => say === "completion_result" && partial === false) - -type WaitForMessageOptions = WaitUntilReadyOptions & { - taskId: string - include: string - exclude?: string -} - -export const waitForMessage = async ({ api, taskId, include, exclude, ...options }: WaitForMessageOptions) => - waitFor(() => !!getMessage({ api, taskId, include, exclude }), options) - -type GetMessageOptions = { +type WaitUntilCompletedOptions = WaitForOptions & { api: RooCodeAPI taskId: string - include: string - exclude?: string } -export const getMessage = ({ api, taskId, include, exclude }: GetMessageOptions) => - api - .getMessages(taskId) - .find( - ({ type, text }) => - type === "say" && text && text.includes(include) && (!exclude || !text.includes(exclude)), - ) +export const waitUntilCompleted = async ({ api, taskId, ...options }: WaitUntilCompletedOptions) => { + const set = new Set() + api.on("taskCompleted", (taskId) => set.add(taskId)) + await waitFor(() => set.has(taskId), options) +} export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/esbuild.js b/esbuild.js index 6fc0c247291..c6a555f5a52 100644 --- a/esbuild.js +++ b/esbuild.js @@ -29,15 +29,23 @@ const copyWasmFiles = { name: "copy-wasm-files", setup(build) { build.onEnd(() => { - // tree sitter - const sourceDir = path.join(__dirname, "node_modules", "web-tree-sitter") - const targetDir = path.join(__dirname, "dist") - - // Copy tree-sitter.wasm - fs.copyFileSync(path.join(sourceDir, "tree-sitter.wasm"), path.join(targetDir, "tree-sitter.wasm")) - - // Copy language-specific WASM files - const languageWasmDir = path.join(__dirname, "node_modules", "tree-sitter-wasms", "out") + const nodeModulesDir = path.join(__dirname, "node_modules") + const distDir = path.join(__dirname, "dist") + + // tiktoken + fs.copyFileSync( + path.join(nodeModulesDir, "tiktoken", "tiktoken_bg.wasm"), + path.join(distDir, "tiktoken_bg.wasm"), + ) + + // tree-sitter WASM + fs.copyFileSync( + path.join(nodeModulesDir, "web-tree-sitter", "tree-sitter.wasm"), + path.join(distDir, "tree-sitter.wasm"), + ) + + // language-specific tree-sitter WASMs + const languageWasmDir = path.join(nodeModulesDir, "tree-sitter-wasms", "out") const languages = [ "typescript", "tsx", @@ -57,7 +65,7 @@ const copyWasmFiles = { languages.forEach((lang) => { const filename = `tree-sitter-${lang}.wasm` - fs.copyFileSync(path.join(languageWasmDir, filename), path.join(targetDir, filename)) + fs.copyFileSync(path.join(languageWasmDir, filename), path.join(distDir, filename)) }) }) }, diff --git a/evals/.env.sample b/evals/.env.sample new file mode 100644 index 00000000000..9130089f56b --- /dev/null +++ b/evals/.env.sample @@ -0,0 +1 @@ +BENCHMARKS_DB_PATH=file:/tmp/evals.db diff --git a/evals/.gitignore b/evals/.gitignore new file mode 100644 index 00000000000..c517160168b --- /dev/null +++ b/evals/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +node_modules +.pnp +.pnp.js + +# Local env files +.env +.env.* +!.env.sample + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Next.js +next-env.d.ts + +# Build Outputs +.next/ +out/ +build +dist +*.tsbuildinfo + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +# Evals +evals diff --git a/evals/.npmrc b/evals/.npmrc new file mode 100644 index 00000000000..30816ae7c74 --- /dev/null +++ b/evals/.npmrc @@ -0,0 +1,2 @@ +# https://github.com/vercel/next.js/issues/68805 +public-hoist-pattern[]=*libsql* diff --git a/evals/.tool-versions b/evals/.tool-versions new file mode 100644 index 00000000000..18277f93113 --- /dev/null +++ b/evals/.tool-versions @@ -0,0 +1,4 @@ +python 3.13.2 +golang 1.24.2 +rust 1.85.1 +nodejs 20.18.1 diff --git a/evals/README.md b/evals/README.md new file mode 100644 index 00000000000..9f343a6e09d --- /dev/null +++ b/evals/README.md @@ -0,0 +1,21 @@ +# Run Roo Code Evals + +## Get Started + +NOTE: This is MacOS only for now! + +Clone the Roo Code repo: + +```sh +git clone https://github.com/RooVetGit/Roo-Code.git +cd Roo-Code +``` + +Run the setup script: + +```sh +cd evals +./scripts/setup.sh +``` + +Navigate to [localhost:3000](http://localhost:3000/) in your browser. diff --git a/evals/apps/cli/eslint.config.mjs b/evals/apps/cli/eslint.config.mjs new file mode 100644 index 00000000000..01fea8d98bf --- /dev/null +++ b/evals/apps/cli/eslint.config.mjs @@ -0,0 +1,4 @@ +import { config } from "@evals/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default [...config] diff --git a/evals/apps/cli/package.json b/evals/apps/cli/package.json new file mode 100644 index 00000000000..bcd88d5c8b9 --- /dev/null +++ b/evals/apps/cli/package.json @@ -0,0 +1,27 @@ +{ + "name": "@evals/cli", + "private": true, + "type": "module", + "scripts": { + "lint": "eslint src/**/*.ts --max-warnings=0", + "check-types": "tsc --noEmit", + "format": "prettier --write src", + "dev": "dotenvx run -f ../../.env -- tsx src/index.ts" + }, + "dependencies": { + "@evals/db": "workspace:^", + "@evals/ipc": "workspace:^", + "@evals/lib": "workspace:^", + "@evals/types": "workspace:^", + "execa": "^9.5.2", + "gluegun": "^5.1.2", + "p-map": "^7.0.3", + "p-wait-for": "^5.0.2", + "ps-tree": "^1.2.0" + }, + "devDependencies": { + "@evals/eslint-config": "workspace:^", + "@evals/typescript-config": "workspace:^", + "@types/ps-tree": "^1.1.6" + } +} diff --git a/evals/apps/cli/src/exercises.ts b/evals/apps/cli/src/exercises.ts new file mode 100644 index 00000000000..03c3ca28e9f --- /dev/null +++ b/evals/apps/cli/src/exercises.ts @@ -0,0 +1,31 @@ +import * as path from "path" +import * as fs from "fs" + +import { filesystem } from "gluegun" + +import { type ExerciseLanguage, exerciseLanguages } from "@evals/types" + +import { exercisesPath } from "./paths.js" + +let exercisesByLanguage: Record | null = null + +export const getExercises = () => { + if (exercisesByLanguage !== null) { + return exercisesByLanguage + } + + const getLanguageExercises = (language: ExerciseLanguage) => + fs.existsSync(path.resolve(exercisesPath, language)) + ? filesystem + .subdirectories(path.resolve(exercisesPath, language)) + .map((exercise) => path.basename(exercise)) + .filter((exercise) => !exercise.startsWith(".")) + : [] + + exercisesByLanguage = exerciseLanguages.reduce( + (collect, language) => ({ ...collect, [language]: getLanguageExercises(language) }), + {} as Record, + ) + + return exercisesByLanguage +} diff --git a/evals/apps/cli/src/index.ts b/evals/apps/cli/src/index.ts new file mode 100644 index 00000000000..d2b19ab21ca --- /dev/null +++ b/evals/apps/cli/src/index.ts @@ -0,0 +1,519 @@ +import * as fs from "fs" +import * as path from "path" +import * as os from "os" + +import pMap from "p-map" +import pWaitFor from "p-wait-for" +import { execa, parseCommandString } from "execa" +import { build, filesystem, GluegunPrompt, GluegunToolbox } from "gluegun" +import psTree from "ps-tree" + +import { + type ExerciseLanguage, + exerciseLanguages, + RooCodeEventName, + IpcOrigin, + IpcMessageType, + TaskCommandName, + rooCodeDefaults, + EvalEventName, +} from "@evals/types" +import { + type Run, + findRun, + createRun, + finishRun, + type Task, + createTask, + getTasks, + updateTask, + createTaskMetrics, + updateTaskMetrics, + createToolError, +} from "@evals/db" +import { IpcServer, IpcClient } from "@evals/ipc" + +import { __dirname, extensionDevelopmentPath, exercisesPath } from "./paths.js" +import { getExercises } from "./exercises.js" + +type TaskResult = { success: boolean } +type TaskPromise = Promise + +const TASK_START_DELAY = 10 * 1_000 +const TASK_TIMEOUT = 5 * 60 * 1_000 +const UNIT_TEST_TIMEOUT = 2 * 60 * 1_000 + +const testCommands: Record = { + go: { commands: ["go test"] }, // timeout 15s bash -c "cd '$dir' && go test > /dev/null 2>&1" + java: { commands: ["./gradlew test"] }, // timeout --foreground 15s bash -c "cd '$dir' && ./gradlew test > /dev/null 2>&1" + javascript: { commands: ["pnpm install", "pnpm test"] }, // timeout 15s bash -c "cd '$dir' && pnpm install >/dev/null 2>&1 && pnpm test >/dev/null 2>&1" + python: { commands: ["uv run python3 -m pytest -o markers=task *_test.py"] }, // timeout 15s bash -c "cd '$dir' && uv run python3 -m pytest -o markers=task *_test.py" + rust: { commands: ["cargo test"] }, // timeout 15s bash -c "cd '$dir' && cargo test > /dev/null 2>&1" +} + +const run = async (toolbox: GluegunToolbox) => { + const { config, prompt } = toolbox + + let { language, exercise } = config + + if (![undefined, ...exerciseLanguages, "all"].includes(language)) { + throw new Error(`Language is invalid: ${language}`) + } + + if (!["undefined", "string"].includes(typeof exercise)) { + throw new Error(`Exercise is invalid: ${exercise}`) + } + + const id = config.runId ? Number(config.runId) : undefined + let run: Run + + if (id) { + run = await findRun(id) + } else { + run = await createRun({ + model: rooCodeDefaults.openRouterModelId!, + pid: process.pid, + socketPath: path.resolve(os.tmpdir(), `roo-code-evals-${crypto.randomUUID().slice(0, 8)}.sock`), + }) + + if (language === "all") { + for (const language of exerciseLanguages) { + const exercises = getExercises()[language as ExerciseLanguage] + + await pMap(exercises, (exercise) => createTask({ runId: run.id, language, exercise }), { + concurrency: run.concurrency, + }) + } + } else if (exercise === "all") { + const exercises = getExercises()[language as ExerciseLanguage] + await pMap(exercises, (exercise) => createTask({ runId: run.id, language, exercise }), { + concurrency: run.concurrency, + }) + } else { + language = language || (await askLanguage(prompt)) + exercise = exercise || (await askExercise(prompt, language)) + await createTask({ runId: run.id, language, exercise }) + } + } + + const tasks = await getTasks(run.id) + + if (!tasks[0]) { + throw new Error("No tasks found.") + } + + await execa({ cwd: exercisesPath })`git config user.name "Agent"` + await execa({ cwd: exercisesPath })`git config user.email "support@roocode.com"` + await execa({ cwd: exercisesPath })`git checkout -f` + await execa({ cwd: exercisesPath })`git clean -fd` + await execa({ cwd: exercisesPath })`git checkout -b runs/${run.id}-${crypto.randomUUID().slice(0, 8)} main` + + fs.writeFileSync( + path.resolve(exercisesPath, "settings.json"), + JSON.stringify({ ...rooCodeDefaults, ...run.settings }, null, 2), + ) + + const server = new IpcServer(run.socketPath, () => {}) + server.listen() + + const runningPromises: TaskPromise[] = [] + + const processTask = async (task: Task, delay = 0) => { + if (task.finishedAt === null) { + await new Promise((resolve) => setTimeout(resolve, delay)) + await runExercise({ run, task, server }) + } + + if (task.passed === null) { + const passed = await runUnitTest({ task }) + await updateTask(task.id, { passed }) + + server.broadcast({ + type: IpcMessageType.TaskEvent, + origin: IpcOrigin.Server, + data: { eventName: passed ? EvalEventName.Pass : EvalEventName.Fail, taskId: task.id }, + }) + + return { success: passed } + } else { + return { success: task.passed } + } + } + + const processTaskResult = async (task: Task, promise: TaskPromise) => { + const index = runningPromises.indexOf(promise) + + if (index > -1) { + runningPromises.splice(index, 1) + } + } + + let delay = TASK_START_DELAY + + for (const task of tasks) { + const promise = processTask(task, delay) + delay = delay + TASK_START_DELAY + runningPromises.push(promise) + promise.then(() => processTaskResult(task, promise)) + + if (runningPromises.length >= run.concurrency) { + delay = 0 + await Promise.race(runningPromises) + } + } + + await Promise.all(runningPromises) + + const result = await finishRun(run.id) + console.log(`${Date.now()} [cli#run]`, result) + + await execa({ cwd: exercisesPath })`git add .` + await execa({ cwd: exercisesPath })`git commit -m ${`Run #${run.id}`} --no-verify` +} + +const runExercise = async ({ run, task, server }: { run: Run; task: Task; server: IpcServer }): TaskPromise => { + const { language, exercise } = task + const prompt = fs.readFileSync(path.resolve(exercisesPath, `prompts/${language}.md`), "utf-8") + const dirname = path.dirname(run.socketPath) + const workspacePath = path.resolve(exercisesPath, language, exercise) + const taskSocketPath = path.resolve(dirname, `${dirname}/task-${task.id}.sock`) + + // If debugging: + // Use --wait --log trace or --verbose. + // Don't await execa and store result as subprocess. + // subprocess.stdout.pipe(process.stdout) + + console.log(`${Date.now()} [cli#runExercise] Opening new VS Code window at ${workspacePath}`) + + await execa({ + env: { + ROO_CODE_IPC_SOCKET_PATH: taskSocketPath, + }, + shell: "/bin/bash", + })`code --disable-workspace-trust -n ${workspacePath}` + + // Give VSCode some time to spawn before connecting to its unix socket. + await new Promise((resolve) => setTimeout(resolve, 3_000)) + console.log(`${Date.now()} [cli#runExercise] Connecting to ${taskSocketPath}`) + const client = new IpcClient(taskSocketPath) + + try { + await pWaitFor(() => client.isReady, { interval: 250, timeout: 5_000 }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + console.log(`${Date.now()} [cli#runExercise | ${language} / ${exercise}] unable to connect`) + client.disconnect() + return { success: false } + } + + let taskStartedAt = Date.now() + let taskFinishedAt: number | undefined + let taskMetricsId: number | undefined + let rooTaskId: string | undefined + let isClientDisconnected = false + + const ignoreEvents: Record<"broadcast" | "log", (RooCodeEventName | EvalEventName)[]> = { + broadcast: [RooCodeEventName.Message], + log: [RooCodeEventName.Message, RooCodeEventName.TaskTokenUsageUpdated, RooCodeEventName.TaskAskResponded], + } + + client.on(IpcMessageType.TaskEvent, async (taskEvent) => { + const { eventName, payload } = taskEvent + + if (!ignoreEvents.broadcast.includes(eventName)) { + server.broadcast({ + type: IpcMessageType.TaskEvent, + origin: IpcOrigin.Server, + relayClientId: client.clientId!, + data: { ...taskEvent, taskId: task.id }, + }) + } + + if (!ignoreEvents.log.includes(eventName)) { + console.log( + `${Date.now()} [cli#runExercise | ${language} / ${exercise}] taskEvent -> ${eventName}`, + payload, + ) + } + + if (eventName === RooCodeEventName.TaskStarted) { + taskStartedAt = Date.now() + + const taskMetrics = await createTaskMetrics({ + cost: 0, + tokensIn: 0, + tokensOut: 0, + tokensContext: 0, + duration: 0, + cacheWrites: 0, + cacheReads: 0, + }) + + await updateTask(task.id, { taskMetricsId: taskMetrics.id, startedAt: new Date() }) + + taskStartedAt = Date.now() + taskMetricsId = taskMetrics.id + rooTaskId = payload[0] + } + + if (eventName === RooCodeEventName.TaskToolFailed) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_taskId, toolName, error] = payload + await createToolError({ taskId: task.id, toolName, error }) + } + + if ( + (eventName === RooCodeEventName.TaskTokenUsageUpdated || eventName === RooCodeEventName.TaskCompleted) && + taskMetricsId + ) { + const duration = Date.now() - taskStartedAt + + const { totalCost, totalTokensIn, totalTokensOut, contextTokens, totalCacheWrites, totalCacheReads } = + payload[1] + + await updateTaskMetrics(taskMetricsId, { + cost: totalCost, + tokensIn: totalTokensIn, + tokensOut: totalTokensOut, + tokensContext: contextTokens, + duration, + cacheWrites: totalCacheWrites ?? 0, + cacheReads: totalCacheReads ?? 0, + }) + } + + if (eventName === RooCodeEventName.TaskCompleted && taskMetricsId) { + const toolUsage = payload[2] + await updateTaskMetrics(taskMetricsId, { toolUsage }) + } + + if (eventName === RooCodeEventName.TaskAborted || eventName === RooCodeEventName.TaskCompleted) { + taskFinishedAt = Date.now() + await updateTask(task.id, { finishedAt: new Date() }) + } + }) + + client.on(IpcMessageType.Disconnect, async () => { + console.log(`${Date.now()} [cli#runExercise | ${language} / ${exercise}] disconnect`) + isClientDisconnected = true + }) + + console.log(`${Date.now()} [cli#runExercise | ${language} / ${exercise}] starting task`) + + client.sendMessage({ + type: IpcMessageType.TaskCommand, + origin: IpcOrigin.Client, + clientId: client.clientId!, + data: { + commandName: TaskCommandName.StartNewTask, + data: { + configuration: { + ...rooCodeDefaults, + openRouterApiKey: process.env.OPENROUTER_API_KEY!, + ...run.settings, + }, + text: prompt, + newTab: true, + }, + }, + }) + + try { + await pWaitFor(() => !!taskFinishedAt || isClientDisconnected, { interval: 1_000, timeout: TASK_TIMEOUT }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + console.log(`${Date.now()} [cli#runExercise | ${language} / ${exercise}] time limit reached`) + + // Cancel the task. + if (rooTaskId && !isClientDisconnected) { + client.sendMessage({ + type: IpcMessageType.TaskCommand, + origin: IpcOrigin.Client, + clientId: client.clientId!, + data: { commandName: TaskCommandName.CancelTask, data: rooTaskId }, + }) + + // Allow some time for the task to cancel. + await new Promise((resolve) => setTimeout(resolve, 5_000)) + } + + await updateTask(task.id, { finishedAt: new Date() }) + } + + if (!isClientDisconnected) { + if (rooTaskId) { + client.sendMessage({ + type: IpcMessageType.TaskCommand, + origin: IpcOrigin.Client, + clientId: client.clientId!, + data: { commandName: TaskCommandName.CloseTask, data: rooTaskId }, + }) + + // Allow some time for the window to close. + await new Promise((resolve) => setTimeout(resolve, 2_000)) + } + + client.disconnect() + } + + return { success: !!taskFinishedAt } +} + +const runUnitTest = async ({ task }: { task: Task }) => { + const cmd = testCommands[task.language] + const exercisePath = path.resolve(exercisesPath, task.language, task.exercise) + const cwd = cmd.cwd ? path.resolve(exercisePath, cmd.cwd) : exercisePath + const commands = cmd.commands.map((cs) => parseCommandString(cs)) + + let passed = true + + for (const command of commands) { + try { + console.log( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] running "${command.join(" ")}"`, + ) + + const subprocess = execa({ cwd, shell: true, reject: false })`${command}` + + const timeout = setTimeout(async () => { + const descendants = await new Promise((resolve, reject) => { + psTree(subprocess.pid!, (err, children) => { + if (err) { + reject(err) + } + + resolve(children.map((p) => parseInt(p.PID))) + }) + }) + + console.log( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] "${command.join(" ")}": unit tests timed out, killing ${subprocess.pid} + ${JSON.stringify(descendants)}`, + ) + + if (descendants.length > 0) { + for (const descendant of descendants) { + try { + console.log( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] killing ${descendant}`, + ) + + await execa`kill -9 ${descendant}` + } catch (error) { + console.error( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] Error killing descendant processes:`, + error, + ) + } + } + } + + console.log( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] killing ${subprocess.pid}`, + ) + + try { + await execa`kill -9 ${subprocess.pid!}` + } catch (error) { + console.error( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] Error killing process:`, + error, + ) + } + }, UNIT_TEST_TIMEOUT) + + const result = await subprocess + + console.log( + `${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}] "${command.join(" ")}" result -> ${JSON.stringify(result)}`, + ) + + clearTimeout(timeout) + + if (result.failed) { + passed = false + break + } + } catch (error) { + console.log(`${Date.now()} [cli#runUnitTest | ${task.language} / ${task.exercise}]`, error) + passed = false + break + } + } + + return passed +} + +const askLanguage = async (prompt: GluegunPrompt) => { + const { language } = await prompt.ask<{ language: ExerciseLanguage }>({ + type: "select", + name: "language", + message: "Which language?", + choices: [...exerciseLanguages], + }) + + return language +} + +const askExercise = async (prompt: GluegunPrompt, language: ExerciseLanguage) => { + const exercises = filesystem.subdirectories(path.join(exercisesPath, language)) + + if (exercises.length === 0) { + throw new Error(`No exercises found for ${language}`) + } + + const { exercise } = await prompt.ask<{ exercise: string }>({ + type: "select", + name: "exercise", + message: "Which exercise?", + choices: exercises.map((exercise) => path.basename(exercise)).filter((exercise) => !exercise.startsWith(".")), + }) + + return exercise +} + +const main = async () => { + const cli = build() + .brand("cli") + .src(__dirname) + .help() + .version() + .command({ + name: "run", + description: "Run an eval", + run: ({ config, parameters }) => { + config.language = parameters.first + config.exercise = parameters.second + + if (parameters.options["runId"]) { + config.runId = parameters.options["runId"] + } + }, + }) + .defaultCommand() + .create() + + const toolbox = await cli.run(process.argv) + const { command } = toolbox + + switch (command?.name) { + case "run": + await run(toolbox) + break + } + + process.exit(0) +} + +if (!fs.existsSync(extensionDevelopmentPath)) { + console.error(`"extensionDevelopmentPath" does not exist.`) + process.exit(1) +} + +if (!fs.existsSync(exercisesPath)) { + console.error( + `Exercises path does not exist. Please run "git clone https://github.com/cte/Roo-Code-Benchmark.git exercises".`, + ) + process.exit(1) +} + +main() diff --git a/evals/apps/cli/src/paths.ts b/evals/apps/cli/src/paths.ts new file mode 100644 index 00000000000..10bf4e3d7a6 --- /dev/null +++ b/evals/apps/cli/src/paths.ts @@ -0,0 +1,7 @@ +import * as path from "path" +import { fileURLToPath } from "url" + +export const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export const extensionDevelopmentPath = path.resolve(__dirname, "..", "..", "..", "..") +export const exercisesPath = path.resolve(extensionDevelopmentPath, "..", "evals") diff --git a/evals/apps/cli/tsconfig.json b/evals/apps/cli/tsconfig.json new file mode 100644 index 00000000000..48fa99573e3 --- /dev/null +++ b/evals/apps/cli/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@evals/typescript-config/base.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/evals/apps/web/components.json b/evals/apps/web/components.json new file mode 100644 index 00000000000..5bcedb31416 --- /dev/null +++ b/evals/apps/web/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/evals/apps/web/eslint.config.mjs b/evals/apps/web/eslint.config.mjs new file mode 100644 index 00000000000..835f477287e --- /dev/null +++ b/evals/apps/web/eslint.config.mjs @@ -0,0 +1,17 @@ +import { nextJsConfig } from "@evals/eslint-config/next-js" + +/** @type {import("eslint").Linter.Config} */ +export default [ + ...nextJsConfig, + { + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + caughtErrorsIgnorePattern: "^_", + }, + ], + }, + }, +] diff --git a/evals/apps/web/next.config.ts b/evals/apps/web/next.config.ts new file mode 100644 index 00000000000..9da1646d2a9 --- /dev/null +++ b/evals/apps/web/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next" + +const nextConfig: NextConfig = { + /* config options here */ +} + +export default nextConfig diff --git a/evals/apps/web/package.json b/evals/apps/web/package.json new file mode 100644 index 00000000000..d52770bbb5d --- /dev/null +++ b/evals/apps/web/package.json @@ -0,0 +1,58 @@ +{ + "name": "@evals/web", + "private": true, + "scripts": { + "lint": "next lint", + "check-types": "tsc -b", + "dev": "dotenvx run -f ../../.env -- next dev --turbopack", + "format": "prettier --write src", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@evals/db": "workspace:^", + "@evals/ipc": "workspace:^", + "@evals/types": "workspace:^", + "@hookform/resolvers": "^4.1.3", + "@radix-ui/react-alert-dialog": "^1.1.7", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.7", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-scroll-area": "^1.2.3", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slider": "^1.2.4", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", + "@tanstack/react-query": "^5.69.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.0", + "fuzzysort": "^3.1.0", + "lucide-react": "^0.479.0", + "next": "15.2.2", + "next-themes": "^0.4.6", + "p-map": "^7.0.3", + "ps-tree": "^1.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-hook-form": "^7.54.2", + "react-use": "^17.6.0", + "sonner": "^2.0.2", + "tailwind-merge": "^3.0.2", + "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "@evals/eslint-config": "workspace:^", + "@evals/typescript-config": "workspace:^", + "@tailwindcss/postcss": "^4", + "@types/ps-tree": "^1.1.6", + "@types/react": "^19", + "@types/react-dom": "^19", + "tailwindcss": "^4" + } +} diff --git a/evals/apps/web/postcss.config.mjs b/evals/apps/web/postcss.config.mjs new file mode 100644 index 00000000000..78452aadce7 --- /dev/null +++ b/evals/apps/web/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +} + +export default config diff --git a/evals/apps/web/public/.gitkeep b/evals/apps/web/public/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/evals/apps/web/src/app/api/runs/[id]/stream/route.ts b/evals/apps/web/src/app/api/runs/[id]/stream/route.ts new file mode 100644 index 00000000000..faf111047cc --- /dev/null +++ b/evals/apps/web/src/app/api/runs/[id]/stream/route.ts @@ -0,0 +1,39 @@ +import type { NextRequest } from "next/server" + +import { findRun } from "@evals/db" +import { IpcMessageType } from "@evals/types" +import { IpcClient } from "@evals/ipc" + +import { SSEStream } from "@/lib/server/sse-stream" + +export const dynamic = "force-dynamic" + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params + const requestId = crypto.randomUUID() + const stream = new SSEStream() + const run = await findRun(Number(id)) + const client = new IpcClient(run.socketPath, () => {}) + + const write = async (data: string | object) => { + // console.log(`[stream#${requestId}] write`, data) + const success = await stream.write(data) + + if (!success) { + client.disconnect() + } + } + + console.log(`[stream#${requestId}] connect`) + client.on(IpcMessageType.Connect, () => write("connect")) + client.on(IpcMessageType.Disconnect, () => write("disconnect")) + client.on(IpcMessageType.TaskEvent, write) + + request.signal.addEventListener("abort", () => { + console.log(`[stream#${requestId}] abort`) + client.disconnect() + stream.close().catch(() => {}) + }) + + return stream.getResponse() +} diff --git a/evals/apps/web/src/app/api/runs/route.ts b/evals/apps/web/src/app/api/runs/route.ts new file mode 100644 index 00000000000..68e4c844fd0 --- /dev/null +++ b/evals/apps/web/src/app/api/runs/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server" + +import { createRun } from "@evals/db" + +export async function POST(request: Request) { + try { + const run = await createRun(await request.json()) + return NextResponse.json({ run }, { status: 201 }) + } catch (error) { + return NextResponse.json({ error: (error as Error).message }, { status: 500 }) + } +} diff --git a/evals/apps/web/src/app/api/tasks/route.ts b/evals/apps/web/src/app/api/tasks/route.ts new file mode 100644 index 00000000000..3613dd27182 --- /dev/null +++ b/evals/apps/web/src/app/api/tasks/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server" + +import { createTask } from "@evals/db" + +export async function POST(request: Request) { + try { + const task = await createTask(await request.json()) + return NextResponse.json({ task }, { status: 201 }) + } catch (error) { + return NextResponse.json({ error: (error as Error).message }, { status: 500 }) + } +} diff --git a/evals/apps/web/src/app/favicon.ico b/evals/apps/web/src/app/favicon.ico new file mode 100644 index 00000000000..718d6fea483 Binary files /dev/null and b/evals/apps/web/src/app/favicon.ico differ diff --git a/evals/apps/web/src/app/globals.css b/evals/apps/web/src/app/globals.css new file mode 100644 index 00000000000..8c12f0d1d2c --- /dev/null +++ b/evals/apps/web/src/app/globals.css @@ -0,0 +1,141 @@ +@import "tailwindcss"; + +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(23.66% 0.0198 271.79); + --foreground: oklch(75.15% 0.0477 278.41); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: var(--primary); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(29.33% 0.0295 276.18); + --primary-foreground: var(--accent); + --secondary: var(--primary); + --secondary-foreground: var(--foreground); + --muted: oklch(28.27% 0.0207 273.06); + --muted-foreground: oklch(75.15% 0.0477 278.41 / 75%); + --accent: oklch(70.21% 0.1813 328.71); + --accent-foreground: oklch(1 0 0 / 75%); + --destructive: oklch(72.14% 0.1616 15.49); + --border: var(--primary); + --input: var(--primary); + --ring: oklch(83.63% 0.1259 176.52); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --animate-hop: hop 0.8s ease-in-out infinite; + + @keyframes hop { + 0%, + 100% { + transform: none; + animation-timing-function: cubic-bezier(0.8, 0, 1, 1); + } + 50% { + transform: translateY(-8px); + animation-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + } +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + html, + body { + height: 100%; + } + body { + @apply bg-background text-foreground; + scrollbar-color: rgba(0, 0, 0, 0.2) transparent; /* Firefox */ + scrollbar-width: thin; + } +} diff --git a/evals/apps/web/src/app/home.tsx b/evals/apps/web/src/app/home.tsx new file mode 100644 index 00000000000..90f9d02b3ec --- /dev/null +++ b/evals/apps/web/src/app/home.tsx @@ -0,0 +1,158 @@ +"use client" + +import { useCallback, useState, useRef } from "react" +import { useRouter } from "next/navigation" +import Link from "next/link" +import { Ellipsis, Rocket } from "lucide-react" + +import type { Run, TaskMetrics } from "@evals/db" + +import { deleteRun } from "@/lib/server/runs" +import { formatCurrency, formatDuration, formatTokens, formatToolUsageSuccessRate } from "@/lib/formatters" +import { + Button, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui" + +export function Home({ runs }: { runs: (Run & { taskMetrics: TaskMetrics | null })[] }) { + const router = useRouter() + + const [deleteRunId, setDeleteRunId] = useState() + const continueRef = useRef(null) + + const onConfirmDelete = useCallback(async () => { + if (!deleteRunId) { + return + } + + try { + await deleteRun(deleteRunId) + setDeleteRunId(undefined) + } catch (error) { + console.error(error) + } + }, [deleteRunId]) + + return ( + <> + + + + Model + Passed + Failed + % Correct + Tokens In / Out + Diff Edits + Cost + Duration + + + + + {runs.length ? ( + runs.map(({ taskMetrics, ...run }) => ( + + {run.model} + {run.passed} + {run.failed} + + {run.passed + run.failed > 0 && ( + {((run.passed / (run.passed + run.failed)) * 100).toFixed(1)}% + )} + + + {taskMetrics && ( +
+
{formatTokens(taskMetrics.tokensIn)}
/ +
{formatTokens(taskMetrics.tokensOut)}
+
+ )} +
+ + {taskMetrics?.toolUsage?.apply_diff && ( +
+
{taskMetrics.toolUsage.apply_diff.attempts}
+
/
+
{formatToolUsageSuccessRate(taskMetrics.toolUsage.apply_diff)}
+
+ )} +
+ {taskMetrics && formatCurrency(taskMetrics.cost)} + {taskMetrics && formatDuration(taskMetrics.duration)} + + + + + + View Tasks + + { + setDeleteRunId(run.id) + setTimeout(() => continueRef.current?.focus(), 0) + }}> + Delete + + + + +
+ )) + ) : ( + + + No eval runs yet. + + one now. + + + )} +
+
+ + setDeleteRunId(undefined)}> + + + Are you sure? + This action cannot be undone. + + + Cancel + + Continue + + + + + + ) +} diff --git a/evals/apps/web/src/app/layout.tsx b/evals/apps/web/src/app/layout.tsx new file mode 100644 index 00000000000..6e0ba930188 --- /dev/null +++ b/evals/apps/web/src/app/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next" +import { Geist, Geist_Mono } from "next/font/google" + +import { ThemeProvider, ReactQueryProvider } from "@/components/providers" +import { Toaster } from "@/components/ui" +import { Header } from "@/components/layout/header" + +import "./globals.css" + +const fontSans = Geist({ variable: "--font-sans", subsets: ["latin"] }) +const fontMono = Geist_Mono({ variable: "--font-mono", subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Agent Evals", +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + + +
+ {children} + + + + + + ) +} diff --git a/evals/apps/web/src/app/page.tsx b/evals/apps/web/src/app/page.tsx new file mode 100644 index 00000000000..8988bde11fe --- /dev/null +++ b/evals/apps/web/src/app/page.tsx @@ -0,0 +1,10 @@ +import { getRuns } from "@evals/db" + +import { Home } from "./home" + +export const dynamic = "force-dynamic" + +export default async function Page() { + const runs = await getRuns() + return +} diff --git a/evals/apps/web/src/app/runs/[id]/connection-status.tsx b/evals/apps/web/src/app/runs/[id]/connection-status.tsx new file mode 100644 index 00000000000..60d6141a531 --- /dev/null +++ b/evals/apps/web/src/app/runs/[id]/connection-status.tsx @@ -0,0 +1,69 @@ +"use client" + +import { useCallback } from "react" +import { Skull } from "lucide-react" + +import { killProcessTree } from "@/lib/server/processes" +import { EventSourceStatus } from "@/hooks/use-event-source" +import { useProcessList } from "@/hooks/use-process-tree" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui" + +type ConnectionStatusProps = { + status: EventSourceStatus + pid: number | null +} + +export const ConnectionStatus = (connectionStatus: ConnectionStatusProps) => { + const { data: pids, isLoading } = useProcessList(connectionStatus.pid) + const status = isLoading ? "loading" : pids === null ? "dead" : connectionStatus.status + + const onKill = useCallback(async () => { + if (connectionStatus.pid) { + await killProcessTree(connectionStatus.pid) + window.location.reload() + } + }, [connectionStatus.pid]) + + return ( +
+
+
+
Status:
+
{status}
+
+
+
+
+
+
+
+
PIDs:
+
{connectionStatus.pid}
+ {status === "connected" && ( + <> +
{pids?.join(" ")}
+ + + )} +
+
+ ) +} diff --git a/evals/apps/web/src/app/runs/[id]/page.tsx b/evals/apps/web/src/app/runs/[id]/page.tsx new file mode 100644 index 00000000000..4b0a2edd8bf --- /dev/null +++ b/evals/apps/web/src/app/runs/[id]/page.tsx @@ -0,0 +1,14 @@ +import { findRun } from "@evals/db" + +import { Run } from "./run" + +export default async function Page({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params + const run = await findRun(Number(id)) + + return ( +
+ +
+ ) +} diff --git a/evals/apps/web/src/app/runs/[id]/run.tsx b/evals/apps/web/src/app/runs/[id]/run.tsx new file mode 100644 index 00000000000..9d5e74f98bc --- /dev/null +++ b/evals/apps/web/src/app/runs/[id]/run.tsx @@ -0,0 +1,111 @@ +"use client" + +import { useMemo } from "react" +import { LoaderCircle } from "lucide-react" + +import * as db from "@evals/db" + +import { formatCurrency, formatDuration, formatTokens } from "@/lib/formatters" +import { useRunStatus } from "@/hooks/use-run-status" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui" + +import { TaskStatus } from "./task-status" +import { ConnectionStatus } from "./connection-status" + +type TaskMetrics = Pick + +export function Run({ run }: { run: db.Run }) { + const { tasks, status, tokenUsage, usageUpdatedAt } = useRunStatus(run) + + const taskMetrics: Record = useMemo(() => { + const metrics: Record = {} + + tasks?.forEach((task) => { + const usage = tokenUsage.get(task.id) + + if (task.finishedAt && task.taskMetrics) { + metrics[task.id] = task.taskMetrics + } else if (usage) { + metrics[task.id] = { + tokensIn: usage.totalTokensIn, + tokensOut: usage.totalTokensOut, + tokensContext: usage.contextTokens, + duration: usage.duration ?? 0, + cost: usage.totalCost, + } + } + }) + + return metrics + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tasks, tokenUsage, usageUpdatedAt]) + + return ( + <> +
+
+
+
{run.model}
+ {run.description &&
{run.description}
} +
+ {!run.taskMetricsId && } +
+ {!tasks ? ( + + ) : ( + + + + Exercise + Tokens In / Out + Context + Duration + Cost + + + + {tasks.map((task) => ( + + +
+ +
+ {task.language}/{task.exercise} +
+
+
+ {taskMetrics[task.id] ? ( + <> + +
+
{formatTokens(taskMetrics[task.id]!.tokensIn)}
/ +
{formatTokens(taskMetrics[task.id]!.tokensOut)}
+
+
+ + {formatTokens(taskMetrics[task.id]!.tokensContext)} + + + {taskMetrics[task.id]!.duration + ? formatDuration(taskMetrics[task.id]!.duration) + : "-"} + + + {formatCurrency(taskMetrics[task.id]!.cost)} + + + ) : ( + + )} +
+ ))} +
+
+ )} +
+ + ) +} diff --git a/evals/apps/web/src/app/runs/[id]/task-status.tsx b/evals/apps/web/src/app/runs/[id]/task-status.tsx new file mode 100644 index 00000000000..2e0b28b419f --- /dev/null +++ b/evals/apps/web/src/app/runs/[id]/task-status.tsx @@ -0,0 +1,20 @@ +import { CircleCheck, CircleDashed, CircleSlash, LoaderCircle } from "lucide-react" + +import { type Task } from "@evals/db" + +type TaskStatusProps = { + task: Task + running: boolean +} + +export const TaskStatus = ({ task, running }: TaskStatusProps) => { + return task.passed === false ? ( + + ) : task.passed === true ? ( + + ) : running ? ( + + ) : ( + + ) +} diff --git a/evals/apps/web/src/app/runs/new/new-run.tsx b/evals/apps/web/src/app/runs/new/new-run.tsx new file mode 100644 index 00000000000..71b7422ff31 --- /dev/null +++ b/evals/apps/web/src/app/runs/new/new-run.tsx @@ -0,0 +1,399 @@ +"use client" + +import { useCallback, useRef, useState } from "react" +import { useRouter } from "next/navigation" +import { z } from "zod" +import { useForm, FormProvider } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import fuzzysort from "fuzzysort" +import { toast } from "sonner" +import { X, Rocket, Check, ChevronsUpDown, HardDriveUpload, CircleCheck } from "lucide-react" + +import { globalSettingsSchema, providerSettingsSchema, rooCodeDefaults } from "@evals/types" + +import { createRun } from "@/lib/server/runs" +import { + createRunSchema as formSchema, + type CreateRun as FormValues, + CONCURRENCY_MIN, + CONCURRENCY_MAX, + CONCURRENCY_DEFAULT, +} from "@/lib/schemas" +import { cn } from "@/lib/utils" +import { useOpenRouterModels } from "@/hooks/use-open-router-models" +import { useExercises } from "@/hooks/use-exercises" +import { + Button, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Textarea, + Tabs, + TabsList, + TabsTrigger, + MultiSelect, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + Popover, + PopoverContent, + PopoverTrigger, + ScrollArea, + Slider, +} from "@/components/ui" + +import { SettingsDiff } from "./settings-diff" + +export function NewRun() { + const router = useRouter() + + const [mode, setMode] = useState<"openrouter" | "settings">("openrouter") + + const [modelSearchValue, setModelSearchValue] = useState("") + const [modelPopoverOpen, setModelPopoverOpen] = useState(false) + const modelSearchResultsRef = useRef>(new Map()) + const modelSearchValueRef = useRef("") + const models = useOpenRouterModels() + + const exercises = useExercises() + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + model: "", + description: "", + suite: "full", + exercises: [], + settings: undefined, + concurrency: CONCURRENCY_DEFAULT, + }, + }) + + const { + setValue, + clearErrors, + watch, + formState: { isSubmitting }, + } = form + + const [model, suite, settings] = watch(["model", "suite", "settings", "concurrency"]) + + const onSubmit = useCallback( + async (values: FormValues) => { + try { + if (mode === "openrouter") { + const openRouterModel = models.data?.find(({ id }) => id === model) + + if (!openRouterModel) { + throw new Error("Model not found.") + } + + const openRouterModelId = openRouterModel.id + values.settings = { ...(values.settings || {}), openRouterModelId } + } + + const { id } = await createRun(values) + router.push(`/runs/${id}`) + } catch (e) { + toast.error(e instanceof Error ? e.message : "An unknown error occurred.") + } + }, + [mode, model, models.data, router], + ) + + const onFilterModels = useCallback( + (value: string, search: string) => { + if (modelSearchValueRef.current !== search) { + modelSearchValueRef.current = search + modelSearchResultsRef.current.clear() + + for (const { + obj: { id }, + score, + } of fuzzysort.go(search, models.data || [], { + key: "name", + })) { + modelSearchResultsRef.current.set(id, score) + } + } + + return modelSearchResultsRef.current.get(value) ?? 0 + }, + [models.data], + ) + + const onSelectModel = useCallback( + (model: string) => { + setValue("model", model) + setModelPopoverOpen(false) + }, + [setValue], + ) + + const onImportSettings = useCallback( + async (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + + if (!file) { + return + } + + clearErrors("settings") + + try { + const { providerProfiles, globalSettings } = z + .object({ + providerProfiles: z.object({ + currentApiConfigName: z.string(), + apiConfigs: z.record(z.string(), providerSettingsSchema), + }), + globalSettings: globalSettingsSchema, + }) + .parse(JSON.parse(await file.text())) + + const providerSettings = providerProfiles.apiConfigs[providerProfiles.currentApiConfigName] ?? {} + + const { + apiProvider, + apiModelId, + openRouterModelId, + glamaModelId, + requestyModelId, + unboundModelId, + ollamaModelId, + lmStudioModelId, + openAiModelId, + } = providerSettings + + switch (apiProvider) { + case "anthropic": + case "bedrock": + case "deepseek": + case "gemini": + case "mistral": + case "openai-native": + case "xai": + case "vertex": + setValue("model", apiModelId ?? "") + break + case "openrouter": + setValue("model", openRouterModelId ?? "") + break + case "glama": + setValue("model", glamaModelId ?? "") + break + case "requesty": + setValue("model", requestyModelId ?? "") + break + case "unbound": + setValue("model", unboundModelId ?? "") + break + case "openai": + setValue("model", openAiModelId ?? "") + break + case "ollama": + setValue("model", ollamaModelId ?? "") + break + case "lmstudio": + setValue("model", lmStudioModelId ?? "") + break + default: + throw new Error(`Unsupported API provider: ${apiProvider}`) + } + + setValue("settings", { ...rooCodeDefaults, ...providerSettings, ...globalSettings }) + setMode("settings") + + event.target.value = "" + } catch (e) { + console.error(e) + toast.error(e instanceof Error ? e.message : "An unknown error occurred.") + } + }, + [clearErrors, setValue], + ) + + return ( + <> + +
+
+ {mode === "openrouter" && ( + ( + + + + + + + + + + No model found. + + {models.data?.map(({ id, name }) => ( + + {name} + + + ))} + + + + + + + + )} + /> + )} + + + + + {settings && ( + + <> +
+ +
+ Imported valid Roo Code settings. Showing differences from default + settings. +
+
+ + +
+ )} + +
+
+ + ( + + Exercises + setValue("suite", value as "full" | "partial")}> + + All + Some + + + {suite === "partial" && ( + ({ value: path, label: path })) || []} + onValueChange={(value) => setValue("exercises", value)} + placeholder="Select" + variant="inverted" + maxCount={4} + /> + )} + + + )} + /> + + ( + + Concurrency + +
+ field.onChange(value[0])} + /> +
{field.value}
+
+
+ +
+ )} + /> + + ( + + Description / Notes + +