@theia/ai-chat 1.74.0-next.9 → 1.74.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/lib/browser/agent-delegation-tool.js +6 -7
  2. package/lib/browser/agent-delegation-tool.js.map +1 -1
  3. package/lib/browser/agent-delegation-tool.spec.js +13 -0
  4. package/lib/browser/agent-delegation-tool.spec.js.map +1 -1
  5. package/lib/browser/chat-request-command-resolution.spec.d.ts +2 -0
  6. package/lib/browser/chat-request-command-resolution.spec.d.ts.map +1 -0
  7. package/lib/browser/chat-request-command-resolution.spec.js +94 -0
  8. package/lib/browser/chat-request-command-resolution.spec.js.map +1 -0
  9. package/lib/browser/chat-session-store-impl.d.ts.map +1 -1
  10. package/lib/browser/chat-session-store-impl.js +2 -0
  11. package/lib/browser/chat-session-store-impl.js.map +1 -1
  12. package/lib/browser/chat-session-store-impl.spec.js +51 -2
  13. package/lib/browser/chat-session-store-impl.spec.js.map +1 -1
  14. package/lib/browser/chat-tool-preference-bindings.d.ts +18 -8
  15. package/lib/browser/chat-tool-preference-bindings.d.ts.map +1 -1
  16. package/lib/browser/chat-tool-preference-bindings.js +27 -28
  17. package/lib/browser/chat-tool-preference-bindings.js.map +1 -1
  18. package/lib/browser/chat-tool-preference-bindings.spec.js +43 -46
  19. package/lib/browser/chat-tool-preference-bindings.spec.js.map +1 -1
  20. package/lib/common/chat-agents.d.ts +8 -0
  21. package/lib/common/chat-agents.d.ts.map +1 -1
  22. package/lib/common/chat-agents.js +35 -1
  23. package/lib/common/chat-agents.js.map +1 -1
  24. package/lib/common/chat-agents.spec.js +94 -0
  25. package/lib/common/chat-agents.spec.js.map +1 -1
  26. package/lib/common/chat-auto-save.spec.js +13 -0
  27. package/lib/common/chat-auto-save.spec.js.map +1 -1
  28. package/lib/common/chat-content-deserializer.d.ts.map +1 -1
  29. package/lib/common/chat-content-deserializer.js +4 -0
  30. package/lib/common/chat-content-deserializer.js.map +1 -1
  31. package/lib/common/chat-model-serialization.d.ts +12 -1
  32. package/lib/common/chat-model-serialization.d.ts.map +1 -1
  33. package/lib/common/chat-model-serialization.js.map +1 -1
  34. package/lib/common/chat-model-serialization.spec.js +29 -0
  35. package/lib/common/chat-model-serialization.spec.js.map +1 -1
  36. package/lib/common/chat-model.d.ts +105 -2
  37. package/lib/common/chat-model.d.ts.map +1 -1
  38. package/lib/common/chat-model.js +159 -3
  39. package/lib/common/chat-model.js.map +1 -1
  40. package/lib/common/chat-model.spec.d.ts +2 -0
  41. package/lib/common/chat-model.spec.d.ts.map +1 -0
  42. package/lib/common/chat-model.spec.js +42 -0
  43. package/lib/common/chat-model.spec.js.map +1 -0
  44. package/lib/common/chat-request-parser.d.ts +9 -1
  45. package/lib/common/chat-request-parser.d.ts.map +1 -1
  46. package/lib/common/chat-request-parser.js +54 -3
  47. package/lib/common/chat-request-parser.js.map +1 -1
  48. package/lib/common/chat-request-parser.spec.js +333 -0
  49. package/lib/common/chat-request-parser.spec.js.map +1 -1
  50. package/lib/common/chat-response-model.spec.js +31 -0
  51. package/lib/common/chat-response-model.spec.js.map +1 -1
  52. package/lib/common/chat-service-deletion.spec.js +43 -0
  53. package/lib/common/chat-service-deletion.spec.js.map +1 -1
  54. package/lib/common/chat-service-pinned-agent.spec.js +52 -2
  55. package/lib/common/chat-service-pinned-agent.spec.js.map +1 -1
  56. package/lib/common/chat-service-session-events.spec.d.ts +2 -0
  57. package/lib/common/chat-service-session-events.spec.d.ts.map +1 -0
  58. package/lib/common/chat-service-session-events.spec.js +130 -0
  59. package/lib/common/chat-service-session-events.spec.js.map +1 -0
  60. package/lib/common/chat-service.d.ts +3 -0
  61. package/lib/common/chat-service.d.ts.map +1 -1
  62. package/lib/common/chat-service.js +60 -6
  63. package/lib/common/chat-service.js.map +1 -1
  64. package/lib/common/chat-service.spec.d.ts +2 -0
  65. package/lib/common/chat-service.spec.d.ts.map +1 -0
  66. package/lib/common/chat-service.spec.js +177 -0
  67. package/lib/common/chat-service.spec.js.map +1 -0
  68. package/lib/common/chat-session-status.spec.d.ts +2 -0
  69. package/lib/common/chat-session-status.spec.d.ts.map +1 -0
  70. package/lib/common/chat-session-status.spec.js +213 -0
  71. package/lib/common/chat-session-status.spec.js.map +1 -0
  72. package/lib/common/chat-session-store.d.ts +8 -0
  73. package/lib/common/chat-session-store.d.ts.map +1 -1
  74. package/lib/common/tool-call-response-content.spec.js +36 -0
  75. package/lib/common/tool-call-response-content.spec.js.map +1 -1
  76. package/package.json +11 -11
  77. package/src/browser/agent-delegation-tool.spec.ts +16 -0
  78. package/src/browser/agent-delegation-tool.ts +7 -7
  79. package/src/browser/chat-request-command-resolution.spec.ts +117 -0
  80. package/src/browser/chat-session-store-impl.spec.ts +62 -2
  81. package/src/browser/chat-session-store-impl.ts +2 -0
  82. package/src/browser/chat-tool-preference-bindings.spec.ts +43 -48
  83. package/src/browser/chat-tool-preference-bindings.ts +42 -30
  84. package/src/common/chat-agents.spec.ts +112 -2
  85. package/src/common/chat-agents.ts +44 -1
  86. package/src/common/chat-auto-save.spec.ts +18 -1
  87. package/src/common/chat-content-deserializer.ts +11 -0
  88. package/src/common/chat-model-serialization.spec.ts +38 -0
  89. package/src/common/chat-model-serialization.ts +12 -1
  90. package/src/common/chat-model.spec.ts +42 -0
  91. package/src/common/chat-model.ts +241 -3
  92. package/src/common/chat-request-parser.spec.ts +401 -1
  93. package/src/common/chat-request-parser.ts +60 -5
  94. package/src/common/chat-response-model.spec.ts +41 -1
  95. package/src/common/chat-service-deletion.spec.ts +52 -0
  96. package/src/common/chat-service-pinned-agent.spec.ts +58 -2
  97. package/src/common/chat-service-session-events.spec.ts +161 -0
  98. package/src/common/chat-service.spec.ts +211 -0
  99. package/src/common/chat-service.ts +64 -8
  100. package/src/common/chat-session-status.spec.ts +280 -0
  101. package/src/common/chat-session-store.ts +8 -0
  102. package/src/common/tool-call-response-content.spec.ts +42 -0
@@ -27,6 +27,7 @@ import {
27
27
  CapabilityAwareContext,
28
28
  GenericCapabilitySelections,
29
29
  getTextOfResponse,
30
+ isCompactionResponsePart,
30
31
  isLanguageModelStreamResponsePart,
31
32
  isServerToolCallResponsePart,
32
33
  isTextResponsePart,
@@ -51,6 +52,7 @@ import {
51
52
  } from '@theia/ai-core';
52
53
  import {
53
54
  Agent,
55
+ FrontendLanguageModelRegistry,
54
56
  isLanguageModelParsedResponse,
55
57
  isLanguageModelStreamResponse,
56
58
  isLanguageModelTextResponse,
@@ -65,6 +67,7 @@ import {
65
67
  ChatRequestModel,
66
68
  ChatResponseContent,
67
69
  CommonChatSessionSettings,
70
+ CompactionChatResponseContentImpl,
68
71
  ErrorChatResponseContentImpl,
69
72
  MarkdownChatResponseContentImpl,
70
73
  MutableChatRequestModel,
@@ -225,10 +228,12 @@ export abstract class AbstractChatAgent implements ChatAgent {
225
228
 
226
229
  async invoke(request: MutableChatRequestModel): Promise<void> {
227
230
  try {
228
- const languageModel = await this.getLanguageModel(this.defaultLanguageModelPurpose);
231
+ const languageModel = await this.getLanguageModelForRequest(request, this.defaultLanguageModelPurpose);
229
232
  if (!languageModel) {
230
233
  throw new Error(nls.localize('theia/ai/chat/couldNotFindMatchingLM', 'Couldn\'t find a matching language model. Please check your setup!'));
231
234
  }
235
+ // Record the model that actually handled this request so the chat thread can show it.
236
+ request.response.setLanguageModel(languageModel.id);
232
237
  const context: ChatSessionContext = {
233
238
  model: request.session,
234
239
  request,
@@ -313,6 +318,36 @@ export abstract class AbstractChatAgent implements ChatAgent {
313
318
  return this.selectLanguageModel(this.getLanguageModelSelector(languageModelPurpose));
314
319
  }
315
320
 
321
+ /**
322
+ * Resolves the language model for a request, honoring a per-session model override
323
+ * ({@link CommonChatSessionSettings.modelId}) when set and resolvable, and otherwise falling
324
+ * back to the agent's configured model for the given purpose.
325
+ */
326
+ protected async getLanguageModelForRequest(request: MutableChatRequestModel, languageModelPurpose: string): Promise<LanguageModel> {
327
+ const overrideId = request.session.settings?.commonSettings?.modelId;
328
+ if (overrideId) {
329
+ // Resolve the override directly. We must not go through `selectLanguageModel` here, because
330
+ // that honors the per-agent model configured in the AI settings, which would take
331
+ // precedence over and thus ignore the session override.
332
+ const overridden = await this.resolveModelById(overrideId);
333
+ if (overridden) {
334
+ return overridden;
335
+ }
336
+ }
337
+ return this.getLanguageModel(languageModelPurpose);
338
+ }
339
+
340
+ /** Resolves a concrete language model (or alias) id to a ready model, or `undefined` if none is ready. */
341
+ protected async resolveModelById(modelId: string): Promise<LanguageModel | undefined> {
342
+ const registry = this.languageModelRegistry as LanguageModelRegistry & Partial<FrontendLanguageModelRegistry>;
343
+ if (typeof registry.getReadyLanguageModel === 'function') {
344
+ // Frontend registry: resolves aliases and only returns the model if it is ready.
345
+ return registry.getReadyLanguageModel(modelId);
346
+ }
347
+ const model = await registry.getLanguageModel(modelId);
348
+ return model?.status.status === 'ready' ? model : undefined;
349
+ }
350
+
316
351
  protected async selectLanguageModel(selector: LanguageModelRequirement): Promise<LanguageModel> {
317
352
  const languageModel = await this.languageModelRegistry.selectLanguageModel({ agent: this.id, ...selector });
318
353
  if (!languageModel) {
@@ -547,6 +582,7 @@ export abstract class AbstractChatAgent implements ChatAgent {
547
582
  serverTools: enabledServerTools.length > 0 ? enabledServerTools : undefined,
548
583
  settings,
549
584
  reasoning: commonSettings?.reasoning,
585
+ compaction: commonSettings?.compaction,
550
586
  agentId: this.id,
551
587
  sessionId: request.session.id,
552
588
  requestId: request.id,
@@ -776,6 +812,13 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
776
812
  request.response.setTokenUsage(this.mapUsageResponsePart(token));
777
813
  return [];
778
814
  }
815
+ if (isCompactionResponsePart(token)) {
816
+ return new CompactionChatResponseContentImpl(
817
+ token.compaction.provider,
818
+ token.compaction.data,
819
+ token.compaction.summary
820
+ );
821
+ }
779
822
  return this.defaultContentFactory.create('', request);
780
823
  }
781
824
 
@@ -25,7 +25,7 @@ import { ILogger } from '@theia/core';
25
25
  import { ChatAgentLocation } from './chat-agents';
26
26
  import { ChatContentDeserializerRegistry, ChatContentDeserializerRegistryImpl, DefaultChatContentDeserializerContribution } from './chat-content-deserializer';
27
27
  import { ChangeSetElementDeserializerRegistry, ChangeSetElementDeserializerRegistryImpl } from './change-set-element-deserializer';
28
- import { ChatModel } from './chat-model';
28
+ import { ChatModel, MutableChatModel } from './chat-model';
29
29
  import { SerializedChatData } from './chat-model-serialization';
30
30
  import { ParsedChatRequest, ParsedChatRequestTextPart } from './parsed-chat-request';
31
31
 
@@ -242,6 +242,23 @@ describe('Chat Auto-Save Mechanism', () => {
242
242
  });
243
243
  });
244
244
 
245
+ describe('Auto-save on settings changes', () => {
246
+ it('should auto-save when per-session settings change', async () => {
247
+ const session = chatService.createSession(ChatAgentLocation.Panel);
248
+ // Non-empty session so saveSession does not skip it.
249
+ await chatService.sendRequest(session.id, { text: 'Test request' });
250
+ sessionStore.reset();
251
+
252
+ // A selector-only / dialog-only settings change must trigger persistence.
253
+ (session.model as MutableChatModel).setSettings({ commonSettings: { modelId: 'test/model' } });
254
+
255
+ // Wait for auto-save to complete (debounce is 500ms + execution time)
256
+ await new Promise(resolve => setTimeout(resolve, 700));
257
+
258
+ expect(sessionStore.saveCount).to.be.greaterThan(0);
259
+ });
260
+ });
261
+
245
262
  describe('Auto-save for all sessions', () => {
246
263
  it('should save all non-empty sessions', async () => {
247
264
  const session1 = chatService.createSession(ChatAgentLocation.Panel);
@@ -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
+ });