feat(vault): add vault and LLM provider services

Adds the services the credential flows need, following the existing
call/fake pattern: a base, an HTTP implementation and a fake for the
vault, the LLM provider catalog and the per-provider credential listing,
registered in all three environments.

The two fakes share an in-memory vault store that reproduces the
server-side rules of the credential listing (owner, active, provider,
soft delete), so a credential created from one panel shows up in the
other exactly as it does against the real backend.

Also teaches extractHttpErrorMessage the RFC 7807 `detail` field, which
is what the execution endpoints answer with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucio Lelii 2026-09-01 23:23:14 +02:00
parent 8b34f49056
commit 7e9b483882
25 changed files with 609 additions and 4 deletions

View File

@ -57,9 +57,34 @@ export type AssistantPhaseModels = {
export type AssistantLlmSelection = {
provider: string;
model: string;
credentialId?: string;
phaseModels?: AssistantPhaseModels;
};
export type VaultSecret = {
id: string;
label: string;
provider: string;
description?: string;
active: boolean;
lastUsedAt?: string;
maskedPreview?: string;
};
export type VaultSecretCreateRequest = {
label: string;
provider: string;
description?: string;
value: string;
};
export type VaultSecretUpdateRequest = {
label?: string;
description?: string;
active?: boolean;
value?: string;
};
export type AssistantSessionRequest = {
llmSelection?: AssistantLlmSelection;
};

View File

@ -0,0 +1,11 @@
export type LlmProviderCapability = {
name: string;
requiresCredential: boolean;
};
export type ExecutionVaultCredential = {
id: string;
label: string;
description?: string;
provider: string;
};

View File

@ -0,0 +1,6 @@
import { ExecutionVaultCredential } from '@models/llm-provider';
import { Observable } from 'rxjs';
export abstract class ExecutionVaultCredentialsCallServiceBase {
abstract listForProvider(provider: string): Observable<ExecutionVaultCredential[]>;
}

View File

@ -0,0 +1,15 @@
import { ExecutionVaultCredential } from '@models/llm-provider';
import { Observable, of } from 'rxjs';
import { vaultFakeStore } from '@services/vault/vault-fake-store';
import { ExecutionVaultCredentialsCallServiceBase } from './execution-vault-credentials-call.base';
export class ExecutionVaultCredentialsCallServiceFake extends ExecutionVaultCredentialsCallServiceBase {
override listForProvider(provider: string): Observable<ExecutionVaultCredential[]> {
return of(vaultFakeStore.listActiveByProvider(provider).map((secret) => ({
id: secret.id,
label: secret.label,
description: secret.description,
provider: secret.provider
})));
}
}

View File

@ -0,0 +1,49 @@
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { environment } from '@environment';
import { firstValueFrom } from 'rxjs';
import { ExecutionVaultCredentialsCallService } from './execution-vault-credentials-call';
describe('ExecutionVaultCredentialsCallService', () => {
let service: ExecutionVaultCredentialsCallService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ExecutionVaultCredentialsCallService, provideHttpClient(), provideHttpClientTesting()]
});
service = TestBed.inject(ExecutionVaultCredentialsCallService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify());
it('requests the credentials of a single provider and maps the vault secret id', async () => {
const result = firstValueFrom(service.listForProvider('OpenAI'));
const request = httpMock.expectOne(
(candidate) => candidate.url === `${environment.apiUrl}/secure-retriever/UserSecrets/forProvider/items`
);
expect(request.request.params.get('provider')).toBe('OpenAI');
request.flush({
items: [
{
data: 'vault-secret-1',
descriptor: { label: 'OpenAI key', description: 'shared', meta: { provider: 'OpenAI' } }
},
{
data: '',
descriptor: { label: 'Broken entry', meta: { provider: 'OpenAI' } }
}
]
});
await expect(result).resolves.toEqual([{
id: 'vault-secret-1',
label: 'OpenAI key',
description: 'shared',
provider: 'OpenAI'
}]);
});
});

View File

@ -0,0 +1,43 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { inject } from '@angular/core';
import { environment } from '@environment';
import { ExecutionVaultCredential } from '@models/llm-provider';
import { map, Observable } from 'rxjs';
import { ExecutionVaultCredentialsCallServiceBase } from './execution-vault-credentials-call.base';
export class ExecutionVaultCredentialsCallService extends ExecutionVaultCredentialsCallServiceBase {
private readonly http = inject(HttpClient);
override listForProvider(provider: string): Observable<ExecutionVaultCredential[]> {
const params = new HttpParams().set('provider', provider);
return this.http.get<unknown>(
`${environment.apiUrl}/secure-retriever/UserSecrets/forProvider/items`,
{ params }
).pipe(map((raw) => normalizeCredentials(raw)));
}
}
function normalizeCredentials(raw: unknown): ExecutionVaultCredential[] {
const items = Array.isArray(raw)
? raw
: raw && typeof raw === 'object'
? ((raw as Record<string, unknown>)['items'] ?? (raw as Record<string, unknown>)['values'] ?? [])
: [];
if (!Array.isArray(items)) return [];
return items.map((item) => {
const value = item && typeof item === 'object' ? item as Record<string, unknown> : {};
const descriptor = value['descriptor'] && typeof value['descriptor'] === 'object'
? value['descriptor'] as Record<string, unknown>
: {};
const meta = descriptor['meta'] && typeof descriptor['meta'] === 'object'
? descriptor['meta'] as Record<string, unknown>
: {};
return {
id: String(value['data'] ?? ''),
label: String(descriptor['label'] ?? 'Credential'),
description: typeof descriptor['description'] === 'string' ? descriptor['description'] : undefined,
provider: String(meta['provider'] ?? '')
};
}).filter((item) => item.id.length > 0);
}

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { environment } from '@environment';
import { ExecutionVaultCredential } from '@models/llm-provider';
import { Observable } from 'rxjs';
import { ExecutionVaultCredentialsCallServiceBase } from './execution-vault-credentials-call.base';
@Injectable({ providedIn: 'root' })
export class ExecutionVaultCredentialsService {
executionVaultCredentialsCallService: ExecutionVaultCredentialsCallServiceBase =
new environment.executionVaultCredentialsCallService();
listForProvider(provider: string): Observable<ExecutionVaultCredential[]> {
return this.executionVaultCredentialsCallService.listForProvider(provider);
}
}

View File

@ -0,0 +1,6 @@
import { LlmProviderCapability } from '@models/llm-provider';
import { Observable } from 'rxjs';
export abstract class LlmProviderCallServiceBase {
abstract listCapabilities(): Observable<LlmProviderCapability[]>;
}

View File

@ -0,0 +1,13 @@
import { LlmProviderCapability } from '@models/llm-provider';
import { Observable, of } from 'rxjs';
import { LlmProviderCallServiceBase } from './llm-provider-call.base';
export class LlmProviderCallServiceFake extends LlmProviderCallServiceBase {
override listCapabilities(): Observable<LlmProviderCapability[]> {
return of([
{ name: 'InternalOllama', requiresCredential: false },
{ name: 'testProvider', requiresCredential: true },
{ name: 'Gemini', requiresCredential: true }
]);
}
}

View File

@ -0,0 +1,33 @@
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { environment } from '@environment';
import { firstValueFrom } from 'rxjs';
import { LlmProviderCallService } from './llm-provider-call';
describe('LlmProviderCallService', () => {
let service: LlmProviderCallService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [LlmProviderCallService, provideHttpClient(), provideHttpClientTesting()]
});
service = TestBed.inject(LlmProviderCallService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify());
it('maps provider capabilities from the backend', async () => {
const result = firstValueFrom(service.listCapabilities());
httpMock.expectOne(`${environment.apiUrl}/llm/providers`).flush([
{ name: 'InternalOllama', requiresCredential: false },
{ name: 'OpenAI', requiresCredential: true }
]);
await expect(result).resolves.toEqual([
{ name: 'InternalOllama', requiresCredential: false },
{ name: 'OpenAI', requiresCredential: true }
]);
});
});

View File

@ -0,0 +1,25 @@
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { environment } from '@environment';
import { LlmProviderCapability } from '@models/llm-provider';
import { map, Observable } from 'rxjs';
import { LlmProviderCallServiceBase } from './llm-provider-call.base';
export class LlmProviderCallService extends LlmProviderCallServiceBase {
private readonly http = inject(HttpClient);
override listCapabilities(): Observable<LlmProviderCapability[]> {
return this.http.get<unknown>(`${environment.apiUrl}/llm/providers`).pipe(
map((raw) => {
if (!Array.isArray(raw)) return [];
return raw
.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object')
.map((item) => ({
name: String(item['name'] ?? '').trim(),
requiresCredential: item['requiresCredential'] === true
}))
.filter((item) => item.name.length > 0);
})
);
}
}

View File

@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { environment } from '@environment';
import { LlmProviderCapability } from '@models/llm-provider';
import { Observable } from 'rxjs';
import { LlmProviderCallServiceBase } from './llm-provider-call.base';
@Injectable({ providedIn: 'root' })
export class LlmProviderService {
llmProviderCallService: LlmProviderCallServiceBase = new environment.llmProviderCallService();
listCapabilities(): Observable<LlmProviderCapability[]> {
return this.llmProviderCallService.listCapabilities();
}
}

View File

@ -12,6 +12,7 @@ describe('extractHttpErrorMessage', () => {
expect(extractHttpErrorMessage(new HttpErrorResponse({ error: { message: 'msg' }, status: 400 }))).toBe('msg');
expect(extractHttpErrorMessage(new HttpErrorResponse({ error: { error: 'err' }, status: 400 }))).toBe('err');
expect(extractHttpErrorMessage(new HttpErrorResponse({ error: { details: 'det' }, status: 400 }))).toBe('det');
expect(extractHttpErrorMessage(new HttpErrorResponse({ error: { detail: 'problem detail' }, status: 400 }))).toBe('problem detail');
});
it('returns null when nothing usable is present', () => {

View File

@ -3,7 +3,7 @@ import { Observable, throwError } from 'rxjs';
/**
* Reads a human-readable message out of a backend error body, trying the
* conventional `message`/`error`/`details` string fields in that order.
* conventional `message`/`error`/`details`/`detail` string fields in that order.
*/
export function extractHttpErrorMessage(error: HttpErrorResponse): string | null {
const payload = error.error;
@ -24,6 +24,11 @@ export function extractHttpErrorMessage(error: HttpErrorResponse): string | null
if (typeof details === 'string' && details.trim().length > 0) {
return details.trim();
}
// RFC 7807 ProblemDetail, which the execution endpoints answer with.
const detail = record['detail'];
if (typeof detail === 'string' && detail.trim().length > 0) {
return detail.trim();
}
}
return null;
}

View File

@ -0,0 +1,9 @@
/**
* Copy shared by every caller that talks to a credential endpoint, so the vault
* and the assistant report the same statuses the same way.
*/
export const CREDENTIAL_ERROR_MESSAGES: Record<number, string> = {
400: 'The credential is invalid, inactive, or not compatible with the selected provider.',
401: 'You must sign in to use or manage credentials.',
409: 'This provider does not support user credentials.'
};

View File

@ -0,0 +1,31 @@
import { VaultSecret, VaultSecretCreateRequest, VaultSecretUpdateRequest } from '@models/assistant';
import { Observable } from 'rxjs';
export abstract class VaultCallServiceBase {
abstract listSecrets(): Observable<VaultSecret[]>;
abstract createSecret(request: VaultSecretCreateRequest): Observable<VaultSecret>;
abstract updateSecret(id: string, request: VaultSecretUpdateRequest): Observable<VaultSecret>;
abstract deleteSecret(id: string): Observable<void>;
}
export function mapVaultSecrets(raw: unknown): VaultSecret[] {
const items = Array.isArray(raw)
? raw
: raw && typeof raw === 'object'
? ((raw as Record<string, unknown>)['items'] ?? (raw as Record<string, unknown>)['values'] ?? [])
: [];
return Array.isArray(items) ? items.map(mapVaultSecret).filter((item) => !!item.id) : [];
}
export function mapVaultSecret(raw: unknown): VaultSecret {
const value = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>;
return {
id: String(value['id'] ?? value['secretId'] ?? ''),
label: String(value['label'] ?? ''),
provider: String(value['provider'] ?? ''),
description: typeof value['description'] === 'string' ? value['description'] : undefined,
active: value['active'] !== false && value['enabled'] !== false,
lastUsedAt: typeof value['lastUsedAt'] === 'string' ? value['lastUsedAt'] : undefined,
maskedPreview: typeof value['maskedPreview'] === 'string' ? value['maskedPreview'] : undefined
};
}

View File

@ -0,0 +1,28 @@
import { VaultSecret, VaultSecretCreateRequest, VaultSecretUpdateRequest } from '@models/assistant';
import { Observable, of, throwError } from 'rxjs';
import { VaultCallServiceBase } from './vault-call.base';
import { vaultFakeStore } from './vault-fake-store';
export class VaultCallServiceFake extends VaultCallServiceBase {
override listSecrets(): Observable<VaultSecret[]> {
return of(vaultFakeStore.list());
}
override createSecret(request: VaultSecretCreateRequest): Observable<VaultSecret> {
if (!request.value.trim()) return throwError(() => new Error('The credential value is required.'));
return of(vaultFakeStore.create(request));
}
override updateSecret(id: string, request: VaultSecretUpdateRequest): Observable<VaultSecret> {
try {
return of(vaultFakeStore.update(id, request));
} catch (error) {
return throwError(() => error);
}
}
override deleteSecret(id: string): Observable<void> {
vaultFakeStore.remove(id);
return of(void 0);
}
}

View File

@ -0,0 +1,66 @@
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { environment } from '@environment';
import { firstValueFrom } from 'rxjs';
import { CREDENTIAL_ERROR_MESSAGES } from './credential-error-messages';
import { VaultCallService } from './vault-call';
describe('VaultCallService', () => {
let service: VaultCallService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [VaultCallService, provideHttpClient(), provideHttpClientTesting()]
});
service = TestBed.inject(VaultCallService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify());
it('maps secret metadata without exposing a value', async () => {
const result = firstValueFrom(service.listSecrets());
httpMock.expectOne(`${environment.apiUrl}/vault/secrets`).flush([{
id: 'credential-1',
label: 'OpenAI key',
provider: 'OpenAI',
active: true,
maskedPreview: '***',
value: 'must-not-reach-the-ui'
}]);
await expect(result).resolves.toEqual([{
id: 'credential-1',
label: 'OpenAI key',
provider: 'OpenAI',
active: true,
maskedPreview: '***'
}]);
});
it('sends a value only on explicit create or rotation requests', async () => {
const create = firstValueFrom(service.createSecret({
label: 'OpenAI key', provider: 'OpenAI', value: 'secret-value'
}));
const createRequest = httpMock.expectOne(`${environment.apiUrl}/vault/secrets`);
expect(createRequest.request.body).toEqual({ label: 'OpenAI key', provider: 'OpenAI', value: 'secret-value' });
createRequest.flush({ id: 'credential-1', label: 'OpenAI key', provider: 'OpenAI', active: true });
await create;
const update = firstValueFrom(service.updateSecret('credential-1', { value: 'rotated-value' }));
const updateRequest = httpMock.expectOne(`${environment.apiUrl}/vault/secrets/credential-1`);
expect(updateRequest.request.body).toEqual({ value: 'rotated-value' });
updateRequest.flush({ id: 'credential-1', label: 'OpenAI key', provider: 'OpenAI', active: true });
await update;
});
it('falls back to the shared credential message when the backend sends no body', async () => {
const result = firstValueFrom(service.deleteSecret('credential-1'));
httpMock.expectOne(`${environment.apiUrl}/vault/secrets/credential-1`)
.flush(null, { status: 409, statusText: 'Conflict' });
await expect(result).rejects.toThrow(CREDENTIAL_ERROR_MESSAGES[409]);
});
});

View File

@ -0,0 +1,47 @@
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { environment } from '@environment';
import { VaultSecret, VaultSecretCreateRequest, VaultSecretUpdateRequest } from '@models/assistant';
import { extractHttpErrorMessage } from '@services/shared/http-error.util';
import { catchError, map, Observable, throwError } from 'rxjs';
import { CREDENTIAL_ERROR_MESSAGES } from './credential-error-messages';
import { mapVaultSecret, mapVaultSecrets, VaultCallServiceBase } from './vault-call.base';
export class VaultCallService extends VaultCallServiceBase {
private readonly http = inject(HttpClient);
override listSecrets(): Observable<VaultSecret[]> {
return this.http.get<unknown>(`${environment.apiUrl}/vault/secrets`).pipe(
map((raw) => mapVaultSecrets(raw)),
catchError((error: unknown) => this.vaultError(error))
);
}
override createSecret(request: VaultSecretCreateRequest): Observable<VaultSecret> {
return this.http.post<unknown>(`${environment.apiUrl}/vault/secrets`, request).pipe(
map((raw) => mapVaultSecret(raw)),
catchError((error: unknown) => this.vaultError(error))
);
}
override updateSecret(id: string, request: VaultSecretUpdateRequest): Observable<VaultSecret> {
return this.http.put<unknown>(`${environment.apiUrl}/vault/secrets/${encodeURIComponent(id)}`, request).pipe(
map((raw) => mapVaultSecret(raw)),
catchError((error: unknown) => this.vaultError(error))
);
}
override deleteSecret(id: string): Observable<void> {
return this.http.delete<void>(`${environment.apiUrl}/vault/secrets/${encodeURIComponent(id)}`).pipe(
catchError((error: unknown) => this.vaultError(error))
);
}
private vaultError(error: unknown): Observable<never> {
const response = error as { status?: unknown; error?: unknown };
const message = extractHttpErrorMessage(response as any)
?? CREDENTIAL_ERROR_MESSAGES[Number(response?.status)]
?? 'Unable to update provider credentials.';
return throwError(() => new Error(message));
}
}

View File

@ -0,0 +1,32 @@
import { ExecutionVaultCredentialsCallServiceFake } from '@services/llm-provider/execution-vault-credentials-call.fake';
import { firstValueFrom } from 'rxjs';
import { VaultCallServiceFake } from './vault-call.fake';
import { vaultFakeStore } from './vault-fake-store';
describe('vault fake store', () => {
const vault = new VaultCallServiceFake();
const credentials = new ExecutionVaultCredentialsCallServiceFake();
it('offers only active credentials of the requested provider to an execution', async () => {
const listed = await firstValueFrom(credentials.listForProvider('testProvider'));
expect(listed.map((item) => item.id)).toEqual([
'vault-secret-testprovider-1',
'vault-secret-testprovider-2'
]);
expect(vaultFakeStore.list().some((secret) => !secret.active)).toBe(true);
});
it('shows a credential created through the vault in the execution picker', async () => {
const created = await firstValueFrom(vault.createSecret({
label: 'testProvider - fresh key', provider: 'testProvider', value: 'sk-brand-new'
}));
const listed = await firstValueFrom(credentials.listForProvider('testProvider'));
expect(listed.map((item) => item.id)).toContain(created.id);
await firstValueFrom(vault.deleteSecret(created.id));
const afterDelete = await firstValueFrom(credentials.listForProvider('testProvider'));
expect(afterDelete.map((item) => item.id)).not.toContain(created.id);
});
});

View File

@ -0,0 +1,85 @@
import { VaultSecret, VaultSecretCreateRequest, VaultSecretUpdateRequest } from '@models/assistant';
/**
* In-memory vault shared by the fake call services, so a credential created from
* the assistant panel shows up in the execution credential picker, exactly as it
* does against the real backend.
*/
class VaultFakeStore {
private sequence = 0;
private readonly secrets: VaultSecret[] = [
{
id: 'vault-secret-testprovider-1',
label: 'testProvider - team key',
provider: 'testProvider',
description: 'Shared key for the demo workspace',
active: true,
maskedPreview: 'sk-...4f2a'
},
{
id: 'vault-secret-testprovider-2',
label: 'testProvider - personal key',
provider: 'testProvider',
active: true,
maskedPreview: 'sk-...91cd'
},
{
id: 'vault-secret-testprovider-3',
label: 'testProvider - revoked key',
provider: 'testProvider',
description: 'Disabled, never offered to an execution',
active: false,
maskedPreview: 'sk-...0007'
},
{
id: 'vault-secret-gemini-1',
label: 'Gemini - default key',
provider: 'Gemini',
active: true,
maskedPreview: 'AI...b31'
}
];
list(): VaultSecret[] {
return this.secrets.map((secret) => ({ ...secret }));
}
/** Mirrors the server-side filter of the credential listing: owner, active, provider. */
listActiveByProvider(provider: string): VaultSecret[] {
const wanted = provider.trim().toLowerCase();
return this.secrets
.filter((secret) => secret.active && secret.provider.trim().toLowerCase() === wanted)
.map((secret) => ({ ...secret }));
}
create(request: VaultSecretCreateRequest): VaultSecret {
const created: VaultSecret = {
id: `vault-secret-created-${++this.sequence}`,
label: request.label,
provider: request.provider,
description: request.description,
active: true,
maskedPreview: `${request.value.slice(0, 2)}...${request.value.slice(-3)}`
};
this.secrets.push(created);
return { ...created };
}
update(id: string, request: VaultSecretUpdateRequest): VaultSecret {
const secret = this.secrets.find((item) => item.id === id);
if (!secret) throw new Error('The credential no longer exists.');
if (request.label !== undefined) secret.label = request.label;
if (request.description !== undefined) secret.description = request.description;
if (request.active !== undefined) secret.active = request.active;
if (request.value) secret.maskedPreview = `${request.value.slice(0, 2)}...${request.value.slice(-3)}`;
return { ...secret };
}
/** The backend soft-deletes, so the secret stays but stops being offered. */
remove(id: string): void {
const secret = this.secrets.find((item) => item.id === id);
if (secret) secret.active = false;
}
}
export const vaultFakeStore = new VaultFakeStore();

View File

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { environment } from '@environment';
import { VaultSecret, VaultSecretCreateRequest, VaultSecretUpdateRequest } from '@models/assistant';
import { Observable } from 'rxjs';
import { VaultCallServiceBase } from './vault-call.base';
export { CREDENTIAL_ERROR_MESSAGES } from './credential-error-messages';
@Injectable({ providedIn: 'root' })
export class VaultService {
vaultCallService: VaultCallServiceBase = new environment.vaultCallService();
listSecrets(): Observable<VaultSecret[]> {
return this.vaultCallService.listSecrets();
}
createSecret(request: VaultSecretCreateRequest): Observable<VaultSecret> {
return this.vaultCallService.createSecret(request);
}
updateSecret(id: string, request: VaultSecretUpdateRequest): Observable<VaultSecret> {
return this.vaultCallService.updateSecret(id, request);
}
deleteSecret(id: string): Observable<void> {
return this.vaultCallService.deleteSecret(id);
}
}

View File

@ -3,6 +3,9 @@ import { AssistantCallServiceFake } from "@services/assistant/assistant-call.fak
import { AuthorizationCallFakeService } from "@services/authorization/authorization-call.fake";
import { BlocksCallServiceFake } from "@services/blocks/blocks-call.fake";
import { ContainersCallServiceFake } from "@services/containers/containers-call.fake";
import { ExecutionVaultCredentialsCallServiceFake } from "@services/llm-provider/execution-vault-credentials-call.fake";
import { LlmProviderCallServiceFake } from "@services/llm-provider/llm-provider-call.fake";
import { VaultCallServiceFake } from "@services/vault/vault-call.fake";
import { FlowsCallServiceFake } from "@services/flows/flows-call.fake";
import { FieldRetrieverCallServiceFake } from "@services/retriever/field-retriever-call.fake";
import { TaskExecutionsCallServiceFake } from "@services/task-executions/task-executions-call.fake";
@ -20,5 +23,8 @@ export const environment = {
blocksCallService: BlocksCallServiceFake,
containersCallService: ContainersCallServiceFake,
fieldRetrieverCallService: FieldRetrieverCallServiceFake,
taskExecutionsCallService: TaskExecutionsCallServiceFake
taskExecutionsCallService: TaskExecutionsCallServiceFake,
llmProviderCallService: LlmProviderCallServiceFake,
executionVaultCredentialsCallService: ExecutionVaultCredentialsCallServiceFake,
vaultCallService: VaultCallServiceFake
};

View File

@ -3,6 +3,9 @@ import { AssistantCallService } from "@services/assistant/assistant-call";
import { AuthorizationCallService } from "@services/authorization/authorization-call";
import { BlocksCallService } from "@services/blocks/blocks-call";
import { ContainersCallService } from "@services/containers/containers-call";
import { ExecutionVaultCredentialsCallService } from "@services/llm-provider/execution-vault-credentials-call";
import { LlmProviderCallService } from "@services/llm-provider/llm-provider-call";
import { VaultCallService } from "@services/vault/vault-call";
import { FlowsCallService } from "@services/flows/flows-call";
import { FieldRetrieverCallService } from "@services/retriever/field-retriever-call";
import { TaskExecutionsCallService } from "@services/task-executions/task-executions-call";
@ -20,5 +23,8 @@ export const environment = {
blocksCallService: BlocksCallService,
containersCallService: ContainersCallService,
fieldRetrieverCallService: FieldRetrieverCallService,
taskExecutionsCallService: TaskExecutionsCallService
taskExecutionsCallService: TaskExecutionsCallService,
llmProviderCallService: LlmProviderCallService,
executionVaultCredentialsCallService: ExecutionVaultCredentialsCallService,
vaultCallService: VaultCallService
};

View File

@ -3,6 +3,9 @@ import { AssistantCallService } from "@services/assistant/assistant-call";
import { AuthorizationCallService } from "@services/authorization/authorization-call";
import { BlocksCallService } from "@services/blocks/blocks-call";
import { ContainersCallService } from "@services/containers/containers-call";
import { ExecutionVaultCredentialsCallService } from "@services/llm-provider/execution-vault-credentials-call";
import { LlmProviderCallService } from "@services/llm-provider/llm-provider-call";
import { VaultCallService } from "@services/vault/vault-call";
import { FlowsCallService } from "@services/flows/flows-call";
import { FieldRetrieverCallService } from "@services/retriever/field-retriever-call";
import { TaskExecutionsCallService } from "@services/task-executions/task-executions-call";
@ -20,5 +23,8 @@ export const environment = {
blocksCallService: BlocksCallService,
containersCallService: ContainersCallService,
fieldRetrieverCallService: FieldRetrieverCallService,
taskExecutionsCallService: TaskExecutionsCallService
taskExecutionsCallService: TaskExecutionsCallService,
llmProviderCallService: LlmProviderCallService,
executionVaultCredentialsCallService: ExecutionVaultCredentialsCallService,
vaultCallService: VaultCallService
};