gaunt-sloth-assistant 0.7.0 → 0.7.2

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 (43) hide show
  1. package/.gsloth.config_.json +7 -0
  2. package/.gsloth.config_.mjs +15 -0
  3. package/README.md +162 -153
  4. package/assets/release-notes/v0_7_2.md +14 -0
  5. package/dist/config.d.ts +1 -1
  6. package/dist/config.js +3 -3
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/Invocation.d.ts +3 -2
  9. package/dist/core/Invocation.js +36 -31
  10. package/dist/core/Invocation.js.map +1 -1
  11. package/dist/core/types.d.ts +1 -0
  12. package/dist/{configs → presets}/anthropic.js +2 -1
  13. package/dist/{configs → presets}/anthropic.js.map +1 -1
  14. package/dist/presets/deepseek.d.ts +4 -0
  15. package/dist/presets/deepseek.js +33 -0
  16. package/dist/presets/deepseek.js.map +1 -0
  17. package/dist/{configs → presets}/fake.js.map +1 -1
  18. package/dist/{configs → presets}/groq.js +4 -5
  19. package/dist/presets/groq.js.map +1 -0
  20. package/dist/{configs → presets}/vertexai.js +2 -3
  21. package/dist/presets/vertexai.js.map +1 -0
  22. package/dist/tools/GthFileSystemToolkit.js +1 -1
  23. package/dist/tools/GthFileSystemToolkit.js.map +1 -1
  24. package/docs/CONFIGURATION.md +468 -437
  25. package/it.js +15 -0
  26. package/package.json +13 -14
  27. package/src/config.ts +3 -3
  28. package/src/core/Invocation.ts +43 -38
  29. package/src/core/types.ts +1 -0
  30. package/src/{configs → presets}/anthropic.ts +4 -1
  31. package/src/presets/deepseek.ts +46 -0
  32. package/src/{configs → presets}/groq.ts +5 -6
  33. package/src/{configs → presets}/vertexai.ts +2 -3
  34. package/src/tools/GthFileSystemToolkit.ts +1 -1
  35. package/vitest-it.config.ts +1 -1
  36. package/dist/configs/groq.js.map +0 -1
  37. package/dist/configs/vertexai.js.map +0 -1
  38. /package/dist/{configs → presets}/anthropic.d.ts +0 -0
  39. /package/dist/{configs → presets}/fake.d.ts +0 -0
  40. /package/dist/{configs → presets}/fake.js +0 -0
  41. /package/dist/{configs → presets}/groq.d.ts +0 -0
  42. /package/dist/{configs → presets}/vertexai.d.ts +0 -0
  43. /package/src/{configs → presets}/fake.ts +0 -0
package/it.js ADDED
@@ -0,0 +1,15 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ // Script to launch integration tests
4
+
5
+ execSync('node integration-tests/setup-config.js ' + process.argv[2], {
6
+ stdio: [process.stdin, process.stdout, process.stderr],
7
+ });
8
+ try {
9
+ const test = process.argv[3] ? ` ${process.argv[3]}` : '';
10
+ execSync('vitest run --config vitest-it.config.ts' + test, {
11
+ stdio: [process.stdin, process.stdout, process.stderr],
12
+ });
13
+ } catch {
14
+ process.exit(1);
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaunt-sloth-assistant",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "author": "Andrew Kondratev",
@@ -14,9 +14,7 @@
14
14
  "scripts": {
15
15
  "build": "tsc",
16
16
  "test": "npm run build && vitest run",
17
- "it-groq": "node integration-tests/setup-config.js groq && npm run build && vitest run --config vitest-it.config.ts",
18
- "it-anthropic": "node integration-tests/setup-config.js anthropic && npm run build && vitest run --config vitest-it.config.ts",
19
- "it-vertexai": "node integration-tests/setup-config.js vertexai && npm run build && vitest run --config vitest-it.config.ts",
17
+ "it": "npm run build && node it.js",
20
18
  "lint": "eslint . --ext .js,.ts",
21
19
  "format": "prettier --write 'src/**/*.{js,ts}'",
22
20
  "prepare": "npm run build"
@@ -26,11 +24,12 @@
26
24
  "gth": "index.js"
27
25
  },
28
26
  "dependencies": {
29
- "@langchain/anthropic": "^0.3.22",
30
- "@langchain/core": "^0.3.58",
31
- "@langchain/google-vertexai": "^0.2.10",
27
+ "@langchain/anthropic": "^0.3.23",
28
+ "@langchain/core": "^0.3.61",
29
+ "@langchain/deepseek": "^0.0.2",
30
+ "@langchain/google-vertexai": "^0.2.14",
32
31
  "@langchain/groq": "^0.2.3",
33
- "@langchain/langgraph": "^0.3.1",
32
+ "@langchain/langgraph": "^0.3.5",
34
33
  "@langchain/mcp-adapters": "^0.5.2",
35
34
  "chalk": "^5.4.1",
36
35
  "commander": "^14.0.0",
@@ -40,17 +39,17 @@
40
39
  "devDependencies": {
41
40
  "@eslint/js": "^9.26.0",
42
41
  "@types/diff": "^7.0.2",
43
- "@types/node": "^24.0.2",
42
+ "@types/node": "^24.0.7",
44
43
  "@types/uuid": "^10.0.0",
45
- "@typescript-eslint/eslint-plugin": "^8.34.0",
46
- "@typescript-eslint/parser": "^8.34.0",
47
- "eslint": "^9.28.0",
44
+ "@typescript-eslint/eslint-plugin": "^8.35.0",
45
+ "@typescript-eslint/parser": "^8.35.0",
46
+ "eslint": "^9.30.0",
48
47
  "eslint-config-prettier": "^10.1.5",
49
- "eslint-plugin-prettier": "^5.4.1",
48
+ "eslint-plugin-prettier": "^5.5.1",
50
49
  "globals": "^16.2.0",
51
50
  "prettier": "3.6.0",
52
51
  "typescript": "^5.8.3",
53
- "vitest": "^3.2.3"
52
+ "vitest": "^3.2.4"
54
53
  },
55
54
  "imports": {
56
55
  "#src/*.js": "./dist/*.js"
package/src/config.ts CHANGED
@@ -76,7 +76,7 @@ export interface LLMConfig extends Record<string, unknown> {
76
76
  model: string;
77
77
  }
78
78
 
79
- export const availableDefaultConfigs = ['vertexai', 'anthropic', 'groq'] as const;
79
+ export const availableDefaultConfigs = ['vertexai', 'anthropic', 'groq', 'deepseek'] as const;
80
80
  export type ConfigType = (typeof availableDefaultConfigs)[number];
81
81
 
82
82
  export const DEFAULT_CONFIG: Partial<SlothConfig> = {
@@ -199,7 +199,7 @@ export async function tryJsonConfig(jsonConfig: RawSlothConfig): Promise<SlothCo
199
199
  // Get the configuration for the specific LLM type
200
200
  const llmConfig = jsonConfig.llm;
201
201
  // Import the appropriate config module
202
- const configModule = await import(`./configs/${llmType}.js`);
202
+ const configModule = await import(`./presets/${llmType}.js`);
203
203
  if (configModule.processJsonConfig) {
204
204
  const llm = (await configModule.processJsonConfig(llmConfig)) as BaseChatModel;
205
205
  return mergeRawConfig(jsonConfig, llm);
@@ -237,7 +237,7 @@ export async function createProjectConfig(configType: string): Promise<void> {
237
237
  displayWarning(`Make sure you add as much detail as possible to your ${PROJECT_GUIDELINES}.\n`);
238
238
 
239
239
  displayInfo(`Creating project config for ${configType}`);
240
- const vendorConfig = await import(`./configs/${configType}.js`);
240
+ const vendorConfig = await import(`./presets/${configType}.js`);
241
241
  vendorConfig.init(getGslothConfigWritePath(USER_PROJECT_CONFIG_JSON));
242
242
  }
243
243
 
@@ -1,16 +1,14 @@
1
1
  import type { Message } from '#src/modules/types.js';
2
2
  import { AIMessage, isAIMessage } from '@langchain/core/messages';
3
- import { SlothConfig } from '#src/config.js';
3
+ import { getDefaultTools, SlothConfig } from '#src/config.js';
4
4
  import type { Connection } from '@langchain/mcp-adapters';
5
5
  import { MultiServerMCPClient } from '@langchain/mcp-adapters';
6
6
  import { createReactAgent } from '@langchain/langgraph/prebuilt';
7
7
  import { BaseCheckpointSaver, CompiledStateGraph } from '@langchain/langgraph';
8
- import { ProgressIndicator } from '#src/utils.js';
8
+ import { formatToolCalls, ProgressIndicator } from '#src/utils.js';
9
9
  import { type RunnableConfig } from '@langchain/core/runnables';
10
10
  import { ToolCall } from '@langchain/core/messages/tool';
11
- import { StatusLevel } from '#src/core/types.js';
12
- import { formatToolCalls } from '#src/utils.js';
13
- import { getDefaultTools } from '#src/config.js';
11
+ import { GthCommand, StatusLevel } from '#src/core/types.js';
14
12
  import { BaseToolkit, StructuredToolInterface } from '@langchain/core/tools';
15
13
 
16
14
  export type StatusUpdateCallback = (level: StatusLevel, message: string) => void;
@@ -32,7 +30,7 @@ export class Invocation {
32
30
  }
33
31
 
34
32
  async init(
35
- command: 'ask' | 'pr' | 'review' | 'chat' | 'code' | undefined,
33
+ command: GthCommand | undefined,
36
34
  config: SlothConfig,
37
35
  checkpointSaver?: BaseCheckpointSaver | undefined
38
36
  ): Promise<void> {
@@ -41,23 +39,14 @@ export class Invocation {
41
39
  }
42
40
 
43
41
  // Merge command-specific filesystem config if provided
44
- let effectiveConfig = config;
45
- if (command && config.commands?.[command]?.filesystem !== undefined) {
46
- effectiveConfig = {
47
- ...config,
48
- filesystem: config.commands[command].filesystem!,
49
- };
50
- }
51
-
52
- this.config = effectiveConfig;
53
- this.mcpClient = this.getMcpClient(effectiveConfig);
42
+ this.config = this.getEffectiveConfig(config, command);
43
+ this.mcpClient = this.getMcpClient(this.config);
54
44
 
55
45
  // Get default filesystem tools (filtered based on config)
56
- const defaultTools = getDefaultTools(effectiveConfig.filesystem || 'none');
46
+ const defaultTools = getDefaultTools(this.config.filesystem || 'none');
57
47
 
58
48
  // Get user config tools
59
- const configTools = effectiveConfig.tools || [];
60
- const flattenedConfigTools = this.extractAndFlattenTools(configTools);
49
+ const flattenedConfigTools = this.extractAndFlattenTools(this.config.tools || []);
61
50
 
62
51
  // Get MCP tools
63
52
  const mcpTools = (await this.mcpClient?.getTools()) ?? [];
@@ -75,12 +64,26 @@ export class Invocation {
75
64
 
76
65
  // Create the React agent
77
66
  this.agent = createReactAgent({
78
- llm: config.llm,
67
+ llm: this.config.llm,
79
68
  tools,
80
69
  checkpointSaver,
81
70
  });
82
71
  }
83
72
 
73
+ getEffectiveConfig(config: SlothConfig, command: GthCommand | undefined): SlothConfig {
74
+ const supportsTools = !!config.llm.bindTools;
75
+ if (!supportsTools) {
76
+ this.statusUpdate('warning', 'Model does not seem to support tools.');
77
+ }
78
+ return {
79
+ ...config,
80
+ filesystem:
81
+ command && config.commands?.[command]?.filesystem !== undefined
82
+ ? config.commands[command].filesystem!
83
+ : config.filesystem,
84
+ };
85
+ }
86
+
84
87
  async invoke(messages: Message[], runConfig?: RunnableConfig): Promise<string> {
85
88
  if (!this.agent || !this.config) {
86
89
  throw new Error('Invocation not initialized. Call init() first.');
@@ -89,7 +92,26 @@ export class Invocation {
89
92
  // Run the agent
90
93
  try {
91
94
  const output = { aiMessage: '' };
92
- if (!this.config.streamOutput) {
95
+ if (this.config.streamOutput) {
96
+ // Streaming output
97
+ this.statusUpdate('info', '\nThinking...\n');
98
+ const stream = await this.agent.stream(
99
+ { messages },
100
+ { ...runConfig, streamMode: 'messages' }
101
+ );
102
+
103
+ for await (const [chunk, _metadata] of stream) {
104
+ if (isAIMessage(chunk)) {
105
+ this.statusUpdate('stream', chunk.text as string);
106
+ output.aiMessage += chunk.text;
107
+ const toolCalls = chunk.tool_calls?.filter((tc) => tc.name);
108
+ if (toolCalls && toolCalls.length > 0) {
109
+ this.statusUpdate('info', `\nUsed tools: ${formatToolCalls(toolCalls)}`);
110
+ }
111
+ }
112
+ }
113
+ } else {
114
+ // Not streaming output
93
115
  const progress = new ProgressIndicator('Thinking.');
94
116
  try {
95
117
  const response = await this.agent.invoke({ messages }, runConfig);
@@ -107,23 +129,6 @@ export class Invocation {
107
129
  progress.stop();
108
130
  }
109
131
  this.statusUpdate('display', output.aiMessage);
110
- } else {
111
- this.statusUpdate('info', '\nThinking...\n');
112
- const stream = await this.agent.stream(
113
- { messages },
114
- { ...runConfig, streamMode: 'messages' }
115
- );
116
-
117
- for await (const [chunk, _metadata] of stream) {
118
- if (isAIMessage(chunk)) {
119
- this.statusUpdate('stream', chunk.text as string);
120
- output.aiMessage += chunk.text;
121
- const toolCalls = chunk.tool_calls?.filter((tc) => tc.name);
122
- if (toolCalls && toolCalls.length > 0) {
123
- this.statusUpdate('info', `Used tools: ${formatToolCalls(toolCalls)}`);
124
- }
125
- }
126
- }
127
132
  }
128
133
 
129
134
  return output.aiMessage;
package/src/core/types.ts CHANGED
@@ -1 +1,2 @@
1
1
  export type StatusLevel = 'info' | 'warning' | 'error' | 'success' | 'debug' | 'display' | 'stream';
2
+ export type GthCommand = 'ask' | 'pr' | 'review' | 'chat' | 'code';
@@ -39,5 +39,8 @@ export function init(configFileName: string): void {
39
39
  }
40
40
 
41
41
  writeFileIfNotExistsWithMessages(configFileName, jsonContent);
42
- displayWarning(`You need to update your ${configFileName} to add your Anthropic API key.`);
42
+ displayWarning(
43
+ `You need to update your ${configFileName} to add your Anthropic API key, ` +
44
+ 'or define ANTHROPIC_API_KEY environment variable.'
45
+ );
43
46
  }
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+ import { displayWarning } from '#src/consoleUtils.js';
3
+ import { env, getCurrentDir } from '#src/systemUtils.js';
4
+ import { writeFileIfNotExistsWithMessages } from '#src/utils.js';
5
+ import type {
6
+ BaseChatModel,
7
+ BaseChatModelParams,
8
+ } from '@langchain/core/language_models/chat_models';
9
+ import { ChatDeepSeekInput } from '@langchain/deepseek';
10
+
11
+ // Function to process JSON config and create DeepSeek LLM instance
12
+ export async function processJsonConfig(
13
+ llmConfig: ChatDeepSeekInput & BaseChatModelParams
14
+ ): Promise<BaseChatModel> {
15
+ const deepseek = await import('@langchain/deepseek');
16
+ // Use environment variable if available, otherwise use the config value
17
+ const deepseekApiKey = env.DEEPSEEK_API_KEY || llmConfig.apiKey;
18
+ return new deepseek.ChatDeepSeek({
19
+ ...llmConfig,
20
+ apiKey: deepseekApiKey,
21
+ model: llmConfig.model || 'deepseek-reasoner',
22
+ });
23
+ }
24
+
25
+ const jsonContent = `{
26
+ "llm": {
27
+ "type": "deepseek",
28
+ "model": "deepseek-reasoner"
29
+ }
30
+ }`;
31
+
32
+ export function init(configFileName: string): void {
33
+ const currentDir = getCurrentDir();
34
+ path.join(currentDir, configFileName);
35
+
36
+ // Determine which content to use based on file extension
37
+ if (!configFileName.endsWith('.json')) {
38
+ throw new Error('Only JSON config is supported.');
39
+ }
40
+
41
+ writeFileIfNotExistsWithMessages(configFileName, jsonContent);
42
+ displayWarning(
43
+ `You need to update your ${configFileName} to add your DeepSeek API key, ` +
44
+ 'or define DEEPSEEK_API_KEY environment variable.'
45
+ );
46
+ }
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { displayInfo, displayWarning } from '#src/consoleUtils.js';
2
+ import { displayWarning } from '#src/consoleUtils.js';
3
3
  import { env, getCurrentDir } from '#src/systemUtils.js';
4
4
  import { writeFileIfNotExistsWithMessages } from '#src/utils.js';
5
5
  import { BaseChatModel } from '@langchain/core/language_models/chat_models';
@@ -20,8 +20,7 @@ export async function processJsonConfig(llmConfig: ChatGroqInput): Promise<BaseC
20
20
  const jsonContent = `{
21
21
  "llm": {
22
22
  "type": "groq",
23
- "model": "deepseek-r1-distill-llama-70b",
24
- "apiKey": "your-api-key-here"
23
+ "model": "deepseek-r1-distill-llama-70b"
25
24
  }
26
25
  }`;
27
26
 
@@ -35,8 +34,8 @@ export function init(configFileName: string): void {
35
34
  }
36
35
 
37
36
  writeFileIfNotExistsWithMessages(configFileName, jsonContent);
38
- displayInfo(
39
- `You can define GROQ_API_KEY environment variable with your Groq API key and it will work with default model.`
37
+ displayWarning(
38
+ `You need to edit your ${configFileName} to configure model, ` +
39
+ 'or define GROQ_API_KEY environment variable.'
40
40
  );
41
- displayWarning(`You need to edit your ${configFileName} to configure model.`);
42
41
  }
@@ -8,8 +8,7 @@ import type { BaseChatModel } from '@langchain/core/language_models/chat_models'
8
8
  const jsonContent = `{
9
9
  "llm": {
10
10
  "type": "vertexai",
11
- "model": "gemini-2.5-pro-preview-05-06",
12
- "temperature": 0
11
+ "model": "gemini-2.5-pro"
13
12
  }
14
13
  }`;
15
14
 
@@ -33,6 +32,6 @@ export async function processJsonConfig(llmConfig: ChatVertexAIInput): Promise<B
33
32
  const vertexAi = await import('@langchain/google-vertexai');
34
33
  return new vertexAi.ChatVertexAI({
35
34
  ...llmConfig,
36
- model: llmConfig.model || 'gemini-2.5-pro-preview-05-06',
35
+ model: llmConfig.model || 'gemini-2.5-pro',
37
36
  });
38
37
  }
@@ -1,7 +1,7 @@
1
1
  import { BaseToolkit, StructuredToolInterface, tool } from '@langchain/core/tools';
2
2
  import { z } from 'zod';
3
3
  import fs from 'fs/promises';
4
- import path from 'path';
4
+ import path from 'node:path';
5
5
  import os from 'os';
6
6
  import { createTwoFilesPatch } from 'diff';
7
7
  import { minimatch } from 'minimatch';
@@ -5,7 +5,7 @@ export default defineConfig({
5
5
  include: ['integration-tests/**/*.it.ts'],
6
6
  environment: 'node',
7
7
  globals: true,
8
- testTimeout: 1000 * 60 * 2,
8
+ testTimeout: 1000 * 60 * 3,
9
9
  maxWorkers: 1,
10
10
  fileParallelism: false,
11
11
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"groq.js","sourceRoot":"","sources":["../../src/configs/groq.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,eAAe,CAAC;AAIjE,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAwB;IAC9D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC7C,wEAAwE;IACxE,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,IAAI,SAAS,CAAC,MAAM,CAAC;IACxD,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC;QACvB,GAAG,SAAS;QACZ,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,+BAA+B;KAC1D,CAAC,CAAC;AACL,CAAC;AAED,MAAM,WAAW,GAAG;;;;;;EAMlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IAEtC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,WAAW,CACT,8GAA8G,CAC/G,CAAC;IACF,cAAc,CAAC,yBAAyB,cAAc,sBAAsB,CAAC,CAAC;AAChF,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"vertexai.js","sourceRoot":"","sources":["../../src/configs/vertexai.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gCAAgC,EAAE,MAAM,eAAe,CAAC;AAIjE,MAAM,WAAW,GAAG;;;;;;EAMlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IAEtC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,cAAc,CACZ,+GAA+G,CAChH,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAA4B;IAClE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;IAC5D,OAAO,IAAI,QAAQ,CAAC,YAAY,CAAC;QAC/B,GAAG,SAAS;QACZ,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,8BAA8B;KACzD,CAAC,CAAC;AACL,CAAC"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes