gaunt-sloth-assistant 0.1.2 → 0.1.4

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.
@@ -4,14 +4,37 @@ import * as td from 'testdouble';
4
4
  describe('reviewCommand', function (){
5
5
 
6
6
  beforeEach(async function() {
7
+ td.reset();
7
8
  this.review = td.function();
8
9
  this.prompt = await td.replaceEsm("../src/prompt.js");
9
10
  td.when(this.prompt.readInternalPreamble()).thenReturn("INTERNAL PREAMBLE");
10
11
  td.when(this.prompt.readPreamble(".gsloth.preamble.review.md")).thenReturn("PROJECT PREAMBLE");
11
12
  this.codeReviewMock = await td.replaceEsm("../src/modules/reviewModule.js");
12
- await td.replaceEsm("../src/config.js");
13
- this.utils = await td.replaceEsm("../src/utils.js");
14
- td.when(this.utils.readFileFromCurrentDir("test.file")).thenReturn("FILE TO REVIEW");
13
+ await td.replaceEsm("../src/config.js", {
14
+ SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
15
+ USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
16
+ slothContext: {
17
+ config: {},
18
+ currentDir: '/mock/current/dir'
19
+ },
20
+ initConfig: td.function()
21
+ });
22
+ const readFileFromCurrentDir = td.function();
23
+ const readMultipleFilesFromCurrentDir = td.function();
24
+ const extractLastMessageContent = td.function();
25
+ const toFileSafeString = td.function();
26
+ const fileSafeLocalDate = td.function();
27
+ this.utilsMock = {
28
+ readFileFromCurrentDir,
29
+ readMultipleFilesFromCurrentDir,
30
+ ProgressIndicator: td.constructor(),
31
+ extractLastMessageContent,
32
+ toFileSafeString,
33
+ fileSafeLocalDate
34
+ };
35
+ await td.replaceEsm("../src/utils.js", this.utilsMock);
36
+ td.when(this.utilsMock.readFileFromCurrentDir("test.file")).thenReturn("FILE TO REVIEW");
37
+ td.when(this.utilsMock.readMultipleFilesFromCurrentDir(["test.file"])).thenReturn("test.file:\n```\nFILE TO REVIEW\n```");
15
38
  td.when(this.codeReviewMock.review(
16
39
  'sloth-DIFF-review',
17
40
  td.matchers.anything(),
@@ -21,7 +44,7 @@ describe('reviewCommand', function (){
21
44
 
22
45
  it('Should call review with file contents', async function() {
23
46
  const { reviewCommand } = await import("../src/commands/reviewCommand.js");
24
- const program = new Command()
47
+ const program = new Command();
25
48
  await reviewCommand(program, {});
26
49
  await program.parseAsync(['na', 'na', 'review', '-f', 'test.file']);
27
50
  td.verify(this.review(
@@ -31,6 +54,20 @@ describe('reviewCommand', function (){
31
54
  );
32
55
  });
33
56
 
57
+ it('Should call review with multiple file contents', async function() {
58
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
59
+ const program = new Command();
60
+ await reviewCommand(program, {});
61
+ td.when(this.utilsMock.readMultipleFilesFromCurrentDir(["test.file", "test2.file"]))
62
+ .thenReturn("test.file:\n```\nFILE TO REVIEW\n```\n\ntest2.file:\n```\nFILE2 TO REVIEW\n```");
63
+ await program.parseAsync(['na', 'na', 'review', '-f', 'test.file', 'test2.file']);
64
+ td.verify(this.review(
65
+ 'sloth-DIFF-review',
66
+ "INTERNAL PREAMBLE\nPROJECT PREAMBLE",
67
+ "test.file:\n```\nFILE TO REVIEW\n```\n\ntest2.file:\n```\nFILE2 TO REVIEW\n```")
68
+ );
69
+ });
70
+
34
71
  it('Should display predefined providers in help', async function() {
35
72
  const { reviewCommand } = await import("../src/commands/reviewCommand.js");
36
73
  const program = new Command();
@@ -43,17 +80,18 @@ describe('reviewCommand', function (){
43
80
 
44
81
  await reviewCommand(program, {});
45
82
 
46
- const commandUnderTest = program.commands.find(c => c.name() == 'review');
83
+ const commandUnderTest = program.commands.find(c => c.name() === 'review');
84
+
47
85
  expect(commandUnderTest).toBeDefined();
48
86
  commandUnderTest.outputHelp();
49
87
 
50
88
  // Verify content providers are displayed
51
89
  expect(testOutput.text).toContain('--content-provider <contentProvider>');
52
- expect(testOutput.text).toContain('(choices: "gh", "text")');
90
+ expect(testOutput.text).toContain('(choices: "gh", "text", "file")');
53
91
 
54
92
  // Verify requirements providers are displayed
55
93
  expect(testOutput.text).toContain('--requirements-provider <requirementsProvider>');
56
- expect(testOutput.text).toContain('(choices: "jira-legacy", "text")');
94
+ expect(testOutput.text).toContain('(choices: "jira-legacy", "text", "file")');
57
95
  });
58
96
 
59
97
  it('Should call review with predefined requirements provider', async function() {
@@ -87,6 +125,46 @@ describe('reviewCommand', function (){
87
125
  td.verify(this.review('sloth-DIFF-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "JIRA Requirements"));
88
126
  });
89
127
 
128
+ it('Should display meaningful error, when JIRA is enabled, but JIRA token is absent', async function() {
129
+ const testOutput = { text: '' };
130
+
131
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
132
+ const program = new Command();
133
+ program.configureOutput({
134
+ writeOut: (str) => testOutput.text += str,
135
+ writeErr: (str) => testOutput.text += str
136
+ });
137
+
138
+ const context = {
139
+ config: {
140
+ requirementsProvider: 'jira-legacy',
141
+ requirementsProviderConfig: {
142
+ 'jira-legacy': {
143
+ username: 'test-user',
144
+ baseUrl: 'https://test-jira.atlassian.net/rest/api/2/issue/'
145
+ }
146
+ }
147
+ }
148
+ };
149
+
150
+ // Mock the jira provider
151
+ const jiraProvider = td.func();
152
+ td.when(jiraProvider(td.matchers.anything(), 'JIRA-123')).thenResolve('JIRA Requirements');
153
+
154
+
155
+ await reviewCommand(program, context);
156
+ try {
157
+ await program.parseAsync(['na', 'na', 'pr', 'content-id', 'JIRA-123']);
158
+ } catch (e) {
159
+ expect(e.message)
160
+ .toContain(
161
+ 'Missing JIRA Legacy API token. ' +
162
+ 'The legacy token can be defined as JIRA_LEGACY_API_TOKEN environment variable ' +
163
+ 'or as "token" in config.'
164
+ );
165
+ }
166
+ });
167
+
90
168
  it('Should call review with predefined content provider', async function() {
91
169
  const { reviewCommand } = await import("../src/commands/reviewCommand.js");
92
170
  const program = new Command();
@@ -129,7 +207,7 @@ describe('reviewCommand', function (){
129
207
 
130
208
  // Mock the gh provider
131
209
  const ghProvider = td.func();
132
- td.when(ghProvider('123')).thenResolve('PR Diff Content');
210
+ td.when(ghProvider(td.matchers.anything(), '123')).thenResolve('PR Diff Content');
133
211
 
134
212
  // Replace the dynamic import with our mock
135
213
  await td.replaceEsm('../src/providers/ghPrDiffProvider.js', {
@@ -1,9 +1,14 @@
1
1
  import { reviewInner } from '../src/modules/reviewModule.js';
2
2
  import { slothContext } from '../src/config.js';
3
3
  import { FakeListChatModel } from "@langchain/core/utils/testing";
4
+ import * as td from "testdouble";
4
5
 
5
6
  describe('reviewModule', () => {
6
7
 
8
+ beforeEach(async function() {
9
+ td.reset();
10
+ });
11
+
7
12
  it('should invoke LLM', async () => {
8
13
  // Setup mock for slothContext
9
14
  const testContext = {...slothContext,
@@ -16,7 +21,8 @@ describe('reviewModule', () => {
16
21
 
17
22
  // Test the function
18
23
  const output = await reviewInner(testContext, () => {}, 'test-preamble', 'test-diff');
24
+
19
25
  expect(output).toBe("First LLM message");
20
26
  });
21
27
 
22
- });
28
+ });
@@ -1,5 +1,5 @@
1
1
  import { readInternalPreamble } from "../prompt.js";
2
- import { readFileFromCurrentDir } from "../utils.js";
2
+ import { readMultipleFilesFromCurrentDir } from "../utils.js";
3
3
  import { initConfig } from "../config.js";
4
4
 
5
5
  /**
@@ -7,20 +7,21 @@ import { initConfig } from "../config.js";
7
7
  * @param {Object} program - The commander program
8
8
  * @param {Object} context - The context object
9
9
  */
10
+ // eslint-disable-next-line no-unused-vars
10
11
  export function askCommand(program, context) {
11
12
  program.command('ask')
12
13
  .description('Ask a question')
13
14
  .argument('<message>', 'A message')
14
- .option('-f, --file <file>', 'Input file. Content of this file will be added BEFORE the diff')
15
+ .option('-f, --file [files...]', 'Input files. Content of these files will be added BEFORE the message')
15
16
  // TODO add option consuming extra message as argument
16
17
  .action(async (message, options) => {
17
18
  const preamble = [readInternalPreamble()];
18
19
  const content = [message];
19
20
  if (options.file) {
20
- content.push(readFileFromCurrentDir(options.file));
21
+ content.push(readMultipleFilesFromCurrentDir(options.file));
21
22
  }
22
23
  await initConfig();
23
24
  const { askQuestion } = await import('../modules/questionAnsweringModule.js');
24
25
  await askQuestion('sloth-ASK', preamble.join("\n"), content.join("\n"));
25
26
  });
26
- }
27
+ }
@@ -6,6 +6,7 @@ import { availableDefaultConfigs, createProjectConfig } from "../config.js";
6
6
  * @param {Object} program - The commander program
7
7
  * @param {Object} context - The context object
8
8
  */
9
+ // eslint-disable-next-line no-unused-vars
9
10
  export function initCommand(program, context) {
10
11
  program.command('init')
11
12
  .description('Initialize the Gaunt Sloth Assistant in your project. This will write necessary config files.')
@@ -13,4 +14,4 @@ export function initCommand(program, context) {
13
14
  .action(async (config) => {
14
15
  await createProjectConfig(config);
15
16
  });
16
- }
17
+ }
@@ -1,7 +1,7 @@
1
1
  import {Option} from 'commander';
2
2
  import {USER_PROJECT_REVIEW_PREAMBLE} from "../config.js";
3
3
  import {readInternalPreamble, readPreamble} from "../prompt.js";
4
- import {readFileFromCurrentDir} from "../utils.js";
4
+ import {readMultipleFilesFromCurrentDir} from "../utils.js";
5
5
  import {displayError} from "../consoleUtils.js";
6
6
 
7
7
  /**
@@ -9,7 +9,8 @@ import {displayError} from "../consoleUtils.js";
9
9
  */
10
10
  const REQUIREMENTS_PROVIDERS = {
11
11
  'jira-legacy': 'jiraIssueLegacyAccessTokenProvider.js',
12
- 'text': 'text.js'
12
+ 'text': 'text.js',
13
+ 'file': 'file.js'
13
14
  };
14
15
 
15
16
  /**
@@ -17,7 +18,8 @@ const REQUIREMENTS_PROVIDERS = {
17
18
  */
18
19
  const CONTENT_PROVIDERS = {
19
20
  'gh': 'ghPrDiffProvider.js',
20
- 'text': 'text.js'
21
+ 'text': 'text.js',
22
+ 'file': 'file.js'
21
23
  };
22
24
 
23
25
  export function reviewCommand(program, context) {
@@ -27,8 +29,7 @@ export function reviewCommand(program, context) {
27
29
  .argument('[contentId]', 'Optional content ID argument to retrieve content with content provider')
28
30
  .alias('r')
29
31
  // TODO add provider to get results of git --no-pager diff
30
- // TODO add support to include multiple files
31
- .option('-f, --file <file>', 'Input file. Content of this file will be added BEFORE the diff, but after requirements')
32
+ .option('-f, --file [files...]', 'Input files. Content of these files will be added BEFORE the diff, but after requirements')
32
33
  // TODO figure out what to do with this (we probably want to merge it with requirementsId)?
33
34
  .option('-r, --requirements <requirements>', 'Requirements for this review.')
34
35
  .addOption(
@@ -46,8 +47,12 @@ export function reviewCommand(program, context) {
46
47
  const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
47
48
  const content = [];
48
49
  const requirementsId = options.requirements;
49
- const requirementsProvider = options.requirementsProvider ?? context.config?.requirementsProvider;
50
- const contentProvider = options.contentProvider ?? context.config?.contentProvider;
50
+ const requirementsProvider = options.requirementsProvider
51
+ ?? context.config?.review?.requirementsProvider
52
+ ?? context.config?.requirementsProvider;
53
+ const contentProvider = options.contentProvider
54
+ ?? context.config?.review?.contentProvider
55
+ ?? context.config?.contentProvider;
51
56
 
52
57
  // TODO consider calling these in parallel
53
58
  const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
@@ -61,7 +66,7 @@ export function reviewCommand(program, context) {
61
66
  }
62
67
 
63
68
  if (options.file) {
64
- content.push(`${options.file}:\n\`\`\`\n${readFileFromCurrentDir(options.file)}\n\`\`\``);
69
+ content.push(readMultipleFilesFromCurrentDir(options.file));
65
70
  }
66
71
  if (context.stdin) {
67
72
  content.push(context.stdin);
@@ -83,14 +88,16 @@ export function reviewCommand(program, context) {
83
88
  new Option('-p, --requirements-provider <requirementsProvider>', 'Requirements provider for this review.')
84
89
  .choices(Object.keys(REQUIREMENTS_PROVIDERS))
85
90
  )
86
- .option('-f, --file <file>', 'Input file. Content of this file will be added BEFORE the diff, but after requirements')
91
+ .option('-f, --file [files...]', 'Input files. Content of these files will be added BEFORE the diff, but after requirements')
87
92
  .action(async (prId, requirementsId, options) => {
88
93
  const {initConfig} = await import("../config.js");
89
94
  await initConfig();
90
95
 
91
96
  const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
92
97
  const content = [];
93
- const requirementsProvider = options.requirementsProvider ?? context.config?.requirementsProvider;
98
+ const requirementsProvider = options.requirementsProvider
99
+ ?? context.config?.pr?.requirementsProvider
100
+ ?? context.config?.requirementsProvider;
94
101
 
95
102
  // Handle requirements
96
103
  const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
@@ -99,7 +106,7 @@ export function reviewCommand(program, context) {
99
106
  }
100
107
 
101
108
  if (options.file) {
102
- content.push(`${options.file}:\n\`\`\`\n${readFileFromCurrentDir(options.file)}\n\`\`\``);
109
+ content.push(readMultipleFilesFromCurrentDir(options.file));
103
110
  }
104
111
 
105
112
  // Get PR diff using the 'gh' provider
@@ -126,7 +133,7 @@ export function reviewCommand(program, context) {
126
133
  contentId,
127
134
  (context.config?.contentProviderConfig ?? {})[contentProvider],
128
135
  CONTENT_PROVIDERS
129
- )
136
+ );
130
137
  }
131
138
 
132
139
  async function getFromProvider(provider, id, config, legitPredefinedProviders) {
package/src/config.js CHANGED
@@ -1,15 +1,29 @@
1
- import path from "node:path";
2
- import url from "node:url";
3
- import { v4 as uuidv4 } from "uuid";
4
- import { displayError, displayInfo, displayWarning } from "./consoleUtils.js";
5
- import { writeFileIfNotExistsWithMessages } from "./utils.js";
1
+ import path from "node:path/posix";
2
+ import {v4 as uuidv4} from "uuid";
3
+ import {displayDebug, displayError, displayInfo, displayWarning} from "./consoleUtils.js";
4
+ import {importExternalFile, writeFileIfNotExistsWithMessages} from "./utils.js";
5
+ import {existsSync, readFileSync} from "node:fs";
6
+ import {exit, getCurrentDir} from "./systemUtils.js";
6
7
 
7
- export const USER_PROJECT_CONFIG_FILE = '.gsloth.config.js'
8
+ export const USER_PROJECT_CONFIG_JS = '.gsloth.config.js';
9
+ export const USER_PROJECT_CONFIG_JSON = '.gsloth.config.json';
10
+ export const USER_PROJECT_CONFIG_MJS = '.gsloth.config.mjs';
8
11
  export const SLOTH_INTERNAL_PREAMBLE = '.gsloth.preamble.internal.md';
9
12
  export const USER_PROJECT_REVIEW_PREAMBLE = '.gsloth.preamble.review.md';
10
13
 
11
14
  export const availableDefaultConfigs = ['vertexai', 'anthropic', 'groq'];
12
15
 
16
+ export const DEFAULT_CONFIG = {
17
+ llm: undefined,
18
+ contentProvider: "file",
19
+ requirementsProvider: "file",
20
+ commands: {
21
+ pr: {
22
+ contentProvider: "gh"
23
+ }
24
+ }
25
+ };
26
+
13
27
  export const slothContext = {
14
28
  /**
15
29
  * Directory where the sloth is installed.
@@ -21,43 +35,143 @@ export const slothContext = {
21
35
  * index.js should set up this.
22
36
  */
23
37
  currentDir: null,
24
- config: null,
38
+ config: DEFAULT_CONFIG,
25
39
  stdin: '',
26
40
  session: {configurable: {thread_id: uuidv4()}}
27
41
  };
28
42
 
29
43
  export async function initConfig() {
30
- const configFileUrl = url.pathToFileURL(path.join(process.cwd(), USER_PROJECT_CONFIG_FILE));
31
- return import(configFileUrl)
32
- .then((i) => i.configure((module) => import(module)))
33
- .then((config) => {
34
- slothContext.config = {...config};
35
- })
36
- .catch((e) => {
37
- console.log(e);
38
- displayError(`Failed to read config, make sure ${configFileUrl} contains valid JavaScript.`);
39
- process.exit();
40
- });
44
+ const currentDir = getCurrentDir();
45
+ const jsonConfigPath = path.join(currentDir, USER_PROJECT_CONFIG_JSON);
46
+ const jsConfigPath = path.join(currentDir, USER_PROJECT_CONFIG_JS);
47
+ const mjsConfigPath = path.join(currentDir, USER_PROJECT_CONFIG_MJS);
48
+
49
+ // Try loading JSON config file first
50
+ if (existsSync(jsonConfigPath)) {
51
+ try {
52
+ const jsonConfig = JSON.parse(readFileSync(jsonConfigPath, 'utf8'));
53
+
54
+ // If the config has an LLM with a type, create the appropriate LLM instance
55
+ if (jsonConfig.llm && jsonConfig.llm.type) {
56
+ await tryJsonConfig(jsonConfig);
57
+ } else {
58
+ slothContext.config = {...slothContext.config, ...jsonConfig};
59
+ }
60
+ } catch (e) {
61
+ displayDebug(e);
62
+ displayError(`Failed to read config from ${USER_PROJECT_CONFIG_JSON}, will try other formats.`);
63
+ // Continue to try other formats
64
+ return tryJsConfig();
65
+ }
66
+ } else {
67
+ // JSON config not found, try JS
68
+ return tryJsConfig();
69
+ }
70
+
71
+ // Helper function to try loading JS config
72
+ async function tryJsConfig() {
73
+ if (existsSync(jsConfigPath)) {
74
+ return importExternalFile(jsConfigPath)
75
+ .then((i) => i.configure((module) => import(module)))
76
+ .then((config) => {
77
+ slothContext.config = {...slothContext.config, ...config};
78
+ })
79
+ .catch((e) => {
80
+ displayDebug(e);
81
+ displayError(`Failed to read config from ${USER_PROJECT_CONFIG_JS}, will try other formats.`);
82
+ // Continue to try other formats
83
+ return tryMjsConfig();
84
+ });
85
+ } else {
86
+ // JS config not found, try MJS
87
+ return tryMjsConfig();
88
+ }
89
+ }
90
+
91
+ // Helper function to try loading MJS config
92
+ async function tryMjsConfig() {
93
+ if (existsSync(mjsConfigPath)) {
94
+ return importExternalFile(mjsConfigPath)
95
+ .then((i) => i.configure((module) => import(module)))
96
+ .then((config) => {
97
+ slothContext.config = {...slothContext.config, ...config};
98
+ })
99
+ .catch((e) => {
100
+ displayDebug(e);
101
+ displayError(`Failed to read config from ${USER_PROJECT_CONFIG_MJS}.`);
102
+ displayError(`No valid configuration found. Please create a valid configuration file.`);
103
+ exit();
104
+ });
105
+ } else {
106
+ // No config files found
107
+ displayError(
108
+ 'No configuration file found. Please create one of: '
109
+ + `${USER_PROJECT_CONFIG_JSON}, ${USER_PROJECT_CONFIG_JS}, or ${USER_PROJECT_CONFIG_MJS} `
110
+ + 'in your project directory.'
111
+ );
112
+ exit();
113
+ }
114
+ }
115
+ }
116
+
117
+ // Process JSON LLM config by creating the appropriate LLM instance
118
+ export async function tryJsonConfig(jsonConfig) {
119
+ const llmConfig = jsonConfig.llm;
120
+ const llmType = llmConfig.type.toLowerCase();
121
+
122
+ // Check if the LLM type is in availableDefaultConfigs
123
+ if (!availableDefaultConfigs.includes(llmType)) {
124
+ displayError(`Unsupported LLM type: ${llmType}. Available types are: ${availableDefaultConfigs.join(', ')}`);
125
+ slothContext.config = {...slothContext.config, ...jsonConfig};
126
+ return;
127
+ }
128
+
129
+ try {
130
+ // Import the appropriate config module based on the LLM type
131
+ try {
132
+ const configModule = await import(`./configs/${llmType}.js`);
133
+ if (configModule.processJsonConfig) {
134
+ jsonConfig.llm = await configModule.processJsonConfig(llmConfig);
135
+ } else {
136
+ displayWarning(`Config module for ${llmType} does not have processJsonConfig function.`);
137
+ }
138
+ } catch (importError) {
139
+ displayDebug(importError);
140
+ displayWarning(`Could not import config module for ${llmType}.`);
141
+ }
142
+ } catch (error) {
143
+ displayDebug(error);
144
+ displayError(`Error creating LLM instance for type ${llmType}.`);
145
+ }
146
+
147
+ slothContext.config = {...slothContext.config, ...jsonConfig};
41
148
  }
42
149
 
43
150
  export async function createProjectConfig(configType) {
44
151
  displayInfo(`Setting up your project\n`);
45
152
  writeProjectReviewPreamble();
46
- displayWarning(`Make sure you add as much detail as possible to your ${USER_PROJECT_REVIEW_PREAMBLE}.\n`)
153
+ displayWarning(`Make sure you add as much detail as possible to your ${USER_PROJECT_REVIEW_PREAMBLE}.\n`);
154
+
155
+ // Check if the config type is in availableDefaultConfigs
156
+ if (!availableDefaultConfigs.includes(configType)) {
157
+ displayError(`Unsupported config type: ${configType}. Available types are: ${availableDefaultConfigs.join(', ')}`);
158
+ exit(1);
159
+ return;
160
+ }
47
161
 
48
162
  displayInfo(`Creating project config for ${configType}`);
49
163
  const vendorConfig = await import(`./configs/${configType}.js`);
50
- vendorConfig.init(USER_PROJECT_CONFIG_FILE, slothContext);
164
+ vendorConfig.init(USER_PROJECT_CONFIG_JSON, slothContext);
51
165
  }
52
166
 
53
- function writeProjectReviewPreamble() {
167
+ export function writeProjectReviewPreamble() {
54
168
  let reviewPreamblePath = path.join(slothContext.currentDir, USER_PROJECT_REVIEW_PREAMBLE);
55
169
  writeFileIfNotExistsWithMessages(
56
170
  reviewPreamblePath,
57
171
  'You are doing generic code review.\n'
58
- + 'Important! Please remind user to prepare proper AI preamble in ' +
172
+ + ' Important! Please remind user to prepare proper AI preamble in'
59
173
  + USER_PROJECT_REVIEW_PREAMBLE
60
- + 'for this project. Use decent amount of ⚠️ to highlight lack of config. '
61
- + 'Explicitly mention `'+ USER_PROJECT_REVIEW_PREAMBLE + '`.'
174
+ + ' for this project. Use decent amount of ⚠️ to highlight lack of config.'
175
+ + ' Explicitly mention `' + USER_PROJECT_REVIEW_PREAMBLE + '`.'
62
176
  );
63
- }
177
+ }
@@ -1,9 +1,20 @@
1
1
  import {writeFileIfNotExistsWithMessages} from "../utils.js";
2
2
  import path from "node:path";
3
3
  import {displayWarning} from "../consoleUtils.js";
4
- import {USER_PROJECT_CONFIG_FILE} from "../config.js";
4
+ import { env } from "../systemUtils.js";
5
5
 
6
- const content = `/* eslint-disable */
6
+ // Function to process JSON config and create Anthropic LLM instance
7
+ export async function processJsonConfig(llmConfig) {
8
+ const anthropic = await import('@langchain/anthropic');
9
+ // Use environment variable if available, otherwise use the config value
10
+ const anthropicApiKey = env.ANTHROPIC_API_KEY || llmConfig.apiKey;
11
+ return new anthropic.ChatAnthropic({
12
+ apiKey: anthropicApiKey,
13
+ model: llmConfig.model || "claude-3-7-sonnet-20250219"
14
+ });
15
+ }
16
+
17
+ const jsContent = `/* eslint-disable */
7
18
  export async function configure(importFunction, global) {
8
19
  // this is going to be imported from sloth dependencies,
9
20
  // but can potentially be pulled from global node modules or from this project
@@ -12,14 +23,26 @@ export async function configure(importFunction, global) {
12
23
  return {
13
24
  llm: new anthropic.ChatAnthropic({
14
25
  apiKey: process.env.ANTHROPIC_API_KEY, // Default value, but you can provide the key in many different ways, even as literal
15
- model: "claude-3-5-sonnet-20241022" // Don't forget to check new models availability.
26
+ model: "claude-3-7-sonnet-20250219" // Don't forget to check new models availability.
16
27
  })
17
28
  };
18
29
  }
19
30
  `;
20
31
 
32
+ const jsonContent = `{
33
+ "llm": {
34
+ "type": "anthropic",
35
+ "apiKey": "your-api-key-here",
36
+ "model": "claude-3-7-sonnet-20250219"
37
+ }
38
+ }`;
39
+
21
40
  export function init(configFileName, context) {
22
41
  path.join(context.currentDir, configFileName);
42
+
43
+ // Determine which content to use based on file extension
44
+ const content = configFileName.endsWith('.json') ? jsonContent : jsContent;
45
+
23
46
  writeFileIfNotExistsWithMessages(configFileName, content);
24
- displayWarning(`You need to update your ${USER_PROJECT_CONFIG_FILE} to add your Anthropic API key.`);
25
- }
47
+ displayWarning(`You need to update your ${configFileName} to add your Anthropic API key.`);
48
+ }
@@ -0,0 +1,15 @@
1
+ import {displayWarning} from "../consoleUtils.js";
2
+
3
+ // Function to process JSON config and create Fake LLM instance for testing
4
+ export async function processJsonConfig(llmConfig) {
5
+ if (llmConfig.responses) {
6
+ const test = await import('@langchain/core/utils/testing');
7
+ return new test.FakeListChatModel({
8
+ responses: llmConfig.responses
9
+ });
10
+ }
11
+ displayWarning("Fake LLM requires 'responses' array in config");
12
+ return null;
13
+ }
14
+
15
+ // No init function needed for fake LLM as it's only used for testing
@@ -1,9 +1,20 @@
1
1
  import {writeFileIfNotExistsWithMessages} from "../utils.js";
2
2
  import path from "node:path";
3
3
  import {displayInfo, displayWarning} from "../consoleUtils.js";
4
- import {USER_PROJECT_CONFIG_FILE} from "../config.js";
4
+ import {env} from "../systemUtils.js";
5
5
 
6
- const content = `/* eslint-disable */
6
+ // Function to process JSON config and create Groq LLM instance
7
+ export async function processJsonConfig(llmConfig) {
8
+ const groq = await import('@langchain/groq');
9
+ // Use environment variable if available, otherwise use the config value
10
+ const groqApiKey = env.GROQ_API_KEY || llmConfig.apiKey;
11
+ return new groq.ChatGroq({
12
+ apiKey: groqApiKey,
13
+ model: llmConfig.model || "deepseek-r1-distill-llama-70b"
14
+ });
15
+ }
16
+
17
+ const jsContent = `/* eslint-disable */
7
18
  export async function configure(importFunction, global) {
8
19
  // this is going to be imported from sloth dependencies,
9
20
  // but can potentially be pulled from global node modules or from this project
@@ -17,9 +28,21 @@ export async function configure(importFunction, global) {
17
28
  }
18
29
  `;
19
30
 
31
+ const jsonContent = `{
32
+ "llm": {
33
+ "type": "groq",
34
+ "model": "deepseek-r1-distill-llama-70b",
35
+ "apiKey": "your-api-key-here"
36
+ }
37
+ }`;
38
+
20
39
  export function init(configFileName, context) {
21
40
  path.join(context.currentDir, configFileName);
41
+
42
+ // Determine which content to use based on file extension
43
+ const content = configFileName.endsWith('.json') ? jsonContent : jsContent;
44
+
22
45
  writeFileIfNotExistsWithMessages(configFileName, content);
23
46
  displayInfo(`You can define GROQ_API_KEY environment variable with your Groq API key and it will work with default model.`);
24
- displayWarning(`You need to edit your ${USER_PROJECT_CONFIG_FILE} to to configure model.`);
25
- }
47
+ displayWarning(`You need to edit your ${configFileName} to configure model.`);
48
+ }