editcodewithai 2.3.0 → 2.5.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # editcodewithai
2
2
 
3
- A lightweight, flexible library for AI-powered code editing.
3
+ A lightweight, model-agnostic library for AI-powered code editing.
4
4
 
5
5
  See also [vizhub-benchmarks](https://github.com/vizhub-core/vizhub-benchmarks).
6
6
 
@@ -8,7 +8,7 @@ See also [vizhub-benchmarks](https://github.com/vizhub-core/vizhub-benchmarks).
8
8
 
9
9
  `editcodewithai` is a JavaScript/TypeScript library that enables AI-powered code editing in your applications. It provides a simple interface to send code files and instructions to an LLM (Large Language Model) and receive edited code in return.
10
10
 
11
- The library is designed to be model-agnostic, allowing you to use any LLM provider while handling the prompt engineering, file parsing, and response processing for you.
11
+ The library is designed to be **model-agnostic** — you provide a function that calls any LLM provider (OpenAI, Anthropic, OpenRouter, local models, etc.), and `editcodewithai` handles the prompt engineering, file parsing, and response processing.
12
12
 
13
13
  Edit formats inspired by [Aider](https://aider.chat/). See [Aider: Edit Formats](https://aider.chat/docs/more/edit-formats.html) for details.
14
14
 
@@ -66,16 +66,24 @@ main();
66
66
 
67
67
  ## Edit Formats
68
68
 
69
- This library supports several "edit formats" that instruct the LLM on how to specify file changes. Different models may perform better with different formats. You can specify the format using the `editFormat` parameter in `performAiEdit`.
69
+ This library supports five edit formats that instruct the LLM on how to specify file changes. Different models may perform better with different formats. You specify the format via the `editFormat` parameter in `performAiEdit`.
70
+
71
+ | Format | Description | File path location |
72
+ | ------------- | ------------------------------------------------------------------------------------------- | ------------------------ |
73
+ | `whole` | Complete file replacement (default). Simple but wasteful for small changes in large files. | After `**file.js**` bold |
74
+ | `diff` | Search/replace blocks. Efficient — only the changed portions are transmitted. (Aider-style) | Outside the code fence |
75
+ | `diff-fenced` | Same search/replace blocks, but with file path _inside_ the code fence. | Inside the code fence |
76
+ | `udiff` | Unified diff format. Each hunk shows exact lines to add/remove with surrounding context. | After `---` / `+++` |
77
+ | `hybrid` | Mix-and-match: the LLM chooses per file whether to use `whole` or `diff` format. | Both styles supported |
70
78
 
71
79
  ### `whole` (default)
72
80
 
73
- The LLM returns the complete, updated content for each file that needs changes. This is simple but can be inefficient for large files with small changes.
81
+ The LLM returns the complete, updated content for each file that needs changes. The file name is written in bold (`**name**`) followed by a fenced code block.
74
82
 
75
- **Example:**
83
+ **Example LLM response:**
76
84
 
77
85
  ````
78
- index.js
86
+ **index.js**
79
87
  ```js
80
88
  console.log("Hello, Universe!");
81
89
  ```
@@ -83,9 +91,9 @@ console.log("Hello, Universe!");
83
91
 
84
92
  ### `diff`
85
93
 
86
- The LLM returns a series of search-and-replace blocks. This is efficient as it only includes the changed portions of the files.
94
+ The LLM returns search/replace blocks. Each block specifies the file path on its own line, then a fenced block containing a `<<<<<<< SEARCH` / `=======` / `>>>>>>> REPLACE` section. The library finds the `SEARCH` text in the original file and replaces it with the `REPLACE` text.
87
95
 
88
- **Example:**
96
+ **Example LLM response:**
89
97
 
90
98
  ````
91
99
  index.js
@@ -98,23 +106,139 @@ console.log("Hello, Universe!");
98
106
  ```
99
107
  ````
100
108
 
101
- ### File Operations
109
+ ### `diff-fenced`
110
+
111
+ Same search/replace concept as `diff`, but the file path is placed on the first line _inside_ the code fence instead of outside it.
112
+
113
+ **Example LLM response:**
114
+
115
+ ````
116
+ ```
117
+ index.js
118
+ <<<<<<< SEARCH
119
+ console.log("Hello, World!");
120
+ =======
121
+ console.log("Hello, Universe!");
122
+ >>>>>>> REPLACE
123
+ ```
124
+ ````
125
+
126
+ ### `udiff`
127
+
128
+ The LLM returns standard unified diff hunks inside a ` ```diff ` code fence. Each hunk begins with `@@ ... @@` and uses `-` / `+` prefixes for removed / added lines. Lines prefixed with a space are context used for matching.
129
+
130
+ **Example LLM response:**
131
+
132
+ ````diff
133
+ ```diff
134
+ --- index.js
135
+ +++ index.js
136
+ @@ -1 +1 @@
137
+ -console.log("Hello, World!");
138
+ +console.log("Hello, Universe!");
139
+ ```
140
+ ````
141
+
142
+ ### `hybrid`
143
+
144
+ The LLM can mix both whole-file and search/replace formats in the same response, choosing per file which is more appropriate. Use the **whole-file format** for major rewrites, new files, or when many parts of a file change. Use the **search/replace diff format** for small, targeted changes. If the same file appears in both formats, the whole-file content takes precedence (applied after the diff).
145
+
146
+ **Example LLM response:**
147
+
148
+ ````
149
+ alpha.js
150
+ ```
151
+ <<<<<<< SEARCH
152
+ const greeting = "Hello";
153
+ =======
154
+ const greeting = "Hi";
155
+ >>>>>>> REPLACE
156
+ ```
157
+
158
+ **beta.js**
159
+
160
+ ```js
161
+ // Entirely rewritten file
162
+ const beta = "new version";
163
+ ```
164
+ ````
165
+
166
+ ## File Operations
102
167
 
103
168
  The library handles several file operations automatically:
104
169
 
105
- - **Updating existing files**: When the AI modifies a file's content
106
- - **Creating new files**: When the AI suggests new files to add
107
- - **Deleting files**: When the AI returns empty content for a file
170
+ - **Updating existing files**: When the AI modifies a file's content (using any edit format).
171
+ - **Creating new files**: When the AI includes a file name that doesn't exist in the original set.
172
+ - **Deleting files**: When the AI returns empty/whitespace-only content for a file (works with `whole` format; for `diff`/`udiff`, deletions happen naturally when all content is replaced).
108
173
 
109
- ## Format Instructions
174
+ ## API Reference
175
+
176
+ ### `performAiEdit(params)`
177
+
178
+ The main entry point. Accepts a `PerformAiEditParams` object and returns a `PerformAiEditResult`.
179
+
180
+ #### Input: `PerformAiEditParams`
181
+
182
+ | Property | Type | Default | Description |
183
+ | ------------- | ----------------------------------------------- | ---------- | -------------------------------------------------------------------------------- |
184
+ | `prompt` | `string` | _required_ | Natural-language instructions describing the desired changes. |
185
+ | `files` | `VizFiles` | _required_ | A record of file objects (`{ [id]: { name, text } }`). |
186
+ | `llmFunction` | `LlmFunction` | _required_ | Async function that sends the assembled prompt to an LLM and returns its output. |
187
+ | `editFormat` | `"whole" \| "diff" \| "diff-fenced" \| "udiff"` | `"whole"` | How the LLM should specify file changes. |
188
+ | `apiKey` | `string` | optional | OpenRouter API key — if provided, cost metadata is fetched automatically. |
189
+
190
+ #### `LlmFunction`
191
+
192
+ ```typescript
193
+ type LlmFunction = (prompt: string) => Promise<{
194
+ content: string; // The raw string response from the LLM
195
+ generationId?: string; // OpenRouter generation ID (for cost tracking)
196
+ }>;
197
+ ```
198
+
199
+ #### Output: `PerformAiEditResult`
200
+
201
+ | Property | Type | Description |
202
+ | ------------------------ | ---------- | -------------------------------------------------------------- |
203
+ | `changedFiles` | `VizFiles` | The updated file collection with all edits applied. |
204
+ | `openRouterGenerationId` | `string` | The generation ID from the LLM function response. |
205
+ | `upstreamCostCents` | `number` | Cost in cents (only populated if `apiKey` was provided). |
206
+ | `provider` | `string` | The OpenRouter provider name used. |
207
+ | `inputTokens` | `number` | Number of input (prompt) tokens billed. |
208
+ | `outputTokens` | `number` | Number of output (completion) tokens billed. |
209
+ | `promptTemplateVersion` | `number` | Version of the prompt template used (for tracking migrations). |
210
+ | `rawResponse` | `string` | The raw string response from the LLM, unmodified. |
110
211
 
111
- The library exports `FORMAT_INSTRUCTIONS` which contains the exact prompt instructions used for each edit format. This can be useful if you want to:
212
+ ### Exported Utilities
112
213
 
113
- - Use the formatting instructions in your own custom prompts
114
- - Understand exactly what instructions are sent to the LLM for each format
115
- - Build your own AI editing tools using the same proven prompt patterns
214
+ ```typescript
215
+ import {
216
+ // --- Prompt assembly ---
217
+ assembleFullPrompt, // Build a complete prompt string from task, files, format
218
+ FORMAT_INSTRUCTIONS, // { whole, diff, 'diff-fenced', udiff, hybrid } — full formatting instructions
219
+ PROMPT_TEMPLATE_VERSION,
220
+
221
+ // --- File processing ---
222
+ mergeFileChanges, // Merge LLM output back into original files
223
+ prepareFilesForPrompt, // Prepare files for prompt (truncation, image exclusion)
224
+ isImageFile, // Check if a filename refers to an image
225
+
226
+ // --- Diff parsing (manual use) ---
227
+ parseDiffs, // Parse search/replace blocks (diff format)
228
+ applyDiffs, // Apply parsed diffs to a file set
229
+ parseDiffFenced, // Parse search/replace blocks (diff-fenced format)
230
+ parseUdiffs, // Parse unified diff hunks
231
+ applyUdiffs, // Apply parsed unified diff hunks to a file set
232
+ applyHybridEdits, // Apply mixed whole-file + diff edits from a single response
233
+
234
+ // --- Cost metadata ---
235
+ getGenerationMetadata, // Fetch OpenRouter cost data for a generation ID
236
+ } from "editcodewithai";
237
+ ```
238
+
239
+ ## Format Instructions
116
240
 
117
- ### Usage
241
+ The `FORMAT_INSTRUCTIONS` export contains the exact prompt text sent to the LLM for each edit format. You can use them in custom prompts:
118
242
 
119
243
  ```typescript
120
244
  import { FORMAT_INSTRUCTIONS } from "editcodewithai";
@@ -134,58 +258,67 @@ ${yourCodeHere}
134
258
  `;
135
259
  ```
136
260
 
137
- ### Available Formats
261
+ ## Image File Handling
138
262
 
139
- The `FORMAT_INSTRUCTIONS` object contains instructions for these edit formats:
263
+ Files with image extensions (`.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.svg`, `.webp`) are handled specially:
140
264
 
141
- - **`whole`**: Instructions for returning complete file contents
142
- - **`diff`**: Instructions for search-and-replace blocks
143
- - **`diff-fenced`**: Instructions for search-and-replace blocks with file paths inside code fences
144
- - **`udiff`**: Instructions for unified diff format
265
+ - They are **excluded** from the file listing sent to the LLM (binary content is not useful in the prompt).
266
+ - Their filenames are listed separately at the end of the prompt so the LLM is aware of them (e.g., to reference them in HTML `<img>` tags).
267
+ - The `isImageFile()` utility can be used to check if a filename is an image.
145
268
 
146
- Each instruction set is a string containing detailed formatting guidelines that help ensure the LLM produces properly structured output that can be parsed by the library.
269
+ ## File Truncation
147
270
 
148
- ## Similar Projects
271
+ When preparing files for the prompt via `prepareFilesForPrompt`, large files are truncated to keep prompts manageable:
149
272
 
150
- - **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/)
273
+ - Regular files: truncated to **500 lines**, each line capped at **200 characters**.
274
+ - CSV and JSON files: truncated to **50 lines** (these files tend to be very large).
275
+ - Image files: excluded entirely (see above).
151
276
 
152
- - **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/)
277
+ ## Benchmarking
153
278
 
154
- - **Cline**: An AI-powered code assistant designed to help developers write and debug code more efficiently. [https://cline.ai/](https://cline.ai/)
279
+ The project includes a benchmarking system for evaluating LLM edit quality across different models and edit formats.
155
280
 
156
- - **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/)
281
+ ```bash
282
+ # Run benchmarks
283
+ npm run benchmark
157
284
 
158
- - **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/)
285
+ # Grade results
286
+ npm run grade
287
+ ```
159
288
 
160
- - **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/)
289
+ Benchmarks live in the `benchmarks/` directory. Results, caches, and challenges are gitignored to keep the repository size small.
161
290
 
162
- - **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)
291
+ ## OpenRouter Cost Tracking
163
292
 
164
- ## Contributing
293
+ If you provide an `apiKey` (OpenRouter API key) and your `LlmFunction` returns a `generationId` from OpenRouter, the library automatically fetches cost metadata after each edit. The result includes `upstreamCostCents`, `provider`, `inputTokens`, and `outputTokens`.
294
+
295
+ The `getGenerationMetadata` utility implements retry logic with up to 10 attempts (1 second delay) to handle the brief delay before OpenRouter makes generation metadata available.
165
296
 
166
- To contribute to this project:
297
+ ## Similar Projects
298
+
299
+ - **[Aider](https://aider.chat/)** — AI pair programming in the terminal with local git repos. The edit formats used in this library were inspired by Aider's search/replace approach.
300
+ - **[Bolt.new / Bolt.diy](https://bolt.new/)** — Prompt, run, edit, and deploy full-stack apps in the browser.
301
+ - **[Cline](https://cline.ai/)** — AI coding assistant for VS Code.
302
+ - **[Cerebras Coder](https://www.cerebras.net/)** — Code generation using Cerebras hardware.
303
+ - **[Pear AI](https://trypear.ai/)** — Open-source AI code editor.
304
+ - **[Void](https://void.dev/)** — Open-source AI coding environment with privacy focus.
305
+ - **[Cody](https://github.com/sourcegraph/cody)** — AI coding assistant by Sourcegraph, available as IDE extensions.
306
+
307
+ ## Contributing
167
308
 
168
309
  ```bash
169
310
  # Clone the repository
170
- git clone https://github.com/yourusername/editcodewithai.git
171
-
172
- # Navigate to project directory
173
- cd editcodewithai
311
+ git clone https://github.com/vizhub-core/editcodewithai.git
174
312
 
175
313
  # Install dependencies
314
+ cd editcodewithai
176
315
  npm install
177
316
  ```
178
317
 
179
- Run tests to ensure everything is working correctly:
318
+ Before submitting a PR, ensure all checks pass:
180
319
 
181
320
  ```bash
182
321
  npm test
183
- ```
184
-
185
- Please submit pull requests with clear descriptions of changes and ensure all tests pass. Protocol for wrapping up a PR:
186
-
187
- ```
188
- npm test
189
322
  npm run typecheck
190
323
  npm run prettier
191
324
  # Verify the README is up to date
@@ -195,4 +328,4 @@ Please create an issue first before creating a PR to discuss the changes you wan
195
328
 
196
329
  ## License
197
330
 
198
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
331
+ MIT. See the [LICENSE](LICENSE) file for details.
@@ -5,9 +5,16 @@ import { VizFiles, VizFile, FileCollection } from "@vizhub/viz-types";
5
5
  */
6
6
  export declare function shouldDeleteFile(file?: VizFile): boolean;
7
7
  /**
8
- * Processes files for the prompt by truncating large files
8
+ * Checks if a filename is an image file based on extension
9
9
  */
10
- export declare function prepareFilesForPrompt(files: VizFiles): FileCollection;
10
+ export declare function isImageFile(fileName: string): boolean;
11
+ /**
12
+ * Processes files for the prompt by truncating large files and excluding images
13
+ */
14
+ export declare function prepareFilesForPrompt(files: VizFiles): {
15
+ files: FileCollection;
16
+ imageFiles: string[];
17
+ };
11
18
  /**
12
19
  * Merges original files with changes from the LLM
13
20
  */
@@ -26,4 +33,14 @@ export interface UdiffHunk {
26
33
  updated: string;
27
34
  }
28
35
  export declare function parseUdiffs(responseText: string): UdiffHunk[];
36
+ /**
37
+ * Hybrid edit mode: parses both whole-file blocks (bold-markdown)
38
+ * and search/replace diff blocks from the same response.
39
+ *
40
+ * Strategy:
41
+ * 1. Apply all search/replace diffs first.
42
+ * 2. Then apply whole-file replacements on top.
43
+ * (If the same file appears in both forms, the whole-file content wins.)
44
+ */
45
+ export declare function applyHybridEdits(responseText: string, originalFiles: VizFiles, parseWholeFiles: (text: string) => FileCollection): VizFiles;
29
46
  export declare function applyUdiffs(originalFiles: VizFiles, hunks: UdiffHunk[]): VizFiles;
package/dist/fileUtils.js CHANGED
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.shouldDeleteFile = shouldDeleteFile;
4
+ exports.isImageFile = isImageFile;
4
5
  exports.prepareFilesForPrompt = prepareFilesForPrompt;
5
6
  exports.mergeFileChanges = mergeFileChanges;
6
7
  exports.parseDiffs = parseDiffs;
7
8
  exports.applyDiffs = applyDiffs;
8
9
  exports.parseDiffFenced = parseDiffFenced;
9
10
  exports.parseUdiffs = parseUdiffs;
11
+ exports.applyHybridEdits = applyHybridEdits;
10
12
  exports.applyUdiffs = applyUdiffs;
11
13
  const viz_utils_1 = require("@vizhub/viz-utils");
12
14
  /**
@@ -19,11 +21,24 @@ function shouldDeleteFile(file) {
19
21
  return file.text.trim() === "";
20
22
  }
21
23
  /**
22
- * Processes files for the prompt by truncating large files
24
+ * Checks if a filename is an image file based on extension
25
+ */
26
+ function isImageFile(fileName) {
27
+ const imageExtensions = /\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i;
28
+ return imageExtensions.test(fileName);
29
+ }
30
+ /**
31
+ * Processes files for the prompt by truncating large files and excluding images
23
32
  */
24
33
  function prepareFilesForPrompt(files) {
25
34
  const result = {};
35
+ const imageFiles = [];
26
36
  Object.values(files).forEach((file) => {
37
+ // Check if it's an image file
38
+ if (isImageFile(file.name)) {
39
+ imageFiles.push(file.name);
40
+ return; // Skip processing image files
41
+ }
27
42
  // Example: truncate large files, etc.
28
43
  result[file.name] = file.text
29
44
  .split("\n")
@@ -31,7 +46,7 @@ function prepareFilesForPrompt(files) {
31
46
  .map((line) => line.slice(0, 200))
32
47
  .join("\n");
33
48
  });
34
- return result;
49
+ return { files: result, imageFiles };
35
50
  }
36
51
  /**
37
52
  * Merges original files with changes from the LLM
@@ -151,6 +166,24 @@ function parseUdiffs(responseText) {
151
166
  }
152
167
  return hunks;
153
168
  }
169
+ /**
170
+ * Hybrid edit mode: parses both whole-file blocks (bold-markdown)
171
+ * and search/replace diff blocks from the same response.
172
+ *
173
+ * Strategy:
174
+ * 1. Apply all search/replace diffs first.
175
+ * 2. Then apply whole-file replacements on top.
176
+ * (If the same file appears in both forms, the whole-file content wins.)
177
+ */
178
+ function applyHybridEdits(responseText, originalFiles, parseWholeFiles) {
179
+ // Step 1: apply search/replace diffs
180
+ const diffs = parseDiffs(responseText);
181
+ let changedFiles = applyDiffs(originalFiles, diffs);
182
+ // Step 2: parse and apply whole-file replacements on top
183
+ const parsed = parseWholeFiles(responseText);
184
+ changedFiles = mergeFileChanges(changedFiles, parsed);
185
+ return changedFiles;
186
+ }
154
187
  function applyUdiffs(originalFiles, hunks) {
155
188
  const changedFiles = JSON.parse(JSON.stringify(originalFiles));
156
189
  for (const hunk of hunks) {
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { PerformAiEditParams, PerformAiEditResult } from "./types";
2
2
  import { PROMPT_TEMPLATE_VERSION, assembleFullPrompt, FORMAT_INSTRUCTIONS } from "./prompt";
3
3
  import { getGenerationMetadata } from "./metadata";
4
- import { prepareFilesForPrompt, mergeFileChanges, parseDiffs, applyDiffs, parseDiffFenced, parseUdiffs, applyUdiffs } from "./fileUtils";
4
+ import { prepareFilesForPrompt, mergeFileChanges, parseDiffs, applyDiffs, parseDiffFenced, parseUdiffs, applyUdiffs, applyHybridEdits, isImageFile } from "./fileUtils";
5
5
  export type { LlmFunction, PerformAiEditParams, PerformAiEditResult, EditFormat, } from "./types";
6
- export { FORMAT_INSTRUCTIONS, mergeFileChanges, prepareFilesForPrompt, parseDiffs, applyDiffs, parseDiffFenced, parseUdiffs, applyUdiffs, assembleFullPrompt, getGenerationMetadata, PROMPT_TEMPLATE_VERSION, };
6
+ export { FORMAT_INSTRUCTIONS, mergeFileChanges, prepareFilesForPrompt, isImageFile, parseDiffs, applyDiffs, parseDiffFenced, parseUdiffs, applyUdiffs, applyHybridEdits, assembleFullPrompt, getGenerationMetadata, PROMPT_TEMPLATE_VERSION, };
7
7
  /**
8
8
  * Core AI logic for:
9
9
  * - Building the prompt (including context)
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PROMPT_TEMPLATE_VERSION = exports.getGenerationMetadata = exports.assembleFullPrompt = exports.applyUdiffs = exports.parseUdiffs = exports.parseDiffFenced = exports.applyDiffs = exports.parseDiffs = exports.prepareFilesForPrompt = exports.mergeFileChanges = exports.FORMAT_INSTRUCTIONS = void 0;
3
+ exports.PROMPT_TEMPLATE_VERSION = exports.getGenerationMetadata = exports.assembleFullPrompt = exports.applyHybridEdits = exports.applyUdiffs = exports.parseUdiffs = exports.parseDiffFenced = exports.applyDiffs = exports.parseDiffs = exports.isImageFile = exports.prepareFilesForPrompt = exports.mergeFileChanges = exports.FORMAT_INSTRUCTIONS = void 0;
4
4
  exports.performAiEdit = performAiEdit;
5
5
  const llm_code_format_1 = require("llm-code-format");
6
6
  const prompt_1 = require("./prompt");
@@ -17,6 +17,8 @@ Object.defineProperty(exports, "applyDiffs", { enumerable: true, get: function (
17
17
  Object.defineProperty(exports, "parseDiffFenced", { enumerable: true, get: function () { return fileUtils_1.parseDiffFenced; } });
18
18
  Object.defineProperty(exports, "parseUdiffs", { enumerable: true, get: function () { return fileUtils_1.parseUdiffs; } });
19
19
  Object.defineProperty(exports, "applyUdiffs", { enumerable: true, get: function () { return fileUtils_1.applyUdiffs; } });
20
+ Object.defineProperty(exports, "applyHybridEdits", { enumerable: true, get: function () { return fileUtils_1.applyHybridEdits; } });
21
+ Object.defineProperty(exports, "isImageFile", { enumerable: true, get: function () { return fileUtils_1.isImageFile; } });
20
22
  const debug = false;
21
23
  /**
22
24
  * Core AI logic for:
@@ -27,10 +29,15 @@ const debug = false;
27
29
  */
28
30
  async function performAiEdit({ prompt, files, llmFunction, apiKey, editFormat = "whole", }) {
29
31
  // 1. Format the existing files into the "markdown code block" format
30
- const preparedFiles = (0, fileUtils_1.prepareFilesForPrompt)(files);
31
- const filesContext = (0, llm_code_format_1.formatMarkdownFiles)(preparedFiles);
32
+ const preparedResult = (0, fileUtils_1.prepareFilesForPrompt)(files);
33
+ const filesContext = (0, llm_code_format_1.formatMarkdownFiles)(preparedResult.files);
32
34
  // 2. Assemble the final prompt
33
- const fullPrompt = (0, prompt_1.assembleFullPrompt)({ filesContext, prompt, editFormat });
35
+ const fullPrompt = (0, prompt_1.assembleFullPrompt)({
36
+ filesContext,
37
+ prompt,
38
+ editFormat,
39
+ imageFiles: preparedResult.imageFiles,
40
+ });
34
41
  debug && console.log("[performAiEdit] fullPrompt:", fullPrompt);
35
42
  // 3. Invoke the model via the provided LLM function
36
43
  const result = await llmFunction(fullPrompt);
@@ -58,6 +65,10 @@ async function performAiEdit({ prompt, files, llmFunction, apiKey, editFormat =
58
65
  changedFiles = (0, fileUtils_1.applyUdiffs)(files, hunks);
59
66
  break;
60
67
  }
68
+ case "hybrid": {
69
+ changedFiles = (0, fileUtils_1.applyHybridEdits)(resultString, files, (text) => (0, llm_code_format_1.parseMarkdownFiles)(text, "bold").files);
70
+ break;
71
+ }
61
72
  default:
62
73
  // This will catch any unhandled or unknown edit formats.
63
74
  throw new Error(`Unknown edit format: ${editFormat}`);
package/dist/prompt.d.ts CHANGED
@@ -4,8 +4,9 @@ export declare const FORMAT_INSTRUCTIONS: Record<EditFormat, string>;
4
4
  /**
5
5
  * Assembles the full prompt by combining task, files context, and formatting instructions
6
6
  */
7
- export declare function assembleFullPrompt({ filesContext, prompt, editFormat, }: {
7
+ export declare function assembleFullPrompt({ filesContext, prompt, editFormat, imageFiles, }: {
8
8
  filesContext: string;
9
9
  prompt: string;
10
10
  editFormat?: EditFormat;
11
+ imageFiles?: string[];
11
12
  }): string;
package/dist/prompt.js CHANGED
@@ -20,11 +20,6 @@ exports.FORMAT_INSTRUCTIONS = {
20
20
  "Delete all unused files, but we need to keep `README.md`. ",
21
21
  "Files can be deleted by setting their content to empty, for example:\n\n",
22
22
  "**fileToDelete.js**\n\n```\n```\n\n",
23
- "For D3 logic, make sure it remains idempotent (use data joins), ",
24
- "and prefer function signatures like `someFunction(selection, options)` ",
25
- "where `selection` is a D3 selection and `options` is an object. ",
26
- 'Only import from the top-level `"d3"` module, not from submodules ',
27
- '(e.g. imports from `"d3-scale"` or `"d3-shape"` are not supported).\n\n',
28
23
  ].join(""),
29
24
  diff: [
30
25
  "## Formatting Instructions\n\n",
@@ -61,11 +56,55 @@ exports.FORMAT_INSTRUCTIONS = {
61
56
  "+// line to be added\n",
62
57
  "```\n",
63
58
  ].join(""),
59
+ hybrid: [
60
+ "## Formatting Instructions\n\n",
61
+ "Suggest changes to the original files. You may use either of these two",
62
+ " formats for each file, choosing whichever is more appropriate:\n\n",
63
+ "**Whole file format** (use for major rewrites, new files, or when many",
64
+ " parts of a file change):\n\n",
65
+ "```\n",
66
+ "**path/to/fileA.js**\n",
67
+ "```js\n",
68
+ "// Entire updated code for fileA\n",
69
+ "```\n",
70
+ "```\n\n",
71
+ "**Search/replace diff format** (use for small, targeted changes):\n\n",
72
+ "```\n",
73
+ "path/to/fileB.js\n",
74
+ "```\n",
75
+ "<<<<<<< SEARCH\n",
76
+ "// code to be replaced\n",
77
+ "=======\n",
78
+ "// new code\n",
79
+ ">>>>>>> REPLACE\n",
80
+ "```\n",
81
+ "```\n\n",
82
+ "You can mix both formats in the same response, choosing per file.",
83
+ " For the whole file format, you MUST include the ENTIRE content of",
84
+ " the updated file. For search/replace, only include the changed",
85
+ " portions.\n\n",
86
+ 'NEVER leave out sections as in "... rest of the code remain the same',
87
+ ' ...".\n\n',
88
+ "Delete all unused files, but we need to keep `README.md`. ",
89
+ "Files can be deleted by setting their content to empty, for example:\n\n",
90
+ "```\n",
91
+ "**fileToDelete.js**\n",
92
+ "```\n",
93
+ "```\n",
94
+ "```\n",
95
+ ].join(""),
64
96
  };
65
97
  /**
66
98
  * Assembles the full prompt by combining task, files context, and formatting instructions
67
99
  */
68
- function assembleFullPrompt({ filesContext, prompt, editFormat = "whole", }) {
100
+ function assembleFullPrompt({ filesContext, prompt, editFormat = "whole", imageFiles = [], }) {
69
101
  const FORMAT = exports.FORMAT_INSTRUCTIONS[editFormat];
70
- return [TASK(prompt), FILES(filesContext), FORMAT].join("\n\n");
102
+ // Add image files list if there are any
103
+ let imageFilesSection = "";
104
+ if (imageFiles.length > 0) {
105
+ imageFilesSection =
106
+ "\nImage files available:\n\n" +
107
+ imageFiles.map((fileName) => ` * \`${fileName}\``).join("\n");
108
+ }
109
+ return [TASK(prompt), FILES(filesContext), FORMAT + imageFilesSection].join("\n\n");
71
110
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { VizFiles } from "@vizhub/viz-types";
2
- export type EditFormat = "whole" | "diff" | "diff-fenced" | "udiff";
2
+ export type EditFormat = "whole" | "diff" | "diff-fenced" | "udiff" | "hybrid";
3
3
  export type LlmFunction = (prompt: string) => Promise<{
4
4
  content: string;
5
5
  generationId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editcodewithai",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Edit Code With AI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -40,22 +40,22 @@
40
40
  },
41
41
  "homepage": "https://github.com/vizhub-core/editcodewithai#readme",
42
42
  "dependencies": {
43
- "@vizhub/viz-types": "^0.3.0",
44
- "@vizhub/viz-utils": "^1.3.0",
45
- "dotenv": "^17.2.0",
43
+ "@vizhub/viz-types": "^0.5.0",
44
+ "@vizhub/viz-utils": "^1.5.0",
45
+ "dotenv": "^17.2.2",
46
46
  "llm-code-format": "^3.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@langchain/core": "^0.3.66",
50
- "@langchain/openai": "^0.6.2",
51
- "langchain": "^0.3.30",
49
+ "@langchain/core": "^0.3.73",
50
+ "@langchain/openai": "^0.6.11",
51
+ "langchain": "^0.3.32",
52
52
  "cors": "^2.8.5",
53
53
  "express": "^5.1.0",
54
- "npm-check-updates": "^18.0.2",
54
+ "npm-check-updates": "^18.0.3",
55
55
  "prettier": "^3.6.2",
56
- "puppeteer": "^24.15.0",
56
+ "puppeteer": "^24.19.0",
57
57
  "ts-node": "^10.9.2",
58
- "typescript": "^5.8.3",
58
+ "typescript": "^5.9.2",
59
59
  "vitest": "^3.2.4"
60
60
  }
61
61
  }