@theia/ai-core 1.64.0-next.35 → 1.64.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 (74) hide show
  1. package/README.md +3 -0
  2. package/lib/browser/ai-activation-service.d.ts +22 -9
  3. package/lib/browser/ai-activation-service.d.ts.map +1 -1
  4. package/lib/browser/ai-activation-service.js +18 -35
  5. package/lib/browser/ai-activation-service.js.map +1 -1
  6. package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
  7. package/lib/browser/ai-core-frontend-module.js +9 -4
  8. package/lib/browser/ai-core-frontend-module.js.map +1 -1
  9. package/lib/browser/ai-core-preferences.d.ts +1 -2
  10. package/lib/browser/ai-core-preferences.d.ts.map +1 -1
  11. package/lib/browser/ai-core-preferences.js +25 -20
  12. package/lib/browser/ai-core-preferences.js.map +1 -1
  13. package/lib/browser/ai-settings-service.d.ts +3 -1
  14. package/lib/browser/ai-settings-service.d.ts.map +1 -1
  15. package/lib/browser/ai-settings-service.js +24 -2
  16. package/lib/browser/ai-settings-service.js.map +1 -1
  17. package/lib/browser/ai-view-contribution.js +1 -1
  18. package/lib/browser/ai-view-contribution.js.map +1 -1
  19. package/lib/browser/frontend-language-model-alias-registry.d.ts +31 -0
  20. package/lib/browser/frontend-language-model-alias-registry.d.ts.map +1 -0
  21. package/lib/browser/frontend-language-model-alias-registry.js +170 -0
  22. package/lib/browser/frontend-language-model-alias-registry.js.map +1 -0
  23. package/lib/browser/frontend-language-model-registry.d.ts +11 -4
  24. package/lib/browser/frontend-language-model-registry.d.ts.map +1 -1
  25. package/lib/browser/frontend-language-model-registry.js +48 -6
  26. package/lib/browser/frontend-language-model-registry.js.map +1 -1
  27. package/lib/browser/index.d.ts +1 -0
  28. package/lib/browser/index.d.ts.map +1 -1
  29. package/lib/browser/index.js +1 -0
  30. package/lib/browser/index.js.map +1 -1
  31. package/lib/common/index.d.ts +1 -0
  32. package/lib/common/index.d.ts.map +1 -1
  33. package/lib/common/index.js +1 -0
  34. package/lib/common/index.js.map +1 -1
  35. package/lib/common/language-model-alias.d.ts +58 -0
  36. package/lib/common/language-model-alias.d.ts.map +1 -0
  37. package/lib/common/language-model-alias.js +20 -0
  38. package/lib/common/language-model-alias.js.map +1 -0
  39. package/lib/common/language-model.d.ts +24 -2
  40. package/lib/common/language-model.d.ts.map +1 -1
  41. package/lib/common/language-model.js +15 -3
  42. package/lib/common/language-model.js.map +1 -1
  43. package/lib/common/prompt-service.d.ts +19 -9
  44. package/lib/common/prompt-service.d.ts.map +1 -1
  45. package/lib/common/prompt-service.js +77 -31
  46. package/lib/common/prompt-service.js.map +1 -1
  47. package/lib/common/protocol.d.ts +4 -0
  48. package/lib/common/protocol.d.ts.map +1 -1
  49. package/lib/common/protocol.js.map +1 -1
  50. package/lib/node/ai-core-backend-module.js +2 -2
  51. package/lib/node/ai-core-backend-module.js.map +1 -1
  52. package/lib/node/backend-language-model-registry.d.ts +2 -1
  53. package/lib/node/backend-language-model-registry.d.ts.map +1 -1
  54. package/lib/node/backend-language-model-registry.js +12 -5
  55. package/lib/node/backend-language-model-registry.js.map +1 -1
  56. package/lib/node/language-model-frontend-delegate.d.ts.map +1 -1
  57. package/lib/node/language-model-frontend-delegate.js +1 -1
  58. package/lib/node/language-model-frontend-delegate.js.map +1 -1
  59. package/package.json +10 -10
  60. package/src/browser/ai-activation-service.ts +26 -34
  61. package/src/browser/ai-core-frontend-module.ts +16 -8
  62. package/src/browser/ai-core-preferences.ts +28 -21
  63. package/src/browser/ai-settings-service.ts +23 -4
  64. package/src/browser/frontend-language-model-alias-registry.ts +166 -0
  65. package/src/browser/frontend-language-model-registry.ts +53 -9
  66. package/src/browser/index.ts +1 -0
  67. package/src/common/index.ts +1 -0
  68. package/src/common/language-model-alias.ts +76 -0
  69. package/src/common/language-model.ts +42 -4
  70. package/src/common/prompt-service.ts +91 -37
  71. package/src/common/protocol.ts +4 -0
  72. package/src/node/ai-core-backend-module.ts +3 -3
  73. package/src/node/backend-language-model-registry.ts +9 -1
  74. package/src/node/language-model-frontend-delegate.ts +2 -2
@@ -25,6 +25,7 @@ export * from './ai-core-preferences';
25
25
  export * from './ai-settings-service';
26
26
  export * from './ai-view-contribution';
27
27
  export * from './frontend-language-model-registry';
28
+ export * from './frontend-language-model-alias-registry';
28
29
  export * from './frontend-variable-service';
29
30
  export * from './prompttemplate-contribution';
30
31
  export * from './theia-variable-contribution';
@@ -20,6 +20,7 @@ export * from './tool-invocation-registry';
20
20
  export * from './language-model-delegate';
21
21
  export * from './language-model-util';
22
22
  export * from './language-model';
23
+ export * from './language-model-alias';
23
24
  export * from './prompt-service';
24
25
  export * from './prompt-service-util';
25
26
  export * from './prompt-text';
@@ -0,0 +1,76 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024-2025 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 { Event } from '@theia/core';
18
+
19
+ /**
20
+ * Represents an alias for a language model, allowing fallback and selection.
21
+ */
22
+ export interface LanguageModelAlias {
23
+ /**
24
+ * The unique identifier for the alias.
25
+ */
26
+ id: string;
27
+ /**
28
+ * The list of default model IDs to use if no selectedModelId is set.
29
+ * Ordered by priority. The first entry also serves as fallback.
30
+ */
31
+ defaultModelIds: string[];
32
+ /**
33
+ * A human-readable description of the alias.
34
+ */
35
+ description?: string;
36
+ /**
37
+ * The currently selected model ID, if any.
38
+ */
39
+ selectedModelId?: string;
40
+ }
41
+
42
+ export const LanguageModelAliasRegistry = Symbol('LanguageModelAliasRegistry');
43
+ /**
44
+ * Registry for managing language model aliases.
45
+ */
46
+ export interface LanguageModelAliasRegistry {
47
+ /**
48
+ * Promise that resolves when the registry is ready for use (preferences loaded).
49
+ */
50
+ ready: Promise<void>;
51
+
52
+ /**
53
+ * Event that is fired when the alias list changes.
54
+ */
55
+ onDidChange: Event<void>;
56
+ /**
57
+ * Add a new alias or update an existing one.
58
+ */
59
+ addAlias(alias: LanguageModelAlias): void;
60
+ /**
61
+ * Remove an alias by its id.
62
+ */
63
+ removeAlias(id: string): void;
64
+ /**
65
+ * Get all aliases.
66
+ */
67
+ getAliases(): LanguageModelAlias[];
68
+ /**
69
+ * Resolve an alias or model id to a prioritized list of model ids.
70
+ * If the id is not an alias, returns [id].
71
+ * If the alias exists and has a selectedModelId, returns [selectedModelId].
72
+ * If the alias exists and has no selectedModelId, returns defaultModelIds.
73
+ * If the alias does not exist, returns undefined.
74
+ */
75
+ resolveAlias(id: string): string[] | undefined;
76
+ }
@@ -295,6 +295,7 @@ export interface LanguageModelMetaData {
295
295
  readonly family?: string;
296
296
  readonly maxInputTokens?: number;
297
297
  readonly maxOutputTokens?: number;
298
+ readonly status: LanguageModelStatus;
298
299
  }
299
300
 
300
301
  export namespace LanguageModelMetaData {
@@ -303,6 +304,11 @@ export namespace LanguageModelMetaData {
303
304
  }
304
305
  }
305
306
 
307
+ export interface LanguageModelStatus {
308
+ status: 'ready' | 'unavailable';
309
+ message?: string;
310
+ }
311
+
306
312
  export interface LanguageModel extends LanguageModelMetaData {
307
313
  request(request: UserRequest, cancellationToken?: CancellationToken): Promise<LanguageModelResponse>;
308
314
  }
@@ -331,6 +337,10 @@ export interface LanguageModelSelector extends VsCodeLanguageModelSelector {
331
337
  export type LanguageModelRequirement = Omit<LanguageModelSelector, 'agent'>;
332
338
 
333
339
  export const LanguageModelRegistry = Symbol('LanguageModelRegistry');
340
+
341
+ /**
342
+ * Base interface for language model registries (frontend and backend).
343
+ */
334
344
  export interface LanguageModelRegistry {
335
345
  onChange: Event<{ models: LanguageModel[] }>;
336
346
  addLanguageModels(models: LanguageModel[]): void;
@@ -338,7 +348,22 @@ export interface LanguageModelRegistry {
338
348
  getLanguageModel(id: string): Promise<LanguageModel | undefined>;
339
349
  removeLanguageModels(id: string[]): void;
340
350
  selectLanguageModel(request: LanguageModelSelector): Promise<LanguageModel | undefined>;
341
- selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[]>;
351
+ selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[] | undefined>;
352
+ patchLanguageModel<T extends LanguageModel = LanguageModel>(id: string, patch: Partial<T>): Promise<void>;
353
+ }
354
+
355
+ export const FrontendLanguageModelRegistry = Symbol('FrontendLanguageModelRegistry');
356
+
357
+ /**
358
+ * Frontend-specific language model registry interface (supports alias resolution).
359
+ */
360
+ export interface FrontendLanguageModelRegistry extends LanguageModelRegistry {
361
+ /**
362
+ * If an id of a language model is provded, returns the LanguageModel if it is `ready`.
363
+ * If an alias is provided, finds the highest-priority ready model from that alias.
364
+ * If none are ready returns undefined.
365
+ */
366
+ getReadyLanguageModel(idOrAlias: string): Promise<LanguageModel | undefined>;
342
367
  }
343
368
 
344
369
  @injectable()
@@ -405,15 +430,28 @@ export class DefaultLanguageModelRegistryImpl implements LanguageModelRegistry {
405
430
  });
406
431
  }
407
432
 
408
- async selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[]> {
433
+ async selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[] | undefined> {
409
434
  await this.initialized;
410
435
  // TODO check for actor and purpose against settings
411
- return this.languageModels.filter(model => isModelMatching(request, model));
436
+ return this.languageModels.filter(model => model.status.status === 'ready' && isModelMatching(request, model));
412
437
  }
413
438
 
414
439
  async selectLanguageModel(request: LanguageModelSelector): Promise<LanguageModel | undefined> {
415
- return (await this.selectLanguageModels(request))[0];
440
+ const models = await this.selectLanguageModels(request);
441
+ return models ? models[0] : undefined;
442
+ }
443
+
444
+ async patchLanguageModel<T extends LanguageModel = LanguageModel>(id: string, patch: Partial<T>): Promise<void> {
445
+ await this.initialized;
446
+ const model = this.languageModels.find(m => m.id === id);
447
+ if (!model) {
448
+ this.logger.warn(`Language model with id ${id} not found for patch.`);
449
+ return;
450
+ }
451
+ Object.assign(model, patch);
452
+ this.changeEmitter.fire({ models: this.languageModels });
416
453
  }
454
+
417
455
  }
418
456
 
419
457
  export function isModelMatching(request: LanguageModelSelector, model: LanguageModel): boolean {
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { Event, Emitter, URI, ILogger } from '@theia/core';
17
+ import { Event, Emitter, URI, ILogger, DisposableCollection } from '@theia/core';
18
18
  import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
19
19
  import { AIVariableArg, AIVariableContext, AIVariableService, createAIResolveVariableCache, ResolvedAIVariable } from './variable-service';
20
20
  import { ToolInvocationRegistry } from './tool-invocation-registry';
@@ -283,7 +283,7 @@ export interface PromptService {
283
283
  /**
284
284
  * Event fired when the selected variant for a prompt variant set changes
285
285
  */
286
- readonly onSelectedVariantChange: Event<{ promptVariantSetId: string, variantId: string }>;
286
+ readonly onSelectedVariantChange: Event<{ promptVariantSetId: string, variantId: string | undefined }>;
287
287
 
288
288
  /**
289
289
  * Gets the raw prompt fragment with comments
@@ -368,7 +368,7 @@ export interface PromptService {
368
368
  * @param promptVariantSetId The prompt variant set id
369
369
  * @returns The selected variant ID from settings, or undefined if none is selected
370
370
  */
371
- getSelectedVariantId(promptVariantSetId: string): Promise<string | undefined>;
371
+ getSelectedVariantId(promptVariantSetId: string): string | undefined;
372
372
 
373
373
  /**
374
374
  * Gets the effective variant ID that is guaranteed to be valid if one exists.
@@ -376,7 +376,7 @@ export interface PromptService {
376
376
  * @param promptVariantSetId The prompt variant set id
377
377
  * @returns A valid variant ID if one exists, or undefined if no valid variant can be found
378
378
  */
379
- getEffectiveVariantId(promptVariantSetId: string): Promise<string | undefined>;
379
+ getEffectiveVariantId(promptVariantSetId: string): string | undefined;
380
380
 
381
381
  /**
382
382
  * Gets the default variant ID of the given set
@@ -419,12 +419,16 @@ export interface PromptService {
419
419
  export class PromptServiceImpl implements PromptService {
420
420
  @inject(ILogger)
421
421
  protected readonly logger: ILogger;
422
+
422
423
  @inject(AISettingsService) @optional()
423
424
  protected readonly settingsService: AISettingsService | undefined;
424
425
 
425
426
  @inject(PromptFragmentCustomizationService) @optional()
426
427
  protected readonly customizationService: PromptFragmentCustomizationService | undefined;
427
428
 
429
+ // Map to store selected variant for each prompt variant set (key: promptVariantSetId, value: variantId)
430
+ protected _selectedVariantsMap = new Map<string, string>();
431
+
428
432
  @inject(AIVariableService) @optional()
429
433
  protected readonly variableService: AIVariableService | undefined;
430
434
 
@@ -445,21 +449,80 @@ export class PromptServiceImpl implements PromptService {
445
449
  readonly onPromptsChange = this._onPromptsChangeEmitter.event;
446
450
 
447
451
  // Event emitter for selected variant changes
448
- protected _onSelectedVariantChangeEmitter = new Emitter<{ promptVariantSetId: string, variantId: string }>();
452
+ protected _onSelectedVariantChangeEmitter = new Emitter<{ promptVariantSetId: string, variantId: string | undefined }>();
449
453
  readonly onSelectedVariantChange = this._onSelectedVariantChangeEmitter.event;
450
454
 
455
+ protected promptChangeDebounceTimer?: NodeJS.Timeout;
456
+
457
+ protected toDispose = new DisposableCollection();
458
+
459
+ protected fireOnPromptsChangeDebounced(): void {
460
+ if (this.promptChangeDebounceTimer) {
461
+ clearTimeout(this.promptChangeDebounceTimer);
462
+ }
463
+ this.promptChangeDebounceTimer = setTimeout(() => {
464
+ this._onPromptsChangeEmitter.fire();
465
+ }, 300);
466
+ }
467
+
451
468
  @postConstruct()
452
469
  protected init(): void {
453
470
  if (this.customizationService) {
454
- this.customizationService.onDidChangePromptFragmentCustomization(() => {
455
- this._onPromptsChangeEmitter.fire();
456
- });
457
- this.customizationService.onDidChangeCustomAgents(() => {
458
- this._onPromptsChangeEmitter.fire();
459
- });
471
+ this.toDispose.pushAll([
472
+ this.customizationService.onDidChangePromptFragmentCustomization(() => {
473
+ this.fireOnPromptsChangeDebounced();
474
+ }),
475
+ this.customizationService.onDidChangeCustomAgents(() => {
476
+ this.fireOnPromptsChangeDebounced();
477
+ })
478
+ ]);
479
+ }
480
+ if (this.settingsService) {
481
+ this.recalculateSelectedVariantsMap();
482
+ this.toDispose.push(
483
+ this.settingsService!.onDidChange(async () => {
484
+ await this.recalculateSelectedVariantsMap();
485
+ })
486
+ );
460
487
  }
461
488
  }
462
489
 
490
+ /**
491
+ * Recalculates the selected variants map for all variant sets and fires the onSelectedVariantChangeEmitter
492
+ * if the selectedVariants field has changed.
493
+ */
494
+ protected async recalculateSelectedVariantsMap(): Promise<void> {
495
+ if (!this.settingsService) {
496
+ return;
497
+ }
498
+ const agentSettingsMap = await this.settingsService.getSettings();
499
+ const newSelectedVariants = new Map<string, string>();
500
+ for (const agentSettings of Object.values(agentSettingsMap)) {
501
+ if (agentSettings.selectedVariants) {
502
+ for (const [variantSetId, variantId] of Object.entries(agentSettings.selectedVariants)) {
503
+ if (!newSelectedVariants.has(variantSetId)) {
504
+ newSelectedVariants.set(variantSetId, variantId);
505
+ }
506
+ }
507
+ }
508
+ }
509
+ // Compare with the old map and fire events for changes and removed variant sets
510
+ for (const [variantSetId, newVariantId] of newSelectedVariants.entries()) {
511
+ const oldVariantId = this._selectedVariantsMap.get(variantSetId);
512
+ if (oldVariantId !== newVariantId) {
513
+ this._onSelectedVariantChangeEmitter.fire({ promptVariantSetId: variantSetId, variantId: newVariantId });
514
+ }
515
+ }
516
+ for (const oldVariantSetId of this._selectedVariantsMap.keys()) {
517
+ if (!newSelectedVariants.has(oldVariantSetId)) {
518
+ this._onSelectedVariantChangeEmitter.fire({ promptVariantSetId: oldVariantSetId, variantId: undefined });
519
+ }
520
+ }
521
+ this._selectedVariantsMap = newSelectedVariants;
522
+ // Also fire a full prompts change, because other fields (like effectiveVariantId) might have changed
523
+ this.fireOnPromptsChangeDebounced();
524
+ }
525
+
463
526
  // ===== Fragment Retrieval Methods =====
464
527
 
465
528
  /**
@@ -506,54 +569,45 @@ export class PromptServiceImpl implements PromptService {
506
569
  return commentRegex.test(templateText) ? templateText.replace(commentRegex, '').trimStart() : templateText;
507
570
  }
508
571
 
509
- async getSelectedVariantId(fragmentId: string): Promise<string | undefined> {
510
- if (this.settingsService) {
511
- const agentSettingsMap = await this.settingsService.getSettings();
512
-
513
- for (const agentSettings of Object.values(agentSettingsMap)) {
514
- if (agentSettings.selectedVariants && agentSettings.selectedVariants[fragmentId]) {
515
- return agentSettings.selectedVariants[fragmentId];
516
- }
517
- }
518
- }
519
- return undefined;
572
+ getSelectedVariantId(variantSetId: string): string | undefined {
573
+ return this._selectedVariantsMap.get(variantSetId);
520
574
  }
521
575
 
522
- async getEffectiveVariantId(fragmentId: string): Promise<string | undefined> {
523
- const selectedVariantId = await this.getSelectedVariantId(fragmentId);
576
+ getEffectiveVariantId(variantSetId: string): string | undefined {
577
+ const selectedVariantId = this.getSelectedVariantId(variantSetId);
524
578
 
525
579
  // Check if the selected variant actually exists
526
580
  if (selectedVariantId) {
527
- const variantIds = this.getVariantIds(fragmentId);
581
+ const variantIds = this.getVariantIds(variantSetId);
528
582
  if (!variantIds.includes(selectedVariantId)) {
529
- this.logger.warn(`Selected variant '${selectedVariantId}' for prompt set '${fragmentId}' does not exist. Falling back to default variant.`);
583
+ this.logger.warn(`Selected variant '${selectedVariantId}' for prompt set '${variantSetId}' does not exist. Falling back to default variant.`);
530
584
  } else {
531
585
  return selectedVariantId;
532
586
  }
533
587
  }
534
588
 
535
589
  // Fall back to default variant
536
- const defaultVariantId = this.getDefaultVariantId(fragmentId);
590
+ const defaultVariantId = this.getDefaultVariantId(variantSetId);
537
591
  if (defaultVariantId) {
538
- const variantIds = this.getVariantIds(fragmentId);
592
+ const variantIds = this.getVariantIds(variantSetId);
539
593
  if (!variantIds.includes(defaultVariantId)) {
540
- this.logger.error(`Default variant '${defaultVariantId}' for prompt set '${fragmentId}' does not exist.`);
594
+ this.logger.error(`Default variant '${defaultVariantId}' for prompt set '${variantSetId}' does not exist.`);
541
595
  return undefined;
542
596
  }
543
597
  return defaultVariantId;
544
598
  }
545
599
 
546
600
  // No valid selected or default variant
547
- if (this.getVariantIds(fragmentId).length > 0) {
548
- this.logger.error(`No valid selected or default variant found for prompt set '${fragmentId}'.`);
601
+ if (this.getVariantIds(variantSetId).length > 0) {
602
+ this.logger.error(`No valid selected or default variant found for prompt set '${variantSetId}'.`);
549
603
  }
550
604
  return undefined;
551
605
  }
552
606
 
553
- protected async resolvePotentialSystemPrompt(promptFragmentId: string): Promise<PromptFragment | undefined> {
607
+ protected resolvePotentialSystemPrompt(promptFragmentId: string): PromptFragment | undefined {
554
608
  if (this._promptVariantSetsMap.has(promptFragmentId)) {
555
609
  // This is a systemPrompt find the effective variant
556
- const effectiveVariantId = await this.getEffectiveVariantId(promptFragmentId);
610
+ const effectiveVariantId = this.getEffectiveVariantId(promptFragmentId);
557
611
  if (effectiveVariantId === undefined) {
558
612
  return undefined;
559
613
  }
@@ -565,7 +619,7 @@ export class PromptServiceImpl implements PromptService {
565
619
  // ===== Fragment Resolution Methods =====
566
620
 
567
621
  async getResolvedPromptFragment(systemOrFragmentId: string, args?: { [key: string]: unknown }, context?: AIVariableContext): Promise<ResolvedPromptFragment | undefined> {
568
- const promptFragment = await this.resolvePotentialSystemPrompt(systemOrFragmentId);
622
+ const promptFragment = this.resolvePotentialSystemPrompt(systemOrFragmentId);
569
623
  if (promptFragment === undefined) {
570
624
  return undefined;
571
625
  }
@@ -609,7 +663,7 @@ export class PromptServiceImpl implements PromptService {
609
663
  context?: AIVariableContext,
610
664
  resolveVariable?: (variable: AIVariableArg) => Promise<ResolvedAIVariable | undefined>
611
665
  ): Promise<Omit<ResolvedPromptFragment, 'functionDescriptions'> | undefined> {
612
- const promptFragment = await this.resolvePotentialSystemPrompt(systemOrFragmentId);
666
+ const promptFragment = this.resolvePotentialSystemPrompt(systemOrFragmentId);
613
667
  if (promptFragment === undefined) {
614
668
  return undefined;
615
669
  }
@@ -764,7 +818,7 @@ export class PromptServiceImpl implements PromptService {
764
818
  }
765
819
  }
766
820
 
767
- this._onPromptsChangeEmitter.fire();
821
+ this.fireOnPromptsChangeDebounced();
768
822
  }
769
823
 
770
824
  getVariantIds(variantSetId: string): string[] {
@@ -839,7 +893,7 @@ export class PromptServiceImpl implements PromptService {
839
893
  this.addFragmentVariant(promptVariantSetId, promptFragment.id, isDefault);
840
894
  }
841
895
 
842
- this._onPromptsChangeEmitter.fire();
896
+ this.fireOnPromptsChangeDebounced();
843
897
  }
844
898
 
845
899
  // ===== Variant Management Methods =====
@@ -22,6 +22,10 @@ export const LanguageModelRegistryClient = Symbol('LanguageModelRegistryClient')
22
22
  export interface LanguageModelRegistryClient {
23
23
  languageModelAdded(metadata: LanguageModelMetaData): void;
24
24
  languageModelRemoved(id: string): void;
25
+ /**
26
+ * Notify the client that a language model was updated.
27
+ */
28
+ onLanguageModelUpdated(id: string): void;
25
29
  }
26
30
 
27
31
  export const TOKEN_USAGE_SERVICE_PATH = '/services/token-usage';
@@ -41,14 +41,14 @@ import {
41
41
  TokenUsageServiceClient,
42
42
  TOKEN_USAGE_SERVICE_PATH
43
43
  } from '../common';
44
- import { BackendLanguageModelRegistry } from './backend-language-model-registry';
44
+ import { BackendLanguageModelRegistryImpl } from './backend-language-model-registry';
45
45
  import { TokenUsageServiceImpl } from './token-usage-service-impl';
46
46
 
47
47
  // We use a connection module to handle AI services separately for each frontend.
48
48
  const aiCoreConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService, bindFrontendService }) => {
49
49
  bindContributionProvider(bind, LanguageModelProvider);
50
- bind(BackendLanguageModelRegistry).toSelf().inSingletonScope();
51
- bind(LanguageModelRegistry).toService(BackendLanguageModelRegistry);
50
+ bind(BackendLanguageModelRegistryImpl).toSelf().inSingletonScope();
51
+ bind(LanguageModelRegistry).toService(BackendLanguageModelRegistryImpl);
52
52
 
53
53
  bind(TokenUsageService).to(TokenUsageServiceImpl).inSingletonScope();
54
54
 
@@ -21,7 +21,7 @@ import { DefaultLanguageModelRegistryImpl, LanguageModel, LanguageModelMetaData,
21
21
  * Notifies a client whenever a model is added or removed
22
22
  */
23
23
  @injectable()
24
- export class BackendLanguageModelRegistry extends DefaultLanguageModelRegistryImpl {
24
+ export class BackendLanguageModelRegistryImpl extends DefaultLanguageModelRegistryImpl {
25
25
 
26
26
  private client: LanguageModelRegistryClient | undefined;
27
27
 
@@ -45,10 +45,18 @@ export class BackendLanguageModelRegistry extends DefaultLanguageModelRegistryIm
45
45
  }
46
46
  }
47
47
 
48
+ override async patchLanguageModel<T extends LanguageModel = LanguageModel>(id: string, patch: Partial<T>): Promise<void> {
49
+ await super.patchLanguageModel(id, patch);
50
+ if (this.client) {
51
+ this.client.onLanguageModelUpdated(id);
52
+ }
53
+ }
54
+
48
55
  mapToMetaData(model: LanguageModel): LanguageModelMetaData {
49
56
  return {
50
57
  id: model.id,
51
58
  name: model.name,
59
+ status: model.status,
52
60
  vendor: model.vendor,
53
61
  version: model.version,
54
62
  family: model.family,
@@ -30,13 +30,13 @@ import {
30
30
  isLanguageModelParsedResponse,
31
31
  UserRequest,
32
32
  } from '../common';
33
- import { BackendLanguageModelRegistry } from './backend-language-model-registry';
33
+ import { BackendLanguageModelRegistryImpl } from './backend-language-model-registry';
34
34
 
35
35
  @injectable()
36
36
  export class LanguageModelRegistryFrontendDelegateImpl implements LanguageModelRegistryFrontendDelegate {
37
37
 
38
38
  @inject(LanguageModelRegistry)
39
- private registry: BackendLanguageModelRegistry;
39
+ private registry: BackendLanguageModelRegistryImpl;
40
40
 
41
41
  setClient(client: LanguageModelRegistryClient): void {
42
42
  this.registry.setClient(client);