├── .nvmrc ├── .prettierignore ├── webview ├── src │ ├── react-app-env.d.ts │ ├── setupTests.ts │ ├── utils │ │ ├── format.ts │ │ ├── helpers.ts │ │ ├── validators.ts │ │ ├── providers.ts │ │ ├── vscode.ts │ │ └── context-mentions.ts │ ├── App.tsx │ ├── reportWebVitals.ts │ ├── components │ │ ├── common │ │ │ ├── VSCodeButtonLink.tsx │ │ │ ├── Thumbnails.tsx │ │ │ ├── MarkdownBlock.tsx │ │ │ ├── Demo.tsx │ │ │ ├── CodeAccordian.tsx │ │ │ └── CodeBlock.tsx │ │ ├── welcome │ │ │ └── WelcomeView.tsx │ │ ├── chat │ │ │ ├── ChatRow.tsx │ │ │ └── Announcement.tsx │ │ └── settings │ │ │ ├── APIOptionsOllama.tsx │ │ │ ├── ModelDescriptionMarkdown.tsx │ │ │ └── APIOptionsModelInfo.tsx │ ├── index.tsx │ ├── AppContent.tsx │ ├── index.css │ └── context │ │ ├── ConfigContext.tsx │ │ └── ExtensionStateContext.tsx ├── public │ ├── robots.txt │ ├── manifest.json │ └── index.html ├── .gitignore ├── tsconfig.json └── package.json ├── assets ├── icons │ ├── icon.png │ ├── robot_panel_dark.png │ └── robot_panel_light.png └── docs │ └── demo.gif ├── .gitignore ├── src ├── global.d.ts ├── shared │ ├── const.ts │ ├── helpers.ts │ ├── metrics.ts │ ├── types.ts │ ├── mentions.ts │ ├── interfaces.ts │ └── combiners.ts ├── services │ ├── tree-sitter │ │ ├── queries │ │ │ ├── python.ts │ │ │ ├── php.ts │ │ │ ├── java.ts │ │ │ ├── rust.ts │ │ │ ├── c.ts │ │ │ ├── c-sharp.ts │ │ │ ├── index.ts │ │ │ ├── go.ts │ │ │ ├── cpp.ts │ │ │ ├── swift.ts │ │ │ ├── ruby.ts │ │ │ ├── javascript.ts │ │ │ └── typescript.ts │ │ └── parser.ts │ ├── glob │ │ └── list-files.ts │ └── editor │ │ └── lsp.ts ├── api │ ├── types.ts │ ├── index.ts │ └── providers │ │ ├── ollama.ts │ │ ├── openai.ts │ │ ├── gemini.ts │ │ ├── openai-native.ts │ │ ├── vertex.ts │ │ └── bedrock.ts ├── utils │ ├── const.ts │ ├── pricing.ts │ ├── fs.ts │ ├── helpers.ts │ └── path.ts ├── integrations │ ├── misc │ │ ├── process-images.ts │ │ ├── open-file.ts │ │ └── extract-text.ts │ ├── editor │ │ ├── detect-omission.ts │ │ └── DecorationController.ts │ ├── terminal │ │ └── TerminalRegistry.ts │ ├── diagnostics │ │ └── index.ts │ ├── theme │ │ └── getTheme.ts │ └── workspace │ │ └── WorkspaceTracker.ts ├── exports │ ├── codey.d.ts │ ├── README.md │ └── index.ts ├── core │ └── config.ts └── types │ └── index.ts ├── .gitattributes ├── .prettierrc.json ├── .vscode-test.mjs ├── tests ├── tsconfig.json └── extension.test.ts ├── .vscode ├── extensions.json ├── settings.json ├── launch.json └── tasks.json ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml ├── .eslintrc.json ├── tsconfig.json ├── .vscodeignore ├── Makefile └── esbuild.js /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules 3 | webview/build/ 4 | CHANGELOG.md -------------------------------------------------------------------------------- /webview/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /assets/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccrvlh/codey/HEAD/assets/icons/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | 7 | .DS_Store 8 | .local -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'puppeteer-chromium-resolver'; 2 | declare module 'pdf-parse/lib/pdf-parse'; -------------------------------------------------------------------------------- /webview/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /assets/icons/robot_panel_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccrvlh/codey/HEAD/assets/icons/robot_panel_dark.png -------------------------------------------------------------------------------- /assets/icons/robot_panel_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccrvlh/codey/HEAD/assets/icons/robot_panel_light.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | demo.gif filter=lfs diff=lfs merge=lfs -text 2 | assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "printWidth": 120, 5 | "semi": false, 6 | "jsxBracketSameLine": true 7 | } -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'out/tests/**/*.test.js', 5 | }); 6 | -------------------------------------------------------------------------------- /src/shared/const.ts: -------------------------------------------------------------------------------- 1 | export const COMMAND_OUTPUT_STRING = "Output:" 2 | export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images per message -------------------------------------------------------------------------------- /assets/docs/demo.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d426d600fa80e9ac237cbad6b3f7f47a4aa2005d5218184e6b9565a8ee46d1ba 3 | size 19108207 4 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "rootDir": ".." 7 | } 8 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"] 5 | } 6 | -------------------------------------------------------------------------------- /webview/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom" 6 | -------------------------------------------------------------------------------- /webview/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | export function formatLargeNumber(num: number): string { 2 | if (num >= 1e9) { 3 | return (num / 1e9).toFixed(1) + "b" 4 | } 5 | if (num >= 1e6) { 6 | return (num / 1e6).toFixed(1) + "m" 7 | } 8 | if (num >= 1e3) { 9 | return (num / 1e3).toFixed(1) + "k" 10 | } 11 | return num.toString() 12 | } 13 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/python.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tree-sitter query for matching various Python constructs. 3 | * 4 | */ 5 | export default ` 6 | (class_definition 7 | name: (identifier) @name.definition.class) @definition.class 8 | 9 | (function_definition 10 | name: (identifier) @name.definition.function) @definition.function 11 | ` -------------------------------------------------------------------------------- /webview/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/php.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - class declarations 3 | - function definitions 4 | - method declarations 5 | */ 6 | export default ` 7 | (class_declaration 8 | name: (name) @name.definition.class) @definition.class 9 | 10 | (function_definition 11 | name: (name) @name.definition.function) @definition.function 12 | 13 | (method_declaration 14 | name: (name) @name.definition.function) @definition.function 15 | ` 16 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/java.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - class declarations 3 | - method declarations 4 | - interface declarations 5 | */ 6 | export default ` 7 | (class_declaration 8 | name: (identifier) @name.definition.class) @definition.class 9 | 10 | (method_declaration 11 | name: (identifier) @name.definition.method) @definition.method 12 | 13 | (interface_declaration 14 | name: (identifier) @name.definition.interface) @definition.interface 15 | ` 16 | -------------------------------------------------------------------------------- /webview/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { AppContent } from "./AppContent" 2 | import { ConfigContextProvider } from "./context/ConfigContext" 3 | import { ExtensionStateContextProvider } from "./context/ExtensionStateContext" 4 | 5 | export default function App() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/api/types.ts: -------------------------------------------------------------------------------- 1 | export type APIStream = AsyncGenerator 2 | export type APIStreamChunk = APIStreamTextChunk | APIStreamUsageChunk 3 | 4 | export interface APIStreamTextChunk { 5 | type: "text" 6 | text: string 7 | } 8 | 9 | export interface APIStreamUsageChunk { 10 | type: "usage" 11 | inputTokens: number 12 | outputTokens: number 13 | cacheWriteTokens?: number 14 | cacheReadTokens?: number 15 | totalCost?: number // openrouter 16 | } 17 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/rust.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - struct definitions 3 | - method definitions 4 | - function definitions 5 | */ 6 | export default ` 7 | (struct_item 8 | name: (type_identifier) @name.definition.class) @definition.class 9 | 10 | (declaration_list 11 | (function_item 12 | name: (identifier) @name.definition.method)) @definition.method 13 | 14 | (function_item 15 | name: (identifier) @name.definition.function) @definition.function 16 | ` 17 | -------------------------------------------------------------------------------- /webview/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals" 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry) 7 | getFID(onPerfEntry) 8 | getFCP(onPerfEntry) 9 | getLCP(onPerfEntry) 10 | getTTFB(onPerfEntry) 11 | }) 12 | } 13 | } 14 | 15 | export default reportWebVitals 16 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/c.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - struct declarations 3 | - union declarations 4 | - function declarations 5 | - typedef declarations 6 | */ 7 | export default ` 8 | (struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class 9 | 10 | (declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class 11 | 12 | (function_declarator declarator: (identifier) @name.definition.function) @definition.function 13 | 14 | (type_definition declarator: (type_identifier) @name.definition.type) @definition.type 15 | ` 16 | -------------------------------------------------------------------------------- /webview/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 4 | url: https://github.com/ccrvlh/codey/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop 5 | about: Share and vote on feature requests for Codey 6 | - name: Codey Discord 7 | url: https://discord.gg/codey 8 | about: Join our Discord community for discussions and support 9 | - name: Other Questions? 10 | url: https://x.com/sdrzn 11 | about: Contact the developer on X @sdrzn for other inquiries 12 | -------------------------------------------------------------------------------- /webview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src", "../src/shared"] 20 | } 21 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/c-sharp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - class declarations 3 | - interface declarations 4 | - method declarations 5 | - namespace declarations 6 | */ 7 | export default ` 8 | (class_declaration 9 | name: (identifier) @name.definition.class 10 | ) @definition.class 11 | 12 | (interface_declaration 13 | name: (identifier) @name.definition.interface 14 | ) @definition.interface 15 | 16 | (method_declaration 17 | name: (identifier) @name.definition.method 18 | ) @definition.method 19 | 20 | (namespace_declaration 21 | name: (identifier) @name.definition.module 22 | ) @definition.module 23 | ` 24 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/index.ts: -------------------------------------------------------------------------------- 1 | export { default as phpQuery } from "./php" 2 | export { default as typescriptQuery } from "./typescript" 3 | export { default as pythonQuery } from "./python" 4 | export { default as javascriptQuery } from "./javascript" 5 | export { default as javaQuery } from "./java" 6 | export { default as rustQuery } from "./rust" 7 | export { default as rubyQuery } from "./ruby" 8 | export { default as cppQuery } from "./cpp" 9 | export { default as cQuery } from "./c" 10 | export { default as csharpQuery } from "./c-sharp" 11 | export { default as goQuery } from "./go" 12 | export { default as swiftQuery } from "./swift" 13 | -------------------------------------------------------------------------------- /webview/src/components/common/VSCodeButtonLink.tsx: -------------------------------------------------------------------------------- 1 | import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" 2 | import React from "react" 3 | 4 | interface VSCodeButtonLinkProps { 5 | href: string 6 | children: React.ReactNode 7 | [key: string]: any 8 | } 9 | 10 | const VSCodeButtonLink: React.FC = ({ href, children, ...props }) => { 11 | return ( 12 | 18 | {children} 19 | 20 | ) 21 | } 22 | 23 | export default VSCodeButtonLink 24 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /webview/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import "../../node_modules/@vscode/codicons/dist/codicon.css" 4 | import App from "./App" 5 | import "./index.css" 6 | import reportWebVitals from "./reportWebVitals" 7 | 8 | const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement) 9 | root.render( 10 | 11 | 12 | 13 | ) 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals() 19 | -------------------------------------------------------------------------------- /src/utils/const.ts: -------------------------------------------------------------------------------- 1 | 2 | export const GlobalFileNames = { 3 | apiConversationHistory: "api_conversation_history.json", 4 | uiMessages: "ui_messages.json", 5 | openRouterModels: "openrouter_models.json", 6 | } 7 | 8 | 9 | /** 10 | * The unique identifier for the Sidebar Provider. 11 | * This ID is used to reference the Sidebar component within the application. 12 | */ 13 | export const SIDEBAR_ID = "codey.SidebarProvider" 14 | 15 | /** 16 | * The unique identifier for the Tab Panel Provider. 17 | * This value is critical for the application's functionality and should not be changed, since it would break existing installations. 18 | */ 19 | export const TAB_PANEL_ID = "codey.TabPanelProvider" 20 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/go.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - function declarations (with associated comments) 3 | - method declarations (with associated comments) 4 | - type specifications 5 | */ 6 | export default ` 7 | ( 8 | (comment)* @doc 9 | . 10 | (function_declaration 11 | name: (identifier) @name.definition.function) @definition.function 12 | (#strip! @doc "^//\\s*") 13 | (#set-adjacent! @doc @definition.function) 14 | ) 15 | 16 | ( 17 | (comment)* @doc 18 | . 19 | (method_declaration 20 | name: (field_identifier) @name.definition.method) @definition.method 21 | (#strip! @doc "^//\\s*") 22 | (#set-adjacent! @doc @definition.method) 23 | ) 24 | 25 | (type_spec 26 | name: (type_identifier) @name.definition.type) @definition.type 27 | ` 28 | -------------------------------------------------------------------------------- /tests/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | 16 | suiteTeardown(async () => { 17 | // Ensure we clean up any resources 18 | await vscode.commands.executeCommand('workbench.action.closeAllEditors'); 19 | // Give some time for cleanup 20 | await new Promise(resolve => setTimeout(resolve, 100)); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": [ 13 | "warn", 14 | { 15 | "selector": "import", 16 | "format": [ "camelCase", "PascalCase" ] 17 | } 18 | ], 19 | "@typescript-eslint/semi": "off", 20 | "curly": "warn", 21 | "eqeqeq": "warn", 22 | "no-throw-literal": "warn", 23 | "semi": "off", 24 | "react-hooks/exhaustive-deps": "off" 25 | }, 26 | "ignorePatterns": [ 27 | "out", 28 | "dist", 29 | "**/*.d.ts" 30 | ] 31 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "experimentalDecorators": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "isolatedModules": true, 7 | "lib": [ 8 | "es2022", 9 | "esnext.disposable", 10 | "DOM" 11 | ], 12 | "module": "esnext", 13 | "moduleResolution": "Bundler", 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitOverride": true, 16 | "noImplicitReturns": true, 17 | "noUnusedLocals": false, 18 | "resolveJsonModule": true, 19 | "rootDir": ".", 20 | "skipLibCheck": true, 21 | "sourceMap": true, 22 | "strict": true, 23 | "target": "es2022", 24 | "useDefineForClassFields": true, 25 | "useUnknownInCatchVariables": false 26 | }, 27 | "include": [ 28 | "src/**/*", 29 | "scripts/**/*", 30 | "tests/**/*" 31 | ], 32 | "exclude": [ 33 | "node_modules", 34 | ".vscode-test", 35 | "webview-ui" 36 | ] 37 | } -------------------------------------------------------------------------------- /src/shared/helpers.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Returns the index of the last element in the array where predicate is true, and -1 4 | * otherwise. 5 | * @param array The source array to search in 6 | * @param predicate find calls predicate once for each element of the array, in descending 7 | * order, until it finds one where predicate returns true. If such an element is found, 8 | * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1. 9 | */ 10 | export function findLastIndex(array: Array, predicate: (value: T, index: number, obj: T[]) => boolean): number { 11 | let l = array.length 12 | while (l--) { 13 | if (predicate(array[l], l, array)) { 14 | return l 15 | } 16 | } 17 | return -1 18 | } 19 | 20 | export function findLast(array: Array, predicate: (value: T, index: number, obj: T[]) => boolean): T | undefined { 21 | const index = findLastIndex(array, predicate) 22 | return index === -1 ? undefined : array[index] 23 | } 24 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/cpp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - struct declarations 3 | - union declarations 4 | - function declarations 5 | - method declarations (with namespace scope) 6 | - typedef declarations 7 | - class declarations 8 | */ 9 | export default ` 10 | (struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class 11 | 12 | (declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class 13 | 14 | (function_declarator declarator: (identifier) @name.definition.function) @definition.function 15 | 16 | (function_declarator declarator: (field_identifier) @name.definition.function) @definition.function 17 | 18 | (function_declarator declarator: (qualified_identifier scope: (namespace_identifier) @scope name: (identifier) @name.definition.method)) @definition.method 19 | 20 | (type_definition declarator: (type_identifier) @name.definition.type) @definition.type 21 | 22 | (class_specifier name: (type_identifier) @name.definition.class) @definition.class 23 | ` 24 | -------------------------------------------------------------------------------- /src/utils/pricing.ts: -------------------------------------------------------------------------------- 1 | import { ModelInfo } from "../shared/interfaces" 2 | 3 | export function calculateApiCost( 4 | modelInfo: ModelInfo, 5 | inputTokens: number, 6 | outputTokens: number, 7 | cacheCreationInputTokens?: number, 8 | cacheReadInputTokens?: number 9 | ): number { 10 | const modelCacheWritesPrice = modelInfo.cacheWritesPrice 11 | let cacheWritesCost = 0 12 | if (cacheCreationInputTokens && modelCacheWritesPrice) { 13 | cacheWritesCost = (modelCacheWritesPrice / 1_000_000) * cacheCreationInputTokens 14 | } 15 | const modelCacheReadsPrice = modelInfo.cacheReadsPrice 16 | let cacheReadsCost = 0 17 | if (cacheReadInputTokens && modelCacheReadsPrice) { 18 | cacheReadsCost = (modelCacheReadsPrice / 1_000_000) * cacheReadInputTokens 19 | } 20 | const baseInputCost = ((modelInfo.inputPrice || 0) / 1_000_000) * inputTokens 21 | const outputCost = ((modelInfo.outputPrice || 0) / 1_000_000) * outputTokens 22 | const totalCost = cacheWritesCost + cacheReadsCost + baseInputCost + outputCost 23 | return totalCost 24 | } 25 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Default 2 | .vscode/** 3 | .vscode-test/** 4 | out/** 5 | node_modules/** 6 | src/** 7 | .gitignore 8 | .yarnrc 9 | esbuild.js 10 | vsc-extension-quickstart.md 11 | **/tsconfig.json 12 | **/.eslintrc.json 13 | **/*.map 14 | **/*.ts 15 | **/.vscode-test.* 16 | 17 | # Custom 18 | demo.gif 19 | .nvmrc 20 | .gitattributes 21 | .prettierignore 22 | 23 | # 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) 24 | webview/src/** 25 | webview/public/** 26 | webview/scripts/** 27 | webview/index.html 28 | webview/README.md 29 | webview/package.json 30 | webview/package-lock.json 31 | webview/node_modules/** 32 | **/.gitignore 33 | 34 | # Fix issue where codicons don't get packaged (https://github.com/microsoft/vscode-extension-samples/issues/692) 35 | !node_modules/@vscode/codicons/dist/codicon.css 36 | !node_modules/@vscode/codicons/dist/codicon.ttf 37 | 38 | # Include default themes JSON files used in getTheme 39 | !src/integrations/theme/default-themes/** 40 | 41 | # Include icons 42 | !assets/icons/** -------------------------------------------------------------------------------- /.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 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Run Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "runtimeExecutable": "${execPath}", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/out/tests" 28 | ], 29 | "outFiles": [ 30 | "${workspaceFolder}/out/tests/**/*.js" 31 | ], 32 | "preLaunchTask": "npm: compile", 33 | "internalConsoleOptions": "openOnSessionStart" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/swift.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - class declarations 3 | - method declarations (including initializers and deinitializers) 4 | - property declarations 5 | - function declarations 6 | */ 7 | export default ` 8 | (class_declaration 9 | name: (type_identifier) @name) @definition.class 10 | 11 | (protocol_declaration 12 | name: (type_identifier) @name) @definition.interface 13 | 14 | (class_declaration 15 | (class_body 16 | [ 17 | (function_declaration 18 | name: (simple_identifier) @name 19 | ) 20 | (subscript_declaration 21 | (parameter (simple_identifier) @name) 22 | ) 23 | (init_declaration "init" @name) 24 | (deinit_declaration "deinit" @name) 25 | ] 26 | ) 27 | ) @definition.method 28 | 29 | (class_declaration 30 | (class_body 31 | [ 32 | (property_declaration 33 | (pattern (simple_identifier) @name) 34 | ) 35 | ] 36 | ) 37 | ) @definition.property 38 | 39 | (property_declaration 40 | (pattern (simple_identifier) @name) 41 | ) @definition.property 42 | 43 | (function_declaration 44 | name: (simple_identifier) @name) @definition.function 45 | ` 46 | -------------------------------------------------------------------------------- /src/integrations/misc/process-images.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises" 2 | import * as path from "path" 3 | import * as vscode from "vscode" 4 | 5 | export async function selectImages(): Promise { 6 | const options: vscode.OpenDialogOptions = { 7 | canSelectMany: true, 8 | openLabel: "Select", 9 | filters: { 10 | Images: ["png", "jpg", "jpeg", "webp"], // supported by anthropic and openrouter 11 | }, 12 | } 13 | 14 | const fileUris = await vscode.window.showOpenDialog(options) 15 | 16 | if (!fileUris || fileUris.length === 0) { 17 | return [] 18 | } 19 | 20 | return await Promise.all( 21 | fileUris.map(async (uri) => { 22 | const imagePath = uri.fsPath 23 | const buffer = await fs.readFile(imagePath) 24 | const base64 = buffer.toString("base64") 25 | const mimeType = getMimeType(imagePath) 26 | const dataUrl = `data:${mimeType};base64,${base64}` 27 | return dataUrl 28 | }) 29 | ) 30 | } 31 | 32 | function getMimeType(filePath: string): string { 33 | const ext = path.extname(filePath).toLowerCase() 34 | switch (ext) { 35 | case ".png": 36 | return "image/png" 37 | case ".jpeg": 38 | case ".jpg": 39 | return "image/jpeg" 40 | case ".webp": 41 | return "image/webp" 42 | default: 43 | throw new Error(`Unsupported file type: ${ext}`) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/exports/codey.d.ts: -------------------------------------------------------------------------------- 1 | export interface CodeyAPI { 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: bump-version package deploy all 2 | 3 | # Default target 4 | all: bump-version package 5 | 6 | # Deploy target that does both operations 7 | deploy: bump-version package 8 | 9 | # Bump version in both package.json files 10 | bump-version: 11 | @echo "Current version in package.json:" 12 | @grep "\"version\":" package.json 13 | @echo "Current version in webview/package.json:" 14 | @grep "\"version\":" webview/package.json 15 | @read -p "What to bump? [patch]/minor/major: " type; \ 16 | type=$${type:-patch}; \ 17 | current_version=$$(grep "\"version\":" package.json | awk -F'"' '{print $$4}'); \ 18 | major=$$(echo $$current_version | cut -d. -f1); \ 19 | minor=$$(echo $$current_version | cut -d. -f2); \ 20 | patch=$$(echo $$current_version | cut -d. -f3); \ 21 | case $$type in \ 22 | major) new_version=$$((major + 1)).0.0 ;; \ 23 | minor) new_version=$$major.$$((minor + 1)).0 ;; \ 24 | patch) new_version=$$major.$$minor.$$((patch + 1)) ;; \ 25 | *) echo "Invalid type. Using patch."; \ 26 | new_version=$$major.$$minor.$$((patch + 1)) ;; \ 27 | esac; \ 28 | sed -i '' 's/"version": "[^"]*"/"version": "'$$new_version'"/' package.json; \ 29 | sed -i '' 's/"version": "[^"]*"/"version": "'$$new_version'"/' webview/package.json; \ 30 | echo "Version bumped to $$new_version" 31 | 32 | # Package the extension 33 | package: 34 | vsce package 35 | -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/ruby.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - method definitions (including singleton methods and aliases, with associated comments) 3 | - class definitions (including singleton classes, with associated comments) 4 | - module definitions 5 | */ 6 | export default ` 7 | ( 8 | (comment)* @doc 9 | . 10 | [ 11 | (method 12 | name: (_) @name.definition.method) @definition.method 13 | (singleton_method 14 | name: (_) @name.definition.method) @definition.method 15 | ] 16 | (#strip! @doc "^#\\s*") 17 | (#select-adjacent! @doc @definition.method) 18 | ) 19 | 20 | (alias 21 | name: (_) @name.definition.method) @definition.method 22 | 23 | ( 24 | (comment)* @doc 25 | . 26 | [ 27 | (class 28 | name: [ 29 | (constant) @name.definition.class 30 | (scope_resolution 31 | name: (_) @name.definition.class) 32 | ]) @definition.class 33 | (singleton_class 34 | value: [ 35 | (constant) @name.definition.class 36 | (scope_resolution 37 | name: (_) @name.definition.class) 38 | ]) @definition.class 39 | ] 40 | (#strip! @doc "^#\\s*") 41 | (#select-adjacent! @doc @definition.class) 42 | ) 43 | 44 | ( 45 | (module 46 | name: [ 47 | (constant) @name.definition.module 48 | (scope_resolution 49 | name: (_) @name.definition.module) 50 | ]) @definition.module 51 | ) 52 | ` 53 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | import { APIConfiguration, ModelInfo } from "../shared/interfaces" 3 | import { AnthropicHandler } from "./providers/anthropic" 4 | import { AwsBedrockHandler } from "./providers/bedrock" 5 | import { GeminiHandler } from "./providers/gemini" 6 | import { OllamaHandler } from "./providers/ollama" 7 | import { OpenAiHandler } from "./providers/openai" 8 | import { OpenAiNativeHandler } from "./providers/openai-native" 9 | import { OpenRouterHandler } from "./providers/openrouter" 10 | import { VertexHandler } from "./providers/vertex" 11 | import { APIStream } from "./types" 12 | 13 | export interface APIHandler { 14 | createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): APIStream 15 | getModel(): { id: string; info: ModelInfo } 16 | } 17 | 18 | export function buildApiHandler(configuration: APIConfiguration): APIHandler { 19 | const { apiProvider, ...options } = configuration 20 | switch (apiProvider) { 21 | case "anthropic": 22 | return new AnthropicHandler(options) 23 | case "openrouter": 24 | return new OpenRouterHandler(options) 25 | case "bedrock": 26 | return new AwsBedrockHandler(options) 27 | case "vertex": 28 | return new VertexHandler(options) 29 | case "openai": 30 | return new OpenAiHandler(options) 31 | case "ollama": 32 | return new OllamaHandler(options) 33 | case "gemini": 34 | return new GeminiHandler(options) 35 | case "openai-native": 36 | return new OpenAiNativeHandler(options) 37 | default: 38 | return new AnthropicHandler(options) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webview/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webview", 3 | "version": "0.1.19", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.101", 11 | "@types/react": "^18.3.3", 12 | "@types/react-dom": "^18.3.0", 13 | "@vscode/webview-ui-toolkit": "^1.4.0", 14 | "debounce": "^2.1.1", 15 | "fast-deep-equal": "^3.1.3", 16 | "fuse.js": "^7.0.0", 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1", 19 | "react-remark": "^2.1.0", 20 | "react-scripts": "5.0.1", 21 | "react-textarea-autosize": "^8.5.3", 22 | "react-use": "^17.5.1", 23 | "react-virtuoso": "^4.7.13", 24 | "rehype-highlight": "^7.0.0", 25 | "rewire": "^7.0.0", 26 | "styled-components": "^6.1.13", 27 | "typescript": "^4.9.5", 28 | "web-vitals": "^2.1.4" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "node ./scripts/build-react-no-split.js", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@types/vscode-webview": "^1.57.5" 56 | } 57 | } -------------------------------------------------------------------------------- /src/services/tree-sitter/queries/javascript.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - class definitions 3 | - method definitions 4 | - named function declarations 5 | - arrow functions and function expressions assigned to variables 6 | */ 7 | export default ` 8 | ( 9 | (comment)* @doc 10 | . 11 | (method_definition 12 | name: (property_identifier) @name) @definition.method 13 | (#not-eq? @name "constructor") 14 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 15 | (#select-adjacent! @doc @definition.method) 16 | ) 17 | 18 | ( 19 | (comment)* @doc 20 | . 21 | [ 22 | (class 23 | name: (_) @name) 24 | (class_declaration 25 | name: (_) @name) 26 | ] @definition.class 27 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 28 | (#select-adjacent! @doc @definition.class) 29 | ) 30 | 31 | ( 32 | (comment)* @doc 33 | . 34 | [ 35 | (function_declaration 36 | name: (identifier) @name) 37 | (generator_function_declaration 38 | name: (identifier) @name) 39 | ] @definition.function 40 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 41 | (#select-adjacent! @doc @definition.function) 42 | ) 43 | 44 | ( 45 | (comment)* @doc 46 | . 47 | (lexical_declaration 48 | (variable_declarator 49 | name: (identifier) @name 50 | value: [(arrow_function) (function_expression)]) @definition.function) 51 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 52 | (#select-adjacent! @doc @definition.function) 53 | ) 54 | 55 | ( 56 | (comment)* @doc 57 | . 58 | (variable_declaration 59 | (variable_declarator 60 | name: (identifier) @name 61 | value: [(arrow_function) (function_expression)]) @definition.function) 62 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 63 | (#select-adjacent! @doc @definition.function) 64 | ) 65 | ` 66 | -------------------------------------------------------------------------------- /src/api/providers/ollama.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from "@anthropic-ai/sdk" 2 | import OpenAI from "openai" 3 | import { APIHandler } from "../" 4 | import { APIHandlerOptions, ModelInfo } from "../../shared/interfaces" 5 | import { convertToOpenAiMessages } from "../transform/openai-format" 6 | import { APIStream } from "../types" 7 | 8 | const openAiModelInfoSaneDefaults: ModelInfo = { 9 | maxTokens: -1, 10 | contextWindow: 128_000, 11 | supportsImages: true, 12 | supportsPromptCache: false, 13 | inputPrice: 0, 14 | outputPrice: 0, 15 | } 16 | 17 | export class OllamaHandler implements APIHandler { 18 | private options: APIHandlerOptions 19 | private client: OpenAI 20 | 21 | constructor(options: APIHandlerOptions) { 22 | this.options = options 23 | this.client = new OpenAI({ 24 | baseURL: (this.options.ollamaBaseUrl || "http://localhost:11434") + "/v1", 25 | apiKey: "ollama", 26 | }) 27 | } 28 | 29 | async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): APIStream { 30 | const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ 31 | { role: "system", content: systemPrompt }, 32 | ...convertToOpenAiMessages(messages), 33 | ] 34 | 35 | const stream = await this.client.chat.completions.create({ 36 | model: this.getModel().id, 37 | messages: openAiMessages, 38 | temperature: 0, 39 | stream: true, 40 | }) 41 | for await (const chunk of stream) { 42 | const delta = chunk.choices[0]?.delta 43 | if (delta?.content) { 44 | yield { 45 | type: "text", 46 | text: delta.content, 47 | } 48 | } 49 | } 50 | } 51 | 52 | getModel(): { id: string; info: ModelInfo } { 53 | return { 54 | id: this.options.ollamaModelId || "", 55 | info: openAiModelInfoSaneDefaults, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /webview/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 |
28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /webview/src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | const extensionToLanguage: { [key: string]: string } = { 2 | // Web technologies 3 | html: "html", 4 | htm: "html", 5 | css: "css", 6 | js: "javascript", 7 | jsx: "jsx", 8 | ts: "typescript", 9 | tsx: "tsx", 10 | 11 | // Backend languages 12 | py: "python", 13 | rb: "ruby", 14 | php: "php", 15 | java: "java", 16 | cs: "csharp", 17 | go: "go", 18 | rs: "rust", 19 | scala: "scala", 20 | kt: "kotlin", 21 | swift: "swift", 22 | 23 | // Markup and data 24 | json: "json", 25 | xml: "xml", 26 | yaml: "yaml", 27 | yml: "yaml", 28 | md: "markdown", 29 | csv: "csv", 30 | 31 | // Shell and scripting 32 | sh: "bash", 33 | bash: "bash", 34 | zsh: "bash", 35 | ps1: "powershell", 36 | 37 | // Configuration 38 | toml: "toml", 39 | ini: "ini", 40 | cfg: "ini", 41 | conf: "ini", 42 | 43 | // Other 44 | sql: "sql", 45 | graphql: "graphql", 46 | gql: "graphql", 47 | tex: "latex", 48 | svg: "svg", 49 | txt: "text", 50 | 51 | // C-family languages 52 | c: "c", 53 | cpp: "cpp", 54 | h: "c", 55 | hpp: "cpp", 56 | 57 | // Functional languages 58 | hs: "haskell", 59 | lhs: "haskell", 60 | elm: "elm", 61 | clj: "clojure", 62 | cljs: "clojure", 63 | erl: "erlang", 64 | ex: "elixir", 65 | exs: "elixir", 66 | 67 | // Mobile development 68 | dart: "dart", 69 | m: "objectivec", 70 | mm: "objectivec", 71 | 72 | // Game development 73 | lua: "lua", 74 | gd: "gdscript", // Godot 75 | unity: "csharp", // Unity (using C#) 76 | 77 | // Data science and ML 78 | r: "r", 79 | jl: "julia", 80 | ipynb: "jupyter", // Jupyter notebooks 81 | } 82 | 83 | // Example usage: 84 | // console.log(getLanguageFromPath('/path/to/file.js')); // Output: javascript 85 | 86 | export function getLanguageFromPath(path: string): string | undefined { 87 | const extension = path.split(".").pop()?.toLowerCase() || "" 88 | return extensionToLanguage[extension] 89 | } 90 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: dropdown 6 | id: provider 7 | attributes: 8 | label: Which API Provider are you using? 9 | multiple: false 10 | options: 11 | - OpenRouter 12 | - Anthropic 13 | - Google Gemini 14 | - GCP Vertex AI 15 | - AWS Bedrock 16 | - OpenAI 17 | - OpenAI Compatible 18 | - Ollama 19 | validations: 20 | required: true 21 | - type: input 22 | id: model 23 | attributes: 24 | label: Which Model are you using? 25 | description: Please specify the model you're using (e.g. Claude 3.5 Sonnet) 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: what-happened 30 | attributes: 31 | label: What happened? 32 | description: Also tell us, what did you expect to happen? 33 | placeholder: Tell us what you see! 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: steps 38 | attributes: 39 | label: Steps to reproduce 40 | description: How do you trigger this bug? Please walk us through it step by step. 41 | value: | 42 | 1. 43 | 2. 44 | 3. 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: logs 49 | attributes: 50 | label: Relevant API REQUEST output 51 | description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks. 52 | render: shell 53 | - type: textarea 54 | id: additional-context 55 | attributes: 56 | label: Additional context 57 | description: Add any other context about the problem here, such as screenshots or related issues. 58 | -------------------------------------------------------------------------------- /webview/src/components/welcome/WelcomeView.tsx: -------------------------------------------------------------------------------- 1 | import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" 2 | import { useEffect, useState } from "react" 3 | import { useExtensionState } from "../../context/ExtensionStateContext" 4 | import { validateApiConfiguration } from "../../utils/validators" 5 | import { vscode } from "../../utils/vscode" 6 | import APIOptions from "../settings/APIOptions" 7 | 8 | const WelcomeView = () => { 9 | const { apiConfiguration } = useExtensionState() 10 | const [apiErrorMessage, setApiErrorMessage] = useState(undefined) 11 | const disableLetsGoButton = apiErrorMessage != null 12 | 13 | const handleSubmit = () => { 14 | vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) 15 | } 16 | 17 | useEffect(() => { 18 | setApiErrorMessage(validateApiConfiguration(apiConfiguration)) 19 | }, [apiConfiguration]) 20 | 21 | return ( 22 |
23 |

Hi, I'm Codey

24 |

25 | I can do all kinds of tasks thanks to the latest breakthroughs in{" "} 26 | 29 | Claude 3.5 Sonnet's agentic coding capabilities 30 | {" "} 31 | and access to tools that let me create & edit files, explore complex projects, and execute terminal commands 32 | (with your permission, of course). 33 |

34 | 35 | To get started, this extension needs an API provider for Claude 3.5 Sonnet. 36 | 37 |
38 | 39 | 40 | Let's go! 41 | 42 |
43 |
44 | ) 45 | } 46 | 47 | export default WelcomeView 48 | -------------------------------------------------------------------------------- /src/exports/README.md: -------------------------------------------------------------------------------- 1 | # Codey API 2 | 3 | The Codey 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/codey.d.ts` to your extension's source directory. 6 | 2. Include `codey.d.ts` in your extension's compilation. 7 | 3. Get access to the API with the following code: 8 | 9 | ```ts 10 | const codeyExtension = vscode.extensions.getExtension("ccrvlh.codey") 11 | 12 | if (!codeyExtension?.isActive) { 13 | throw new Error("Codey extension is not activated") 14 | } 15 | 16 | const codey = codeyExtension.exports 17 | 18 | if (codey) { 19 | // Now you can use the API 20 | 21 | // Set custom instructions 22 | await codey.setCustomInstructions("Talk like a pirate") 23 | 24 | // Get custom instructions 25 | const instructions = await codey.getCustomInstructions() 26 | console.log("Current custom instructions:", instructions) 27 | 28 | // Start a new task with an initial message 29 | await codey.startNewTask("Hello, Codey! Let's make a new project...") 30 | 31 | // Start a new task with an initial message and images 32 | await codey.startNewTask("Use this design language", ["data:image/webp;base64,..."]) 33 | 34 | // Send a message to the current task 35 | await codey.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 codey.pressPrimaryButton() 39 | 40 | // Simulate pressing the secondary button in the chat interface (e.g. 'Reject') 41 | await codey.pressSecondaryButton() 42 | } else { 43 | console.error("Codey API is not available") 44 | } 45 | ``` 46 | 47 | **Note:** To ensure that the `ccrvlh.codey` extension is activated before your extension, add it to the `extensionDependencies` in your `package.json`: 48 | 49 | ```json 50 | "extensionDependencies": [ 51 | "ccrvlh.codey" 52 | ] 53 | ``` 54 | 55 | For detailed information on the available methods and their usage, refer to the `codey.d.ts` file. 56 | -------------------------------------------------------------------------------- /src/integrations/misc/open-file.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os" 2 | import * as path from "path" 3 | import * as vscode from "vscode" 4 | import { arePathsEqual } from "../../utils/path" 5 | 6 | export async function openImage(dataUri: string) { 7 | const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/) 8 | if (!matches) { 9 | vscode.window.showErrorMessage("Invalid data URI format") 10 | return 11 | } 12 | const [, format, base64Data] = matches 13 | const imageBuffer = Buffer.from(base64Data, "base64") 14 | const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`) 15 | try { 16 | await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer) 17 | await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath)) 18 | } catch (error) { 19 | vscode.window.showErrorMessage(`Error opening image: ${error}`) 20 | } 21 | } 22 | 23 | export async function openFile(absolutePath: string) { 24 | try { 25 | const uri = vscode.Uri.file(absolutePath) 26 | 27 | // Check if the document is already open in a tab group that's not in the active editor's column. If it is, then close it (if not dirty) so that we don't duplicate tabs 28 | try { 29 | for (const group of vscode.window.tabGroups.all) { 30 | const existingTab = group.tabs.find( 31 | (tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath) 32 | ) 33 | if (existingTab) { 34 | const activeColumn = vscode.window.activeTextEditor?.viewColumn 35 | const tabColumn = vscode.window.tabGroups.all.find((group) => 36 | group.tabs.includes(existingTab) 37 | )?.viewColumn 38 | if (activeColumn && activeColumn !== tabColumn && !existingTab.isDirty) { 39 | await vscode.window.tabGroups.close(existingTab) 40 | } 41 | break 42 | } 43 | } 44 | } catch { } // not essential, sometimes tab operations fail 45 | 46 | const document = await vscode.workspace.openTextDocument(uri) 47 | await vscode.window.showTextDocument(document, { preview: false }) 48 | } catch (error) { 49 | vscode.window.showErrorMessage(`Could not open file!`) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /webview/src/components/chat/ChatRow.tsx: -------------------------------------------------------------------------------- 1 | import deepEqual from "fast-deep-equal" 2 | import { memo, useEffect, useRef } from "react" 3 | import { useSize } from "react-use" 4 | import { CodeyMessage } from "../../../../src/shared/interfaces" 5 | import ChatRowContent from "./ChatRowContent" 6 | 7 | export interface ChatRowProps { 8 | message: CodeyMessage 9 | isExpanded: boolean 10 | onToggleExpand: () => void 11 | lastModifiedMessage?: CodeyMessage 12 | isLast: boolean 13 | onHeightChange: (isTaller: boolean) => void 14 | } 15 | 16 | const ChatRow = memo( 17 | (props: ChatRowProps) => { 18 | const { isLast, onHeightChange, message } = props 19 | // Store the previous height to compare with the current height 20 | // This allows us to detect changes without causing re-renders 21 | const prevHeightRef = useRef(0) 22 | 23 | const [chatrow, { height }] = useSize( 24 |
28 | 29 |
30 | ) 31 | 32 | useEffect(() => { 33 | // used for partials, command output, etc. 34 | // NOTE: it's important we don't distinguish between partial or complete here since our scroll effects in chatview need to handle height change during partial -> complete 35 | const isInitialRender = prevHeightRef.current === 0 // prevents scrolling when new element is added since we already scroll for that 36 | // height starts off at Infinity 37 | if (isLast && height !== 0 && height !== Infinity && height !== prevHeightRef.current) { 38 | if (!isInitialRender) { 39 | onHeightChange(height > prevHeightRef.current) 40 | } 41 | prevHeightRef.current = height 42 | } 43 | }, [height, isLast, onHeightChange, message]) 44 | 45 | // we cannot return null as virtuoso does not support it, so we use a separate visibleMessages array to filter out messages that should not be rendered 46 | return chatrow 47 | }, 48 | // memo does shallow comparison of props, so we need to do deep comparison of arrays/objects whose properties might change 49 | deepEqual 50 | ) 51 | 52 | export default ChatRow 53 | -------------------------------------------------------------------------------- /src/integrations/misc/extract-text.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | // @ts-ignore-next-line 3 | import fs from "fs/promises" 4 | import { isBinaryFile } from "isbinaryfile" 5 | import mammoth from "mammoth" 6 | import pdf from "pdf-parse/lib/pdf-parse" 7 | 8 | export async function extractTextFromFile(filePath: string): Promise { 9 | try { 10 | await fs.access(filePath) 11 | } catch (error) { 12 | throw new Error(`File not found: ${filePath}`) 13 | } 14 | const fileExtension = path.extname(filePath).toLowerCase() 15 | switch (fileExtension) { 16 | case ".pdf": 17 | return extractTextFromPDF(filePath) 18 | case ".docx": 19 | return extractTextFromDOCX(filePath) 20 | case ".ipynb": 21 | return extractTextFromIPYNB(filePath) 22 | default: 23 | const isBinary = await isBinaryFile(filePath).catch(() => false) 24 | if (!isBinary) { 25 | const content = await fs.readFile(filePath, "utf8") 26 | return addLineNumbers(content) 27 | } else { 28 | throw new Error(`Cannot read text for file type: ${fileExtension}`) 29 | } 30 | } 31 | } 32 | 33 | async function extractTextFromPDF(filePath: string): Promise { 34 | const dataBuffer = await fs.readFile(filePath) 35 | const data = await pdf(dataBuffer) 36 | return addLineNumbers(data.text) 37 | } 38 | 39 | async function extractTextFromDOCX(filePath: string): Promise { 40 | const result = await mammoth.extractRawText({ path: filePath }) 41 | return addLineNumbers(result.value) 42 | } 43 | 44 | async function extractTextFromIPYNB(filePath: string): Promise { 45 | const data = await fs.readFile(filePath, "utf8") 46 | const notebook = JSON.parse(data) 47 | let extractedText = "" 48 | 49 | for (const cell of notebook.cells) { 50 | if ((cell.cell_type === "markdown" || cell.cell_type === "code") && cell.source) { 51 | extractedText += cell.source.join("\n") + "\n" 52 | } 53 | } 54 | 55 | return addLineNumbers(extractedText) 56 | } 57 | 58 | function addLineNumbers(text: string): string { 59 | const lines = text.split('\n') 60 | const maxLineNumberWidth = lines.length.toString().length 61 | return lines.map((line, index) => { 62 | const lineNumber = (index + 1).toString().padStart(maxLineNumberWidth, ' ') 63 | return `${lineNumber} | ${line}` 64 | }).join('\n') 65 | } 66 | -------------------------------------------------------------------------------- /src/integrations/editor/detect-omission.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | /** 4 | * Detects potential AI-generated code omissions in the given file content. 5 | * @param originalFileContent The original content of the file. 6 | * @param newFileContent The new content of the file to check. 7 | * @returns True if a potential omission is detected, false otherwise. 8 | */ 9 | function detectCodeOmission(originalFileContent: string, newFileContent: string): boolean { 10 | const originalLines = originalFileContent.split("\n") 11 | const newLines = newFileContent.split("\n") 12 | const omissionKeywords = ["remain", "remains", "unchanged", "rest", "previous", "existing", "..."] 13 | 14 | const commentPatterns = [ 15 | /^\s*\/\//, // Single-line comment for most languages 16 | /^\s*#/, // Single-line comment for Python, Ruby, etc. 17 | /^\s*\/\*/, // Multi-line comment opening 18 | /^\s*{\s*\/\*/, // JSX comment opening 19 | /^\s*