@proteinjs/conversation 1.0.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 (76) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/dist/index.js +27 -0
  4. package/dist/jest.config.js +10 -0
  5. package/dist/src/CodegenConversation.js +120 -0
  6. package/dist/src/Conversation.js +193 -0
  7. package/dist/src/ConversationModule.js +2 -0
  8. package/dist/src/Function.js +2 -0
  9. package/dist/src/OpenAi.js +209 -0
  10. package/dist/src/Paragraph.js +18 -0
  11. package/dist/src/Sentence.js +22 -0
  12. package/dist/src/code_template/Code.js +41 -0
  13. package/dist/src/code_template/CodeTemplate.js +39 -0
  14. package/dist/src/code_template/CodeTemplateModule.js +46 -0
  15. package/dist/src/code_template/Repo.js +127 -0
  16. package/dist/src/fs/conversation_fs/ConversationFsModerator.js +99 -0
  17. package/dist/src/fs/conversation_fs/ConversationFsModule.js +68 -0
  18. package/dist/src/fs/conversation_fs/FsFunctions.js +256 -0
  19. package/dist/src/fs/git/GitModule.js +45 -0
  20. package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexFunctions.js +65 -0
  21. package/dist/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.js +89 -0
  22. package/dist/src/fs/package/PackageFunctions.js +214 -0
  23. package/dist/src/fs/package/PackageModule.js +102 -0
  24. package/dist/src/history/MessageHistory.js +44 -0
  25. package/dist/src/history/MessageModerator.js +2 -0
  26. package/dist/src/template/ConversationTemplate.js +2 -0
  27. package/dist/src/template/ConversationTemplateFunctions.js +54 -0
  28. package/dist/src/template/ConversationTemplateModule.js +80 -0
  29. package/dist/src/template/createApp/CreateAppTemplate.js +40 -0
  30. package/dist/src/template/createCode/CreateCodeConversationTemplate.js +51 -0
  31. package/dist/src/template/createPackage/CreatePackageConversationTemplate.js +54 -0
  32. package/dist/src/template/createPackage/jest.config.js +10 -0
  33. package/dist/src/template/createPackage/tsconfig.json +13 -0
  34. package/dist/test/createKeywordFilesIndex.test.js +17 -0
  35. package/dist/test/openai/openai.generateList.test.js +16 -0
  36. package/dist/test/openai/openai.parseCodeFromMarkdown.test.js +18 -0
  37. package/dist/test/repo/repo.test.js +29 -0
  38. package/dist/test/setup.js +1 -0
  39. package/index.ts +11 -0
  40. package/jest.config.js +9 -0
  41. package/package.json +34 -0
  42. package/src/CodegenConversation.ts +92 -0
  43. package/src/Conversation.ts +207 -0
  44. package/src/ConversationModule.ts +13 -0
  45. package/src/Function.ts +8 -0
  46. package/src/OpenAi.ts +212 -0
  47. package/src/Paragraph.ts +17 -0
  48. package/src/Sentence.ts +20 -0
  49. package/src/code_template/Code.ts +53 -0
  50. package/src/code_template/CodeTemplate.ts +39 -0
  51. package/src/code_template/CodeTemplateModule.ts +50 -0
  52. package/src/code_template/Repo.ts +156 -0
  53. package/src/fs/conversation_fs/ConversationFsModerator.ts +121 -0
  54. package/src/fs/conversation_fs/ConversationFsModule.ts +64 -0
  55. package/src/fs/conversation_fs/FsFunctions.ts +253 -0
  56. package/src/fs/git/GitModule.ts +39 -0
  57. package/src/fs/keyword_to_files_index/KeywordToFilesIndexFunctions.ts +55 -0
  58. package/src/fs/keyword_to_files_index/KeywordToFilesIndexModule.ts +90 -0
  59. package/src/fs/package/PackageFunctions.ts +210 -0
  60. package/src/fs/package/PackageModule.ts +106 -0
  61. package/src/history/MessageHistory.ts +57 -0
  62. package/src/history/MessageModerator.ts +6 -0
  63. package/src/template/ConversationTemplate.ts +12 -0
  64. package/src/template/ConversationTemplateFunctions.ts +43 -0
  65. package/src/template/ConversationTemplateModule.ts +83 -0
  66. package/src/template/createApp/CreateAppTemplate.ts +33 -0
  67. package/src/template/createCode/CreateCodeConversationTemplate.ts +41 -0
  68. package/src/template/createPackage/CreatePackageConversationTemplate.ts +42 -0
  69. package/src/template/createPackage/jest.config.js +9 -0
  70. package/src/template/createPackage/tsconfig.json +13 -0
  71. package/test/createKeywordFilesIndex.test.ts +7 -0
  72. package/test/openai/openai.generateList.test.ts +6 -0
  73. package/test/openai/openai.parseCodeFromMarkdown.test.ts +20 -0
  74. package/test/repo/repo.test.ts +33 -0
  75. package/test/setup.js +0 -0
  76. package/tsconfig.json +109 -0
@@ -0,0 +1,50 @@
1
+ import { ConversationModule } from '../ConversationModule';
2
+ import { Function } from '../Function';
3
+ import { MessageModerator } from '../history/MessageModerator';
4
+
5
+ export class CodeTemplateModule implements ConversationModule {
6
+ private static CODE_RESPONSE = 'Code with user input:';
7
+
8
+ getName(): string {
9
+ return 'Code Template';
10
+ }
11
+
12
+ getSystemMessages(): string[] {
13
+ return [
14
+ `If they want to create a function/class/object using an API we are familiar with, we will ask the user for the required information to fill in all mandatory parameters and ask them if they want to provide optional parameter values`,
15
+ `Once we have gotten the values for all parameters, respond with '${CodeTemplateModule.CODE_RESPONSE}' followed by the code to instantiate/call the function/class/object with the user's responses for parameter values`,
16
+ `If you're generating a call to a class that extends Template, require that the user provide Template's constructor parameters as well and combine them into the parameters passed into the base class you're instantiating`,
17
+ `Make sure you ask for a srcPath and pass that in to the Template base class constructor arg`,
18
+ `Surround generated code (not including imports) with a self-executing, anonymous async function like this (async function() =>{})()`,
19
+ ];
20
+ }
21
+ getFunctions(): Function[] {
22
+ return [];
23
+ }
24
+ getMessageModerators(): MessageModerator[] {
25
+ return [];
26
+ }
27
+ }
28
+
29
+ // if (response.includes(CodegenConversation.CODE_RESPONSE))
30
+ // await this.generateCode(response, conversation);
31
+
32
+ // private async generateCode(message: string, conversation: Conversation) {
33
+ // const code = OpenAi.parseCodeFromMarkdown(message);
34
+ // const srcPathToken = 'TOKEN';
35
+ // const responseSrcPath = await conversation.generateResponse([`Return the srcPath the user provided surrounded by the token ${srcPathToken}`], CodegenConversation.MODEL);
36
+ // const srcPath = responseSrcPath.replace(/["'`]/g, '').match(/TOKEN(.*?)TOKEN/)?.[1];
37
+ // if (!srcPath)
38
+ // throw new Error(`Failed to parse responseSrcPath: ${responseSrcPath}`);
39
+ // const codePath = path.join(process.cwd(), srcPath, 'template.ts');
40
+ // await fs.ensureFile(codePath);
41
+ // await fs.writeFile(codePath, code);
42
+ // console.log(`Wrote file: ${codePath}`);
43
+ // const command = 'node';
44
+ // const args = [codePath];
45
+ // const commandLog = `${command} ` + args.join(' ');
46
+ // console.info(`Running command: ${commandLog}`);
47
+ // await cmd(command, args, {OPENAI_API_KEY: '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'});
48
+ // console.info(`Ran command: ${commandLog}`);
49
+ // console.info(`Generated code from template: ${codePath}`);
50
+ // }
@@ -0,0 +1,156 @@
1
+ import { Logger } from '@proteinjs/util';
2
+ import { FileDescriptor, Fs, PackageUtil } from '@proteinjs/util-node';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+
6
+ export interface TsFile extends FileDescriptor {
7
+ declaration: string;
8
+ }
9
+
10
+ export interface PackageInfo {
11
+ packageJSON: any; // The content of the package.json
12
+ dirPath: string;
13
+ tsFiles: { [tsFilePath: string]: TsFile };
14
+ fileDescriptors: FileDescriptor[];
15
+ }
16
+
17
+ export type SlimPackageInfo = Omit<PackageInfo, 'packageJSON'|'tsFiles'>;
18
+
19
+ export type RepoParams = {
20
+ packages: Record<string, PackageInfo>,
21
+ slimPackages: Record<string, SlimPackageInfo>,
22
+ tsFiles: { [tsFilePath: string]: TsFile },
23
+ keywordFilesIndex: { [keyword: string]: string[] /** file paths */ },
24
+ }
25
+
26
+ export class Repo {
27
+ private logger = new Logger(this.constructor.name);
28
+ params: RepoParams;
29
+
30
+ constructor(params: RepoParams) {
31
+ this.params = params;
32
+ }
33
+
34
+ packages() {
35
+ return this.params.packages;
36
+ }
37
+
38
+ slimPackages() {
39
+ return this.params.slimPackages;
40
+ }
41
+
42
+ tsFiles() {
43
+ return this.params.tsFiles;
44
+ }
45
+
46
+ keywordFilesIndex() {
47
+ return this.params.keywordFilesIndex;
48
+ }
49
+
50
+ searchFiles(params: { keyword: string }) {
51
+ this.logger.info(`Searching for file, keyword: ${params.keyword}`);
52
+ const filePaths = this.keywordFilesIndex()[params.keyword];
53
+ return filePaths || [];
54
+ }
55
+
56
+ getDeclarations(params: { tsFilePaths: string[] }) {
57
+ const queriedDeclarations: { [tsFilePath: string]: string } = {};
58
+ for (let tsFilePath of params.tsFilePaths) {
59
+ queriedDeclarations[tsFilePath] = this.params.tsFiles[tsFilePath].declaration;
60
+ this.logger.info(`Accessed declaration: ${tsFilePath}`);
61
+ }
62
+ return queriedDeclarations;
63
+ }
64
+ }
65
+
66
+ export class RepoFactory {
67
+ private static LOGGER = new Logger('RepoFactory');
68
+
69
+ public static async createRepo(dir: string): Promise<Repo> {
70
+ this.LOGGER.info(`Creating repo for dir: ${dir}`);
71
+ let repoParams: RepoParams = { packages: {}, slimPackages: {}, tsFiles: {}, keywordFilesIndex: {} };
72
+
73
+ async function traverse(dir: string) {
74
+ const childrenNames = await fs.readdir(dir, { withFileTypes: true });
75
+ let hasPackageJson = childrenNames.some(dirent => dirent.name === 'package.json');
76
+ if (hasPackageJson) {
77
+ const packageContent = await fs.readFile(path.join(dir, 'package.json'), 'utf-8');
78
+ const packageJSON = JSON.parse(packageContent);
79
+ repoParams.packages[packageJSON.name] = {
80
+ packageJSON: packageJSON,
81
+ dirPath: dir,
82
+ fileDescriptors: [],
83
+ tsFiles: {},
84
+ };
85
+ }
86
+
87
+ for (const dirent of childrenNames) {
88
+ if (!dirent.isDirectory())
89
+ continue;
90
+
91
+ // Exclude directories 'node_modules' and 'dist' right at the beginning
92
+ if (dirent.name.includes('node_modules') || dirent.name.includes('dist')) {
93
+ continue;
94
+ }
95
+
96
+ // Continue with the traversal if it's a directory
97
+ const childPath = path.join(dir, dirent.name);
98
+ await traverse(childPath);
99
+ }
100
+ }
101
+
102
+ await traverse(dir);
103
+ await RepoFactory.loadFiles(repoParams);
104
+ Object.keys(repoParams.packages).forEach(packageName => {
105
+ const { packageJSON, tsFiles, ...slimPackage } = repoParams.packages[packageName];
106
+ repoParams.slimPackages[packageName] = slimPackage;
107
+ });
108
+ this.LOGGER.info(`Created repo for dir: ${dir}`);
109
+ return new Repo(repoParams);
110
+ }
111
+
112
+ private static async loadFiles(repoParams: RepoParams) {
113
+ for (let packageName of Object.keys(repoParams.packages)) {
114
+ this.LOGGER.info(`Loading files for package: ${packageName}`);
115
+ const dirPath = repoParams.packages[packageName].dirPath;
116
+ if (dirPath) {
117
+ repoParams.packages[packageName].fileDescriptors.push(...await Fs.getFilesInDirectory(dirPath, ['node_modules', 'dist']));
118
+ for (let fileDescriptor of repoParams.packages[packageName].fileDescriptors) {
119
+ const typescriptDeclaration = PackageUtil.generateTypescriptDeclarations({ tsFilePaths: [fileDescriptor.path] })[fileDescriptor.path];
120
+ const tsFile = Object.assign({ declaration: typescriptDeclaration }, fileDescriptor);
121
+ repoParams.packages[packageName].tsFiles[fileDescriptor.path] = tsFile;
122
+ repoParams.tsFiles[fileDescriptor.path] = tsFile;
123
+ if (!repoParams.keywordFilesIndex[fileDescriptor.nameWithoutExtension])
124
+ repoParams.keywordFilesIndex[fileDescriptor.nameWithoutExtension] = [];
125
+
126
+ repoParams.keywordFilesIndex[fileDescriptor.nameWithoutExtension].push(fileDescriptor.path);
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ // private static async generateDirectoryMap(dir: string): Promise<TsFile> {
133
+ // const stat = await fs.stat(dir);
134
+ // let map: TsFile = {
135
+ // name: path.basename(dir),
136
+ // path: dir,
137
+ // type: stat.isDirectory() ? 'folder' : 'file'
138
+ // };
139
+
140
+ // if (stat.isDirectory()) {
141
+ // const childrenNames = await fs.readdir(dir);
142
+ // map.children = await Promise.all(
143
+ // childrenNames.map(child => RepoFactory.generateDirectoryMap(path.join(dir, child)))
144
+ // );
145
+ // } else if (dir.endsWith('.ts') || dir.endsWith('.tsx')) {
146
+ // const declarations = this.generateDeclarations({ tsFilePaths: [dir] });
147
+ // const declarationFilePath = path.join(path.dirname(dir), path.basename(dir, path.extname(dir))) + '.d.ts';
148
+ // // console.log(`dir: ${dir}\ndeclarationFilePath: ${declarationFilePath}`);
149
+ // if (declarations[declarationFilePath]) {
150
+ // map.declaration = declarations[declarationFilePath];
151
+ // }
152
+ // }
153
+
154
+ // return map;
155
+ // }
156
+ }
@@ -0,0 +1,121 @@
1
+ import { LogLevel, Logger } from '@proteinjs/util';
2
+ import { FileContentMap } from '@proteinjs/util-node';
3
+ import { ChatCompletionMessageParam } from 'openai/resources/chat';
4
+ import { MessageModerator } from '../../history/MessageModerator';
5
+
6
+ export interface ConversationFs {
7
+ fileContentMap: FileContentMap;
8
+ order: string[]; // ordered list of filePaths
9
+ }
10
+
11
+ export interface ConversationFsFactoryParams {
12
+ maxFiles: number;
13
+ logLevel: LogLevel;
14
+ }
15
+
16
+ export class ConversationFsFactory {
17
+ private logger: Logger;
18
+ private params: ConversationFsFactoryParams;
19
+
20
+ constructor(params?: Partial<ConversationFsFactoryParams>) {
21
+ this.params = Object.assign({ maxFiles: 3, logLevel: 'info' }, params);
22
+ this.logger = new Logger(this.constructor.name, this.params.logLevel);
23
+ }
24
+
25
+ merge(existingFs: ConversationFs, updates: FileContentMap): ConversationFs {
26
+ for (let filePath of Object.keys(updates)) {
27
+ // if the file already exists in the fs
28
+ if (existingFs.fileContentMap[filePath]) {
29
+ this.logger.debug(`Updating existing file: ${filePath}`)
30
+ existingFs.fileContentMap[filePath] = updates[filePath];
31
+ const oldIndex = existingFs.order.findIndex(item => item == filePath);
32
+ existingFs.order.splice(oldIndex, 1);
33
+ existingFs.order.push(filePath);
34
+ continue;
35
+ }
36
+
37
+ // if we have less than the max number of files in the fs
38
+ if (Object.keys(existingFs.fileContentMap).length < this.params.maxFiles) {
39
+ this.logger.debug(`Adding new file (under limit): ${filePath}`)
40
+ existingFs.fileContentMap[filePath] = updates[filePath];
41
+ existingFs.order.push(filePath);
42
+ continue;
43
+ } else {
44
+ this.logger.debug(`Adding new file (over limit): ${filePath}`)
45
+ const removedFilePath = existingFs.order.splice(0, 1)[0];
46
+ delete existingFs.fileContentMap[removedFilePath];
47
+ existingFs.fileContentMap[filePath] = updates[filePath];
48
+ existingFs.order.push(filePath);
49
+ }
50
+ }
51
+
52
+ return existingFs;
53
+ }
54
+ }
55
+
56
+ export class ConversationFsModerator implements MessageModerator {
57
+ private logLevel: LogLevel = 'info';
58
+
59
+ constructor(logLevel?: LogLevel) {
60
+ if (logLevel)
61
+ this.logLevel = logLevel;
62
+ }
63
+
64
+ observe(messages: ChatCompletionMessageParam[]): ChatCompletionMessageParam[] {
65
+ let conversationFileSystemMessageIndex: number = -1;
66
+ let conversationFileSystem: ConversationFs|undefined;
67
+ let readFilesFunctionCallMessageIndexes: number[] = [];
68
+ let writeFilesFunctionCallMessageIndexes: number[] = [];
69
+ const readFilesConsolidatedOutput: FileContentMap = {}; // newest version of file wins
70
+ for (let i = 0; i < messages.length; i++) {
71
+ const message = messages[i];
72
+ if (message.role == 'system' && message.content) {
73
+ let parsedContent: any;
74
+ try {
75
+ parsedContent = JSON.parse(message.content);
76
+ } catch (error) {}
77
+ if (!parsedContent || !parsedContent['fileSystem'])
78
+ continue;
79
+
80
+ conversationFileSystem = parsedContent['fileSystem'];
81
+ conversationFileSystemMessageIndex = i;
82
+ continue;
83
+ }
84
+
85
+ if (message.role == 'function' && message.name == 'readFiles' && message.content) {
86
+ let parsedContent: any|undefined;
87
+ try {
88
+ parsedContent = JSON.parse(message.content);
89
+ } catch (error) {}
90
+ if (!parsedContent)
91
+ continue;
92
+
93
+ for (let filePath of Object.keys(parsedContent)) {
94
+ readFilesConsolidatedOutput[filePath] = parsedContent[filePath];
95
+ }
96
+
97
+ readFilesFunctionCallMessageIndexes.push(i);
98
+ }
99
+
100
+ if (message.role == 'function' && message.name == 'writeFiles') {
101
+ writeFilesFunctionCallMessageIndexes.push(i);
102
+ }
103
+ }
104
+
105
+ if (conversationFileSystem) {
106
+ conversationFileSystem = new ConversationFsFactory({ logLevel: this.logLevel }).merge(conversationFileSystem, readFilesConsolidatedOutput);
107
+ const content = JSON.stringify({ fileSystem: conversationFileSystem });
108
+ messages[conversationFileSystemMessageIndex].content = content;
109
+ } else {
110
+ conversationFileSystem = { fileContentMap: readFilesConsolidatedOutput, order: Object.keys(readFilesConsolidatedOutput) };
111
+ messages.push({ role: 'system', content: `Whenever you make a call to readFiles, the file content will be loaded into the { fileSystem } object in the message history. Do not respond with fileSystem's content in a message.` });
112
+ const content = JSON.stringify({ fileSystem: conversationFileSystem });
113
+ messages.push({ role: 'system', content });
114
+ }
115
+
116
+ const moderatedMessages = messages
117
+ .filter((message, i) => !readFilesFunctionCallMessageIndexes.includes(i) && !writeFilesFunctionCallMessageIndexes.includes(i))
118
+ ;
119
+ return moderatedMessages;
120
+ }
121
+ }
@@ -0,0 +1,64 @@
1
+ import { ConversationModule, ConversationModuleFactory } from '../../ConversationModule';
2
+ import { Function } from '../../Function';
3
+ import { searchFilesFunctionName } from '../keyword_to_files_index/KeywordToFilesIndexFunctions';
4
+ import { searchLibrariesFunctionName, searchPackagesFunctionName } from '../package/PackageFunctions';
5
+ import { ConversationFsModerator } from './ConversationFsModerator';
6
+ import { fsFunctions, getRecentlyAccessedFilePathsFunction, getRecentlyAccessedFilePathsFunctionName, readFilesFunction, readFilesFunctionName, writeFilesFunction, writeFilesFunctionName } from './FsFunctions';
7
+
8
+ export class ConversationFsModule implements ConversationModule {
9
+ private repoPath: string;
10
+ private recentlyAccessedFilePaths: string[] = [];
11
+
12
+ constructor(repoPath: string) {
13
+ this.repoPath = repoPath;
14
+ }
15
+
16
+ getName(): string {
17
+ return 'Conversation Fs';
18
+ }
19
+
20
+ getSystemMessages(): string[] {
21
+ return [
22
+ `Assume the current working directory is: ${this.repoPath} unless specified by the user`,
23
+ `Pre-pend the current working directory as the base path to file paths when performing file operations, unless specified otherwise by the user`,
24
+ `If the user asks to change the cwd, do not create a new folder, just fail if it's not a valid path`,
25
+ `If the user wants to work in a package, use the ${searchPackagesFunctionName} function and take the directory from the package.json file path and make that the cwd`,
26
+ `You have access to code in a local repo, you can read and write code to and from the file system with the ${readFilesFunctionName} function and the ${writeFilesFunctionName} function`,
27
+ `Before writing to a file that already exists, read the file first and make your changes to its contents`,
28
+ `When reading/writing a file in a specified package, join the package directory with the relative path to form the file path`,
29
+ `When searching for source files, do not look in the dist or node_modules directories`,
30
+ `If you don't know a file path, don't try to guess it, use the ${searchFilesFunctionName} function to find it`,
31
+ `When searching for something (ie. a file to work with/in), unless more context is specified, use the ${searchLibrariesFunctionName} function first, then fall back to functions: ${searchPackagesFunctionName}, ${searchFilesFunctionName}`,
32
+ `After finding a file to work with, assume the user's following question pertains to that file and use ${readFilesFunctionName} to read the file if needed`,
33
+ ];
34
+ }
35
+
36
+ getFunctions(): Function[] {
37
+ return [
38
+ readFilesFunction(this),
39
+ writeFilesFunction(this),
40
+ getRecentlyAccessedFilePathsFunction(this),
41
+ ...fsFunctions
42
+ ];
43
+ }
44
+
45
+ getMessageModerators() {
46
+ return [
47
+ new ConversationFsModerator(),
48
+ ];
49
+ }
50
+
51
+ pushRecentlyAccessedFilePath(filePaths: string[]) {
52
+ this.recentlyAccessedFilePaths.push(...filePaths);
53
+ }
54
+
55
+ getRecentlyAccessedFilePaths() {
56
+ return this.recentlyAccessedFilePaths;
57
+ }
58
+ }
59
+
60
+ export class ConversationFsModuleFactory implements ConversationModuleFactory {
61
+ async createModule(repoPath: string): Promise<ConversationFsModule> {
62
+ return new ConversationFsModule(repoPath);
63
+ }
64
+ }
@@ -0,0 +1,253 @@
1
+ import { File, Fs } from '@proteinjs/util-node';
2
+ import { Function } from '../../Function';
3
+ import { ConversationFsModule } from './ConversationFsModule';
4
+
5
+ export const readFilesFunctionName = 'readFiles';
6
+ export function readFilesFunction(fsModule: ConversationFsModule) {
7
+ return {
8
+ definition: {
9
+ name: readFilesFunctionName,
10
+ description: 'Get the content of files',
11
+ parameters: {
12
+ type: 'object',
13
+ properties: {
14
+ filePaths: {
15
+ type: 'array',
16
+ description: 'Paths to the files',
17
+ items: {
18
+ type: 'string',
19
+ },
20
+ },
21
+ },
22
+ required: ['filePaths']
23
+ },
24
+ },
25
+ call: async (params: { filePaths: string[] }) => {
26
+ fsModule.pushRecentlyAccessedFilePath(params.filePaths);
27
+ return await Fs.readFiles(params.filePaths);
28
+ },
29
+ instructions: [
30
+ `To read files from the local file system, use the ${readFilesFunctionName} function`,
31
+ ],
32
+ }
33
+ }
34
+
35
+ export const writeFilesFunctionName = 'writeFiles';
36
+ export function writeFilesFunction(fsModule: ConversationFsModule) {
37
+ return {
38
+ definition: {
39
+ name: writeFilesFunctionName,
40
+ description: 'Write files to the file system',
41
+ parameters: {
42
+ type: 'object',
43
+ properties: {
44
+ files: {
45
+ type: 'array',
46
+ description: 'Files to write',
47
+ items: {
48
+ type: 'object',
49
+ properties: {
50
+ path: {
51
+ type: 'string',
52
+ description: 'the file path',
53
+ },
54
+ content: {
55
+ type: 'string',
56
+ description: 'the content to write to the file',
57
+ },
58
+ },
59
+ },
60
+ },
61
+ },
62
+ required: ['files']
63
+ },
64
+ },
65
+ call: async (params: { files: File[] }) => {
66
+ fsModule.pushRecentlyAccessedFilePath(params.files.map(file => file.path));
67
+ return await Fs.writeFiles(params.files);
68
+ },
69
+ instructions: [
70
+ `To write files to the local file system, use the ${writeFilesFunctionName} function`,
71
+ ],
72
+ }
73
+ }
74
+
75
+ export const getRecentlyAccessedFilePathsFunctionName = 'getRecentlyAccessedFilePaths';
76
+ export function getRecentlyAccessedFilePathsFunction(fsModule: ConversationFsModule) {
77
+ return {
78
+ definition: {
79
+ name: getRecentlyAccessedFilePathsFunctionName,
80
+ description: 'Get paths of files accessed during this conversation, in order from oldest to newest',
81
+ parameters: {
82
+ type: 'object',
83
+ properties: {},
84
+ required: [],
85
+ },
86
+ },
87
+ call: async () => fsModule.getRecentlyAccessedFilePaths(),
88
+ }
89
+ }
90
+
91
+ const createFolderFunctionName = 'createFolder';
92
+ const createFolderFunction: Function = {
93
+ definition: {
94
+ name: createFolderFunctionName,
95
+ description: 'Create a folder/directory',
96
+ parameters: {
97
+ type: 'object',
98
+ properties: {
99
+ path: {
100
+ type: 'string',
101
+ description: 'Path of the new directory',
102
+ },
103
+ },
104
+ required: ['path']
105
+ },
106
+ },
107
+ call: async (params: { path: string }) => await Fs.createFolder(params.path),
108
+ instructions: [
109
+ `To create a folder on the local file system, use the ${createFolderFunctionName} function`,
110
+ ],
111
+ }
112
+
113
+ export const fileOrDirectoryExistsFunctionName = 'fileOrDirectoryExists';
114
+ export const fileOrDirectoryExistsFunction: Function = {
115
+ definition: {
116
+ name: fileOrDirectoryExistsFunctionName,
117
+ description: 'Check if a file or directory exists',
118
+ parameters: {
119
+ type: 'object',
120
+ properties: {
121
+ path: {
122
+ type: 'string',
123
+ description: 'Path of the file or directory',
124
+ },
125
+ },
126
+ required: ['path']
127
+ },
128
+ },
129
+ call: async (params: { path: string }) => await Fs.exists(params.path),
130
+ instructions: [
131
+ `To check if a file or folder exists on the local file system, use the ${fileOrDirectoryExistsFunctionName} function`,
132
+ ],
133
+ }
134
+
135
+ export const getFilePathsMatchingGlobFunctionName = 'getFilePathsMatchingGlob';
136
+ const getFilePathsMatchingGlobFunction: Function = {
137
+ definition: {
138
+ name: getFilePathsMatchingGlobFunctionName,
139
+ description: 'Get file paths matching a glob',
140
+ parameters: {
141
+ type: 'object',
142
+ properties: {
143
+ dirPrefix: {
144
+ type: 'string',
145
+ description: 'Directory to recursively search for files',
146
+ },
147
+ glob: {
148
+ type: 'string',
149
+ description: 'File matching pattern',
150
+ },
151
+ globIgnorePatterns: {
152
+ type: 'array',
153
+ description: 'Directories to ignore',
154
+ items: {
155
+ type: 'string',
156
+ },
157
+ },
158
+ },
159
+ required: ['dirPrefix', 'glob']
160
+ },
161
+ },
162
+ call: async (params: { dirPrefix: string, glob: string, globIgnorePatterns?: string[] }) => await Fs.getFilePathsMatchingGlob(params.dirPrefix, params.glob, params.globIgnorePatterns),
163
+ instructions: [
164
+ `To get file paths matching a glob, use the ${getFilePathsMatchingGlobFunctionName} function`,
165
+ ],
166
+ }
167
+
168
+ export const renameFunctionName = 'renameFileOrDirectory';
169
+ const renameFunction: Function = {
170
+ definition: {
171
+ name: renameFunctionName,
172
+ description: 'Rename a file or directory',
173
+ parameters: {
174
+ type: 'object',
175
+ properties: {
176
+ oldPath: {
177
+ type: 'string',
178
+ description: 'Original path of the file or directory',
179
+ },
180
+ newName: {
181
+ type: 'string',
182
+ description: 'New name for the file or directory',
183
+ },
184
+ },
185
+ required: ['oldPath', 'newName']
186
+ },
187
+ },
188
+ call: async (params: { oldPath: string, newName: string }) => await Fs.rename(params.oldPath, params.newName),
189
+ instructions: [
190
+ `To rename a file or directory, use the ${renameFunctionName} function`,
191
+ ],
192
+ }
193
+
194
+ export const copyFunctionName = 'copyFileOrDirectory';
195
+ const copyFunction: Function = {
196
+ definition: {
197
+ name: copyFunctionName,
198
+ description: 'Copy a file or directory',
199
+ parameters: {
200
+ type: 'object',
201
+ properties: {
202
+ sourcePath: {
203
+ type: 'string',
204
+ description: 'Path of the source file or directory',
205
+ },
206
+ destinationPath: {
207
+ type: 'string',
208
+ description: 'Destination path for the copied file or directory',
209
+ },
210
+ },
211
+ required: ['sourcePath', 'destinationPath']
212
+ },
213
+ },
214
+ call: async (params: { sourcePath: string, destinationPath: string }) => await Fs.copy(params.sourcePath, params.destinationPath),
215
+ instructions: [
216
+ `To copy a file or directory, use the ${copyFunctionName} function`,
217
+ ],
218
+ }
219
+
220
+ export const moveFunctionName = 'moveFileOrDirectory';
221
+ const moveFunction: Function = {
222
+ definition: {
223
+ name: moveFunctionName,
224
+ description: 'Move a file or directory',
225
+ parameters: {
226
+ type: 'object',
227
+ properties: {
228
+ sourcePath: {
229
+ type: 'string',
230
+ description: 'Path of the source file or directory',
231
+ },
232
+ destinationPath: {
233
+ type: 'string',
234
+ description: 'Destination path for the moved file or directory',
235
+ },
236
+ },
237
+ required: ['sourcePath', 'destinationPath']
238
+ },
239
+ },
240
+ call: async (params: { sourcePath: string, destinationPath: string }) => await Fs.move(params.sourcePath, params.destinationPath),
241
+ instructions: [
242
+ `To move a file or directory, use the ${moveFunctionName} function`,
243
+ ],
244
+ }
245
+
246
+ export const fsFunctions: Function[] = [
247
+ createFolderFunction,
248
+ fileOrDirectoryExistsFunction,
249
+ getFilePathsMatchingGlobFunction,
250
+ renameFunction,
251
+ copyFunction,
252
+ moveFunction,
253
+ ]
@@ -0,0 +1,39 @@
1
+ import { gitFunctions } from '@proteinjs/util-node';
2
+ import { ConversationModule, ConversationModuleFactory } from '../../ConversationModule';
3
+ import { Function } from '../../Function';
4
+
5
+ export class GitModule implements ConversationModule {
6
+ private repoPath: string;
7
+
8
+ constructor(repoPath: string) {
9
+ this.repoPath = repoPath;
10
+ }
11
+
12
+ getName(): string {
13
+ return 'Git';
14
+ }
15
+
16
+ getSystemMessages(): string[] {
17
+ return [
18
+ `After we make code changes (write to files), ask the user if they'd like to commit and sync those changes`,
19
+ `If they want to commit, offer them 3 generated messages that summarize the changes to choose from, as well as the option to write a custom message`,
20
+ `The generated messages should be 1-3 sentences in length, and each generated option should be a different length of summary`,
21
+ ];
22
+ }
23
+
24
+ getFunctions(): Function[] {
25
+ return [
26
+ ...gitFunctions,
27
+ ];
28
+ }
29
+
30
+ getMessageModerators() {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ export class GitModuleFactory implements ConversationModuleFactory {
36
+ async createModule(repoPath: string): Promise<GitModule> {
37
+ return new GitModule(repoPath);
38
+ }
39
+ }