@theia/ai-core 1.46.0-next.241

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 (209) hide show
  1. package/README.md +31 -0
  2. package/data/prompttemplate.tmLanguage.json +107 -0
  3. package/lib/browser/ai-activation-service.d.ts +17 -0
  4. package/lib/browser/ai-activation-service.d.ts.map +1 -0
  5. package/lib/browser/ai-activation-service.js +63 -0
  6. package/lib/browser/ai-activation-service.js.map +1 -0
  7. package/lib/browser/ai-command-handler-factory.d.ts +4 -0
  8. package/lib/browser/ai-command-handler-factory.d.ts.map +1 -0
  9. package/lib/browser/ai-command-handler-factory.js +20 -0
  10. package/lib/browser/ai-command-handler-factory.js.map +1 -0
  11. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts +28 -0
  12. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -0
  13. package/lib/browser/ai-configuration/agent-configuration-widget.js +242 -0
  14. package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -0
  15. package/lib/browser/ai-configuration/ai-configuration-service.d.ts +13 -0
  16. package/lib/browser/ai-configuration/ai-configuration-service.d.ts.map +1 -0
  17. package/lib/browser/ai-configuration/ai-configuration-service.js +44 -0
  18. package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -0
  19. package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts +12 -0
  20. package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts.map +1 -0
  21. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +41 -0
  22. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -0
  23. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +20 -0
  24. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -0
  25. package/lib/browser/ai-configuration/ai-configuration-widget.js +88 -0
  26. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -0
  27. package/lib/browser/ai-configuration/language-model-renderer.d.ts +13 -0
  28. package/lib/browser/ai-configuration/language-model-renderer.d.ts.map +1 -0
  29. package/lib/browser/ai-configuration/language-model-renderer.js +104 -0
  30. package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -0
  31. package/lib/browser/ai-configuration/template-settings-renderer.d.ts +13 -0
  32. package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -0
  33. package/lib/browser/ai-configuration/template-settings-renderer.js +71 -0
  34. package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -0
  35. package/lib/browser/ai-configuration/variable-configuration-widget.d.ts +20 -0
  36. package/lib/browser/ai-configuration/variable-configuration-widget.d.ts.map +1 -0
  37. package/lib/browser/ai-configuration/variable-configuration-widget.js +99 -0
  38. package/lib/browser/ai-configuration/variable-configuration-widget.js.map +1 -0
  39. package/lib/browser/ai-core-frontend-application-contribution.d.ts +10 -0
  40. package/lib/browser/ai-core-frontend-application-contribution.d.ts.map +1 -0
  41. package/lib/browser/ai-core-frontend-application-contribution.js +46 -0
  42. package/lib/browser/ai-core-frontend-application-contribution.js.map +1 -0
  43. package/lib/browser/ai-core-frontend-module.d.ts +4 -0
  44. package/lib/browser/ai-core-frontend-module.d.ts.map +1 -0
  45. package/lib/browser/ai-core-frontend-module.js +122 -0
  46. package/lib/browser/ai-core-frontend-module.js.map +1 -0
  47. package/lib/browser/ai-core-preferences.d.ts +29 -0
  48. package/lib/browser/ai-core-preferences.d.ts.map +1 -0
  49. package/lib/browser/ai-core-preferences.js +98 -0
  50. package/lib/browser/ai-core-preferences.js.map +1 -0
  51. package/lib/browser/ai-settings-service.d.ts +14 -0
  52. package/lib/browser/ai-settings-service.d.ts.map +1 -0
  53. package/lib/browser/ai-settings-service.js +56 -0
  54. package/lib/browser/ai-settings-service.js.map +1 -0
  55. package/lib/browser/ai-view-contribution.d.ts +14 -0
  56. package/lib/browser/ai-view-contribution.d.ts.map +1 -0
  57. package/lib/browser/ai-view-contribution.js +71 -0
  58. package/lib/browser/ai-view-contribution.js.map +1 -0
  59. package/lib/browser/frontend-language-model-registry.d.ts +39 -0
  60. package/lib/browser/frontend-language-model-registry.d.ts.map +1 -0
  61. package/lib/browser/frontend-language-model-registry.js +321 -0
  62. package/lib/browser/frontend-language-model-registry.js.map +1 -0
  63. package/lib/browser/frontend-prompt-customization-service.d.ts +33 -0
  64. package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -0
  65. package/lib/browser/frontend-prompt-customization-service.js +237 -0
  66. package/lib/browser/frontend-prompt-customization-service.js.map +1 -0
  67. package/lib/browser/frontend-variable-service.d.ts +6 -0
  68. package/lib/browser/frontend-variable-service.d.ts.map +1 -0
  69. package/lib/browser/frontend-variable-service.js +31 -0
  70. package/lib/browser/frontend-variable-service.js.map +1 -0
  71. package/lib/browser/index.d.ts +11 -0
  72. package/lib/browser/index.d.ts.map +1 -0
  73. package/lib/browser/index.js +29 -0
  74. package/lib/browser/index.js.map +1 -0
  75. package/lib/browser/prompttemplate-contribution.d.ts +29 -0
  76. package/lib/browser/prompttemplate-contribution.d.ts.map +1 -0
  77. package/lib/browser/prompttemplate-contribution.js +208 -0
  78. package/lib/browser/prompttemplate-contribution.js.map +1 -0
  79. package/lib/browser/theia-variable-contribution.d.ts +13 -0
  80. package/lib/browser/theia-variable-contribution.d.ts.map +1 -0
  81. package/lib/browser/theia-variable-contribution.js +64 -0
  82. package/lib/browser/theia-variable-contribution.js.map +1 -0
  83. package/lib/common/agent-service.d.ts +66 -0
  84. package/lib/common/agent-service.d.ts.map +1 -0
  85. package/lib/common/agent-service.js +92 -0
  86. package/lib/common/agent-service.js.map +1 -0
  87. package/lib/common/agent.d.ts +45 -0
  88. package/lib/common/agent.d.ts.map +1 -0
  89. package/lib/common/agent.js +20 -0
  90. package/lib/common/agent.js.map +1 -0
  91. package/lib/common/agents-variable-contribution.d.ts +19 -0
  92. package/lib/common/agents-variable-contribution.d.ts.map +1 -0
  93. package/lib/common/agents-variable-contribution.js +56 -0
  94. package/lib/common/agents-variable-contribution.js.map +1 -0
  95. package/lib/common/communication-recording-service.d.ts +29 -0
  96. package/lib/common/communication-recording-service.d.ts.map +1 -0
  97. package/lib/common/communication-recording-service.js +20 -0
  98. package/lib/common/communication-recording-service.js.map +1 -0
  99. package/lib/common/index.d.ts +16 -0
  100. package/lib/common/index.d.ts.map +1 -0
  101. package/lib/common/index.js +34 -0
  102. package/lib/common/index.js.map +1 -0
  103. package/lib/common/language-model-delegate.d.ts +24 -0
  104. package/lib/common/language-model-delegate.d.ts.map +1 -0
  105. package/lib/common/language-model-delegate.js +26 -0
  106. package/lib/common/language-model-delegate.js.map +1 -0
  107. package/lib/common/language-model-util.d.ts +16 -0
  108. package/lib/common/language-model-util.d.ts.map +1 -0
  109. package/lib/common/language-model-util.js +92 -0
  110. package/lib/common/language-model-util.js.map +1 -0
  111. package/lib/common/language-model.d.ts +144 -0
  112. package/lib/common/language-model.d.ts.map +1 -0
  113. package/lib/common/language-model.js +142 -0
  114. package/lib/common/language-model.js.map +1 -0
  115. package/lib/common/language-model.spec.d.ts +2 -0
  116. package/lib/common/language-model.spec.d.ts.map +1 -0
  117. package/lib/common/language-model.spec.js +62 -0
  118. package/lib/common/language-model.spec.js.map +1 -0
  119. package/lib/common/prompt-service-util.d.ts +8 -0
  120. package/lib/common/prompt-service-util.d.ts.map +1 -0
  121. package/lib/common/prompt-service-util.js +34 -0
  122. package/lib/common/prompt-service-util.js.map +1 -0
  123. package/lib/common/prompt-service.d.ts +161 -0
  124. package/lib/common/prompt-service.d.ts.map +1 -0
  125. package/lib/common/prompt-service.js +204 -0
  126. package/lib/common/prompt-service.js.map +1 -0
  127. package/lib/common/prompt-service.spec.d.ts +2 -0
  128. package/lib/common/prompt-service.spec.d.ts.map +1 -0
  129. package/lib/common/prompt-service.spec.js +254 -0
  130. package/lib/common/prompt-service.spec.js.map +1 -0
  131. package/lib/common/protocol.d.ts +7 -0
  132. package/lib/common/protocol.d.ts.map +1 -0
  133. package/lib/common/protocol.js +20 -0
  134. package/lib/common/protocol.js.map +1 -0
  135. package/lib/common/settings-service.d.ts +23 -0
  136. package/lib/common/settings-service.d.ts.map +1 -0
  137. package/lib/common/settings-service.js +5 -0
  138. package/lib/common/settings-service.js.map +1 -0
  139. package/lib/common/today-variable-contribution.d.ts +17 -0
  140. package/lib/common/today-variable-contribution.d.ts.map +1 -0
  141. package/lib/common/today-variable-contribution.js +48 -0
  142. package/lib/common/today-variable-contribution.js.map +1 -0
  143. package/lib/common/tomorrow-variable-contribution.d.ts +17 -0
  144. package/lib/common/tomorrow-variable-contribution.d.ts.map +1 -0
  145. package/lib/common/tomorrow-variable-contribution.js +48 -0
  146. package/lib/common/tomorrow-variable-contribution.js.map +1 -0
  147. package/lib/common/tool-invocation-registry.d.ts +23 -0
  148. package/lib/common/tool-invocation-registry.d.ts.map +1 -0
  149. package/lib/common/tool-invocation-registry.js +72 -0
  150. package/lib/common/tool-invocation-registry.js.map +1 -0
  151. package/lib/common/variable-service.d.ts +70 -0
  152. package/lib/common/variable-service.d.ts.map +1 -0
  153. package/lib/common/variable-service.js +122 -0
  154. package/lib/common/variable-service.js.map +1 -0
  155. package/lib/node/ai-core-backend-module.d.ts +4 -0
  156. package/lib/node/ai-core-backend-module.d.ts.map +1 -0
  157. package/lib/node/ai-core-backend-module.js +47 -0
  158. package/lib/node/ai-core-backend-module.js.map +1 -0
  159. package/lib/node/backend-language-model-registry.d.ts +12 -0
  160. package/lib/node/backend-language-model-registry.d.ts.map +1 -0
  161. package/lib/node/backend-language-model-registry.js +61 -0
  162. package/lib/node/backend-language-model-registry.js.map +1 -0
  163. package/lib/node/language-model-frontend-delegate.d.ts +18 -0
  164. package/lib/node/language-model-frontend-delegate.d.ts.map +1 -0
  165. package/lib/node/language-model-frontend-delegate.js +100 -0
  166. package/lib/node/language-model-frontend-delegate.js.map +1 -0
  167. package/package.json +61 -0
  168. package/src/browser/ai-activation-service.ts +56 -0
  169. package/src/browser/ai-command-handler-factory.ts +20 -0
  170. package/src/browser/ai-configuration/agent-configuration-widget.tsx +324 -0
  171. package/src/browser/ai-configuration/ai-configuration-service.ts +43 -0
  172. package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +54 -0
  173. package/src/browser/ai-configuration/ai-configuration-widget.tsx +80 -0
  174. package/src/browser/ai-configuration/language-model-renderer.tsx +113 -0
  175. package/src/browser/ai-configuration/template-settings-renderer.tsx +128 -0
  176. package/src/browser/ai-configuration/variable-configuration-widget.tsx +110 -0
  177. package/src/browser/ai-core-frontend-application-contribution.ts +39 -0
  178. package/src/browser/ai-core-frontend-module.ts +161 -0
  179. package/src/browser/ai-core-preferences.ts +115 -0
  180. package/src/browser/ai-settings-service.ts +50 -0
  181. package/src/browser/ai-view-contribution.ts +77 -0
  182. package/src/browser/frontend-language-model-registry.ts +405 -0
  183. package/src/browser/frontend-prompt-customization-service.ts +243 -0
  184. package/src/browser/frontend-variable-service.ts +26 -0
  185. package/src/browser/index.ts +26 -0
  186. package/src/browser/prompttemplate-contribution.ts +252 -0
  187. package/src/browser/style/index.css +127 -0
  188. package/src/browser/theia-variable-contribution.ts +58 -0
  189. package/src/common/agent-service.ts +137 -0
  190. package/src/common/agent.ts +70 -0
  191. package/src/common/agents-variable-contribution.ts +64 -0
  192. package/src/common/communication-recording-service.ts +53 -0
  193. package/src/common/index.ts +30 -0
  194. package/src/common/language-model-delegate.ts +45 -0
  195. package/src/common/language-model-util.ts +84 -0
  196. package/src/common/language-model.spec.ts +86 -0
  197. package/src/common/language-model.ts +242 -0
  198. package/src/common/prompt-service-util.ts +31 -0
  199. package/src/common/prompt-service.spec.ts +301 -0
  200. package/src/common/prompt-service.ts +334 -0
  201. package/src/common/protocol.ts +23 -0
  202. package/src/common/settings-service.ts +38 -0
  203. package/src/common/today-variable-contribution.ts +67 -0
  204. package/src/common/tomorrow-variable-contribution.ts +66 -0
  205. package/src/common/tool-invocation-registry.ts +79 -0
  206. package/src/common/variable-service.ts +177 -0
  207. package/src/node/ai-core-backend-module.ts +83 -0
  208. package/src/node/backend-language-model-registry.ts +59 -0
  209. package/src/node/language-model-frontend-delegate.ts +116 -0
@@ -0,0 +1,242 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ContributionProvider, ILogger, isFunction, isObject, Event, Emitter, CancellationToken } from '@theia/core';
18
+ import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
19
+
20
+ export type MessageActor = 'user' | 'ai' | 'system';
21
+
22
+ export interface LanguageModelRequestMessage {
23
+ actor: MessageActor;
24
+ type: 'text';
25
+ query: string;
26
+ }
27
+ export const isLanguageModelRequestMessage = (obj: unknown): obj is LanguageModelRequestMessage =>
28
+ !!(obj && typeof obj === 'object' &&
29
+ 'type' in obj &&
30
+ typeof (obj as { type: unknown }).type === 'string' &&
31
+ (obj as { type: unknown }).type === 'text' &&
32
+ 'query' in obj &&
33
+ typeof (obj as { query: unknown }).query === 'string'
34
+ );
35
+ export interface ToolRequest {
36
+ id: string;
37
+ name: string;
38
+ parameters?: { type?: 'object', properties: Record<string, { type: string, [key: string]: unknown }> };
39
+ description?: string;
40
+ handler: (arg_string: string) => Promise<unknown>;
41
+ }
42
+ export interface LanguageModelRequest {
43
+ messages: LanguageModelRequestMessage[],
44
+ tools?: ToolRequest[];
45
+ response_format?: { type: 'text' } | { type: 'json_object' } | ResponseFormatJsonSchema;
46
+ settings?: { [key: string]: unknown };
47
+ }
48
+ export interface ResponseFormatJsonSchema {
49
+ type: 'json_schema';
50
+ json_schema: {
51
+ name: string,
52
+ description?: string,
53
+ schema?: Record<string, unknown>,
54
+ strict?: boolean | null
55
+ };
56
+ }
57
+
58
+ export interface LanguageModelTextResponse {
59
+ text: string;
60
+ }
61
+ export const isLanguageModelTextResponse = (obj: unknown): obj is LanguageModelTextResponse =>
62
+ !!(obj && typeof obj === 'object' && 'text' in obj && typeof (obj as { text: unknown }).text === 'string');
63
+
64
+ export interface LanguageModelStreamResponsePart {
65
+ content?: string | null;
66
+ tool_calls?: ToolCall[];
67
+ }
68
+
69
+ export interface ToolCall {
70
+ id?: string;
71
+ function?: {
72
+ arguments?: string;
73
+ name?: string;
74
+ },
75
+ finished?: boolean;
76
+ result?: string;
77
+ }
78
+
79
+ export interface LanguageModelStreamResponse {
80
+ stream: AsyncIterable<LanguageModelStreamResponsePart>;
81
+ }
82
+ export const isLanguageModelStreamResponse = (obj: unknown): obj is LanguageModelStreamResponse =>
83
+ !!(obj && typeof obj === 'object' && 'stream' in obj);
84
+
85
+ export interface LanguageModelParsedResponse {
86
+ parsed: unknown;
87
+ content: string;
88
+ }
89
+ export const isLanguageModelParsedResponse = (obj: unknown): obj is LanguageModelParsedResponse =>
90
+ !!(obj && typeof obj === 'object' && 'parsed' in obj && 'content' in obj);
91
+
92
+ export type LanguageModelResponse = LanguageModelTextResponse | LanguageModelStreamResponse | LanguageModelParsedResponse;
93
+
94
+ ///////////////////////////////////////////
95
+ // Language Model Provider
96
+ ///////////////////////////////////////////
97
+
98
+ export const LanguageModelProvider = Symbol('LanguageModelProvider');
99
+ export type LanguageModelProvider = () => Promise<LanguageModel[]>;
100
+
101
+ // See also VS Code `ILanguageModelChatMetadata`
102
+ export interface LanguageModelMetaData {
103
+ readonly id: string;
104
+ readonly name?: string;
105
+ readonly vendor?: string;
106
+ readonly version?: string;
107
+ readonly family?: string;
108
+ readonly maxInputTokens?: number;
109
+ readonly maxOutputTokens?: number;
110
+ /**
111
+ * Default request settings for the language model. These settings can be set by a user preferences.
112
+ * Settings in a request will override these default settings.
113
+ */
114
+ readonly defaultRequestSettings?: { [key: string]: unknown };
115
+ }
116
+
117
+ export namespace LanguageModelMetaData {
118
+ export function is(arg: unknown): arg is LanguageModelMetaData {
119
+ return isObject(arg) && 'id' in arg;
120
+ }
121
+ }
122
+
123
+ export interface LanguageModel extends LanguageModelMetaData {
124
+ request(request: LanguageModelRequest, cancellationToken?: CancellationToken): Promise<LanguageModelResponse>;
125
+ }
126
+
127
+ export namespace LanguageModel {
128
+ export function is(arg: unknown): arg is LanguageModel {
129
+ return isObject(arg) && 'id' in arg && isFunction(arg.request);
130
+ }
131
+ }
132
+
133
+ // See also VS Code `ILanguageModelChatSelector`
134
+ interface VsCodeLanguageModelSelector {
135
+ readonly identifier?: string;
136
+ readonly name?: string;
137
+ readonly vendor?: string;
138
+ readonly version?: string;
139
+ readonly family?: string;
140
+ readonly tokens?: number;
141
+ }
142
+
143
+ export interface LanguageModelSelector extends VsCodeLanguageModelSelector {
144
+ readonly agent: string;
145
+ readonly purpose: string;
146
+ }
147
+
148
+ export type LanguageModelRequirement = Omit<LanguageModelSelector, 'agent'>;
149
+
150
+ export const LanguageModelRegistry = Symbol('LanguageModelRegistry');
151
+ export interface LanguageModelRegistry {
152
+ onChange: Event<{ models: LanguageModel[] }>;
153
+ addLanguageModels(models: LanguageModel[]): void;
154
+ getLanguageModels(): Promise<LanguageModel[]>;
155
+ getLanguageModel(id: string): Promise<LanguageModel | undefined>;
156
+ removeLanguageModels(id: string[]): void;
157
+ selectLanguageModel(request: LanguageModelSelector): Promise<LanguageModel | undefined>;
158
+ selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[]>;
159
+ }
160
+
161
+ @injectable()
162
+ export class DefaultLanguageModelRegistryImpl implements LanguageModelRegistry {
163
+ @inject(ILogger)
164
+ protected logger: ILogger;
165
+ @inject(ContributionProvider) @named(LanguageModelProvider)
166
+ protected readonly languageModelContributions: ContributionProvider<LanguageModelProvider>;
167
+
168
+ protected languageModels: LanguageModel[] = [];
169
+
170
+ protected markInitialized: () => void;
171
+ protected initialized: Promise<void> = new Promise(resolve => { this.markInitialized = resolve; });
172
+
173
+ protected changeEmitter = new Emitter<{ models: LanguageModel[] }>();
174
+ onChange = this.changeEmitter.event;
175
+
176
+ @postConstruct()
177
+ protected init(): void {
178
+ const contributions = this.languageModelContributions.getContributions();
179
+ const promises = contributions.map(provider => provider());
180
+ Promise.allSettled(promises).then(results => {
181
+ for (const result of results) {
182
+ if (result.status === 'fulfilled') {
183
+ this.languageModels.push(...result.value);
184
+ } else {
185
+ this.logger.error('Failed to add some language models:', result.reason);
186
+ }
187
+ }
188
+ this.markInitialized();
189
+ });
190
+ }
191
+
192
+ addLanguageModels(models: LanguageModel[]): void {
193
+ models.forEach(model => {
194
+ if (this.languageModels.find(lm => lm.id === model.id)) {
195
+ console.warn(`Tried to add already existing language model with id ${model.id}. The new model will be ignored.`);
196
+ return;
197
+ }
198
+ this.languageModels.push(model);
199
+ this.changeEmitter.fire({ models: this.languageModels });
200
+ });
201
+ }
202
+
203
+ async getLanguageModels(): Promise<LanguageModel[]> {
204
+ await this.initialized;
205
+ return this.languageModels;
206
+ }
207
+
208
+ async getLanguageModel(id: string): Promise<LanguageModel | undefined> {
209
+ await this.initialized;
210
+ return this.languageModels.find(model => model.id === id);
211
+ }
212
+
213
+ removeLanguageModels(ids: string[]): void {
214
+ ids.forEach(id => {
215
+ const index = this.languageModels.findIndex(model => model.id === id);
216
+ if (index !== -1) {
217
+ this.languageModels.splice(index, 1);
218
+ this.changeEmitter.fire({ models: this.languageModels });
219
+ } else {
220
+ console.warn(`Language model with id ${id} was requested to be removed, however it does not exist`);
221
+ }
222
+ });
223
+ }
224
+
225
+ async selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[]> {
226
+ await this.initialized;
227
+ // TODO check for actor and purpose against settings
228
+ return this.languageModels.filter(model => isModelMatching(request, model));
229
+ }
230
+
231
+ async selectLanguageModel(request: LanguageModelSelector): Promise<LanguageModel | undefined> {
232
+ return (await this.selectLanguageModels(request))[0];
233
+ }
234
+ }
235
+
236
+ export function isModelMatching(request: LanguageModelSelector, model: LanguageModel): boolean {
237
+ return (!request.identifier || model.id === request.identifier) &&
238
+ (!request.name || model.name === request.name) &&
239
+ (!request.vendor || model.vendor === request.vendor) &&
240
+ (!request.version || model.version === request.version) &&
241
+ (!request.family || model.family === request.family);
242
+ }
@@ -0,0 +1,31 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ /** Should match the one from VariableResolverService. The format is `{{variableName:arg}}`. We allow {{}} and {{{}}} but no mixtures */
18
+ export const PROMPT_VARIABLE_TWO_BRACES_REGEX = /(?<!\{)\{\{\s*([^{}]+?)\s*\}\}(?!\})/g;
19
+ export const PROMPT_VARIABLE_THREE_BRACES_REGEX = /(?<!\{)\{\{\{\s*([^{}]+?)\s*\}\}\}(?!\})/g;
20
+ export function matchVariablesRegEx(template: string): RegExpMatchArray[] {
21
+ const twoBraceMatches = [...template.matchAll(PROMPT_VARIABLE_TWO_BRACES_REGEX)];
22
+ const threeBraceMatches = [...template.matchAll(PROMPT_VARIABLE_THREE_BRACES_REGEX)];
23
+ return twoBraceMatches.concat(threeBraceMatches);
24
+ }
25
+
26
+ /** Match function/tool references in the prompt. The format is `~{functionId}`. */
27
+ export const PROMPT_FUNCTION_REGEX = /\~\{\s*(.*?)\s*\}/g;
28
+
29
+ export function matchFunctionsRegEx(template: string): RegExpMatchArray[] {
30
+ return [...template.matchAll(PROMPT_FUNCTION_REGEX)];
31
+ }
@@ -0,0 +1,301 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import 'reflect-metadata';
18
+
19
+ import { expect } from 'chai';
20
+ import { Container } from 'inversify';
21
+ import { PromptService, PromptServiceImpl } from './prompt-service';
22
+ import { DefaultAIVariableService, AIVariableService } from './variable-service';
23
+
24
+ describe('PromptService', () => {
25
+ let promptService: PromptService;
26
+
27
+ beforeEach(() => {
28
+ const container = new Container();
29
+ container.bind<PromptService>(PromptService).to(PromptServiceImpl).inSingletonScope();
30
+
31
+ const variableService = new DefaultAIVariableService({ getContributions: () => [] });
32
+ const nameVariable = { id: 'test', name: 'name', description: 'Test name ' };
33
+ variableService.registerResolver(nameVariable, {
34
+ canResolve: () => 100,
35
+ resolve: async () => ({ variable: nameVariable, value: 'Jane' })
36
+ });
37
+ container.bind<AIVariableService>(AIVariableService).toConstantValue(variableService);
38
+
39
+ promptService = container.get<PromptService>(PromptService);
40
+ promptService.storePromptTemplate({ id: '1', template: 'Hello, {{name}}!' });
41
+ promptService.storePromptTemplate({ id: '2', template: 'Goodbye, {{name}}!' });
42
+ promptService.storePromptTemplate({ id: '3', template: 'Ciao, {{invalid}}!' });
43
+ promptService.storePromptTemplate({ id: '8', template: 'Hello, {{{name}}}' });
44
+ });
45
+
46
+ it('should initialize prompts from PromptCollectionService', () => {
47
+ const allPrompts = promptService.getAllPrompts();
48
+ expect(allPrompts['1'].template).to.equal('Hello, {{name}}!');
49
+ expect(allPrompts['2'].template).to.equal('Goodbye, {{name}}!');
50
+ expect(allPrompts['3'].template).to.equal('Ciao, {{invalid}}!');
51
+ expect(allPrompts['8'].template).to.equal('Hello, {{{name}}}');
52
+ });
53
+
54
+ it('should retrieve raw prompt by id', () => {
55
+ const rawPrompt = promptService.getRawPrompt('1');
56
+ expect(rawPrompt?.template).to.equal('Hello, {{name}}!');
57
+ });
58
+
59
+ it('should format prompt with provided arguments', async () => {
60
+ const formattedPrompt = await promptService.getPrompt('1', { name: 'John' });
61
+ expect(formattedPrompt?.text).to.equal('Hello, John!');
62
+ });
63
+
64
+ it('should store a new prompt', () => {
65
+ promptService.storePromptTemplate({ id: '3', template: 'Welcome, {{name}}!' });
66
+ const newPrompt = promptService.getRawPrompt('3');
67
+ expect(newPrompt?.template).to.equal('Welcome, {{name}}!');
68
+ });
69
+
70
+ it('should replace placeholders with provided arguments', async () => {
71
+ const prompt = await promptService.getPrompt('1', { name: 'John' });
72
+ expect(prompt?.text).to.equal('Hello, John!');
73
+ });
74
+
75
+ it('should use variable service to resolve placeholders if argument value is not provided', async () => {
76
+ const prompt = await promptService.getPrompt('1');
77
+ expect(prompt?.text).to.equal('Hello, Jane!');
78
+ });
79
+
80
+ it('should return the prompt even if there are no replacements', async () => {
81
+ const prompt = await promptService.getPrompt('3');
82
+ expect(prompt?.text).to.equal('Ciao, {{invalid}}!');
83
+ });
84
+
85
+ it('should return undefined if the prompt id is not found', async () => {
86
+ const prompt = await promptService.getPrompt('4');
87
+ expect(prompt).to.be.undefined;
88
+ });
89
+
90
+ it('should ignore whitespace in variables', async () => {
91
+ promptService.storePromptTemplate({ id: '4', template: 'Hello, {{name }}!' });
92
+ promptService.storePromptTemplate({ id: '5', template: 'Hello, {{ name}}!' });
93
+ promptService.storePromptTemplate({ id: '6', template: 'Hello, {{ name }}!' });
94
+ promptService.storePromptTemplate({ id: '7', template: 'Hello, {{ name }}!' });
95
+ for (let i = 4; i <= 7; i++) {
96
+ const prompt = await promptService.getPrompt(`${i}`, { name: 'John' });
97
+ expect(prompt?.text).to.equal('Hello, John!');
98
+ }
99
+ });
100
+
101
+ it('should retrieve raw prompt by id (three bracket)', () => {
102
+ const rawPrompt = promptService.getRawPrompt('8');
103
+ expect(rawPrompt?.template).to.equal('Hello, {{{name}}}');
104
+ });
105
+
106
+ it('should correctly replace variables (three brackets)', async () => {
107
+ const formattedPrompt = await promptService.getPrompt('8');
108
+ expect(formattedPrompt?.text).to.equal('Hello, Jane');
109
+ });
110
+
111
+ it('should ignore whitespace in variables (three bracket)', async () => {
112
+ promptService.storePromptTemplate({ id: '9', template: 'Hello, {{{name }}}' });
113
+ promptService.storePromptTemplate({ id: '10', template: 'Hello, {{{ name}}}' });
114
+ promptService.storePromptTemplate({ id: '11', template: 'Hello, {{{ name }}}' });
115
+ promptService.storePromptTemplate({ id: '12', template: 'Hello, {{{ name }}}' });
116
+ for (let i = 9; i <= 12; i++) {
117
+ const prompt = await promptService.getPrompt(`${i}`, { name: 'John' });
118
+ expect(prompt?.text).to.equal('Hello, John');
119
+ }
120
+ });
121
+
122
+ it('should ignore invalid prompts with unmatched brackets', async () => {
123
+ promptService.storePromptTemplate({ id: '9', template: 'Hello, {{name' });
124
+ promptService.storePromptTemplate({ id: '10', template: 'Hello, {{{name' });
125
+ promptService.storePromptTemplate({ id: '11', template: 'Hello, name}}}}' });
126
+ const prompt1 = await promptService.getPrompt('9', { name: 'John' });
127
+ expect(prompt1?.text).to.equal('Hello, {{name'); // Not matching due to missing closing brackets
128
+ const prompt2 = await promptService.getPrompt('10', { name: 'John' });
129
+ expect(prompt2?.text).to.equal('Hello, {{{name'); // Matches pattern due to valid three-start-two-end brackets
130
+ const prompt3 = await promptService.getPrompt('11', { name: 'John' });
131
+ expect(prompt3?.text).to.equal('Hello, name}}}}'); // Extra closing bracket, does not match cleanly
132
+ });
133
+
134
+ it('should handle a mixture of two and three brackets correctly', async () => {
135
+ promptService.storePromptTemplate({ id: '12', template: 'Hi, {{name}}}' }); // (invalid)
136
+ promptService.storePromptTemplate({ id: '13', template: 'Hello, {{{name}}' }); // (invalid)
137
+ promptService.storePromptTemplate({ id: '14', template: 'Greetings, {{{name}}}}' }); // (invalid)
138
+ promptService.storePromptTemplate({ id: '15', template: 'Bye, {{{{name}}}' }); // (invalid)
139
+ promptService.storePromptTemplate({ id: '16', template: 'Ciao, {{{{name}}}}' }); // (invalid)
140
+ promptService.storePromptTemplate({ id: '17', template: 'Hi, {{name}}! {{{name}}}' }); // Mixed valid patterns
141
+
142
+ const prompt12 = await promptService.getPrompt('12', { name: 'John' });
143
+ expect(prompt12?.text).to.equal('Hi, {{name}}}');
144
+
145
+ const prompt13 = await promptService.getPrompt('13', { name: 'John' });
146
+ expect(prompt13?.text).to.equal('Hello, {{{name}}');
147
+
148
+ const prompt14 = await promptService.getPrompt('14', { name: 'John' });
149
+ expect(prompt14?.text).to.equal('Greetings, {{{name}}}}');
150
+
151
+ const prompt15 = await promptService.getPrompt('15', { name: 'John' });
152
+ expect(prompt15?.text).to.equal('Bye, {{{{name}}}');
153
+
154
+ const prompt16 = await promptService.getPrompt('16', { name: 'John' });
155
+ expect(prompt16?.text).to.equal('Ciao, {{{{name}}}}');
156
+
157
+ const prompt17 = await promptService.getPrompt('17', { name: 'John' });
158
+ expect(prompt17?.text).to.equal('Hi, John! John');
159
+ });
160
+
161
+ it('should strip single-line comments at the start of the template', () => {
162
+ promptService.storePromptTemplate({ id: 'comment-basic', template: '{{!-- Comment --}}Hello, {{name}}!' });
163
+ const prompt = promptService.getUnresolvedPrompt('comment-basic');
164
+ expect(prompt?.template).to.equal('Hello, {{name}}!');
165
+ });
166
+
167
+ it('should remove line break after first-line comment', () => {
168
+ promptService.storePromptTemplate({ id: 'comment-line-break', template: '{{!-- Comment --}}\nHello, {{name}}!' });
169
+ const prompt = promptService.getUnresolvedPrompt('comment-line-break');
170
+ expect(prompt?.template).to.equal('Hello, {{name}}!');
171
+ });
172
+
173
+ it('should strip multiline comments at the start of the template', () => {
174
+ promptService.storePromptTemplate({ id: 'comment-multiline', template: '{{!--\nMultiline comment\n--}}\nGoodbye, {{name}}!' });
175
+ const prompt = promptService.getUnresolvedPrompt('comment-multiline');
176
+ expect(prompt?.template).to.equal('Goodbye, {{name}}!');
177
+ });
178
+
179
+ it('should not strip comments not in the first line', () => {
180
+ promptService.storePromptTemplate({ id: 'comment-second-line', template: 'Hello, {{name}}!\n{{!-- Comment --}}' });
181
+ const prompt = promptService.getUnresolvedPrompt('comment-second-line');
182
+ expect(prompt?.template).to.equal('Hello, {{name}}!\n{{!-- Comment --}}');
183
+ });
184
+
185
+ it('should treat unclosed comments as regular text', () => {
186
+ promptService.storePromptTemplate({ id: 'comment-unclosed', template: '{{!-- Unclosed comment' });
187
+ const prompt = promptService.getUnresolvedPrompt('comment-unclosed');
188
+ expect(prompt?.template).to.equal('{{!-- Unclosed comment');
189
+ });
190
+
191
+ it('should treat standalone closing delimiters as regular text', () => {
192
+ promptService.storePromptTemplate({ id: 'comment-standalone', template: '--}} Hello, {{name}}!' });
193
+ const prompt = promptService.getUnresolvedPrompt('comment-standalone');
194
+ expect(prompt?.template).to.equal('--}} Hello, {{name}}!');
195
+ });
196
+
197
+ it('should handle nested comments and stop at the first closing tag', () => {
198
+ promptService.storePromptTemplate({ id: 'nested-comment', template: '{{!-- {{!-- Nested comment --}} --}}text' });
199
+ const prompt = promptService.getUnresolvedPrompt('nested-comment');
200
+ expect(prompt?.template).to.equal('--}}text');
201
+ });
202
+
203
+ it('should handle templates with only comments', () => {
204
+ promptService.storePromptTemplate({ id: 'comment-only', template: '{{!-- Only comments --}}' });
205
+ const prompt = promptService.getUnresolvedPrompt('comment-only');
206
+ expect(prompt?.template).to.equal('');
207
+ });
208
+
209
+ it('should handle mixed delimiters on the same line', () => {
210
+ promptService.storePromptTemplate({ id: 'comment-mixed', template: '{{!-- Unclosed comment --}}' });
211
+ const prompt = promptService.getUnresolvedPrompt('comment-mixed');
212
+ expect(prompt?.template).to.equal('');
213
+ });
214
+
215
+ it('should resolve variables after stripping single-line comments', async () => {
216
+ promptService.storePromptTemplate({ id: 'comment-resolve', template: '{{!-- Comment --}}Hello, {{name}}!' });
217
+ const prompt = await promptService.getPrompt('comment-resolve', { name: 'John' });
218
+ expect(prompt?.text).to.equal('Hello, John!');
219
+ });
220
+
221
+ it('should resolve variables in multiline templates with comments', async () => {
222
+ promptService.storePromptTemplate({ id: 'comment-multiline-vars', template: '{{!--\nMultiline comment\n--}}\nHello, {{name}}!' });
223
+ const prompt = await promptService.getPrompt('comment-multiline-vars', { name: 'John' });
224
+ expect(prompt?.text).to.equal('Hello, John!');
225
+ });
226
+
227
+ it('should resolve variables with standalone closing delimiters', async () => {
228
+ promptService.storePromptTemplate({ id: 'comment-standalone-vars', template: '--}} Hello, {{name}}!' });
229
+ const prompt = await promptService.getPrompt('comment-standalone-vars', { name: 'John' });
230
+ expect(prompt?.text).to.equal('--}} Hello, John!');
231
+ });
232
+
233
+ it('should treat unclosed comments as text and resolve variables', async () => {
234
+ promptService.storePromptTemplate({ id: 'comment-unclosed-vars', template: '{{!-- Unclosed comment\nHello, {{name}}!' });
235
+ const prompt = await promptService.getPrompt('comment-unclosed-vars', { name: 'John' });
236
+ expect(prompt?.text).to.equal('{{!-- Unclosed comment\nHello, John!');
237
+ });
238
+
239
+ it('should handle templates with mixed comments and variables', async () => {
240
+ promptService.storePromptTemplate({ id: 'comment-mixed-vars', template: '{{!-- Comment --}}Hi, {{name}}! {{!-- Another comment --}}' });
241
+ const prompt = await promptService.getPrompt('comment-mixed-vars', { name: 'John' });
242
+ expect(prompt?.text).to.equal('Hi, John! {{!-- Another comment --}}');
243
+ });
244
+
245
+ it('should return all variant IDs of a given prompt', () => {
246
+ promptService.storePromptTemplate({ id: 'main', template: 'Main template' });
247
+
248
+ promptService.storePromptTemplate({
249
+ id: 'variant1',
250
+ template: 'Variant 1',
251
+ variantOf: 'main'
252
+ });
253
+ promptService.storePromptTemplate({
254
+ id: 'variant2',
255
+ template: 'Variant 2',
256
+ variantOf: 'main'
257
+ });
258
+ promptService.storePromptTemplate({
259
+ id: 'variant3',
260
+ template: 'Variant 3',
261
+ variantOf: 'main'
262
+ });
263
+
264
+ const variantIds = promptService.getVariantIds('main');
265
+ expect(variantIds).to.deep.equal(['variant1', 'variant2', 'variant3']);
266
+ });
267
+
268
+ it('should return an empty array if no variants exist for a given prompt', () => {
269
+ promptService.storePromptTemplate({ id: 'main', template: 'Main template' });
270
+
271
+ const variantIds = promptService.getVariantIds('main');
272
+ expect(variantIds).to.deep.equal([]);
273
+ });
274
+
275
+ it('should return an empty array if the main prompt ID does not exist', () => {
276
+ const variantIds = promptService.getVariantIds('nonExistent');
277
+ expect(variantIds).to.deep.equal([]);
278
+ });
279
+
280
+ it('should not influence prompts without variants when other prompts have variants', () => {
281
+ promptService.storePromptTemplate({ id: 'mainWithVariants', template: 'Main template with variants' });
282
+ promptService.storePromptTemplate({ id: 'mainWithoutVariants', template: 'Main template without variants' });
283
+
284
+ promptService.storePromptTemplate({
285
+ id: 'variant1',
286
+ template: 'Variant 1',
287
+ variantOf: 'mainWithVariants'
288
+ });
289
+ promptService.storePromptTemplate({
290
+ id: 'variant2',
291
+ template: 'Variant 2',
292
+ variantOf: 'mainWithVariants'
293
+ });
294
+
295
+ const variantsForMainWithVariants = promptService.getVariantIds('mainWithVariants');
296
+ const variantsForMainWithoutVariants = promptService.getVariantIds('mainWithoutVariants');
297
+
298
+ expect(variantsForMainWithVariants).to.deep.equal(['variant1', 'variant2']);
299
+ expect(variantsForMainWithoutVariants).to.deep.equal([]);
300
+ });
301
+ });