@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,55 @@
1
+ import { KeywordToFilesIndexModule } from './KeywordToFilesIndexModule'
2
+
3
+ export const searchFilesFunctionName = 'searchFiles';
4
+ export const searchFilesFunction = (module: KeywordToFilesIndexModule) => {
5
+ return {
6
+ definition: {
7
+ name: searchFilesFunctionName,
8
+ description: 'Get file paths to files that contain keyword',
9
+ parameters: {
10
+ type: 'object',
11
+ properties: {
12
+ keyword: {
13
+ type: 'string',
14
+ description: 'Search files for this keyword'
15
+ },
16
+ },
17
+ required: ['keyword']
18
+ },
19
+ },
20
+ call: async (params: { keyword: string }) => module.searchFiles(params),
21
+ instructions: [
22
+ `If the user is trying to interact with a file, but does not provide a path, you can find file paths that match a keyword using the ${searchFilesFunctionName} function`,
23
+ `Only call functions that take in filePaths with valid file paths, if you don't know the valid file path try and search for it by keyword with the ${searchFilesFunctionName} function`,
24
+ `If the user references a file in a package without providing a path, use the ${searchFilesFunctionName} function on the keyword to find potentially relevant files, and choose the one that references the package name in its path`,
25
+ ],
26
+ }
27
+ }
28
+
29
+ // {
30
+ // definition: {
31
+ // name: 'getDeclarations',
32
+ // description: 'Get the typescript declarations of files',
33
+ // parameters: {
34
+ // type: 'object',
35
+ // properties: {
36
+ // tsFilePaths: {
37
+ // type: 'array',
38
+ // description: 'Paths to the files',
39
+ // items: {
40
+ // type: 'string',
41
+ // },
42
+ // },
43
+ // includeDependencyDeclarations: {
44
+ // type: 'boolean',
45
+ // description: 'if true, returns declarations for input tsFilePaths and all dependencies. defaults to false.'
46
+ // },
47
+ // },
48
+ // required: ['tsFilePaths']
49
+ // },
50
+ // },
51
+ // call: async (params: { tsFilePaths: string[] }) => this.repo.getDeclarations(params),
52
+ // instructions: [
53
+ // `Favor calling getDeclarations over readFiles if full file content is not needed`,
54
+ // ],
55
+ // },
@@ -0,0 +1,90 @@
1
+ import { Logger } from '@proteinjs/util';
2
+ import { Fs } from '@proteinjs/util-node';
3
+ import { ConversationModule, ConversationModuleFactory } from '../../ConversationModule';
4
+ import { Function } from '../../Function';
5
+ import path from 'path';
6
+ import { searchFilesFunction, searchFilesFunctionName } from './KeywordToFilesIndexFunctions';
7
+
8
+ export type KeywordToFilesIndexModuleParams = {
9
+ dir: string,
10
+ keywordFilesIndex: { [keyword: string]: string[] /** file paths */ },
11
+ }
12
+
13
+ export class KeywordToFilesIndexModule implements ConversationModule {
14
+ private logger = new Logger(this.constructor.name);
15
+ params: KeywordToFilesIndexModuleParams;
16
+
17
+ constructor(params: KeywordToFilesIndexModuleParams) {
18
+ this.params = params;
19
+ }
20
+
21
+ getName(): string {
22
+ return 'Keyword to files index';
23
+ }
24
+
25
+ searchFiles(params: { keyword: string }) {
26
+ this.logger.info(`Searching for file, keyword: ${params.keyword}`);
27
+ const filePaths = this.params.keywordFilesIndex[params.keyword];
28
+ return filePaths || [];
29
+ }
30
+
31
+ getSystemMessages(): string[] {
32
+ return [
33
+ `If you're searching for something, use the ${searchFilesFunctionName} function to find a file matching the search string`,
34
+ ];
35
+ }
36
+
37
+ getFunctions(): Function[] {
38
+ return [
39
+ searchFilesFunction(this),
40
+ ];
41
+ }
42
+
43
+ getMessageModerators() {
44
+ return [];
45
+ }
46
+ }
47
+
48
+ export class KeywordToFilesIndexModuleFactory implements ConversationModuleFactory {
49
+ private logger = new Logger(this.constructor.name);
50
+
51
+ async createModule(repoPath: string): Promise<KeywordToFilesIndexModule> {
52
+ this.logger.debug(`Creating module for repo: ${repoPath}`);
53
+ let repoParams: KeywordToFilesIndexModuleParams = { keywordFilesIndex: {}, dir: repoPath };
54
+ repoParams.keywordFilesIndex = await this.createKeywordFilesIndex(repoPath, ['**/node-typescript-parser/**']);
55
+ this.logger.debug(`Created module for repo: ${repoPath}`);
56
+ return new KeywordToFilesIndexModule(repoParams);
57
+ }
58
+
59
+ /**
60
+ * Create keyword-files index for the given base directory.
61
+ *
62
+ * @param baseDir - The directory to start the file search from.
63
+ * @returns An index with keywords mapped to file paths.
64
+ */
65
+ async createKeywordFilesIndex(baseDir: string, globIgnorePatterns: string[] = []): Promise<{ [keyword: string]: string[] }> {
66
+ // Ensure the base directory has a trailing slash
67
+ if (!baseDir.endsWith(path.sep)) {
68
+ baseDir += path.sep;
69
+ }
70
+
71
+ // Get all file paths, recursively, excluding node_modules and dist directories
72
+ const filePaths = await Fs.getFilePaths(baseDir, ['**/node_modules/**', '**/dist/**', ...globIgnorePatterns]);
73
+
74
+ const keywordFilesIndex: { [keyword: string]: string[] } = {};
75
+
76
+ // Process each file path
77
+ for (const filePath of filePaths) {
78
+ const fileName = path.parse(filePath).name; // Get file name without extension
79
+
80
+ if (!keywordFilesIndex[fileName]) {
81
+ keywordFilesIndex[fileName] = [];
82
+ }
83
+
84
+ this.logger.debug(`fileName: ${fileName}, filePath: ${filePath}`);
85
+ keywordFilesIndex[fileName].push(filePath);
86
+ }
87
+
88
+ return keywordFilesIndex;
89
+ }
90
+ }
@@ -0,0 +1,210 @@
1
+ import { Package, PackageUtil } from '@proteinjs/util-node';
2
+ import { Function } from '../../Function';
3
+ import { PackageModule } from './PackageModule';
4
+
5
+ export const installPackagesFunctionName = 'installPackages';
6
+ export const installPackagesFunction: Function = {
7
+ definition: {
8
+ name: installPackagesFunctionName,
9
+ description: 'Get the content of files',
10
+ parameters: {
11
+ type: 'object',
12
+ properties: {
13
+ packages: {
14
+ type: 'array',
15
+ description: 'Packages to install',
16
+ items: {
17
+ type: 'object',
18
+ properties: {
19
+ name: {
20
+ type: 'string',
21
+ description: 'Name of package',
22
+ },
23
+ version: {
24
+ type: 'string',
25
+ description: 'Version of package',
26
+ },
27
+ exactVersion: {
28
+ type: 'boolean',
29
+ description: 'If true, freeze the version so subsequent installs do not bump minor versions',
30
+ },
31
+ development: {
32
+ type: 'boolean',
33
+ description: 'If true, install as a dev dependency',
34
+ },
35
+ },
36
+ required: ['name'],
37
+ },
38
+ },
39
+ cwdPath: {
40
+ type: 'string',
41
+ description: 'The directory to install packages in',
42
+ }
43
+ },
44
+ required: ['packages', 'cwdPath']
45
+ },
46
+ },
47
+ call: async (params: { packages: Package[], cwdPath: string }) => await PackageUtil.installPackages(params.packages, params.cwdPath),
48
+ instructions: [
49
+ `To install a package, use the ${installPackagesFunctionName} function`,
50
+ ],
51
+ }
52
+
53
+ export const runPackageScriptFunctionName = 'runPackageScript';
54
+ const runPackageScriptFunction: Function = {
55
+ definition: {
56
+ name: runPackageScriptFunctionName,
57
+ description: 'Run `npm run x`, where `x` is an existing script in a package.json',
58
+ parameters: {
59
+ type: 'object',
60
+ properties: {
61
+ name: {
62
+ type: 'string',
63
+ description: 'Name of the script',
64
+ },
65
+ cwdPath: {
66
+ type: 'string',
67
+ description: 'If omitted, defaults to process.cwd',
68
+ }
69
+ },
70
+ required: ['name']
71
+ },
72
+ },
73
+ call: async (params: { name: string, cwdPath?: string }) => await PackageUtil.runPackageScript(params.name, params.cwdPath),
74
+ instructions: [
75
+ `To run a npm script (such as start, test, or watch), use the ${runPackageScriptFunctionName} function`,
76
+ ],
77
+ }
78
+
79
+ export const searchPackagesFunctionName = 'searchPackages';
80
+ export function searchPackagesFunction(packageModule: PackageModule) {
81
+ return {
82
+ definition: {
83
+ name: searchPackagesFunctionName,
84
+ description: 'Get package.json file paths for package names that include the keyword',
85
+ parameters: {
86
+ type: 'object',
87
+ properties: {
88
+ keyword: {
89
+ type: 'string',
90
+ description: 'Keyword to match package names',
91
+ }
92
+ },
93
+ required: ['keyword']
94
+ },
95
+ },
96
+ call: async (params: { keyword: string }) => await packageModule.searchPackages(params.keyword),
97
+ instructions: [
98
+ `To search for packages in the local repo, use the ${searchPackagesFunctionName} function`,
99
+ ],
100
+ }
101
+ }
102
+
103
+ export const searchLibrariesFunctionName = 'searchLibraries';
104
+ export function searchLibrariesFunction(packageModule: PackageModule) {
105
+ return {
106
+ definition: {
107
+ name: searchLibrariesFunctionName,
108
+ description: 'Return libraries that match the keyword',
109
+ parameters: {
110
+ type: 'object',
111
+ properties: {
112
+ keyword: {
113
+ type: 'string',
114
+ description: 'Keyword to match file names',
115
+ }
116
+ },
117
+ required: ['keyword']
118
+ },
119
+ },
120
+ call: async (params: { keyword: string }) => await packageModule.searchLibraries(params.keyword),
121
+ instructions: [
122
+ `To search for libraries in the local repo, use the ${searchLibrariesFunctionName} function`,
123
+ ],
124
+ }
125
+ }
126
+
127
+ export const generateTypescriptDeclarationsFunctionName = 'generateTypescriptDesclarations';
128
+ export const generateTypescriptDeclarationsFunction = {
129
+ definition: {
130
+ name: generateTypescriptDeclarationsFunctionName,
131
+ description: 'Return the typescript declarations for the files',
132
+ parameters: {
133
+ type: 'object',
134
+ properties: {
135
+ tsFilePaths: {
136
+ type: 'array',
137
+ description: 'File paths to generate declarations for',
138
+ items: {
139
+ type: 'string',
140
+ }
141
+ }
142
+ },
143
+ required: ['tsFilePaths']
144
+ },
145
+ },
146
+ call: async (params: { tsFilePaths: string[] }) => PackageUtil.generateTypescriptDeclarations(params),
147
+ instructions: [
148
+ `To generate typescript declarations for a local file, use the ${generateTypescriptDeclarationsFunctionName} function`,
149
+ ],
150
+ }
151
+
152
+ export const npmInstallFunctionName = 'npmInstall';
153
+ export const npmInstallFunction: Function = {
154
+ definition: {
155
+ name: npmInstallFunctionName,
156
+ description: 'Run `npm install` in the specified directory',
157
+ parameters: {
158
+ type: 'object',
159
+ properties: {
160
+ cwdPath: {
161
+ type: 'string',
162
+ description: 'Directory to execute the command from',
163
+ }
164
+ },
165
+ required: ['cwdPath']
166
+ },
167
+ },
168
+ call: async (params: { cwdPath: string }) => await PackageUtil.npmInstall(params.cwdPath),
169
+ instructions: [
170
+ `To run npm install in a specific directory, use the ${npmInstallFunctionName} function`,
171
+ ],
172
+ }
173
+
174
+ export const uninstallPackagesFunctionName = 'uninstallPackages';
175
+ export const uninstallPackagesFunction: Function = {
176
+ definition: {
177
+ name: uninstallPackagesFunctionName,
178
+ description: 'Uninstall packages',
179
+ parameters: {
180
+ type: 'object',
181
+ properties: {
182
+ packageNames: {
183
+ type: 'array',
184
+ description: 'Packages to uninstall',
185
+ items: {
186
+ type: 'string',
187
+ description: 'Name of package',
188
+ },
189
+ },
190
+ cwdPath: {
191
+ type: 'string',
192
+ description: 'The directory to uninstall packages from',
193
+ }
194
+ },
195
+ required: ['packageNames', 'cwdPath']
196
+ },
197
+ },
198
+ call: async (params: { packageNames: string[], cwdPath: string }) => await PackageUtil.uninstallPackages(params.packageNames, params.cwdPath),
199
+ instructions: [
200
+ `To uninstall a package, use the ${uninstallPackagesFunctionName} function`,
201
+ ],
202
+ }
203
+
204
+ export const packageFunctions: Function[] = [
205
+ installPackagesFunction,
206
+ runPackageScriptFunction,
207
+ generateTypescriptDeclarationsFunction,
208
+ npmInstallFunction,
209
+ uninstallPackagesFunction,
210
+ ]
@@ -0,0 +1,106 @@
1
+ import { Fs } from '@proteinjs/util-node';
2
+ import { ConversationModule, ConversationModuleFactory } from '../../ConversationModule';
3
+ import { Function } from '../../Function';
4
+ import { packageFunctions, searchLibrariesFunction, searchLibrariesFunctionName, searchPackagesFunction, searchPackagesFunctionName } from './PackageFunctions';
5
+ import path from 'path';
6
+ import { searchFilesFunctionName } from '../keyword_to_files_index/KeywordToFilesIndexFunctions';
7
+ import { readFilesFunctionName } from '../conversation_fs/FsFunctions';
8
+
9
+ export type Library = {
10
+ fileName: string,
11
+ filePath: string,
12
+ packageName: string,
13
+ }
14
+
15
+ export type LibraryImport = {
16
+ importStatements: string[],
17
+ typescriptDeclaration: string,
18
+ }
19
+
20
+ export class PackageModule implements ConversationModule {
21
+ private repoPath: string;
22
+
23
+ constructor(repoPath: string) {
24
+ this.repoPath = repoPath;
25
+ }
26
+
27
+ getName(): string {
28
+ return 'Package';
29
+ }
30
+
31
+ getSystemMessages(): string[] {
32
+ return [
33
+ `When generating code, prefer importing code from local packages`,
34
+ `Use the ${searchPackagesFunctionName} function on every package you plan to install to derermine if the package is in the local repo; if it is, calculate the relative path from the cwd package to the package being installed, use that path as the version when installing the package`,
35
+ // `When generating code, use the searchFiles function to find all file paths to index.ts files; these are the local apis we have access to`,
36
+ // `When generating import statements, use the searchFiles function to find all file paths to package.json files; if importing from a local package, make sure you import via its package if it is not a local file to the package we're generating code in`,
37
+ `When generating import statements from another package, do not use relative paths`,
38
+ ];
39
+ }
40
+
41
+ getFunctions(): Function[] {
42
+ return [
43
+ ...packageFunctions,
44
+ searchPackagesFunction(this),
45
+ searchLibrariesFunction(this),
46
+ ];
47
+ }
48
+
49
+ getMessageModerators() {
50
+ return [];
51
+ }
52
+
53
+ /**
54
+ * @param keyword either the package name or some substring
55
+ * @return string[] of file paths
56
+ */
57
+ async searchPackages(keyword: string): Promise<string[]> {
58
+ const matchingPackageJsonPaths: string[] = [];
59
+ const packageJsonFilePaths = await Fs.getFilePathsMatchingGlob(this.repoPath, '**/package.json', ['**/node_modules/**', '**/dist/**']);
60
+ const packageJsonFileMap = await Fs.readFiles(packageJsonFilePaths);
61
+ for (let packageJsonFilePath of Object.keys(packageJsonFileMap)) {
62
+ const packageJson = JSON.parse(packageJsonFileMap[packageJsonFilePath]);
63
+ if (packageJson.name.toLowerCase().includes(keyword.toLocaleLowerCase()))
64
+ matchingPackageJsonPaths.push(packageJsonFilePath);
65
+ }
66
+
67
+ return matchingPackageJsonPaths;
68
+ }
69
+
70
+ /**
71
+ * Search packages in repo for file names that include keyword
72
+ * @param keyword substring of file name to search for
73
+ * @returns all libraries in the repo matching the keyword
74
+ */
75
+ async searchLibraries(keyword: string): Promise<Library[]> {
76
+ const matchingLibraries: Library[] = [];
77
+ const packageJsonFilePaths = await Fs.getFilePathsMatchingGlob(this.repoPath, '**/package.json', ['**/node_modules/**', '**/dist/**']);
78
+ const packageJsonFileMap = await Fs.readFiles(packageJsonFilePaths);
79
+ for (let packageJsonFilePath of Object.keys(packageJsonFileMap)) {
80
+ const packageJson = JSON.parse(packageJsonFileMap[packageJsonFilePath]);
81
+ const packageJsonFilePathParts = packageJsonFilePath.split(path.sep);
82
+ packageJsonFilePathParts.pop();
83
+ const packageDirectory = packageJsonFilePathParts.join(path.sep);
84
+ const srcFilePaths = await Fs.getFilePathsMatchingGlob(path.join(packageDirectory, 'src'), '**/*.ts', ['**/node_modules/**', '**/dist/**']);
85
+ for (let srcFilePath of srcFilePaths) {
86
+ const fileNameWithExtension = path.basename(srcFilePath);
87
+ if (fileNameWithExtension.includes(keyword)) {
88
+ const fileName = path.basename(srcFilePath, path.extname(srcFilePath));
89
+ matchingLibraries.push({
90
+ fileName,
91
+ filePath: srcFilePath,
92
+ packageName: packageJson.name,
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ return matchingLibraries;
99
+ }
100
+ }
101
+
102
+ export class PackageModuleFactory implements ConversationModuleFactory {
103
+ async createModule(repoPath: string): Promise<PackageModule> {
104
+ return new PackageModule(repoPath);
105
+ }
106
+ }
@@ -0,0 +1,57 @@
1
+ import { ChatCompletionMessageParam } from 'openai/resources/chat';
2
+ import { Logger } from '@proteinjs/util';
3
+
4
+ export interface MessageHistoryParams {
5
+ enforceMessageLimit?: boolean;
6
+ maxMessages: number; // max number of non-system messages to retain, fifo
7
+ }
8
+
9
+ export class MessageHistory {
10
+ private logger = new Logger(this.constructor.name);
11
+ private messages: ChatCompletionMessageParam[] = [];
12
+ private params: MessageHistoryParams;
13
+
14
+ constructor(params?: Partial<MessageHistoryParams>) {
15
+ this.params = Object.assign({ maxMessages: 20 }, params);
16
+ }
17
+
18
+ getMessages() {
19
+ return this.messages;
20
+ }
21
+
22
+ toString() {
23
+ return this.messages.map(message => message.content).join('. ');
24
+ }
25
+
26
+ setMessages(messages: ChatCompletionMessageParam[]): MessageHistory {
27
+ this.messages = messages;
28
+ this.prune();
29
+ return this;
30
+ }
31
+
32
+ push(messages: ChatCompletionMessageParam[]): MessageHistory {
33
+ this.messages.push(...messages);
34
+ this.prune();
35
+ return this;
36
+ }
37
+
38
+ prune() {
39
+ if (this.params.enforceMessageLimit === false)
40
+ return;
41
+
42
+ let messageCount = 0;
43
+ const messagesToRemoveIndexes: number[] = [];
44
+ for (let i = this.messages.length - 1; i >= 0; i--) {
45
+ const message = this.messages[i];
46
+ if (message.role == 'system')
47
+ continue;
48
+
49
+ messageCount++;
50
+ if (messageCount > this.params.maxMessages)
51
+ messagesToRemoveIndexes.push(i);
52
+ }
53
+
54
+ this.messages = this.messages.filter((message, i) => !messagesToRemoveIndexes.includes(i));
55
+ this.logger.debug(`Pruned ${messagesToRemoveIndexes.length} messages`);
56
+ }
57
+ }
@@ -0,0 +1,6 @@
1
+ import { ChatCompletionMessageParam } from 'openai/resources/chat';
2
+
3
+ export interface MessageModerator {
4
+ // given a set of messages, modify and return
5
+ observe(messages: ChatCompletionMessageParam[]): ChatCompletionMessageParam[];
6
+ }
@@ -0,0 +1,12 @@
1
+ export type Question = {
2
+ text: string,
3
+ optional?: boolean,
4
+ }
5
+
6
+ export type ConversationTemplate = {
7
+ name: string,
8
+ keywords: string[],
9
+ description: string,
10
+ questions: Question[],
11
+ instructions: () => Promise<string[]>,
12
+ }
@@ -0,0 +1,43 @@
1
+ import { ConversationTemplateModule } from './ConversationTemplateModule';
2
+
3
+ export const searchConversationTemplatesFunctionName = 'searchConversationTemplates';
4
+ export const searchConversationTemplatesFunction = (repo: ConversationTemplateModule) => {
5
+ return {
6
+ definition: {
7
+ name: searchConversationTemplatesFunctionName,
8
+ description: 'Get the conversation template names for templates matching the keyword',
9
+ parameters: {
10
+ type: 'object',
11
+ properties: {
12
+ keyword: {
13
+ type: 'string',
14
+ description: 'Search for conversation template names that match this keyword',
15
+ },
16
+ },
17
+ required: ['keyword']
18
+ },
19
+ },
20
+ call: async (params: { keyword: string }) => repo.searchConversationTemplates(params.keyword),
21
+ }
22
+ }
23
+
24
+ export const getConversationTemplateFunctionName = 'getConversationTemplate';
25
+ export const getConversationTemplateFunction = (repo: ConversationTemplateModule) => {
26
+ return {
27
+ definition: {
28
+ name: getConversationTemplateFunctionName,
29
+ description: 'Get the conversation template matching the name',
30
+ parameters: {
31
+ type: 'object',
32
+ properties: {
33
+ conversationTemplateName: {
34
+ type: 'string',
35
+ description: 'Get the conversation template that has this name',
36
+ },
37
+ },
38
+ required: ['conversationTemplateName']
39
+ },
40
+ },
41
+ call: async (params: { conversationTemplateName: string }) => await repo.getConversationTemplate(params.conversationTemplateName),
42
+ }
43
+ }
@@ -0,0 +1,83 @@
1
+ import { Logger } from '@proteinjs/util';
2
+ import { ConversationTemplate } from './ConversationTemplate';
3
+ import { createPackageConversationTemplate } from './createPackage/CreatePackageConversationTemplate';
4
+ import { ConversationModule, ConversationModuleFactory } from '../ConversationModule';
5
+ import { getConversationTemplateFunction, getConversationTemplateFunctionName, searchConversationTemplatesFunction, searchConversationTemplatesFunctionName } from './ConversationTemplateFunctions';
6
+ import { createCodeConversationTemplate } from './createCode/CreateCodeConversationTemplate';
7
+ import { createAppTemplate } from './createApp/CreateAppTemplate';
8
+
9
+ const conversationTemplates: ConversationTemplate[] = [
10
+ createPackageConversationTemplate,
11
+ createCodeConversationTemplate,
12
+ createAppTemplate,
13
+ ];
14
+
15
+ export type ConversationTemplateModuleParams = {
16
+ conversationTemplates: { [conversationTemplateName: string]: ConversationTemplate},
17
+ conversationTemplateKeywordIndex: { [keyword: string]: string[] /** conversationTemplateNames */ }
18
+ }
19
+
20
+ export class ConversationTemplateModule implements ConversationModule {
21
+ private logger = new Logger(this.constructor.name);
22
+ params: ConversationTemplateModuleParams;
23
+
24
+ constructor(params: ConversationTemplateModuleParams) {
25
+ this.params = params;
26
+ }
27
+
28
+ getName(): string {
29
+ return 'Conversation Template';
30
+ }
31
+
32
+ searchConversationTemplates(keyword: string) {
33
+ this.logger.info(`Searching for conversation template, keyword: ${keyword}`);
34
+ const conversationNames = this.params.conversationTemplateKeywordIndex[keyword];
35
+ return conversationNames || [];
36
+ }
37
+
38
+ async getConversationTemplate(conversationTemplateName: string): Promise<Omit<ConversationTemplate, 'instructions'> & { instructions: string[] }> {
39
+ const conversationTemplate = this.params.conversationTemplates[conversationTemplateName];
40
+ if (!conversationTemplate)
41
+ return {} as any;
42
+
43
+ const instructions = await conversationTemplate.instructions();
44
+ return Object.assign(conversationTemplate, { instructions });
45
+ }
46
+
47
+ getSystemMessages() {
48
+ return [
49
+ `Whenever the user wants to create or talk about something, fisrt use the ${searchConversationTemplatesFunctionName} function to identify if there are relevant conversation templates to use`,
50
+ `Use the ${getConversationTemplateFunctionName} function to get the conversation template`,
51
+ `Once you've identified a conversation template that's relevant to the conversation, ask the user if they'd like to use that conversation template`,
52
+ `If they want to engage in the templated conversation, ask only the conversation template questions (template.questions), then use the user's answers to carry out the conversation template instructions (template.instructions)`,
53
+ ];
54
+ }
55
+
56
+ getFunctions() {
57
+ return [
58
+ searchConversationTemplatesFunction(this),
59
+ getConversationTemplateFunction(this),
60
+ ];
61
+ }
62
+
63
+ getMessageModerators() {
64
+ return [];
65
+ }
66
+ }
67
+
68
+ export class ConversationTemplateModuleFactory implements ConversationModuleFactory {
69
+ async createModule(repoPath: string) {
70
+ const params: ConversationTemplateModuleParams = { conversationTemplates: {}, conversationTemplateKeywordIndex: {} };
71
+ for (let conversationTemplate of conversationTemplates) {
72
+ params.conversationTemplates[conversationTemplate.name] = conversationTemplate;
73
+ for (let keyword of conversationTemplate.keywords) {
74
+ if (!params.conversationTemplateKeywordIndex[keyword])
75
+ params.conversationTemplateKeywordIndex[keyword] = [];
76
+
77
+ params.conversationTemplateKeywordIndex[keyword].push(conversationTemplate.name);
78
+ }
79
+ }
80
+
81
+ return new ConversationTemplateModule(params);
82
+ }
83
+ }