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/README.md +57 -9
- package/dist/benchmark.d.ts +37 -0
- package/dist/benchmark.js +123 -0
- package/dist/benchmarks/benchmarkRunner.d.ts +5 -0
- package/dist/benchmarks/benchmarkRunner.js +85 -0
- package/dist/benchmarks/challenges.d.ts +2 -0
- package/dist/benchmarks/challenges.js +158 -0
- package/dist/benchmarks/fileUtils.d.ts +15 -0
- package/dist/benchmarks/fileUtils.js +55 -0
- package/dist/benchmarks/index.d.ts +5 -0
- package/dist/benchmarks/index.js +44 -0
- package/dist/benchmarks/llm.d.ts +10 -0
- package/dist/benchmarks/llm.js +29 -0
- package/dist/benchmarks/models.d.ts +1 -0
- package/dist/benchmarks/models.js +21 -0
- package/dist/benchmarks/runner.d.ts +4 -0
- package/dist/benchmarks/runner.js +35 -0
- package/dist/benchmarks/types.d.ts +16 -0
- package/dist/benchmarks/types.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +157 -0
- package/dist/computeInitialDocument.d.ts +13 -0
- package/dist/computeInitialDocument.js +135 -0
- package/dist/fileUtils.d.ts +20 -0
- package/dist/fileUtils.js +62 -0
- package/dist/index.d.ts +3 -19
- package/dist/index.js +19 -139
- package/dist/metadata.d.ts +12 -0
- package/dist/metadata.js +51 -0
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.js +31 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +2 -0
- package/package.json +12 -8
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.models = void 0;
|
|
4
|
+
exports.models = [
|
|
5
|
+
"anthropic/claude-3.7-sonnet",
|
|
6
|
+
"anthropic/claude-3.7-sonnet:thinking",
|
|
7
|
+
"google/gemini-2.0-flash-001",
|
|
8
|
+
"anthropic/claude-3.5-sonnet",
|
|
9
|
+
"deepseek/deepseek-r1",
|
|
10
|
+
"openai/o3-mini-high",
|
|
11
|
+
"openai/o3-mini",
|
|
12
|
+
"deepseek/deepseek-chat",
|
|
13
|
+
"qwen/qwen-2.5-72b-instruct",
|
|
14
|
+
"qwen/qwq-32b",
|
|
15
|
+
"anthropic/claude-3.5-haiku",
|
|
16
|
+
"qwen/qwen-2.5-7b-instruct",
|
|
17
|
+
"amazon/nova-micro-v1",
|
|
18
|
+
"meta-llama/llama-3.1-8b-instruct",
|
|
19
|
+
"meta-llama/llama-3.2-3b-instruct",
|
|
20
|
+
"meta-llama/llama-3.2-1b-instruct",
|
|
21
|
+
];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runNodeTest = runNodeTest;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
/**
|
|
6
|
+
* Runs "index.mjs" for a given challenge directory with Node and returns exit code.
|
|
7
|
+
*/
|
|
8
|
+
function runNodeTest(challengeDir) {
|
|
9
|
+
const result = runNodeProcess(challengeDir);
|
|
10
|
+
logTestResult(challengeDir, result);
|
|
11
|
+
return result.status ?? 1; // If status is null, consider that a failure
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Executes the Node.js process for a test
|
|
15
|
+
*/
|
|
16
|
+
function runNodeProcess(challengeDir) {
|
|
17
|
+
const child = (0, child_process_1.spawnSync)("node", ["index.mjs"], {
|
|
18
|
+
cwd: challengeDir,
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
status: child.status,
|
|
23
|
+
stdout: child.stdout,
|
|
24
|
+
stderr: child.stderr
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Logs the test result to the console
|
|
29
|
+
*/
|
|
30
|
+
function logTestResult(challengeDir, result) {
|
|
31
|
+
console.log(`--- Running test in ${challengeDir} ---`);
|
|
32
|
+
console.log("stdout:\n", result.stdout.trim());
|
|
33
|
+
console.log("stderr:\n", result.stderr.trim());
|
|
34
|
+
console.log("exit code:", result.status, "\n");
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { VizFiles } from "@vizhub/viz-types";
|
|
2
|
+
export interface Challenge {
|
|
3
|
+
name: string;
|
|
4
|
+
prompt: string;
|
|
5
|
+
files: VizFiles;
|
|
6
|
+
}
|
|
7
|
+
export interface ChallengeResult {
|
|
8
|
+
challenge: string;
|
|
9
|
+
model: string;
|
|
10
|
+
passFail: "pass" | "fail" | "error";
|
|
11
|
+
}
|
|
12
|
+
export interface TestRunResult {
|
|
13
|
+
status: number | null;
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const index_1 = require("./index");
|
|
44
|
+
const llm_1 = require("./benchmarks/llm");
|
|
45
|
+
const computeInitialDocument_1 = require("./computeInitialDocument");
|
|
46
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
47
|
+
const fs = __importStar(require("fs/promises"));
|
|
48
|
+
// Load environment variables
|
|
49
|
+
dotenv_1.default.config();
|
|
50
|
+
const program = new commander_1.Command();
|
|
51
|
+
program
|
|
52
|
+
.name("editcode")
|
|
53
|
+
.description("Edit code files using AI")
|
|
54
|
+
.option("-p, --prompt <prompt>", "The instruction for editing the code")
|
|
55
|
+
.option("-d, --dir <directory>", "Directory to process", process.cwd())
|
|
56
|
+
.option("--dry-run", "Show changes without writing them", false)
|
|
57
|
+
.option("--model <model>", "OpenRouter model to use", "anthropic/claude-3.7-sonnet")
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
try {
|
|
60
|
+
// Get prompt from user if not provided as argument
|
|
61
|
+
if (!options.prompt) {
|
|
62
|
+
const rl = readline.createInterface({
|
|
63
|
+
input: process.stdin,
|
|
64
|
+
output: process.stdout
|
|
65
|
+
});
|
|
66
|
+
options.prompt = await new Promise((resolve) => {
|
|
67
|
+
rl.question("> ", (answer) => {
|
|
68
|
+
rl.close();
|
|
69
|
+
resolve(answer);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (!options.prompt.trim()) {
|
|
74
|
+
console.error("Error: Prompt cannot be empty");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
// Debug: Log environment
|
|
78
|
+
console.log('Environment variables:', {
|
|
79
|
+
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY ? '[PRESENT]' : '[MISSING]',
|
|
80
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
81
|
+
PATH: process.env.PATH?.substring(0, 50) + '...'
|
|
82
|
+
});
|
|
83
|
+
// Validate API key
|
|
84
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
85
|
+
if (!apiKey) {
|
|
86
|
+
console.error("Error: OPENROUTER_API_KEY environment variable is required");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
// Get absolute path
|
|
90
|
+
const fullPath = path.resolve(options.dir);
|
|
91
|
+
console.log(`Processing directory: ${fullPath}`);
|
|
92
|
+
// Use computeInitialDocument to get files (respects .ignore files)
|
|
93
|
+
const initialDocument = (0, computeInitialDocument_1.computeInitialDocument)({ fullPath });
|
|
94
|
+
// Convert to VizFiles format
|
|
95
|
+
const vizFiles = {};
|
|
96
|
+
Object.entries(initialDocument.files).forEach(([id, file]) => {
|
|
97
|
+
if (file.text !== null) {
|
|
98
|
+
// Skip directories
|
|
99
|
+
vizFiles[id] = {
|
|
100
|
+
name: path.relative(process.cwd(), path.join(fullPath, file.name)),
|
|
101
|
+
text: file.text,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const fileCount = Object.keys(vizFiles).length;
|
|
106
|
+
if (fileCount === 0) {
|
|
107
|
+
console.error("No files found to process");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
console.log(`Found ${fileCount} files to process`);
|
|
111
|
+
// Create LLM function
|
|
112
|
+
const llmFunction = (0, llm_1.createOpenRouterLlmFunction)(options.model, apiKey);
|
|
113
|
+
// Perform the edit
|
|
114
|
+
console.log("\nRequesting changes from AI...");
|
|
115
|
+
const result = await (0, index_1.performAiEdit)({
|
|
116
|
+
prompt: options.prompt,
|
|
117
|
+
files: vizFiles,
|
|
118
|
+
llmFunction,
|
|
119
|
+
apiKey,
|
|
120
|
+
});
|
|
121
|
+
// Show changes
|
|
122
|
+
console.log("\nProposed changes:");
|
|
123
|
+
for (const [id, file] of Object.entries(result.changedFiles)) {
|
|
124
|
+
const original = vizFiles[id];
|
|
125
|
+
if (!original) {
|
|
126
|
+
console.log(`\nNew file: ${file.name}`);
|
|
127
|
+
console.log(file.text);
|
|
128
|
+
}
|
|
129
|
+
else if (original.text !== file.text) {
|
|
130
|
+
console.log(`\nModified: ${file.name}`);
|
|
131
|
+
console.log(file.text);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Write changes if not dry run
|
|
135
|
+
if (!options.dryRun) {
|
|
136
|
+
console.log("\nWriting changes...");
|
|
137
|
+
for (const [_, file] of Object.entries(result.changedFiles)) {
|
|
138
|
+
const fullPath = path.resolve(file.name);
|
|
139
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
140
|
+
await fs.writeFile(fullPath, file.text);
|
|
141
|
+
console.log(`Updated ${fullPath}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Show metadata
|
|
145
|
+
console.log("\nMetadata:");
|
|
146
|
+
console.log(`Model: ${options.model}`);
|
|
147
|
+
console.log(`Provider: ${result.provider}`);
|
|
148
|
+
console.log(`Cost: ${result.upstreamCostCents / 100} USD`);
|
|
149
|
+
console.log(`Input tokens: ${result.inputTokens}`);
|
|
150
|
+
console.log(`Output tokens: ${result.outputTokens}`);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("Error:", error);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
program.parse();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface InitialDocument {
|
|
2
|
+
files: {
|
|
3
|
+
[key: string]: {
|
|
4
|
+
text: string | null;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare const isDirectory: (path: string) => boolean;
|
|
10
|
+
export declare const computeInitialDocument: ({ fullPath }: {
|
|
11
|
+
fullPath: string;
|
|
12
|
+
}) => InitialDocument;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.computeInitialDocument = exports.isDirectory = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
10
|
+
// Configuration constants
|
|
11
|
+
const ignoreFilePattern = '**/{.ignore,.gitignore}';
|
|
12
|
+
const baseIgnore = [
|
|
13
|
+
'.git',
|
|
14
|
+
'node_modules',
|
|
15
|
+
'dist',
|
|
16
|
+
'*.log',
|
|
17
|
+
'.DS_Store',
|
|
18
|
+
'coverage',
|
|
19
|
+
'.env',
|
|
20
|
+
'.env.*',
|
|
21
|
+
];
|
|
22
|
+
const debugIgnore = false;
|
|
23
|
+
const debugDirectories = false;
|
|
24
|
+
const enableDirectories = true;
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} fullPath - absolute path of the workspace root
|
|
27
|
+
* @param {string} currentDirectoryPath - path where the ignore file is found, relative to fullPath
|
|
28
|
+
* @param {string} fileName - name of the ignore file
|
|
29
|
+
* @returns {string[]} parsed lines
|
|
30
|
+
*/
|
|
31
|
+
const parseIgnoreFile = (fullPath, currentDirectory, fileName) => {
|
|
32
|
+
const filePath = path_1.default.join(fullPath, currentDirectory, fileName);
|
|
33
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
34
|
+
const globs = content
|
|
35
|
+
.split(/[\n\r]+/)
|
|
36
|
+
.filter(
|
|
37
|
+
// remove blank line and comments
|
|
38
|
+
(line) => line.length > 0 && !line.startsWith('#'))
|
|
39
|
+
.map((line) => {
|
|
40
|
+
const { bang, slash, glob } = line.match(/^(?<bang>!?)(?<slash>\/?)(?<glob>.*)$/).groups;
|
|
41
|
+
const hasSlash = Boolean(slash) || /\/.*\S/.test(glob);
|
|
42
|
+
const relativeGlob = path_1.default.posix.join(currentDirectory.replace(
|
|
43
|
+
// escape characters with special meaning in glob expressions
|
|
44
|
+
/[*?!# \[\]\\]/g, (char) => '\\' + char),
|
|
45
|
+
// a pattern that doesn't include a slash (not counting a trailing one) matches files in any descendant directory of the current one
|
|
46
|
+
hasSlash ? '' : '**', glob);
|
|
47
|
+
// preserve leading `!` and `/` characters
|
|
48
|
+
return bang + slash + relativeGlob;
|
|
49
|
+
});
|
|
50
|
+
if (debugIgnore) {
|
|
51
|
+
console.debug('at', currentDirectory, 'parsing', fileName, 'obtained globs', globs);
|
|
52
|
+
}
|
|
53
|
+
return globs;
|
|
54
|
+
};
|
|
55
|
+
const isDirectory = (path) => path.endsWith('/');
|
|
56
|
+
exports.isDirectory = isDirectory;
|
|
57
|
+
// Lists files from the file system,
|
|
58
|
+
// converts them into the internal data structure.
|
|
59
|
+
const computeInitialDocument = ({ fullPath }) => {
|
|
60
|
+
// Initialize the document using our data structure for representing files.
|
|
61
|
+
const initialDocument = {
|
|
62
|
+
files: {},
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Stack for recursively traversing directories.
|
|
66
|
+
* @type {string[]}
|
|
67
|
+
*/
|
|
68
|
+
let files = [];
|
|
69
|
+
const ignoreFileMatcher = (0, ignore_1.default)().add(ignoreFilePattern);
|
|
70
|
+
const isIgnoreFile = (fileName) => ignoreFileMatcher.ignores(fileName);
|
|
71
|
+
const unsearchedDirectories = [
|
|
72
|
+
{
|
|
73
|
+
currentDirectory: '.',
|
|
74
|
+
ignore: (0, ignore_1.default)().add(baseIgnore),
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
while (unsearchedDirectories.length !== 0) {
|
|
78
|
+
const { currentDirectory, ignore: parentIgnore } = unsearchedDirectories.pop();
|
|
79
|
+
const currentDirectoryPath = path_1.default.join(fullPath, currentDirectory);
|
|
80
|
+
const dirEntries = fs_1.default
|
|
81
|
+
.readdirSync(currentDirectoryPath, {
|
|
82
|
+
withFileTypes: true,
|
|
83
|
+
})
|
|
84
|
+
.filter((dirent) => enableDirectories ? true : dirent.isFile());
|
|
85
|
+
// find .ignore or .gitignore files in the current directory
|
|
86
|
+
const ignoreFiles = dirEntries
|
|
87
|
+
.filter((dirent) => dirent.isFile() && isIgnoreFile(dirent.name))
|
|
88
|
+
.map((file) => file.name);
|
|
89
|
+
let ignore = parentIgnore;
|
|
90
|
+
if (ignoreFiles.length > 0) {
|
|
91
|
+
const globs = ignoreFiles.flatMap((fileName) => parseIgnoreFile(fullPath, currentDirectory, fileName));
|
|
92
|
+
ignore = (0, ignore_1.default)().add(parentIgnore).add(globs);
|
|
93
|
+
}
|
|
94
|
+
const newFiles = dirEntries
|
|
95
|
+
.filter((dirent) => {
|
|
96
|
+
const relativePath = path_1.default.posix.join(currentDirectory, dirent.name) +
|
|
97
|
+
(dirent.isDirectory() ? '/' : '');
|
|
98
|
+
const keep = !ignore.ignores(relativePath);
|
|
99
|
+
if (debugIgnore && !keep) {
|
|
100
|
+
console.debug('at', currentDirectory, 'ignoring', relativePath);
|
|
101
|
+
}
|
|
102
|
+
return keep;
|
|
103
|
+
})
|
|
104
|
+
// Add a trailing slash for directories
|
|
105
|
+
.map((dirent) => {
|
|
106
|
+
const relativePath = path_1.default.posix.join(currentDirectory, dirent.name);
|
|
107
|
+
if (!dirent.isDirectory()) {
|
|
108
|
+
return relativePath;
|
|
109
|
+
}
|
|
110
|
+
unsearchedDirectories.push({
|
|
111
|
+
currentDirectory: relativePath,
|
|
112
|
+
ignore,
|
|
113
|
+
});
|
|
114
|
+
return relativePath + '/';
|
|
115
|
+
});
|
|
116
|
+
files.push(...newFiles);
|
|
117
|
+
}
|
|
118
|
+
files.forEach((file) => {
|
|
119
|
+
const id = Math.floor(Math.random() * 10000000).toString();
|
|
120
|
+
initialDocument.files[id] = {
|
|
121
|
+
text: (0, exports.isDirectory)(file)
|
|
122
|
+
? null
|
|
123
|
+
: fs_1.default.readFileSync(path_1.default.join(fullPath, file), 'utf-8'),
|
|
124
|
+
name: file,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
if (debugDirectories) {
|
|
128
|
+
console.log('files:');
|
|
129
|
+
console.log(files);
|
|
130
|
+
console.log('initialDocument:');
|
|
131
|
+
console.log(JSON.stringify(initialDocument, null, 2));
|
|
132
|
+
}
|
|
133
|
+
return initialDocument;
|
|
134
|
+
};
|
|
135
|
+
exports.computeInitialDocument = computeInitialDocument;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { VizFiles, VizFile } from "@vizhub/viz-types";
|
|
2
|
+
/**
|
|
3
|
+
* If the LLM outputs empty text for a file, we interpret this
|
|
4
|
+
* as a request to delete the file.
|
|
5
|
+
*/
|
|
6
|
+
export declare function shouldDeleteFile(file?: VizFile): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Processes files for the prompt by truncating large files
|
|
9
|
+
*/
|
|
10
|
+
export declare function prepareFilesForPrompt(files: VizFiles): {
|
|
11
|
+
name: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
/**
|
|
15
|
+
* Merges original files with changes from the LLM
|
|
16
|
+
*/
|
|
17
|
+
export declare function mergeFileChanges(originalFiles: VizFiles, parsedFiles: {
|
|
18
|
+
name: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}[]): VizFiles;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldDeleteFile = shouldDeleteFile;
|
|
4
|
+
exports.prepareFilesForPrompt = prepareFilesForPrompt;
|
|
5
|
+
exports.mergeFileChanges = mergeFileChanges;
|
|
6
|
+
const viz_utils_1 = require("@vizhub/viz-utils");
|
|
7
|
+
/**
|
|
8
|
+
* If the LLM outputs empty text for a file, we interpret this
|
|
9
|
+
* as a request to delete the file.
|
|
10
|
+
*/
|
|
11
|
+
function shouldDeleteFile(file) {
|
|
12
|
+
if (!file)
|
|
13
|
+
return false;
|
|
14
|
+
return file.text.trim() === "";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Processes files for the prompt by truncating large files
|
|
18
|
+
*/
|
|
19
|
+
function prepareFilesForPrompt(files) {
|
|
20
|
+
return Object.values(files).map((file) => ({
|
|
21
|
+
name: file.name,
|
|
22
|
+
// Example: truncate large files, etc.
|
|
23
|
+
text: file.text
|
|
24
|
+
.split("\n")
|
|
25
|
+
.slice(0, file.name.endsWith(".csv") || file.name.endsWith(".json") ? 50 : 500)
|
|
26
|
+
.map((line) => line.slice(0, 200))
|
|
27
|
+
.join("\n"),
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Merges original files with changes from the LLM
|
|
32
|
+
*/
|
|
33
|
+
function mergeFileChanges(originalFiles, parsedFiles) {
|
|
34
|
+
// Start with existing files
|
|
35
|
+
let changedFiles = Object.keys(originalFiles).reduce((acc, fileId) => {
|
|
36
|
+
const original = originalFiles[fileId];
|
|
37
|
+
const changedFile = parsedFiles.find((f) => f.name === original.name);
|
|
38
|
+
if (shouldDeleteFile(changedFile)) {
|
|
39
|
+
// Exclude from new set
|
|
40
|
+
return acc;
|
|
41
|
+
}
|
|
42
|
+
// If changedFile is present, use it; otherwise use original
|
|
43
|
+
acc[fileId] = {
|
|
44
|
+
...original,
|
|
45
|
+
text: changedFile ? changedFile.text : original.text,
|
|
46
|
+
};
|
|
47
|
+
return acc;
|
|
48
|
+
}, {});
|
|
49
|
+
// Handle newly-created files
|
|
50
|
+
parsedFiles.forEach((changedFile) => {
|
|
51
|
+
const existingFile = Object.values(changedFiles).find((file) => file.name === changedFile.name);
|
|
52
|
+
// If no existing file and not empty => it's a new file
|
|
53
|
+
if (!existingFile && !shouldDeleteFile(changedFile)) {
|
|
54
|
+
const newFileId = (0, viz_utils_1.generateVizFileId)();
|
|
55
|
+
changedFiles[newFileId] = {
|
|
56
|
+
name: changedFile.name,
|
|
57
|
+
text: changedFile.text,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return changedFiles;
|
|
62
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
-
}
|
|
1
|
+
import { PerformAiEditParams, PerformAiEditResult } from "./types";
|
|
18
2
|
/**
|
|
19
3
|
* Core AI logic for:
|
|
20
4
|
* - Building the prompt (including context)
|
|
21
|
-
* - Calling the LLM
|
|
5
|
+
* - Calling the provided LLM function
|
|
22
6
|
* - Parsing and merging file changes
|
|
23
7
|
* - Retrieving cost metadata
|
|
24
8
|
*/
|
|
25
|
-
export declare function performAiEdit({ prompt,
|
|
9
|
+
export declare function performAiEdit({ prompt, files, llmFunction, apiKey, }: PerformAiEditParams): Promise<PerformAiEditResult>;
|