@theia/ai-chat 1.71.0-next.8 → 1.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/lib/browser/agent-delegation-tool.d.ts +4 -10
  2. package/lib/browser/agent-delegation-tool.d.ts.map +1 -1
  3. package/lib/browser/agent-delegation-tool.js +31 -22
  4. package/lib/browser/agent-delegation-tool.js.map +1 -1
  5. package/lib/browser/agent-delegation-tool.spec.js +3 -2
  6. package/lib/browser/agent-delegation-tool.spec.js.map +1 -1
  7. package/lib/browser/chat-tool-preference-bindings.d.ts +3 -2
  8. package/lib/browser/chat-tool-preference-bindings.d.ts.map +1 -1
  9. package/lib/browser/chat-tool-preference-bindings.js +9 -8
  10. package/lib/browser/chat-tool-preference-bindings.js.map +1 -1
  11. package/lib/browser/chat-tool-preference-bindings.spec.js +72 -21
  12. package/lib/browser/chat-tool-preference-bindings.spec.js.map +1 -1
  13. package/lib/browser/chat-tool-request-service.d.ts.map +1 -1
  14. package/lib/browser/chat-tool-request-service.js +3 -1
  15. package/lib/browser/chat-tool-request-service.js.map +1 -1
  16. package/lib/common/chat-agents.d.ts +5 -2
  17. package/lib/common/chat-agents.d.ts.map +1 -1
  18. package/lib/common/chat-agents.js +54 -1
  19. package/lib/common/chat-agents.js.map +1 -1
  20. package/lib/common/chat-agents.spec.d.ts +2 -0
  21. package/lib/common/chat-agents.spec.d.ts.map +1 -0
  22. package/lib/common/chat-agents.spec.js +100 -0
  23. package/lib/common/chat-agents.spec.js.map +1 -0
  24. package/lib/common/chat-model-serialization.d.ts +2 -0
  25. package/lib/common/chat-model-serialization.d.ts.map +1 -1
  26. package/lib/common/chat-model-serialization.js.map +1 -1
  27. package/lib/common/chat-model.d.ts +59 -11
  28. package/lib/common/chat-model.d.ts.map +1 -1
  29. package/lib/common/chat-model.js +82 -2
  30. package/lib/common/chat-model.js.map +1 -1
  31. package/lib/common/chat-request-parser.spec.js +5 -2
  32. package/lib/common/chat-request-parser.spec.js.map +1 -1
  33. package/lib/common/chat-response-model.spec.d.ts +2 -0
  34. package/lib/common/chat-response-model.spec.d.ts.map +1 -0
  35. package/lib/common/chat-response-model.spec.js +43 -0
  36. package/lib/common/chat-response-model.spec.js.map +1 -0
  37. package/package.json +11 -11
  38. package/src/browser/agent-delegation-tool.spec.ts +4 -2
  39. package/src/browser/agent-delegation-tool.ts +40 -29
  40. package/src/browser/chat-tool-preference-bindings.spec.ts +83 -23
  41. package/src/browser/chat-tool-preference-bindings.ts +17 -8
  42. package/src/browser/chat-tool-request-service.ts +3 -2
  43. package/src/common/chat-agents.spec.ts +124 -0
  44. package/src/common/chat-agents.ts +59 -2
  45. package/src/common/chat-model-serialization.ts +2 -0
  46. package/src/common/chat-model.ts +136 -12
  47. package/src/common/chat-request-parser.spec.ts +6 -2
  48. package/src/common/chat-response-model.spec.ts +47 -0
@@ -16,6 +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
20
 
20
21
  export interface SerializableChangeSetElement {
21
22
  kind?: string;
@@ -129,6 +130,7 @@ export interface SerializableChatResponseData {
129
130
  errorMessage?: string;
130
131
  promptVariantId?: string;
131
132
  isPromptVariantEdited?: boolean;
133
+ tokenUsage?: ResponseTokenUsage;
132
134
  content: SerializableChatResponseContentData[];
133
135
  }
134
136
 
@@ -23,11 +23,11 @@ import {
23
23
  AIVariableResolutionRequest,
24
24
  GenericCapabilitySelections,
25
25
  LanguageModelMessage,
26
+ ReasoningSettings,
26
27
  ResolvedAIContextVariable,
27
28
  ResolvedAIVariable,
28
29
  TextMessage,
29
30
  ThinkingMessage,
30
- ThinkingModeSettings,
31
31
  ToolCallResult,
32
32
  ToolRequest,
33
33
  ToolResultMessage,
@@ -78,7 +78,8 @@ export type ChatChangeEvent =
78
78
  | ChatEditCancelEvent
79
79
  | ChatEditSubmitEvent
80
80
  | ChatResponseChangedEvent
81
- | ChatChangeHierarchyBranchEvent;
81
+ | ChatChangeHierarchyBranchEvent
82
+ | ChatInteractionNeededEvent;
82
83
 
83
84
  export interface ChatAddRequestEvent {
84
85
  kind: 'addRequest';
@@ -135,10 +136,18 @@ export interface ChatResponseChangedEvent {
135
136
  kind: 'responseChanged';
136
137
  }
137
138
 
139
+ export interface ChatInteractionNeededEvent {
140
+ kind: 'interactionNeeded';
141
+ contentPart: InteractiveContent & ChatResponseContent;
142
+ }
143
+
138
144
  export namespace ChatChangeEvent {
139
145
  export function isChangeSetEvent(event: ChatChangeEvent): event is ChatUpdateChangeSetEvent {
140
146
  return event.kind === 'updateChangeSet';
141
147
  }
148
+ export function isInteractionNeededEvent(event: ChatChangeEvent): event is ChatInteractionNeededEvent {
149
+ return event.kind === 'interactionNeeded';
150
+ }
142
151
  }
143
152
 
144
153
  export type ChatRequestRemovalReason = 'removal' | 'resend' | 'adoption';
@@ -212,11 +221,8 @@ export interface ChatHierarchyBranchItem<TRequest extends ChatRequestModel = Cha
212
221
  }
213
222
 
214
223
  export interface CommonChatSessionSettings {
215
- /**
216
- * Theia-specific settings for extended thinking mode.
217
- * These are processed by Theia and converted to provider-specific formats.
218
- */
219
- thinkingMode?: ThinkingModeSettings;
224
+ /** Reasoning configuration for this session; applied to reasoning-capable models. */
225
+ reasoning?: ReasoningSettings;
220
226
  /** Per-session tool confirmation timeout in seconds. Overrides the global preference when set. */
221
227
  confirmationTimeout?: number;
222
228
  }
@@ -382,6 +388,29 @@ export interface ChatProgressMessage {
382
388
  content: string;
383
389
  }
384
390
 
391
+ /**
392
+ * Interface for ChatResponseContent parts that require user interaction.
393
+ * Content parts that implement this interface can be tracked by the delegation
394
+ * renderer without content-type-specific checks.
395
+ */
396
+ export interface InteractiveContent {
397
+ /** Stable identifier for deduplication in pending interaction tracking. */
398
+ readonly interactionId: string | undefined;
399
+ /** Whether the interaction has been resolved (e.g., confirmed/denied, option selected). */
400
+ readonly isResolved: boolean;
401
+ /** Resolves when the interaction is resolved. Used for cleanup in delegation chains. */
402
+ readonly whenResolved: Promise<void>;
403
+ }
404
+
405
+ export namespace InteractiveContent {
406
+ export function is(content: unknown): content is InteractiveContent {
407
+ return typeof content === 'object' && !!content
408
+ && 'interactionId' in content
409
+ && 'isResolved' in content
410
+ && 'whenResolved' in content;
411
+ }
412
+ }
413
+
385
414
  export interface ChatResponseContent {
386
415
  kind: string;
387
416
  /**
@@ -526,7 +555,7 @@ export interface HorizontalLayoutChatResponseContent extends ChatResponseContent
526
555
  content: ChatResponseContent[];
527
556
  }
528
557
 
529
- export interface ToolCallChatResponseContent extends Required<ChatResponseContent> {
558
+ export interface ToolCallChatResponseContent extends Required<ChatResponseContent>, InteractiveContent {
530
559
  kind: 'toolCall';
531
560
  id?: string;
532
561
  name?: string;
@@ -803,7 +832,7 @@ export type MultiSelectQuestionResponseHandler = (
803
832
  selectedOptions: { text: string, value?: string }[],
804
833
  ) => void;
805
834
 
806
- export interface QuestionResponseContent extends ChatResponseContent {
835
+ export interface QuestionResponseContent extends ChatResponseContent, InteractiveContent {
807
836
  kind: 'question';
808
837
  question: string;
809
838
  header?: string;
@@ -844,6 +873,13 @@ export namespace QuestionResponseContent {
844
873
  }
845
874
  }
846
875
 
876
+ export interface ResponseTokenUsage {
877
+ readonly inputTokens: number;
878
+ readonly outputTokens: number;
879
+ readonly cacheCreationInputTokens?: number;
880
+ readonly cacheReadInputTokens?: number;
881
+ }
882
+
847
883
  export interface ChatResponse {
848
884
  readonly content: ChatResponseContent[];
849
885
  asString(): string;
@@ -858,6 +894,11 @@ export interface ChatResponseModel {
858
894
  * Use this to be notified for any change in the response model
859
895
  */
860
896
  readonly onDidChange: Event<void>;
897
+ /**
898
+ * Fires when this response requires user interaction (e.g., tool confirmation, question response).
899
+ * The content part that needs interaction is provided as the event payload.
900
+ */
901
+ readonly onInteractionNeeded: Event<InteractiveContent & ChatResponseContent>;
861
902
  /**
862
903
  * The unique identifier of the response model
863
904
  */
@@ -911,6 +952,7 @@ export interface ChatResponseModel {
911
952
  * Indicates whether the prompt variant was customized/edited
912
953
  */
913
954
  readonly isPromptVariantEdited?: boolean;
955
+ readonly tokenUsage?: ResponseTokenUsage;
914
956
  toSerializable(): SerializableChatResponseData;
915
957
  }
916
958
 
@@ -1052,6 +1094,17 @@ export class MutableChatModel implements ChatModel, Disposable {
1052
1094
  this._settings = settings;
1053
1095
  }
1054
1096
 
1097
+ addChildModel(child: MutableChatModel): Disposable {
1098
+ const disposable = new DisposableCollection();
1099
+ disposable.push(child.onDidChange(event => {
1100
+ if (ChatChangeEvent.isInteractionNeededEvent(event)) {
1101
+ this._onDidChangeEmitter.fire(event);
1102
+ }
1103
+ }));
1104
+ this.toDispose.push(disposable);
1105
+ return disposable;
1106
+ }
1107
+
1055
1108
  addRequest(parsedChatRequest: ParsedChatRequest, agentId?: string, context: ChatContext = { variables: [] }): MutableChatRequestModel {
1056
1109
  const add = this.getTargetForRequestAddition(parsedChatRequest);
1057
1110
  const requestModel = new MutableChatRequestModel(
@@ -1680,10 +1733,14 @@ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRe
1680
1733
 
1681
1734
  // Wire response changes to propagate through request to session
1682
1735
  this._response.onDidChange(() => {
1683
- // Fire a generic addVariable event to propagate response changes
1684
1736
  this._onDidChangeEmitter.fire({ kind: 'responseChanged' });
1685
1737
  }, this, this.toDispose);
1686
1738
 
1739
+ // Wire interaction needed events to propagate through request to session
1740
+ this._response.onInteractionNeeded(contentPart => {
1741
+ this._onDidChangeEmitter.fire({ kind: 'interactionNeeded', contentPart });
1742
+ }, this, this.toDispose);
1743
+
1687
1744
  this.toDispose.push(this._onDidChangeEmitter);
1688
1745
  }
1689
1746
 
@@ -2236,7 +2293,7 @@ export class CodeChatResponseContentImpl implements CodeChatResponseContent {
2236
2293
  }
2237
2294
  }
2238
2295
 
2239
- export class ToolCallChatResponseContentImpl implements ToolCallChatResponseContent {
2296
+ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseContent, InteractiveContent {
2240
2297
  readonly kind = 'toolCall';
2241
2298
  protected _id?: string;
2242
2299
  protected _name?: string;
@@ -2298,6 +2355,14 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
2298
2355
  this._confirmationTimeout = value;
2299
2356
  }
2300
2357
 
2358
+ get interactionId(): string | undefined {
2359
+ return this._id;
2360
+ }
2361
+
2362
+ get isResolved(): boolean {
2363
+ return this.finished;
2364
+ }
2365
+
2301
2366
  get confirmed(): Promise<boolean> {
2302
2367
  return this._confirmed;
2303
2368
  }
@@ -2310,6 +2375,10 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
2310
2375
  return this._whenFinished;
2311
2376
  }
2312
2377
 
2378
+ get whenResolved(): Promise<void> {
2379
+ return this._whenFinished;
2380
+ }
2381
+
2313
2382
  createConfirmationPromise(): Promise<boolean> {
2314
2383
  if (!this._confirmationResolver) {
2315
2384
  this._confirmed = new Promise<boolean>((resolve, reject) => {
@@ -2563,12 +2632,14 @@ export interface QuestionResponseContentOptions {
2563
2632
  * Default implementation for the QuestionResponseContent.
2564
2633
  * Can be created with or without handler/request for read-only (restored) mode.
2565
2634
  */
2566
- export class QuestionResponseContentImpl implements QuestionResponseContent {
2635
+ export class QuestionResponseContentImpl implements QuestionResponseContent, InteractiveContent {
2567
2636
  readonly kind = 'question';
2568
2637
  public multiSelect?: boolean;
2569
2638
  public header?: string;
2570
2639
  public onSkip?: () => void;
2571
2640
  protected _selectedOptions: { text: string; value?: string }[] | undefined;
2641
+ protected _resolvedResolver?: () => void;
2642
+ readonly whenResolved: Promise<void>;
2572
2643
 
2573
2644
  constructor(
2574
2645
  question: string,
@@ -2596,14 +2667,33 @@ export class QuestionResponseContentImpl implements QuestionResponseContent {
2596
2667
  this.onSkip = questionOptions?.onSkip;
2597
2668
  this._selectedOptions = questionOptions?.selectedOptions ??
2598
2669
  (questionOptions?.selectedOption ? [questionOptions.selectedOption] : undefined);
2670
+ if (this._selectedOptions) {
2671
+ this.whenResolved = Promise.resolve();
2672
+ } else {
2673
+ this.whenResolved = new Promise<void>(resolve => {
2674
+ this._resolvedResolver = resolve;
2675
+ });
2676
+ }
2677
+ if (!this.isReadOnly && this.request) {
2678
+ this.request.response.fireInteractionNeeded(this);
2679
+ }
2599
2680
  }
2600
2681
 
2601
2682
  get isReadOnly(): boolean {
2602
2683
  return !this.handler || !this.request;
2603
2684
  }
2604
2685
 
2686
+ get interactionId(): string | undefined {
2687
+ return `question-${this.question}`;
2688
+ }
2689
+
2690
+ get isResolved(): boolean {
2691
+ return this.selectedOption !== undefined;
2692
+ }
2693
+
2605
2694
  set selectedOption(option: { text: string; value?: string } | undefined) {
2606
2695
  this._selectedOptions = option ? [option] : undefined;
2696
+ this._resolvedResolver?.();
2607
2697
  if (this.request) {
2608
2698
  this.request.response.response.responseContentChanged();
2609
2699
  }
@@ -2614,6 +2704,7 @@ export class QuestionResponseContentImpl implements QuestionResponseContent {
2614
2704
 
2615
2705
  set selectedOptions(options: { text: string; value?: string }[] | undefined) {
2616
2706
  this._selectedOptions = options;
2707
+ this._resolvedResolver?.();
2617
2708
  if (this.request) {
2618
2709
  this.request.response.response.responseContentChanged();
2619
2710
  }
@@ -2766,6 +2857,9 @@ export class MutableChatResponseModel implements ChatResponseModel {
2766
2857
  protected readonly _onDidChangeEmitter = new Emitter<void>();
2767
2858
  onDidChange: Event<void> = this._onDidChangeEmitter.event;
2768
2859
 
2860
+ protected readonly _onInteractionNeededEmitter = new Emitter<InteractiveContent & ChatResponseContent>();
2861
+ readonly onInteractionNeeded: Event<InteractiveContent & ChatResponseContent> = this._onInteractionNeededEmitter.event;
2862
+
2769
2863
  data = {};
2770
2864
 
2771
2865
  protected _id: string;
@@ -2780,6 +2874,8 @@ export class MutableChatResponseModel implements ChatResponseModel {
2780
2874
  protected _cancellationToken: CancellationTokenSource;
2781
2875
  protected _promptVariantId?: string;
2782
2876
  protected _isPromptVariantEdited?: boolean;
2877
+ protected _tokenUsage?: ResponseTokenUsage;
2878
+ protected _tokenUsageEntries: ResponseTokenUsage[] = [];
2783
2879
 
2784
2880
  constructor(
2785
2881
  requestId: string,
@@ -2823,6 +2919,7 @@ export class MutableChatResponseModel implements ChatResponseModel {
2823
2919
  this._progressMessages = [];
2824
2920
  this._promptVariantId = data.promptVariantId;
2825
2921
  this._isPromptVariantEdited = data.isPromptVariantEdited ?? false;
2922
+ this._tokenUsage = data.tokenUsage;
2826
2923
 
2827
2924
  if (data.errorMessage) {
2828
2925
  this._errorObject = new Error(data.errorMessage);
@@ -2902,6 +2999,24 @@ export class MutableChatResponseModel implements ChatResponseModel {
2902
2999
  return this._isPromptVariantEdited ?? false;
2903
3000
  }
2904
3001
 
3002
+ get tokenUsage(): ResponseTokenUsage | undefined {
3003
+ return this._tokenUsage;
3004
+ }
3005
+
3006
+ setTokenUsage(usage: ResponseTokenUsage): void {
3007
+ this._tokenUsage = usage;
3008
+ this.addTokenUsageEntry(usage);
3009
+ this._onDidChangeEmitter.fire();
3010
+ }
3011
+
3012
+ addTokenUsageEntry(usage: ResponseTokenUsage): void {
3013
+ this._tokenUsageEntries.push(usage);
3014
+ }
3015
+
3016
+ get tokenUsageEntries(): readonly ResponseTokenUsage[] {
3017
+ return this._tokenUsageEntries;
3018
+ }
3019
+
2905
3020
  setPromptVariantInfo(variantId: string | undefined, isEdited: boolean): void {
2906
3021
  this._promptVariantId = variantId;
2907
3022
  this._isPromptVariantEdited = isEdited;
@@ -2952,6 +3067,14 @@ export class MutableChatResponseModel implements ChatResponseModel {
2952
3067
  this._onDidChangeEmitter.fire();
2953
3068
  }
2954
3069
 
3070
+ fireInteractionNeeded(contentPart: InteractiveContent & ChatResponseContent): void {
3071
+ this._onInteractionNeededEmitter.fire(contentPart);
3072
+ }
3073
+
3074
+ notifyChanged(): void {
3075
+ this._onDidChangeEmitter.fire();
3076
+ }
3077
+
2955
3078
  error(error: Error): void {
2956
3079
  this._isComplete = true;
2957
3080
  this._isWaitingForInput = false;
@@ -2975,6 +3098,7 @@ export class MutableChatResponseModel implements ChatResponseModel {
2975
3098
  errorMessage: this.errorObject?.message,
2976
3099
  promptVariantId: this._promptVariantId,
2977
3100
  isPromptVariantEdited: this._isPromptVariantEdited,
3101
+ tokenUsage: this._tokenUsage,
2978
3102
  content: this.response.content.map(c => {
2979
3103
  const serialized = c.toSerializable?.();
2980
3104
  if (!serialized) {
@@ -323,6 +323,9 @@ describe('ChatRequestParserImpl', () => {
323
323
  invoke: async () => undefined,
324
324
  });
325
325
 
326
+ // Set up the parser's agent service so it can recognise agents during parsing
327
+ chatAgentService.getAgents.returns([createAgent('agentA'), createAgent('agentB')]);
328
+
326
329
  const tool = new AgentDelegationTool();
327
330
  (tool as unknown as { getChatAgentService: () => unknown }).getChatAgentService = () => ({
328
331
  getAgent: sinon.stub().withArgs('agentA').returns(createAgent('agentA')),
@@ -348,11 +351,12 @@ describe('ChatRequestParserImpl', () => {
348
351
  id: 'session-1',
349
352
  model: {
350
353
  changeSet: {
351
- onDidChange: sinon.stub().returns({}),
354
+ onDidChange: sinon.stub().returns({ dispose: sinon.stub() }),
352
355
  getElements: sinon.stub().returns([]),
353
356
  setTitle: sinon.stub(),
354
357
  addElements: sinon.stub(),
355
- }
358
+ },
359
+ onDidChange: sinon.stub().returns({ dispose: sinon.stub() })
356
360
  }
357
361
  }),
358
362
  sendRequest,
@@ -0,0 +1,47 @@
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 { MutableChatResponseModel } from './chat-model';
19
+
20
+ describe('MutableChatResponseModel', () => {
21
+ describe('setTokenUsage', () => {
22
+ it('should also add a token usage entry', () => {
23
+ const response = new MutableChatResponseModel('req-1');
24
+ const usage = { inputTokens: 100, outputTokens: 50 };
25
+
26
+ response.setTokenUsage(usage);
27
+
28
+ expect(response.tokenUsage).to.deep.equal(usage);
29
+ expect(response.tokenUsageEntries).to.have.lengthOf(1);
30
+ expect(response.tokenUsageEntries[0]).to.deep.equal(usage);
31
+ });
32
+
33
+ it('should accumulate entries across multiple setTokenUsage calls', () => {
34
+ const response = new MutableChatResponseModel('req-1');
35
+ const usage1 = { inputTokens: 100, outputTokens: 50 };
36
+ const usage2 = { inputTokens: 200, outputTokens: 80 };
37
+
38
+ response.setTokenUsage(usage1);
39
+ response.setTokenUsage(usage2);
40
+
41
+ expect(response.tokenUsage).to.deep.equal(usage2);
42
+ expect(response.tokenUsageEntries).to.have.lengthOf(2);
43
+ expect(response.tokenUsageEntries[0]).to.deep.equal(usage1);
44
+ expect(response.tokenUsageEntries[1]).to.deep.equal(usage2);
45
+ });
46
+ });
47
+ });