@proteinjs/conversation 2.1.3 → 2.1.5

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/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +8 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/Conversation.d.ts +43 -1
  8. package/dist/src/Conversation.d.ts.map +1 -1
  9. package/dist/src/Conversation.js +255 -5
  10. package/dist/src/Conversation.js.map +1 -1
  11. package/dist/src/OpenAi.d.ts +29 -0
  12. package/dist/src/OpenAi.d.ts.map +1 -1
  13. package/dist/src/OpenAi.js +77 -30
  14. package/dist/src/OpenAi.js.map +1 -1
  15. package/dist/src/fs/conversation_fs/ConversationFsModule.d.ts +1 -0
  16. package/dist/src/fs/conversation_fs/ConversationFsModule.d.ts.map +1 -1
  17. package/dist/src/fs/conversation_fs/ConversationFsModule.js +6 -2
  18. package/dist/src/fs/conversation_fs/ConversationFsModule.js.map +1 -1
  19. package/dist/src/fs/conversation_fs/FsFunctions.d.ts +36 -3
  20. package/dist/src/fs/conversation_fs/FsFunctions.d.ts.map +1 -1
  21. package/dist/src/fs/conversation_fs/FsFunctions.js +142 -20
  22. package/dist/src/fs/conversation_fs/FsFunctions.js.map +1 -1
  23. package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.d.ts +4 -1
  24. package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.d.ts.map +1 -1
  25. package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.js +13 -9
  26. package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.js.map +1 -1
  27. package/index.ts +10 -1
  28. package/package.json +6 -4
  29. package/src/Conversation.ts +311 -5
  30. package/src/OpenAi.ts +123 -13
  31. package/src/fs/conversation_fs/ConversationFsModule.ts +8 -2
  32. package/src/fs/conversation_fs/FsFunctions.ts +97 -17
  33. package/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.ts +14 -9
@@ -1,6 +1,50 @@
1
1
  import { File, Fs } from '@proteinjs/util-node';
2
2
  import { Function } from '../../Function';
3
3
  import { ConversationFsModule } from './ConversationFsModule';
4
+ import path from 'path';
5
+
6
+ const toRepoAbs = (mod: ConversationFsModule, p: string) => (path.isAbsolute(p) ? p : path.join(mod.getRepoPath(), p));
7
+
8
+ // If path doesn’t exist, try to resolve "<repo>/<basename>" to the actual file under repo
9
+ async function canonicalizePaths(mod: ConversationFsModule, paths: string[]): Promise<string[]> {
10
+ const repo = mod.getRepoPath();
11
+ const ignore = ['**/node_modules/**', '**/dist/**', '**/.git/**'];
12
+
13
+ const out: string[] = [];
14
+ for (const p of paths) {
15
+ const abs = toRepoAbs(mod, p);
16
+ if (await Fs.exists(abs)) {
17
+ out.push(abs);
18
+ continue;
19
+ }
20
+ const base = path.basename(p);
21
+ if (!base) {
22
+ out.push(abs);
23
+ continue;
24
+ }
25
+ const parsed = path.parse(base); // { name, ext }
26
+ const pattern = parsed.ext ? `**/${parsed.name}${parsed.ext}` : `**/${parsed.name}.*`;
27
+
28
+ let matches: string[] = [];
29
+ try {
30
+ matches = await (Fs as any).getFilePathsMatchingGlob(repo, pattern, ignore);
31
+ } catch {
32
+ // fall through
33
+ }
34
+
35
+ if (matches.length === 1) {
36
+ out.push(matches[0]);
37
+ } else if (matches.length > 1) {
38
+ // Prefer the shortest match (usually “src/...” beats deeper/duplicate locations)
39
+ matches.sort((a, b) => a.length - b.length);
40
+ out.push(matches[0]);
41
+ } else {
42
+ // No luck; keep the original absolute (will throw with a clear error)
43
+ out.push(abs);
44
+ }
45
+ }
46
+ return out;
47
+ }
4
48
 
5
49
  export const readFilesFunctionName = 'readFiles';
6
50
  export function readFilesFunction(fsModule: ConversationFsModule) {
@@ -14,9 +58,7 @@ export function readFilesFunction(fsModule: ConversationFsModule) {
14
58
  filePaths: {
15
59
  type: 'array',
16
60
  description: 'Paths to the files',
17
- items: {
18
- type: 'string',
19
- },
61
+ items: { type: 'string' },
20
62
  },
21
63
  },
22
64
  required: ['filePaths'],
@@ -24,7 +66,8 @@ export function readFilesFunction(fsModule: ConversationFsModule) {
24
66
  },
25
67
  call: async (params: { filePaths: string[] }) => {
26
68
  fsModule.pushRecentlyAccessedFilePath(params.filePaths);
27
- return await Fs.readFiles(params.filePaths);
69
+ const absPaths = await canonicalizePaths(fsModule, params.filePaths);
70
+ return await Fs.readFiles(absPaths);
28
71
  },
29
72
  instructions: [`To read files from the local file system, use the ${readFilesFunctionName} function`],
30
73
  };
@@ -41,19 +84,10 @@ export function writeFilesFunction(fsModule: ConversationFsModule) {
41
84
  properties: {
42
85
  files: {
43
86
  type: 'array',
44
- description: 'Files to write',
45
87
  items: {
46
88
  type: 'object',
47
- properties: {
48
- path: {
49
- type: 'string',
50
- description: 'the file path',
51
- },
52
- content: {
53
- type: 'string',
54
- description: 'the content to write to the file',
55
- },
56
- },
89
+ properties: { path: { type: 'string' }, content: { type: 'string' } },
90
+ required: ['path', 'content'],
57
91
  },
58
92
  },
59
93
  },
@@ -61,8 +95,13 @@ export function writeFilesFunction(fsModule: ConversationFsModule) {
61
95
  },
62
96
  },
63
97
  call: async (params: { files: File[] }) => {
64
- fsModule.pushRecentlyAccessedFilePath(params.files.map((file) => file.path));
65
- return await Fs.writeFiles(params.files);
98
+ fsModule.pushRecentlyAccessedFilePath(params.files.map((f) => f.path));
99
+ const canon = await canonicalizePaths(
100
+ fsModule,
101
+ params.files.map((f) => f.path)
102
+ );
103
+ const absFiles = params.files.map((f, i) => ({ ...f, path: canon[i] }));
104
+ return await Fs.writeFiles(absFiles);
66
105
  },
67
106
  instructions: [`To write files to the local file system, use the ${writeFilesFunctionName} function`],
68
107
  };
@@ -232,6 +271,47 @@ const moveFunction: Function = {
232
271
  instructions: [`To move a file or directory, use the ${moveFunctionName} function`],
233
272
  };
234
273
 
274
+ export const grepFunctionName = 'grep';
275
+ export function grepFunction(fsModule: ConversationFsModule) {
276
+ return {
277
+ definition: {
278
+ name: grepFunctionName,
279
+ description:
280
+ "Run system grep recursively (-F literal) within the repository and return raw stdout/stderr/code. Excludes node_modules, dist, and .git. Use 'maxResults' to cap output.",
281
+ parameters: {
282
+ type: 'object',
283
+ properties: {
284
+ pattern: {
285
+ type: 'string',
286
+ description:
287
+ 'Literal text to search for (grep -F). For parentheses or special characters, pass them as-is; no regex needed.',
288
+ },
289
+ dir: {
290
+ type: 'string',
291
+ description:
292
+ 'Directory to search under. If relative, it is resolved against the repo root. Defaults to the repo root.',
293
+ },
294
+ maxResults: {
295
+ type: 'number',
296
+ description: 'Maximum number of matching lines to return (uses grep -m N).',
297
+ },
298
+ },
299
+ required: ['pattern'],
300
+ },
301
+ },
302
+ call: async (params: { pattern: string; dir?: string; maxResults?: number }) => {
303
+ const repo = fsModule.getRepoPath();
304
+ const cwd = params.dir ? toRepoAbs(fsModule, params.dir) : repo;
305
+ return await Fs.grep({ pattern: params.pattern, dir: cwd, maxResults: params.maxResults });
306
+ },
307
+ instructions: [
308
+ `Use ${grepFunctionName} to search for literal text across the repo.`,
309
+ `Prefer small 'maxResults' (e.g., 5-20) to avoid flooding the context.`,
310
+ `Parse the returned stdout yourself (format: "<path>:<line>:<text>") to pick files to read.`,
311
+ ],
312
+ };
313
+ }
314
+
235
315
  export const fsFunctions: Function[] = [
236
316
  createFolderFunction,
237
317
  fileOrDirectoryExistsFunction,
@@ -7,7 +7,8 @@ import { searchFilesFunction, searchFilesFunctionName } from './KeywordToFilesIn
7
7
 
8
8
  export type KeywordToFilesIndexModuleParams = {
9
9
  dir: string;
10
- keywordFilesIndex: { [keyword: string]: string[] /** file paths */ };
10
+ // Map from lowercase filename *stem* (no extension) → file paths
11
+ keywordFilesIndex: { [keyword: string]: string[] };
11
12
  };
12
13
 
13
14
  export class KeywordToFilesIndexModule implements ConversationModule {
@@ -22,15 +23,19 @@ export class KeywordToFilesIndexModule implements ConversationModule {
22
23
  return 'Keyword to files index';
23
24
  }
24
25
 
26
+ /**
27
+ * Case-insensitive file name search that ignores extension.
28
+ */
25
29
  searchFiles(params: { keyword: string }) {
26
- this.logger.info({ message: `Searching for file, keyword: ${params.keyword}` });
27
- const filePaths = this.params.keywordFilesIndex[params.keyword];
30
+ this.logger.debug({ message: `Searching for file, keyword: ${params.keyword}` });
31
+ const keywordLowerNoExtension = path.parse(params.keyword).name.toLowerCase();
32
+ const filePaths = this.params.keywordFilesIndex[keywordLowerNoExtension];
28
33
  return filePaths || [];
29
34
  }
30
35
 
31
36
  getSystemMessages(): string[] {
32
37
  return [
33
- `If you're searching for something, use the ${searchFilesFunctionName} function to find a file matching the search string`,
38
+ `If you're searching for something, use the ${searchFilesFunctionName} function to find a file (by name) matching the search string`,
34
39
  ];
35
40
  }
36
41
 
@@ -76,14 +81,14 @@ export class KeywordToFilesIndexModuleFactory implements ConversationModuleFacto
76
81
 
77
82
  // Process each file path
78
83
  for (const filePath of filePaths) {
79
- const fileName = path.parse(filePath).name; // Get file name without extension
84
+ const fileNameLower = path.parse(filePath).name.toLowerCase(); // Get file name without extension
80
85
 
81
- if (!keywordFilesIndex[fileName]) {
82
- keywordFilesIndex[fileName] = [];
86
+ if (!keywordFilesIndex[fileNameLower]) {
87
+ keywordFilesIndex[fileNameLower] = [];
83
88
  }
84
89
 
85
- this.logger.debug({ message: `fileName: ${fileName}, filePath: ${filePath}` });
86
- keywordFilesIndex[fileName].push(filePath);
90
+ this.logger.debug({ message: `fileName: ${fileNameLower}, filePath: ${filePath}` });
91
+ keywordFilesIndex[fileNameLower].push(filePath);
87
92
  }
88
93
 
89
94
  return keywordFilesIndex;