@theia/ai-core 1.61.0 → 1.62.0

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 (69) hide show
  1. package/lib/browser/ai-core-frontend-module.js +2 -2
  2. package/lib/browser/ai-core-frontend-module.js.map +1 -1
  3. package/lib/browser/frontend-language-model-service.d.ts +1 -1
  4. package/lib/browser/frontend-language-model-service.d.ts.map +1 -1
  5. package/lib/browser/frontend-language-model-service.js.map +1 -1
  6. package/lib/browser/frontend-prompt-customization-service.d.ts +142 -48
  7. package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
  8. package/lib/browser/frontend-prompt-customization-service.js +452 -153
  9. package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
  10. package/lib/browser/prompttemplate-contribution.d.ts +1 -2
  11. package/lib/browser/prompttemplate-contribution.d.ts.map +1 -1
  12. package/lib/browser/prompttemplate-contribution.js +5 -9
  13. package/lib/browser/prompttemplate-contribution.js.map +1 -1
  14. package/lib/common/agent-service.d.ts.map +1 -1
  15. package/lib/common/agent-service.js +14 -2
  16. package/lib/common/agent-service.js.map +1 -1
  17. package/lib/common/agent.d.ts +8 -3
  18. package/lib/common/agent.d.ts.map +1 -1
  19. package/lib/common/agent.js.map +1 -1
  20. package/lib/common/index.d.ts +0 -1
  21. package/lib/common/index.d.ts.map +1 -1
  22. package/lib/common/index.js +0 -1
  23. package/lib/common/index.js.map +1 -1
  24. package/lib/common/language-model-interaction-model.d.ts +74 -0
  25. package/lib/common/language-model-interaction-model.d.ts.map +1 -0
  26. package/lib/common/language-model-interaction-model.js +3 -0
  27. package/lib/common/language-model-interaction-model.js.map +1 -0
  28. package/lib/common/language-model-service.d.ts +26 -3
  29. package/lib/common/language-model-service.d.ts.map +1 -1
  30. package/lib/common/language-model-service.js +83 -6
  31. package/lib/common/language-model-service.js.map +1 -1
  32. package/lib/common/language-model-util.d.ts +3 -2
  33. package/lib/common/language-model-util.d.ts.map +1 -1
  34. package/lib/common/language-model-util.js +8 -0
  35. package/lib/common/language-model-util.js.map +1 -1
  36. package/lib/common/language-model.d.ts +25 -2
  37. package/lib/common/language-model.d.ts.map +1 -1
  38. package/lib/common/language-model.js +3 -1
  39. package/lib/common/language-model.js.map +1 -1
  40. package/lib/common/prompt-service.d.ts +332 -126
  41. package/lib/common/prompt-service.d.ts.map +1 -1
  42. package/lib/common/prompt-service.js +363 -102
  43. package/lib/common/prompt-service.js.map +1 -1
  44. package/lib/common/prompt-service.spec.js +104 -114
  45. package/lib/common/prompt-service.spec.js.map +1 -1
  46. package/lib/common/prompt-variable-contribution.d.ts +1 -2
  47. package/lib/common/prompt-variable-contribution.d.ts.map +1 -1
  48. package/lib/common/prompt-variable-contribution.js +17 -26
  49. package/lib/common/prompt-variable-contribution.js.map +1 -1
  50. package/package.json +10 -10
  51. package/src/browser/ai-core-frontend-module.ts +4 -4
  52. package/src/browser/frontend-language-model-service.ts +1 -1
  53. package/src/browser/frontend-prompt-customization-service.ts +574 -183
  54. package/src/browser/prompttemplate-contribution.ts +6 -9
  55. package/src/common/agent-service.ts +14 -4
  56. package/src/common/agent.ts +9 -3
  57. package/src/common/index.ts +0 -1
  58. package/src/common/language-model-interaction-model.ts +98 -0
  59. package/src/common/language-model-service.ts +115 -6
  60. package/src/common/language-model-util.ts +10 -2
  61. package/src/common/language-model.ts +28 -2
  62. package/src/common/prompt-service.spec.ts +108 -114
  63. package/src/common/prompt-service.ts +694 -221
  64. package/src/common/prompt-variable-contribution.ts +22 -27
  65. package/lib/common/communication-recording-service.d.ts +0 -30
  66. package/lib/common/communication-recording-service.d.ts.map +0 -1
  67. package/lib/common/communication-recording-service.js +0 -20
  68. package/lib/common/communication-recording-service.js.map +0 -1
  69. package/src/common/communication-recording-service.ts +0 -55
@@ -22,7 +22,7 @@ import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/li
22
22
 
23
23
  import { codicon, Widget } from '@theia/core/lib/browser';
24
24
  import { EditorWidget, ReplaceOperation } from '@theia/editor/lib/browser';
25
- import { PromptCustomizationService, PromptService, PromptText, ToolInvocationRegistry } from '../common';
25
+ import { PromptService, PromptText, ToolInvocationRegistry } from '../common';
26
26
  import { ProviderResult } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
27
27
  import { AIVariableService } from '../common/variable-service';
28
28
 
@@ -44,9 +44,6 @@ export class PromptTemplateContribution implements LanguageGrammarDefinitionCont
44
44
  @inject(PromptService)
45
45
  private readonly promptService: PromptService;
46
46
 
47
- @inject(PromptCustomizationService)
48
- protected readonly customizationService: PromptCustomizationService;
49
-
50
47
  @inject(ToolInvocationRegistry)
51
48
  protected readonly toolInvocationRegistry: ToolInvocationRegistry;
52
49
 
@@ -254,22 +251,22 @@ export class PromptTemplateContribution implements LanguageGrammarDefinitionCont
254
251
 
255
252
  protected canDiscard(widget: EditorWidget): boolean {
256
253
  const resourceUri = widget.editor.uri;
257
- const id = this.customizationService.getTemplateIDFromURI(resourceUri);
254
+ const id = this.promptService.getTemplateIDFromResource(resourceUri);
258
255
  if (id === undefined) {
259
256
  return false;
260
257
  }
261
- const rawPrompt = this.promptService.getRawPrompt(id);
262
- const defaultPrompt = this.promptService.getDefaultRawPrompt(id);
258
+ const rawPrompt = this.promptService.getRawPromptFragment(id);
259
+ const defaultPrompt = this.promptService.getBuiltInRawPrompt(id);
263
260
  return rawPrompt?.template !== defaultPrompt?.template;
264
261
  }
265
262
 
266
263
  protected async discard(widget: EditorWidget): Promise<void> {
267
264
  const resourceUri = widget.editor.uri;
268
- const id = this.customizationService.getTemplateIDFromURI(resourceUri);
265
+ const id = this.promptService.getTemplateIDFromResource(resourceUri);
269
266
  if (id === undefined) {
270
267
  return;
271
268
  }
272
- const defaultPrompt = this.promptService.getDefaultRawPrompt(id);
269
+ const defaultPrompt = this.promptService.getBuiltInRawPrompt(id);
273
270
  if (defaultPrompt === undefined) {
274
271
  return;
275
272
  }
@@ -98,8 +98,13 @@ export class AgentServiceImpl implements AgentService {
98
98
 
99
99
  registerAgent(agent: Agent): void {
100
100
  this._agents.push(agent);
101
- agent.promptTemplates.forEach(
102
- template => this.promptService.storePromptTemplate(template)
101
+ agent.prompts.forEach(
102
+ prompt => {
103
+ this.promptService.addBuiltInPromptFragment(prompt.defaultVariant, prompt.id, true);
104
+ prompt.variants?.forEach(variant => {
105
+ this.promptService.addBuiltInPromptFragment(variant, prompt.id);
106
+ });
107
+ }
103
108
  );
104
109
  this.onDidChangeAgentsEmitter.fire();
105
110
  }
@@ -108,8 +113,13 @@ export class AgentServiceImpl implements AgentService {
108
113
  const agent = this._agents.find(a => a.id === agentId);
109
114
  this._agents = this._agents.filter(a => a.id !== agentId);
110
115
  this.onDidChangeAgentsEmitter.fire();
111
- agent?.promptTemplates.forEach(
112
- template => this.promptService.removePrompt(template.id)
116
+ agent?.prompts.forEach(
117
+ prompt => {
118
+ this.promptService.removePromptFragment(prompt.defaultVariant.id);
119
+ prompt.variants?.forEach(variant => {
120
+ this.promptService.removePromptFragment(variant.id);
121
+ });
122
+ }
113
123
  );
114
124
  }
115
125
 
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { LanguageModelRequirement } from './language-model';
18
- import { PromptTemplate } from './prompt-service';
18
+ import { BasePromptFragment } from './prompt-service';
19
19
 
20
20
  export interface AgentSpecificVariables {
21
21
  name: string;
@@ -23,6 +23,12 @@ export interface AgentSpecificVariables {
23
23
  usedInPrompt: boolean;
24
24
  }
25
25
 
26
+ export interface PromptVariantSet {
27
+ id: string;
28
+ defaultVariant: BasePromptFragment;
29
+ variants?: BasePromptFragment[];
30
+ }
31
+
26
32
  export const Agent = Symbol('Agent');
27
33
  /**
28
34
  * Agents represent the main functionality of the AI system. They are responsible for processing user input, collecting information from the environment,
@@ -53,8 +59,8 @@ export interface Agent {
53
59
  /** The list of global variable identifiers this agent needs to clarify its context requirements. See #39. */
54
60
  readonly variables: string[];
55
61
 
56
- /** The prompt templates introduced and used by this agent. */
57
- readonly promptTemplates: PromptTemplate[];
62
+ /** The prompts introduced and used by this agent. */
63
+ readonly prompts: PromptVariantSet[];
58
64
 
59
65
  /** Required language models. This includes the purpose and optional language model selector arguments. See #47. */
60
66
  readonly languageModelRequirements: LanguageModelRequirement[];
@@ -16,7 +16,6 @@
16
16
  export * from './agent-service';
17
17
  export * from './agent';
18
18
  export * from './agents-variable-contribution';
19
- export * from './communication-recording-service';
20
19
  export * from './tool-invocation-registry';
21
20
  export * from './language-model-delegate';
22
21
  export * from './language-model-util';
@@ -0,0 +1,98 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 STMicroelectronics and others.
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
+ import {
17
+ LanguageModelRequest,
18
+ LanguageModelResponse,
19
+ LanguageModelStreamResponse,
20
+ LanguageModelStreamResponsePart,
21
+ } from './language-model';
22
+
23
+ /**
24
+ * A session tracking raw exchanges with language models, organized into exchange units.
25
+ */
26
+ export interface LanguageModelSession {
27
+ /**
28
+ * Identifier of this Language Model Session. Corresponds to Chat session ids
29
+ */
30
+ id: string;
31
+ /**
32
+ * All exchange units part of this session
33
+ */
34
+ exchanges: LanguageModelExchange[];
35
+ }
36
+
37
+ /**
38
+ * An exchange unit representing a logical operation which may involve multiple model requests.
39
+ */
40
+ export interface LanguageModelExchange {
41
+ /**
42
+ * Identifier of the exchange unit.
43
+ */
44
+ id: string;
45
+ /**
46
+ * All requests that constitute this exchange
47
+ */
48
+ requests: LanguageModelExchangeRequest[];
49
+ /**
50
+ * Arbitrary metadata for the exchange
51
+ */
52
+ metadata: {
53
+ agent?: string;
54
+ [key: string]: unknown;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Alternative to the LanguageModelStreamResponse, suited for inspection
60
+ */
61
+ export interface LanguageModelMonitoredStreamResponse {
62
+ parts: LanguageModelStreamResponsePart[];
63
+ }
64
+
65
+ /**
66
+ * Alternative to the LanguageModelResponse, suited for inspection
67
+ */
68
+ export type LanguageModelExchangeRequestResponse = Exclude<LanguageModelResponse, LanguageModelStreamResponse> | LanguageModelMonitoredStreamResponse;
69
+
70
+ /**
71
+ * Represents a request to a language model within an exchange unit, capturing the request and its response.
72
+ */
73
+ export interface LanguageModelExchangeRequest {
74
+ /**
75
+ * Identifier of the request. Might share the id with the parent exchange if there's only one request.
76
+ */
77
+ id: string;
78
+ /**
79
+ * The actual request sent to the language model
80
+ */
81
+ request: LanguageModelRequest;
82
+ /**
83
+ * Arbitrary metadata for the request. Might contain an agent id and timestamp.
84
+ */
85
+ metadata: {
86
+ agent?: string;
87
+ timestamp?: number;
88
+ [key: string]: unknown;
89
+ };
90
+ /**
91
+ * The identifier of the language model the request was sent to
92
+ */
93
+ languageModel: string;
94
+ /**
95
+ * The recorded response
96
+ */
97
+ response: LanguageModelExchangeRequestResponse;
98
+ }
@@ -15,13 +15,32 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject } from '@theia/core/shared/inversify';
18
- import { LanguageModel, LanguageModelRegistry, LanguageModelResponse, UserRequest } from './language-model';
19
- import { CommunicationRecordingService } from './communication-recording-service';
18
+ import { isLanguageModelStreamResponse, LanguageModel, LanguageModelRegistry, LanguageModelResponse, LanguageModelStreamResponsePart, UserRequest } from './language-model';
19
+ import { LanguageModelExchangeRequest, LanguageModelSession } from './language-model-interaction-model';
20
+ import { Emitter } from '@theia/core';
21
+
22
+ export interface RequestAddedEvent {
23
+ type: 'requestAdded',
24
+ id: string;
25
+ }
26
+ export interface ResponseCompletedEvent {
27
+ type: 'responseCompleted',
28
+ requestId: string;
29
+ }
30
+ export interface SessionsClearedEvent {
31
+ type: 'sessionsCleared'
32
+ }
33
+ export type SessionEvent = RequestAddedEvent | ResponseCompletedEvent | SessionsClearedEvent;
20
34
 
21
35
  export const LanguageModelService = Symbol('LanguageModelService');
22
36
  export interface LanguageModelService {
37
+ onSessionChanged: Emitter<SessionEvent>['event'];
38
+ /**
39
+ * Collection of all recorded LanguageModelSessions.
40
+ */
41
+ sessions: LanguageModelSession[];
23
42
  /**
24
- * Submit a language model request in the context of the given `chatRequest`.
43
+ * Submit a language model request, it will automatically be recorded within a LanguageModelSession.
25
44
  */
26
45
  sendRequest(
27
46
  languageModel: LanguageModel,
@@ -33,8 +52,21 @@ export class LanguageModelServiceImpl implements LanguageModelService {
33
52
  @inject(LanguageModelRegistry)
34
53
  protected languageModelRegistry: LanguageModelRegistry;
35
54
 
36
- @inject(CommunicationRecordingService)
37
- protected recordingService: CommunicationRecordingService;
55
+ private _sessions: LanguageModelSession[] = [];
56
+
57
+ get sessions(): LanguageModelSession[] {
58
+ return this._sessions;
59
+ }
60
+
61
+ set sessions(newSessions: LanguageModelSession[]) {
62
+ this._sessions = newSessions;
63
+ if (newSessions.length === 0) {
64
+ this.sessionChangedEmitter.fire({ type: 'sessionsCleared' });
65
+ }
66
+ }
67
+
68
+ protected sessionChangedEmitter = new Emitter<SessionEvent>();
69
+ onSessionChanged = this.sessionChangedEmitter.event;
38
70
 
39
71
  async sendRequest(
40
72
  languageModel: LanguageModel,
@@ -53,7 +85,84 @@ export class LanguageModelServiceImpl implements LanguageModelService {
53
85
  return true;
54
86
  });
55
87
 
56
- return languageModel.request(languageModelRequest, languageModelRequest.cancellationToken);
88
+ let response = await languageModel.request(languageModelRequest, languageModelRequest.cancellationToken);
89
+ let storedResponse: LanguageModelExchangeRequest['response'];
90
+ if (isLanguageModelStreamResponse(response)) {
91
+ const parts: LanguageModelStreamResponsePart[] = [];
92
+ response = {
93
+ ...response,
94
+ stream: createLoggingAsyncIterable(response.stream,
95
+ parts,
96
+ () => this.sessionChangedEmitter.fire({ type: 'responseCompleted', requestId: languageModelRequest.subRequestId ?? languageModelRequest.requestId }))
97
+ };
98
+ storedResponse = { parts };
99
+ } else {
100
+ storedResponse = response;
101
+ }
102
+ this.storeRequest(languageModel, languageModelRequest, storedResponse);
103
+
104
+ return response;
57
105
  }
58
106
 
107
+ protected storeRequest(languageModel: LanguageModel, languageModelRequest: UserRequest, response: LanguageModelExchangeRequest['response']): void {
108
+ // Find or create the session for this request
109
+ let session = this._sessions.find(s => s.id === languageModelRequest.sessionId);
110
+ if (!session) {
111
+ session = {
112
+ id: languageModelRequest.sessionId,
113
+ exchanges: []
114
+ };
115
+ this._sessions.push(session);
116
+ }
117
+
118
+ // Find or create the exchange for this request
119
+ let exchange = session.exchanges.find(r => r.id === languageModelRequest.requestId);
120
+ if (!exchange) {
121
+ exchange = {
122
+ id: languageModelRequest.requestId,
123
+ requests: [],
124
+ metadata: { agent: languageModelRequest.agentId }
125
+ };
126
+ session.exchanges.push(exchange);
127
+ }
128
+
129
+ // Create and add the LanguageModelExchangeRequest to the exchange
130
+ const exchangeRequest: LanguageModelExchangeRequest = {
131
+ id: languageModelRequest.subRequestId ?? languageModelRequest.requestId,
132
+ request: languageModelRequest,
133
+ languageModel: languageModel.id,
134
+ response: response,
135
+ metadata: {}
136
+ };
137
+
138
+ exchange.requests.push(exchangeRequest);
139
+
140
+ exchangeRequest.metadata.agent = languageModelRequest.agentId;
141
+ exchangeRequest.metadata.timestamp = Date.now();
142
+
143
+ this.sessionChangedEmitter.fire({ type: 'requestAdded', id: languageModelRequest.subRequestId ?? languageModelRequest.requestId });
144
+ }
145
+
146
+ }
147
+
148
+ /**
149
+ * Creates an AsyncIterable wrapper that stores each yielded item while preserving the
150
+ * original AsyncIterable behavior.
151
+ */
152
+ async function* createLoggingAsyncIterable(
153
+ stream: AsyncIterable<LanguageModelStreamResponsePart>,
154
+ parts: LanguageModelStreamResponsePart[],
155
+ streamFinished: () => void
156
+ ): AsyncIterable<LanguageModelStreamResponsePart> {
157
+ try {
158
+ for await (const part of stream) {
159
+ parts.push(part);
160
+ yield part;
161
+ }
162
+ } catch (error) {
163
+ parts.push({ content: `[NOT FROM LLM] An error occurred: ${error.message}` });
164
+ throw error;
165
+ } finally {
166
+ streamFinished();
167
+ }
59
168
  }
@@ -22,6 +22,7 @@ import {
22
22
  LanguageModelResponse,
23
23
  ToolRequest
24
24
  } from './language-model';
25
+ import { LanguageModelMonitoredStreamResponse } from './language-model-interaction-model';
25
26
 
26
27
  /**
27
28
  * Retrieves the text content from a `LanguageModelResponse` object.
@@ -33,7 +34,7 @@ import {
33
34
  * @returns {Promise<string>} - A promise that resolves to the text content of the response.
34
35
  * @throws {Error} - Throws an error if the response type is not supported or does not contain valid text content.
35
36
  */
36
- export const getTextOfResponse = async (response: LanguageModelResponse): Promise<string> => {
37
+ export const getTextOfResponse = async (response: LanguageModelResponse | LanguageModelMonitoredStreamResponse): Promise<string> => {
37
38
  if (isLanguageModelTextResponse(response)) {
38
39
  return response.text;
39
40
  } else if (isLanguageModelStreamResponse(response)) {
@@ -44,11 +45,18 @@ export const getTextOfResponse = async (response: LanguageModelResponse): Promis
44
45
  return result;
45
46
  } else if (isLanguageModelParsedResponse(response)) {
46
47
  return response.content;
48
+ } else if ('parts' in response) {
49
+ // Handle monitored stream response
50
+ let result = '';
51
+ for (const chunk of response.parts) {
52
+ result += (isTextResponsePart(chunk) && chunk.content) ? chunk.content : '';
53
+ }
54
+ return result;
47
55
  }
48
56
  throw new Error(`Invalid response type ${response}`);
49
57
  };
50
58
 
51
- export const getJsonOfResponse = async (response: LanguageModelResponse): Promise<unknown> => {
59
+ export const getJsonOfResponse = async (response: LanguageModelResponse | LanguageModelMonitoredStreamResponse): Promise<unknown> => {
52
60
  const text = await getTextOfResponse(response);
53
61
  return getJsonOfText(text);
54
62
  };
@@ -74,7 +74,7 @@ export const isLanguageModelRequestMessage = (obj: unknown): obj is LanguageMode
74
74
  );
75
75
 
76
76
  export interface ToolRequestParameterProperty {
77
- type?: string;
77
+ type?: | 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
78
78
  anyOf?: ToolRequestParameterProperty[];
79
79
  [key: string]: unknown;
80
80
  }
@@ -159,10 +159,32 @@ export interface ResponseFormatJsonSchema {
159
159
  };
160
160
  }
161
161
 
162
+ /**
163
+ * The UserRequest extends the "pure" LanguageModelRequest for cancelling support as well as
164
+ * logging metadata.
165
+ * The additional metadata might also be used for other use cases, for example to query default
166
+ * request settings based on the agent id, merging with the request settings handed over.
167
+ */
162
168
  export interface UserRequest extends LanguageModelRequest {
169
+ /**
170
+ * Identifier of the Ai/ChatSession
171
+ */
163
172
  sessionId: string;
173
+ /**
174
+ * Identifier of the request or overall exchange. Corresponds to request id in Chat sessions
175
+ */
164
176
  requestId: string;
165
- agentId: string;
177
+ /**
178
+ * Id of a request in case a single exchange consists of multiple requests. In this case the requestId corresponds to the overall exchange.
179
+ */
180
+ subRequestId?: string;
181
+ /**
182
+ * Optional agent identifier in case the request was sent by an agent
183
+ */
184
+ agentId?: string;
185
+ /**
186
+ * Cancellation support
187
+ */
166
188
  cancellationToken?: CancellationToken;
167
189
  }
168
190
 
@@ -173,6 +195,10 @@ export const isLanguageModelTextResponse = (obj: unknown): obj is LanguageModelT
173
195
  !!(obj && typeof obj === 'object' && 'text' in obj && typeof (obj as { text: unknown }).text === 'string');
174
196
 
175
197
  export type LanguageModelStreamResponsePart = TextResponsePart | ToolCallResponsePart | ThinkingResponsePart | UsageResponsePart;
198
+
199
+ export const isLanguageModelStreamResponsePart = (part: unknown): part is LanguageModelStreamResponsePart =>
200
+ isUsageResponsePart(part) || isTextResponsePart(part) || isThinkingResponsePart(part) || isToolCallResponsePart(part);
201
+
176
202
  export interface UsageResponsePart {
177
203
  input_tokens: number;
178
204
  output_tokens: number;