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.
Files changed (33) hide show
  1. package/README.md +13 -0
  2. package/package.json +3 -2
  3. package/src/ai-service/anthropic.js +45 -14
  4. package/src/ai-service/chat-gpt.js +34 -7
  5. package/src/ai-service/common.js +10 -1
  6. package/src/cli/cli-options.js +12 -0
  7. package/src/cli/cli-params.js +15 -0
  8. package/src/cli/validate-cli-params.js +20 -0
  9. package/src/files/read-files.js +8 -3
  10. package/src/files/read-files.test.js +1 -0
  11. package/src/main/codegen.test.js +1 -0
  12. package/src/prompt/function-calling.js +33 -440
  13. package/src/prompt/function-defs/ask-question.js +33 -0
  14. package/src/prompt/function-defs/codegen-summary.js +87 -0
  15. package/src/prompt/function-defs/create-directory.js +21 -0
  16. package/src/prompt/function-defs/create-file.js +26 -0
  17. package/src/prompt/function-defs/delete-file.js +21 -0
  18. package/src/prompt/function-defs/download-file.js +25 -0
  19. package/src/prompt/function-defs/explanation.js +17 -0
  20. package/src/prompt/function-defs/generate-image.js +51 -0
  21. package/src/prompt/function-defs/get-image-assets.js +21 -0
  22. package/src/prompt/function-defs/get-source-code.js +21 -0
  23. package/src/prompt/function-defs/imgly-remove-background.js +25 -0
  24. package/src/prompt/function-defs/move-file.js +25 -0
  25. package/src/prompt/function-defs/patch-file.js +39 -0
  26. package/src/prompt/function-defs/resize-image.js +36 -0
  27. package/src/prompt/function-defs/split-image.js +45 -0
  28. package/src/prompt/function-defs/update-file.js +26 -0
  29. package/src/prompt/prompt-service-ask-question.test.js +163 -0
  30. package/src/prompt/prompt-service.js +56 -8
  31. package/src/prompt/prompt-service.test.js +1 -0
  32. package/src/prompt/systemprompt.js +8 -1
  33. 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.34",
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.24.3",
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
- const response = await anthropic.messages.create({
61
- model: model,
62
- system: prompt.find((item) => item.type === 'systemPrompt').systemPrompt,
63
- messages,
64
- tools: functionDefs.map((fd) => ({
65
- name: fd.name,
66
- description: fd.description,
67
- input_schema: fd.parameters,
68
- })),
69
- tool_choice: requiredFunctionName ? { type: 'tool', name: requiredFunctionName } : { type: 'any' },
70
- max_tokens: cheap ? 4096 : 8192,
71
- temperature: temperature,
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
- const response = await openai.chat.completions.create({
62
- model: model,
63
- messages,
64
- tools: functionDefs.map((funDef) => ({ type: 'function', function: funDef })),
65
- tool_choice: requiredFunctionName ? { type: 'function', function: { name: requiredFunctionName } } : 'required',
66
- temperature: temperature,
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 = {
@@ -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 = usage.inputTokens * inputCostPerToken;
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');
@@ -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
  /**
@@ -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
  /**
@@ -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
- const content = fs.readFileSync(file, 'utf-8');
27
- sourceCode[file] = { content };
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;
@@ -19,6 +19,7 @@ vi.mock('../prompt/limits.js');
19
19
  vi.mock('../cli/cli-params.js', () => ({
20
20
  taskFile: null,
21
21
  contentMask: null,
22
+ ignorePatterns: [],
22
23
  }));
23
24
  vi.mock('../main/config.js', () => ({
24
25
  rcConfig: { rootDir: '/home/project' },
@@ -37,6 +37,7 @@ vi.mock('../cli/cli-params.js', () => ({
37
37
  cheap: false,
38
38
  taskFile: undefined,
39
39
  disableInitialLint: undefined,
40
+ askQuestion: undefined,
40
41
  }));
41
42
  vi.mock('../files/find-files.js', () => ({
42
43
  getSourceFiles: () => [],