genaicode 0.0.34 → 0.0.37
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 +13 -0
- package/package.json +3 -2
- package/src/ai-service/anthropic.js +45 -14
- package/src/ai-service/chat-gpt.js +34 -7
- package/src/ai-service/common.js +10 -1
- package/src/cli/cli-options.js +12 -0
- package/src/cli/cli-params.js +15 -0
- package/src/cli/validate-cli-params.js +20 -0
- package/src/files/read-files.js +8 -3
- package/src/files/read-files.test.js +1 -0
- package/src/main/codegen.test.js +1 -0
- package/src/prompt/function-calling.js +33 -440
- package/src/prompt/function-defs/ask-question.js +33 -0
- package/src/prompt/function-defs/codegen-summary.js +87 -0
- package/src/prompt/function-defs/create-directory.js +21 -0
- package/src/prompt/function-defs/create-file.js +26 -0
- package/src/prompt/function-defs/delete-file.js +21 -0
- package/src/prompt/function-defs/download-file.js +25 -0
- package/src/prompt/function-defs/explanation.js +17 -0
- package/src/prompt/function-defs/generate-image.js +51 -0
- package/src/prompt/function-defs/get-image-assets.js +21 -0
- package/src/prompt/function-defs/get-source-code.js +21 -0
- package/src/prompt/function-defs/imgly-remove-background.js +25 -0
- package/src/prompt/function-defs/move-file.js +25 -0
- package/src/prompt/function-defs/patch-file.js +39 -0
- package/src/prompt/function-defs/resize-image.js +36 -0
- package/src/prompt/function-defs/split-image.js +45 -0
- package/src/prompt/function-defs/update-file.js +26 -0
- package/src/prompt/prompt-service-ask-question.test.js +163 -0
- package/src/prompt/prompt-service.js +56 -8
- package/src/prompt/prompt-service.test.js +1 -0
- package/src/prompt/systemprompt.js +8 -1
- package/src/prompt/systemprompt.test.js +1 -0
package/README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="media/logo-dark.png">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="media/logo.png">
|
|
5
|
+
<img alt="GenAIcode Logo." src="media/logo.png" width="100%" height="auto">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<div align="center">
|
|
10
|
+
|
|
1
11
|
# Programming on steroids
|
|
2
12
|
|
|
3
13
|
<a href="https://www.npmjs.com/package/genaicode">
|
|
@@ -69,6 +79,9 @@ GenAIcode supports various command-line options to customize its behavior:
|
|
|
69
79
|
- `--imagen`: Enables image generation capabilities using AI models.
|
|
70
80
|
- `--cheap`: Uses a cheaper, faster model for code generation, which may provide lower quality results but is more cost-effective for simpler tasks.
|
|
71
81
|
- `--content-mask=<path>`: Applies a content mask to limit the initial source code files included in the request. The value should be a prefix of the path relative to rootDir.
|
|
82
|
+
- `--ignore-pattern="glob/regex"`: Specify a pattern of files to ignore during the initial source code fetching. This saves initial token usage.
|
|
83
|
+
- `--ask-question`: Allows the AI assistant to ask questions for clarification during the code generation process.
|
|
84
|
+
- `--disable-cache`: Disables caching for the application, which can be useful if caching is causing issues or if you want to ensure fresh data is used for each operation.
|
|
72
85
|
- `--help`: Displays the help message with all available options.
|
|
73
86
|
|
|
74
87
|
## Configuration (.genaicoderc)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genaicode",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"author": "Grzegorz Tańczyk",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,12 +43,13 @@
|
|
|
43
43
|
"vitest": "^2.0.4"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@anthropic-ai/sdk": "^0.
|
|
46
|
+
"@anthropic-ai/sdk": "^0.26.1",
|
|
47
47
|
"@anthropic-ai/vertex-sdk": "^0.4.1",
|
|
48
48
|
"@google-cloud/aiplatform": "^3.25.0",
|
|
49
49
|
"@google-cloud/vertexai": "^1.3.0",
|
|
50
50
|
"@imgly/background-removal-node": "^1.4.5",
|
|
51
51
|
"diff": "^5.2.0",
|
|
52
|
+
"glob-regex": "^0.3.2",
|
|
52
53
|
"image-size": "^1.1.1",
|
|
53
54
|
"jsonschema": "^1.4.1",
|
|
54
55
|
"mime-types": "^2.1.35",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import { printTokenUsageAndCost, processFunctionCalls } from './common.js';
|
|
3
|
+
import { disableCache } from '../cli/cli-params.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* This function generates content using the Anthropic Claude model.
|
|
@@ -7,7 +8,7 @@ import { printTokenUsageAndCost, processFunctionCalls } from './common.js';
|
|
|
7
8
|
export async function generateContent(prompt, functionDefs, requiredFunctionName, temperature, cheap = false) {
|
|
8
9
|
const anthropic = new Anthropic({
|
|
9
10
|
defaultHeaders: {
|
|
10
|
-
'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15',
|
|
11
|
+
'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15' + (!disableCache ? ',prompt-caching-2024-07-31' : ''),
|
|
11
12
|
},
|
|
12
13
|
});
|
|
13
14
|
|
|
@@ -35,6 +36,7 @@ export async function generateContent(prompt, functionDefs, requiredFunctionName
|
|
|
35
36
|
{
|
|
36
37
|
type: 'text',
|
|
37
38
|
text: item.text,
|
|
39
|
+
...(item.cache && !disableCache ? { cache_control: { type: 'ephemeral' } } : {}),
|
|
38
40
|
},
|
|
39
41
|
],
|
|
40
42
|
};
|
|
@@ -57,22 +59,51 @@ export async function generateContent(prompt, functionDefs, requiredFunctionName
|
|
|
57
59
|
const model = cheap ? 'claude-3-haiku-20240307' : 'claude-3-5-sonnet-20240620';
|
|
58
60
|
console.log(`Using Anthropic model: ${model}`);
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
let retryCount = 0;
|
|
63
|
+
let response;
|
|
64
|
+
while (retryCount < 3) {
|
|
65
|
+
try {
|
|
66
|
+
response = await anthropic.beta.promptCaching.messages.create({
|
|
67
|
+
model: model,
|
|
68
|
+
system: prompt.find((item) => item.type === 'systemPrompt').systemPrompt,
|
|
69
|
+
messages,
|
|
70
|
+
tools: functionDefs.map((fd) => ({
|
|
71
|
+
name: fd.name,
|
|
72
|
+
description: fd.description,
|
|
73
|
+
input_schema: fd.parameters,
|
|
74
|
+
})),
|
|
75
|
+
tool_choice: requiredFunctionName ? { type: 'tool', name: requiredFunctionName } : { type: 'any' },
|
|
76
|
+
max_tokens: cheap ? 4096 : 8192,
|
|
77
|
+
temperature: temperature,
|
|
78
|
+
});
|
|
79
|
+
break; // Exit loop if successful
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.headers?.['retry-after']) {
|
|
82
|
+
let retryAfter;
|
|
83
|
+
if (error.headers['retry-after'] === '0') {
|
|
84
|
+
retryAfter = (new Date(error.headers['anthropic-ratelimit-tokens-reset']).getTime() - Date.now()) / 1000;
|
|
85
|
+
} else {
|
|
86
|
+
retryAfter = Math.max(parseInt(error.headers['retry-after'], 10), 10);
|
|
87
|
+
}
|
|
88
|
+
console.log(`Rate limited. Retrying after ${retryAfter} seconds. Attempt ${retryCount + 1} of 3.`);
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
|
|
90
|
+
retryCount++;
|
|
91
|
+
} else {
|
|
92
|
+
console.error('An error occurred:', error);
|
|
93
|
+
throw error; // Re-throw the error if it's not a rate limit error
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (retryCount === 3) {
|
|
99
|
+
console.error('Failed to complete request after 3 attempts due to rate limiting.');
|
|
100
|
+
throw new Error('Rate limit exceeded. Operation aborted.');
|
|
101
|
+
}
|
|
73
102
|
|
|
74
103
|
// Print token usage for Anthropic
|
|
75
104
|
const usage = {
|
|
105
|
+
cacheCreateTokens: response.usage.cache_creation_input_tokens,
|
|
106
|
+
cacheReadTokens: response.usage.cache_read_input_tokens,
|
|
76
107
|
inputTokens: response.usage.input_tokens,
|
|
77
108
|
outputTokens: response.usage.output_tokens,
|
|
78
109
|
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
@@ -58,13 +58,40 @@ export async function generateContent(prompt, functionDefs, requiredFunctionName
|
|
|
58
58
|
const model = cheap ? 'gpt-4o-mini' : 'gpt-4o-2024-08-06';
|
|
59
59
|
console.log(`Using OpenAI model: ${model}`);
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
let retryCount = 0;
|
|
62
|
+
let response;
|
|
63
|
+
while (retryCount < 3) {
|
|
64
|
+
try {
|
|
65
|
+
response = await openai.chat.completions.create({
|
|
66
|
+
model: model,
|
|
67
|
+
messages,
|
|
68
|
+
tools: functionDefs.map((funDef) => ({ type: 'function', function: funDef })),
|
|
69
|
+
tool_choice: requiredFunctionName ? { type: 'function', function: { name: requiredFunctionName } } : 'required',
|
|
70
|
+
temperature: temperature,
|
|
71
|
+
});
|
|
72
|
+
break; // Exit loop if successful
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error.response?.headers?.['x-ratelimit-limit-tokens']) {
|
|
75
|
+
const rateLimitTokens = parseInt(error.response.headers['x-ratelimit-limit-tokens'], 10);
|
|
76
|
+
const retryAfter = error.response.headers['retry-after']
|
|
77
|
+
? parseInt(error.response.headers['retry-after'], 10)
|
|
78
|
+
: 1;
|
|
79
|
+
console.log(
|
|
80
|
+
`Rate limited. Token limit: ${rateLimitTokens}. Retrying after ${retryAfter} seconds. Attempt ${retryCount + 1} of 3.`,
|
|
81
|
+
);
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
|
|
83
|
+
retryCount++;
|
|
84
|
+
} else {
|
|
85
|
+
console.error('An error occurred:', error);
|
|
86
|
+
throw error; // Re-throw the error if it's not a rate limit error
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (retryCount === 3) {
|
|
92
|
+
console.error('Failed to complete request after 3 attempts due to rate limiting.');
|
|
93
|
+
throw new Error('Rate limit exceeded. Operation aborted.');
|
|
94
|
+
}
|
|
68
95
|
|
|
69
96
|
// Print token usage for chat gpt
|
|
70
97
|
const usage = {
|
package/src/ai-service/common.js
CHANGED
|
@@ -12,8 +12,17 @@ export function printTokenUsageAndCost(usage, inputCostPerToken, outputCostPerTo
|
|
|
12
12
|
console.log(' - Input tokens: ', usage.inputTokens);
|
|
13
13
|
console.log(' - Output tokens: ', usage.outputTokens);
|
|
14
14
|
console.log(' - Total tokens: ', usage.totalTokens);
|
|
15
|
+
if (usage.cacheCreateTokens) {
|
|
16
|
+
console.log(' - Cache create tokens: ', usage.cacheCreateTokens);
|
|
17
|
+
}
|
|
18
|
+
if (usage.cacheReadTokens) {
|
|
19
|
+
console.log(' - Cache read tokens: ', usage.cacheReadTokens);
|
|
20
|
+
}
|
|
15
21
|
|
|
16
|
-
const inputCost =
|
|
22
|
+
const inputCost =
|
|
23
|
+
usage.inputTokens * inputCostPerToken +
|
|
24
|
+
(usage.cacheCreateTokens ?? 0) * inputCostPerToken * 1.25 +
|
|
25
|
+
(usage.cacheReadTokens ?? 0) * inputCostPerToken * 0.2;
|
|
17
26
|
const outputCost = usage.outputTokens * outputCostPerToken;
|
|
18
27
|
const totalCost = inputCost + outputCost;
|
|
19
28
|
console.log(' - Estimated cost: ', totalCost.toFixed(6), ' USD');
|
package/src/cli/cli-options.js
CHANGED
|
@@ -100,6 +100,18 @@ const cliOptions = [
|
|
|
100
100
|
description:
|
|
101
101
|
'Apply a content mask to limit the initial source code files included in the request. The value should be a prefix of the path relative to rootDir.',
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
name: '--ignore-pattern=<pattern>',
|
|
105
|
+
description: 'Specify a pattern of files to ignore during the initial source code fetching.',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: '--disable-cache',
|
|
109
|
+
description: 'Disable caching for the application.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: '--ask-question',
|
|
113
|
+
description: 'Allows the AI assistant to ask questions for clarification during the code generation process.',
|
|
114
|
+
},
|
|
103
115
|
];
|
|
104
116
|
|
|
105
117
|
/**
|
package/src/cli/cli-params.js
CHANGED
|
@@ -17,6 +17,7 @@ export let vertexAi = params.includes('--vertex-ai');
|
|
|
17
17
|
export let vertexAiClaude = params.includes('--vertex-ai-claude');
|
|
18
18
|
export const dependencyTree = params.includes('--dependency-tree');
|
|
19
19
|
export const verbosePrompt = params.includes('--verbose-prompt');
|
|
20
|
+
export const disableCache = params.includes('--disable-cache');
|
|
20
21
|
export let explicitPrompt = params.find((param) => param.startsWith('--explicit-prompt'))?.split('=')[1];
|
|
21
22
|
export const disableContextOptimization = params.includes('--disable-context-optimization');
|
|
22
23
|
export let taskFile = params.find((param) => param.startsWith('--task-file'))?.split('=')[1];
|
|
@@ -26,6 +27,7 @@ export const disableInitialLint = params.includes('--disable-initial-lint');
|
|
|
26
27
|
export const vision = params.includes('--vision');
|
|
27
28
|
export const imagen = params.find((param) => param.startsWith('--imagen'))?.split('=')[1];
|
|
28
29
|
export const cheap = params.includes('--cheap');
|
|
30
|
+
export const askQuestion = params.includes('--ask-question');
|
|
29
31
|
|
|
30
32
|
// Add support for --help option
|
|
31
33
|
export const helpRequested = params.includes('--help');
|
|
@@ -41,6 +43,11 @@ export const temperature = parseFloat(
|
|
|
41
43
|
// New content mask parameter
|
|
42
44
|
export const contentMask = params.find((param) => param.startsWith('--content-mask='))?.split('=')[1] || null;
|
|
43
45
|
|
|
46
|
+
// New ignore pattern parameter
|
|
47
|
+
export const ignorePatterns = params
|
|
48
|
+
.filter((param) => param.startsWith('--ignore-pattern='))
|
|
49
|
+
.map((param) => param.split('=')[1]);
|
|
50
|
+
|
|
44
51
|
if (taskFile) {
|
|
45
52
|
if (explicitPrompt) {
|
|
46
53
|
throw new Error('The --task-file option is exclusive with the --explicit-prompt option');
|
|
@@ -97,3 +104,11 @@ if (cheap) {
|
|
|
97
104
|
if (contentMask) {
|
|
98
105
|
console.log(`Content mask: ${contentMask}`);
|
|
99
106
|
}
|
|
107
|
+
|
|
108
|
+
if (ignorePatterns) {
|
|
109
|
+
console.log(`Ignore pattern: ${ignorePatterns}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (askQuestion) {
|
|
113
|
+
console.log('Assistant can ask questions to the user');
|
|
114
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import globRegex from 'glob-regex';
|
|
3
4
|
import { rcConfig } from '../main/config.js';
|
|
4
5
|
|
|
5
6
|
// List of allowed CLI parameters
|
|
@@ -26,8 +27,11 @@ const allowedParameters = [
|
|
|
26
27
|
'--vision',
|
|
27
28
|
'--imagen=',
|
|
28
29
|
'--cheap',
|
|
30
|
+
'--ask-question',
|
|
29
31
|
'--help',
|
|
30
32
|
'--content-mask=',
|
|
33
|
+
'--disable-cache',
|
|
34
|
+
'--ignore-pattern=',
|
|
31
35
|
];
|
|
32
36
|
|
|
33
37
|
/**
|
|
@@ -95,6 +99,22 @@ export function validateCliParams() {
|
|
|
95
99
|
);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
102
|
+
|
|
103
|
+
// Validate ignore pattern parameter
|
|
104
|
+
const ignorePatternParams = providedParameters.filter((param) => param.startsWith('--ignore-pattern='));
|
|
105
|
+
if (ignorePatternParams.length > 0) {
|
|
106
|
+
for (const ignorePatternParam of ignorePatternParams) {
|
|
107
|
+
const ignorePatternValue = ignorePatternParam.split('=')[1];
|
|
108
|
+
if (!ignorePatternValue) {
|
|
109
|
+
throw new Error('Invalid --ignore-pattern value. The pattern cannot be empty.');
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
globRegex(ignorePatternValue);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
throw new Error(`Invalid --ignore-pattern value. The pattern "${ignorePatternValue}" is not valid.`, e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
/**
|
package/src/files/read-files.js
CHANGED
|
@@ -2,11 +2,12 @@ import fs from 'fs';
|
|
|
2
2
|
import mime from 'mime-types';
|
|
3
3
|
import sizeOf from 'image-size';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import globRegex from 'glob-regex';
|
|
5
6
|
|
|
6
7
|
import { getSourceFiles, getImageAssetFiles } from './find-files.js';
|
|
7
8
|
import { rcConfig } from '../main/config.js';
|
|
8
9
|
import { verifySourceCodeLimit } from '../prompt/limits.js';
|
|
9
|
-
import { taskFile, contentMask } from '../cli/cli-params.js';
|
|
10
|
+
import { taskFile, contentMask, ignorePatterns } from '../cli/cli-params.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Read contents of source files and create a map with file path as key and file content as value
|
|
@@ -23,8 +24,12 @@ function readSourceFiles(filterPaths) {
|
|
|
23
24
|
continue;
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if (ignorePatterns.some((pattern) => globRegex(pattern).test(file))) {
|
|
28
|
+
sourceCode[file] = { content: null };
|
|
29
|
+
} else {
|
|
30
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
31
|
+
sourceCode[file] = { content };
|
|
32
|
+
}
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
return sourceCode;
|
package/src/main/codegen.test.js
CHANGED