editcodewithai 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +176 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# editcodewithai
|
|
2
|
+
Edit Code With AI
|
|
3
|
+
|
|
4
|
+
## A Simple Open Source AI Code Editor
|
|
5
|
+
|
|
6
|
+
The premise of this project is to build and iterate an open source AI code editing system. The core idea is that you can feed this system a set of source code files and high level instructions, and it will return the edited code after some time. It aims to be compatible with many LLMs, and flexible enough to adapt to future models as they come out.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Clone the repository
|
|
12
|
+
git clone https://github.com/yourusername/editcodewithai.git
|
|
13
|
+
|
|
14
|
+
# Navigate to project directory
|
|
15
|
+
cd editcodewithai
|
|
16
|
+
|
|
17
|
+
# Install dependencies
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { performAiEdit } from 'editcodewithai';
|
|
25
|
+
|
|
26
|
+
const result = await performAiEdit({
|
|
27
|
+
prompt: "Update the code to use async/await",
|
|
28
|
+
modelName: "openai/gpt-4",
|
|
29
|
+
files: {
|
|
30
|
+
// Your files object
|
|
31
|
+
},
|
|
32
|
+
apiKey: "your-openrouter-api-key",
|
|
33
|
+
baseURL: "https://openrouter.ai/api/v1"
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `files` object should be a map of file IDs to file objects containing:
|
|
38
|
+
- `name`: The filename (e.g. "index.js")
|
|
39
|
+
- `text`: The file contents as a string
|
|
40
|
+
|
|
41
|
+
The function returns:
|
|
42
|
+
- `changedFiles`: Updated files with modifications
|
|
43
|
+
- `openRouterGenerationId`: ID of the generation
|
|
44
|
+
- `upstreamCostCents`: Cost in cents
|
|
45
|
+
- `provider`: The AI provider used
|
|
46
|
+
- `inputTokens`: Number of input tokens
|
|
47
|
+
- `outputTokens`: Number of output tokens
|
|
48
|
+
|
|
49
|
+
## Similar open source projects
|
|
50
|
+
|
|
51
|
+
- **Aider**: An AI pair programming tool that integrates with your terminal to assist in code editing within your local git repository. [https://aider.chat/](https://aider.chat/)
|
|
52
|
+
|
|
53
|
+
- **Bolt.new / Bolt.diy**: A platform that allows users to prompt, run, edit, and deploy full-stack web and mobile applications. [https://bolt.new/](https://bolt.new/)
|
|
54
|
+
|
|
55
|
+
- **Cline**: An AI-powered code assistant designed to help developers write and debug code more efficiently. [https://cline.ai/](https://cline.ai/)
|
|
56
|
+
|
|
57
|
+
- **Cerebras Coder**: A code generation tool developed by Cerebras Systems, leveraging advanced AI models to assist in coding tasks. [https://www.cerebras.net/](https://www.cerebras.net/)
|
|
58
|
+
|
|
59
|
+
- **Pear AI**: An open-source AI code editor that accelerates the development process by integrating features like AI chat, code generation, and debugging assistance. [https://trypear.ai/](https://trypear.ai/)
|
|
60
|
+
|
|
61
|
+
- **Void**: An open-source alternative to proprietary AI code editors, offering AI-assisted coding features while prioritizing user privacy and control. [https://void.dev/](https://void.dev/)
|
|
62
|
+
|
|
63
|
+
- **Cody**: An advanced AI coding assistant developed by Sourcegraph, integrating seamlessly with popular IDEs to provide features like AI-driven chat, code autocompletion, and inline editing. [https://github.com/sourcegraph/cody](https://github.com/sourcegraph/cody)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { VizFiles } from "@vizhub/viz-types";
|
|
2
|
+
export interface PerformAiEditParams {
|
|
3
|
+
prompt: string;
|
|
4
|
+
modelName: string;
|
|
5
|
+
files: VizFiles;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
baseURL: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface PerformAiEditResult {
|
|
10
|
+
changedFiles: VizFiles;
|
|
11
|
+
openRouterGenerationId: string;
|
|
12
|
+
upstreamCostCents: number;
|
|
13
|
+
provider: string;
|
|
14
|
+
inputTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
promptTemplateVersion: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Core AI logic for:
|
|
20
|
+
* - Building the prompt (including context)
|
|
21
|
+
* - Calling the LLM (ChatOpenAI)
|
|
22
|
+
* - Parsing and merging file changes
|
|
23
|
+
* - Retrieving cost metadata
|
|
24
|
+
*/
|
|
25
|
+
export declare function performAiEdit({ prompt, modelName, files, apiKey, baseURL, }: PerformAiEditParams): Promise<PerformAiEditResult>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.performAiEdit = performAiEdit;
|
|
4
|
+
const openai_1 = require("@langchain/openai");
|
|
5
|
+
const output_parsers_1 = require("@langchain/core/output_parsers");
|
|
6
|
+
const llm_code_format_1 = require("llm-code-format");
|
|
7
|
+
const viz_types_1 = require("@vizhub/viz-types");
|
|
8
|
+
const debug = false;
|
|
9
|
+
// Versions of the prompt template.
|
|
10
|
+
const PROMPT_TEMPLATE_VERSION = 1;
|
|
11
|
+
// Template pieces
|
|
12
|
+
const TASK = (prompt) => `## Your Task\n\n${prompt}`;
|
|
13
|
+
const FILES = (filesContext) => `## Original Files\n\n${filesContext}`;
|
|
14
|
+
const FORMAT = [
|
|
15
|
+
"## Formatting Instructions\n\n",
|
|
16
|
+
"Suggest changes to the original files using this exact format:\n\n",
|
|
17
|
+
"**fileA.js**\n\n```js\n// Entire updated code for fileA\n```\n\n",
|
|
18
|
+
"**fileB.js**\n\n```js\n// Entire updated code for fileB\n```\n\n",
|
|
19
|
+
"Only include the files that need to be updated or created.\n\n",
|
|
20
|
+
"To suggest changes you MUST include the ENTIRE content of the updated file.\n\n",
|
|
21
|
+
'NEVER leave out sections as in "... rest of the code remain the same ...".\n\n',
|
|
22
|
+
"Refactor large files into smaller files in the same directory.\n\n",
|
|
23
|
+
"Delete all unused files, but we need to keep `README.md`. ",
|
|
24
|
+
"Files can be deleted by setting their content to empty, for example:\n\n",
|
|
25
|
+
"**fileToDelete.js**\n\n```\n```\n\n",
|
|
26
|
+
"For D3 logic, make sure it remains idempotent (use data joins), ",
|
|
27
|
+
"and prefer function signatures like `someFunction(selection, options)` ",
|
|
28
|
+
"where `selection` is a D3 selection and `options` is an object.\n\n",
|
|
29
|
+
].join("");
|
|
30
|
+
function assembleFullPrompt({ filesContext, prompt, }) {
|
|
31
|
+
return [TASK(prompt), FILES(filesContext), FORMAT].join("\n\n");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* If the LLM outputs empty text for a file, we interpret this
|
|
35
|
+
* as a request to delete the file.
|
|
36
|
+
*/
|
|
37
|
+
function shouldDeleteFile(file) {
|
|
38
|
+
return file && file.text.trim() === "";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get cost and metadata for a generation from OpenRouter.
|
|
42
|
+
*/
|
|
43
|
+
async function getGenerationMetadata({ apiKey, generationId, }) {
|
|
44
|
+
const url = `https://openrouter.ai/api/v1/generation?id=${generationId}`;
|
|
45
|
+
const headers = {
|
|
46
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47
|
+
};
|
|
48
|
+
// Retry logic for potential 404 from OpenRouter
|
|
49
|
+
const maxRetries = 10;
|
|
50
|
+
const retryDelay = 1000; // 1 second
|
|
51
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
method: "GET",
|
|
54
|
+
headers,
|
|
55
|
+
});
|
|
56
|
+
if (response.ok) {
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
debug && console.log("[getCost] data:", JSON.stringify(data, null, 2));
|
|
59
|
+
const upstreamCostInDollars = data.data.total_cost;
|
|
60
|
+
const upstreamCostCents = upstreamCostInDollars * 100;
|
|
61
|
+
const provider = data.data.provider_name;
|
|
62
|
+
const inputTokens = data.data.tokens_prompt;
|
|
63
|
+
const outputTokens = data.data.tokens_completion;
|
|
64
|
+
return {
|
|
65
|
+
upstreamCostCents,
|
|
66
|
+
provider,
|
|
67
|
+
inputTokens,
|
|
68
|
+
outputTokens,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const text = await response.text();
|
|
73
|
+
debug &&
|
|
74
|
+
console.error(`Attempt ${attempt} failed. Status: ${response.status}, response text: ${text}`);
|
|
75
|
+
if (attempt < maxRetries) {
|
|
76
|
+
debug && console.log(`Retrying in ${retryDelay / 1000} seconds...`);
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw new Error(`HTTP error! Status: ${response.status} after ${maxRetries} attempts.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// You theoretically never reach here because of the throw above
|
|
85
|
+
throw new Error("Failed to get generation metadata.");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Core AI logic for:
|
|
89
|
+
* - Building the prompt (including context)
|
|
90
|
+
* - Calling the LLM (ChatOpenAI)
|
|
91
|
+
* - Parsing and merging file changes
|
|
92
|
+
* - Retrieving cost metadata
|
|
93
|
+
*/
|
|
94
|
+
async function performAiEdit({ prompt, modelName, files, apiKey, baseURL, }) {
|
|
95
|
+
// 1. Serialize the existing files into the “markdown code block” format
|
|
96
|
+
const filesContext = (0, llm_code_format_1.serializeMarkdownFiles)(Object.values(files).map((file) => ({
|
|
97
|
+
name: file.name,
|
|
98
|
+
// Example: truncate large files, etc.
|
|
99
|
+
text: file.text
|
|
100
|
+
.split("\n")
|
|
101
|
+
.slice(0, file.name.endsWith(".csv") || file.name.endsWith(".json") ? 50 : 500)
|
|
102
|
+
.map((line) => line.slice(0, 200))
|
|
103
|
+
.join("\n"),
|
|
104
|
+
})));
|
|
105
|
+
// 2. Assemble the final prompt
|
|
106
|
+
const fullPrompt = assembleFullPrompt({ filesContext, prompt });
|
|
107
|
+
debug && console.log("[performAiEdit] fullPrompt:", fullPrompt);
|
|
108
|
+
// 3. Invoke the model via LangChain’s ChatOpenAI
|
|
109
|
+
const options = {
|
|
110
|
+
modelName,
|
|
111
|
+
configuration: {
|
|
112
|
+
apiKey,
|
|
113
|
+
baseURL,
|
|
114
|
+
},
|
|
115
|
+
streaming: false,
|
|
116
|
+
};
|
|
117
|
+
const chatModel = new openai_1.ChatOpenAI(options);
|
|
118
|
+
const result = await chatModel.invoke(fullPrompt);
|
|
119
|
+
// 4. We parse the output to figure out which files changed
|
|
120
|
+
const parser = new output_parsers_1.StringOutputParser();
|
|
121
|
+
const resultString = await parser.invoke(result);
|
|
122
|
+
const parsed = (0, llm_code_format_1.parseMarkdownFiles)(resultString, "bold");
|
|
123
|
+
// 5. Merge the changes into a new `Files` object
|
|
124
|
+
let changedFiles = Object.keys(files).reduce((acc, fileId) => {
|
|
125
|
+
const original = files[fileId];
|
|
126
|
+
const changedFile = parsed.files.find((f) => f.name === original.name);
|
|
127
|
+
if (shouldDeleteFile(changedFile)) {
|
|
128
|
+
// Exclude from new set
|
|
129
|
+
return acc;
|
|
130
|
+
}
|
|
131
|
+
// If changedFile is present, use it; otherwise use original
|
|
132
|
+
acc[fileId] = {
|
|
133
|
+
...original,
|
|
134
|
+
text: changedFile ? changedFile.text : original.text,
|
|
135
|
+
};
|
|
136
|
+
return acc;
|
|
137
|
+
}, {});
|
|
138
|
+
// Handle newly-created files
|
|
139
|
+
parsed.files.forEach((changedFile) => {
|
|
140
|
+
const existingFile = Object.values(changedFiles).find((file) => file.name === changedFile.name);
|
|
141
|
+
// If no existing file and not empty => it's a new file
|
|
142
|
+
if (!existingFile && !shouldDeleteFile(changedFile)) {
|
|
143
|
+
const newFileId = (0, viz_types_1.generateVizFileId)();
|
|
144
|
+
changedFiles[newFileId] = {
|
|
145
|
+
name: changedFile.name,
|
|
146
|
+
text: changedFile.text,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// 6. Retrieve cost metadata for charging the user
|
|
151
|
+
// The openRouterGenerationId is stored in `result.lc_kwargs.id` by LangChain
|
|
152
|
+
const openRouterGenerationId = result.lc_kwargs?.id;
|
|
153
|
+
let upstreamCostCents = 0;
|
|
154
|
+
let provider = "";
|
|
155
|
+
let inputTokens = 0;
|
|
156
|
+
let outputTokens = 0;
|
|
157
|
+
if (openRouterGenerationId) {
|
|
158
|
+
const costData = await getGenerationMetadata({
|
|
159
|
+
apiKey,
|
|
160
|
+
generationId: openRouterGenerationId,
|
|
161
|
+
});
|
|
162
|
+
upstreamCostCents = costData.upstreamCostCents;
|
|
163
|
+
provider = costData.provider;
|
|
164
|
+
inputTokens = costData.inputTokens;
|
|
165
|
+
outputTokens = costData.outputTokens;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
changedFiles,
|
|
169
|
+
openRouterGenerationId: openRouterGenerationId ?? "",
|
|
170
|
+
upstreamCostCents,
|
|
171
|
+
provider,
|
|
172
|
+
inputTokens,
|
|
173
|
+
outputTokens,
|
|
174
|
+
promptTemplateVersion: PROMPT_TEMPLATE_VERSION,
|
|
175
|
+
};
|
|
176
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "editcodewithai",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Edit Code With AI",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"type-check": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+ssh://git@github.com/vizhub-core/editcodewithai.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"AI",
|
|
23
|
+
"codegen",
|
|
24
|
+
"vibe",
|
|
25
|
+
"coding",
|
|
26
|
+
"LLM",
|
|
27
|
+
"artificial",
|
|
28
|
+
"intelligence",
|
|
29
|
+
"coding"
|
|
30
|
+
],
|
|
31
|
+
"author": "Curran Kelleher",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/vizhub-core/editcodewithai/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/vizhub-core/editcodewithai#readme",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@langchain/core": "^0.3.42",
|
|
39
|
+
"@langchain/openai": "^0.4.6",
|
|
40
|
+
"@vizhub/viz-types": "^0.0.1",
|
|
41
|
+
"llm-code-format": "^0.1.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.3.3",
|
|
45
|
+
"vitest": "^3.0.9"
|
|
46
|
+
}
|
|
47
|
+
}
|