Skip to content

Support AI variable dependencies explicitly #15000

@planger

Description

@planger

In the course of #14971, we identified the potential need of AI variables that depend on other variables. The concrete example use case was a chat context summary variable that lists all variables of the context; to be able to summarize a context variable, it needs to be resolved first. Other use cases may involve reusing core variables, like workspace root, environment, etc.

Currently, resolving a variable in the course of another variable resolution is not explicitly supported; the variable provider can only capture a reference to the variable service when it is asked to register the variable itself, and then use this variable. It cannot get the variable service injected directly, as this would cause cyclic dependency injection. Also we may run into cyclic variable resolutions.

As a potential solution we discussed the approach below.

Extend the variable resolver

export interface AIVariableResolverWithVariableDependencies extends AIVariableResolver {
    resolve(
        request: AIVariableResolutionRequest,
        context: AIVariableContext,
        resolveDependency: (req: AIVariableResolutionRequest) => Promise<ResolvedAIVariable | undefined>
    ): Promise<ResolvedAIVariable | undefined>;
}

function isResolverWithDependencies(resolver: AIVariableResolver | undefined): resolver is AIVariableResolverWithVariableDependencies {
    return resolver !== undefined && resolver.resolve.length >= 3;
}

Enhance the resolveVariable method of the variable service

interface CacheEntry {
    promise: Promise<ResolvedAIVariable | undefined>;
    inProgress: boolean;
}

export class DefaultAIVariableService implements AIVariableService {
    // ... existing properties and methods

    async resolveVariable(
        request: AIVariableArg,
        context: AIVariableContext,
        cache: Map<string, CacheEntry> = new Map()
    ): Promise<ResolvedAIVariable | undefined> {
        const variableName = typeof request === 'string'
            ? request
            : typeof request.variable === 'string'
                ? request.variable
                : request.variable.name;

        if (cache.has(variableName)) {
            const entry = cache.get(variableName)!;
            if (entry.inProgress) {
                this.logger.warn(`Cycle detected for variable: ${variableName}. Skipping resolution.`);
                return undefined;
            }
            return entry.promise;
        }

        const entry: CacheEntry = { promise: Promise.resolve(undefined), inProgress: true };
        cache.set(variableName, entry);

        const promise = (async () => {
            const variable = this.getVariable(variableName);
            if (!variable) {
                return undefined;
            }
            const arg = typeof request === 'string' ? undefined : request.arg;
            const resolver = await this.getResolver(variableName, arg, context);
            let resolved: ResolvedAIVariable | undefined;
            if (isResolverWithDependencies(resolver)) {
                resolved = await resolver.resolve(
                    { variable, arg },
                    context,
                    async (depRequest: AIVariableResolutionRequest) =>
                        this.resolveVariable(depRequest, context, cache)
                );
            } else {
                resolved = await resolver?.resolve({ variable, arg }, context);
            }
            return resolved ? { ...resolved, arg } : undefined;
        })();

        entry.promise = promise;
        promise.finally(() => {
            entry.inProgress = false;
        });

        return promise;
    }
}

Summary

  • AIVariableResolverWithVariableDependencies lets a resolver access dependencies through a callback during its resolution.
  • The updated resolveVariable method uses a promise-based cache with an inProgress flag for cycle detection. This way, if a variable is already being resolved, it’s skipped—avoiding cycles.
  • With this approach, resolved variables are accessible during the resolution of dependent ones.

Metadata

Metadata

Assignees

Labels

theia-aiissues related to TheiaAI

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions