editcodewithai 0.0.1 → 0.1.0

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/dist/index.js CHANGED
@@ -1,161 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.performAiEdit = performAiEdit;
4
- const openai_1 = require("@langchain/openai");
5
- const output_parsers_1 = require("@langchain/core/output_parsers");
6
4
  const llm_code_format_1 = require("llm-code-format");
7
- const viz_types_1 = require("@vizhub/viz-types");
5
+ const prompt_1 = require("./prompt");
6
+ const metadata_1 = require("./metadata");
7
+ const fileUtils_1 = require("./fileUtils");
8
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
9
  /**
88
10
  * Core AI logic for:
89
11
  * - Building the prompt (including context)
90
- * - Calling the LLM (ChatOpenAI)
12
+ * - Calling the provided LLM function
91
13
  * - Parsing and merging file changes
92
14
  * - Retrieving cost metadata
93
15
  */
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
- })));
16
+ async function performAiEdit({ prompt, files, llmFunction, apiKey, }) {
17
+ // 1. Serialize the existing files into the "markdown code block" format
18
+ const preparedFiles = (0, fileUtils_1.prepareFilesForPrompt)(files);
19
+ const filesContext = (0, llm_code_format_1.serializeMarkdownFiles)(preparedFiles);
105
20
  // 2. Assemble the final prompt
106
- const fullPrompt = assembleFullPrompt({ filesContext, prompt });
21
+ const fullPrompt = (0, prompt_1.assembleFullPrompt)({ filesContext, prompt });
107
22
  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);
23
+ // 3. Invoke the model via the provided LLM function
24
+ const result = await llmFunction(fullPrompt);
119
25
  // 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);
26
+ const resultString = result.content;
122
27
  const parsed = (0, llm_code_format_1.parseMarkdownFiles)(resultString, "bold");
123
28
  // 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
- });
29
+ const changedFiles = (0, fileUtils_1.mergeFileChanges)(files, parsed.files);
150
30
  // 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;
31
+ const openRouterGenerationId = result.generationId || "";
153
32
  let upstreamCostCents = 0;
154
33
  let provider = "";
155
34
  let inputTokens = 0;
156
35
  let outputTokens = 0;
157
- if (openRouterGenerationId) {
158
- const costData = await getGenerationMetadata({
36
+ if (openRouterGenerationId && apiKey) {
37
+ const costData = await (0, metadata_1.getGenerationMetadata)({
159
38
  apiKey,
160
39
  generationId: openRouterGenerationId,
161
40
  });
@@ -166,11 +45,12 @@ async function performAiEdit({ prompt, modelName, files, apiKey, baseURL, }) {
166
45
  }
167
46
  return {
168
47
  changedFiles,
169
- openRouterGenerationId: openRouterGenerationId ?? "",
48
+ openRouterGenerationId,
170
49
  upstreamCostCents,
171
50
  provider,
172
51
  inputTokens,
173
52
  outputTokens,
174
- promptTemplateVersion: PROMPT_TEMPLATE_VERSION,
53
+ promptTemplateVersion: prompt_1.PROMPT_TEMPLATE_VERSION,
54
+ rawResponse: resultString, // Include the raw response
175
55
  };
176
56
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Get cost and metadata for a generation from OpenRouter.
3
+ */
4
+ export declare function getGenerationMetadata({ apiKey, generationId, }: {
5
+ apiKey: string;
6
+ generationId: string;
7
+ }): Promise<{
8
+ upstreamCostCents: number;
9
+ provider: string;
10
+ inputTokens: number;
11
+ outputTokens: number;
12
+ }>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getGenerationMetadata = getGenerationMetadata;
4
+ const debug = false;
5
+ /**
6
+ * Get cost and metadata for a generation from OpenRouter.
7
+ */
8
+ async function getGenerationMetadata({ apiKey, generationId, }) {
9
+ const url = `https://openrouter.ai/api/v1/generation?id=${generationId}`;
10
+ const headers = {
11
+ Authorization: `Bearer ${apiKey}`,
12
+ };
13
+ // Retry logic for potential 404 from OpenRouter
14
+ const maxRetries = 10;
15
+ const retryDelay = 1000; // 1 second
16
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
17
+ const response = await fetch(url, {
18
+ method: "GET",
19
+ headers,
20
+ });
21
+ if (response.ok) {
22
+ const data = await response.json();
23
+ debug && console.log("[getCost] data:", JSON.stringify(data, null, 2));
24
+ const upstreamCostInDollars = data.data.total_cost;
25
+ const upstreamCostCents = upstreamCostInDollars * 100;
26
+ const provider = data.data.provider_name;
27
+ const inputTokens = data.data.tokens_prompt;
28
+ const outputTokens = data.data.tokens_completion;
29
+ return {
30
+ upstreamCostCents,
31
+ provider,
32
+ inputTokens,
33
+ outputTokens,
34
+ };
35
+ }
36
+ else {
37
+ const text = await response.text();
38
+ debug &&
39
+ console.error(`Attempt ${attempt} failed. Status: ${response.status}, response text: ${text}`);
40
+ if (attempt < maxRetries) {
41
+ debug && console.log(`Retrying in ${retryDelay / 1000} seconds...`);
42
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
43
+ }
44
+ else {
45
+ throw new Error(`HTTP error! Status: ${response.status} after ${maxRetries} attempts.`);
46
+ }
47
+ }
48
+ }
49
+ // You theoretically never reach here because of the throw above
50
+ throw new Error("Failed to get generation metadata.");
51
+ }
@@ -0,0 +1,8 @@
1
+ export declare const PROMPT_TEMPLATE_VERSION = 1;
2
+ /**
3
+ * Assembles the full prompt by combining task, files context, and formatting instructions
4
+ */
5
+ export declare function assembleFullPrompt({ filesContext, prompt, }: {
6
+ filesContext: string;
7
+ prompt: string;
8
+ }): string;
package/dist/prompt.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROMPT_TEMPLATE_VERSION = void 0;
4
+ exports.assembleFullPrompt = assembleFullPrompt;
5
+ // Versions of the prompt template.
6
+ exports.PROMPT_TEMPLATE_VERSION = 1;
7
+ // Template pieces
8
+ const TASK = (prompt) => `## Your Task\n\n${prompt}`;
9
+ const FILES = (filesContext) => `## Original Files\n\n${filesContext}`;
10
+ const FORMAT = [
11
+ "## Formatting Instructions\n\n",
12
+ "Suggest changes to the original files using this exact format:\n\n",
13
+ "**fileA.js**\n\n```js\n// Entire updated code for fileA\n```\n\n",
14
+ "**fileB.js**\n\n```js\n// Entire updated code for fileB\n```\n\n",
15
+ "Only include the files that need to be updated or created.\n\n",
16
+ "To suggest changes you MUST include the ENTIRE content of the updated file.\n\n",
17
+ 'NEVER leave out sections as in "... rest of the code remain the same ...".\n\n',
18
+ "Refactor large files into smaller files in the same directory.\n\n",
19
+ "Delete all unused files, but we need to keep `README.md`. ",
20
+ "Files can be deleted by setting their content to empty, for example:\n\n",
21
+ "**fileToDelete.js**\n\n```\n```\n\n",
22
+ "For D3 logic, make sure it remains idempotent (use data joins), ",
23
+ "and prefer function signatures like `someFunction(selection, options)` ",
24
+ "where `selection` is a D3 selection and `options` is an object.\n\n",
25
+ ].join("");
26
+ /**
27
+ * Assembles the full prompt by combining task, files context, and formatting instructions
28
+ */
29
+ function assembleFullPrompt({ filesContext, prompt, }) {
30
+ return [TASK(prompt), FILES(filesContext), FORMAT].join("\n\n");
31
+ }
@@ -0,0 +1,21 @@
1
+ import { VizFiles } from "@vizhub/viz-types";
2
+ export type LlmFunction = (prompt: string) => Promise<{
3
+ content: string;
4
+ generationId?: string;
5
+ }>;
6
+ export interface PerformAiEditParams {
7
+ prompt: string;
8
+ files: VizFiles;
9
+ llmFunction: LlmFunction;
10
+ apiKey?: string;
11
+ }
12
+ export interface PerformAiEditResult {
13
+ changedFiles: VizFiles;
14
+ openRouterGenerationId: string;
15
+ upstreamCostCents: number;
16
+ provider: string;
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ promptTemplateVersion: number;
20
+ rawResponse: string;
21
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editcodewithai",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Edit Code With AI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,8 @@
12
12
  "test": "vitest run",
13
13
  "build": "tsc",
14
14
  "prepublishOnly": "npm run build",
15
- "type-check": "tsc --noEmit"
15
+ "typecheck": "tsc --noEmit",
16
+ "benchmark": "ts-node src/benchmarks/index.ts"
16
17
  },
17
18
  "repository": {
18
19
  "type": "git",
@@ -35,13 +36,16 @@
35
36
  },
36
37
  "homepage": "https://github.com/vizhub-core/editcodewithai#readme",
37
38
  "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"
39
+ "@langchain/core": "^0.3.43",
40
+ "@langchain/openai": "^0.5.2",
41
+ "@vizhub/viz-types": "^0.0.2",
42
+ "@vizhub/viz-utils": "^0.0.1",
43
+ "dotenv": "^16.4.7",
44
+ "llm-code-format": "^0.2.2"
42
45
  },
43
46
  "devDependencies": {
44
- "typescript": "^5.3.3",
45
- "vitest": "^3.0.9"
47
+ "ts-node": "^10.9.2",
48
+ "typescript": "^5.8.2",
49
+ "vitest": "^3.1.1"
46
50
  }
47
51
  }