@theia/ai-core 1.59.0 → 1.60.0-next.47

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 (53) hide show
  1. package/lib/browser/agent-preferences.d.ts +4 -0
  2. package/lib/browser/agent-preferences.d.ts.map +1 -0
  3. package/lib/browser/agent-preferences.js +74 -0
  4. package/lib/browser/agent-preferences.js.map +1 -0
  5. package/lib/browser/ai-activation-service.d.ts +1 -0
  6. package/lib/browser/ai-activation-service.d.ts.map +1 -1
  7. package/lib/browser/ai-activation-service.js +12 -2
  8. package/lib/browser/ai-activation-service.js.map +1 -1
  9. package/lib/browser/ai-core-command-contribution.d.ts +8 -0
  10. package/lib/browser/ai-core-command-contribution.d.ts.map +1 -0
  11. package/lib/browser/ai-core-command-contribution.js +44 -0
  12. package/lib/browser/ai-core-command-contribution.js.map +1 -0
  13. package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
  14. package/lib/browser/ai-core-frontend-module.js +8 -1
  15. package/lib/browser/ai-core-frontend-module.js.map +1 -1
  16. package/lib/browser/index.d.ts +1 -0
  17. package/lib/browser/index.d.ts.map +1 -1
  18. package/lib/browser/index.js +1 -0
  19. package/lib/browser/index.js.map +1 -1
  20. package/lib/browser/prompttemplate-contribution.d.ts.map +1 -1
  21. package/lib/browser/prompttemplate-contribution.js +4 -4
  22. package/lib/browser/prompttemplate-contribution.js.map +1 -1
  23. package/lib/common/prompt-service.d.ts +42 -1
  24. package/lib/common/prompt-service.d.ts.map +1 -1
  25. package/lib/common/prompt-service.js +68 -24
  26. package/lib/common/prompt-service.js.map +1 -1
  27. package/lib/common/prompt-service.spec.js +59 -1
  28. package/lib/common/prompt-service.spec.js.map +1 -1
  29. package/lib/common/prompt-variable-contribution.d.ts +16 -0
  30. package/lib/common/prompt-variable-contribution.d.ts.map +1 -0
  31. package/lib/common/prompt-variable-contribution.js +122 -0
  32. package/lib/common/prompt-variable-contribution.js.map +1 -0
  33. package/lib/common/variable-service.d.ts +26 -4
  34. package/lib/common/variable-service.d.ts.map +1 -1
  35. package/lib/common/variable-service.js +77 -16
  36. package/lib/common/variable-service.js.map +1 -1
  37. package/lib/common/variable-service.spec.d.ts +2 -0
  38. package/lib/common/variable-service.spec.d.ts.map +1 -0
  39. package/lib/common/variable-service.spec.js +237 -0
  40. package/lib/common/variable-service.spec.js.map +1 -0
  41. package/package.json +9 -9
  42. package/src/browser/agent-preferences.ts +74 -0
  43. package/src/browser/ai-activation-service.ts +13 -2
  44. package/src/browser/ai-core-command-contribution.ts +37 -0
  45. package/src/browser/ai-core-frontend-module.ts +10 -2
  46. package/src/browser/ai-settings-service.ts +1 -1
  47. package/src/browser/index.ts +1 -0
  48. package/src/browser/prompttemplate-contribution.ts +4 -4
  49. package/src/common/prompt-service.spec.ts +68 -1
  50. package/src/common/prompt-service.ts +109 -24
  51. package/src/common/prompt-variable-contribution.ts +138 -0
  52. package/src/common/variable-service.spec.ts +289 -0
  53. package/src/common/variable-service.ts +110 -13
@@ -0,0 +1,289 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 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 * as sinon from 'sinon';
18
+ import { ContributionProvider, Logger } from '@theia/core';
19
+ import { expect } from 'chai';
20
+ import {
21
+ DefaultAIVariableService,
22
+ AIVariable,
23
+ AIVariableContribution,
24
+ AIVariableResolverWithVariableDependencies,
25
+ ResolvedAIVariable,
26
+ createAIResolveVariableCache,
27
+ AIVariableArg
28
+ } from './variable-service';
29
+
30
+ describe('DefaultAIVariableService', () => {
31
+ let variableService: DefaultAIVariableService;
32
+ let contributionProvider: sinon.SinonStubbedInstance<ContributionProvider<AIVariableContribution>>;
33
+ let logger: sinon.SinonStubbedInstance<Logger>;
34
+
35
+ const varA: AIVariable = {
36
+ id: 'provider.a',
37
+ name: 'a',
38
+ description: 'Variable A'
39
+ };
40
+
41
+ const varB: AIVariable = {
42
+ id: 'provider.b',
43
+ name: 'b',
44
+ description: 'Variable B'
45
+ };
46
+
47
+ const varC: AIVariable = {
48
+ id: 'provider.c',
49
+ name: 'c',
50
+ description: 'Variable C'
51
+ };
52
+
53
+ const varD: AIVariable = {
54
+ id: 'provider.d',
55
+ name: 'd',
56
+ description: 'Variable D'
57
+ };
58
+
59
+ // Create resolvers for our variables
60
+ const resolverA: AIVariableResolverWithVariableDependencies = {
61
+ canResolve: sinon.stub().returns(1),
62
+ resolve: async (_request, _context, resolveDependency?: (variable: AIVariableArg) => Promise<ResolvedAIVariable | undefined>) => {
63
+ if (resolveDependency) {
64
+ // Variable A depends on both B and C
65
+ const dependencyB = await resolveDependency({ variable: varB });
66
+ const dependencyC = await resolveDependency({ variable: varC });
67
+
68
+ return {
69
+ variable: varA,
70
+ value: `A resolved with B: ${dependencyB?.value ?? 'undefined'} and C: ${dependencyC?.value ?? 'undefined'}`,
71
+ allResolvedDependencies: [
72
+ ...(dependencyB ? [dependencyB] : []),
73
+ ...(dependencyC ? [dependencyC] : [])
74
+ ]
75
+ };
76
+ }
77
+ return { variable: varA, value: 'A value' };
78
+ }
79
+ };
80
+
81
+ const resolverB: AIVariableResolverWithVariableDependencies = {
82
+ canResolve: sinon.stub().returns(1),
83
+ resolve: async (_request, _context, resolveDependency?: (variable: AIVariableArg) => Promise<ResolvedAIVariable | undefined>) => {
84
+ if (resolveDependency) {
85
+ // Variable B depends on A, creating a cycle
86
+ const dependencyA = await resolveDependency({ variable: varA });
87
+
88
+ return {
89
+ variable: varB,
90
+ value: `B resolved with A: ${dependencyA?.value ?? 'undefined (cycle detected)'}`,
91
+ allResolvedDependencies: dependencyA ? [dependencyA] : []
92
+ };
93
+ }
94
+ return { variable: varB, value: 'B value' };
95
+ }
96
+ };
97
+
98
+ const resolverC: AIVariableResolverWithVariableDependencies = {
99
+ canResolve: sinon.stub().returns(1),
100
+ resolve: async (_request, _context, resolveDependency?: (variable: AIVariableArg) => Promise<ResolvedAIVariable | undefined>) => {
101
+ if (resolveDependency) {
102
+ // Variable C depends on D with two different arguments
103
+ const dependencyD1 = await resolveDependency({ variable: varD, arg: 'arg1' });
104
+ const dependencyD2 = await resolveDependency({ variable: varD, arg: 'arg2' });
105
+
106
+ return {
107
+ variable: varC,
108
+ value: `C resolved with D(arg1): ${dependencyD1?.value ?? 'undefined'} and D(arg2): ${dependencyD2?.value ?? 'undefined'}`,
109
+ allResolvedDependencies: [
110
+ ...(dependencyD1 ? [dependencyD1] : []),
111
+ ...(dependencyD2 ? [dependencyD2] : [])
112
+ ]
113
+ };
114
+ }
115
+ return { variable: varC, value: 'C value' };
116
+ }
117
+ };
118
+
119
+ const resolverD: AIVariableResolverWithVariableDependencies = {
120
+ canResolve: sinon.stub().returns(1),
121
+ resolve: async request => {
122
+ const arg = request.arg;
123
+ return {
124
+ variable: varD,
125
+ value: arg ? `D value with ${arg}` : 'D value'
126
+ };
127
+ }
128
+ };
129
+
130
+ beforeEach(() => {
131
+ // Create stub for the contribution provider
132
+ contributionProvider = {
133
+ getContributions: sinon.stub().returns([])
134
+ } as sinon.SinonStubbedInstance<ContributionProvider<AIVariableContribution>>;
135
+
136
+ // Create stub for logger
137
+ logger = sinon.createStubInstance(Logger);
138
+
139
+ // Create the service under test
140
+ variableService = new DefaultAIVariableService(
141
+ contributionProvider,
142
+ logger
143
+ );
144
+
145
+ // Register the variables and resolvers
146
+ variableService.registerResolver(varA, resolverA);
147
+ variableService.registerResolver(varB, resolverB);
148
+ variableService.registerResolver(varC, resolverC);
149
+ variableService.registerResolver(varD, resolverD);
150
+ });
151
+
152
+ describe('resolveVariable', () => {
153
+ it('should handle recursive variable resolution and detect cycles', async () => {
154
+ // Try to resolve variable A, which has a cycle with B and also depends on C which depends on D
155
+ const result = await variableService.resolveVariable('a', {});
156
+
157
+ // Verify the result
158
+ expect(result).to.not.be.undefined;
159
+ expect(result!.variable).to.deep.equal(varA);
160
+
161
+ // The value should contain B's value (with a cycle detection) and C's value
162
+ expect(result!.value).to.include('B resolved with A: undefined (cycle detected)');
163
+ expect(result!.value).to.include('C resolved with D(arg1): D value with arg1 and D(arg2): D value with arg2');
164
+
165
+ // Verify that we logged a warning about the cycle
166
+ expect(logger.warn.calledOnce).to.be.true;
167
+ expect(logger.warn.firstCall.args[0]).to.include('Cycle detected for variable: a');
168
+
169
+ // Verify dependencies are tracked
170
+ expect(result!.allResolvedDependencies).to.have.lengthOf(2);
171
+
172
+ // Find the B dependency and verify it doesn't have A in its dependencies (due to cycle detection)
173
+ const bDependency = result!.allResolvedDependencies!.find(d => d.variable.name === 'b');
174
+ expect(bDependency).to.not.be.undefined;
175
+ expect(bDependency!.allResolvedDependencies).to.be.empty;
176
+
177
+ // Find the C dependency and its D dependencies
178
+ const cDependency = result!.allResolvedDependencies!.find(d => d.variable.name === 'c');
179
+ expect(cDependency).to.not.be.undefined;
180
+ expect(cDependency!.value).to.equal('C resolved with D(arg1): D value with arg1 and D(arg2): D value with arg2');
181
+ expect(cDependency!.allResolvedDependencies).to.have.lengthOf(2);
182
+
183
+ const dDependency1 = cDependency!.allResolvedDependencies![0];
184
+ expect(dDependency1.variable.name).to.equal('d');
185
+ expect(dDependency1.value).to.equal('D value with arg1');
186
+
187
+ const dDependency2 = cDependency!.allResolvedDependencies![1];
188
+ expect(dDependency2.variable.name).to.equal('d');
189
+ expect(dDependency2.value).to.equal('D value with arg2');
190
+ });
191
+
192
+ it('should handle variables with a simple chain of dependencies', async () => {
193
+ // Variable C depends on D with two different arguments
194
+ const result = await variableService.resolveVariable('c', {});
195
+
196
+ expect(result).to.not.be.undefined;
197
+ expect(result!.variable).to.deep.equal(varC);
198
+ expect(result!.value).to.equal('C resolved with D(arg1): D value with arg1 and D(arg2): D value with arg2');
199
+
200
+ // Verify dependency chain
201
+ expect(result!.allResolvedDependencies).to.have.lengthOf(2);
202
+
203
+ const dDependency1 = result!.allResolvedDependencies![0];
204
+ expect(dDependency1.variable.name).to.equal('d');
205
+ expect(dDependency1.value).to.equal('D value with arg1');
206
+
207
+ const dDependency2 = result!.allResolvedDependencies![1];
208
+ expect(dDependency2.variable.name).to.equal('d');
209
+ expect(dDependency2.value).to.equal('D value with arg2');
210
+ });
211
+
212
+ it('should handle variables without dependencies', async () => {
213
+ // D has no dependencies
214
+ const result = await variableService.resolveVariable('d', {});
215
+
216
+ expect(result).to.not.be.undefined;
217
+ expect(result!.variable).to.deep.equal(varD);
218
+ expect(result!.value).to.equal('D value');
219
+ expect(result!.allResolvedDependencies).to.be.undefined;
220
+ });
221
+
222
+ it('should handle variables with arguments', async () => {
223
+ // Test D with an argument
224
+ const result = await variableService.resolveVariable({ variable: 'd', arg: 'test-arg' }, {}, undefined);
225
+
226
+ expect(result).to.not.be.undefined;
227
+ expect(result!.variable).to.deep.equal(varD);
228
+ expect(result!.value).to.equal('D value with test-arg');
229
+ expect(result!.allResolvedDependencies).to.be.undefined;
230
+ });
231
+
232
+ it('should return undefined for non-existent variables', async () => {
233
+ const result = await variableService.resolveVariable('nonexistent', {});
234
+ expect(result).to.be.undefined;
235
+ });
236
+
237
+ it('should properly populate cache when resolving variables with dependencies', async () => {
238
+ // Create a cache to pass into the resolver
239
+ const cache = createAIResolveVariableCache();
240
+
241
+ // Resolve variable A, which depends on B and C, and C depends on D with two arguments
242
+ const result = await variableService.resolveVariable('a', {}, cache);
243
+
244
+ // Verify that the result is correct
245
+ expect(result).to.not.be.undefined;
246
+ expect(result!.variable).to.deep.equal(varA);
247
+
248
+ // Verify that the cache has entries for all variables
249
+ expect(cache.size).to.equal(5); // A, B, C, D(arg1), and D(arg2)
250
+
251
+ // Verify that all variables have entries in the cache
252
+ expect(cache.has('a:')).to.be.true; // 'a:' key format is variableName + separator + arg (empty in this case)
253
+ expect(cache.has('b:')).to.be.true;
254
+ expect(cache.has('c:')).to.be.true;
255
+ expect(cache.has('d:arg1')).to.be.true;
256
+ expect(cache.has('d:arg2')).to.be.true;
257
+
258
+ // Verify that all promises in the cache are resolved
259
+ for (const entry of cache.values()) {
260
+ const resolvedVar = await entry.promise;
261
+ expect(resolvedVar).to.not.be.undefined;
262
+ expect(entry.inProgress).to.be.false;
263
+ }
264
+
265
+ // Check specific variable results from cache
266
+ const aEntry = cache.get('a:');
267
+ const aResult = await aEntry!.promise;
268
+ expect(aResult!.variable.name).to.equal('a');
269
+
270
+ const bEntry = cache.get('b:');
271
+ const bResult = await bEntry!.promise;
272
+ expect(bResult!.variable.name).to.equal('b');
273
+
274
+ const cEntry = cache.get('c:');
275
+ const cResult = await cEntry!.promise;
276
+ expect(cResult!.variable.name).to.equal('c');
277
+
278
+ const dEntry1 = cache.get('d:arg1');
279
+ const dResult1 = await dEntry1!.promise;
280
+ expect(dResult1!.variable.name).to.equal('d');
281
+ expect(dResult1!.value).to.equal('D value with arg1');
282
+
283
+ const dEntry2 = cache.get('d:arg2');
284
+ const dResult2 = await dEntry2!.promise;
285
+ expect(dResult2!.variable.name).to.equal('d');
286
+ expect(dResult2!.value).to.equal('D value with arg2');
287
+ });
288
+ });
289
+ });
@@ -22,6 +22,7 @@
22
22
  import { ContributionProvider, Disposable, Emitter, ILogger, MaybePromise, Prioritizeable, Event } from '@theia/core';
23
23
  import { inject, injectable, named } from '@theia/core/shared/inversify';
24
24
  import * as monaco from '@theia/monaco-editor-core';
25
+ import { PromptText } from './prompt-text';
25
26
 
26
27
  /**
27
28
  * A variable is a short string that is used to reference a value that is resolved and replaced in the user prompt at request-time.
@@ -75,6 +76,8 @@ export interface ResolvedAIVariable {
75
76
  arg?: string;
76
77
  /** value that is inserted into the prompt at the position of the variable usage */
77
78
  value: string;
79
+ /** Flat list of all variables that have been (recursively) resolved while resolving this variable. */
80
+ allResolvedDependencies?: ResolvedAIVariable[];
78
81
  }
79
82
 
80
83
  export namespace ResolvedAIVariable {
@@ -132,6 +135,24 @@ export interface AIVariableResolver {
132
135
  resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<ResolvedAIVariable | undefined>;
133
136
  }
134
137
 
138
+ export interface AIVariableResolverWithVariableDependencies extends AIVariableResolver {
139
+ resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<ResolvedAIVariable | undefined>;
140
+ /**
141
+ * Resolve the given AI variable resolution request. When resolving dependencies with `resolveDependency`,
142
+ * add the resolved dependencies to the result's `allResolvedDependencies` list
143
+ * to enable consumers of the resolved variable to inspect dependencies.
144
+ */
145
+ resolve(
146
+ request: AIVariableResolutionRequest,
147
+ context: AIVariableContext,
148
+ resolveDependency: (variable: AIVariableArg) => Promise<ResolvedAIVariable | undefined>
149
+ ): Promise<ResolvedAIVariable | undefined>;
150
+ }
151
+
152
+ function isResolverWithDependencies(resolver: AIVariableResolver | undefined): resolver is AIVariableResolverWithVariableDependencies {
153
+ return resolver !== undefined && resolver.resolve.length >= 3;
154
+ }
155
+
135
156
  export const AIVariableService = Symbol('AIVariableService');
136
157
  export interface AIVariableService {
137
158
  hasVariable(name: string): boolean;
@@ -153,7 +174,7 @@ export interface AIVariableService {
153
174
  unregisterArgumentCompletionProvider(variable: AIVariable, argPicker: AIVariableArgCompletionProvider): void;
154
175
  getArgumentCompletionProvider(name: string): Promise<AIVariableArgCompletionProvider | undefined>;
155
176
 
156
- resolveVariable(variable: AIVariableArg, context: AIVariableContext): Promise<ResolvedAIVariable | undefined>;
177
+ resolveVariable(variable: AIVariableArg, context: AIVariableContext, cache?: Map<string, ResolveAIVariableCacheEntry>): Promise<ResolvedAIVariable | undefined>;
157
178
  }
158
179
 
159
180
  /** Contributions on the frontend can optionally implement `FrontendVariableContribution`. */
@@ -162,6 +183,33 @@ export interface AIVariableContribution {
162
183
  registerVariables(service: AIVariableService): void;
163
184
  }
164
185
 
186
+ export interface ResolveAIVariableCacheEntry {
187
+ promise: Promise<ResolvedAIVariable | undefined>;
188
+ inProgress: boolean;
189
+ }
190
+
191
+ export type ResolveAIVariableCache = Map<string, ResolveAIVariableCacheEntry>;
192
+ /**
193
+ * Creates a new, empty cache for AI variable resolvement to hand into `AIVariableService.resolveVariable`.
194
+ */
195
+ export function createAIResolveVariableCache(): Map<string, ResolveAIVariableCacheEntry> {
196
+ return new Map();
197
+ }
198
+
199
+ /** Utility function to get all resolved AI variables from a {@link ResolveAIVariableCache} */
200
+ export async function getAllResolvedAIVariables(cache: ResolveAIVariableCache): Promise<ResolvedAIVariable[]> {
201
+ const resolvedVariables: ResolvedAIVariable[] = [];
202
+ for (const cacheEntry of cache.values()) {
203
+ if (!cacheEntry.inProgress) {
204
+ const resolvedVariable = await cacheEntry.promise;
205
+ if (resolvedVariable) {
206
+ resolvedVariables.push(resolvedVariable);
207
+ }
208
+ }
209
+ }
210
+ return resolvedVariables;
211
+ }
212
+
165
213
  @injectable()
166
214
  export class DefaultAIVariableService implements AIVariableService {
167
215
  protected variables = new Map<string, AIVariable>();
@@ -172,11 +220,10 @@ export class DefaultAIVariableService implements AIVariableService {
172
220
  protected readonly onDidChangeVariablesEmitter = new Emitter<void>();
173
221
  readonly onDidChangeVariables: Event<void> = this.onDidChangeVariablesEmitter.event;
174
222
 
175
- @inject(ILogger) protected logger: ILogger;
176
-
177
223
  constructor(
178
224
  @inject(ContributionProvider) @named(AIVariableContribution)
179
- protected readonly contributionProvider: ContributionProvider<AIVariableContribution>
225
+ protected readonly contributionProvider: ContributionProvider<AIVariableContribution>,
226
+ @inject(ILogger) protected readonly logger: ILogger
180
227
  ) {
181
228
  }
182
229
 
@@ -291,15 +338,65 @@ export class DefaultAIVariableService implements AIVariableService {
291
338
  return this.argCompletionProviders.get(this.getKey(name)) ?? undefined;
292
339
  }
293
340
 
294
- async resolveVariable(request: AIVariableArg, context: AIVariableContext): Promise<ResolvedAIVariable | undefined> {
295
- const variableName = typeof request === 'string' ? request : typeof request.variable === 'string' ? request.variable : request.variable.name;
296
- const variable = this.getVariable(variableName);
297
- if (!variable) {
298
- return undefined;
299
- }
341
+ async resolveVariable(
342
+ request: AIVariableArg,
343
+ context: AIVariableContext,
344
+ cache: ResolveAIVariableCache = createAIResolveVariableCache()
345
+ ): Promise<ResolvedAIVariable | undefined> {
346
+ // Calculate unique variable cache key from variable name and argument
347
+ const variableName = typeof request === 'string'
348
+ ? request
349
+ : typeof request.variable === 'string'
350
+ ? request.variable
351
+ : request.variable.name;
300
352
  const arg = typeof request === 'string' ? undefined : request.arg;
301
- const resolver = await this.getResolver(variableName, arg, context);
302
- const resolved = await resolver?.resolve({ variable, arg }, context);
303
- return resolved ? { ...resolved, arg } : undefined;
353
+ const cacheKey = `${variableName}${PromptText.VARIABLE_SEPARATOR_CHAR}${arg ?? ''}`;
354
+
355
+ // If the current cache key exists and is still in progress, we reached a cycle.
356
+ // If we reach it but it has been resolved, it was part of another resolvement branch and we can simply return it.
357
+ if (cache.has(cacheKey)) {
358
+ const existingEntry = cache.get(cacheKey)!;
359
+ if (existingEntry.inProgress) {
360
+ this.logger.warn(`Cycle detected for variable: ${variableName} with arg: ${arg}. Skipping resolution.`);
361
+ return undefined;
362
+ }
363
+ return existingEntry.promise;
364
+ }
365
+
366
+ const entry: ResolveAIVariableCacheEntry = { promise: Promise.resolve(undefined), inProgress: true };
367
+ cache.set(cacheKey, entry);
368
+
369
+ // Asynchronously resolves a variable, handling its dependencies while preventing cyclical resolution.
370
+ // Selects the appropriate resolver and resolution strategy based on whether nested dependency resolution is supported.
371
+ const promise = (async () => {
372
+ const variable = this.getVariable(variableName);
373
+ if (!variable) {
374
+ return undefined;
375
+ }
376
+ const resolver = await this.getResolver(variableName, arg, context);
377
+ let resolved: ResolvedAIVariable | undefined;
378
+ if (isResolverWithDependencies(resolver)) {
379
+ // Explicit cast needed because Typescript does not consider the method parameter length of the type guard at compile time
380
+ resolved = await (resolver as AIVariableResolverWithVariableDependencies).resolve(
381
+ { variable, arg },
382
+ context,
383
+ async (depRequest: AIVariableResolutionRequest) =>
384
+ this.resolveVariable(depRequest, context, cache)
385
+ );
386
+ } else if (resolver) {
387
+ // Explicit cast needed because Typescript does not consider the method parameter length of the type guard at compile time
388
+ resolved = await (resolver as AIVariableResolver).resolve({ variable, arg }, context);
389
+ } else {
390
+ resolved = undefined;
391
+ }
392
+ return resolved ? { ...resolved, arg } : undefined;
393
+ })();
394
+
395
+ entry.promise = promise;
396
+ promise.finally(() => {
397
+ entry.inProgress = false;
398
+ });
399
+
400
+ return promise;
304
401
  }
305
402
  }