@theia/ai-chat 1.74.0-next.9 → 1.74.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.
- package/lib/browser/agent-delegation-tool.js +6 -7
- package/lib/browser/agent-delegation-tool.js.map +1 -1
- package/lib/browser/agent-delegation-tool.spec.js +13 -0
- package/lib/browser/agent-delegation-tool.spec.js.map +1 -1
- package/lib/browser/chat-session-store-impl.d.ts.map +1 -1
- package/lib/browser/chat-session-store-impl.js +2 -0
- package/lib/browser/chat-session-store-impl.js.map +1 -1
- package/lib/browser/chat-session-store-impl.spec.js +51 -2
- package/lib/browser/chat-session-store-impl.spec.js.map +1 -1
- package/lib/browser/chat-tool-preference-bindings.d.ts +18 -8
- package/lib/browser/chat-tool-preference-bindings.d.ts.map +1 -1
- package/lib/browser/chat-tool-preference-bindings.js +27 -28
- package/lib/browser/chat-tool-preference-bindings.js.map +1 -1
- package/lib/browser/chat-tool-preference-bindings.spec.js +43 -46
- package/lib/browser/chat-tool-preference-bindings.spec.js.map +1 -1
- package/lib/common/chat-agents.d.ts +8 -0
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +35 -1
- package/lib/common/chat-agents.js.map +1 -1
- package/lib/common/chat-agents.spec.js +94 -0
- package/lib/common/chat-agents.spec.js.map +1 -1
- package/lib/common/chat-auto-save.spec.js +13 -0
- package/lib/common/chat-auto-save.spec.js.map +1 -1
- package/lib/common/chat-content-deserializer.d.ts.map +1 -1
- package/lib/common/chat-content-deserializer.js +4 -0
- package/lib/common/chat-content-deserializer.js.map +1 -1
- package/lib/common/chat-model-serialization.d.ts +12 -1
- package/lib/common/chat-model-serialization.d.ts.map +1 -1
- package/lib/common/chat-model-serialization.js.map +1 -1
- package/lib/common/chat-model-serialization.spec.js +29 -0
- package/lib/common/chat-model-serialization.spec.js.map +1 -1
- package/lib/common/chat-model.d.ts +104 -2
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +151 -2
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-model.spec.d.ts +2 -0
- package/lib/common/chat-model.spec.d.ts.map +1 -0
- package/lib/common/chat-model.spec.js +42 -0
- package/lib/common/chat-model.spec.js.map +1 -0
- package/lib/common/chat-request-parser.d.ts +4 -1
- package/lib/common/chat-request-parser.d.ts.map +1 -1
- package/lib/common/chat-request-parser.js +43 -3
- package/lib/common/chat-request-parser.js.map +1 -1
- package/lib/common/chat-request-parser.spec.js +59 -0
- package/lib/common/chat-request-parser.spec.js.map +1 -1
- package/lib/common/chat-service-deletion.spec.js +43 -0
- package/lib/common/chat-service-deletion.spec.js.map +1 -1
- package/lib/common/chat-service-pinned-agent.spec.js +52 -2
- package/lib/common/chat-service-pinned-agent.spec.js.map +1 -1
- package/lib/common/chat-service-session-events.spec.d.ts +2 -0
- package/lib/common/chat-service-session-events.spec.d.ts.map +1 -0
- package/lib/common/chat-service-session-events.spec.js +130 -0
- package/lib/common/chat-service-session-events.spec.js.map +1 -0
- package/lib/common/chat-service.d.ts +3 -0
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +60 -6
- package/lib/common/chat-service.js.map +1 -1
- package/lib/common/chat-service.spec.d.ts +2 -0
- package/lib/common/chat-service.spec.d.ts.map +1 -0
- package/lib/common/chat-service.spec.js +177 -0
- package/lib/common/chat-service.spec.js.map +1 -0
- package/lib/common/chat-session-status.spec.d.ts +2 -0
- package/lib/common/chat-session-status.spec.d.ts.map +1 -0
- package/lib/common/chat-session-status.spec.js +213 -0
- package/lib/common/chat-session-status.spec.js.map +1 -0
- package/lib/common/chat-session-store.d.ts +8 -0
- package/lib/common/chat-session-store.d.ts.map +1 -1
- package/lib/common/tool-call-response-content.spec.js +36 -0
- package/lib/common/tool-call-response-content.spec.js.map +1 -1
- package/package.json +11 -11
- package/src/browser/agent-delegation-tool.spec.ts +16 -0
- package/src/browser/agent-delegation-tool.ts +7 -7
- package/src/browser/chat-session-store-impl.spec.ts +62 -2
- package/src/browser/chat-session-store-impl.ts +2 -0
- package/src/browser/chat-tool-preference-bindings.spec.ts +43 -48
- package/src/browser/chat-tool-preference-bindings.ts +42 -30
- package/src/common/chat-agents.spec.ts +112 -2
- package/src/common/chat-agents.ts +44 -1
- package/src/common/chat-auto-save.spec.ts +18 -1
- package/src/common/chat-content-deserializer.ts +11 -0
- package/src/common/chat-model-serialization.spec.ts +38 -0
- package/src/common/chat-model-serialization.ts +12 -1
- package/src/common/chat-model.spec.ts +42 -0
- package/src/common/chat-model.ts +233 -2
- package/src/common/chat-request-parser.spec.ts +69 -1
- package/src/common/chat-request-parser.ts +51 -6
- package/src/common/chat-service-deletion.spec.ts +52 -0
- package/src/common/chat-service-pinned-agent.spec.ts +58 -2
- package/src/common/chat-service-session-events.spec.ts +161 -0
- package/src/common/chat-service.spec.ts +211 -0
- package/src/common/chat-service.ts +64 -8
- package/src/common/chat-session-status.spec.ts +280 -0
- package/src/common/chat-session-store.ts +8 -0
- package/src/common/tool-call-response-content.spec.ts +42 -0
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
ChatResponseContent,
|
|
20
20
|
CodeChatResponseContentImpl,
|
|
21
21
|
CommandChatResponseContentImpl,
|
|
22
|
+
CompactionChatResponseContentImpl,
|
|
23
|
+
CompactionContentData,
|
|
22
24
|
ErrorChatResponseContentImpl,
|
|
23
25
|
HorizontalLayoutChatResponseContentImpl,
|
|
24
26
|
InformationalChatResponseContentImpl,
|
|
@@ -250,6 +252,15 @@ export class DefaultChatContentDeserializerContribution implements ChatContentDe
|
|
|
250
252
|
)
|
|
251
253
|
});
|
|
252
254
|
|
|
255
|
+
registry.register({
|
|
256
|
+
kind: 'compaction',
|
|
257
|
+
deserialize: (data: CompactionContentData) => new CompactionChatResponseContentImpl(
|
|
258
|
+
data.provider,
|
|
259
|
+
data.data,
|
|
260
|
+
data.summary
|
|
261
|
+
)
|
|
262
|
+
});
|
|
263
|
+
|
|
253
264
|
registry.register({
|
|
254
265
|
kind: 'markdownContent',
|
|
255
266
|
deserialize: (data: MarkdownContentData) => new MarkdownChatResponseContentImpl(data.content)
|
|
@@ -700,4 +700,42 @@ describe('ChatModel Serialization and Restoration', () => {
|
|
|
700
700
|
expect(restoredPart.resolution?.variable.description).to.equal('File variable');
|
|
701
701
|
});
|
|
702
702
|
});
|
|
703
|
+
|
|
704
|
+
describe('Session settings and response model serialization', () => {
|
|
705
|
+
it('should serialize and restore the per-session model override', () => {
|
|
706
|
+
const model = new MutableChatModel(ChatAgentLocation.Panel);
|
|
707
|
+
model.addRequest(createParsedRequest('Hello'));
|
|
708
|
+
model.setSettings({ commonSettings: { modelId: 'anthropic/claude-opus-4-8' } });
|
|
709
|
+
|
|
710
|
+
const serialized = model.toSerializable();
|
|
711
|
+
expect(serialized.settings?.commonSettings?.modelId).to.equal('anthropic/claude-opus-4-8');
|
|
712
|
+
|
|
713
|
+
const restored = new MutableChatModel(serialized);
|
|
714
|
+
expect(restored.settings?.commonSettings?.modelId).to.equal('anthropic/claude-opus-4-8');
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it('should leave settings undefined when none were set', () => {
|
|
718
|
+
const model = new MutableChatModel(ChatAgentLocation.Panel);
|
|
719
|
+
model.addRequest(createParsedRequest('Hello'));
|
|
720
|
+
|
|
721
|
+
const serialized = model.toSerializable();
|
|
722
|
+
expect(serialized.settings).to.be.undefined;
|
|
723
|
+
|
|
724
|
+
const restored = new MutableChatModel(serialized);
|
|
725
|
+
expect(restored.settings).to.be.undefined;
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('should serialize and restore the language model recorded on a response', () => {
|
|
729
|
+
const model = new MutableChatModel(ChatAgentLocation.Panel);
|
|
730
|
+
const request = model.addRequest(createParsedRequest('Hello'));
|
|
731
|
+
request.response.setLanguageModel('anthropic/claude-opus-4-8');
|
|
732
|
+
request.response.complete();
|
|
733
|
+
|
|
734
|
+
const serialized = model.toSerializable();
|
|
735
|
+
expect(serialized.responses[0].languageModel).to.equal('anthropic/claude-opus-4-8');
|
|
736
|
+
|
|
737
|
+
const restored = new MutableChatModel(serialized);
|
|
738
|
+
expect(restored.getAllRequests()[0].response.languageModel).to.equal('anthropic/claude-opus-4-8');
|
|
739
|
+
});
|
|
740
|
+
});
|
|
703
741
|
});
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { GenericCapabilitySelections } from '@theia/ai-core';
|
|
18
18
|
import { ChatAgentLocation } from './chat-agents';
|
|
19
|
-
import { ResponseTokenUsage } from './chat-model';
|
|
19
|
+
import { ChatSessionSettings, ResponseTokenUsage } from './chat-model';
|
|
20
20
|
|
|
21
21
|
export interface SerializableChangeSetElement {
|
|
22
22
|
kind?: string;
|
|
@@ -138,6 +138,8 @@ export interface SerializableChatResponseData {
|
|
|
138
138
|
errorMessage?: string;
|
|
139
139
|
promptVariantId?: string;
|
|
140
140
|
isPromptVariantEdited?: boolean;
|
|
141
|
+
/** Identifier of the language model that produced this response, if recorded. */
|
|
142
|
+
languageModel?: string;
|
|
141
143
|
tokenUsage?: ResponseTokenUsage;
|
|
142
144
|
content: SerializableChatResponseContentData[];
|
|
143
145
|
}
|
|
@@ -189,6 +191,11 @@ export interface SerializedChatModel {
|
|
|
189
191
|
requests: SerializableChatRequestData[];
|
|
190
192
|
/** All responses for the requests */
|
|
191
193
|
responses: SerializableChatResponseData[];
|
|
194
|
+
/**
|
|
195
|
+
* Per-session settings (e.g. the per-session model override) so they survive a reload and the
|
|
196
|
+
* chat input can restore the correct selection.
|
|
197
|
+
*/
|
|
198
|
+
settings?: ChatSessionSettings;
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
/**
|
|
@@ -201,6 +208,10 @@ export interface SerializedChatData {
|
|
|
201
208
|
title?: string;
|
|
202
209
|
model: SerializedChatModel;
|
|
203
210
|
saveDate: number;
|
|
211
|
+
/** ID of the root session in the delegation chain. */
|
|
212
|
+
rootSessionId?: string;
|
|
213
|
+
/** ID of the immediate parent session that delegated this one. */
|
|
214
|
+
parentSessionId?: string;
|
|
204
215
|
}
|
|
205
216
|
|
|
206
217
|
export interface SerializableChatsData {
|
|
@@ -0,0 +1,42 @@
|
|
|
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 { expect } from 'chai';
|
|
18
|
+
import { CompactionChatResponseContentImpl, CompactionChatResponseContent } from './chat-model';
|
|
19
|
+
|
|
20
|
+
describe('CompactionChatResponseContent', () => {
|
|
21
|
+
it('round-trips through serialization', () => {
|
|
22
|
+
const original = new CompactionChatResponseContentImpl('anthropic', { block: 'opaque' }, 'summary text');
|
|
23
|
+
const serialized = original.toSerializable();
|
|
24
|
+
expect(serialized.kind).to.equal('compaction');
|
|
25
|
+
const restored = new CompactionChatResponseContentImpl(
|
|
26
|
+
serialized.data.provider, serialized.data.data, serialized.data.summary);
|
|
27
|
+
expect(restored.provider).to.equal('anthropic');
|
|
28
|
+
expect(restored.summary).to.equal('summary text');
|
|
29
|
+
expect(restored.data).to.deep.equal({ block: 'opaque' });
|
|
30
|
+
});
|
|
31
|
+
it('produces a compaction language model message', () => {
|
|
32
|
+
const content = new CompactionChatResponseContentImpl('openai-responses', { item: 'x' });
|
|
33
|
+
const message = content.toLanguageModelMessage();
|
|
34
|
+
expect(message).to.deep.equal({ actor: 'ai', type: 'compaction', provider: 'openai-responses', data: { item: 'x' }, summary: undefined });
|
|
35
|
+
});
|
|
36
|
+
it('is excluded from plain-text history but offers a display string', () => {
|
|
37
|
+
const content = new CompactionChatResponseContentImpl('anthropic', {}, 'sum');
|
|
38
|
+
expect(content.asString()).to.equal(undefined);
|
|
39
|
+
expect(content.asDisplayString()).to.equal('sum');
|
|
40
|
+
expect(CompactionChatResponseContent.is(content)).to.equal(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
package/src/common/chat-model.ts
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
AIVariableResolutionRequest,
|
|
24
|
+
CompactionMessage,
|
|
25
|
+
CompactionSettings,
|
|
24
26
|
GenericCapabilitySelections,
|
|
25
27
|
LanguageModelMessage,
|
|
26
28
|
ReasoningSettings,
|
|
@@ -80,7 +82,9 @@ export type ChatChangeEvent =
|
|
|
80
82
|
| ChatEditSubmitEvent
|
|
81
83
|
| ChatResponseChangedEvent
|
|
82
84
|
| ChatChangeHierarchyBranchEvent
|
|
83
|
-
| ChatInteractionNeededEvent
|
|
85
|
+
| ChatInteractionNeededEvent
|
|
86
|
+
| ChatSessionStatusChangedEvent
|
|
87
|
+
| ChatSettingsChangedEvent;
|
|
84
88
|
|
|
85
89
|
export interface ChatAddRequestEvent {
|
|
86
90
|
kind: 'addRequest';
|
|
@@ -142,6 +146,16 @@ export interface ChatInteractionNeededEvent {
|
|
|
142
146
|
contentPart: InteractiveContent & ChatResponseContent;
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
export interface ChatSessionStatusChangedEvent {
|
|
150
|
+
kind: 'statusChanged';
|
|
151
|
+
status: ChatSessionStatus;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ChatSettingsChangedEvent {
|
|
155
|
+
kind: 'settingsChanged';
|
|
156
|
+
settings: ChatSessionSettings;
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
export namespace ChatChangeEvent {
|
|
146
160
|
export function isChangeSetEvent(event: ChatChangeEvent): event is ChatUpdateChangeSetEvent {
|
|
147
161
|
return event.kind === 'updateChangeSet';
|
|
@@ -149,6 +163,9 @@ export namespace ChatChangeEvent {
|
|
|
149
163
|
export function isInteractionNeededEvent(event: ChatChangeEvent): event is ChatInteractionNeededEvent {
|
|
150
164
|
return event.kind === 'interactionNeeded';
|
|
151
165
|
}
|
|
166
|
+
export function isStatusChangedEvent(event: ChatChangeEvent): event is ChatSessionStatusChangedEvent {
|
|
167
|
+
return event.kind === 'statusChanged';
|
|
168
|
+
}
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
export type ChatRequestRemovalReason = 'removal' | 'resend' | 'adoption';
|
|
@@ -222,10 +239,18 @@ export interface ChatHierarchyBranchItem<TRequest extends ChatRequestModel = Cha
|
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
export interface CommonChatSessionSettings {
|
|
242
|
+
/**
|
|
243
|
+
* Language model (or alias) id to use for this session only, overriding the agent's default.
|
|
244
|
+
* New sessions start without an override and use the agent's configured model. Cleared to
|
|
245
|
+
* revert to the default.
|
|
246
|
+
*/
|
|
247
|
+
modelId?: string;
|
|
225
248
|
/** Reasoning configuration for this session; applied to reasoning-capable models. */
|
|
226
249
|
reasoning?: ReasoningSettings;
|
|
227
250
|
/** Per-session tool confirmation timeout in seconds. Overrides the global preference when set. */
|
|
228
251
|
confirmationTimeout?: number;
|
|
252
|
+
/** Per-session server-side compaction settings; set values win over the per-provider and global settings. */
|
|
253
|
+
compaction?: CompactionSettings;
|
|
229
254
|
}
|
|
230
255
|
|
|
231
256
|
export interface ChatSessionSettings {
|
|
@@ -243,6 +268,69 @@ export interface ChatSessionSettings {
|
|
|
243
268
|
[key: string]: unknown;
|
|
244
269
|
}
|
|
245
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Aggregated, observable status of a chat session, derived from the state of the session's last request.
|
|
273
|
+
*/
|
|
274
|
+
export type ChatSessionStatus =
|
|
275
|
+
/** No request is in progress. The last request, if any, completed successfully or was canceled. */
|
|
276
|
+
| 'idle'
|
|
277
|
+
/** A request is in progress. */
|
|
278
|
+
| 'running'
|
|
279
|
+
/** A tool call requires user confirmation before it can be executed. */
|
|
280
|
+
| 'awaitingApproval'
|
|
281
|
+
/** An approved or confirmation-free tool call is still executing. */
|
|
282
|
+
| 'awaitingToolCall'
|
|
283
|
+
/** The agent is waiting for user input, e.g. an answer to a structured question. */
|
|
284
|
+
| 'awaitingInput'
|
|
285
|
+
/** The last request ended in an error. */
|
|
286
|
+
| 'failed';
|
|
287
|
+
|
|
288
|
+
export namespace ChatSessionStatus {
|
|
289
|
+
/**
|
|
290
|
+
* Whether a request is in progress in this status, including the states waiting on the user or a tool.
|
|
291
|
+
*/
|
|
292
|
+
export function isInProgress(status: ChatSessionStatus): boolean {
|
|
293
|
+
return status !== 'idle' && status !== 'failed';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Whether the session is blocked on the user, i.e. a tool approval or another input is required to proceed.
|
|
298
|
+
*/
|
|
299
|
+
export function requiresUserAction(status: ChatSessionStatus): boolean {
|
|
300
|
+
return status === 'awaitingApproval' || status === 'awaitingInput';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Derives the session status from the state of the given request (usually the session's last request).
|
|
305
|
+
*/
|
|
306
|
+
export function fromRequest(request: ChatRequestModel | undefined): ChatSessionStatus {
|
|
307
|
+
if (!request) {
|
|
308
|
+
return 'idle';
|
|
309
|
+
}
|
|
310
|
+
const response = request.response;
|
|
311
|
+
if (response.isError) {
|
|
312
|
+
return 'failed';
|
|
313
|
+
}
|
|
314
|
+
if (!ChatRequestModel.isInProgress(request)) {
|
|
315
|
+
return 'idle';
|
|
316
|
+
}
|
|
317
|
+
const content = response.response.content;
|
|
318
|
+
if (content.some(part => ToolCallChatResponseContent.is(part) && part.isAwaitingUserConfirmation)) {
|
|
319
|
+
return 'awaitingApproval';
|
|
320
|
+
}
|
|
321
|
+
// Unresolved non-tool-call interactive parts are structured questions (tool calls are
|
|
322
|
+
// "unresolved" while merely executing). The waiting-for-input flag additionally covers
|
|
323
|
+
// inputs without a dedicated content part, e.g. the user-interaction tool's wizard.
|
|
324
|
+
if (response.isWaitingForInput || content.some(part => !ToolCallChatResponseContent.is(part) && InteractiveContent.is(part) && !part.isResolved)) {
|
|
325
|
+
return 'awaitingInput';
|
|
326
|
+
}
|
|
327
|
+
if (content.some(part => ToolCallChatResponseContent.is(part) && !part.finished)) {
|
|
328
|
+
return 'awaitingToolCall';
|
|
329
|
+
}
|
|
330
|
+
return 'running';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
246
334
|
export interface ChatModel {
|
|
247
335
|
readonly onDidChange: Event<ChatChangeEvent>;
|
|
248
336
|
readonly id: string;
|
|
@@ -251,8 +339,15 @@ export interface ChatModel {
|
|
|
251
339
|
readonly suggestions: readonly ChatSuggestion[];
|
|
252
340
|
readonly settings?: ChatSessionSettings;
|
|
253
341
|
readonly changeSet: ChangeSet;
|
|
342
|
+
/**
|
|
343
|
+
* Aggregated status of this session, derived from the state of its last request.
|
|
344
|
+
* Changes are announced via {@link onDidChange} with a {@link ChatSessionStatusChangedEvent}.
|
|
345
|
+
*/
|
|
346
|
+
readonly status: ChatSessionStatus;
|
|
254
347
|
/** ID of the root session in the delegation chain. For delegated sessions, this points to the topmost session where task contexts are stored. */
|
|
255
348
|
rootSessionId?: string;
|
|
349
|
+
/** ID of the immediate parent session that delegated this one. Undefined for top-level sessions. */
|
|
350
|
+
parentSessionId?: string;
|
|
256
351
|
getRequests(): ChatRequestModel[];
|
|
257
352
|
getBranches(): ChatHierarchyBranch<ChatRequestModel>[];
|
|
258
353
|
isEmpty(): boolean;
|
|
@@ -477,6 +572,12 @@ export interface ThinkingContentData {
|
|
|
477
572
|
signature: string;
|
|
478
573
|
}
|
|
479
574
|
|
|
575
|
+
export interface CompactionContentData {
|
|
576
|
+
provider: string;
|
|
577
|
+
data: unknown;
|
|
578
|
+
summary?: string;
|
|
579
|
+
}
|
|
580
|
+
|
|
480
581
|
export interface MarkdownContentData {
|
|
481
582
|
content: string;
|
|
482
583
|
}
|
|
@@ -581,6 +682,11 @@ export interface ToolCallChatResponseContent extends Required<ChatResponseConten
|
|
|
581
682
|
confirmed: Promise<boolean>;
|
|
582
683
|
/** Resolves when the tool call requires user confirmation (show Allow/Deny UI). */
|
|
583
684
|
needsUserConfirmation: Promise<void>;
|
|
685
|
+
/**
|
|
686
|
+
* Whether the tool call is currently awaiting the user's confirmation decision, i.e.
|
|
687
|
+
* {@link requestUserConfirmation} was called and the user has neither confirmed nor denied it yet.
|
|
688
|
+
*/
|
|
689
|
+
readonly isAwaitingUserConfirmation: boolean;
|
|
584
690
|
whenFinished: Promise<void>;
|
|
585
691
|
/**
|
|
586
692
|
* Provider-specific metadata about the tool call that the language model needs back on
|
|
@@ -869,6 +975,18 @@ export namespace ThinkingChatResponseContent {
|
|
|
869
975
|
}
|
|
870
976
|
}
|
|
871
977
|
|
|
978
|
+
export interface CompactionChatResponseContent extends ChatResponseContent {
|
|
979
|
+
kind: 'compaction';
|
|
980
|
+
provider: string;
|
|
981
|
+
data: unknown;
|
|
982
|
+
summary?: string;
|
|
983
|
+
}
|
|
984
|
+
export namespace CompactionChatResponseContent {
|
|
985
|
+
export function is(obj: unknown): obj is CompactionChatResponseContent {
|
|
986
|
+
return ChatResponseContent.is(obj) && obj.kind === 'compaction';
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
872
990
|
export namespace ProgressChatResponseContent {
|
|
873
991
|
export function is(obj: unknown): obj is ProgressChatResponseContent {
|
|
874
992
|
return (
|
|
@@ -1008,6 +1126,10 @@ export interface ChatResponseModel {
|
|
|
1008
1126
|
* Indicates whether the prompt variant was customized/edited
|
|
1009
1127
|
*/
|
|
1010
1128
|
readonly isPromptVariantEdited?: boolean;
|
|
1129
|
+
/**
|
|
1130
|
+
* The identifier of the language model that produced this response, if recorded.
|
|
1131
|
+
*/
|
|
1132
|
+
readonly languageModel?: string;
|
|
1011
1133
|
readonly tokenUsage?: ResponseTokenUsage;
|
|
1012
1134
|
toSerializable(): SerializableChatResponseData;
|
|
1013
1135
|
}
|
|
@@ -1029,7 +1151,9 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
1029
1151
|
protected _changeSet: ChatTreeChangeSet;
|
|
1030
1152
|
protected _settings: ChatSessionSettings;
|
|
1031
1153
|
protected _location: ChatAgentLocation;
|
|
1154
|
+
protected _status: ChatSessionStatus = 'idle';
|
|
1032
1155
|
rootSessionId?: string;
|
|
1156
|
+
parentSessionId?: string;
|
|
1033
1157
|
|
|
1034
1158
|
get location(): ChatAgentLocation {
|
|
1035
1159
|
return this._location;
|
|
@@ -1060,7 +1184,14 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
1060
1184
|
branch: event.branch,
|
|
1061
1185
|
});
|
|
1062
1186
|
}),
|
|
1187
|
+
// Re-derive the aggregated session status whenever anything in the model changes.
|
|
1188
|
+
this.onDidChange(event => {
|
|
1189
|
+
if (!ChatChangeEvent.isStatusChangedEvent(event)) {
|
|
1190
|
+
this.updateStatus();
|
|
1191
|
+
}
|
|
1192
|
+
}),
|
|
1063
1193
|
]);
|
|
1194
|
+
this._status = this.computeStatus();
|
|
1064
1195
|
}
|
|
1065
1196
|
|
|
1066
1197
|
/**
|
|
@@ -1097,6 +1228,12 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
1097
1228
|
}, this, this.toDispose);
|
|
1098
1229
|
}
|
|
1099
1230
|
|
|
1231
|
+
// Restore per-session settings (e.g. the per-session model override) so the chat input can
|
|
1232
|
+
// reflect the previous selection.
|
|
1233
|
+
if (data.settings) {
|
|
1234
|
+
this._settings = data.settings;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1100
1237
|
// Restore the hierarchy structure with all alternatives
|
|
1101
1238
|
this._hierarchy = new ChatRequestHierarchyImpl<MutableChatRequestModel>(data.hierarchy, requestMap);
|
|
1102
1239
|
|
|
@@ -1138,6 +1275,22 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
1138
1275
|
return this._suggestions;
|
|
1139
1276
|
}
|
|
1140
1277
|
|
|
1278
|
+
get status(): ChatSessionStatus {
|
|
1279
|
+
return this._status;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
protected computeStatus(): ChatSessionStatus {
|
|
1283
|
+
return ChatSessionStatus.fromRequest(this.getRequests().at(-1));
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
protected updateStatus(): void {
|
|
1287
|
+
const status = this.computeStatus();
|
|
1288
|
+
if (status !== this._status) {
|
|
1289
|
+
this._status = status;
|
|
1290
|
+
this._onDidChangeEmitter.fire({ kind: 'statusChanged', status });
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1141
1294
|
get context(): ChatContextManager {
|
|
1142
1295
|
return this._contextManager;
|
|
1143
1296
|
}
|
|
@@ -1148,6 +1301,9 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
1148
1301
|
|
|
1149
1302
|
setSettings(settings: ChatSessionSettings): void {
|
|
1150
1303
|
this._settings = settings;
|
|
1304
|
+
// Emit a change so listeners (e.g. session auto-save) persist selector-only or dialog-only
|
|
1305
|
+
// settings updates that are not accompanied by another model change.
|
|
1306
|
+
this._onDidChangeEmitter.fire({ kind: 'settingsChanged', settings });
|
|
1151
1307
|
}
|
|
1152
1308
|
|
|
1153
1309
|
addChildModel(child: MutableChatModel): Disposable {
|
|
@@ -1219,7 +1375,8 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
1219
1375
|
location: this.location,
|
|
1220
1376
|
hierarchy,
|
|
1221
1377
|
requests: serializedRequests,
|
|
1222
|
-
responses: serializedResponses
|
|
1378
|
+
responses: serializedResponses,
|
|
1379
|
+
settings: this._settings
|
|
1223
1380
|
};
|
|
1224
1381
|
}
|
|
1225
1382
|
|
|
@@ -2242,6 +2399,58 @@ export class ThinkingChatResponseContentImpl implements ThinkingChatResponseCont
|
|
|
2242
2399
|
}
|
|
2243
2400
|
}
|
|
2244
2401
|
|
|
2402
|
+
export class CompactionChatResponseContentImpl implements CompactionChatResponseContent {
|
|
2403
|
+
readonly kind = 'compaction';
|
|
2404
|
+
protected _provider: string;
|
|
2405
|
+
protected _data: unknown;
|
|
2406
|
+
protected _summary?: string;
|
|
2407
|
+
|
|
2408
|
+
constructor(provider: string, data: unknown, summary?: string) {
|
|
2409
|
+
this._provider = provider;
|
|
2410
|
+
this._data = data;
|
|
2411
|
+
this._summary = summary;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
get provider(): string {
|
|
2415
|
+
return this._provider;
|
|
2416
|
+
}
|
|
2417
|
+
get data(): unknown {
|
|
2418
|
+
return this._data;
|
|
2419
|
+
}
|
|
2420
|
+
get summary(): string | undefined {
|
|
2421
|
+
return this._summary;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
asString(): string | undefined {
|
|
2425
|
+
return undefined;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
asDisplayString(): string | undefined {
|
|
2429
|
+
return this._summary;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
toLanguageModelMessage(): CompactionMessage {
|
|
2433
|
+
return {
|
|
2434
|
+
actor: 'ai',
|
|
2435
|
+
type: 'compaction',
|
|
2436
|
+
provider: this._provider,
|
|
2437
|
+
data: this._data,
|
|
2438
|
+
summary: this._summary
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
toSerializable(): SerializableChatResponseContentData<CompactionContentData> {
|
|
2443
|
+
return {
|
|
2444
|
+
kind: 'compaction',
|
|
2445
|
+
data: {
|
|
2446
|
+
provider: this._provider,
|
|
2447
|
+
data: this._data,
|
|
2448
|
+
summary: this._summary
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2245
2454
|
export class MarkdownChatResponseContentImpl implements MarkdownChatResponseContent {
|
|
2246
2455
|
readonly kind = 'markdownContent';
|
|
2247
2456
|
protected _content: MarkdownStringImpl = new MarkdownStringImpl();
|
|
@@ -2368,6 +2577,7 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
2368
2577
|
protected _confirmationTimeout?: number;
|
|
2369
2578
|
protected _needsUserConfirmation: Promise<void>;
|
|
2370
2579
|
protected _needsUserConfirmationResolver?: () => void;
|
|
2580
|
+
protected _isAwaitingUserConfirmation = false;
|
|
2371
2581
|
protected _confirmed: Promise<boolean>;
|
|
2372
2582
|
protected _confirmationResolver?: (value: boolean) => void;
|
|
2373
2583
|
protected _confirmationRejecter?: (reason?: unknown) => void;
|
|
@@ -2445,6 +2655,10 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
2445
2655
|
return this._needsUserConfirmation;
|
|
2446
2656
|
}
|
|
2447
2657
|
|
|
2658
|
+
get isAwaitingUserConfirmation(): boolean {
|
|
2659
|
+
return this._isAwaitingUserConfirmation && !this.finished;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2448
2662
|
get whenFinished(): Promise<void> {
|
|
2449
2663
|
return this._whenFinished;
|
|
2450
2664
|
}
|
|
@@ -2479,12 +2693,14 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
2479
2693
|
* Confirm the tool execution
|
|
2480
2694
|
*/
|
|
2481
2695
|
confirm(): void {
|
|
2696
|
+
this._isAwaitingUserConfirmation = false;
|
|
2482
2697
|
if (this._confirmationResolver) {
|
|
2483
2698
|
this._confirmationResolver(true);
|
|
2484
2699
|
}
|
|
2485
2700
|
}
|
|
2486
2701
|
|
|
2487
2702
|
deny(reason?: string): void {
|
|
2703
|
+
this._isAwaitingUserConfirmation = false;
|
|
2488
2704
|
if (this._confirmationResolver) {
|
|
2489
2705
|
this._finished = true;
|
|
2490
2706
|
this._result = { denied: true, reason };
|
|
@@ -2494,6 +2710,7 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
2494
2710
|
}
|
|
2495
2711
|
|
|
2496
2712
|
requestUserConfirmation(): void {
|
|
2713
|
+
this._isAwaitingUserConfirmation = true;
|
|
2497
2714
|
if (this._needsUserConfirmationResolver) {
|
|
2498
2715
|
this._needsUserConfirmationResolver();
|
|
2499
2716
|
this._needsUserConfirmationResolver = undefined;
|
|
@@ -2506,6 +2723,7 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
2506
2723
|
}
|
|
2507
2724
|
|
|
2508
2725
|
cancelConfirmation(reason?: unknown): void {
|
|
2726
|
+
this._isAwaitingUserConfirmation = false;
|
|
2509
2727
|
if (this._confirmationRejecter) {
|
|
2510
2728
|
this._confirmationRejecter(reason);
|
|
2511
2729
|
}
|
|
@@ -3084,6 +3302,7 @@ export class MutableChatResponseModel implements ChatResponseModel {
|
|
|
3084
3302
|
protected _cancellationToken: CancellationTokenSource;
|
|
3085
3303
|
protected _promptVariantId?: string;
|
|
3086
3304
|
protected _isPromptVariantEdited?: boolean;
|
|
3305
|
+
protected _languageModel?: string;
|
|
3087
3306
|
protected _tokenUsage?: ResponseTokenUsage;
|
|
3088
3307
|
protected _tokenUsageEntries: ResponseTokenUsage[] = [];
|
|
3089
3308
|
|
|
@@ -3129,6 +3348,7 @@ export class MutableChatResponseModel implements ChatResponseModel {
|
|
|
3129
3348
|
this._progressMessages = [];
|
|
3130
3349
|
this._promptVariantId = data.promptVariantId;
|
|
3131
3350
|
this._isPromptVariantEdited = data.isPromptVariantEdited ?? false;
|
|
3351
|
+
this._languageModel = data.languageModel;
|
|
3132
3352
|
this._tokenUsage = data.tokenUsage;
|
|
3133
3353
|
|
|
3134
3354
|
if (data.errorMessage) {
|
|
@@ -3209,6 +3429,10 @@ export class MutableChatResponseModel implements ChatResponseModel {
|
|
|
3209
3429
|
return this._isPromptVariantEdited ?? false;
|
|
3210
3430
|
}
|
|
3211
3431
|
|
|
3432
|
+
get languageModel(): string | undefined {
|
|
3433
|
+
return this._languageModel;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3212
3436
|
get tokenUsage(): ResponseTokenUsage | undefined {
|
|
3213
3437
|
return this._tokenUsage;
|
|
3214
3438
|
}
|
|
@@ -3233,6 +3457,12 @@ export class MutableChatResponseModel implements ChatResponseModel {
|
|
|
3233
3457
|
this._onDidChangeEmitter.fire();
|
|
3234
3458
|
}
|
|
3235
3459
|
|
|
3460
|
+
/** Records the identifier of the language model that produced this response. */
|
|
3461
|
+
setLanguageModel(languageModel: string | undefined): void {
|
|
3462
|
+
this._languageModel = languageModel;
|
|
3463
|
+
this._onDidChangeEmitter.fire();
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3236
3466
|
overrideAgentId(agentId: string): void {
|
|
3237
3467
|
this._agentId = agentId;
|
|
3238
3468
|
}
|
|
@@ -3310,6 +3540,7 @@ export class MutableChatResponseModel implements ChatResponseModel {
|
|
|
3310
3540
|
errorMessage: this.errorObject?.message,
|
|
3311
3541
|
promptVariantId: this._promptVariantId,
|
|
3312
3542
|
isPromptVariantEdited: this._isPromptVariantEdited,
|
|
3543
|
+
languageModel: this._languageModel,
|
|
3313
3544
|
tokenUsage: this._tokenUsage,
|
|
3314
3545
|
content: this.response.content.map(c => {
|
|
3315
3546
|
const serialized = c.toSerializable?.();
|
|
@@ -20,7 +20,7 @@ import { ChatRequestParserImpl } from './chat-request-parser';
|
|
|
20
20
|
import { ChatAgent, ChatAgentLocation } from './chat-agents';
|
|
21
21
|
import { ChatContext, ChatRequest } from './chat-model';
|
|
22
22
|
import { expect } from 'chai';
|
|
23
|
-
import { AIVariable, DefaultAIVariableService, ResolvedAIVariable, ToolInvocationRegistryImpl, ToolRequest } from '@theia/ai-core';
|
|
23
|
+
import { AIVariable, DefaultAIVariableService, PromptService, ResolvedAIVariable, ToolInvocationRegistryImpl, ToolRequest } from '@theia/ai-core';
|
|
24
24
|
import { ILogger, Logger } from '@theia/core';
|
|
25
25
|
import { ParsedChatRequestAgentPart, ParsedChatRequestFunctionPart, ParsedChatRequestTextPart, ParsedChatRequestVariablePart } from './parsed-chat-request';
|
|
26
26
|
import { AgentDelegationTool } from '../browser/agent-delegation-tool';
|
|
@@ -276,6 +276,74 @@ describe('ChatRequestParserImpl', () => {
|
|
|
276
276
|
expect(varPart.variableArg).to.equal('cmd|"arg with \\"quote\\"" other');
|
|
277
277
|
});
|
|
278
278
|
|
|
279
|
+
it('parses multiple commands in one message', async () => {
|
|
280
|
+
const req: ChatRequest = {
|
|
281
|
+
text: '/skill-one /skill-two'
|
|
282
|
+
};
|
|
283
|
+
const context: ChatContext = { variables: [] };
|
|
284
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
285
|
+
|
|
286
|
+
expect(result.parts.length).to.equal(3);
|
|
287
|
+
const firstCommand = result.parts[0] as ParsedChatRequestVariablePart;
|
|
288
|
+
const separator = result.parts[1] as ParsedChatRequestTextPart;
|
|
289
|
+
const secondCommand = result.parts[2] as ParsedChatRequestVariablePart;
|
|
290
|
+
expect(firstCommand.variableName).to.equal('prompt');
|
|
291
|
+
expect(firstCommand.variableArg).to.equal('skill-one');
|
|
292
|
+
expect(separator.text).to.equal(' ');
|
|
293
|
+
expect(secondCommand.variableName).to.equal('prompt');
|
|
294
|
+
expect(secondCommand.variableArg).to.equal('skill-two');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('inserts a separator between two argument-taking commands', async () => {
|
|
298
|
+
const req: ChatRequest = {
|
|
299
|
+
text: '/summarize foo /hello bar'
|
|
300
|
+
};
|
|
301
|
+
const context: ChatContext = { variables: [] };
|
|
302
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
303
|
+
|
|
304
|
+
expect(result.parts.length).to.equal(3);
|
|
305
|
+
const firstCommand = result.parts[0] as ParsedChatRequestVariablePart;
|
|
306
|
+
const separator = result.parts[1] as ParsedChatRequestTextPart;
|
|
307
|
+
const secondCommand = result.parts[2] as ParsedChatRequestVariablePart;
|
|
308
|
+
expect(firstCommand.variableArg).to.equal('summarize|foo');
|
|
309
|
+
expect(separator.kind).to.equal('text');
|
|
310
|
+
expect(separator.text).to.equal(' ');
|
|
311
|
+
expect(secondCommand.variableArg).to.equal('hello|bar');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('keeps path-like slash arguments as command arguments', async () => {
|
|
315
|
+
const req: ChatRequest = {
|
|
316
|
+
text: '/explain /path/to/file'
|
|
317
|
+
};
|
|
318
|
+
const context: ChatContext = { variables: [] };
|
|
319
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
320
|
+
|
|
321
|
+
expect(result.parts.length).to.equal(1);
|
|
322
|
+
const varPart = result.parts[0] as ParsedChatRequestVariablePart;
|
|
323
|
+
expect(varPart.variableName).to.equal('prompt');
|
|
324
|
+
expect(varPart.variableArg).to.equal('explain|/path/to/file');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('uses known command names to disambiguate slash command arguments', async () => {
|
|
328
|
+
const promptService = {
|
|
329
|
+
getCommands: () => [
|
|
330
|
+
{ id: 'command-skill-one', template: '', isCommand: true, commandName: 'skill-one' },
|
|
331
|
+
{ id: 'command-skill-two', template: '', isCommand: true, commandName: 'skill-two' },
|
|
332
|
+
]
|
|
333
|
+
} as unknown as PromptService;
|
|
334
|
+
const parserWithPromptService = new ChatRequestParserImpl(chatAgentService, variableService, toolInvocationRegistry, logger);
|
|
335
|
+
(parserWithPromptService as unknown as { promptService: PromptService }).promptService = promptService;
|
|
336
|
+
const context: ChatContext = { variables: [] };
|
|
337
|
+
|
|
338
|
+
const multipleCommands = await parserWithPromptService.parseChatRequest({ text: '/skill-one /skill-two' }, ChatAgentLocation.Panel, context);
|
|
339
|
+
expect((multipleCommands.parts[0] as ParsedChatRequestVariablePart).variableArg).to.equal('skill-one');
|
|
340
|
+
expect((multipleCommands.parts[2] as ParsedChatRequestVariablePart).variableArg).to.equal('skill-two');
|
|
341
|
+
|
|
342
|
+
const pathArgument = await parserWithPromptService.parseChatRequest({ text: '/skill-one /tmp' }, ChatAgentLocation.Panel, context);
|
|
343
|
+
expect(pathArgument.parts.length).to.equal(1);
|
|
344
|
+
expect((pathArgument.parts[0] as ParsedChatRequestVariablePart).variableArg).to.equal('skill-one|/tmp');
|
|
345
|
+
});
|
|
346
|
+
|
|
279
347
|
it('treats the first @agent mention as the selector and does not allow later mentions to override it', async () => {
|
|
280
348
|
const createAgent = (id: string): ChatAgent => ({
|
|
281
349
|
id,
|