@theia/ai-core 1.60.2 → 1.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-core-frontend-module.js +16 -0
- package/lib/browser/ai-core-frontend-module.js.map +1 -1
- package/lib/browser/ai-variable-uri-label-provider.d.ts +17 -0
- package/lib/browser/ai-variable-uri-label-provider.d.ts.map +1 -0
- package/lib/browser/ai-variable-uri-label-provider.js +85 -0
- package/lib/browser/ai-variable-uri-label-provider.js.map +1 -0
- package/lib/browser/file-variable-contribution.d.ts +9 -3
- package/lib/browser/file-variable-contribution.d.ts.map +1 -1
- package/lib/browser/file-variable-contribution.js +26 -8
- package/lib/browser/file-variable-contribution.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.d.ts +22 -1
- package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.js +63 -25
- package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
- package/lib/browser/frontend-variable-service.d.ts +28 -4
- package/lib/browser/frontend-variable-service.d.ts.map +1 -1
- package/lib/browser/frontend-variable-service.js +84 -1
- package/lib/browser/frontend-variable-service.js.map +1 -1
- package/lib/browser/prompttemplate-contribution.d.ts +0 -3
- package/lib/browser/prompttemplate-contribution.d.ts.map +1 -1
- package/lib/browser/prompttemplate-contribution.js +5 -24
- package/lib/browser/prompttemplate-contribution.js.map +1 -1
- package/lib/browser/token-usage-frontend-service-impl.d.ts +25 -0
- package/lib/browser/token-usage-frontend-service-impl.d.ts.map +1 -0
- package/lib/browser/token-usage-frontend-service-impl.js +120 -0
- package/lib/browser/token-usage-frontend-service-impl.js.map +1 -0
- package/lib/browser/token-usage-frontend-service.d.ts +29 -0
- package/lib/browser/token-usage-frontend-service.d.ts.map +1 -0
- package/lib/browser/token-usage-frontend-service.js +23 -0
- package/lib/browser/token-usage-frontend-service.js.map +1 -0
- package/lib/common/ai-variable-resource.d.ts +18 -0
- package/lib/common/ai-variable-resource.d.ts.map +1 -0
- package/lib/common/ai-variable-resource.js +103 -0
- package/lib/common/ai-variable-resource.js.map +1 -0
- package/lib/common/configurable-in-memory-resources.d.ts +45 -0
- package/lib/common/configurable-in-memory-resources.d.ts.map +1 -0
- package/lib/common/configurable-in-memory-resources.js +147 -0
- package/lib/common/configurable-in-memory-resources.js.map +1 -0
- package/lib/common/index.d.ts +3 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +3 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/language-model.d.ts +7 -2
- package/lib/common/language-model.d.ts.map +1 -1
- package/lib/common/language-model.js +5 -1
- package/lib/common/language-model.js.map +1 -1
- package/lib/common/prompt-service.d.ts +16 -4
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js +1 -1
- package/lib/common/prompt-service.js.map +1 -1
- package/lib/common/prompt-variable-contribution.d.ts +2 -1
- package/lib/common/prompt-variable-contribution.d.ts.map +1 -1
- package/lib/common/prompt-variable-contribution.js +10 -1
- package/lib/common/prompt-variable-contribution.js.map +1 -1
- package/lib/common/protocol.d.ts +14 -0
- package/lib/common/protocol.d.ts.map +1 -1
- package/lib/common/protocol.js +3 -1
- package/lib/common/protocol.js.map +1 -1
- package/lib/common/token-usage-service.d.ts +35 -0
- package/lib/common/token-usage-service.d.ts.map +1 -0
- package/lib/common/token-usage-service.js +20 -0
- package/lib/common/token-usage-service.js.map +1 -0
- package/lib/common/variable-service.d.ts +17 -2
- package/lib/common/variable-service.d.ts.map +1 -1
- package/lib/common/variable-service.js +43 -32
- package/lib/common/variable-service.js.map +1 -1
- package/lib/node/ai-core-backend-module.d.ts.map +1 -1
- package/lib/node/ai-core-backend-module.js +9 -0
- package/lib/node/ai-core-backend-module.js.map +1 -1
- package/lib/node/language-model-frontend-delegate.d.ts +2 -2
- package/lib/node/language-model-frontend-delegate.d.ts.map +1 -1
- package/lib/node/language-model-frontend-delegate.js.map +1 -1
- package/lib/node/token-usage-service-impl.d.ts +23 -0
- package/lib/node/token-usage-service-impl.d.ts.map +1 -0
- package/lib/node/token-usage-service-impl.js +65 -0
- package/lib/node/token-usage-service-impl.js.map +1 -0
- package/package.json +11 -10
- package/src/browser/ai-core-frontend-module.ts +26 -5
- package/src/browser/ai-variable-uri-label-provider.ts +66 -0
- package/src/browser/file-variable-contribution.ts +34 -14
- package/src/browser/frontend-prompt-customization-service.ts +72 -25
- package/src/browser/frontend-variable-service.ts +115 -5
- package/src/browser/prompttemplate-contribution.ts +5 -26
- package/src/browser/token-usage-frontend-service-impl.ts +117 -0
- package/src/browser/token-usage-frontend-service.ts +47 -0
- package/src/common/ai-variable-resource.ts +86 -0
- package/src/common/configurable-in-memory-resources.ts +156 -0
- package/src/common/index.ts +3 -0
- package/src/common/language-model.ts +10 -2
- package/src/common/prompt-service.ts +14 -4
- package/src/common/prompt-variable-contribution.ts +10 -2
- package/src/common/protocol.ts +18 -0
- package/src/common/token-usage-service.ts +56 -0
- package/src/common/variable-service.ts +58 -44
- package/src/node/ai-core-backend-module.ts +21 -1
- package/src/node/language-model-frontend-delegate.ts +2 -2
- package/src/node/token-usage-service-impl.ts +65 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH and others.
|
|
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 * as deepEqual from 'fast-deep-equal';
|
|
18
|
+
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
19
|
+
import { Resource, URI, generateUuid } from '@theia/core';
|
|
20
|
+
import { AIVariableContext, AIVariableResolutionRequest } from './variable-service';
|
|
21
|
+
import stableJsonStringify = require('fast-json-stable-stringify');
|
|
22
|
+
import { ConfigurableInMemoryResources, ConfigurableMutableReferenceResource } from './configurable-in-memory-resources';
|
|
23
|
+
|
|
24
|
+
export const AI_VARIABLE_RESOURCE_SCHEME = 'ai-variable';
|
|
25
|
+
export const NO_CONTEXT_AUTHORITY = 'context-free';
|
|
26
|
+
|
|
27
|
+
@injectable()
|
|
28
|
+
export class AIVariableResourceResolver {
|
|
29
|
+
@inject(ConfigurableInMemoryResources) protected readonly inMemoryResources: ConfigurableInMemoryResources;
|
|
30
|
+
|
|
31
|
+
@postConstruct()
|
|
32
|
+
protected init(): void {
|
|
33
|
+
this.inMemoryResources.onWillDispose(resource => this.cache.delete(resource.uri.toString()));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected readonly cache = new Map<string, [Resource, AIVariableContext]>();
|
|
37
|
+
|
|
38
|
+
getOrCreate(request: AIVariableResolutionRequest, context: AIVariableContext, value: string): ConfigurableMutableReferenceResource {
|
|
39
|
+
const uri = this.toUri(request, context);
|
|
40
|
+
try {
|
|
41
|
+
const existing = this.inMemoryResources.resolve(uri);
|
|
42
|
+
existing.update({ contents: value });
|
|
43
|
+
return existing;
|
|
44
|
+
} catch { /* No-op */ }
|
|
45
|
+
const fresh = this.inMemoryResources.add(uri, { contents: value, readOnly: true, initiallyDirty: false });
|
|
46
|
+
const key = uri.toString();
|
|
47
|
+
this.cache.set(key, [fresh, context]);
|
|
48
|
+
return fresh;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected toUri(request: AIVariableResolutionRequest, context: AIVariableContext): URI {
|
|
52
|
+
return URI.fromComponents({
|
|
53
|
+
scheme: AI_VARIABLE_RESOURCE_SCHEME,
|
|
54
|
+
query: stableJsonStringify({ arg: request.arg, name: request.variable.name }),
|
|
55
|
+
path: '/',
|
|
56
|
+
authority: this.toAuthority(context),
|
|
57
|
+
fragment: ''
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected toAuthority(context: AIVariableContext): string {
|
|
62
|
+
try {
|
|
63
|
+
if (deepEqual(context, {})) { return NO_CONTEXT_AUTHORITY; }
|
|
64
|
+
for (const [resource, cachedContext] of this.cache.values()) {
|
|
65
|
+
if (deepEqual(context, cachedContext)) {
|
|
66
|
+
return resource.uri.authority;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// Mostly that deep equal could overflow the stack, but it should run into === or inequality before that.
|
|
71
|
+
console.warn('Problem evaluating context in AIVariableResourceResolver', err);
|
|
72
|
+
}
|
|
73
|
+
return generateUuid();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fromUri(uri: URI): { variableName: string, arg: string | undefined } | undefined {
|
|
77
|
+
if (uri.scheme !== AI_VARIABLE_RESOURCE_SCHEME) { return undefined; }
|
|
78
|
+
try {
|
|
79
|
+
const { name: variableName, arg } = JSON.parse(uri.query);
|
|
80
|
+
return variableName ? {
|
|
81
|
+
variableName,
|
|
82
|
+
arg,
|
|
83
|
+
} : undefined;
|
|
84
|
+
} catch { return undefined; }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclispeSource GmbH and others.
|
|
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 { injectable } from '@theia/core/shared/inversify';
|
|
18
|
+
import { SyncReferenceCollection, Reference, ResourceResolver, Resource, Event, Emitter, URI } from '@theia/core';
|
|
19
|
+
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
|
20
|
+
|
|
21
|
+
@injectable()
|
|
22
|
+
/** For creating highly configurable in-memory resources */
|
|
23
|
+
export class ConfigurableInMemoryResources implements ResourceResolver {
|
|
24
|
+
|
|
25
|
+
protected readonly resources = new SyncReferenceCollection<string, ConfigurableMutableResource>(uri => new ConfigurableMutableResource(new URI(uri)));
|
|
26
|
+
|
|
27
|
+
get onWillDispose(): Event<ConfigurableMutableResource> {
|
|
28
|
+
return this.resources.onWillDispose;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
add(uri: URI, options: ResourceInitializationOptions): ConfigurableMutableReferenceResource {
|
|
32
|
+
const resourceUri = uri.toString();
|
|
33
|
+
if (this.resources.has(resourceUri)) {
|
|
34
|
+
throw new Error(`Cannot add already existing in-memory resource '${resourceUri}'`);
|
|
35
|
+
}
|
|
36
|
+
const resource = this.acquire(resourceUri);
|
|
37
|
+
resource.update(options);
|
|
38
|
+
return resource;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
update(uri: URI, options: ResourceInitializationOptions): Resource {
|
|
42
|
+
const resourceUri = uri.toString();
|
|
43
|
+
const resource = this.resources.get(resourceUri);
|
|
44
|
+
if (!resource) {
|
|
45
|
+
throw new Error(`Cannot update non-existent in-memory resource '${resourceUri}'`);
|
|
46
|
+
}
|
|
47
|
+
resource.update(options);
|
|
48
|
+
return resource;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
resolve(uri: URI): ConfigurableMutableReferenceResource {
|
|
52
|
+
const uriString = uri.toString();
|
|
53
|
+
if (!this.resources.has(uriString)) {
|
|
54
|
+
throw new Error(`In memory '${uriString}' resource does not exist.`);
|
|
55
|
+
}
|
|
56
|
+
return this.acquire(uriString);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected acquire(uri: string): ConfigurableMutableReferenceResource {
|
|
60
|
+
const reference = this.resources.acquire(uri);
|
|
61
|
+
return new ConfigurableMutableReferenceResource(reference);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ResourceInitializationOptions = Pick<Resource, 'autosaveable' | 'initiallyDirty' | 'readOnly'>
|
|
66
|
+
& { contents?: string | Promise<string>, onSave?: Resource['saveContents'] };
|
|
67
|
+
|
|
68
|
+
export class ConfigurableMutableResource implements Resource {
|
|
69
|
+
protected readonly onDidChangeContentsEmitter = new Emitter<void>();
|
|
70
|
+
readonly onDidChangeContents = this.onDidChangeContentsEmitter.event;
|
|
71
|
+
protected fireDidChangeContents(): void {
|
|
72
|
+
this.onDidChangeContentsEmitter.fire();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected readonly onDidChangeReadonlyEmitter = new Emitter<boolean | MarkdownString>();
|
|
76
|
+
readonly onDidChangeReadOnly = this.onDidChangeReadonlyEmitter.event;
|
|
77
|
+
|
|
78
|
+
constructor(readonly uri: URI, protected options?: ResourceInitializationOptions) { }
|
|
79
|
+
|
|
80
|
+
get readOnly(): Resource['readOnly'] {
|
|
81
|
+
return this.options?.readOnly;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get autosaveable(): boolean {
|
|
85
|
+
return this.options?.autosaveable !== false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get initiallyDirty(): boolean {
|
|
89
|
+
return !!this.options?.initiallyDirty;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
readContents(): Promise<string> {
|
|
93
|
+
return Promise.resolve(this.options?.contents ?? '');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async saveContents(contents: string): Promise<void> {
|
|
97
|
+
await this.options?.onSave?.(contents);
|
|
98
|
+
this.update({ contents });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
update(options: ResourceInitializationOptions): void {
|
|
102
|
+
const didContentsChange = 'contents' in options && options.contents !== this.options?.contents;
|
|
103
|
+
const didReadOnlyChange = 'readOnly' in options && options.readOnly !== this.options?.readOnly;
|
|
104
|
+
this.options = { ...this.options, ...options };
|
|
105
|
+
if (didContentsChange) {
|
|
106
|
+
this.onDidChangeContentsEmitter.fire();
|
|
107
|
+
}
|
|
108
|
+
if (didReadOnlyChange) {
|
|
109
|
+
this.onDidChangeReadonlyEmitter.fire(this.readOnly ?? false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
dispose(): void {
|
|
114
|
+
this.onDidChangeContentsEmitter.dispose();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class ConfigurableMutableReferenceResource implements Resource {
|
|
119
|
+
constructor(protected reference: Reference<ConfigurableMutableResource>) { }
|
|
120
|
+
|
|
121
|
+
get uri(): URI {
|
|
122
|
+
return this.reference.object.uri;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get onDidChangeContents(): Event<void> {
|
|
126
|
+
return this.reference.object.onDidChangeContents;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
dispose(): void {
|
|
130
|
+
this.reference.dispose();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
readContents(): Promise<string> {
|
|
134
|
+
return this.reference.object.readContents();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
saveContents(contents: string): Promise<void> {
|
|
138
|
+
return this.reference.object.saveContents(contents);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
update(options: ResourceInitializationOptions): void {
|
|
142
|
+
this.reference.object.update(options);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get readOnly(): Resource['readOnly'] {
|
|
146
|
+
return this.reference.object.readOnly;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get initiallyDirty(): boolean {
|
|
150
|
+
return this.reference.object.initiallyDirty;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get autosaveable(): boolean {
|
|
154
|
+
return this.reference.object.autosaveable;
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/common/index.ts
CHANGED
|
@@ -29,3 +29,6 @@ export * from './today-variable-contribution';
|
|
|
29
29
|
export * from './variable-service';
|
|
30
30
|
export * from './settings-service';
|
|
31
31
|
export * from './language-model-service';
|
|
32
|
+
export * from './token-usage-service';
|
|
33
|
+
export * from './ai-variable-resource';
|
|
34
|
+
export * from './configurable-in-memory-resources';
|
|
@@ -172,7 +172,15 @@ export interface LanguageModelTextResponse {
|
|
|
172
172
|
export const isLanguageModelTextResponse = (obj: unknown): obj is LanguageModelTextResponse =>
|
|
173
173
|
!!(obj && typeof obj === 'object' && 'text' in obj && typeof (obj as { text: unknown }).text === 'string');
|
|
174
174
|
|
|
175
|
-
export type LanguageModelStreamResponsePart = TextResponsePart | ToolCallResponsePart | ThinkingResponsePart;
|
|
175
|
+
export type LanguageModelStreamResponsePart = TextResponsePart | ToolCallResponsePart | ThinkingResponsePart | UsageResponsePart;
|
|
176
|
+
export interface UsageResponsePart {
|
|
177
|
+
input_tokens: number;
|
|
178
|
+
output_tokens: number;
|
|
179
|
+
}
|
|
180
|
+
export const isUsageResponsePart = (part: unknown): part is UsageResponsePart =>
|
|
181
|
+
!!(part && typeof part === 'object' &&
|
|
182
|
+
'input_tokens' in part && typeof part.input_tokens === 'number' &&
|
|
183
|
+
'output_tokens' in part && typeof part.output_tokens === 'number');
|
|
176
184
|
export interface TextResponsePart {
|
|
177
185
|
content: string;
|
|
178
186
|
}
|
|
@@ -242,7 +250,7 @@ export namespace LanguageModelMetaData {
|
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
export interface LanguageModel extends LanguageModelMetaData {
|
|
245
|
-
request(request:
|
|
253
|
+
request(request: UserRequest, cancellationToken?: CancellationToken): Promise<LanguageModelResponse>;
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
export namespace LanguageModel {
|
|
@@ -84,7 +84,7 @@ export interface PromptService {
|
|
|
84
84
|
*
|
|
85
85
|
* @param id the id of the prompt
|
|
86
86
|
* @param args the object with placeholders, mapping the placeholder key to the value
|
|
87
|
-
* @param context the {@link AIVariableContext} to use during variable
|
|
87
|
+
* @param context the {@link AIVariableContext} to use during variable resolution
|
|
88
88
|
* @param resolveVariable the variable resolving method. Fall back to using the {@link AIVariableService} if not given.
|
|
89
89
|
*/
|
|
90
90
|
getPromptFragment(
|
|
@@ -202,9 +202,19 @@ export interface PromptCustomizationService {
|
|
|
202
202
|
readonly onDidChangeCustomAgents: Event<void>;
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
*
|
|
205
|
+
* Returns all locations of existing customAgents.yml files and potential locations where
|
|
206
|
+
* new customAgents.yml files could be created.
|
|
207
|
+
*
|
|
208
|
+
* @returns An array of objects containing the URI and whether the file exists
|
|
209
|
+
*/
|
|
210
|
+
getCustomAgentsLocations(): Promise<{ uri: URI, exists: boolean }[]>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Opens an existing customAgents.yml file at the given URI, or creates a new one if it doesn't exist.
|
|
214
|
+
*
|
|
215
|
+
* @param uri The URI of the customAgents.yml file to open or create
|
|
206
216
|
*/
|
|
207
|
-
openCustomAgentYaml(): void
|
|
217
|
+
openCustomAgentYaml(uri: URI): Promise<void>;
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
@injectable()
|
|
@@ -331,7 +341,7 @@ export class PromptServiceImpl implements PromptService {
|
|
|
331
341
|
*
|
|
332
342
|
* @param template the unresolved template text
|
|
333
343
|
* @param args the object with placeholders, mapping the placeholder key to the value
|
|
334
|
-
* @param context the {@link AIVariableContext} to use during variable
|
|
344
|
+
* @param context the {@link AIVariableContext} to use during variable resolution
|
|
335
345
|
* @param resolveVariable the variable resolving method. Fall back to using the {@link AIVariableService} if not given.
|
|
336
346
|
*/
|
|
337
347
|
protected async getVariableAndArgReplacements(
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
import { CommandService, nls } from '@theia/core';
|
|
16
|
+
import { CommandService, ILogger, nls } from '@theia/core';
|
|
17
17
|
import { injectable, inject, optional } from '@theia/core/shared/inversify';
|
|
18
18
|
import * as monaco from '@theia/monaco-editor-core';
|
|
19
19
|
import {
|
|
@@ -50,6 +50,9 @@ export class PromptVariableContribution implements AIVariableContribution, AIVar
|
|
|
50
50
|
@inject(PromptCustomizationService) @optional()
|
|
51
51
|
protected readonly promptCustomizationService: PromptCustomizationService;
|
|
52
52
|
|
|
53
|
+
@inject(ILogger)
|
|
54
|
+
protected logger: ILogger;
|
|
55
|
+
|
|
53
56
|
registerVariables(service: AIVariableService): void {
|
|
54
57
|
service.registerResolver(PROMPT_VARIABLE, this);
|
|
55
58
|
service.registerArgumentPicker(PROMPT_VARIABLE, this.triggerArgumentPicker.bind(this));
|
|
@@ -81,7 +84,12 @@ export class PromptVariableContribution implements AIVariableContribution, AIVar
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
|
-
|
|
87
|
+
this.logger.debug(`Could not resolve prompt variable '${request.variable.name}' with arg '${request.arg}'. Returning empty string.`);
|
|
88
|
+
return {
|
|
89
|
+
variable: request.variable,
|
|
90
|
+
value: '',
|
|
91
|
+
allResolvedDependencies: []
|
|
92
|
+
};
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
protected async triggerArgumentPicker(): Promise<string | undefined> {
|
package/src/common/protocol.ts
CHANGED
|
@@ -14,10 +14,28 @@
|
|
|
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 } from '@theia/core';
|
|
17
18
|
import { LanguageModelMetaData } from './language-model';
|
|
19
|
+
import { TokenUsage } from './token-usage-service';
|
|
18
20
|
|
|
19
21
|
export const LanguageModelRegistryClient = Symbol('LanguageModelRegistryClient');
|
|
20
22
|
export interface LanguageModelRegistryClient {
|
|
21
23
|
languageModelAdded(metadata: LanguageModelMetaData): void;
|
|
22
24
|
languageModelRemoved(id: string): void;
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
export const TOKEN_USAGE_SERVICE_PATH = '/services/token-usage';
|
|
28
|
+
|
|
29
|
+
export const TokenUsageServiceClient = Symbol('TokenUsageServiceClient');
|
|
30
|
+
|
|
31
|
+
export interface TokenUsageServiceClient {
|
|
32
|
+
/**
|
|
33
|
+
* Notify the client about new token usage
|
|
34
|
+
*/
|
|
35
|
+
notifyTokenUsage(usage: TokenUsage): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* An event that is fired when token usage data is updated.
|
|
39
|
+
*/
|
|
40
|
+
readonly onTokenUsageUpdated: Event<TokenUsage>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 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 { TokenUsageServiceClient } from './protocol';
|
|
18
|
+
|
|
19
|
+
export const TokenUsageService = Symbol('TokenUsageService');
|
|
20
|
+
|
|
21
|
+
export interface TokenUsage {
|
|
22
|
+
/** The input token count */
|
|
23
|
+
inputTokens: number;
|
|
24
|
+
/** The output token count */
|
|
25
|
+
outputTokens: number;
|
|
26
|
+
/** The model identifier */
|
|
27
|
+
model: string;
|
|
28
|
+
/** The timestamp of when the tokens were used */
|
|
29
|
+
timestamp: Date;
|
|
30
|
+
/** Request identifier */
|
|
31
|
+
requestId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TokenUsageParams {
|
|
35
|
+
/** The input token count */
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
/** The output token count */
|
|
38
|
+
outputTokens: number;
|
|
39
|
+
/** Request identifier */
|
|
40
|
+
requestId: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TokenUsageService {
|
|
44
|
+
/**
|
|
45
|
+
* Records token usage for a model interaction.
|
|
46
|
+
*
|
|
47
|
+
* @param model The identifier of the model that was used
|
|
48
|
+
* @param params Object containing token usage information
|
|
49
|
+
* @returns A promise that resolves when the token usage has been recorded
|
|
50
|
+
*/
|
|
51
|
+
recordTokenUsage(model: string, params: TokenUsageParams): Promise<void>;
|
|
52
|
+
|
|
53
|
+
getTokenUsages(): Promise<TokenUsage[]>;
|
|
54
|
+
|
|
55
|
+
setClient(tokenUsageClient: TokenUsageServiceClient): void;
|
|
56
|
+
}
|
|
@@ -120,6 +120,13 @@ export namespace AIVariableResolutionRequest {
|
|
|
120
120
|
'variable' in arg &&
|
|
121
121
|
typeof (arg as { variable: { name: unknown } }).variable.name === 'string';
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
export function fromResolved(arg: ResolvedAIContextVariable): AIVariableResolutionRequest {
|
|
125
|
+
return {
|
|
126
|
+
variable: arg.variable,
|
|
127
|
+
arg: arg.arg
|
|
128
|
+
};
|
|
129
|
+
}
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
export interface AIVariableContext {
|
|
@@ -136,6 +143,11 @@ export interface AIVariableResolver {
|
|
|
136
143
|
resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<ResolvedAIVariable | undefined>;
|
|
137
144
|
}
|
|
138
145
|
|
|
146
|
+
export interface AIVariableOpener {
|
|
147
|
+
canOpen(request: AIVariableResolutionRequest, context: AIVariableContext): MaybePromise<number>;
|
|
148
|
+
open(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<void>;
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
export interface AIVariableResolverWithVariableDependencies extends AIVariableResolver {
|
|
140
152
|
resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<ResolvedAIVariable | undefined>;
|
|
141
153
|
/**
|
|
@@ -166,6 +178,7 @@ export interface AIVariableService {
|
|
|
166
178
|
registerResolver(variable: AIVariable, resolver: AIVariableResolver): Disposable;
|
|
167
179
|
unregisterResolver(variable: AIVariable, resolver: AIVariableResolver): void;
|
|
168
180
|
getResolver(name: string, arg: string | undefined, context: AIVariableContext): Promise<AIVariableResolver | undefined>;
|
|
181
|
+
resolveVariable(variable: AIVariableArg, context: AIVariableContext, cache?: Map<string, ResolveAIVariableCacheEntry>): Promise<ResolvedAIVariable | undefined>;
|
|
169
182
|
|
|
170
183
|
registerArgumentPicker(variable: AIVariable, argPicker: AIVariableArgPicker): Disposable;
|
|
171
184
|
unregisterArgumentPicker(variable: AIVariable, argPicker: AIVariableArgPicker): void;
|
|
@@ -174,8 +187,6 @@ export interface AIVariableService {
|
|
|
174
187
|
registerArgumentCompletionProvider(variable: AIVariable, argPicker: AIVariableArgCompletionProvider): Disposable;
|
|
175
188
|
unregisterArgumentCompletionProvider(variable: AIVariable, argPicker: AIVariableArgCompletionProvider): void;
|
|
176
189
|
getArgumentCompletionProvider(name: string): Promise<AIVariableArgCompletionProvider | undefined>;
|
|
177
|
-
|
|
178
|
-
resolveVariable(variable: AIVariableArg, context: AIVariableContext, cache?: Map<string, ResolveAIVariableCacheEntry>): Promise<ResolvedAIVariable | undefined>;
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
/** Contributions on the frontend can optionally implement `FrontendVariableContribution`. */
|
|
@@ -191,7 +202,7 @@ export interface ResolveAIVariableCacheEntry {
|
|
|
191
202
|
|
|
192
203
|
export type ResolveAIVariableCache = Map<string, ResolveAIVariableCacheEntry>;
|
|
193
204
|
/**
|
|
194
|
-
* Creates a new, empty cache for AI variable
|
|
205
|
+
* Creates a new, empty cache for AI variable resolution to hand into `AIVariableService.resolveVariable`.
|
|
195
206
|
*/
|
|
196
207
|
export function createAIResolveVariableCache(): Map<string, ResolveAIVariableCacheEntry> {
|
|
197
208
|
return new Map();
|
|
@@ -216,6 +227,7 @@ export class DefaultAIVariableService implements AIVariableService {
|
|
|
216
227
|
protected variables = new Map<string, AIVariable>();
|
|
217
228
|
protected resolvers = new Map<string, AIVariableResolver[]>();
|
|
218
229
|
protected argPickers = new Map<string, AIVariableArgPicker>();
|
|
230
|
+
protected openers = new Map<string, AIVariableOpener[]>();
|
|
219
231
|
protected argCompletionProviders = new Map<string, AIVariableArgCompletionProvider>();
|
|
220
232
|
|
|
221
233
|
protected readonly onDidChangeVariablesEmitter = new Emitter<void>();
|
|
@@ -225,8 +237,7 @@ export class DefaultAIVariableService implements AIVariableService {
|
|
|
225
237
|
@inject(ContributionProvider) @named(AIVariableContribution)
|
|
226
238
|
protected readonly contributionProvider: ContributionProvider<AIVariableContribution>,
|
|
227
239
|
@inject(ILogger) protected readonly logger: ILogger
|
|
228
|
-
) {
|
|
229
|
-
}
|
|
240
|
+
) { }
|
|
230
241
|
|
|
231
242
|
protected initContributions(): void {
|
|
232
243
|
this.contributionProvider.getContributions().forEach(contribution => contribution.registerVariables(this));
|
|
@@ -339,22 +350,27 @@ export class DefaultAIVariableService implements AIVariableService {
|
|
|
339
350
|
return this.argCompletionProviders.get(this.getKey(name)) ?? undefined;
|
|
340
351
|
}
|
|
341
352
|
|
|
342
|
-
|
|
343
|
-
request: AIVariableArg,
|
|
344
|
-
context: AIVariableContext,
|
|
345
|
-
cache: ResolveAIVariableCache = createAIResolveVariableCache()
|
|
346
|
-
): Promise<ResolvedAIVariable | undefined> {
|
|
347
|
-
// Calculate unique variable cache key from variable name and argument
|
|
353
|
+
protected parseRequest(request: AIVariableArg): { variableName: string, arg: string | undefined } {
|
|
348
354
|
const variableName = typeof request === 'string'
|
|
349
355
|
? request
|
|
350
356
|
: typeof request.variable === 'string'
|
|
351
357
|
? request.variable
|
|
352
358
|
: request.variable.name;
|
|
353
359
|
const arg = typeof request === 'string' ? undefined : request.arg;
|
|
360
|
+
return { variableName, arg };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async resolveVariable(
|
|
364
|
+
request: AIVariableArg,
|
|
365
|
+
context: AIVariableContext,
|
|
366
|
+
cache: ResolveAIVariableCache = createAIResolveVariableCache()
|
|
367
|
+
): Promise<ResolvedAIVariable | undefined> {
|
|
368
|
+
// Calculate unique variable cache key from variable name and argument
|
|
369
|
+
const { variableName, arg } = this.parseRequest(request);
|
|
354
370
|
const cacheKey = `${variableName}${PromptText.VARIABLE_SEPARATOR_CHAR}${arg ?? ''}`;
|
|
355
371
|
|
|
356
372
|
// If the current cache key exists and is still in progress, we reached a cycle.
|
|
357
|
-
// If we reach it but it has been resolved, it was part of another
|
|
373
|
+
// If we reach it but it has been resolved, it was part of another resolution branch and we can simply return it.
|
|
358
374
|
if (cache.has(cacheKey)) {
|
|
359
375
|
const existingEntry = cache.get(cacheKey)!;
|
|
360
376
|
if (existingEntry.inProgress) {
|
|
@@ -364,40 +380,38 @@ export class DefaultAIVariableService implements AIVariableService {
|
|
|
364
380
|
return existingEntry.promise;
|
|
365
381
|
}
|
|
366
382
|
|
|
367
|
-
const entry: ResolveAIVariableCacheEntry = { promise:
|
|
383
|
+
const entry: ResolveAIVariableCacheEntry = { promise: this.doResolve(variableName, arg, context, cache), inProgress: true };
|
|
384
|
+
entry.promise.finally(() => entry.inProgress = false);
|
|
368
385
|
cache.set(cacheKey, entry);
|
|
369
386
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const promise = (async () => {
|
|
373
|
-
const variable = this.getVariable(variableName);
|
|
374
|
-
if (!variable) {
|
|
375
|
-
return undefined;
|
|
376
|
-
}
|
|
377
|
-
const resolver = await this.getResolver(variableName, arg, context);
|
|
378
|
-
let resolved: ResolvedAIVariable | undefined;
|
|
379
|
-
if (isResolverWithDependencies(resolver)) {
|
|
380
|
-
// Explicit cast needed because Typescript does not consider the method parameter length of the type guard at compile time
|
|
381
|
-
resolved = await (resolver as AIVariableResolverWithVariableDependencies).resolve(
|
|
382
|
-
{ variable, arg },
|
|
383
|
-
context,
|
|
384
|
-
async (depRequest: AIVariableResolutionRequest) =>
|
|
385
|
-
this.resolveVariable(depRequest, context, cache)
|
|
386
|
-
);
|
|
387
|
-
} else if (resolver) {
|
|
388
|
-
// Explicit cast needed because Typescript does not consider the method parameter length of the type guard at compile time
|
|
389
|
-
resolved = await (resolver as AIVariableResolver).resolve({ variable, arg }, context);
|
|
390
|
-
} else {
|
|
391
|
-
resolved = undefined;
|
|
392
|
-
}
|
|
393
|
-
return resolved ? { ...resolved, arg } : undefined;
|
|
394
|
-
})();
|
|
395
|
-
|
|
396
|
-
entry.promise = promise;
|
|
397
|
-
promise.finally(() => {
|
|
398
|
-
entry.inProgress = false;
|
|
399
|
-
});
|
|
387
|
+
return entry.promise;
|
|
388
|
+
}
|
|
400
389
|
|
|
401
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Asynchronously resolves a variable, handling its dependencies while preventing cyclical resolution.
|
|
392
|
+
* Selects the appropriate resolver and resolution strategy based on whether nested dependency resolution is supported.
|
|
393
|
+
*/
|
|
394
|
+
protected async doResolve(variableName: string, arg: string | undefined, context: AIVariableContext, cache: ResolveAIVariableCache): Promise<ResolvedAIVariable | undefined> {
|
|
395
|
+
const variable = this.getVariable(variableName);
|
|
396
|
+
if (!variable) {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
const resolver = await this.getResolver(variableName, arg, context);
|
|
400
|
+
let resolved: ResolvedAIVariable | undefined;
|
|
401
|
+
if (isResolverWithDependencies(resolver)) {
|
|
402
|
+
// Explicit cast needed because Typescript does not consider the method parameter length of the type guard at compile time
|
|
403
|
+
resolved = await (resolver as AIVariableResolverWithVariableDependencies).resolve(
|
|
404
|
+
{ variable, arg },
|
|
405
|
+
context,
|
|
406
|
+
async (depRequest: AIVariableResolutionRequest) =>
|
|
407
|
+
this.resolveVariable(depRequest, context, cache)
|
|
408
|
+
);
|
|
409
|
+
} else if (resolver) {
|
|
410
|
+
// Explicit cast needed because Typescript does not consider the method parameter length of the type guard at compile time
|
|
411
|
+
resolved = await (resolver as AIVariableResolver).resolve({ variable, arg }, context);
|
|
412
|
+
} else {
|
|
413
|
+
resolved = undefined;
|
|
414
|
+
}
|
|
415
|
+
return resolved ? { ...resolved, arg } : undefined;
|
|
402
416
|
}
|
|
403
417
|
}
|
|
@@ -36,9 +36,13 @@ import {
|
|
|
36
36
|
LanguageModelRegistryFrontendDelegate,
|
|
37
37
|
languageModelDelegatePath,
|
|
38
38
|
languageModelRegistryDelegatePath,
|
|
39
|
-
LanguageModelRegistryClient
|
|
39
|
+
LanguageModelRegistryClient,
|
|
40
|
+
TokenUsageService,
|
|
41
|
+
TokenUsageServiceClient,
|
|
42
|
+
TOKEN_USAGE_SERVICE_PATH
|
|
40
43
|
} from '../common';
|
|
41
44
|
import { BackendLanguageModelRegistry } from './backend-language-model-registry';
|
|
45
|
+
import { TokenUsageServiceImpl } from './token-usage-service-impl';
|
|
42
46
|
|
|
43
47
|
// We use a connection module to handle AI services separately for each frontend.
|
|
44
48
|
const aiCoreConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService, bindFrontendService }) => {
|
|
@@ -46,6 +50,22 @@ const aiCoreConnectionModule = ConnectionContainerModule.create(({ bind, bindBac
|
|
|
46
50
|
bind(BackendLanguageModelRegistry).toSelf().inSingletonScope();
|
|
47
51
|
bind(LanguageModelRegistry).toService(BackendLanguageModelRegistry);
|
|
48
52
|
|
|
53
|
+
bind(TokenUsageService).to(TokenUsageServiceImpl).inSingletonScope();
|
|
54
|
+
|
|
55
|
+
bind(ConnectionHandler)
|
|
56
|
+
.toDynamicValue(
|
|
57
|
+
({ container }) =>
|
|
58
|
+
new RpcConnectionHandler<TokenUsageServiceClient>(
|
|
59
|
+
TOKEN_USAGE_SERVICE_PATH,
|
|
60
|
+
client => {
|
|
61
|
+
const service = container.get<TokenUsageService>(TokenUsageService);
|
|
62
|
+
service.setClient(client);
|
|
63
|
+
return service;
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
.inSingletonScope();
|
|
68
|
+
|
|
49
69
|
bind(LanguageModelRegistryFrontendDelegate).to(LanguageModelRegistryFrontendDelegateImpl).inSingletonScope();
|
|
50
70
|
bind(ConnectionHandler)
|
|
51
71
|
.toDynamicValue(
|
|
@@ -19,7 +19,6 @@ import { CancellationToken, CancellationTokenSource, ILogger, generateUuid } fro
|
|
|
19
19
|
import {
|
|
20
20
|
LanguageModelMetaData,
|
|
21
21
|
LanguageModelRegistry,
|
|
22
|
-
LanguageModelRequest,
|
|
23
22
|
isLanguageModelStreamResponse,
|
|
24
23
|
isLanguageModelTextResponse,
|
|
25
24
|
LanguageModelStreamResponsePart,
|
|
@@ -29,6 +28,7 @@ import {
|
|
|
29
28
|
LanguageModelResponseDelegate,
|
|
30
29
|
LanguageModelRegistryClient,
|
|
31
30
|
isLanguageModelParsedResponse,
|
|
31
|
+
UserRequest,
|
|
32
32
|
} from '../common';
|
|
33
33
|
import { BackendLanguageModelRegistry } from './backend-language-model-registry';
|
|
34
34
|
|
|
@@ -69,7 +69,7 @@ export class LanguageModelFrontendDelegateImpl implements LanguageModelFrontendD
|
|
|
69
69
|
|
|
70
70
|
async request(
|
|
71
71
|
modelId: string,
|
|
72
|
-
request:
|
|
72
|
+
request: UserRequest,
|
|
73
73
|
requestId: string,
|
|
74
74
|
cancellationToken?: CancellationToken
|
|
75
75
|
): Promise<LanguageModelResponseDelegate> {
|