@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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/src/Conversation.d.ts +43 -1
- package/dist/src/Conversation.d.ts.map +1 -1
- package/dist/src/Conversation.js +255 -5
- package/dist/src/Conversation.js.map +1 -1
- package/dist/src/OpenAi.d.ts +29 -0
- package/dist/src/OpenAi.d.ts.map +1 -1
- package/dist/src/OpenAi.js +77 -30
- package/dist/src/OpenAi.js.map +1 -1
- package/dist/src/fs/conversation_fs/ConversationFsModule.d.ts +1 -0
- package/dist/src/fs/conversation_fs/ConversationFsModule.d.ts.map +1 -1
- package/dist/src/fs/conversation_fs/ConversationFsModule.js +6 -2
- package/dist/src/fs/conversation_fs/ConversationFsModule.js.map +1 -1
- package/dist/src/fs/conversation_fs/FsFunctions.d.ts +36 -3
- package/dist/src/fs/conversation_fs/FsFunctions.d.ts.map +1 -1
- package/dist/src/fs/conversation_fs/FsFunctions.js +142 -20
- package/dist/src/fs/conversation_fs/FsFunctions.js.map +1 -1
- package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.d.ts +4 -1
- package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.d.ts.map +1 -1
- package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.js +13 -9
- package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.js.map +1 -1
- package/index.ts +10 -1
- package/package.json +6 -4
- package/src/Conversation.ts +311 -5
- package/src/OpenAi.ts +123 -13
- package/src/fs/conversation_fs/ConversationFsModule.ts +8 -2
- package/src/fs/conversation_fs/FsFunctions.ts +97 -17
- 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
|
-
|
|
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
|
-
|
|
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((
|
|
65
|
-
|
|
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
|
-
|
|
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.
|
|
27
|
-
const
|
|
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
|
|
84
|
+
const fileNameLower = path.parse(filePath).name.toLowerCase(); // Get file name without extension
|
|
80
85
|
|
|
81
|
-
if (!keywordFilesIndex[
|
|
82
|
-
keywordFilesIndex[
|
|
86
|
+
if (!keywordFilesIndex[fileNameLower]) {
|
|
87
|
+
keywordFilesIndex[fileNameLower] = [];
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
this.logger.debug({ message: `fileName: ${
|
|
86
|
-
keywordFilesIndex[
|
|
90
|
+
this.logger.debug({ message: `fileName: ${fileNameLower}, filePath: ${filePath}` });
|
|
91
|
+
keywordFilesIndex[fileNameLower].push(filePath);
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
return keywordFilesIndex;
|