@theia/ai-core 1.74.0-next.0 → 1.74.0-next.15

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 (82) hide show
  1. package/lib/browser/ai-configuration-service-impl.d.ts +32 -0
  2. package/lib/browser/ai-configuration-service-impl.d.ts.map +1 -0
  3. package/lib/browser/ai-configuration-service-impl.js +120 -0
  4. package/lib/browser/ai-configuration-service-impl.js.map +1 -0
  5. package/lib/browser/ai-configuration-service-impl.spec.d.ts +2 -0
  6. package/lib/browser/ai-configuration-service-impl.spec.d.ts.map +1 -0
  7. package/lib/browser/ai-configuration-service-impl.spec.js +214 -0
  8. package/lib/browser/ai-configuration-service-impl.spec.js.map +1 -0
  9. package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
  10. package/lib/browser/ai-core-frontend-module.js +4 -0
  11. package/lib/browser/ai-core-frontend-module.js.map +1 -1
  12. package/lib/browser/ai-settings-service.d.ts +2 -5
  13. package/lib/browser/ai-settings-service.d.ts.map +1 -1
  14. package/lib/browser/ai-settings-service.js +8 -15
  15. package/lib/browser/ai-settings-service.js.map +1 -1
  16. package/lib/browser/frontend-language-model-alias-registry.d.ts +2 -4
  17. package/lib/browser/frontend-language-model-alias-registry.d.ts.map +1 -1
  18. package/lib/browser/frontend-language-model-alias-registry.js +11 -16
  19. package/lib/browser/frontend-language-model-alias-registry.js.map +1 -1
  20. package/lib/browser/frontend-language-model-service.d.ts +2 -3
  21. package/lib/browser/frontend-language-model-service.d.ts.map +1 -1
  22. package/lib/browser/frontend-language-model-service.js +6 -6
  23. package/lib/browser/frontend-language-model-service.js.map +1 -1
  24. package/lib/browser/frontend-prompt-customization-service.d.ts +38 -0
  25. package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
  26. package/lib/browser/frontend-prompt-customization-service.js +189 -76
  27. package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
  28. package/lib/browser/frontend-prompt-customization-service.spec.js +103 -0
  29. package/lib/browser/frontend-prompt-customization-service.spec.js.map +1 -1
  30. package/lib/browser/index.d.ts +1 -0
  31. package/lib/browser/index.d.ts.map +1 -1
  32. package/lib/browser/index.js +1 -0
  33. package/lib/browser/index.js.map +1 -1
  34. package/lib/browser/trust-aware-preference-reader.d.ts +13 -1
  35. package/lib/browser/trust-aware-preference-reader.d.ts.map +1 -1
  36. package/lib/browser/trust-aware-preference-reader.js +21 -2
  37. package/lib/browser/trust-aware-preference-reader.js.map +1 -1
  38. package/lib/browser/trust-aware-preference-reader.spec.js +26 -0
  39. package/lib/browser/trust-aware-preference-reader.spec.js.map +1 -1
  40. package/lib/common/ai-configuration-service.d.ts +104 -0
  41. package/lib/common/ai-configuration-service.d.ts.map +1 -0
  42. package/lib/common/ai-configuration-service.js +20 -0
  43. package/lib/common/ai-configuration-service.js.map +1 -0
  44. package/lib/common/ai-core-preferences.d.ts +2 -0
  45. package/lib/common/ai-core-preferences.d.ts.map +1 -1
  46. package/lib/common/ai-core-preferences.js +11 -1
  47. package/lib/common/ai-core-preferences.js.map +1 -1
  48. package/lib/common/index.d.ts +1 -0
  49. package/lib/common/index.d.ts.map +1 -1
  50. package/lib/common/index.js +1 -0
  51. package/lib/common/index.js.map +1 -1
  52. package/lib/common/language-model.d.ts +51 -2
  53. package/lib/common/language-model.d.ts.map +1 -1
  54. package/lib/common/language-model.js +40 -2
  55. package/lib/common/language-model.js.map +1 -1
  56. package/lib/common/language-model.spec.js +34 -0
  57. package/lib/common/language-model.spec.js.map +1 -1
  58. package/lib/common/prompt-service.d.ts +13 -3
  59. package/lib/common/prompt-service.d.ts.map +1 -1
  60. package/lib/common/prompt-service.js.map +1 -1
  61. package/lib/node/backend-language-model-registry.d.ts.map +1 -1
  62. package/lib/node/backend-language-model-registry.js +1 -0
  63. package/lib/node/backend-language-model-registry.js.map +1 -1
  64. package/package.json +9 -9
  65. package/src/browser/ai-configuration-service-impl.spec.ts +254 -0
  66. package/src/browser/ai-configuration-service-impl.ts +120 -0
  67. package/src/browser/ai-core-frontend-module.ts +7 -1
  68. package/src/browser/ai-settings-service.ts +8 -16
  69. package/src/browser/frontend-language-model-alias-registry.ts +12 -16
  70. package/src/browser/frontend-language-model-service.ts +5 -6
  71. package/src/browser/frontend-prompt-customization-service.spec.ts +130 -1
  72. package/src/browser/frontend-prompt-customization-service.ts +204 -84
  73. package/src/browser/index.ts +1 -0
  74. package/src/browser/trust-aware-preference-reader.spec.ts +29 -0
  75. package/src/browser/trust-aware-preference-reader.ts +23 -3
  76. package/src/common/ai-configuration-service.ts +128 -0
  77. package/src/common/ai-core-preferences.ts +12 -0
  78. package/src/common/index.ts +1 -0
  79. package/src/common/language-model.spec.ts +45 -1
  80. package/src/common/language-model.ts +85 -3
  81. package/src/common/prompt-service.ts +14 -3
  82. package/src/node/backend-language-model-registry.ts +1 -0
@@ -0,0 +1,128 @@
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 { Event } from '@theia/core';
18
+ import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
19
+ import { PreferenceInspection, PreferenceScope } from '@theia/core/lib/common/preferences';
20
+
21
+ export const AiConfigurationService = Symbol('AiConfigurationService');
22
+
23
+ /**
24
+ * A {@link PreferenceInspection} augmented with a derived, workspace-trust-aware source scope.
25
+ */
26
+ export interface AiConfigurationInspection<T extends JSONValue = JSONValue> extends PreferenceInspection<T> {
27
+ /**
28
+ * The narrowest scope (honoring workspace trust) in which a value is explicitly set, i.e. the
29
+ * scope that determines the effective {@link PreferenceInspection.value}. Walked in precedence
30
+ * order `Folder -> Workspace -> User`; `undefined` when only the default value applies. In an
31
+ * untrusted workspace the folder/workspace scopes are ignored, so `sourceScope` is either
32
+ * `User` or `undefined`.
33
+ */
34
+ sourceScope?: PreferenceScope;
35
+ }
36
+
37
+ /**
38
+ * Payload of {@link AiConfigurationService.onDidChange}.
39
+ */
40
+ export interface AiConfigurationChange {
41
+ /**
42
+ * The preference key that changed. When the change is caused by a workspace-trust transition
43
+ * (which can change the effective value of any trust-gated key) this is `undefined` and
44
+ * {@link affects} returns `true` for every resource. Listeners tracking a specific key should
45
+ * re-query the value when `preferenceName` is `undefined` or equals their key.
46
+ */
47
+ readonly preferenceName?: string;
48
+ /**
49
+ * Tests whether the given resource is affected by the change.
50
+ * @param resourceUri the uri of the resource to test.
51
+ */
52
+ affects(resourceUri?: string): boolean;
53
+ /**
54
+ * Tests whether the given preference key is affected by this change, treating a workspace-trust
55
+ * transition (where {@link preferenceName} is `undefined`) as affecting every trust-gated key.
56
+ * Prefer this over comparing {@link preferenceName} directly so listeners do not silently miss
57
+ * trust transitions.
58
+ * @param preferenceName the preference key to test.
59
+ */
60
+ affectsPreference(preferenceName: string): boolean;
61
+ }
62
+
63
+ /**
64
+ * Framework API for reading and writing AI-related (`ai-features.*`) preferences.
65
+ *
66
+ * `AiConfigurationService` wraps the core `PreferenceService` and is the intended extension point
67
+ * for adopters and extensions that configure AI features: prefer it over talking to
68
+ * `PreferenceService` directly for `ai-features.*` keys. Routing all AI configuration through this
69
+ * seam keeps consumers insulated from future changes to how AI preferences are stored.
70
+ *
71
+ * Reads ({@link get}, {@link inspect}) are **workspace-trust-aware**: workspace and folder scope
72
+ * values are suppressed while the workspace is untrusted (failing closed until the trust state
73
+ * resolves), matching how AI features read preferences today. Writes ({@link set}, {@link update})
74
+ * are never gated by trust.
75
+ */
76
+ export interface AiConfigurationService {
77
+ /**
78
+ * Resolves once the underlying preference service and the initial workspace-trust state are
79
+ * ready. Await this before the first read for a deterministic, trust-aware result.
80
+ */
81
+ readonly ready: Promise<void>;
82
+
83
+ /**
84
+ * Retrieves the effective, trust-aware value for the given preference.
85
+ *
86
+ * @param key the preference identifier.
87
+ * @param defaultValue the value to return when no value is stored.
88
+ * @param resourceUri the uri of the resource for which the preference is read.
89
+ */
90
+ get<T>(key: string, defaultValue?: T, resourceUri?: string): T | undefined;
91
+
92
+ /**
93
+ * Writes `value` to the given scope. Maps to `PreferenceService.set`.
94
+ *
95
+ * @param key the preference identifier.
96
+ * @param value the new value (must be JSON-serializable). `undefined` clears the value in the given scope.
97
+ * @param scope the scope to write to. For {@link PreferenceScope.Folder} a `resourceUri` is required.
98
+ * @param resourceUri the uri of the resource for which the preference is stored.
99
+ */
100
+ set(key: string, value: unknown, scope: PreferenceScope, resourceUri?: string): Promise<void>;
101
+
102
+ /**
103
+ * "Smart write": picks the scope so that the effective value becomes `value`. Maps to
104
+ * `PreferenceService.updateValue`.
105
+ *
106
+ * @param key the preference identifier.
107
+ * @param value the value to apply (must be JSON-serializable). `undefined` resets the preference to its default value.
108
+ * @param resourceUri the uri of the resource to which the change applies.
109
+ */
110
+ update(key: string, value: unknown, resourceUri?: string): Promise<void>;
111
+
112
+ /**
113
+ * Retrieves the per-scope values for the given preference, enriched with a derived,
114
+ * trust-aware {@link AiConfigurationInspection.sourceScope} and effective
115
+ * {@link PreferenceInspection.value}.
116
+ *
117
+ * @param key the preference identifier.
118
+ * @param resourceUri the uri of the resource for which the preference is inspected.
119
+ */
120
+ inspect<T extends JSONValue>(key: string, resourceUri?: string): AiConfigurationInspection<T> | undefined;
121
+
122
+ /**
123
+ * Fires when a `ai-features.*` preference changes or when the workspace-trust state transitions
124
+ * (which can change the effective value of trust-gated keys). The effective value is not carried
125
+ * on the event; listeners re-query it via {@link get}/{@link inspect}.
126
+ */
127
+ onDidChange: Event<AiConfigurationChange>;
128
+ }
@@ -34,11 +34,22 @@ export const PREFERENCE_NAME_REASONING = 'ai-features.reasoning.defaults';
34
34
  export const PREFERENCE_NAME_MAX_RETRIES = 'ai-features.modelSettings.maxRetries';
35
35
  export const PREFERENCE_NAME_DEFAULT_NOTIFICATION_TYPE = 'ai-features.notifications.default';
36
36
  export const PREFERENCE_NAME_SKILL_DIRECTORIES = 'ai-features.skills.skillDirectories';
37
+ export const PREFERENCE_NAME_SERVER_SIDE_COMPACTION = 'ai-features.chat.serverSideCompaction';
37
38
 
38
39
  export const LANGUAGE_MODEL_ALIASES_PREFERENCE = 'ai-features.languageModelAliases';
39
40
 
40
41
  export const aiCorePreferenceSchema: PreferenceSchema = {
41
42
  properties: {
43
+ [PREFERENCE_NAME_SERVER_SIDE_COMPACTION]: {
44
+ title: AI_CORE_PREFERENCES_TITLE,
45
+ type: 'boolean',
46
+ description: nls.localize('theia/ai/core/serverSideCompaction/description',
47
+ 'Enable provider-native server-side compaction by default for chat requests. When on, models that support it ' +
48
+ 'may automatically summarize older turns once the conversation grows past their own threshold, letting long ' +
49
+ 'sessions continue past the context limit. Each provider offers its own override, and individual chat sessions ' +
50
+ 'can override this in their session settings. Models without support ignore it.'),
51
+ default: true,
52
+ },
42
53
  [PREFERENCE_NAME_PROMPT_TEMPLATES]: {
43
54
  title: AI_CORE_PREFERENCES_TITLE,
44
55
  description: nls.localize('theia/ai/core/promptTemplates/description',
@@ -239,6 +250,7 @@ export interface AICoreConfiguration {
239
250
  [PREFERENCE_NAME_MAX_RETRIES]: number | undefined;
240
251
  [PREFERENCE_NAME_DEFAULT_NOTIFICATION_TYPE]: NotificationType | undefined;
241
252
  [PREFERENCE_NAME_SKILL_DIRECTORIES]: string[] | undefined;
253
+ [PREFERENCE_NAME_SERVER_SIDE_COMPACTION]: boolean | undefined;
242
254
  }
243
255
 
244
256
  export interface RequestSetting {
@@ -31,6 +31,7 @@ export * from './protocol';
31
31
  export * from './today-variable-contribution';
32
32
  export * from './variable-service';
33
33
  export * from './settings-service';
34
+ export * from './ai-configuration-service';
34
35
  export * from './language-model-service';
35
36
  export * from './token-usage-service';
36
37
  export * from './ai-variable-resource';
@@ -15,7 +15,16 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { expect } from 'chai';
18
- import { isModelMatching, isToolCallContent, LanguageModel, LanguageModelSelector } from './language-model';
18
+ import {
19
+ isCompactionResponsePart,
20
+ isLanguageModelStreamResponsePart,
21
+ isModelMatching,
22
+ isToolCallContent,
23
+ LanguageModel,
24
+ LanguageModelSelector,
25
+ resolveCompactionDefault,
26
+ resolveServerSideCompaction
27
+ } from './language-model';
19
28
 
20
29
  describe('isModelMatching', () => {
21
30
  it('returns false with one of two parameter mismatches', () => {
@@ -163,3 +172,38 @@ describe('isToolCallContent', () => {
163
172
  });
164
173
 
165
174
  });
175
+
176
+ describe('compaction contract', () => {
177
+ it('recognizes a compaction response part', () => {
178
+ const part = { compaction: { provider: 'anthropic', data: { foo: 1 } } };
179
+ expect(isCompactionResponsePart(part)).to.equal(true);
180
+ expect(isLanguageModelStreamResponsePart(part)).to.equal(true);
181
+ });
182
+ it('rejects a non-compaction part', () => {
183
+ expect(isCompactionResponsePart({ content: 'hi' })).to.equal(false);
184
+ // eslint-disable-next-line no-null/no-null
185
+ expect(isCompactionResponsePart(null)).to.equal(false);
186
+ expect(isCompactionResponsePart(undefined)).to.equal(false);
187
+ // eslint-disable-next-line no-null/no-null
188
+ expect(isCompactionResponsePart({ compaction: null })).to.equal(false);
189
+ expect(isCompactionResponsePart({ compaction: { provider: 42 } })).to.equal(false);
190
+ });
191
+ it('resolves a model default from the global preference and the per-provider override', () => {
192
+ expect(resolveCompactionDefault(true, 'default')).to.equal(true);
193
+ expect(resolveCompactionDefault(false, 'default')).to.equal(false);
194
+ expect(resolveCompactionDefault(false, 'enabled')).to.equal(true);
195
+ expect(resolveCompactionDefault(true, 'disabled')).to.equal(false);
196
+ });
197
+ it('resolves server-side compaction with capability gate and session-wins precedence', () => {
198
+ // capability gate
199
+ expect(resolveServerSideCompaction(false, true, { enabled: true })).to.equal(false);
200
+ expect(resolveServerSideCompaction(undefined, true, undefined)).to.equal(false);
201
+ // no per-session setting -> model default
202
+ expect(resolveServerSideCompaction(true, true, undefined)).to.equal(true);
203
+ expect(resolveServerSideCompaction(true, false, undefined)).to.equal(false);
204
+ expect(resolveServerSideCompaction(true, true, {})).to.equal(true);
205
+ // explicit per-session setting wins over the model default (which already folds in the per-provider override)
206
+ expect(resolveServerSideCompaction(true, false, { enabled: true })).to.equal(true);
207
+ expect(resolveServerSideCompaction(true, true, { enabled: false })).to.equal(false);
208
+ });
209
+ });
@@ -42,7 +42,8 @@ export interface ReasoningSupport {
42
42
  readonly defaultLevel?: ReasoningLevel;
43
43
  }
44
44
 
45
- export type LanguageModelMessage = TextMessage | ThinkingMessage | ToolUseMessage | ToolResultMessage | ServerToolUseMessage | ImageMessage;
45
+ export type LanguageModelMessage =
46
+ TextMessage | ThinkingMessage | ToolUseMessage | ToolResultMessage | ServerToolUseMessage | ImageMessage | CompactionMessage;
46
47
  export namespace LanguageModelMessage {
47
48
 
48
49
  export function isTextMessage(obj: LanguageModelMessage): obj is TextMessage {
@@ -63,6 +64,9 @@ export namespace LanguageModelMessage {
63
64
  export function isImageMessage(obj: LanguageModelMessage): obj is ImageMessage {
64
65
  return obj.type === 'image';
65
66
  }
67
+ export function isCompactionMessage(obj: LanguageModelMessage): obj is CompactionMessage {
68
+ return obj.type === 'compaction';
69
+ }
66
70
  }
67
71
  export interface TextMessage {
68
72
  actor: MessageActor;
@@ -127,6 +131,17 @@ export interface ImageMessage {
127
131
  image: ImageContent;
128
132
  }
129
133
 
134
+ export interface CompactionMessage {
135
+ actor: 'ai';
136
+ type: 'compaction';
137
+ /** Originating provider tag; a backend replays the payload only when this matches its own provider. */
138
+ provider: string;
139
+ /** Opaque provider payload to replay. */
140
+ data: unknown;
141
+ /** Human-readable summary, when the provider exposes one. */
142
+ summary?: string;
143
+ }
144
+
130
145
  export const isLanguageModelRequestMessage = (obj: unknown): obj is LanguageModelMessage =>
131
146
  !!(obj && typeof obj === 'object' &&
132
147
  'type' in obj &&
@@ -304,6 +319,50 @@ export namespace ToolRequest {
304
319
  (!('required' in obj) || (Array.isArray(obj.required) && obj.required.every(prop => typeof prop === 'string')));
305
320
  }
306
321
  }
322
+ /**
323
+ * Per-session/per-request server-side compaction settings, carried verbatim from the chat
324
+ * session's common settings to the request. Kept as an object so further parameters can be
325
+ * added later.
326
+ */
327
+ export interface CompactionSettings {
328
+ /** Explicit enablement for this session; when set it wins over the model's default. `undefined` means "no explicit choice". */
329
+ enabled?: boolean;
330
+ }
331
+
332
+ /** Per-provider override for server-side compaction; combined with the global preference by {@link resolveCompactionDefault}. */
333
+ export type ServerSideCompactionSetting = 'default' | 'enabled' | 'disabled';
334
+
335
+ /**
336
+ * Resolves a model's default server-side compaction enablement from the global preference and
337
+ * the per-provider override. `'enabled'`/`'disabled'` force the result; `'default'` defers to
338
+ * the global preference. Intended to be called where the preferences are read (the provider's
339
+ * frontend contribution) and stored on the model.
340
+ */
341
+ export function resolveCompactionDefault(globalEnabled: boolean, perProviderOverride: ServerSideCompactionSetting): boolean {
342
+ if (perProviderOverride === 'enabled') {
343
+ return true;
344
+ }
345
+ if (perProviderOverride === 'disabled') {
346
+ return false;
347
+ }
348
+ return globalEnabled;
349
+ }
350
+
351
+ /**
352
+ * Resolves whether server-side compaction is effective for a request: the model must support it
353
+ * (capability), then an explicit per-session setting wins, otherwise the model's resolved default applies.
354
+ */
355
+ export function resolveServerSideCompaction(
356
+ capability: boolean | undefined,
357
+ enabledByDefault: boolean,
358
+ compaction: CompactionSettings | undefined
359
+ ): boolean {
360
+ if (!capability) {
361
+ return false;
362
+ }
363
+ return compaction?.enabled ?? enabledByDefault;
364
+ }
365
+
307
366
  export interface LanguageModelRequest {
308
367
  messages: LanguageModelMessage[],
309
368
  tools?: ToolRequest[];
@@ -323,6 +382,8 @@ export interface LanguageModelRequest {
323
382
  clientSettings?: { keepToolCalls: boolean; keepThinking: boolean };
324
383
  /** Provider-agnostic reasoning configuration; providers translate it to their native API. */
325
384
  reasoning?: ReasoningSettings;
385
+ /** Provider-agnostic server-side compaction settings, copied verbatim from the chat session. Resolved against the model's capability and default in the backend. */
386
+ compaction?: CompactionSettings;
326
387
  }
327
388
  export interface ResponseFormatJsonSchema {
328
389
  type: 'json_schema';
@@ -378,10 +439,12 @@ export interface LanguageModelTextResponse {
378
439
  export const isLanguageModelTextResponse = (obj: unknown): obj is LanguageModelTextResponse =>
379
440
  !!(obj && typeof obj === 'object' && 'text' in obj && typeof (obj as { text: unknown }).text === 'string');
380
441
 
381
- export type LanguageModelStreamResponsePart = TextResponsePart | ToolCallResponsePart | ServerToolCallResponsePart | ThinkingResponsePart | UsageResponsePart;
442
+ export type LanguageModelStreamResponsePart =
443
+ TextResponsePart | ToolCallResponsePart | ServerToolCallResponsePart | ThinkingResponsePart | UsageResponsePart | CompactionResponsePart;
382
444
 
383
445
  export const isLanguageModelStreamResponsePart = (part: unknown): part is LanguageModelStreamResponsePart =>
384
- isUsageResponsePart(part) || isTextResponsePart(part) || isThinkingResponsePart(part) || isToolCallResponsePart(part) || isServerToolCallResponsePart(part);
446
+ isUsageResponsePart(part) || isTextResponsePart(part) || isThinkingResponsePart(part) ||
447
+ isToolCallResponsePart(part) || isServerToolCallResponsePart(part) || isCompactionResponsePart(part);
385
448
 
386
449
  export interface UsageResponsePart {
387
450
  input_tokens: number;
@@ -432,6 +495,23 @@ export interface ThinkingResponsePart {
432
495
  export const isThinkingResponsePart = (part: unknown): part is ThinkingResponsePart =>
433
496
  !!(part && typeof part === 'object' && 'thought' in part && typeof part.thought === 'string');
434
497
 
498
+ export interface CompactionResponsePart {
499
+ compaction: {
500
+ /** Originating provider tag, e.g. 'anthropic' or 'openai-responses'. */
501
+ provider: string;
502
+ /** Opaque provider payload (Anthropic compaction block(s) / OpenAI compaction item). Never interpreted outside the originating backend. */
503
+ data: unknown;
504
+ /** Human-readable summary, when the provider exposes one. */
505
+ summary?: string;
506
+ };
507
+ }
508
+ export const isCompactionResponsePart = (part: unknown): part is CompactionResponsePart =>
509
+ !!(part && typeof part === 'object' && 'compaction' in part &&
510
+ typeof (part as CompactionResponsePart).compaction === 'object' &&
511
+ (part as CompactionResponsePart).compaction &&
512
+ 'provider' in (part as CompactionResponsePart).compaction &&
513
+ typeof (part as CompactionResponsePart).compaction.provider === 'string');
514
+
435
515
  export interface ToolCallTextResult { type: 'text', text: string; };
436
516
  export interface ToolCallImageResult extends Base64ImageContent { type: 'image' };
437
517
  export interface ToolCallAudioResult { type: 'audio', data: string; mimeType: string };
@@ -530,6 +610,8 @@ export interface LanguageModelMetaData {
530
610
  * **Note:** If you provide these, you must also provide `vendor` because server tools are vendor-specific.
531
611
  */
532
612
  readonly serverTools?: ServerToolDescriptor[];
613
+ /** Whether this model supports provider-native server-side compaction (capability, distinct from whether it is activated). */
614
+ readonly serverSideCompactionSupport?: boolean;
533
615
  }
534
616
 
535
617
  export namespace LanguageModelMetaData {
@@ -385,11 +385,21 @@ export interface PromptFragmentCustomizationService {
385
385
  createCustomAgentFile(parentDirectory: URI, agent: CustomAgentDescription): Promise<URI>;
386
386
 
387
387
  /**
388
- * Migrates every reachable `customAgents.yml` to the per-agent `agents/<id>/agent.md` layout.
388
+ * Returns `true` if migration would write anything: a scope still holds a legacy
389
+ * `customAgents.yml`, or a previously migrated scope has `agent.md` files that can be corrected
390
+ * (e.g. headings that were folded by an earlier migration). Read-only; performs no writes and is
391
+ * intended for deciding whether to prompt the user before migrating.
392
+ */
393
+ hasPendingCustomAgentMigration(): Promise<boolean>;
394
+
395
+ /**
396
+ * Migrates every reachable `customAgents.yml` to the per-agent `agents/<id>/agent.md` layout and
397
+ * corrects already-migrated `agent.md` files whose headings were folded by an earlier migration.
389
398
  * The user's original content is never deleted: on success (or on partial failure when no backup
390
399
  * exists yet) the YAML is renamed to `customAgents.yml.bak`; if a `.bak` already exists it is not
391
- * overwritten and the YAML is left in place.
392
- * Idempotent rerunning never overwrites an already-migrated agent file.
400
+ * overwritten and the YAML is left in place. Corrections only overwrite `agent.md` files the user
401
+ * has not edited since migration.
402
+ * Idempotent — rerunning never overwrites an already up-to-date agent file.
393
403
  */
394
404
  migrateCustomAgentsYaml(): Promise<Array<{
395
405
  scope: URI;
@@ -399,6 +409,7 @@ export interface PromptFragmentCustomizationService {
399
409
  failed: number;
400
410
  yamlBackedUp: boolean;
401
411
  promptOverridesMigrated: number;
412
+ corrected: number;
402
413
  }>>;
403
414
 
404
415
  /**
@@ -64,6 +64,7 @@ export class BackendLanguageModelRegistryImpl extends DefaultLanguageModelRegist
64
64
  maxOutputTokens: model.maxOutputTokens,
65
65
  reasoningSupport: model.reasoningSupport,
66
66
  serverTools: model.serverTools,
67
+ serverSideCompactionSupport: model.serverSideCompactionSupport,
67
68
  };
68
69
  }
69
70
  }