├── .changeset ├── README.md ├── changelog-config.js ├── config.json └── fair-boxes-smile.md ├── .clinerules ├── .env.integration.example ├── .eslintrc.json ├── .git-blame-ignore-revs ├── .gitattributes ├── .gitconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── actions │ └── ai-release-notes │ │ └── action.yml ├── dependabot.yml ├── pull_request_template.md ├── scripts │ ├── ai-release-notes.py │ ├── get_prev_version_refs.py │ ├── overwrite_changeset_changelog.py │ ├── parse_changeset_changelog.py │ └── release-notes-prompt.py └── workflows │ ├── changeset-release.yml │ ├── code-qa.yml │ ├── codeql.yml │ ├── discord-pr-notify.yml │ └── marketplace-publish.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode-test.mjs ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── docs │ └── demo.gif └── icons │ └── rocket.png ├── audio ├── celebration.wav ├── notification.wav └── progress_loop.wav ├── cline_docs └── settings.md ├── ellipsis.yaml ├── esbuild.js ├── flake.lock ├── flake.nix ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── __mocks__ │ ├── @modelcontextprotocol │ │ └── sdk │ │ │ ├── client │ │ │ ├── index.js │ │ │ └── stdio.js │ │ │ ├── index.js │ │ │ └── types.js │ ├── McpHub.ts │ ├── default-shell.js │ ├── delay.js │ ├── fs │ │ └── promises.ts │ ├── globby.js │ ├── os-name.js │ ├── p-wait-for.js │ ├── serialize-error.js │ ├── strip-ansi.js │ └── vscode.js ├── activate │ ├── handleUri.ts │ ├── index.ts │ ├── registerCodeActions.ts │ ├── registerCommands.ts │ └── registerTerminalActions.ts ├── api │ ├── index.ts │ ├── providers │ │ ├── __tests__ │ │ │ ├── anthropic.test.ts │ │ │ ├── bedrock.test.ts │ │ │ ├── deepseek.test.ts │ │ │ ├── gemini.test.ts │ │ │ ├── glama.test.ts │ │ │ ├── lmstudio.test.ts │ │ │ ├── ollama.test.ts │ │ │ ├── openai-native.test.ts │ │ │ ├── openai.test.ts │ │ │ ├── openrouter.test.ts │ │ │ ├── requesty.test.ts │ │ │ ├── unbound.test.ts │ │ │ ├── vertex.test.ts │ │ │ └── vscode-lm.test.ts │ │ ├── anthropic.ts │ │ ├── bedrock.ts │ │ ├── deepseek.ts │ │ ├── gemini.ts │ │ ├── glama.ts │ │ ├── lmstudio.ts │ │ ├── mistral.ts │ │ ├── ollama.ts │ │ ├── openai-native.ts │ │ ├── openai.ts │ │ ├── openrouter.ts │ │ ├── requesty.ts │ │ ├── unbound.ts │ │ ├── vertex.ts │ │ └── vscode-lm.ts │ └── transform │ │ ├── __tests__ │ │ ├── bedrock-converse-format.test.ts │ │ ├── openai-format.test.ts │ │ ├── r1-format.test.ts │ │ ├── simple-format.test.ts │ │ ├── stream.test.ts │ │ └── vscode-lm-format.test.ts │ │ ├── bedrock-converse-format.ts │ │ ├── gemini-format.ts │ │ ├── mistral-format.ts │ │ ├── o1-format.ts │ │ ├── openai-format.ts │ │ ├── r1-format.ts │ │ ├── simple-format.ts │ │ ├── stream.ts │ │ └── vscode-lm-format.ts ├── core │ ├── Cline.ts │ ├── CodeActionProvider.ts │ ├── EditorUtils.ts │ ├── __tests__ │ │ ├── Cline.test.ts │ │ ├── CodeActionProvider.test.ts │ │ ├── EditorUtils.test.ts │ │ └── mode-validator.test.ts │ ├── assistant-message │ │ ├── index.ts │ │ └── parse-assistant-message.ts │ ├── config │ │ ├── ConfigManager.ts │ │ ├── CustomModesManager.ts │ │ ├── CustomModesSchema.ts │ │ └── __tests__ │ │ │ ├── ConfigManager.test.ts │ │ │ ├── CustomModesManager.test.ts │ │ │ ├── CustomModesSchema.test.ts │ │ │ ├── CustomModesSettings.test.ts │ │ │ └── GroupConfigSchema.test.ts │ ├── diff │ │ ├── DiffStrategy.ts │ │ ├── insert-groups.ts │ │ ├── strategies │ │ │ ├── __tests__ │ │ │ │ ├── new-unified.test.ts │ │ │ │ ├── search-replace.test.ts │ │ │ │ └── unified.test.ts │ │ │ ├── new-unified │ │ │ │ ├── __tests__ │ │ │ │ │ ├── edit-strategies.test.ts │ │ │ │ │ └── search-strategies.test.ts │ │ │ │ ├── edit-strategies.ts │ │ │ │ ├── index.ts │ │ │ │ ├── search-strategies.ts │ │ │ │ └── types.ts │ │ │ ├── search-replace.ts │ │ │ └── unified.ts │ │ └── types.ts │ ├── mentions │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ └── index.ts │ ├── mode-validator.ts │ ├── prompts │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── system.test.ts.snap │ │ │ ├── sections.test.ts │ │ │ └── system.test.ts │ │ ├── responses.ts │ │ ├── sections │ │ │ ├── capabilities.ts │ │ │ ├── custom-instructions.ts │ │ │ ├── index.ts │ │ │ ├── mcp-servers.ts │ │ │ ├── modes.ts │ │ │ ├── objective.ts │ │ │ ├── rules.ts │ │ │ ├── system-info.ts │ │ │ ├── tool-use-guidelines.ts │ │ │ └── tool-use.ts │ │ ├── system.ts │ │ └── tools │ │ │ ├── access-mcp-resource.ts │ │ │ ├── ask-followup-question.ts │ │ │ ├── attempt-completion.ts │ │ │ ├── browser-action.ts │ │ │ ├── execute-command.ts │ │ │ ├── index.ts │ │ │ ├── insert-content.ts │ │ │ ├── list-code-definition-names.ts │ │ │ ├── list-files.ts │ │ │ ├── new-task.ts │ │ │ ├── read-file.ts │ │ │ ├── search-and-replace.ts │ │ │ ├── search-files.ts │ │ │ ├── switch-mode.ts │ │ │ ├── types.ts │ │ │ ├── use-mcp-tool.ts │ │ │ └── write-to-file.ts │ ├── sliding-window │ │ └── index.ts │ └── webview │ │ ├── ClineProvider.ts │ │ ├── __tests__ │ │ └── ClineProvider.test.ts │ │ ├── getNonce.ts │ │ └── getUri.ts ├── exports │ ├── README.md │ ├── cline.d.ts │ └── index.ts ├── extension.ts ├── integrations │ ├── diagnostics │ │ ├── DiagnosticsMonitor.ts │ │ └── index.ts │ ├── editor │ │ ├── DecorationController.ts │ │ ├── DiffViewProvider.ts │ │ ├── __tests__ │ │ │ ├── DiffViewProvider.test.ts │ │ │ └── detect-omission.test.ts │ │ └── detect-omission.ts │ ├── misc │ │ ├── __tests__ │ │ │ └── extract-text.test.ts │ │ ├── export-markdown.ts │ │ ├── extract-text.ts │ │ ├── open-file.ts │ │ └── process-images.ts │ ├── terminal │ │ ├── TerminalActions.ts │ │ ├── TerminalManager.ts │ │ ├── TerminalProcess.ts │ │ ├── TerminalRegistry.ts │ │ └── __tests__ │ │ │ ├── TerminalProcess.test.ts │ │ │ └── TerminalRegistry.test.ts │ ├── theme │ │ ├── default-themes │ │ │ ├── dark_modern.json │ │ │ ├── dark_plus.json │ │ │ ├── dark_vs.json │ │ │ ├── hc_black.json │ │ │ ├── hc_light.json │ │ │ ├── light_modern.json │ │ │ ├── light_plus.json │ │ │ └── light_vs.json │ │ └── getTheme.ts │ └── workspace │ │ ├── WorkspaceTracker.ts │ │ ├── __tests__ │ │ └── WorkspaceTracker.test.ts │ │ └── get-python-env.ts ├── services │ ├── browser │ │ ├── BrowserSession.ts │ │ └── UrlContentFetcher.ts │ ├── checkpoints │ │ ├── CheckpointService.ts │ │ └── __tests__ │ │ │ └── CheckpointService.test.ts │ ├── glob │ │ └── list-files.ts │ ├── mcp │ │ ├── McpHub.ts │ │ ├── McpServerManager.ts │ │ └── __tests__ │ │ │ └── McpHub.test.ts │ ├── ripgrep │ │ └── index.ts │ └── tree-sitter │ │ ├── __tests__ │ │ ├── index.test.ts │ │ └── languageParser.test.ts │ │ ├── index.ts │ │ ├── languageParser.ts │ │ └── queries │ │ ├── c-sharp.ts │ │ ├── c.ts │ │ ├── cpp.ts │ │ ├── go.ts │ │ ├── index.ts │ │ ├── java.ts │ │ ├── javascript.ts │ │ ├── php.ts │ │ ├── python.ts │ │ ├── ruby.ts │ │ ├── rust.ts │ │ ├── swift.ts │ │ └── typescript.ts ├── shared │ ├── ExtensionMessage.ts │ ├── HistoryItem.ts │ ├── WebviewMessage.ts │ ├── __tests__ │ │ ├── checkExistApiConfig.test.ts │ │ ├── modes.test.ts │ │ ├── support-prompts.test.ts │ │ └── vsCodeSelectorUtils.test.ts │ ├── api.ts │ ├── array.ts │ ├── checkExistApiConfig.ts │ ├── combineApiRequests.ts │ ├── combineCommandSequences.ts │ ├── context-mentions.ts │ ├── experiments.ts │ ├── getApiMetrics.ts │ ├── mcp.ts │ ├── modes.ts │ ├── support-prompt.ts │ ├── tool-groups.ts │ └── vsCodeSelectorUtils.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── extension.test.ts │ │ ├── index.ts │ │ ├── modes.test.ts │ │ └── task.test.ts └── utils │ ├── __tests__ │ ├── cost.test.ts │ ├── enhance-prompt.test.ts │ ├── git.test.ts │ ├── path.test.ts │ └── shell.test.ts │ ├── cost.ts │ ├── fs.ts │ ├── git.ts │ ├── logging │ ├── CompactLogger.ts │ ├── CompactTransport.ts │ ├── __tests__ │ │ ├── CompactLogger.test.ts │ │ ├── CompactTransport.test.ts │ │ └── MockTransport.ts │ ├── index.ts │ └── types.ts │ ├── path.ts │ ├── shell.ts │ ├── single-completion-handler.ts │ └── sound.ts ├── tsconfig.integration.json ├── tsconfig.json └── webview-ui ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .storybook ├── main.ts ├── preview.ts └── vscode.css ├── components.json ├── index.html ├── jest.config.cjs ├── package-lock.json ├── package.json ├── public └── .gitkeep ├── src ├── App.tsx ├── __mocks__ │ ├── @vscode │ │ └── webview-ui-toolkit │ │ │ └── react.ts │ └── vscrui.ts ├── components │ ├── chat │ │ ├── Announcement.tsx │ │ ├── AutoApproveMenu.tsx │ │ ├── BrowserSessionRow.tsx │ │ ├── ChatRow.tsx │ │ ├── ChatTextArea.tsx │ │ ├── ChatView.tsx │ │ ├── ContextMenu.tsx │ │ ├── ReasoningBlock.tsx │ │ ├── TaskHeader.tsx │ │ ├── __tests__ │ │ │ ├── ChatTextArea.test.tsx │ │ │ ├── ChatView.auto-approve.test.tsx │ │ │ └── ChatView.test.tsx │ │ └── checkpoints │ │ │ ├── CheckpointMenu.tsx │ │ │ └── CheckpointSaved.tsx │ ├── common │ │ ├── CaretIcon.tsx │ │ ├── CodeAccordian.tsx │ │ ├── CodeBlock.tsx │ │ ├── Demo.tsx │ │ ├── MarkdownBlock.tsx │ │ ├── Thumbnails.tsx │ │ ├── VSCodeButtonLink.tsx │ │ └── __mocks__ │ │ │ ├── CodeBlock.tsx │ │ │ └── MarkdownBlock.tsx │ ├── history │ │ ├── HistoryPreview.tsx │ │ ├── HistoryView.tsx │ │ └── __tests__ │ │ │ └── HistoryView.test.tsx │ ├── mcp │ │ ├── McpEnabledToggle.tsx │ │ ├── McpResourceRow.tsx │ │ ├── McpToolRow.tsx │ │ ├── McpView.tsx │ │ └── __tests__ │ │ │ └── McpToolRow.test.tsx │ ├── prompts │ │ ├── PromptsView.tsx │ │ └── __tests__ │ │ │ └── PromptsView.test.tsx │ ├── settings │ │ ├── ApiConfigManager.tsx │ │ ├── ApiOptions.tsx │ │ ├── ExperimentalFeature.tsx │ │ ├── GlamaModelPicker.tsx │ │ ├── ModelDescriptionMarkdown.tsx │ │ ├── ModelInfoView.tsx │ │ ├── ModelPicker.tsx │ │ ├── OpenAiModelPicker.tsx │ │ ├── OpenRouterModelPicker.tsx │ │ ├── RequestyModelPicker.tsx │ │ ├── SettingsView.tsx │ │ ├── TabNavbar.tsx │ │ ├── TemperatureControl.tsx │ │ ├── UnboundModelPicker.tsx │ │ ├── __tests__ │ │ │ ├── ApiConfigManager.test.tsx │ │ │ ├── ApiOptions.test.tsx │ │ │ ├── ModelPicker.test.tsx │ │ │ ├── SettingsView.test.tsx │ │ │ └── TemperatureControl.test.tsx │ │ └── styles.ts │ ├── ui │ │ ├── button.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── index.ts │ │ ├── popover.tsx │ │ └── tooltip.tsx │ └── welcome │ │ └── WelcomeView.tsx ├── context │ ├── ExtensionStateContext.tsx │ └── __tests__ │ │ └── ExtensionStateContext.test.tsx ├── index.css ├── index.tsx ├── lib │ └── utils.ts ├── preflight.css ├── services │ └── GitService.ts ├── setupTests.ts ├── stories │ ├── Button.stories.ts │ ├── Combobox.stories.tsx │ ├── DropdownMenu.stories.tsx │ ├── Welcome.mdx │ ├── assets │ │ └── .gitkeep │ └── vscrui │ │ └── Dropdown.stories.tsx ├── utils │ ├── __tests__ │ │ ├── command-validation.test.ts │ │ └── context-mentions.test.ts │ ├── clipboard.ts │ ├── command-validation.ts │ ├── context-mentions.ts │ ├── format.ts │ ├── formatPrice.ts │ ├── getLanguageFromPath.ts │ ├── highlight.ts │ ├── mcp.ts │ ├── textMateToHljs.ts │ ├── validate.ts │ └── vscode.ts └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/changelog-config.js: -------------------------------------------------------------------------------- 1 | // Half-works to simplify the format but needs 'overwrite_changeset_changelog.py' in GHA to finish formatting 2 | 3 | const getReleaseLine = async (changeset) => { 4 | const [firstLine] = changeset.summary 5 | .split("\n") 6 | .map((l) => l.trim()) 7 | .filter(Boolean) 8 | return `- ${firstLine}` 9 | } 10 | 11 | const getDependencyReleaseLine = async () => { 12 | return "" 13 | } 14 | 15 | const changelogFunctions = { 16 | getReleaseLine, 17 | getDependencyReleaseLine, 18 | } 19 | 20 | module.exports = changelogFunctions 21 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", 3 | "changelog": "./changelog-config.js", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/fair-boxes-smile.md: -------------------------------------------------------------------------------- 1 | --- 2 | "roo-cline": patch 3 | --- 4 | 5 | Make sure that we fire the onBlur for edited fields when the user hits Done in settings 6 | -------------------------------------------------------------------------------- /.clinerules: -------------------------------------------------------------------------------- 1 | # Code Quality Rules 2 | 3 | 1. Test Coverage: 4 | - Before attempting completion, always make sure that any code changes have test coverage 5 | - Ensure all tests pass before submitting changes 6 | 7 | 2. Lint Rules: 8 | - Never disable any lint rules without explicit user approval 9 | - If a lint rule needs to be disabled, ask the user first and explain why 10 | - Prefer fixing the underlying issue over disabling the lint rule 11 | - Document any approved lint rule disabling with a comment explaining the reason 12 | 13 | 3. Logging Guidelines: 14 | - Always instrument code changes using the logger exported from `src\utils\logging\index.ts`. 15 | - This will facilitate efficient debugging without impacting production (as the logger no-ops outside of a test environment.) 16 | - Logs can be found in `logs\app.log` 17 | - Logfile is overwritten on each run to keep it to a manageable volume. 18 | 19 | 20 | # Adding a New Setting 21 | 22 | To add a new setting that persists its state, follow the steps in cline_docs/settings.md 23 | -------------------------------------------------------------------------------- /.env.integration.example: -------------------------------------------------------------------------------- 1 | OPENROUTER_API_KEY=sk-or-v1-... 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": ["@typescript-eslint"], 9 | "rules": { 10 | "@typescript-eslint/naming-convention": [ 11 | "warn", 12 | { 13 | "selector": "import", 14 | "format": ["camelCase", "PascalCase"] 15 | } 16 | ], 17 | "@typescript-eslint/semi": "off", 18 | "eqeqeq": "warn", 19 | "no-throw-literal": "warn", 20 | "semi": "off" 21 | }, 22 | "ignorePatterns": ["out", "dist", "**/*.d.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Ran Prettier on all files - https://github.com/RooVetGit/Roo-Code/pull/404 2 | 60a0a824b96a0b326af4d8871b6903f4ddcfe114 3 | 579bdd9dbf6d2d569e5e7adb5ff6292b1e42ea34 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | demo.gif filter=lfs diff=lfs merge=lfs -text 2 | assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [blame] 2 | ignoreRevsFile = .git-blame-ignore-revs 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo 2 | * @stea9499 @ColemanRoo @mrubens @cte 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: input 6 | id: version 7 | attributes: 8 | label: Which version of the app are you using? 9 | description: Please specify the app version you're using (e.g. v3.3.1) 10 | validations: 11 | required: true 12 | - type: dropdown 13 | id: provider 14 | attributes: 15 | label: Which API Provider are you using? 16 | multiple: false 17 | options: 18 | - OpenRouter 19 | - Anthropic 20 | - Google Gemini 21 | - DeepSeek 22 | - OpenAI 23 | - OpenAI Compatible 24 | - GCP Vertex AI 25 | - AWS Bedrock 26 | - Glama 27 | - VS Code LM API 28 | - LM Studio 29 | - Ollama 30 | validations: 31 | required: true 32 | - type: input 33 | id: model 34 | attributes: 35 | label: Which Model are you using? 36 | description: Please specify the model you're using (e.g. Claude 3.5 Sonnet) 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: what-happened 41 | attributes: 42 | label: What happened? 43 | description: Also tell us, what did you expect to happen? 44 | placeholder: Tell us what you see! 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: steps 49 | attributes: 50 | label: Steps to reproduce 51 | description: How do you trigger this bug? Please walk us through it step by step. 52 | value: | 53 | 1. 54 | 2. 55 | 3. 56 | validations: 57 | required: true 58 | - type: textarea 59 | id: logs 60 | attributes: 61 | label: Relevant API REQUEST output 62 | description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks. 63 | render: shell 64 | - type: textarea 65 | id: additional-context 66 | attributes: 67 | label: Additional context 68 | description: Add any other context about the problem here, such as screenshots or related issues. 69 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 4 | url: https://github.com/RooVetGit/Roo-Code/discussions/categories/feature-requests 5 | about: Share and vote on feature requests for Roo Code 6 | - name: Leave a Review 7 | url: https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline&ssr=false#review-details 8 | about: Enjoying Roo Code? Leave a review here! 9 | -------------------------------------------------------------------------------- /.github/actions/ai-release-notes/action.yml: -------------------------------------------------------------------------------- 1 | name: AI Release Notes 2 | description: Generate AI release notes using git and openai, outputs 'RELEASE_NOTES' and 'OPENAI_PROMPT' 3 | 4 | inputs: 5 | OPENAI_API_KEY: 6 | required: true 7 | type: string 8 | GHA_PAT: 9 | required: true 10 | type: string 11 | model_name: 12 | required: false 13 | type: string 14 | default: gpt-4o-mini 15 | repo_path: 16 | required: false 17 | type: string 18 | custom_prompt: 19 | required: false 20 | default: '' 21 | type: string 22 | git_ref: 23 | required: true 24 | type: string 25 | head_ref: 26 | required: true 27 | type: string 28 | base_ref: 29 | required: true 30 | type: string 31 | 32 | outputs: 33 | RELEASE_NOTES: 34 | description: "AI generated release notes" 35 | value: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }} 36 | OPENAI_PROMPT: 37 | description: "Prompt used to generate release notes" 38 | value: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }} 39 | 40 | env: 41 | GITHUB_REF: ${{ inputs.git_ref }} 42 | BASE_REF: ${{ inputs.base_ref }} 43 | HEAD_REF: ${{ inputs.head_ref }} 44 | 45 | runs: 46 | using: "composite" 47 | steps: 48 | - uses: actions/checkout@v4 49 | with: 50 | repository: ${{ inputs.repo_path }} 51 | token: ${{ inputs.GHA_PAT }} 52 | ref: ${{ env.GITHUB_REF }} 53 | fetch-depth: 0 54 | 55 | - name: Set Workspace 56 | shell: bash 57 | run: | 58 | pip install tiktoken 59 | pip install pytz 60 | 61 | # Github outputs: 'OPENAI_PROMPT' 62 | - name: Add Git Info to base prompt 63 | id: ai_prompt 64 | shell: bash 65 | env: 66 | BASE_REF: ${{ env.BASE_REF }} 67 | HEAD_SHA: ${{ env.HEAD_SHA }} 68 | PR_TITLE: ${{ github.event.pull_request.title }} 69 | PR_BODY: ${{ github.event.pull_request.body }} 70 | MODEL_NAME: ${{ inputs.model_name }} 71 | CUSTOM_PROMPT: ${{ inputs.custom_prompt }} # Default: '' 72 | run: python .github/scripts/release-notes-prompt.py 73 | 74 | # Github outputs: 'RELEASE_NOTES' 75 | - name: Generate AI release notes 76 | id: ai_release_notes 77 | shell: bash 78 | env: 79 | OPENAI_API_KEY: ${{ inputs.OPENAI_API_KEY }} 80 | CUSTOM_PROMPT: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }} 81 | MODEL_NAME: ${{ inputs.model_name }} 82 | run: python .github/scripts/ai-release-notes.py 83 | 84 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | ## Type of change 6 | 7 | 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] This change requires a documentation update 13 | 14 | ## How Has This Been Tested? 15 | 16 | 17 | 18 | ## Checklist: 19 | 20 | 21 | 22 | - [ ] My code follows the patterns of this project 23 | - [ ] I have performed a self-review of my own code 24 | - [ ] I have commented my code, particularly in hard-to-understand areas 25 | - [ ] I have made corresponding changes to the documentation 26 | 27 | ## Additional context 28 | 29 | 30 | 31 | ## Related Issues 32 | 33 | 34 | 35 | ## Reviewers 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/scripts/get_prev_version_refs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import subprocess 4 | 5 | def run_git_command(command): 6 | result = subprocess.getoutput(command) 7 | print(f"Git Command: {command}") 8 | print(f"Git Output: {result}") 9 | return result 10 | 11 | def parse_merge_commit(line): 12 | # Parse merge commit messages like: 13 | # "355dc82 Merge pull request #71 from RooVetGit/better-error-handling" 14 | pattern = r"([a-f0-9]+)\s+Merge pull request #(\d+) from (.+)" 15 | match = re.match(pattern, line) 16 | if match: 17 | sha, pr_number, branch = match.groups() 18 | return { 19 | 'sha': sha, 20 | 'pr_number': pr_number, 21 | 'branch': branch 22 | } 23 | return None 24 | 25 | def get_version_refs(): 26 | # Get the merge commits with full message 27 | command = 'git log --merges --pretty=oneline -n 3' 28 | result = run_git_command(command) 29 | 30 | if result: 31 | commits = result.split('\n') 32 | if len(commits) >= 3: 33 | # Parse HEAD~1 (PR to generate notes for) 34 | head_info = parse_merge_commit(commits[1]) 35 | # Parse HEAD~2 (previous PR to compare against) 36 | base_info = parse_merge_commit(commits[2]) 37 | 38 | if head_info and base_info: 39 | # Set output for GitHub Actions 40 | with open(os.environ['GITHUB_OUTPUT'], 'a') as gha_outputs: 41 | gha_outputs.write(f"head_ref={head_info['sha']}\n") 42 | gha_outputs.write(f"base_ref={base_info['sha']}") 43 | 44 | print(f"Head ref (PR #{head_info['pr_number']}): {head_info['sha']}") 45 | print(f"Base ref (PR #{base_info['pr_number']}): {base_info['sha']}") 46 | return head_info, base_info 47 | 48 | print("Could not find or parse sufficient merge history") 49 | return None, None 50 | 51 | if __name__ == "__main__": 52 | head_info, base_info = get_version_refs() -------------------------------------------------------------------------------- /.github/scripts/parse_changeset_changelog.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script extracts the release notes section for a specific version from CHANGELOG.md. 3 | 4 | The script: 5 | 1. Takes a version number and changelog path as input from environment variables 6 | 2. Finds the section in the changelog for the specified version 7 | 3. Extracts the content between the current version header and the next version header 8 | (or end of file if it's the latest version) 9 | 4. Outputs the extracted release notes to GITHUB_OUTPUT for use in creating GitHub releases 10 | 11 | Environment Variables: 12 | GITHUB_OUTPUT: Path to GitHub Actions output file 13 | CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md') 14 | VERSION: The version number to extract notes for 15 | """ 16 | 17 | #!/usr/bin/env python3 18 | 19 | import sys 20 | import os 21 | import subprocess 22 | 23 | GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT") 24 | CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md") 25 | VERSION = os.environ['VERSION'] 26 | 27 | def parse_changelog_section(content: str): 28 | """Parse a specific version section from the changelog content. 29 | 30 | Args: 31 | content: The full changelog content as a string 32 | 33 | Returns: 34 | The formatted content for this version, or None if version not found 35 | 36 | Example: 37 | >>> content = "## 1.2.0\\nChanges\\n## 1.1.0\\nOld changes" 38 | >>> parse_changelog_section(content) 39 | 'Changes\\n' 40 | """ 41 | # Find the section for the specified version 42 | version_pattern = f"## {VERSION}\n" 43 | print(f"latest version: {VERSION}") 44 | notes_start_index = content.find(version_pattern) + len(version_pattern) 45 | prev_version = subprocess.getoutput("git show origin/main:package.json | grep '\"version\":' | cut -d'\"' -f4") 46 | print(f"prev_version: {prev_version}") 47 | prev_version_pattern = f"## {prev_version}\n" 48 | notes_end_index = content.find(prev_version_pattern, notes_start_index) if prev_version_pattern in content else len(content) 49 | 50 | return content[notes_start_index:notes_end_index] 51 | 52 | with open(CHANGELOG_PATH, 'r') as f: 53 | content = f.read() 54 | 55 | formatted_content = parse_changelog_section(content) 56 | if not formatted_content: 57 | print(f"Version {VERSION} not found in changelog", file=sys.stderr) 58 | sys.exit(1) 59 | 60 | print(formatted_content) 61 | 62 | # Write the extracted release notes to GITHUB_OUTPUT 63 | with open(GITHUB_OUTPUT, "a") as gha_output: 64 | gha_output.write(f"release-notes<> $GITHUB_OUTPUT; 57 | else 58 | echo "defined=false" >> $GITHUB_OUTPUT; 59 | fi 60 | 61 | integration-test: 62 | runs-on: ubuntu-latest 63 | needs: [check-openrouter-api-key] 64 | if: needs.check-openrouter-api-key.outputs.exists == 'true' 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@v4 68 | - name: Setup Node.js 69 | uses: actions/setup-node@v4 70 | with: 71 | node-version: '18' 72 | cache: 'npm' 73 | - name: Create env.integration file 74 | run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration 75 | - name: Install dependencies 76 | run: npm run install:all 77 | - name: Run integration tests 78 | run: xvfb-run -a npm run test:integration 79 | -------------------------------------------------------------------------------- /.github/workflows/discord-pr-notify.yml: -------------------------------------------------------------------------------- 1 | name: Discord PR Notifier 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request_target: 6 | types: [opened] 7 | 8 | jobs: 9 | notify: 10 | runs-on: ubuntu-latest 11 | if: github.head_ref != 'changeset-release/main' 12 | steps: 13 | - name: Send Discord Notification 14 | run: | 15 | PAYLOAD=$(jq -n \ 16 | --arg title "${{ github.event.pull_request.title }}" \ 17 | --arg url "${{ github.event.pull_request.html_url }}" \ 18 | --arg author "${{ github.event.pull_request.user.login }}" \ 19 | '{ 20 | content: ("🚀 **New PR:** " + $title + "\n🔗 <" + $url + ">\n👤 **Author:** " + $author), 21 | thread_name: ($title + " by " + $author) 22 | }') 23 | 24 | curl -X POST "${{ secrets.DISCORD_WEBHOOK }}" \ 25 | -H "Content-Type: application/json" \ 26 | -d "$PAYLOAD" 27 | -------------------------------------------------------------------------------- /.github/workflows/marketplace-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Extension 2 | on: 3 | pull_request: 4 | types: [closed] 5 | workflow_dispatch: 6 | 7 | env: 8 | GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }} 9 | 10 | jobs: 11 | publish-extension: 12 | runs-on: ubuntu-latest 13 | if: > 14 | ( github.event_name == 'pull_request' && 15 | github.event.pull_request.base.ref == 'main' && 16 | contains(github.event.pull_request.title, 'Changeset version bump') ) || 17 | github.event_name == 'workflow_dispatch' 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | ref: ${{ env.GIT_REF }} 22 | 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 18 26 | - run: | 27 | git config user.name github-actions 28 | git config user.email github-actions@github.com 29 | - name: Install Dependencies 30 | run: | 31 | npm install -g vsce ovsx 32 | npm install 33 | cd webview-ui 34 | npm install 35 | cd .. 36 | - name: Package and Publish Extension 37 | env: 38 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 39 | OVSX_PAT: ${{ secrets.OVSX_PAT }} 40 | run: | 41 | current_package_version=$(node -p "require('./package.json').version") 42 | 43 | npm run vsix 44 | package=$(unzip -l bin/roo-cline-${current_package_version}.vsix) 45 | echo "$package" 46 | echo "$package" | grep -q "dist/extension.js" || exit 1 47 | echo "$package" | grep -q "extension/webview-ui/build/assets/index.js" || exit 1 48 | echo "$package" | grep -q "extension/node_modules/@vscode/codicons/dist/codicon.ttf" || exit 1 49 | 50 | npm run publish:marketplace 51 | echo "Successfully published version $current_package_version to VS Code Marketplace" 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | out 3 | out-* 4 | node_modules 5 | coverage/ 6 | 7 | .DS_Store 8 | 9 | # Builds 10 | bin/ 11 | roo-cline-*.vsix 12 | 13 | # Local prompts and rules 14 | /local-prompts 15 | 16 | # Test environment 17 | .test_env 18 | .vscode-test/ 19 | 20 | # Docs 21 | docs/_site/ 22 | 23 | # Dotenv 24 | .env.integration 25 | 26 | #Local lint config 27 | .eslintrc.local.json 28 | 29 | #Logging 30 | logs 31 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | branch="$(git rev-parse --abbrev-ref HEAD)" 2 | 3 | if [ "$branch" = "main" ]; then 4 | echo "You can't commit directly to main - please check out a branch." 5 | exit 1 6 | fi 7 | 8 | npx lint-staged 9 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | branch="$(git rev-parse --abbrev-ref HEAD)" 2 | 3 | if [ "$branch" = "main" ]; then 4 | echo "You can't push directly to main - please check out a branch." 5 | exit 1 6 | fi 7 | 8 | npm run compile 9 | 10 | # Check for new changesets. 11 | NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ') 12 | echo "Changeset files: $NEW_CHANGESETS" 13 | 14 | if [ "$NEW_CHANGESETS" == "0" ]; then 15 | echo "-------------------------------------------------------------------------------------" 16 | echo "Changes detected. Please run 'npm run changeset' to create a changeset if applicable." 17 | echo "-------------------------------------------------------------------------------------" 18 | fi 19 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules 3 | webview-ui/build/ 4 | CHANGELOG.md 5 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "useTabs": true, 4 | "printWidth": 120, 5 | "semi": false, 6 | "bracketSameLine": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * See: https://code.visualstudio.com/api/working-with-extensions/testing-extension 3 | */ 4 | 5 | import { defineConfig } from '@vscode/test-cli'; 6 | 7 | export default defineConfig({ 8 | label: 'integrationTest', 9 | files: 'out-integration/test/**/*.test.js', 10 | workspaceFolder: '.', 11 | mocha: { 12 | ui: 'tdd', 13 | timeout: 60000, 14 | }, 15 | launchArgs: [ 16 | '--enable-proposed-api=RooVeterinaryInc.roo-cline', 17 | '--disable-extensions' 18 | ] 19 | }); 20 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "connor4312.esbuild-problem-matchers", 7 | "ms-vscode.extension-test-runner", 8 | "csstools.postcss", 9 | "bradlc.vscode-tailwindcss", 10 | "tobermory.es6-string-html" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "sourceMaps": true, 15 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 16 | "preLaunchTask": "${defaultBuildTask}", 17 | "env": { 18 | "NODE_ENV": "development", 19 | "VSCODE_DEBUG_MODE": "true" 20 | }, 21 | "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], 22 | "presentation": { 23 | "hidden": false, 24 | "group": "tasks", 25 | "order": 1 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "watch", 8 | "dependsOn": ["npm: dev", "npm: watch:tsc", "npm: watch:esbuild"], 9 | "presentation": { 10 | "reveal": "never" 11 | }, 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | }, 17 | { 18 | "label": "npm: dev", 19 | "type": "npm", 20 | "script": "dev", 21 | "group": "build", 22 | "problemMatcher": { 23 | "owner": "vite", 24 | "pattern": { 25 | "regexp": "^$" 26 | }, 27 | "background": { 28 | "activeOnStart": true, 29 | "beginsPattern": ".*VITE.*", 30 | "endsPattern": ".*Local:.*" 31 | } 32 | }, 33 | "isBackground": true, 34 | "presentation": { 35 | "group": "webview-ui", 36 | "reveal": "always" 37 | } 38 | }, 39 | { 40 | "label": "npm: watch:esbuild", 41 | "type": "npm", 42 | "script": "watch:esbuild", 43 | "group": "build", 44 | "problemMatcher": "$esbuild-watch", 45 | "isBackground": true, 46 | "presentation": { 47 | "group": "watch", 48 | "reveal": "always" 49 | } 50 | }, 51 | { 52 | "label": "npm: watch:tsc", 53 | "type": "npm", 54 | "script": "watch:tsc", 55 | "group": "build", 56 | "problemMatcher": "$tsc-watch", 57 | "isBackground": true, 58 | "presentation": { 59 | "group": "watch", 60 | "reveal": "always" 61 | } 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Default 2 | .github/** 3 | .husky/** 4 | .vscode/** 5 | .vscode-test/** 6 | out/** 7 | node_modules/** 8 | src/** 9 | .gitignore 10 | .yarnrc 11 | esbuild.js 12 | vsc-extension-quickstart.md 13 | **/tsconfig.json 14 | **/.eslintrc.json 15 | **/*.map 16 | **/*.ts 17 | **/.vscode-test.* 18 | 19 | # Custom 20 | demo.gif 21 | .nvmrc 22 | .gitattributes 23 | .prettierignore 24 | 25 | # 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) 26 | webview-ui/src/** 27 | webview-ui/public/** 28 | webview-ui/scripts/** 29 | webview-ui/index.html 30 | webview-ui/README.md 31 | webview-ui/package.json 32 | webview-ui/package-lock.json 33 | webview-ui/node_modules/** 34 | **/.gitignore 35 | 36 | # Fix issue where codicons don't get packaged (https://github.com/microsoft/vscode-extension-samples/issues/692) 37 | !node_modules/@vscode/codicons/dist/codicon.css 38 | !node_modules/@vscode/codicons/dist/codicon.ttf 39 | 40 | # Include default themes JSON files used in getTheme 41 | !src/integrations/theme/default-themes/** 42 | 43 | # Include icons 44 | !assets/icons/** -------------------------------------------------------------------------------- /assets/docs/demo.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d426d600fa80e9ac237cbad6b3f7f47a4aa2005d5218184e6b9565a8ee46d1ba 3 | size 19108207 4 | -------------------------------------------------------------------------------- /assets/icons/rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidrivencoder/Roo-Cline/4aa43d0b5e00b596024738e23fc84544536ad37f/assets/icons/rocket.png -------------------------------------------------------------------------------- /audio/celebration.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidrivencoder/Roo-Cline/4aa43d0b5e00b596024738e23fc84544536ad37f/audio/celebration.wav -------------------------------------------------------------------------------- /audio/notification.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidrivencoder/Roo-Cline/4aa43d0b5e00b596024738e23fc84544536ad37f/audio/notification.wav -------------------------------------------------------------------------------- /audio/progress_loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidrivencoder/Roo-Cline/4aa43d0b5e00b596024738e23fc84544536ad37f/audio/progress_loop.wav -------------------------------------------------------------------------------- /ellipsis.yaml: -------------------------------------------------------------------------------- 1 | version: 1.3 2 | pr_review: 3 | 4 | # Modify confidence_threshold to show fewer/more comments. Increase this to show fewer, but higher quality comments. 5 | # If there’s too much noise, we suggest 0.9. The default value is 0.7. 6 | confidence_threshold: 0.7 7 | 8 | # If quiet mode is enabled, Ellipsis will only leave reviews when it has comments, so “Looks good to me” reviews 9 | # will be skipped. This can reduce clutter. 10 | quiet: true 11 | 12 | # You can disable automatic code review using auto_review_enabled. This will override any global settings you 13 | # have configured via the web UI. 14 | auto_review_enabled: true 15 | 16 | # You can enable auto-review on draft PRs using auto_review_draft. This will override any global settings you 17 | # have configured via the web UI. 18 | auto_review_draft: false 19 | 20 | # You can allow Ellipsis to approve PRs using enable_approve_prs. Note: in common branch GitHub protection configurations, 21 | # the Ellipsis approval will count towards the approval total and allow the PR to be merged when it otherwise may not be. 22 | enable_approve_prs: false 23 | -------------------------------------------------------------------------------- /esbuild.js: -------------------------------------------------------------------------------- 1 | const esbuild = require("esbuild") 2 | const fs = require("fs") 3 | const path = require("path") 4 | 5 | const production = process.argv.includes("--production") 6 | const watch = process.argv.includes("--watch") 7 | 8 | /** 9 | * @type {import('esbuild').Plugin} 10 | */ 11 | const esbuildProblemMatcherPlugin = { 12 | name: "esbuild-problem-matcher", 13 | 14 | setup(build) { 15 | build.onStart(() => { 16 | console.log("[watch] build started") 17 | }) 18 | build.onEnd((result) => { 19 | result.errors.forEach(({ text, location }) => { 20 | console.error(`✘ [ERROR] ${text}`) 21 | console.error(` ${location.file}:${location.line}:${location.column}:`) 22 | }) 23 | console.log("[watch] build finished") 24 | }) 25 | }, 26 | } 27 | 28 | const copyWasmFiles = { 29 | name: "copy-wasm-files", 30 | setup(build) { 31 | build.onEnd(() => { 32 | // tree sitter 33 | const sourceDir = path.join(__dirname, "node_modules", "web-tree-sitter") 34 | const targetDir = path.join(__dirname, "dist") 35 | 36 | // Copy tree-sitter.wasm 37 | fs.copyFileSync(path.join(sourceDir, "tree-sitter.wasm"), path.join(targetDir, "tree-sitter.wasm")) 38 | 39 | // Copy language-specific WASM files 40 | const languageWasmDir = path.join(__dirname, "node_modules", "tree-sitter-wasms", "out") 41 | const languages = [ 42 | "typescript", 43 | "tsx", 44 | "python", 45 | "rust", 46 | "javascript", 47 | "go", 48 | "cpp", 49 | "c", 50 | "c_sharp", 51 | "ruby", 52 | "java", 53 | "php", 54 | "swift", 55 | ] 56 | 57 | languages.forEach((lang) => { 58 | const filename = `tree-sitter-${lang}.wasm` 59 | fs.copyFileSync(path.join(languageWasmDir, filename), path.join(targetDir, filename)) 60 | }) 61 | }) 62 | }, 63 | } 64 | 65 | const extensionConfig = { 66 | bundle: true, 67 | minify: production, 68 | sourcemap: !production, 69 | logLevel: "silent", 70 | plugins: [ 71 | copyWasmFiles, 72 | /* add to the end of plugins array */ 73 | esbuildProblemMatcherPlugin, 74 | ], 75 | entryPoints: ["src/extension.ts"], 76 | format: "cjs", 77 | sourcesContent: false, 78 | platform: "node", 79 | outfile: "dist/extension.js", 80 | external: ["vscode"], 81 | } 82 | 83 | async function main() { 84 | const extensionCtx = await esbuild.context(extensionConfig) 85 | if (watch) { 86 | await extensionCtx.watch() 87 | } else { 88 | await extensionCtx.rebuild() 89 | await extensionCtx.dispose() 90 | } 91 | } 92 | 93 | main().catch((e) => { 94 | console.error(e) 95 | process.exit(1) 96 | }) 97 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1737569578, 6 | "narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "47addd76727f42d351590c905d9d1905ca895b82", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-24.11", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Roo Code development environment"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; 6 | }; 7 | 8 | outputs = { self, nixpkgs, ... }: let 9 | systems = [ "aarch64-darwin" "x86_64-linux" ]; 10 | 11 | forAllSystems = nixpkgs.lib.genAttrs systems; 12 | 13 | mkDevShell = system: let 14 | pkgs = import nixpkgs { inherit system; }; 15 | in pkgs.mkShell { 16 | name = "roo-code"; 17 | 18 | packages = with pkgs; [ 19 | zsh 20 | nodejs_18 21 | corepack_18 22 | ]; 23 | 24 | shellHook = '' 25 | exec zsh 26 | ''; 27 | }; 28 | in { 29 | devShells = forAllSystems (system: { 30 | default = mkDevShell system; 31 | }); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 6 | transform: { 7 | "^.+\\.tsx?$": [ 8 | "ts-jest", 9 | { 10 | tsconfig: { 11 | module: "CommonJS", 12 | moduleResolution: "node", 13 | esModuleInterop: true, 14 | allowJs: true, 15 | }, 16 | diagnostics: false, 17 | isolatedModules: true, 18 | }, 19 | ], 20 | }, 21 | testMatch: ["**/__tests__/**/*.test.ts"], 22 | moduleNameMapper: { 23 | "^vscode$": "/src/__mocks__/vscode.js", 24 | "@modelcontextprotocol/sdk$": "/src/__mocks__/@modelcontextprotocol/sdk/index.js", 25 | "@modelcontextprotocol/sdk/(.*)": "/src/__mocks__/@modelcontextprotocol/sdk/$1", 26 | "^delay$": "/src/__mocks__/delay.js", 27 | "^p-wait-for$": "/src/__mocks__/p-wait-for.js", 28 | "^globby$": "/src/__mocks__/globby.js", 29 | "^serialize-error$": "/src/__mocks__/serialize-error.js", 30 | "^strip-ansi$": "/src/__mocks__/strip-ansi.js", 31 | "^default-shell$": "/src/__mocks__/default-shell.js", 32 | "^os-name$": "/src/__mocks__/os-name.js", 33 | }, 34 | transformIgnorePatterns: [ 35 | "node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name)/)", 36 | ], 37 | roots: ["/src", "/webview-ui/src"], 38 | modulePathIgnorePatterns: [".vscode-test"], 39 | reporters: [["jest-simple-dot-reporter", {}]], 40 | setupFiles: [], 41 | } 42 | -------------------------------------------------------------------------------- /src/__mocks__/@modelcontextprotocol/sdk/client/index.js: -------------------------------------------------------------------------------- 1 | class Client { 2 | constructor() { 3 | this.request = jest.fn() 4 | } 5 | 6 | connect() { 7 | return Promise.resolve() 8 | } 9 | 10 | close() { 11 | return Promise.resolve() 12 | } 13 | } 14 | 15 | module.exports = { 16 | Client, 17 | } 18 | -------------------------------------------------------------------------------- /src/__mocks__/@modelcontextprotocol/sdk/client/stdio.js: -------------------------------------------------------------------------------- 1 | class StdioClientTransport { 2 | constructor() { 3 | this.start = jest.fn().mockResolvedValue(undefined) 4 | this.close = jest.fn().mockResolvedValue(undefined) 5 | this.stderr = { 6 | on: jest.fn(), 7 | } 8 | } 9 | } 10 | 11 | class StdioServerParameters { 12 | constructor() { 13 | this.command = "" 14 | this.args = [] 15 | this.env = {} 16 | } 17 | } 18 | 19 | module.exports = { 20 | StdioClientTransport, 21 | StdioServerParameters, 22 | } 23 | -------------------------------------------------------------------------------- /src/__mocks__/@modelcontextprotocol/sdk/index.js: -------------------------------------------------------------------------------- 1 | const { Client } = require("./client/index.js") 2 | const { StdioClientTransport, StdioServerParameters } = require("./client/stdio.js") 3 | const { 4 | CallToolResultSchema, 5 | ListToolsResultSchema, 6 | ListResourcesResultSchema, 7 | ListResourceTemplatesResultSchema, 8 | ReadResourceResultSchema, 9 | ErrorCode, 10 | McpError, 11 | } = require("./types.js") 12 | 13 | module.exports = { 14 | Client, 15 | StdioClientTransport, 16 | StdioServerParameters, 17 | CallToolResultSchema, 18 | ListToolsResultSchema, 19 | ListResourcesResultSchema, 20 | ListResourceTemplatesResultSchema, 21 | ReadResourceResultSchema, 22 | ErrorCode, 23 | McpError, 24 | } 25 | -------------------------------------------------------------------------------- /src/__mocks__/@modelcontextprotocol/sdk/types.js: -------------------------------------------------------------------------------- 1 | const CallToolResultSchema = { 2 | parse: jest.fn().mockReturnValue({}), 3 | } 4 | 5 | const ListToolsResultSchema = { 6 | parse: jest.fn().mockReturnValue({ 7 | tools: [], 8 | }), 9 | } 10 | 11 | const ListResourcesResultSchema = { 12 | parse: jest.fn().mockReturnValue({ 13 | resources: [], 14 | }), 15 | } 16 | 17 | const ListResourceTemplatesResultSchema = { 18 | parse: jest.fn().mockReturnValue({ 19 | resourceTemplates: [], 20 | }), 21 | } 22 | 23 | const ReadResourceResultSchema = { 24 | parse: jest.fn().mockReturnValue({ 25 | contents: [], 26 | }), 27 | } 28 | 29 | const ErrorCode = { 30 | InvalidRequest: "InvalidRequest", 31 | MethodNotFound: "MethodNotFound", 32 | InvalidParams: "InvalidParams", 33 | InternalError: "InternalError", 34 | } 35 | 36 | class McpError extends Error { 37 | constructor(code, message) { 38 | super(message) 39 | this.code = code 40 | } 41 | } 42 | 43 | module.exports = { 44 | CallToolResultSchema, 45 | ListToolsResultSchema, 46 | ListResourcesResultSchema, 47 | ListResourceTemplatesResultSchema, 48 | ReadResourceResultSchema, 49 | ErrorCode, 50 | McpError, 51 | } 52 | -------------------------------------------------------------------------------- /src/__mocks__/McpHub.ts: -------------------------------------------------------------------------------- 1 | export class McpHub { 2 | connections = [] 3 | isConnecting = false 4 | 5 | constructor() { 6 | this.toggleToolAlwaysAllow = jest.fn() 7 | this.callTool = jest.fn() 8 | } 9 | 10 | async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise { 11 | return Promise.resolve() 12 | } 13 | 14 | async callTool(serverName: string, toolName: string, toolArguments?: Record): Promise { 15 | return Promise.resolve({ result: "success" }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/__mocks__/default-shell.js: -------------------------------------------------------------------------------- 1 | // Mock default shell based on platform 2 | const os = require("os") 3 | 4 | let defaultShell 5 | if (os.platform() === "win32") { 6 | defaultShell = "cmd.exe" 7 | } else { 8 | defaultShell = "/bin/bash" 9 | } 10 | 11 | module.exports = defaultShell 12 | module.exports.default = defaultShell 13 | -------------------------------------------------------------------------------- /src/__mocks__/delay.js: -------------------------------------------------------------------------------- 1 | function delay(ms) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)) 3 | } 4 | 5 | module.exports = delay 6 | module.exports.default = delay 7 | -------------------------------------------------------------------------------- /src/__mocks__/globby.js: -------------------------------------------------------------------------------- 1 | function globby(patterns, options) { 2 | return Promise.resolve([]) 3 | } 4 | 5 | globby.sync = function (patterns, options) { 6 | return [] 7 | } 8 | 9 | module.exports = globby 10 | module.exports.default = globby 11 | -------------------------------------------------------------------------------- /src/__mocks__/os-name.js: -------------------------------------------------------------------------------- 1 | function osName() { 2 | return "macOS" 3 | } 4 | 5 | module.exports = osName 6 | module.exports.default = osName 7 | -------------------------------------------------------------------------------- /src/__mocks__/p-wait-for.js: -------------------------------------------------------------------------------- 1 | function pWaitFor(condition, options = {}) { 2 | return new Promise((resolve, reject) => { 3 | const interval = setInterval(() => { 4 | if (condition()) { 5 | clearInterval(interval) 6 | resolve() 7 | } 8 | }, options.interval || 20) 9 | 10 | if (options.timeout) { 11 | setTimeout(() => { 12 | clearInterval(interval) 13 | reject(new Error("Timed out")) 14 | }, options.timeout) 15 | } 16 | }) 17 | } 18 | 19 | module.exports = pWaitFor 20 | module.exports.default = pWaitFor 21 | -------------------------------------------------------------------------------- /src/__mocks__/serialize-error.js: -------------------------------------------------------------------------------- 1 | function serializeError(error) { 2 | if (error instanceof Error) { 3 | return { 4 | name: error.name, 5 | message: error.message, 6 | stack: error.stack, 7 | } 8 | } 9 | return error 10 | } 11 | 12 | function deserializeError(errorData) { 13 | if (errorData && typeof errorData === "object") { 14 | const error = new Error(errorData.message) 15 | error.name = errorData.name 16 | error.stack = errorData.stack 17 | return error 18 | } 19 | return errorData 20 | } 21 | 22 | module.exports = { 23 | serializeError, 24 | deserializeError, 25 | } 26 | -------------------------------------------------------------------------------- /src/__mocks__/strip-ansi.js: -------------------------------------------------------------------------------- 1 | function stripAnsi(string) { 2 | // Simple mock that just returns the input string 3 | return string 4 | } 5 | 6 | module.exports = stripAnsi 7 | module.exports.default = stripAnsi 8 | -------------------------------------------------------------------------------- /src/__mocks__/vscode.js: -------------------------------------------------------------------------------- 1 | const vscode = { 2 | window: { 3 | showInformationMessage: jest.fn(), 4 | showErrorMessage: jest.fn(), 5 | createTextEditorDecorationType: jest.fn().mockReturnValue({ 6 | dispose: jest.fn(), 7 | }), 8 | tabGroups: { 9 | onDidChangeTabs: jest.fn(() => { 10 | return { 11 | dispose: jest.fn(), 12 | } 13 | }), 14 | all: [], 15 | }, 16 | }, 17 | workspace: { 18 | onDidSaveTextDocument: jest.fn(), 19 | createFileSystemWatcher: jest.fn().mockReturnValue({ 20 | onDidCreate: jest.fn().mockReturnValue({ dispose: jest.fn() }), 21 | onDidDelete: jest.fn().mockReturnValue({ dispose: jest.fn() }), 22 | dispose: jest.fn(), 23 | }), 24 | fs: { 25 | stat: jest.fn(), 26 | }, 27 | }, 28 | Disposable: class { 29 | dispose() {} 30 | }, 31 | Uri: { 32 | file: (path) => ({ 33 | fsPath: path, 34 | scheme: "file", 35 | authority: "", 36 | path: path, 37 | query: "", 38 | fragment: "", 39 | with: jest.fn(), 40 | toJSON: jest.fn(), 41 | }), 42 | }, 43 | EventEmitter: class { 44 | constructor() { 45 | this.event = jest.fn() 46 | this.fire = jest.fn() 47 | } 48 | }, 49 | ConfigurationTarget: { 50 | Global: 1, 51 | Workspace: 2, 52 | WorkspaceFolder: 3, 53 | }, 54 | Position: class { 55 | constructor(line, character) { 56 | this.line = line 57 | this.character = character 58 | } 59 | }, 60 | Range: class { 61 | constructor(startLine, startCharacter, endLine, endCharacter) { 62 | this.start = new vscode.Position(startLine, startCharacter) 63 | this.end = new vscode.Position(endLine, endCharacter) 64 | } 65 | }, 66 | ThemeColor: class { 67 | constructor(id) { 68 | this.id = id 69 | } 70 | }, 71 | ExtensionMode: { 72 | Production: 1, 73 | Development: 2, 74 | Test: 3, 75 | }, 76 | FileType: { 77 | Unknown: 0, 78 | File: 1, 79 | Directory: 2, 80 | SymbolicLink: 64, 81 | }, 82 | TabInputText: class { 83 | constructor(uri) { 84 | this.uri = uri 85 | } 86 | }, 87 | } 88 | 89 | module.exports = vscode 90 | -------------------------------------------------------------------------------- /src/activate/handleUri.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | import { ClineProvider } from "../core/webview/ClineProvider" 4 | 5 | export const handleUri = async (uri: vscode.Uri) => { 6 | const path = uri.path 7 | const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B")) 8 | const visibleProvider = ClineProvider.getVisibleInstance() 9 | 10 | if (!visibleProvider) { 11 | return 12 | } 13 | 14 | switch (path) { 15 | case "/glama": { 16 | const code = query.get("code") 17 | if (code) { 18 | await visibleProvider.handleGlamaCallback(code) 19 | } 20 | break 21 | } 22 | case "/openrouter": { 23 | const code = query.get("code") 24 | if (code) { 25 | await visibleProvider.handleOpenRouterCallback(code) 26 | } 27 | break 28 | } 29 | default: 30 | break 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/activate/index.ts: -------------------------------------------------------------------------------- 1 | export { handleUri } from "./handleUri" 2 | export { registerCommands } from "./registerCommands" 3 | export { registerCodeActions } from "./registerCodeActions" 4 | export { registerTerminalActions } from "./registerTerminalActions" 5 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | import { GlamaHandler } from "./providers/glama" 3 | import { ApiConfiguration, ModelInfo } from "../shared/api" 4 | import { AnthropicHandler } from "./providers/anthropic" 5 | import { AwsBedrockHandler } from "./providers/bedrock" 6 | import { OpenRouterHandler } from "./providers/openrouter" 7 | import { VertexHandler } from "./providers/vertex" 8 | import { OpenAiHandler } from "./providers/openai" 9 | import { OllamaHandler } from "./providers/ollama" 10 | import { LmStudioHandler } from "./providers/lmstudio" 11 | import { GeminiHandler } from "./providers/gemini" 12 | import { OpenAiNativeHandler } from "./providers/openai-native" 13 | import { DeepSeekHandler } from "./providers/deepseek" 14 | import { MistralHandler } from "./providers/mistral" 15 | import { VsCodeLmHandler } from "./providers/vscode-lm" 16 | import { ApiStream } from "./transform/stream" 17 | import { UnboundHandler } from "./providers/unbound" 18 | import { RequestyHandler } from "./providers/requesty" 19 | 20 | export interface SingleCompletionHandler { 21 | completePrompt(prompt: string): Promise 22 | } 23 | 24 | export interface ApiHandler { 25 | createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream 26 | getModel(): { id: string; info: ModelInfo } 27 | } 28 | 29 | export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { 30 | const { apiProvider, ...options } = configuration 31 | switch (apiProvider) { 32 | case "anthropic": 33 | return new AnthropicHandler(options) 34 | case "glama": 35 | return new GlamaHandler(options) 36 | case "openrouter": 37 | return new OpenRouterHandler(options) 38 | case "bedrock": 39 | return new AwsBedrockHandler(options) 40 | case "vertex": 41 | return new VertexHandler(options) 42 | case "openai": 43 | return new OpenAiHandler(options) 44 | case "ollama": 45 | return new OllamaHandler(options) 46 | case "lmstudio": 47 | return new LmStudioHandler(options) 48 | case "gemini": 49 | return new GeminiHandler(options) 50 | case "openai-native": 51 | return new OpenAiNativeHandler(options) 52 | case "deepseek": 53 | return new DeepSeekHandler(options) 54 | case "vscode-lm": 55 | return new VsCodeLmHandler(options) 56 | case "mistral": 57 | return new MistralHandler(options) 58 | case "unbound": 59 | return new UnboundHandler(options) 60 | case "requesty": 61 | return new RequestyHandler(options) 62 | default: 63 | return new AnthropicHandler(options) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/api/providers/deepseek.ts: -------------------------------------------------------------------------------- 1 | import { OpenAiHandler, OpenAiHandlerOptions } from "./openai" 2 | import { ModelInfo } from "../../shared/api" 3 | import { deepSeekModels, deepSeekDefaultModelId } from "../../shared/api" 4 | 5 | export class DeepSeekHandler extends OpenAiHandler { 6 | constructor(options: OpenAiHandlerOptions) { 7 | super({ 8 | ...options, 9 | openAiApiKey: options.deepSeekApiKey ?? "not-provided", 10 | openAiModelId: options.apiModelId ?? deepSeekDefaultModelId, 11 | openAiBaseUrl: options.deepSeekBaseUrl ?? "https://api.deepseek.com/v1", 12 | openAiStreamingEnabled: true, 13 | includeMaxTokens: true, 14 | }) 15 | } 16 | 17 | override getModel(): { id: string; info: ModelInfo } { 18 | const modelId = this.options.apiModelId ?? deepSeekDefaultModelId 19 | return { 20 | id: modelId, 21 | info: deepSeekModels[modelId as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId], 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/api/providers/gemini.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | import { GoogleGenerativeAI } from "@google/generative-ai" 3 | import { ApiHandler, SingleCompletionHandler } from "../" 4 | import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "../../shared/api" 5 | import { convertAnthropicMessageToGemini } from "../transform/gemini-format" 6 | import { ApiStream } from "../transform/stream" 7 | 8 | const GEMINI_DEFAULT_TEMPERATURE = 0 9 | 10 | export class GeminiHandler implements ApiHandler, SingleCompletionHandler { 11 | private options: ApiHandlerOptions 12 | private client: GoogleGenerativeAI 13 | 14 | constructor(options: ApiHandlerOptions) { 15 | this.options = options 16 | this.client = new GoogleGenerativeAI(options.geminiApiKey ?? "not-provided") 17 | } 18 | 19 | async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { 20 | const model = this.client.getGenerativeModel({ 21 | model: this.getModel().id, 22 | systemInstruction: systemPrompt, 23 | }) 24 | const result = await model.generateContentStream({ 25 | contents: messages.map(convertAnthropicMessageToGemini), 26 | generationConfig: { 27 | // maxOutputTokens: this.getModel().info.maxTokens, 28 | temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE, 29 | }, 30 | }) 31 | 32 | for await (const chunk of result.stream) { 33 | yield { 34 | type: "text", 35 | text: chunk.text(), 36 | } 37 | } 38 | 39 | const response = await result.response 40 | yield { 41 | type: "usage", 42 | inputTokens: response.usageMetadata?.promptTokenCount ?? 0, 43 | outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0, 44 | } 45 | } 46 | 47 | getModel(): { id: GeminiModelId; info: ModelInfo } { 48 | const modelId = this.options.apiModelId 49 | if (modelId && modelId in geminiModels) { 50 | const id = modelId as GeminiModelId 51 | return { id, info: geminiModels[id] } 52 | } 53 | return { id: geminiDefaultModelId, info: geminiModels[geminiDefaultModelId] } 54 | } 55 | 56 | async completePrompt(prompt: string): Promise { 57 | try { 58 | const model = this.client.getGenerativeModel({ 59 | model: this.getModel().id, 60 | }) 61 | 62 | const result = await model.generateContent({ 63 | contents: [{ role: "user", parts: [{ text: prompt }] }], 64 | generationConfig: { 65 | temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE, 66 | }, 67 | }) 68 | 69 | return result.response.text() 70 | } catch (error) { 71 | if (error instanceof Error) { 72 | throw new Error(`Gemini completion error: ${error.message}`) 73 | } 74 | throw error 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/api/providers/lmstudio.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | import OpenAI from "openai" 3 | import { ApiHandler, SingleCompletionHandler } from "../" 4 | import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api" 5 | import { convertToOpenAiMessages } from "../transform/openai-format" 6 | import { ApiStream } from "../transform/stream" 7 | 8 | const LMSTUDIO_DEFAULT_TEMPERATURE = 0 9 | 10 | export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { 11 | private options: ApiHandlerOptions 12 | private client: OpenAI 13 | 14 | constructor(options: ApiHandlerOptions) { 15 | this.options = options 16 | this.client = new OpenAI({ 17 | baseURL: (this.options.lmStudioBaseUrl || "http://localhost:1234") + "/v1", 18 | apiKey: "noop", 19 | }) 20 | } 21 | 22 | async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { 23 | const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ 24 | { role: "system", content: systemPrompt }, 25 | ...convertToOpenAiMessages(messages), 26 | ] 27 | 28 | try { 29 | const stream = await this.client.chat.completions.create({ 30 | model: this.getModel().id, 31 | messages: openAiMessages, 32 | temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE, 33 | stream: true, 34 | }) 35 | for await (const chunk of stream) { 36 | const delta = chunk.choices[0]?.delta 37 | if (delta?.content) { 38 | yield { 39 | type: "text", 40 | text: delta.content, 41 | } 42 | } 43 | } 44 | } catch (error) { 45 | // LM Studio doesn't return an error code/body for now 46 | throw new Error( 47 | "Please check the LM Studio developer logs to debug what went wrong. You may need to load the model with a larger context length to work with Roo Code's prompts.", 48 | ) 49 | } 50 | } 51 | 52 | getModel(): { id: string; info: ModelInfo } { 53 | return { 54 | id: this.options.lmStudioModelId || "", 55 | info: openAiModelInfoSaneDefaults, 56 | } 57 | } 58 | 59 | async completePrompt(prompt: string): Promise { 60 | try { 61 | const response = await this.client.chat.completions.create({ 62 | model: this.getModel().id, 63 | messages: [{ role: "user", content: prompt }], 64 | temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE, 65 | stream: false, 66 | }) 67 | return response.choices[0]?.message.content || "" 68 | } catch (error) { 69 | throw new Error( 70 | "Please check the LM Studio developer logs to debug what went wrong. You may need to load the model with a larger context length to work with Roo Code's prompts.", 71 | ) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/api/providers/mistral.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | import { Mistral } from "@mistralai/mistralai" 3 | import { ApiHandler } from "../" 4 | import { 5 | ApiHandlerOptions, 6 | mistralDefaultModelId, 7 | MistralModelId, 8 | mistralModels, 9 | ModelInfo, 10 | openAiNativeDefaultModelId, 11 | OpenAiNativeModelId, 12 | openAiNativeModels, 13 | } from "../../shared/api" 14 | import { convertToMistralMessages } from "../transform/mistral-format" 15 | import { ApiStream } from "../transform/stream" 16 | 17 | const MISTRAL_DEFAULT_TEMPERATURE = 0 18 | 19 | export class MistralHandler implements ApiHandler { 20 | private options: ApiHandlerOptions 21 | private client: Mistral 22 | 23 | constructor(options: ApiHandlerOptions) { 24 | this.options = options 25 | this.client = new Mistral({ 26 | serverURL: "https://codestral.mistral.ai", 27 | apiKey: this.options.mistralApiKey, 28 | }) 29 | } 30 | 31 | async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { 32 | const stream = await this.client.chat.stream({ 33 | model: this.getModel().id, 34 | // max_completion_tokens: this.getModel().info.maxTokens, 35 | temperature: this.options.modelTemperature ?? MISTRAL_DEFAULT_TEMPERATURE, 36 | messages: [{ role: "system", content: systemPrompt }, ...convertToMistralMessages(messages)], 37 | stream: true, 38 | }) 39 | 40 | for await (const chunk of stream) { 41 | const delta = chunk.data.choices[0]?.delta 42 | if (delta?.content) { 43 | let content: string = "" 44 | if (typeof delta.content === "string") { 45 | content = delta.content 46 | } else if (Array.isArray(delta.content)) { 47 | content = delta.content.map((c) => (c.type === "text" ? c.text : "")).join("") 48 | } 49 | yield { 50 | type: "text", 51 | text: content, 52 | } 53 | } 54 | 55 | if (chunk.data.usage) { 56 | yield { 57 | type: "usage", 58 | inputTokens: chunk.data.usage.promptTokens || 0, 59 | outputTokens: chunk.data.usage.completionTokens || 0, 60 | } 61 | } 62 | } 63 | } 64 | 65 | getModel(): { id: MistralModelId; info: ModelInfo } { 66 | const modelId = this.options.apiModelId 67 | if (modelId && modelId in mistralModels) { 68 | const id = modelId as MistralModelId 69 | return { id, info: mistralModels[id] } 70 | } 71 | return { 72 | id: mistralDefaultModelId, 73 | info: mistralModels[mistralDefaultModelId], 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/api/providers/requesty.ts: -------------------------------------------------------------------------------- 1 | import { OpenAiHandler, OpenAiHandlerOptions } from "./openai" 2 | import { ModelInfo, requestyModelInfoSaneDefaults, requestyDefaultModelId } from "../../shared/api" 3 | import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" 4 | 5 | export class RequestyHandler extends OpenAiHandler { 6 | constructor(options: OpenAiHandlerOptions) { 7 | if (!options.requestyApiKey) { 8 | throw new Error("Requesty API key is required. Please provide it in the settings.") 9 | } 10 | super({ 11 | ...options, 12 | openAiApiKey: options.requestyApiKey, 13 | openAiModelId: options.requestyModelId ?? requestyDefaultModelId, 14 | openAiBaseUrl: "https://router.requesty.ai/v1", 15 | openAiCustomModelInfo: options.requestyModelInfo ?? requestyModelInfoSaneDefaults, 16 | defaultHeaders: { 17 | "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", 18 | "X-Title": "Roo Code", 19 | }, 20 | }) 21 | } 22 | 23 | override getModel(): { id: string; info: ModelInfo } { 24 | const modelId = this.options.requestyModelId ?? requestyDefaultModelId 25 | return { 26 | id: modelId, 27 | info: this.options.requestyModelInfo ?? requestyModelInfoSaneDefaults, 28 | } 29 | } 30 | 31 | protected override processUsageMetrics(usage: any): ApiStreamUsageChunk { 32 | return { 33 | type: "usage", 34 | inputTokens: usage?.prompt_tokens || 0, 35 | outputTokens: usage?.completion_tokens || 0, 36 | cacheWriteTokens: usage?.cache_creation_input_tokens, 37 | cacheReadTokens: usage?.cache_read_input_tokens, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/transform/simple-format.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | 3 | /** 4 | * Convert complex content blocks to simple string content 5 | */ 6 | export function convertToSimpleContent( 7 | content: 8 | | string 9 | | Array< 10 | | Anthropic.Messages.TextBlockParam 11 | | Anthropic.Messages.ImageBlockParam 12 | | Anthropic.Messages.ToolUseBlockParam 13 | | Anthropic.Messages.ToolResultBlockParam 14 | >, 15 | ): string { 16 | if (typeof content === "string") { 17 | return content 18 | } 19 | 20 | // Extract text from content blocks 21 | return content 22 | .map((block) => { 23 | if (block.type === "text") { 24 | return block.text 25 | } 26 | if (block.type === "image") { 27 | return `[Image: ${block.source.media_type}]` 28 | } 29 | if (block.type === "tool_use") { 30 | return `[Tool Use: ${block.name}]` 31 | } 32 | if (block.type === "tool_result") { 33 | if (typeof block.content === "string") { 34 | return block.content 35 | } 36 | if (Array.isArray(block.content)) { 37 | return block.content 38 | .map((part) => { 39 | if (part.type === "text") { 40 | return part.text 41 | } 42 | if (part.type === "image") { 43 | return `[Image: ${part.source.media_type}]` 44 | } 45 | return "" 46 | }) 47 | .join("\n") 48 | } 49 | return "" 50 | } 51 | return "" 52 | }) 53 | .filter(Boolean) 54 | .join("\n") 55 | } 56 | 57 | /** 58 | * Convert Anthropic messages to simple format with string content 59 | */ 60 | export function convertToSimpleMessages( 61 | messages: Anthropic.Messages.MessageParam[], 62 | ): Array<{ role: "user" | "assistant"; content: string }> { 63 | return messages.map((message) => ({ 64 | role: message.role, 65 | content: convertToSimpleContent(message.content), 66 | })) 67 | } 68 | -------------------------------------------------------------------------------- /src/api/transform/stream.ts: -------------------------------------------------------------------------------- 1 | export type ApiStream = AsyncGenerator 2 | export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamUsageChunk | ApiStreamReasoningChunk 3 | 4 | export interface ApiStreamTextChunk { 5 | type: "text" 6 | text: string 7 | } 8 | 9 | export interface ApiStreamReasoningChunk { 10 | type: "reasoning" 11 | text: string 12 | } 13 | 14 | export interface ApiStreamUsageChunk { 15 | type: "usage" 16 | inputTokens: number 17 | outputTokens: number 18 | cacheWriteTokens?: number 19 | cacheReadTokens?: number 20 | totalCost?: number // openrouter 21 | } 22 | -------------------------------------------------------------------------------- /src/core/config/__tests__/GroupConfigSchema.test.ts: -------------------------------------------------------------------------------- 1 | import { CustomModeSchema } from "../CustomModesSchema" 2 | import { ModeConfig } from "../../../shared/modes" 3 | 4 | describe("GroupConfigSchema", () => { 5 | const validBaseMode = { 6 | slug: "123e4567-e89b-12d3-a456-426614174000", 7 | name: "Test Mode", 8 | roleDefinition: "Test role definition", 9 | } 10 | 11 | describe("group format validation", () => { 12 | test("accepts single group", () => { 13 | const mode = { 14 | ...validBaseMode, 15 | groups: ["read"] as const, 16 | } satisfies ModeConfig 17 | 18 | expect(() => CustomModeSchema.parse(mode)).not.toThrow() 19 | }) 20 | 21 | test("accepts multiple groups", () => { 22 | const mode = { 23 | ...validBaseMode, 24 | groups: ["read", "edit", "browser"] as const, 25 | } satisfies ModeConfig 26 | 27 | expect(() => CustomModeSchema.parse(mode)).not.toThrow() 28 | }) 29 | 30 | test("accepts all available groups", () => { 31 | const mode = { 32 | ...validBaseMode, 33 | groups: ["read", "edit", "browser", "command", "mcp"] as const, 34 | } satisfies ModeConfig 35 | 36 | expect(() => CustomModeSchema.parse(mode)).not.toThrow() 37 | }) 38 | 39 | test("rejects non-array group format", () => { 40 | const mode = { 41 | ...validBaseMode, 42 | groups: "not-an-array" as any, 43 | } 44 | 45 | expect(() => CustomModeSchema.parse(mode)).toThrow() 46 | }) 47 | 48 | test("rejects empty groups array", () => { 49 | const mode = { 50 | ...validBaseMode, 51 | groups: [] as const, 52 | } satisfies ModeConfig 53 | 54 | expect(() => CustomModeSchema.parse(mode)).toThrow("At least one tool group is required") 55 | }) 56 | 57 | test("rejects invalid group names", () => { 58 | const mode = { 59 | ...validBaseMode, 60 | groups: ["invalid_group"] as any, 61 | } 62 | 63 | expect(() => CustomModeSchema.parse(mode)).toThrow() 64 | }) 65 | 66 | test("rejects duplicate groups", () => { 67 | const mode = { 68 | ...validBaseMode, 69 | groups: ["read", "read"] as any, 70 | } 71 | 72 | expect(() => CustomModeSchema.parse(mode)).toThrow("Duplicate groups are not allowed") 73 | }) 74 | 75 | test("rejects null or undefined groups", () => { 76 | const modeWithNull = { 77 | ...validBaseMode, 78 | groups: null as any, 79 | } 80 | 81 | const modeWithUndefined = { 82 | ...validBaseMode, 83 | groups: undefined as any, 84 | } 85 | 86 | expect(() => CustomModeSchema.parse(modeWithNull)).toThrow() 87 | expect(() => CustomModeSchema.parse(modeWithUndefined)).toThrow() 88 | }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /src/core/diff/DiffStrategy.ts: -------------------------------------------------------------------------------- 1 | import type { DiffStrategy } from "./types" 2 | import { UnifiedDiffStrategy } from "./strategies/unified" 3 | import { SearchReplaceDiffStrategy } from "./strategies/search-replace" 4 | import { NewUnifiedDiffStrategy } from "./strategies/new-unified" 5 | /** 6 | * Get the appropriate diff strategy for the given model 7 | * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus') 8 | * @returns The appropriate diff strategy for the model 9 | */ 10 | export function getDiffStrategy( 11 | model: string, 12 | fuzzyMatchThreshold?: number, 13 | experimentalDiffStrategy: boolean = false, 14 | ): DiffStrategy { 15 | if (experimentalDiffStrategy) { 16 | return new NewUnifiedDiffStrategy(fuzzyMatchThreshold) 17 | } 18 | return new SearchReplaceDiffStrategy(fuzzyMatchThreshold) 19 | } 20 | 21 | export type { DiffStrategy } 22 | export { UnifiedDiffStrategy, SearchReplaceDiffStrategy } 23 | -------------------------------------------------------------------------------- /src/core/diff/insert-groups.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inserts multiple groups of elements at specified indices in an array 3 | * @param original Array to insert into, split by lines 4 | * @param insertGroups Array of groups to insert, each with an index and elements to insert 5 | * @returns New array with all insertions applied 6 | */ 7 | export interface InsertGroup { 8 | index: number 9 | elements: string[] 10 | } 11 | 12 | export function insertGroups(original: string[], insertGroups: InsertGroup[]): string[] { 13 | // Sort groups by index to maintain order 14 | insertGroups.sort((a, b) => a.index - b.index) 15 | 16 | let result: string[] = [] 17 | let lastIndex = 0 18 | 19 | insertGroups.forEach(({ index, elements }) => { 20 | // Add elements from original array up to insertion point 21 | result.push(...original.slice(lastIndex, index)) 22 | // Add the group of elements 23 | result.push(...elements) 24 | lastIndex = index 25 | }) 26 | 27 | // Add remaining elements from original array 28 | result.push(...original.slice(lastIndex)) 29 | 30 | return result 31 | } 32 | -------------------------------------------------------------------------------- /src/core/diff/strategies/new-unified/types.ts: -------------------------------------------------------------------------------- 1 | export type Change = { 2 | type: "context" | "add" | "remove" 3 | content: string 4 | indent: string 5 | originalLine?: string 6 | } 7 | 8 | export type Hunk = { 9 | changes: Change[] 10 | } 11 | 12 | export type Diff = { 13 | hunks: Hunk[] 14 | } 15 | 16 | export type EditResult = { 17 | confidence: number 18 | result: string[] 19 | strategy: string 20 | } 21 | -------------------------------------------------------------------------------- /src/core/diff/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface for implementing different diff strategies 3 | */ 4 | 5 | export type DiffResult = 6 | | { success: true; content: string } 7 | | { 8 | success: false 9 | error: string 10 | details?: { 11 | similarity?: number 12 | threshold?: number 13 | matchedRange?: { start: number; end: number } 14 | searchContent?: string 15 | bestMatch?: string 16 | } 17 | } 18 | 19 | export interface DiffStrategy { 20 | /** 21 | * Get the tool description for this diff strategy 22 | * @param args The tool arguments including cwd and toolOptions 23 | * @returns The complete tool description including format requirements and examples 24 | */ 25 | getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string 26 | 27 | /** 28 | * Apply a diff to the original content 29 | * @param originalContent The original file content 30 | * @param diffContent The diff content in the strategy's format 31 | * @param startLine Optional line number where the search block starts. If not provided, searches the entire file. 32 | * @param endLine Optional line number where the search block ends. If not provided, searches the entire file. 33 | * @returns A DiffResult object containing either the successful result or error details 34 | */ 35 | applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise 36 | } 37 | -------------------------------------------------------------------------------- /src/core/mode-validator.ts: -------------------------------------------------------------------------------- 1 | import { Mode, isToolAllowedForMode, getModeConfig, ModeConfig, FileRestrictionError } from "../shared/modes" 2 | import { ToolName } from "../shared/tool-groups" 3 | 4 | export { isToolAllowedForMode } 5 | export type { ToolName } 6 | 7 | export function validateToolUse( 8 | toolName: ToolName, 9 | mode: Mode, 10 | customModes?: ModeConfig[], 11 | toolRequirements?: Record, 12 | toolParams?: Record, 13 | ): void { 14 | if (!isToolAllowedForMode(toolName, mode, customModes ?? [], toolRequirements, toolParams)) { 15 | throw new Error(`Tool "${toolName}" is not allowed in ${mode} mode.`) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/core/prompts/__tests__/sections.test.ts: -------------------------------------------------------------------------------- 1 | import { addCustomInstructions } from "../sections/custom-instructions" 2 | import { getCapabilitiesSection } from "../sections/capabilities" 3 | import { DiffStrategy, DiffResult } from "../../diff/types" 4 | 5 | describe("addCustomInstructions", () => { 6 | test("adds preferred language to custom instructions", async () => { 7 | const result = await addCustomInstructions( 8 | "mode instructions", 9 | "global instructions", 10 | "/test/path", 11 | "test-mode", 12 | { preferredLanguage: "French" }, 13 | ) 14 | 15 | expect(result).toContain("Language Preference:") 16 | expect(result).toContain("You should always speak and think in the French language") 17 | }) 18 | 19 | test("works without preferred language", async () => { 20 | const result = await addCustomInstructions( 21 | "mode instructions", 22 | "global instructions", 23 | "/test/path", 24 | "test-mode", 25 | ) 26 | 27 | expect(result).not.toContain("Language Preference:") 28 | expect(result).not.toContain("You should always speak and think in") 29 | }) 30 | }) 31 | 32 | describe("getCapabilitiesSection", () => { 33 | const cwd = "/test/path" 34 | const mcpHub = undefined 35 | const mockDiffStrategy: DiffStrategy = { 36 | getToolDescription: () => "apply_diff tool description", 37 | applyDiff: async (originalContent: string, diffContent: string): Promise => { 38 | return { success: true, content: "mock result" } 39 | }, 40 | } 41 | 42 | test("includes apply_diff in capabilities when diffStrategy is provided", () => { 43 | const result = getCapabilitiesSection(cwd, false, mcpHub, mockDiffStrategy) 44 | 45 | expect(result).toContain("or apply_diff") 46 | expect(result).toContain("then use the write_to_file or apply_diff tool") 47 | }) 48 | 49 | test("excludes apply_diff from capabilities when diffStrategy is undefined", () => { 50 | const result = getCapabilitiesSection(cwd, false, mcpHub, undefined) 51 | 52 | expect(result).not.toContain("or apply_diff") 53 | expect(result).toContain("then use the write_to_file tool") 54 | expect(result).not.toContain("write_to_file or apply_diff") 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /src/core/prompts/sections/index.ts: -------------------------------------------------------------------------------- 1 | export { getRulesSection } from "./rules" 2 | export { getSystemInfoSection } from "./system-info" 3 | export { getObjectiveSection } from "./objective" 4 | export { addCustomInstructions } from "./custom-instructions" 5 | export { getSharedToolUseSection } from "./tool-use" 6 | export { getMcpServersSection } from "./mcp-servers" 7 | export { getToolUseGuidelinesSection } from "./tool-use-guidelines" 8 | export { getCapabilitiesSection } from "./capabilities" 9 | export { getModesSection } from "./modes" 10 | -------------------------------------------------------------------------------- /src/core/prompts/sections/objective.ts: -------------------------------------------------------------------------------- 1 | export function getObjectiveSection(): string { 2 | return `==== 3 | 4 | OBJECTIVE 5 | 6 | You accomplish a given task iteratively, breaking it down into clear steps and working through them methodically. 7 | 8 | 1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order. 9 | 2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go. 10 | 3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided. 11 | 4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built. 12 | 5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.` 13 | } 14 | -------------------------------------------------------------------------------- /src/core/prompts/sections/system-info.ts: -------------------------------------------------------------------------------- 1 | import defaultShell from "default-shell" 2 | import os from "os" 3 | import osName from "os-name" 4 | import { Mode, ModeConfig, getModeBySlug, defaultModeSlug, isToolAllowedForMode } from "../../../shared/modes" 5 | import { getShell } from "../../../utils/shell" 6 | 7 | export function getSystemInfoSection(cwd: string, currentMode: Mode, customModes?: ModeConfig[]): string { 8 | const findModeBySlug = (slug: string, modes?: ModeConfig[]) => modes?.find((m) => m.slug === slug) 9 | 10 | const currentModeName = findModeBySlug(currentMode, customModes)?.name || currentMode 11 | const codeModeName = findModeBySlug(defaultModeSlug, customModes)?.name || "Code" 12 | 13 | let details = `==== 14 | 15 | SYSTEM INFORMATION 16 | 17 | Operating System: ${osName()} 18 | Default Shell: ${getShell()} 19 | Home Directory: ${os.homedir().toPosix()} 20 | Current Working Directory: ${cwd.toPosix()} 21 | 22 | When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('/test/path') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.` 23 | 24 | return details 25 | } 26 | -------------------------------------------------------------------------------- /src/core/prompts/sections/tool-use-guidelines.ts: -------------------------------------------------------------------------------- 1 | export function getToolUseGuidelinesSection(): string { 2 | return `# Tool Use Guidelines 3 | 4 | 1. In tags, assess what information you already have and what information you need to proceed with the task. 5 | 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 6 | 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 7 | 4. Formulate your tool use using the XML format specified for each tool. 8 | 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: 9 | - Information about whether the tool succeeded or failed, along with any reasons for failure. 10 | - Linter errors that may have arisen due to the changes you made, which you'll need to address. 11 | - New terminal output in reaction to the changes, which you may need to consider or act upon. 12 | - Any other relevant feedback or information related to the tool use. 13 | 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. 14 | 15 | It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 16 | 1. Confirm the success of each step before proceeding. 17 | 2. Address any issues or errors that arise immediately. 18 | 3. Adapt your approach based on new information or unexpected results. 19 | 4. Ensure that each action builds correctly on the previous ones. 20 | 21 | By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.` 22 | } 23 | -------------------------------------------------------------------------------- /src/core/prompts/sections/tool-use.ts: -------------------------------------------------------------------------------- 1 | export function getSharedToolUseSection(): string { 2 | return `==== 3 | 4 | TOOL USE 5 | 6 | You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. 7 | 8 | # Tool Use Formatting 9 | 10 | Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: 11 | 12 | 13 | value1 14 | value2 15 | ... 16 | 17 | 18 | For example: 19 | 20 | 21 | src/main.js 22 | 23 | 24 | Always adhere to this format for the tool use to ensure proper parsing and execution.` 25 | } 26 | -------------------------------------------------------------------------------- /src/core/prompts/tools/access-mcp-resource.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getAccessMcpResourceDescription(args: ToolArgs): string | undefined { 4 | if (!args.mcpHub) { 5 | return undefined 6 | } 7 | return `## access_mcp_resource 8 | Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information. 9 | Parameters: 10 | - server_name: (required) The name of the MCP server providing the resource 11 | - uri: (required) The URI identifying the specific resource to access 12 | Usage: 13 | 14 | server name here 15 | resource URI here 16 | 17 | 18 | Example: Requesting to access an MCP resource 19 | 20 | 21 | weather-server 22 | weather://san-francisco/current 23 | ` 24 | } 25 | -------------------------------------------------------------------------------- /src/core/prompts/tools/ask-followup-question.ts: -------------------------------------------------------------------------------- 1 | export function getAskFollowupQuestionDescription(): string { 2 | return `## ask_followup_question 3 | Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth. 4 | Parameters: 5 | - question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. 6 | Usage: 7 | 8 | Your question here 9 | 10 | 11 | Example: Requesting to ask the user for the path to the frontend-config.json file 12 | 13 | What is the path to the frontend-config.json file? 14 | ` 15 | } 16 | -------------------------------------------------------------------------------- /src/core/prompts/tools/attempt-completion.ts: -------------------------------------------------------------------------------- 1 | export function getAttemptCompletionDescription(): string { 2 | return `## attempt_completion 3 | Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. Optionally you may provide a CLI command to showcase the result of your work. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. 4 | IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. 5 | Parameters: 6 | - result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. 7 | - command: (optional) A CLI command to execute to show a live demo of the result to the user. For example, use \`open index.html\` to display a created html website, or \`open localhost:3000\` to display a locally running development server. But DO NOT use commands like \`echo\` or \`cat\` that merely print text. This command should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions. 8 | Usage: 9 | 10 | 11 | Your final result description here 12 | 13 | Command to demonstrate result (optional) 14 | 15 | 16 | Example: Requesting to attempt completion with a result and command 17 | 18 | 19 | I've updated the CSS 20 | 21 | open index.html 22 | ` 23 | } 24 | -------------------------------------------------------------------------------- /src/core/prompts/tools/execute-command.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getExecuteCommandDescription(args: ToolArgs): string | undefined { 4 | return `## execute_command 5 | Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${args.cwd} 6 | Parameters: 7 | - command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions. 8 | Usage: 9 | 10 | Your command here 11 | 12 | 13 | Example: Requesting to execute npm run dev 14 | 15 | npm run dev 16 | ` 17 | } 18 | -------------------------------------------------------------------------------- /src/core/prompts/tools/insert-content.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getInsertContentDescription(args: ToolArgs): string { 4 | return `## insert_content 5 | Description: Inserts content at specific line positions in a file. This is the primary tool for adding new content and code (functions/methods/classes, imports, attributes etc.) as it allows for precise insertions without overwriting existing content. The tool uses an efficient line-based insertion system that maintains file integrity and proper ordering of multiple insertions. Beware to use the proper indentation. This tool is the preferred way to add new content and code to files. 6 | Parameters: 7 | - path: (required) The path of the file to insert content into (relative to the current working directory ${args.cwd.toPosix()}) 8 | - operations: (required) A JSON array of insertion operations. Each operation is an object with: 9 | * start_line: (required) The line number where the content should be inserted. The content currently at that line will end up below the inserted content. 10 | * content: (required) The content to insert at the specified position. IMPORTANT NOTE: If the content is a single line, it can be a string. If it's a multi-line content, it should be a string with newline characters (\n) for line breaks. Make sure to include the correct indentation for the content. 11 | Usage: 12 | 13 | File path here 14 | [ 15 | { 16 | "start_line": 10, 17 | "content": "Your content here" 18 | } 19 | ] 20 | 21 | Example: Insert a new function and its import statement 22 | 23 | File path here 24 | [ 25 | { 26 | "start_line": 1, 27 | "content": "import { sum } from './utils';" 28 | }, 29 | { 30 | "start_line": 10, 31 | "content": "function calculateTotal(items: number[]): number {\n return items.reduce((sum, item) => sum + item, 0);\n}" 32 | } 33 | ] 34 | ` 35 | } 36 | -------------------------------------------------------------------------------- /src/core/prompts/tools/list-code-definition-names.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getListCodeDefinitionNamesDescription(args: ToolArgs): string { 4 | return `## list_code_definition_names 5 | Description: Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture. 6 | Parameters: 7 | - path: (required) The path of the directory (relative to the current working directory ${args.cwd}) to list top level source code definitions for. 8 | Usage: 9 | 10 | Directory path here 11 | 12 | 13 | Example: Requesting to list all top level source code definitions in the current directory 14 | 15 | . 16 | ` 17 | } 18 | -------------------------------------------------------------------------------- /src/core/prompts/tools/list-files.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getListFilesDescription(args: ToolArgs): string { 4 | return `## list_files 5 | Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. 6 | Parameters: 7 | - path: (required) The path of the directory to list contents for (relative to the current working directory ${args.cwd}) 8 | - recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. 9 | Usage: 10 | 11 | Directory path here 12 | true or false (optional) 13 | 14 | 15 | Example: Requesting to list all files in the current directory 16 | 17 | . 18 | false 19 | ` 20 | } 21 | -------------------------------------------------------------------------------- /src/core/prompts/tools/new-task.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getNewTaskDescription(args: ToolArgs): string { 4 | return `## new_task 5 | Description: Create a new task with a specified starting mode and initial message. This tool instructs the system to create a new Cline instance in the given mode with the provided message. 6 | 7 | Parameters: 8 | - mode: (required) The slug of the mode to start the new task in (e.g., "code", "ask", "architect"). 9 | - message: (required) The initial user message or instructions for this new task. 10 | 11 | Usage: 12 | 13 | your-mode-slug-here 14 | Your initial instructions here 15 | 16 | 17 | Example: 18 | 19 | code 20 | Implement a new feature for the application. 21 | 22 | ` 23 | } 24 | -------------------------------------------------------------------------------- /src/core/prompts/tools/read-file.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getReadFileDescription(args: ToolArgs): string { 4 | return `## read_file 5 | Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. The output includes line numbers prefixed to each line (e.g. "1 | const x = 1"), making it easier to reference specific lines when creating diffs or discussing code. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string. 6 | Parameters: 7 | - path: (required) The path of the file to read (relative to the current working directory ${args.cwd}) 8 | Usage: 9 | 10 | File path here 11 | 12 | 13 | Example: Requesting to read frontend-config.json 14 | 15 | frontend-config.json 16 | ` 17 | } 18 | -------------------------------------------------------------------------------- /src/core/prompts/tools/search-and-replace.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getSearchAndReplaceDescription(args: ToolArgs): string { 4 | return `## search_and_replace 5 | Description: Request to perform search and replace operations on a file. Each operation can specify a search pattern (string or regex) and replacement text, with optional line range restrictions and regex flags. Shows a diff preview before applying changes. 6 | Parameters: 7 | - path: (required) The path of the file to modify (relative to the current working directory ${args.cwd.toPosix()}) 8 | - operations: (required) A JSON array of search/replace operations. Each operation is an object with: 9 | * search: (required) The text or pattern to search for 10 | * replace: (required) The text to replace matches with. If multiple lines need to be replaced, use "\n" for newlines 11 | * start_line: (optional) Starting line number for restricted replacement 12 | * end_line: (optional) Ending line number for restricted replacement 13 | * use_regex: (optional) Whether to treat search as a regex pattern 14 | * ignore_case: (optional) Whether to ignore case when matching 15 | * regex_flags: (optional) Additional regex flags when use_regex is true 16 | Usage: 17 | 18 | File path here 19 | [ 20 | { 21 | "search": "text to find", 22 | "replace": "replacement text", 23 | "start_line": 1, 24 | "end_line": 10 25 | } 26 | ] 27 | 28 | Example: Replace "foo" with "bar" in lines 1-10 of example.ts 29 | 30 | example.ts 31 | [ 32 | { 33 | "search": "foo", 34 | "replace": "bar", 35 | "start_line": 1, 36 | "end_line": 10 37 | } 38 | ] 39 | 40 | Example: Replace all occurrences of "old" with "new" using regex 41 | 42 | example.ts 43 | [ 44 | { 45 | "search": "old\\w+", 46 | "replace": "new$&", 47 | "use_regex": true, 48 | "ignore_case": true 49 | } 50 | ] 51 | ` 52 | } 53 | -------------------------------------------------------------------------------- /src/core/prompts/tools/search-files.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getSearchFilesDescription(args: ToolArgs): string { 4 | return `## search_files 5 | Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. 6 | Parameters: 7 | - path: (required) The path of the directory to search in (relative to the current working directory ${args.cwd}). This directory will be recursively searched. 8 | - regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. 9 | - file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). 10 | Usage: 11 | 12 | Directory path here 13 | Your regex pattern here 14 | file pattern here (optional) 15 | 16 | 17 | Example: Requesting to search for all .ts files in the current directory 18 | 19 | . 20 | .* 21 | *.ts 22 | ` 23 | } 24 | -------------------------------------------------------------------------------- /src/core/prompts/tools/switch-mode.ts: -------------------------------------------------------------------------------- 1 | export function getSwitchModeDescription(): string { 2 | return `## switch_mode 3 | Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. 4 | Parameters: 5 | - mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") 6 | - reason: (optional) The reason for switching modes 7 | Usage: 8 | 9 | Mode slug here 10 | Reason for switching here 11 | 12 | 13 | Example: Requesting to switch to code mode 14 | 15 | code 16 | Need to make code changes 17 | ` 18 | } 19 | -------------------------------------------------------------------------------- /src/core/prompts/tools/types.ts: -------------------------------------------------------------------------------- 1 | import { DiffStrategy } from "../../diff/DiffStrategy" 2 | import { McpHub } from "../../../services/mcp/McpHub" 3 | 4 | export type ToolArgs = { 5 | cwd: string 6 | supportsComputerUse: boolean 7 | diffStrategy?: DiffStrategy 8 | browserViewportSize?: string 9 | mcpHub?: McpHub 10 | toolOptions?: any 11 | } 12 | -------------------------------------------------------------------------------- /src/core/prompts/tools/use-mcp-tool.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getUseMcpToolDescription(args: ToolArgs): string | undefined { 4 | if (!args.mcpHub) { 5 | return undefined 6 | } 7 | return `## use_mcp_tool 8 | Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters. 9 | Parameters: 10 | - server_name: (required) The name of the MCP server providing the tool 11 | - tool_name: (required) The name of the tool to execute 12 | - arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema 13 | Usage: 14 | 15 | server name here 16 | tool name here 17 | 18 | { 19 | "param1": "value1", 20 | "param2": "value2" 21 | } 22 | 23 | 24 | 25 | Example: Requesting to use an MCP tool 26 | 27 | 28 | weather-server 29 | get_forecast 30 | 31 | { 32 | "city": "San Francisco", 33 | "days": 5 34 | } 35 | 36 | ` 37 | } 38 | -------------------------------------------------------------------------------- /src/core/prompts/tools/write-to-file.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getWriteToFileDescription(args: ToolArgs): string { 4 | return `## write_to_file 5 | Description: Request to write full content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. 6 | Parameters: 7 | - path: (required) The path of the file to write to (relative to the current working directory ${args.cwd}) 8 | - content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. 9 | - line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. 10 | Usage: 11 | 12 | File path here 13 | 14 | Your file content here 15 | 16 | total number of lines in the file, including empty lines 17 | 18 | 19 | Example: Requesting to write to frontend-config.json 20 | 21 | frontend-config.json 22 | 23 | { 24 | "apiEndpoint": "https://api.example.com", 25 | "theme": { 26 | "primaryColor": "#007bff", 27 | "secondaryColor": "#6c757d", 28 | "fontFamily": "Arial, sans-serif" 29 | }, 30 | "features": { 31 | "darkMode": true, 32 | "notifications": true, 33 | "analytics": false 34 | }, 35 | "version": "1.0.0" 36 | } 37 | 38 | 14 39 | ` 40 | } 41 | -------------------------------------------------------------------------------- /src/core/webview/getNonce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A helper function that returns a unique alphanumeric identifier called a nonce. 3 | * 4 | * @remarks This function is primarily used to help enforce content security 5 | * policies for resources/scripts being executed in a webview context. 6 | * 7 | * @returns A nonce 8 | */ 9 | export function getNonce() { 10 | let text = "" 11 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 12 | for (let i = 0; i < 32; i++) { 13 | text += possible.charAt(Math.floor(Math.random() * possible.length)) 14 | } 15 | return text 16 | } 17 | -------------------------------------------------------------------------------- /src/core/webview/getUri.ts: -------------------------------------------------------------------------------- 1 | import { Uri, Webview } from "vscode" 2 | /** 3 | * A helper function which will get the webview URI of a given file or resource. 4 | * 5 | * @remarks This URI can be used within a webview's HTML as a link to the 6 | * given file/resource. 7 | * 8 | * @param webview A reference to the extension webview 9 | * @param extensionUri The URI of the directory containing the extension 10 | * @param pathList An array of strings representing the path to a file/resource 11 | * @returns A URI pointing to the file/resource 12 | */ 13 | export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { 14 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)) 15 | } 16 | -------------------------------------------------------------------------------- /src/exports/README.md: -------------------------------------------------------------------------------- 1 | # Cline API 2 | 3 | The Cline extension exposes an API that can be used by other extensions. To use this API in your extension: 4 | 5 | 1. Copy `src/extension-api/cline.d.ts` to your extension's source directory. 6 | 2. Include `cline.d.ts` in your extension's compilation. 7 | 3. Get access to the API with the following code: 8 | 9 | ```ts 10 | const clineExtension = vscode.extensions.getExtension("rooveterinaryinc.roo-cline") 11 | 12 | if (!clineExtension?.isActive) { 13 | throw new Error("Cline extension is not activated") 14 | } 15 | 16 | const cline = clineExtension.exports 17 | 18 | if (cline) { 19 | // Now you can use the API 20 | 21 | // Set custom instructions 22 | await cline.setCustomInstructions("Talk like a pirate") 23 | 24 | // Get custom instructions 25 | const instructions = await cline.getCustomInstructions() 26 | console.log("Current custom instructions:", instructions) 27 | 28 | // Start a new task with an initial message 29 | await cline.startNewTask("Hello, Cline! Let's make a new project...") 30 | 31 | // Start a new task with an initial message and images 32 | await cline.startNewTask("Use this design language", ["data:image/webp;base64,..."]) 33 | 34 | // Send a message to the current task 35 | await cline.sendMessage("Can you fix the @problems?") 36 | 37 | // Simulate pressing the primary button in the chat interface (e.g. 'Save' or 'Proceed While Running') 38 | await cline.pressPrimaryButton() 39 | 40 | // Simulate pressing the secondary button in the chat interface (e.g. 'Reject') 41 | await cline.pressSecondaryButton() 42 | } else { 43 | console.error("Cline API is not available") 44 | } 45 | ``` 46 | 47 | **Note:** To ensure that the `rooveterinaryinc.roo-cline` extension is activated before your extension, add it to the `extensionDependencies` in your `package.json`: 48 | 49 | ```json 50 | "extensionDependencies": [ 51 | "rooveterinaryinc.roo-cline" 52 | ] 53 | ``` 54 | 55 | For detailed information on the available methods and their usage, refer to the `cline.d.ts` file. 56 | -------------------------------------------------------------------------------- /src/exports/cline.d.ts: -------------------------------------------------------------------------------- 1 | export interface ClineAPI { 2 | /** 3 | * Sets the custom instructions in the global storage. 4 | * @param value The custom instructions to be saved. 5 | */ 6 | setCustomInstructions(value: string): Promise 7 | 8 | /** 9 | * Retrieves the custom instructions from the global storage. 10 | * @returns The saved custom instructions, or undefined if not set. 11 | */ 12 | getCustomInstructions(): Promise 13 | 14 | /** 15 | * Starts a new task with an optional initial message and images. 16 | * @param task Optional initial task message. 17 | * @param images Optional array of image data URIs (e.g., "data:image/webp;base64,..."). 18 | */ 19 | startNewTask(task?: string, images?: string[]): Promise 20 | 21 | /** 22 | * Sends a message to the current task. 23 | * @param message Optional message to send. 24 | * @param images Optional array of image data URIs (e.g., "data:image/webp;base64,..."). 25 | */ 26 | sendMessage(message?: string, images?: string[]): Promise 27 | 28 | /** 29 | * Simulates pressing the primary button in the chat interface. 30 | */ 31 | pressPrimaryButton(): Promise 32 | 33 | /** 34 | * Simulates pressing the secondary button in the chat interface. 35 | */ 36 | pressSecondaryButton(): Promise 37 | 38 | /** 39 | * The sidebar provider instance. 40 | */ 41 | sidebarProvider: ClineSidebarProvider 42 | } 43 | -------------------------------------------------------------------------------- /src/exports/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { ClineProvider } from "../core/webview/ClineProvider" 3 | import { ClineAPI } from "./cline" 4 | 5 | export function createClineAPI(outputChannel: vscode.OutputChannel, sidebarProvider: ClineProvider): ClineAPI { 6 | const api: ClineAPI = { 7 | setCustomInstructions: async (value: string) => { 8 | await sidebarProvider.updateCustomInstructions(value) 9 | outputChannel.appendLine("Custom instructions set") 10 | }, 11 | 12 | getCustomInstructions: async () => { 13 | return (await sidebarProvider.getGlobalState("customInstructions")) as string | undefined 14 | }, 15 | 16 | startNewTask: async (task?: string, images?: string[]) => { 17 | outputChannel.appendLine("Starting new task") 18 | await sidebarProvider.clearTask() 19 | await sidebarProvider.postStateToWebview() 20 | await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) 21 | await sidebarProvider.postMessageToWebview({ 22 | type: "invoke", 23 | invoke: "sendMessage", 24 | text: task, 25 | images: images, 26 | }) 27 | outputChannel.appendLine( 28 | `Task started with message: ${task ? `"${task}"` : "undefined"} and ${images?.length || 0} image(s)`, 29 | ) 30 | }, 31 | 32 | sendMessage: async (message?: string, images?: string[]) => { 33 | outputChannel.appendLine( 34 | `Sending message: ${message ? `"${message}"` : "undefined"} with ${images?.length || 0} image(s)`, 35 | ) 36 | await sidebarProvider.postMessageToWebview({ 37 | type: "invoke", 38 | invoke: "sendMessage", 39 | text: message, 40 | images: images, 41 | }) 42 | }, 43 | 44 | pressPrimaryButton: async () => { 45 | outputChannel.appendLine("Pressing primary button") 46 | await sidebarProvider.postMessageToWebview({ 47 | type: "invoke", 48 | invoke: "primaryButtonClick", 49 | }) 50 | }, 51 | 52 | pressSecondaryButton: async () => { 53 | outputChannel.appendLine("Pressing secondary button") 54 | await sidebarProvider.postMessageToWebview({ 55 | type: "invoke", 56 | invoke: "secondaryButtonClick", 57 | }) 58 | }, 59 | 60 | sidebarProvider: sidebarProvider, 61 | } 62 | 63 | return api 64 | } 65 | -------------------------------------------------------------------------------- /src/integrations/editor/DecorationController.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | const fadedOverlayDecorationType = vscode.window.createTextEditorDecorationType({ 4 | backgroundColor: "rgba(255, 255, 0, 0.1)", 5 | opacity: "0.4", 6 | isWholeLine: true, 7 | }) 8 | 9 | const activeLineDecorationType = vscode.window.createTextEditorDecorationType({ 10 | backgroundColor: "rgba(255, 255, 0, 0.3)", 11 | opacity: "1", 12 | isWholeLine: true, 13 | border: "1px solid rgba(255, 255, 0, 0.5)", 14 | }) 15 | 16 | type DecorationType = "fadedOverlay" | "activeLine" 17 | 18 | export class DecorationController { 19 | private decorationType: DecorationType 20 | private editor: vscode.TextEditor 21 | private ranges: vscode.Range[] = [] 22 | 23 | constructor(decorationType: DecorationType, editor: vscode.TextEditor) { 24 | this.decorationType = decorationType 25 | this.editor = editor 26 | } 27 | 28 | getDecoration() { 29 | switch (this.decorationType) { 30 | case "fadedOverlay": 31 | return fadedOverlayDecorationType 32 | case "activeLine": 33 | return activeLineDecorationType 34 | } 35 | } 36 | 37 | addLines(startIndex: number, numLines: number) { 38 | // Guard against invalid inputs 39 | if (startIndex < 0 || numLines <= 0) { 40 | return 41 | } 42 | 43 | const lastRange = this.ranges[this.ranges.length - 1] 44 | if (lastRange && lastRange.end.line === startIndex - 1) { 45 | this.ranges[this.ranges.length - 1] = lastRange.with(undefined, lastRange.end.translate(numLines)) 46 | } else { 47 | const endLine = startIndex + numLines - 1 48 | this.ranges.push(new vscode.Range(startIndex, 0, endLine, Number.MAX_SAFE_INTEGER)) 49 | } 50 | 51 | this.editor.setDecorations(this.getDecoration(), this.ranges) 52 | } 53 | 54 | clear() { 55 | this.ranges = [] 56 | this.editor.setDecorations(this.getDecoration(), this.ranges) 57 | } 58 | 59 | updateOverlayAfterLine(line: number, totalLines: number) { 60 | // Remove any existing ranges that start at or after the current line 61 | this.ranges = this.ranges.filter((range) => range.end.line < line) 62 | 63 | // Add a new range for all lines after the current line 64 | if (line < totalLines - 1) { 65 | this.ranges.push( 66 | new vscode.Range( 67 | new vscode.Position(line + 1, 0), 68 | new vscode.Position(totalLines - 1, Number.MAX_SAFE_INTEGER), 69 | ), 70 | ) 71 | } 72 | 73 | // Apply the updated decorations 74 | this.editor.setDecorations(this.getDecoration(), this.ranges) 75 | } 76 | 77 | setActiveLine(line: number) { 78 | this.ranges = [new vscode.Range(line, 0, line, Number.MAX_SAFE_INTEGER)] 79 | this.editor.setDecorations(this.getDecoration(), this.ranges) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/integrations/editor/detect-omission.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Detects potential AI-generated code omissions in the given file content. 3 | * @param originalFileContent The original content of the file. 4 | * @param newFileContent The new content of the file to check. 5 | * @param predictedLineCount The predicted number of lines in the new content. 6 | * @returns True if a potential omission is detected, false otherwise. 7 | */ 8 | export function detectCodeOmission( 9 | originalFileContent: string, 10 | newFileContent: string, 11 | predictedLineCount: number, 12 | ): boolean { 13 | // Skip all checks if predictedLineCount is less than 100 14 | if (!predictedLineCount || predictedLineCount < 100) { 15 | return false 16 | } 17 | 18 | const actualLineCount = newFileContent.split("\n").length 19 | const lengthRatio = actualLineCount / predictedLineCount 20 | 21 | const originalLines = originalFileContent.split("\n") 22 | const newLines = newFileContent.split("\n") 23 | const omissionKeywords = [ 24 | "remain", 25 | "remains", 26 | "unchanged", 27 | "rest", 28 | "previous", 29 | "existing", 30 | "content", 31 | "same", 32 | "...", 33 | ] 34 | 35 | const commentPatterns = [ 36 | /^\s*\/\//, // Single-line comment for most languages 37 | /^\s*#/, // Single-line comment for Python, Ruby, etc. 38 | /^\s*\/\*/, // Multi-line comment opening 39 | /^\s*{\s*\/\*/, // JSX comment opening 40 | /^\s*