gennady 0.3.0 → 0.5.1-next.1

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 (44) hide show
  1. package/README.md +150 -18
  2. package/cli/cmd/cat.js +26 -0
  3. package/cli/cmd/commit.js +83 -0
  4. package/cli/cmd/review.js +58 -0
  5. package/cli/gennady.js +19 -43
  6. package/index.d.ts +3 -0
  7. package/index.js +6 -0
  8. package/llm.md +82 -0
  9. package/package.json +6 -3
  10. package/src/ai/ai-core.js +116 -0
  11. package/src/ai/ai-model.d.ts +37 -0
  12. package/src/ai/ai-model.js +179 -0
  13. package/src/ai/ai-model.test.js +113 -0
  14. package/src/cat-gen/cat-gen.js +53 -0
  15. package/src/commit-gen/commit-gen.js +30 -175
  16. package/src/git/git-core.js +69 -0
  17. package/src/git/git-diff.js +42 -13
  18. package/src/prompts/commit/commit-base-prompt.md +38 -0
  19. package/src/prompts/commit/commit-format-detailed-prompt.md +29 -0
  20. package/src/prompts/commit/commit-format-oneline-prompt.md +19 -0
  21. package/src/prompts/commit/commit-translate-prompt.md +15 -0
  22. package/src/prompts/index.js +10 -0
  23. package/src/prompts/review/review-base-prompt.md +46 -0
  24. package/src/rc/rc-config.d.ts +30 -0
  25. package/src/rc/rc-config.js +122 -0
  26. package/src/rc/rc-config.test.js +61 -0
  27. package/src/review-gen/__fixture__/review-gen-fixture.md +110 -0
  28. package/src/review-gen/review-gen.js +119 -0
  29. package/src/review-gen/review-gen.test.js +79 -0
  30. package/src/review-gen/specs/js/Function.prototype.json +162 -0
  31. package/src/review-gen/specs/js/Global.json +219 -0
  32. package/src/review-gen/specs/js/JSON.json +85 -0
  33. package/src/review-gen/specs/js/Object.json +626 -0
  34. package/src/review-gen/specs/js/Object.prototype.json +337 -0
  35. package/src/review-gen/specs/js/Storage.json +97 -0
  36. package/src/utils/parse-args.js +4 -2
  37. package/src/utils/style.js +12 -3
  38. package/src/utils/unguard.d.ts +9 -0
  39. package/src/utils/unguard.js +57 -0
  40. package/src/git/git-cmd.js +0 -32
  41. package/src/prompts/base-prompt.md +0 -27
  42. package/src/prompts/format-detailed-prompt.md +0 -16
  43. package/src/prompts/format-oneline-prompt.md +0 -12
  44. package/src/prompts/translate-prompt.md +0 -11
package/README.md CHANGED
@@ -3,11 +3,103 @@
3
3
  **GEN**erate **N**ext-level **A**utomated **D**escription **Y**ntelligence.
4
4
 
5
5
  ```bash
6
+ # Commit message
6
7
  npx gennady
8
+
9
+ # Code review for critical issues
10
+ npx gennady review
11
+
12
+ # Quickly display the contents
13
+ npx gennady cat <path1> <path2> ...
7
14
  ```
8
15
 
9
16
  ---
10
17
 
18
+ ### ✨ Features
19
+
20
+ - 🤖 [**Commit Message**](#-commit-messages): Automatically generate clear, descriptive git commit messages from your staged changes.
21
+ - 📝 [**review**](#-review): Instantly review your staged git changes for critical issues (logic, runtime, security).
22
+ - 🐱 [**cat**](#-cat): Quickly display the contents of multiple files or directories at once, filtered by allowed extensions (default: .js, .ts, .tsx).
23
+ - 🛠️ **TypeScript Support**: Full TypeScript type definitions for seamless integration into TypeScript projects.
24
+
25
+ ---
26
+
27
+ ## 🔖 Usage Overview
28
+
29
+ Gennady provides several main CLI commands:
30
+ - `npx gennady` — Generate commit messages from your staged git changes.
31
+ - `npx gennady cat <path1> <path2> ...` — Display the contents of one or more files or directories, filtered by allowed extensions.
32
+ - `npx gennady review` — Review your staged git changes for critical issues.
33
+
34
+ ---
35
+
36
+ ## 🤖 Commit Messages
37
+
38
+ ```sh
39
+ # Basic usage
40
+ npx gennady
41
+
42
+ # Generate oneline commit message
43
+ npx gennady --mode=oneline
44
+
45
+ # Generate detailed commit message
46
+ npx gennady --mode=detailed
47
+
48
+ # Generate detailed commit message relative to the target branch
49
+ npx gennady --branch=develop
50
+ ```
51
+
52
+ ### Options
53
+ | Option | Alias(es) | Description |
54
+ |-------------------|------------------|----------------------------------------------|
55
+ | `--mode` | `-m` | Set the mode (`auto`, `oneline`, `detailed`) |
56
+ | `--oneline` | `--short`, `-o` | Generate a one-line commit message |
57
+ | `--model` | | Specify the AI model |
58
+ | `--branch` | `-b` | Target branch for diff |
59
+ | `--apply` | | Immediately apply the generated commit message to git |
60
+
61
+
62
+ #### What Happens?
63
+ - Gennady analyzes your staged changes.
64
+ - It generates a commit message.
65
+ - If your system language isn't English, it translates the message for you.
66
+
67
+ ---
68
+
69
+
70
+ ## 📝 review
71
+
72
+ Review your staged git changes for critical issues.
73
+
74
+ ```sh
75
+ npx gennady review
76
+
77
+ # Review changes relative to a specific branch
78
+ npx gennady review --branch=develop
79
+ ```
80
+
81
+ #### What Happens?
82
+ - Gennady analyzes your staged changes.
83
+ - It checks only the lines added or modified in your diff for critical issues (logic, runtime, and security errors).
84
+ - If no critical issues are found, it outputs `GOOD`.
85
+ - If issues are found, they are listed in a clear, structured format.
86
+
87
+ ---
88
+
89
+ ## 🐱 cat
90
+
91
+ Display the contents of files or directories (with filtering for allowed extensions).
92
+
93
+ ```sh
94
+ npx gennady cat ./src/
95
+ ```
96
+
97
+ #### Output
98
+ - Shows file contents with headers per file.
99
+ - Hints for copying output without color codes.
100
+
101
+ ---
102
+
11
103
  ## Setup LLM
12
104
 
13
105
  ### Local
@@ -22,32 +114,72 @@ ollama serve
22
114
 
23
115
  ### External
24
116
 
25
- Create `~/.gennadyrc` file:
117
+ Create `~/.gennadyrc` configuration file:
26
118
 
27
119
  ```json
28
- [
29
- {
30
- "url": "https://api.openai.com/v1/chat/completions",
31
- "key": "...",
32
- "model": "gpt-4o"
33
- }
34
- ]
120
+ {
121
+ "models": [
122
+ {
123
+ "model": "gpt-3.5-turbo-0125",
124
+ "url": "https://api.openai.com/v1/chat/completions",
125
+ "key": "...",
126
+ },
127
+ {
128
+ "model": "llama3:8b",
129
+ "url": "http://127.0.0.1:11434/api/generate",
130
+ }
131
+ ]
132
+ }
35
133
  ```
36
134
 
37
135
  ---
38
136
 
39
- ### Usage
137
+ ## 🔌 API
138
+
139
+ Gennady provides a powerful JavaScript/TypeScript API for programmatic usage in your projects.
140
+
141
+ ### Installation
40
142
 
41
143
  ```bash
42
- # Basic usage
43
- npx gennady
144
+ npm install gennady
145
+ ```
44
146
 
45
- # Generate oneline commit message
46
- npx gennady --mode=oneline
147
+ ### Basic Usage
47
148
 
48
- # Generate detailed commit message
49
- npx gennady --mode=detailed
149
+ ```typescript
150
+ import { GennadyRc } from 'gennady/src/rc/rc-config';
151
+ import { AiModel } from 'gennady/src/ai/ai-model';
50
152
 
51
- # Generate detailed commit message relative to the target branch
52
- npx gennady --branch=develop
53
- ```
153
+ // Load configuration
154
+ const rc = new GennadyRc();
155
+
156
+ // Get available AI models
157
+ const models = rc.getModels();
158
+
159
+ // Create AI model instance
160
+ const aiModel = new AiModel(models[0]);
161
+
162
+ // Generate text
163
+ const [response, error] = await aiModel.generate('Hello, world!', {
164
+ temperature: 0.7,
165
+ timeout: 10000
166
+ });
167
+
168
+ if (error) {
169
+ console.error('Error:', error);
170
+ } else {
171
+ console.log('Response:', response);
172
+ }
173
+ ```
174
+
175
+ ### Available Modules
176
+
177
+ - `AiModel`: Core AI model interaction
178
+ - `GennadyRc`: Configuration management
179
+ - `unguard`: Utility functions for error handling
180
+
181
+ For complete API documentation, check the source code with detailed JSDoc comments.
182
+
183
+ ## 🎉 Happy Coding with Gennady!
184
+
185
+ > Made with 🤖 by Konstantin Lebedev
package/cli/cmd/cat.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import { catGen } from '../../src/cat-gen/cat-gen.js';
5
+ import { style } from '../../src/utils/style.js';
6
+ import { parseArgs } from '../../src/utils/parse-args.js';
7
+
8
+ //
9
+ // 🐱 CAT-GEN
10
+ //
11
+ const args = parseArgs(process.argv);
12
+
13
+ if (args._.length === 0) {
14
+ console.error(style.yellow('Usage: npx gennady cat <path1> <path2> ...'));
15
+ process.exit(1);
16
+ }
17
+
18
+ catGen(args._).forEach(({ relativePath, content }) => {
19
+ console.log(style.blue(`#### ${relativePath}`));
20
+ console.log(content);
21
+ console.log('');
22
+ });
23
+
24
+ console.log(style.green(`^`.repeat(40)));
25
+ console.log(style.italic.gray(`Hint: npx gennady cat ${process.argv.slice(3).join(' ')} --plain | pbcopy`));
26
+ console.log('');
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { execSync } from 'node:child_process';
7
+ import { CommitGen } from '../../src/commit-gen/commit-gen.js';
8
+ import { parseArgs } from '../../src/utils/parse-args.js';
9
+ import { style } from '../../src/utils/style.js';
10
+ import { getSysLang } from '../../src/utils/language.js';
11
+
12
+ //
13
+ // 🤖 COMMIT-GEN 💬
14
+ //
15
+
16
+ const CACHE_PATH = path.join(os.homedir(), '.gennady_commit_cache.json');
17
+ const PROJECT_KEY = process.cwd();
18
+ const COMMIT_CACHE = readCache();
19
+
20
+ const params = parseArgs(process.argv, {
21
+ apply: ['apply'],
22
+ mode: ['mode', 'm'],
23
+ oneline: ['short', 'one', 'o'],
24
+ model: ['model'],
25
+ targetBranch: ['branch', 'b'],
26
+ apiUrl: ['api', 'apiUrl'],
27
+ });
28
+
29
+ const commitGen = new CommitGen(params);
30
+
31
+ console.info(
32
+ '🤖',
33
+ style.whiteBright.bold('GENNADY'),
34
+ `(${style.cyan(commitGen.model)} → ${style.yellow(commitGen.mode)})`,
35
+ '🗯️',
36
+ );
37
+
38
+ console.info(style.gray('-'.repeat(40)));
39
+ console.info(`- url: ${style.blue(commitGen.apiUrl)}`);
40
+ console.info(style.gray('-'.repeat(40)));
41
+
42
+ const commitMessage = params.apply && COMMIT_CACHE[PROJECT_KEY] || await commitGen.generate();
43
+ if (!commitMessage) {
44
+ process.exit(0);
45
+ }
46
+
47
+ if (params.apply) {
48
+ // APPLY COMMIT
49
+ execSync(
50
+ `git commit -am "${commitMessage.replace(/"/g, '\"')}"`,
51
+ {stdio: 'inherit'},
52
+ );
53
+ saveCache({ [PROJECT_KEY]: undefined });
54
+ } else {
55
+ // GENERATE COMMIT
56
+ console.info('-'.repeat(40), '\n');
57
+ console.info(style.whiteBright(commitMessage), '\n');
58
+ console.info('^'.repeat(40), '\n');
59
+
60
+ const lang = getSysLang();
61
+ if (lang !== 'en') {
62
+ console.info(await commitGen.translate(commitMessage, lang), '\n');
63
+ console.info('^'.repeat(40), '\n');
64
+ }
65
+
66
+ console.log(style.italic.gray(`Hint: npx gennady ${process.argv.slice(3).join(' ')} --apply`));
67
+ console.log('');
68
+
69
+ saveCache({ [PROJECT_KEY]: commitMessage });
70
+ }
71
+
72
+ function readCache() {
73
+ try {
74
+ return JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8'));
75
+ } catch {
76
+ return {};
77
+ }
78
+ }
79
+
80
+ function saveCache(patch) {
81
+ const next = { ...COMMIT_CACHE, ...patch };
82
+ fs.writeFileSync(CACHE_PATH, JSON.stringify(next, null, 2));
83
+ }
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { getGitDiffInfo } from '../../src/git/git-core.js';
4
+ import { ReviewGen } from '../../src/review-gen/review-gen.js';
5
+ import { parseArgs } from '../../src/utils/parse-args.js';
6
+ import { style } from '../../src/utils/style.js';
7
+
8
+ //
9
+ // 📝 REVIEW-GEN
10
+ //
11
+
12
+ const params = parseArgs(process.argv, {
13
+ branch: ['branch', 'b'],
14
+ });
15
+
16
+ const review = new ReviewGen();
17
+
18
+ console.info(
19
+ `🤖`,
20
+ style.whiteBright.bold(`GENNADY`),
21
+ `(${style.cyan(review.ai.model)})`,
22
+ `📝`,
23
+ );
24
+
25
+ console.info(style.gray(`-`.repeat(40)));
26
+
27
+ const {
28
+ parsedCodeDiff,
29
+ parsedCodeTokens,
30
+ programmingLanguages,
31
+ parsedCodeChunkMaxTokens,
32
+ } = getGitDiffInfo(params.branch);
33
+
34
+ console.info(`- Tokens: ${style.bold.cyanBright(parsedCodeTokens)} ${style.gray(`(max per file: ${parsedCodeChunkMaxTokens})`)}`);
35
+ console.info(`- Languages: ${style.yellow(programmingLanguages)}`);
36
+
37
+ if (parsedCodeDiff.length === 0) {
38
+ console.info(`No changes detected, skipping review.`);
39
+ console.info(style.italic.gray(`Hint: git add`));
40
+ process.exit(0);
41
+ }
42
+
43
+ const batches = review.ai.createPromptsBatchesByDiff(parsedCodeDiff);
44
+
45
+ console.info(`- Queue: ${style.bold.cyan(batches.length)}`);
46
+ console.info(style.gray(`-`.repeat(40)));
47
+
48
+ const startGenTime = performance.now();
49
+ const results = await Promise.all(batches.map(async (batch) => {
50
+ console.info(`- Task:`, batch.tokens, batch.languages);
51
+ return await review.generate(batch.diff, batch.languages);
52
+ }));
53
+
54
+ console.info(style.gray(`-`.repeat(40)));
55
+ console.info(`- Generation time: ${style.blueBright(((performance.now() - startGenTime) / 1000).toFixed(2))}s`);
56
+ console.info(style.gray(`-`.repeat(40)));
57
+
58
+ console.info(results.join('\n\n'));
package/cli/gennady.js CHANGED
@@ -1,48 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync } from 'fs';
4
- import { fileURLToPath } from 'url';
5
- import { dirname, join } from 'path';
6
- import { CommitGen } from '../src/commit-gen/commit-gen.js';
7
- import { parseArgs } from '../src/utils/parse-args.js';
8
- import { style } from '../src/utils/style.js';
9
- import { getSysLang } from '../src/utils/language.js';
3
+ switch(process.argv[2]) {
4
+ //
5
+ // 🐱 CAT-GEN
6
+ //
7
+ case 'cat':
8
+ import('./cmd/cat.js');
9
+ break;
10
10
 
11
- const PROMPTS_DIR = join(
12
- typeof __dirname !== 'string' ? dirname(fileURLToPath(import.meta.url)) : __dirname,
13
- '../src/prompts',
14
- );
11
+ //
12
+ // 📝 REVIEW-GEN
13
+ //
14
+ case 'review':
15
+ import('./cmd/review.js');
16
+ break;
15
17
 
16
- const params = parseArgs(process.argv, {
17
- mode: ['mode', 'm'],
18
- oneline: ['short', 'one', 'o'],
19
- model: ['model'],
20
- targetBranch: ['branch', 'b'],
21
- apiUrl: ['api', 'apiUrl'],
22
- });
23
-
24
- const commit = new CommitGen({
25
- ...params,
26
- basePromptTemplate: readFileSync(join(PROMPTS_DIR, 'base-prompt.md')).toString(),
27
- formatOnelinePromptTemplate: readFileSync(join(PROMPTS_DIR, 'format-oneline-prompt.md')).toString(),
28
- formatDetailedPromptTemplate: readFileSync(join(PROMPTS_DIR, 'format-detailed-prompt.md')).toString(),
29
- translatePromptTemplate: readFileSync(join(PROMPTS_DIR, 'translate-prompt.md')).toString(),
30
- });
31
-
32
- console.info(`🤖`, style.whiteBright.bold(`GENNADY`), `(${style.cyan(commit.model)} → ${style.yellow(commit.mode)})`, `🗯️`);
33
- console.info(style.gray(`-`.repeat(30)));
34
- console.info(`- url: ${style.blue(commit.apiUrl)}`);
35
- console.info(style.gray(`-`.repeat(30)));
36
-
37
- const msg = await commit.generate();
38
- if (msg) {
39
- console.info(`-`.repeat(40), '\n');
40
- console.info(style.whiteBright(msg), '\n');
41
- console.info(`^`.repeat(40), '\n');
42
-
43
- const lang = getSysLang();
44
- if (lang !== 'en') {
45
- console.info(await commit.translate(msg, lang), '\n');
46
- console.info(`^`.repeat(40), '\n');
47
- }
18
+ //
19
+ // 🤖 COMMIT-GEN 💬
20
+ //
21
+ default:
22
+ import('./cmd/commit.js');
23
+ break;
48
24
  }
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './src/ai/ai-model';
2
+ export * from './src/rc/rc-config';
3
+ export * from './src/utils/unguard';
package/index.js CHANGED
@@ -1 +1,7 @@
1
+ export * from './src/ai/ai-model.js';
2
+ export * from './src/rc/rc-config.js';
3
+ export * from './src/utils/unguard.js';
4
+
1
5
  export * from './src/commit-gen/commit-gen.js';
6
+ export * from './src/git/git-diff.js';
7
+ export * from './src/utils/language.js';
package/llm.md ADDED
@@ -0,0 +1,82 @@
1
+ # Gennady API: Using AiModel.generate
2
+
3
+ This guide demonstrates best practices for calling `AiModel.generate` and handling its results, particularly when using TypeScript.
4
+
5
+ ## 1. Basic Usage of `AiModel.generate`
6
+
7
+ The `AiModel.generate` method returns a Promise that resolves to a tuple: `[result, error]`. You need to handle both potential outcomes.
8
+
9
+ ### ✅ GOOD: Handling the Result Tuple
10
+
11
+ This example assumes `aiModel` is an initialized instance of `AiModel`.
12
+
13
+ ```typescript
14
+ import { AiModel } from 'gennady/src/ai/ai-model';
15
+
16
+ async function generateTextWithTupleHandling(aiModel: AiModel, prompt: string) {
17
+ const [response, error] = await aiModel.generate(prompt, {
18
+ temperature: 0.7,
19
+ timeout: 10000, // Optional: timeout in milliseconds
20
+ });
21
+ if (error) {
22
+ throw new Error(`[GENERATE_TEXT_WITH_TUPLE_ERROR] [${aiModel.name}] Generate failed`, {cause: error})
23
+ }
24
+
25
+ return response;
26
+ }
27
+ ```
28
+ **Reasoning:** This approach explicitly checks for an error before attempting to use the response. This is the fundamental way to interact with functions designed with this tuple-based error handling pattern.
29
+
30
+ ## 2. Using `AiModel.generate` with `unguardOrThrow` for Cleaner Code
31
+
32
+ For scenarios where you want to simplify error handling and prefer exceptions for the error path, especially in a sequence of operations, `unguardOrThrow` is recommended.
33
+
34
+ ### ❌ BAD: Multiple Manual Error Checks in Sequential Operations
35
+
36
+ ```typescript
37
+ // ... imports and aiModel initialization ...
38
+ async function sequentialGenerationBad(aiModel: AiModel) {
39
+ const [response1, error1] = await aiModel.generate("First prompt");
40
+ if (error1) {
41
+ throw new Error(`[SEQUENTIAL_GENERATION_BAD_ERROR_1] [${aiModel.name}] Generate failed`, {cause: error1})
42
+ }
43
+ console.log("First response:", response1);
44
+
45
+ const [response2, error2] = await aiModel.generate("Second prompt using: " + response1.substring(0, 20));
46
+ if (error2) {
47
+ throw new Error(`[SEQUENTIAL_GENERATION_BAD_ERROR_2] [${aiModel.name}] Generate failed`, {cause: error2})
48
+ }
49
+ console.log("Second response:", response2);
50
+ // ... and so on
51
+ }
52
+ ```
53
+ **Reasoning:** This becomes verbose and repetitive. Each step requires its own error check and handling logic, making the main flow harder to read.
54
+
55
+ ### ✅ GOOD: Using `unguardOrThrow` for Concise Sequential Operations
56
+
57
+ ```typescript
58
+ import { AiModel } from 'gennady/src/ai/ai-model';
59
+ import { unguardOrThrow } from 'gennady/src/utils/unguard';
60
+
61
+ async function sequentialGenerationGood(aiModel: AiModel) {
62
+ try {
63
+ console.log("Attempting first generation...");
64
+ const response1 = await unguardOrThrow(aiModel.generate("Write a short poem about coding."));
65
+ console.log("First response:\n", response1);
66
+
67
+ console.log("\nAttempting second generation based on the first...");
68
+ const response2 = await unguardOrThrow(aiModel.generate(`Write a haiku based on this line: "${response1.split('\n')[0]}"`));
69
+ console.log("Second response (haiku):\n", response2);
70
+
71
+ // You can continue the chain of operations here
72
+ // const response3 = await unguardOrThrow(aiModel.generate(...));
73
+
74
+ } catch (error) {
75
+ throw new Error(`[SEQUENTIAL_GENERATION_GOOD_ERROR] [${aiModel.name}] Generate failed`, {cause: error})
76
+ }
77
+ }
78
+ ```
79
+ **Reasoning:** `unguardOrThrow` unwraps the success value or throws the error if present. This allows you to write cleaner, more linear code for the success path and handle all errors in a single `catch` block. This is particularly useful for chained asynchronous operations where an error at any point should halt the entire chain.
80
+
81
+ ---
82
+ Remember to always initialize `AiModel` with valid configuration, typically loaded via `GennadyRc`. The examples above focus on the `generate` call itself, assuming `aiModel` is ready.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gennady",
3
- "version": "0.3.0",
3
+ "version": "0.5.1-next.1",
4
4
  "author": "Konstantin Lebedev <ibnrubaxa@gmail.com>",
5
5
  "description": "Gennady — Generate Next-level Automated Description Yntelligence",
6
6
  "keywords": [
@@ -10,14 +10,17 @@
10
10
  "local",
11
11
  "commit",
12
12
  "generator",
13
- "gennady"
13
+ "gennady",
14
+ "review",
15
+ "code-review"
14
16
  ],
15
17
  "license": "MIT",
16
18
  "repository": "https://github.com/rubaxa/gennady",
17
19
  "type": "module",
18
20
  "bin": "./cli/gennady.js",
19
21
  "main": "index.js",
22
+ "types": "index.d.ts",
20
23
  "scripts": {
21
- "test": "echo \"Error: no test specified\" && exit 1"
24
+ "test": "node --test"
22
25
  }
23
26
  }
@@ -0,0 +1,116 @@
1
+ import { AiModel } from './ai-model.js';
2
+ import { GennadyRc } from '../rc/rc-config.js';
3
+ import { unguardOrThrow } from '../utils/unguard.js';
4
+
5
+ /** @deprecated */
6
+ export class AiCore {
7
+ /**
8
+ * List of AI Models
9
+ * @type {AiModel[]} array of #AI_MODEL_CLASS
10
+ */
11
+ #models = [];
12
+
13
+ /**
14
+ * Active AI Model
15
+ * @type {AiModel|null}
16
+ */
17
+ #activeModel = null;
18
+
19
+ constructor(init) {
20
+ this.init = {
21
+ logger: console,
22
+ timeout: 120,
23
+ maxInputTokens: init.maxInputTokens || 4000,
24
+ ...init,
25
+ };
26
+
27
+ GennadyRc.getDefaults().forEach((rc) => {
28
+ if (rc.isValid()) {
29
+ this.#models.push(...rc.getModels().map(model => new AiModel(model)));
30
+ }
31
+ });
32
+
33
+ if (!this.#models.length) {
34
+ this.#models.push(AiModel.getDefault());
35
+ }
36
+ }
37
+
38
+ get model() {
39
+ return this.#models[0]?.name;
40
+ }
41
+
42
+ get apiUrl() {
43
+ return this.#models[0]?.url
44
+ }
45
+
46
+ get maxInputTokens() {
47
+ return this.init.maxInputTokens;
48
+ }
49
+
50
+ get logger() {
51
+ return this.init.logger;
52
+ }
53
+
54
+ createPromptsBatchesByDiff(parseDiff) {
55
+ const maxChunkTokens = parseDiff.at(-1)?.tokens || 0
56
+
57
+ if (maxChunkTokens > this.maxInputTokens) {
58
+ this.logger.error(`TODO: Diff is too large`);
59
+ process.exit(1);
60
+ }
61
+
62
+ const batches = parseDiff.reduce((acc, file) => {
63
+ if (!acc[0] || acc[0].tokens + file.tokens > this.maxInputTokens) {
64
+ acc.unshift({tokens: 0, diff: '', languages: []});
65
+ }
66
+
67
+ const fileDiff = file.diff.hunks.flatMap(h => h.changes).join('\n');
68
+ if (fileDiff.trim()) {
69
+ acc[0].tokens += file.tokens;
70
+ acc[0].diff += `### File **${file.filename}**:\n${fileDiff}\n\n`;
71
+
72
+ if (!acc[0].languages.includes(file.programmingLanguage)) {
73
+ acc[0].languages.push(file.programmingLanguage);
74
+ }
75
+ }
76
+
77
+ return acc;
78
+ }, []);
79
+
80
+ return batches;
81
+ }
82
+
83
+ async generate(prompt, context) {
84
+ try {
85
+ const model = await unguardOrThrow(this.#choiceModel());
86
+ const result = await unguardOrThrow(model.generate(prompt, context));
87
+ return result;
88
+ } catch (error) {
89
+ this.logger.error(`[AI_CORE_ERROR_GENERATE] Failed to generate LLM response:`, error);
90
+ return '';
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Choose active model
96
+ * @anchor AI_CORE_CHOICE_MODEL
97
+ * @returns {Promise<[AiModel, null] | [null, Error]>}
98
+ */
99
+ async #choiceModel() {
100
+ if (this.#activeModel) {
101
+ return [this.#activeModel, null];
102
+ }
103
+
104
+ for (const model of this.#models) {
105
+ const [ok, error] = await model.ping();
106
+ if (ok) {
107
+ this.#activeModel = model;
108
+ return [model, null];
109
+ } else {
110
+ this.logger.warn(`[AI_CORE_ERROR_PING_MODEL_FAIL] [${model.name}] Ping failed:`, error);
111
+ }
112
+ }
113
+
114
+ return [null, new Error(`[AI_CORE_ERROR_PING_FAIL] No available models`)];
115
+ }
116
+ }