@theia/ai-external-api 1.76.0-next.33

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 (72) hide show
  1. package/README.md +83 -0
  2. package/lib/browser/ai-external-api-frontend-contribution.d.ts +28 -0
  3. package/lib/browser/ai-external-api-frontend-contribution.d.ts.map +1 -0
  4. package/lib/browser/ai-external-api-frontend-contribution.js +88 -0
  5. package/lib/browser/ai-external-api-frontend-contribution.js.map +1 -0
  6. package/lib/browser/ai-external-api-frontend-contribution.spec.d.ts +2 -0
  7. package/lib/browser/ai-external-api-frontend-contribution.spec.d.ts.map +1 -0
  8. package/lib/browser/ai-external-api-frontend-contribution.spec.js +102 -0
  9. package/lib/browser/ai-external-api-frontend-contribution.spec.js.map +1 -0
  10. package/lib/browser/ai-external-api-frontend-module.d.ts +4 -0
  11. package/lib/browser/ai-external-api-frontend-module.d.ts.map +1 -0
  12. package/lib/browser/ai-external-api-frontend-module.js +47 -0
  13. package/lib/browser/ai-external-api-frontend-module.js.map +1 -0
  14. package/lib/browser/frontend-external-chat-session-provider.d.ts +63 -0
  15. package/lib/browser/frontend-external-chat-session-provider.d.ts.map +1 -0
  16. package/lib/browser/frontend-external-chat-session-provider.js +271 -0
  17. package/lib/browser/frontend-external-chat-session-provider.js.map +1 -0
  18. package/lib/browser/frontend-external-chat-session-provider.spec.d.ts +2 -0
  19. package/lib/browser/frontend-external-chat-session-provider.spec.d.ts.map +1 -0
  20. package/lib/browser/frontend-external-chat-session-provider.spec.js +421 -0
  21. package/lib/browser/frontend-external-chat-session-provider.spec.js.map +1 -0
  22. package/lib/browser/index.d.ts +3 -0
  23. package/lib/browser/index.d.ts.map +1 -0
  24. package/lib/browser/index.js +21 -0
  25. package/lib/browser/index.js.map +1 -0
  26. package/lib/common/external-chat-session-provider.d.ts +213 -0
  27. package/lib/common/external-chat-session-provider.d.ts.map +1 -0
  28. package/lib/common/external-chat-session-provider.js +147 -0
  29. package/lib/common/external-chat-session-provider.js.map +1 -0
  30. package/lib/common/index.d.ts +2 -0
  31. package/lib/common/index.d.ts.map +1 -0
  32. package/lib/common/index.js +20 -0
  33. package/lib/common/index.js.map +1 -0
  34. package/lib/node/ai-external-api-backend-module.d.ts +4 -0
  35. package/lib/node/ai-external-api-backend-module.d.ts.map +1 -0
  36. package/lib/node/ai-external-api-backend-module.js +38 -0
  37. package/lib/node/ai-external-api-backend-module.js.map +1 -0
  38. package/lib/node/ai-external-api-endpoint.d.ts +31 -0
  39. package/lib/node/ai-external-api-endpoint.d.ts.map +1 -0
  40. package/lib/node/ai-external-api-endpoint.js +189 -0
  41. package/lib/node/ai-external-api-endpoint.js.map +1 -0
  42. package/lib/node/ai-external-api-endpoint.spec.d.ts +2 -0
  43. package/lib/node/ai-external-api-endpoint.spec.d.ts.map +1 -0
  44. package/lib/node/ai-external-api-endpoint.spec.js +233 -0
  45. package/lib/node/ai-external-api-endpoint.spec.js.map +1 -0
  46. package/lib/node/external-chat-session-registry.d.ts +83 -0
  47. package/lib/node/external-chat-session-registry.d.ts.map +1 -0
  48. package/lib/node/external-chat-session-registry.js +266 -0
  49. package/lib/node/external-chat-session-registry.js.map +1 -0
  50. package/lib/node/external-chat-session-registry.spec.d.ts +2 -0
  51. package/lib/node/external-chat-session-registry.spec.d.ts.map +1 -0
  52. package/lib/node/external-chat-session-registry.spec.js +366 -0
  53. package/lib/node/external-chat-session-registry.spec.js.map +1 -0
  54. package/lib/node/index.d.ts +3 -0
  55. package/lib/node/index.d.ts.map +1 -0
  56. package/lib/node/index.js +21 -0
  57. package/lib/node/index.js.map +1 -0
  58. package/package.json +52 -0
  59. package/src/browser/ai-external-api-frontend-contribution.spec.ts +128 -0
  60. package/src/browser/ai-external-api-frontend-contribution.ts +90 -0
  61. package/src/browser/ai-external-api-frontend-module.ts +48 -0
  62. package/src/browser/frontend-external-chat-session-provider.spec.ts +491 -0
  63. package/src/browser/frontend-external-chat-session-provider.ts +277 -0
  64. package/src/browser/index.ts +18 -0
  65. package/src/common/external-chat-session-provider.ts +330 -0
  66. package/src/common/index.ts +17 -0
  67. package/src/node/ai-external-api-backend-module.ts +41 -0
  68. package/src/node/ai-external-api-endpoint.spec.ts +272 -0
  69. package/src/node/ai-external-api-endpoint.ts +195 -0
  70. package/src/node/external-chat-session-registry.spec.ts +413 -0
  71. package/src/node/external-chat-session-registry.ts +270 -0
  72. package/src/node/index.ts +18 -0
@@ -0,0 +1,277 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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 { SESSION_STORAGE_PREF, SessionStorageScope } from '@theia/ai-chat/lib/common/ai-chat-preferences';
18
+ import { ChatAgentService } from '@theia/ai-chat/lib/common/chat-agent-service';
19
+ import { ChatAgent, ChatAgentLocation } from '@theia/ai-chat/lib/common/chat-agents';
20
+ import { ChatSessionStatus } from '@theia/ai-chat/lib/common/chat-model';
21
+ import { ChatService, ChatSession, NoChatAgentError } from '@theia/ai-chat/lib/common/chat-service';
22
+ import { ChatSessionMetadata } from '@theia/ai-chat/lib/common/chat-session-store';
23
+ import { PreferenceService } from '@theia/core';
24
+ import { ILogger } from '@theia/core/lib/common/logger';
25
+ import { inject, injectable, named } from '@theia/core/shared/inversify';
26
+ import { WorkspaceService } from '@theia/workspace/lib/browser';
27
+ import {
28
+ ExternalChatMessage, ExternalChatPrompt, ExternalChatPromptResult, ExternalChatSessionCreateRequest, ExternalChatSessionCreateResult,
29
+ ExternalChatSessionDetail, ExternalChatSessionProvider, ExternalChatSessionSummary
30
+ } from '../common/external-chat-session-provider';
31
+
32
+ /**
33
+ * Answers the backend's external session queries from this frontend's {@link ChatService}.
34
+ *
35
+ * Reports sessions that are restored (live) in this frontend with their full state, and
36
+ * persisted sessions that have not been restored with their persisted metadata. Live sessions
37
+ * are attributed to the workspace this frontend has opened; persisted sessions only when
38
+ * sessions are persisted per workspace, as the global session store holds sessions saved
39
+ * from any workspace.
40
+ */
41
+ @injectable()
42
+ export class FrontendExternalChatSessionProvider implements ExternalChatSessionProvider {
43
+
44
+ /** Number of trailing conversation lines included in a summary's preview. */
45
+ protected readonly previewLineCount = 5;
46
+ /** Maximum length of a single preview line; longer lines are truncated. */
47
+ protected readonly previewLineLength = 200;
48
+
49
+ @inject(ILogger) @named('ai-external-api:FrontendExternalChatSessionProvider')
50
+ protected readonly logger: ILogger;
51
+
52
+ @inject(ChatService)
53
+ protected readonly chatService: ChatService;
54
+
55
+ @inject(ChatAgentService)
56
+ protected readonly agentService: ChatAgentService;
57
+
58
+ @inject(WorkspaceService)
59
+ protected readonly workspaceService: WorkspaceService;
60
+
61
+ @inject(PreferenceService)
62
+ protected readonly preferenceService: PreferenceService;
63
+
64
+ async getSessions(): Promise<ExternalChatSessionSummary[]> {
65
+ const workspace = await this.getWorkspaceUri();
66
+ const persistedWorkspace = await this.getPersistedSessionsWorkspace(workspace);
67
+ const restored = this.chatService.getSessions().map(session => this.toSummary(session, workspace));
68
+ const restoredIds = new Set(restored.map(summary => summary.id));
69
+ const persisted = (await this.getPersistedMetadata())
70
+ .filter(metadata => !restoredIds.has(metadata.sessionId))
71
+ .map(metadata => this.toPersistedSummary(metadata, persistedWorkspace));
72
+ return [...restored, ...persisted];
73
+ }
74
+
75
+ async getSessionSummary(sessionId: string): Promise<ExternalChatSessionSummary | undefined> {
76
+ const workspace = await this.getWorkspaceUri();
77
+ const live = this.chatService.getSession(sessionId);
78
+ if (live) {
79
+ return this.toSummary(live, workspace);
80
+ }
81
+ const metadata = (await this.getPersistedMetadata()).find(candidate => candidate.sessionId === sessionId);
82
+ return metadata && this.toPersistedSummary(metadata, await this.getPersistedSessionsWorkspace(workspace));
83
+ }
84
+
85
+ async getSession(sessionId: string): Promise<ExternalChatSessionDetail | undefined> {
86
+ const workspace = await this.getWorkspaceUri();
87
+ const live = this.chatService.getSession(sessionId);
88
+ if (live) {
89
+ return this.toDetail(live, workspace);
90
+ }
91
+ const metadata = (await this.getPersistedMetadata()).find(candidate => candidate.sessionId === sessionId);
92
+ return metadata && this.toPersistedSummary(metadata, await this.getPersistedSessionsWorkspace(workspace));
93
+ }
94
+
95
+ async openSession(sessionId: string): Promise<boolean> {
96
+ const session = await this.chatService.getOrRestoreSession(sessionId);
97
+ if (!session) {
98
+ return false;
99
+ }
100
+ this.chatService.setActiveSession(sessionId, { focus: true });
101
+ return true;
102
+ }
103
+
104
+ async restoreSession(sessionId: string): Promise<ExternalChatSessionDetail | undefined> {
105
+ const session = await this.chatService.getOrRestoreSession(sessionId);
106
+ if (!session) {
107
+ return undefined;
108
+ }
109
+ return this.toDetail(session, await this.getWorkspaceUri());
110
+ }
111
+
112
+ async sendPrompt(sessionId: string, prompt: ExternalChatPrompt): Promise<ExternalChatPromptResult | undefined> {
113
+ const session = await this.chatService.getOrRestoreSession(sessionId);
114
+ if (!session) {
115
+ return undefined;
116
+ }
117
+ if (!prompt.interrupt && ChatSessionStatus.isInProgress(session.model.status)) {
118
+ return { failure: 'busy' };
119
+ }
120
+ // an in-progress request is canceled by sendRequest itself when interrupting
121
+ const invocation = await this.chatService.sendRequest(sessionId, { text: prompt.text });
122
+ if (!invocation) {
123
+ return undefined;
124
+ }
125
+ try {
126
+ return { sent: { sessionId, requestId: (await invocation.requestCompleted).id } };
127
+ } catch (error) {
128
+ if (NoChatAgentError.is(error)) {
129
+ return { failure: 'noAgent' };
130
+ }
131
+ // unexpected failure: propagate to the backend, which logs it and falls back to another frontend
132
+ throw error;
133
+ }
134
+ }
135
+
136
+ async createSession(request: ExternalChatSessionCreateRequest): Promise<ExternalChatSessionCreateResult> {
137
+ let agent: ChatAgent | undefined;
138
+ if (request.agentId) {
139
+ agent = this.agentService.getAgent(request.agentId, true);
140
+ if (!agent) {
141
+ return { failure: 'unknownAgent' };
142
+ }
143
+ }
144
+ const session = this.chatService.createSession(ChatAgentLocation.Panel, { focus: request.focus }, agent);
145
+ let requestId: string | undefined;
146
+ if (request.prompt) {
147
+ const invocation = await this.chatService.sendRequest(session.id, { text: request.prompt });
148
+ try {
149
+ requestId = invocation && (await invocation.requestCompleted).id;
150
+ } catch (error) {
151
+ // do not keep the unusable session when its initial prompt failed
152
+ await this.chatService.deleteSession(session.id);
153
+ if (NoChatAgentError.is(error)) {
154
+ return { failure: 'noAgent' };
155
+ }
156
+ // unexpected failure: propagate to the backend, which logs it and falls back to another frontend
157
+ throw error;
158
+ }
159
+ }
160
+ const created = this.chatService.getSession(session.id) ?? session;
161
+ return { created: { session: this.toSummary(created, await this.getWorkspaceUri()), requestId } };
162
+ }
163
+
164
+ async getWorkspace(): Promise<string | undefined> {
165
+ return this.getWorkspaceUri();
166
+ }
167
+
168
+ protected async getWorkspaceUri(): Promise<string | undefined> {
169
+ await this.workspaceService.ready;
170
+ return this.workspaceService.workspace?.resource.toString();
171
+ }
172
+
173
+ /**
174
+ * Returns the workspace to attribute persisted sessions to: this frontend's workspace when
175
+ * sessions are persisted per workspace, and `undefined` when they are persisted globally,
176
+ * as the global session store holds sessions saved from any workspace.
177
+ */
178
+ protected async getPersistedSessionsWorkspace(workspace: string | undefined): Promise<string | undefined> {
179
+ await this.preferenceService.ready;
180
+ const scope = this.preferenceService.get<SessionStorageScope>(SESSION_STORAGE_PREF, 'workspace');
181
+ return scope === 'workspace' ? workspace : undefined;
182
+ }
183
+
184
+ /** Returns the metadata of all persisted sessions, or an empty list if reading the persisted index fails. */
185
+ protected async getPersistedMetadata(): Promise<ChatSessionMetadata[]> {
186
+ try {
187
+ return Object.values(await this.chatService.getPersistedSessions());
188
+ } catch (error) {
189
+ this.logger.warn('Failed to read the persisted chat sessions, reporting only restored sessions.', error);
190
+ return [];
191
+ }
192
+ }
193
+
194
+ protected toDetail(session: ChatSession, workspace: string | undefined): ExternalChatSessionDetail {
195
+ return {
196
+ ...this.toSummary(session, workspace),
197
+ messages: this.toMessages(session)
198
+ };
199
+ }
200
+
201
+ protected toSummary(session: ChatSession, workspace: string | undefined): ExternalChatSessionSummary {
202
+ return {
203
+ id: session.id,
204
+ title: session.title,
205
+ status: session.model.status,
206
+ lastInteraction: session.lastInteraction?.getTime(),
207
+ workspace,
208
+ preview: this.toPreview(session),
209
+ agentId: session.pinnedAgent?.id,
210
+ agentName: session.pinnedAgent?.name,
211
+ parentSessionId: session.parentSessionId,
212
+ rootSessionId: session.rootSessionId,
213
+ restored: true
214
+ };
215
+ }
216
+
217
+ /** Reduces the metadata of a persisted session that is not restored to a summary. */
218
+ protected toPersistedSummary(metadata: ChatSessionMetadata, workspace: string | undefined): ExternalChatSessionSummary {
219
+ return {
220
+ id: metadata.sessionId,
221
+ title: metadata.title || undefined,
222
+ status: metadata.hasError ? 'failed' : 'idle',
223
+ lastInteraction: metadata.saveDate,
224
+ workspace,
225
+ agentId: metadata.pinnedAgentId,
226
+ agentName: metadata.pinnedAgentId ? this.agentService.getAgent(metadata.pinnedAgentId, true)?.name : undefined,
227
+ parentSessionId: metadata.parentSessionId,
228
+ rootSessionId: metadata.rootSessionId,
229
+ restored: false
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Reduces the session's conversation to plain-text messages, similar to the message history
235
+ * sent to the language model for the next request. In-progress responses are included with
236
+ * their content so far.
237
+ */
238
+ protected toMessages(session: ChatSession): ExternalChatMessage[] {
239
+ const messages: ExternalChatMessage[] = [];
240
+ for (const request of session.model.getRequests()) {
241
+ if (request.request.text) {
242
+ messages.push({ actor: 'user', text: request.request.text });
243
+ }
244
+ const response = request.response.response.asDisplayString();
245
+ if (response) {
246
+ messages.push({ actor: 'ai', text: response });
247
+ }
248
+ }
249
+ return messages;
250
+ }
251
+
252
+ /**
253
+ * Returns the last few non-empty lines of the conversation, or `undefined` for an empty
254
+ * conversation. Walks the conversation from the end so that only as many entries as the
255
+ * preview needs are rendered, however long the conversation is.
256
+ */
257
+ protected toPreview(session: ChatSession): string | undefined {
258
+ const lines: string[] = [];
259
+ const requests = session.model.getRequests();
260
+ for (let index = requests.length - 1; index >= 0 && lines.length < this.previewLineCount; index--) {
261
+ const request = requests[index];
262
+ const entries = [request.request.text, request.response.response.asDisplayString()];
263
+ for (let entry = entries.length - 1; entry >= 0 && lines.length < this.previewLineCount; entry--) {
264
+ lines.push(...this.toPreviewLines(entries[entry]).slice(-(this.previewLineCount - lines.length)).reverse());
265
+ }
266
+ }
267
+ return lines.length > 0 ? lines.reverse().join('\n') : undefined;
268
+ }
269
+
270
+ /** Splits an entry into its non-empty, truncated preview lines. */
271
+ protected toPreviewLines(text: string | undefined): string[] {
272
+ return (text ?? '').split('\n')
273
+ .map(line => line.trim())
274
+ .filter(line => line.length > 0)
275
+ .map(line => line.length > this.previewLineLength ? `${line.substring(0, this.previewLineLength)}…` : line);
276
+ }
277
+ }
@@ -0,0 +1,18 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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
+ export * from './ai-external-api-frontend-contribution';
18
+ export * from './frontend-external-chat-session-provider';
@@ -0,0 +1,330 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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 { ChatSessionStatus } from '@theia/ai-chat/lib/common/chat-model';
18
+ import { IJSONSchema } from '@theia/core/lib/common/json-schema';
19
+ import { RestBodySchema } from '@theia/external-api/lib/common/rest-body-schema';
20
+
21
+ /** Base path of the external AI session HTTP API. */
22
+ export const AI_SESSIONS_API_PATH = '/api/ai/sessions';
23
+
24
+ /** RPC path on which each frontend registers its {@link ExternalChatSessionProvider} with the backend. */
25
+ export const EXTERNAL_CHAT_SESSION_PROVIDER_PATH = '/services/ai-external-api/session-provider';
26
+
27
+ /**
28
+ * Chat session summary as exposed by the external session list endpoint.
29
+ */
30
+ export interface ExternalChatSessionSummary {
31
+ id: string;
32
+ title?: string;
33
+ status: ChatSessionStatus;
34
+ /** Timestamp in milliseconds since epoch of the last interaction with the session. */
35
+ lastInteraction?: number;
36
+ /**
37
+ * URI of the workspace the session belongs to, or `undefined` if its frontend has no open
38
+ * workspace. Also `undefined` for not-restored sessions when sessions are persisted
39
+ * globally, as the global session store holds sessions saved from any workspace.
40
+ */
41
+ workspace?: string;
42
+ /** The last few lines of the conversation as plain text, or `undefined` for an empty conversation. */
43
+ preview?: string;
44
+ /** ID of the agent driving the session, or `undefined` if no agent is pinned to the session. */
45
+ agentId?: string;
46
+ /** Human-readable name of the agent driving the session, or `undefined` if no agent is pinned or the agent is not registered. */
47
+ agentName?: string;
48
+ /**
49
+ * ID of the session that delegated this one, or `undefined` for a top-level session.
50
+ * Delegated sessions are listed alongside top-level ones; use this to group them.
51
+ */
52
+ parentSessionId?: string;
53
+ /** ID of the topmost session of the delegation chain, or `undefined` for a top-level session. */
54
+ rootSessionId?: string;
55
+ /**
56
+ * Whether the session is restored (live) in a connected frontend. Sessions that are only
57
+ * persisted report metadata only: their status is reduced to `idle` or `failed` and they
58
+ * carry neither a preview nor messages until they are restored.
59
+ */
60
+ restored: boolean;
61
+ }
62
+
63
+ export namespace ExternalChatSessionSummary {
64
+ /** JSON Schema of {@link ExternalChatSessionSummary}, published in the OpenAPI document. */
65
+ export const SCHEMA: IJSONSchema = {
66
+ type: 'object',
67
+ required: ['id', 'status', 'restored'],
68
+ properties: {
69
+ id: { type: 'string', description: 'Unique session id.' },
70
+ title: { type: 'string', description: 'Session title, if one has been set or generated.' },
71
+ status: {
72
+ type: 'string',
73
+ enum: [...ChatSessionStatus.VALUES],
74
+ description: 'Aggregated session status, derived from the state of the last request. '
75
+ + "'awaitingApproval' and 'awaitingInput' mean the session is blocked on the user."
76
+ },
77
+ lastInteraction: { type: 'number', description: 'Timestamp in milliseconds since epoch of the last interaction with the session.' },
78
+ workspace: {
79
+ type: 'string',
80
+ description: "URI of the workspace the session belongs to; absent when the session's frontend has no open workspace "
81
+ + 'and for not-restored sessions when sessions are persisted globally.'
82
+ },
83
+ preview: {
84
+ type: 'string',
85
+ description: 'The last few lines of the conversation as plain text; absent for an empty conversation and for sessions that are not restored.'
86
+ },
87
+ agentId: { type: 'string', description: 'ID of the agent driving the session; absent when no agent is pinned to the session.' },
88
+ agentName: { type: 'string', description: 'Human-readable name of the agent driving the session.' },
89
+ parentSessionId: {
90
+ type: 'string',
91
+ description: 'ID of the session that delegated this one; absent for a top-level session. '
92
+ + 'Delegated sessions are listed alongside top-level ones; use this to group them.'
93
+ },
94
+ rootSessionId: {
95
+ type: 'string',
96
+ description: 'ID of the topmost session of the delegation chain; absent for a top-level session.'
97
+ },
98
+ restored: {
99
+ type: 'boolean',
100
+ description: 'Whether the session is restored in a connected frontend. Sessions that are not restored report persisted metadata only.'
101
+ }
102
+ }
103
+ };
104
+ }
105
+
106
+ /**
107
+ * A single entry of the simplified conversation exposed by the external session detail endpoint.
108
+ *
109
+ * The conversation is reduced to plain text, similar to the message history sent to the language
110
+ * model for the next request: tool calls, thinking, and errors are rendered as text, while
111
+ * internal details such as content ids, branches, or serialization metadata are not exposed.
112
+ */
113
+ export interface ExternalChatMessage {
114
+ /** Author of the entry. */
115
+ actor: 'user' | 'ai';
116
+ /** Plain-text representation of the entry. */
117
+ text: string;
118
+ }
119
+
120
+ export namespace ExternalChatMessage {
121
+ /** JSON Schema of {@link ExternalChatMessage}, published in the OpenAPI document. */
122
+ export const SCHEMA: IJSONSchema = {
123
+ type: 'object',
124
+ required: ['actor', 'text'],
125
+ properties: {
126
+ actor: { type: 'string', enum: ['user', 'ai'], description: 'Author of the entry.' },
127
+ text: { type: 'string', description: 'Plain-text representation of the entry.' }
128
+ }
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Chat session detail as exposed by the external session detail endpoint.
134
+ */
135
+ export interface ExternalChatSessionDetail extends ExternalChatSessionSummary {
136
+ /**
137
+ * The session's conversation reduced to plain-text messages, oldest first.
138
+ * Absent when the session is not restored (see {@link ExternalChatSessionSummary.restored}).
139
+ */
140
+ messages?: ExternalChatMessage[];
141
+ }
142
+
143
+ export namespace ExternalChatSessionDetail {
144
+ /** JSON Schema of {@link ExternalChatSessionDetail}, published in the OpenAPI document. */
145
+ export const SCHEMA: IJSONSchema = {
146
+ ...ExternalChatSessionSummary.SCHEMA,
147
+ properties: {
148
+ ...ExternalChatSessionSummary.SCHEMA.properties,
149
+ messages: {
150
+ type: 'array',
151
+ items: ExternalChatMessage.SCHEMA,
152
+ description: "The session's conversation reduced to plain-text messages, oldest first. Absent when the session is not restored."
153
+ }
154
+ }
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Prompt sent to an existing session through the external API.
160
+ */
161
+ export interface ExternalChatPrompt {
162
+ /** The prompt text, including optional `@agent` mentions and variable references. */
163
+ text: string;
164
+ /**
165
+ * Cancel an in-progress request (including pending tool calls) before sending.
166
+ * Without this flag, prompting a session whose status is in progress is rejected as busy.
167
+ */
168
+ interrupt?: boolean;
169
+ }
170
+
171
+ export namespace ExternalChatPrompt {
172
+ /** JSON Schema of {@link ExternalChatPrompt}; bodies of the prompt endpoint are validated against it. */
173
+ export const SCHEMA: RestBodySchema<ExternalChatPrompt> = {
174
+ type: 'object',
175
+ required: ['text'],
176
+ additionalProperties: false,
177
+ properties: {
178
+ text: {
179
+ type: 'string',
180
+ pattern: '\\S',
181
+ description: 'The prompt text, including optional `@agent` mentions and variable references. Must not be blank.'
182
+ },
183
+ interrupt: {
184
+ type: 'boolean',
185
+ description: 'Cancel an in-progress request (including pending tool calls) before sending. '
186
+ + 'Without this flag, prompting a session whose status is in progress is rejected as busy.'
187
+ }
188
+ }
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Result of sending a prompt: the created request, or the reason the prompt was rejected.
194
+ *
195
+ * `busy` means a request was in progress and `interrupt` was not set; `noAgent` means no
196
+ * agent was available to handle the prompt (none mentioned, none pinned, no default configured).
197
+ */
198
+ export type ExternalChatPromptResult =
199
+ | { sent: { sessionId: string; requestId: string } }
200
+ | { failure: 'busy' | 'noAgent' };
201
+
202
+ /**
203
+ * Session creation request as accepted by the external session creation endpoint.
204
+ */
205
+ export interface ExternalChatSessionCreateRequest {
206
+ /**
207
+ * URI of the workspace in which to create the session. The session is created in a
208
+ * frontend that has this workspace open. May be omitted when all connected frontends
209
+ * share the same workspace.
210
+ */
211
+ workspace?: string;
212
+ /** ID of the agent to pin to the session. When omitted, the default agent handles prompts. */
213
+ agentId?: string;
214
+ /** Initial prompt to send right after creation. */
215
+ prompt?: string;
216
+ /**
217
+ * Raise the chat view in the IDE. Note that the created session becomes the active
218
+ * session of its frontend in any case.
219
+ */
220
+ focus?: boolean;
221
+ }
222
+
223
+ export namespace ExternalChatSessionCreateRequest {
224
+ /** JSON Schema of {@link ExternalChatSessionCreateRequest}; bodies of the creation endpoint are validated against it. */
225
+ export const SCHEMA: RestBodySchema<ExternalChatSessionCreateRequest> = {
226
+ type: 'object',
227
+ additionalProperties: false,
228
+ properties: {
229
+ workspace: {
230
+ type: 'string',
231
+ pattern: '\\S',
232
+ description: 'URI of the workspace in which to create the session. The session is created in a frontend that has this workspace open. '
233
+ + 'May be omitted when all connected frontends share the same workspace.'
234
+ },
235
+ agentId: {
236
+ type: 'string',
237
+ pattern: '\\S',
238
+ description: 'ID of the agent to pin to the session. When omitted, the default agent handles prompts.'
239
+ },
240
+ prompt: {
241
+ type: 'string',
242
+ pattern: '\\S',
243
+ description: 'Initial prompt to send right after creation. Must not be blank when given.'
244
+ },
245
+ focus: {
246
+ type: 'boolean',
247
+ description: 'Raise the chat view in the IDE. The created session becomes the active session of its frontend in any case.'
248
+ }
249
+ }
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Result of a session creation: the created session, or the reason creation was rejected.
255
+ *
256
+ * `unknownAgent` means the requested agent id is not registered. `noAgent` means the initial
257
+ * prompt could not be handled because no agent was available (the session is not kept in that
258
+ * case). `noFrontend` means no connected frontend is available to create the session.
259
+ * `workspaceNotFound` means no connected frontend matches the requested workspace.
260
+ * `ambiguousWorkspace` means no workspace was given and the connected frontends have
261
+ * different workspaces open.
262
+ */
263
+ export type ExternalChatSessionCreateResult =
264
+ | { created: { session: ExternalChatSessionSummary; requestId?: string } }
265
+ | { failure: 'unknownAgent' | 'noAgent' | 'noFrontend' | 'workspaceNotFound' | 'ambiguousWorkspace' };
266
+
267
+ export const ExternalChatSessionProvider = Symbol('ExternalChatSessionProvider');
268
+ /**
269
+ * Provides chat session information and session actions for the external API.
270
+ *
271
+ * Implemented by each frontend and registered with the backend over RPC. The backend
272
+ * queries the registered providers on demand when external clients call the HTTP API.
273
+ */
274
+ export interface ExternalChatSessionProvider {
275
+ /** Returns the URI of the workspace this frontend has opened, or `undefined` if none. */
276
+ getWorkspace(): Promise<string | undefined>;
277
+ /**
278
+ * Returns summaries of all sessions known to this frontend: sessions restored in memory
279
+ * as well as persisted sessions that have not been restored (reported with their
280
+ * persisted metadata and `restored: false`).
281
+ */
282
+ getSessions(): Promise<ExternalChatSessionSummary[]>;
283
+ /**
284
+ * Returns the summary of the given session, or `undefined` if the session is unknown to this
285
+ * frontend. Used to pick the frontend a session action is routed to, so it must stay cheap:
286
+ * unlike {@link getSession} it does not carry the conversation.
287
+ */
288
+ getSessionSummary(sessionId: string): Promise<ExternalChatSessionSummary | undefined>;
289
+ /**
290
+ * Returns the detail of the given session, or `undefined` if the session is unknown to this
291
+ * frontend. For persisted sessions that are not restored, the detail carries no messages.
292
+ */
293
+ getSession(sessionId: string): Promise<ExternalChatSessionDetail | undefined>;
294
+ /**
295
+ * Restores the given session if necessary and shows it in this frontend's chat view.
296
+ * Returns `false` if the session is unknown to this frontend.
297
+ */
298
+ openSession(sessionId: string): Promise<boolean>;
299
+ /**
300
+ * Restores the given session in this frontend without focusing it. Returns the detail of
301
+ * the restored session, or `undefined` if the session is unknown to this frontend.
302
+ */
303
+ restoreSession(sessionId: string): Promise<ExternalChatSessionDetail | undefined>;
304
+ /**
305
+ * Sends a prompt to the given session, restoring it first if necessary. Returns `undefined`
306
+ * if the session is unknown to this frontend.
307
+ */
308
+ sendPrompt(sessionId: string, prompt: ExternalChatPrompt): Promise<ExternalChatPromptResult | undefined>;
309
+ /**
310
+ * Creates a new session in this frontend and sends the initial prompt, if one is given.
311
+ * The created session becomes the frontend's active session. The request's `workspace` is
312
+ * ignored; routing to a matching frontend is the backend's responsibility.
313
+ */
314
+ createSession(request: ExternalChatSessionCreateRequest): Promise<ExternalChatSessionCreateResult>;
315
+ }
316
+
317
+ export const ExternalChatSessionBackendService = Symbol('ExternalChatSessionBackendService');
318
+ /**
319
+ * Backend side of the {@link ExternalChatSessionProvider} connection.
320
+ *
321
+ * Invoked by each frontend to inform the backend about session changes so that the backend
322
+ * can push updates to external event stream clients.
323
+ */
324
+ export interface ExternalChatSessionBackendService {
325
+ /**
326
+ * Notifies the backend that the sessions of the calling frontend changed in a way visible
327
+ * to the external API (session created/deleted/renamed or session status changed).
328
+ */
329
+ notifySessionsChanged(): void;
330
+ }
@@ -0,0 +1,17 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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
+ export * from './external-chat-session-provider';