diff --git a/apps/server/src/express.d.ts b/apps/server/src/express.d.ts
index f2cb77c78..781c6db55 100644
--- a/apps/server/src/express.d.ts
+++ b/apps/server/src/express.d.ts
@@ -1,14 +1,7 @@
-import { Session } from "express-session";
+import type { SessionData } from "express-session";
export declare module "express-serve-static-core" {
interface Request {
- session: Session & {
- loggedIn: boolean;
- lastAuthState: {
- totpEnabled: boolean;
- ssoEnabled: boolean;
- };
- };
headers: {
"x-local-date"?: string;
"x-labels"?: string;
@@ -25,3 +18,13 @@ export declare module "express-serve-static-core" {
};
}
}
+
+export declare module "express-session" {
+ interface SessionData {
+ loggedIn: boolean;
+ lastAuthState: {
+ totpEnabled: boolean;
+ ssoEnabled: boolean;
+ };
+ }
+}
diff --git a/apps/server/src/routes/api/embeddings.ts b/apps/server/src/routes/api/embeddings.ts
index 012a9c82f..226231ea2 100644
--- a/apps/server/src/routes/api/embeddings.ts
+++ b/apps/server/src/routes/api/embeddings.ts
@@ -408,7 +408,7 @@ async function reprocessAllNotes(req: Request, res: Response) {
try {
// Wrap the operation in cls.init to ensure proper context
cls.init(async () => {
- await vectorStore.reprocessAllNotes();
+ await indexService.reprocessAllNotes();
log.info("Embedding reprocessing completed successfully");
});
} catch (error: any) {
@@ -782,6 +782,49 @@ async function getIndexRebuildStatus(req: Request, res: Response) {
};
}
+/**
+ * Start embedding generation when AI is enabled
+ */
+async function startEmbeddings(req: Request, res: Response) {
+ try {
+ log.info("Starting embedding generation system");
+
+ // Initialize the index service if not already initialized
+ await indexService.initialize();
+
+ // Start automatic indexing
+ await indexService.startEmbeddingGeneration();
+
+ return {
+ success: true,
+ message: "Embedding generation started"
+ };
+ } catch (error: any) {
+ log.error(`Error starting embeddings: ${error.message || 'Unknown error'}`);
+ throw new Error(`Failed to start embeddings: ${error.message || 'Unknown error'}`);
+ }
+}
+
+/**
+ * Stop embedding generation when AI is disabled
+ */
+async function stopEmbeddings(req: Request, res: Response) {
+ try {
+ log.info("Stopping embedding generation system");
+
+ // Stop automatic indexing
+ await indexService.stopEmbeddingGeneration();
+
+ return {
+ success: true,
+ message: "Embedding generation stopped"
+ };
+ } catch (error: any) {
+ log.error(`Error stopping embeddings: ${error.message || 'Unknown error'}`);
+ throw new Error(`Failed to stop embeddings: ${error.message || 'Unknown error'}`);
+ }
+}
+
export default {
findSimilarNotes,
searchByText,
@@ -794,5 +837,7 @@ export default {
retryFailedNote,
retryAllFailedNotes,
rebuildIndex,
- getIndexRebuildStatus
+ getIndexRebuildStatus,
+ startEmbeddings,
+ stopEmbeddings
};
diff --git a/apps/server/src/routes/api/llm.ts b/apps/server/src/routes/api/llm.ts
index c21426a66..f586b85d6 100644
--- a/apps/server/src/routes/api/llm.ts
+++ b/apps/server/src/routes/api/llm.ts
@@ -825,7 +825,10 @@ async function streamMessage(req: Request, res: Response) {
success: true,
message: 'Streaming initiated successfully'
});
- log.info(`Sent immediate success response for streaming setup`);
+
+ // Mark response as handled to prevent apiResultHandler from processing it again
+ (res as any).triliumResponseHandled = true;
+
// Create a new response object for streaming through WebSocket only
// We won't use HTTP streaming since we've already sent the HTTP response
@@ -889,78 +892,33 @@ async function streamMessage(req: Request, res: Response) {
thinking: showThinking ? 'Initializing streaming LLM response...' : undefined
});
- // Instead of trying to reimplement the streaming logic ourselves,
- // delegate to restChatService but set up the correct protocol:
- // 1. We've already sent a success response to the initial POST
- // 2. Now we'll have restChatService process the actual streaming through WebSocket
+ // Process the LLM request using the existing service but with streaming setup
+ // Since we've already sent the initial HTTP response, we'll use the WebSocket for streaming
try {
- // Import the WebSocket service for sending messages
- const wsService = (await import('../../services/ws.js')).default;
-
- // Create a simple pass-through response object that won't write to the HTTP response
- // but will allow restChatService to send WebSocket messages
- const dummyResponse = {
- writableEnded: false,
- // Implement methods that would normally be used by restChatService
- write: (_chunk: string) => {
- // Silent no-op - we're only using WebSocket
- return true;
+ // Call restChatService with streaming mode enabled
+ // The important part is setting method to GET to indicate streaming mode
+ await restChatService.handleSendMessage({
+ ...req,
+ method: 'GET', // Indicate streaming mode
+ query: {
+ ...req.query,
+ stream: 'true' // Add the required stream parameter
},
- end: (_chunk?: string) => {
- // Log when streaming is complete via WebSocket
- log.info(`[${chatNoteId}] Completed HTTP response handling during WebSocket streaming`);
- return dummyResponse;
+ body: {
+ content: enhancedContent,
+ useAdvancedContext: useAdvancedContext === true,
+ showThinking: showThinking === true
},
- setHeader: (name: string, _value: string) => {
- // Only log for content-type to reduce noise
- if (name.toLowerCase() === 'content-type') {
- log.info(`[${chatNoteId}] Setting up streaming for WebSocket only`);
- }
- return dummyResponse;
- }
- };
+ params: { chatNoteId }
+ } as unknown as Request, res);
+ } catch (streamError) {
+ log.error(`Error during WebSocket streaming: ${streamError}`);
- // Process the streaming now through WebSocket only
- try {
- log.info(`[${chatNoteId}] Processing LLM streaming through WebSocket after successful initiation at ${new Date().toISOString()}`);
-
- // Call restChatService with our enhanced request and dummy response
- // The important part is setting method to GET to indicate streaming mode
- await restChatService.handleSendMessage({
- ...req,
- method: 'GET', // Indicate streaming mode
- query: {
- ...req.query,
- stream: 'true' // Add the required stream parameter
- },
- body: {
- content: enhancedContent,
- useAdvancedContext: useAdvancedContext === true,
- showThinking: showThinking === true
- },
- params: { chatNoteId }
- } as unknown as Request, dummyResponse as unknown as Response);
-
- log.info(`[${chatNoteId}] WebSocket streaming completed at ${new Date().toISOString()}`);
- } catch (streamError) {
- log.error(`[${chatNoteId}] Error during WebSocket streaming: ${streamError}`);
-
- // Send error message through WebSocket
- wsService.sendMessageToAllClients({
- type: 'llm-stream',
- chatNoteId: chatNoteId,
- error: `Error during streaming: ${streamError}`,
- done: true
- });
- }
- } catch (error) {
- log.error(`Error during streaming: ${error}`);
-
- // Send error to client via WebSocket
+ // Send error message through WebSocket
wsService.sendMessageToAllClients({
type: 'llm-stream',
chatNoteId: chatNoteId,
- error: `Error processing message: ${error}`,
+ error: `Error during streaming: ${streamError}`,
done: true
});
}
diff --git a/apps/server/src/routes/api/openai.ts b/apps/server/src/routes/api/openai.ts
index ced03ce04..84efad2ca 100644
--- a/apps/server/src/routes/api/openai.ts
+++ b/apps/server/src/routes/api/openai.ts
@@ -66,12 +66,13 @@ async function listModels(req: Request, res: Response) {
const apiKey = await options.getOption('openaiApiKey');
if (!apiKey) {
- throw new Error('OpenAI API key is not configured');
+ // Log warning but don't throw - some OpenAI-compatible endpoints don't require API keys
+ log.info('OpenAI API key is not configured when listing models. This may cause issues with official OpenAI endpoints.');
}
- // Initialize OpenAI client with the API key and base URL
+ // Initialize OpenAI client with the API key (or empty string) and base URL
const openai = new OpenAI({
- apiKey,
+ apiKey: apiKey || '', // Default to empty string if no API key
baseURL: openaiBaseUrl
});
@@ -84,9 +85,9 @@ async function listModels(req: Request, res: Response) {
// Include all models as chat models, without filtering by specific model names
// This allows models from providers like OpenRouter to be displayed
const chatModels = allModels
- .filter((model) =>
+ .filter((model) =>
// Exclude models that are explicitly for embeddings
- !model.id.includes('embedding') &&
+ !model.id.includes('embedding') &&
!model.id.includes('embed')
)
.map((model) => ({
diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts
index 42d4fb110..022b4514e 100644
--- a/apps/server/src/routes/api/options.ts
+++ b/apps/server/src/routes/api/options.ts
@@ -96,22 +96,26 @@ const ALLOWED_OPTIONS = new Set
([
"aiEnabled",
"aiTemperature",
"aiSystemPrompt",
- "aiProviderPrecedence",
+ "aiSelectedProvider",
"openaiApiKey",
"openaiBaseUrl",
"openaiDefaultModel",
"openaiEmbeddingModel",
+ "openaiEmbeddingApiKey",
+ "openaiEmbeddingBaseUrl",
"anthropicApiKey",
"anthropicBaseUrl",
"anthropicDefaultModel",
"voyageApiKey",
"voyageEmbeddingModel",
+ "voyageEmbeddingBaseUrl",
"ollamaBaseUrl",
"ollamaDefaultModel",
"ollamaEmbeddingModel",
+ "ollamaEmbeddingBaseUrl",
"embeddingAutoUpdateEnabled",
"embeddingDimensionStrategy",
- "embeddingProviderPrecedence",
+ "embeddingSelectedProvider",
"embeddingSimilarityThreshold",
"embeddingBatchSize",
"embeddingUpdateInterval",
diff --git a/apps/server/src/routes/login.spec.ts b/apps/server/src/routes/login.spec.ts
index 99984dbda..69e2cff6a 100644
--- a/apps/server/src/routes/login.spec.ts
+++ b/apps/server/src/routes/login.spec.ts
@@ -1,15 +1,21 @@
import { beforeAll, describe, expect, it } from "vitest";
-import supertest from "supertest";
+import supertest, { type Response } from "supertest";
import type { Application } from "express";
import dayjs from "dayjs";
+import { type SQLiteSessionStore } from "./session_parser.js";
+import { SessionData } from "express-session";
let app: Application;
+let sessionStore: SQLiteSessionStore;
+let CLEAN_UP_INTERVAL: number;
describe("Login Route test", () => {
beforeAll(async () => {
+ vi.useFakeTimers();
const buildApp = (await import("../app.js")).default;
app = await buildApp();
+ ({ sessionStore, CLEAN_UP_INTERVAL } = (await import("./session_parser.js")));
});
it("should return the login page, when using a GET request", async () => {
@@ -32,58 +38,171 @@ describe("Login Route test", () => {
});
-
- it("sets correct Expires, when 'Remember Me' is ticked", async () => {
-
+ describe("Login when 'Remember Me' is ticked", async () => {
// TriliumNextTODO: make setting cookieMaxAge via env variable work
// => process.env.TRILIUM_SESSION_COOKIEMAXAGE
// the custom cookieMaxAge is currently hardocded in the test data dir's config.ini
- const CUSTOM_MAX_AGE_SECONDS = 86400;
- const expectedExpiresDate = dayjs().utc().add(CUSTOM_MAX_AGE_SECONDS, "seconds").toDate().toUTCString();
+ let res: Response;
+ let setCookieHeader: string;
+ let expectedExpiresDate: string;
- const res = await supertest(app)
- .post("/login")
- .send({ password: "demo1234", rememberMe: 1 })
- .expect(302)
+ beforeAll(async () => {
+ const CUSTOM_MAX_AGE_SECONDS = 86400;
- const setCookieHeader = res.headers["set-cookie"][0];
+ expectedExpiresDate = dayjs().utc().add(CUSTOM_MAX_AGE_SECONDS, "seconds").toDate().toUTCString();
+ res = await supertest(app)
+ .post("/login")
+ .send({ password: "demo1234", rememberMe: 1 })
+ .expect(302);
+ setCookieHeader = res.headers["set-cookie"][0];
+ });
- // match for e.g. "Expires=Wed, 07 May 2025 07:02:59 GMT;"
- const expiresCookieRegExp = /Expires=(?[\w\s,:]+)/;
- const expiresCookieMatch = setCookieHeader.match(expiresCookieRegExp);
- const actualExpiresDate = new Date(expiresCookieMatch?.groups?.date || "").toUTCString()
+ it("sets correct Expires for the cookie", async () => {
+ // match for e.g. "Expires=Wed, 07 May 2025 07:02:59 GMT;"
+ const expiresCookieRegExp = /Expires=(?[\w\s,:]+)/;
+ const expiresCookieMatch = setCookieHeader.match(expiresCookieRegExp);
+ const actualExpiresDate = new Date(expiresCookieMatch?.groups?.date || "").toUTCString();
- expect(actualExpiresDate).to.not.eql("Invalid Date");
+ expect(actualExpiresDate).to.not.eql("Invalid Date");
- // ignore the seconds in the comparison, just to avoid flakiness in tests,
- // if for some reason execution is slow between calculation of expected and actual
- expect(actualExpiresDate.slice(0,23)).toBe(expectedExpiresDate.slice(0,23))
+ // ignore the seconds in the comparison, just to avoid flakiness in tests,
+ // if for some reason execution is slow between calculation of expected and actual
+ expect(actualExpiresDate.slice(0,23)).toBe(expectedExpiresDate.slice(0,23))
+ });
- }, 10_000);
- // use 10 sec (10_000 ms) timeout for now, instead of default 5 sec to work around
- // failing CI, because for some reason it currently takes approx. 6 secs to run
- // TODO: actually identify what is causing this and fix the flakiness
+ it("sets the correct sesssion data", async () => {
+ // Check the session is stored in the database.
+ const { session, expiry } = await getSessionFromCookie(setCookieHeader);
+ expect(session!).toBeTruthy();
+ expect(session!.cookie.expires).toBeTruthy();
+ expect(new Date(session!.cookie.expires!).toUTCString().substring(0, 23))
+ .toBe(expectedExpiresDate.substring(0, 23));
+ expect(session!.loggedIn).toBe(true);
+ expect(expiry).toStrictEqual(new Date(session!.cookie.expires!));
+ });
+ it("doesn't renew the session on subsequent requests", async () => {
+ const { expiry: originalExpiry } = await getSessionFromCookie(setCookieHeader);
- it("does not set Expires, when 'Remember Me' is not ticked", async () => {
+ // Simulate user waiting half the period before the session expires.
+ vi.setSystemTime(originalExpiry!.getTime() - (originalExpiry!.getTime() - Date.now()) / 2);
- const res = await supertest(app)
- .post("/login")
- .send({ password: "demo1234" })
- .expect(302)
+ // Make a request to renew the session.
+ await supertest(app)
+ .get("/")
+ .set("Cookie", setCookieHeader)
+ .expect(200);
- const setCookieHeader = res.headers["set-cookie"][0];
+ // Check the session is still valid and has not been renewed.
+ const { session, expiry } = await getSessionFromCookie(setCookieHeader);
+ expect(session).toBeTruthy();
+ expect(expiry!.getTime()).toStrictEqual(originalExpiry!.getTime());
+ });
- // match for e.g. "Expires=Wed, 07 May 2025 07:02:59 GMT;"
- const expiresCookieRegExp = /Expires=(?[\w\s,:]+)/;
- const expiresCookieMatch = setCookieHeader.match(expiresCookieRegExp);
- expect(expiresCookieMatch).toBeNull();
+ it("cleans up expired sessions", async () => {
+ let { session, expiry } = await getSessionFromCookie(setCookieHeader);
+ expect(session).toBeTruthy();
+ expect(expiry).toBeTruthy();
- }, 10_000);
- // use 10 sec (10_000 ms) timeout for now, instead of default 5 sec to work around
- // failing CI, because for some reason it currently takes approx. 6 secs to run
- // TODO: actually identify what is causing this and fix the flakiness
+ vi.setSystemTime(expiry!);
+ vi.advanceTimersByTime(CLEAN_UP_INTERVAL);
+ ({ session } = await getSessionFromCookie(setCookieHeader));
+ expect(session).toBeFalsy();
+ });
+ });
+ describe("Login when 'Remember Me' is not ticked", async () => {
+ let res: Response;
+ let setCookieHeader: string;
-});
+ beforeAll(async () => {
+ res = await supertest(app)
+ .post("/login")
+ .send({ password: "demo1234" })
+ .expect(302)
+
+ setCookieHeader = res.headers["set-cookie"][0];
+ });
+
+ it("does not set Expires", async () => {
+ // match for e.g. "Expires=Wed, 07 May 2025 07:02:59 GMT;"
+ expect(setCookieHeader).not.toMatch(/Expires=(?[\w\s,:]+)/)
+ });
+
+ it("stores the session in the database", async () => {
+ const { session, expiry } = await getSessionFromCookie(setCookieHeader);
+ expect(session!).toBeTruthy();
+ expect(session!.cookie.expires).toBeUndefined();
+ expect(session!.loggedIn).toBe(true);
+
+ const expectedExpirationDate = dayjs().utc().add(1, "day").toDate();
+ expect(expiry?.getTime()).toBeGreaterThan(new Date().getTime());
+ expect(expiry?.getTime()).toBeLessThanOrEqual(expectedExpirationDate.getTime());
+ });
+
+ it("renews the session on subsequent requests", async () => {
+ const { expiry: originalExpiry } = await getSessionFromCookie(setCookieHeader);
+
+ // Simulate user waiting half the period before the session expires.
+ vi.setSystemTime(originalExpiry!.getTime() - (originalExpiry!.getTime() - Date.now()) / 2);
+
+ // Make a request to renew the session.
+ await supertest(app)
+ .get("/")
+ .set("Cookie", setCookieHeader)
+ .expect(200);
+
+ // Check the session is still valid and has been renewed.
+ const { session, expiry } = await getSessionFromCookie(setCookieHeader);
+ expect(session).toBeTruthy();
+ expect(expiry!.getTime()).toBeGreaterThan(originalExpiry!.getTime());
+ });
+
+ it("keeps session up to 24 hours", async () => {
+ // Simulate user waiting 23 hours.
+ vi.setSystemTime(dayjs().add(23, "hours").toDate());
+ vi.advanceTimersByTime(CLEAN_UP_INTERVAL);
+
+ // Check the session is still valid.
+ const { session } = await getSessionFromCookie(setCookieHeader);
+ expect(session).toBeTruthy();
+ });
+
+ it("cleans up expired sessions", async () => {
+ let { session, expiry } = await getSessionFromCookie(setCookieHeader);
+ expect(session).toBeTruthy();
+ expect(expiry).toBeTruthy();
+
+ vi.setSystemTime(expiry!);
+ vi.advanceTimersByTime(CLEAN_UP_INTERVAL);
+ ({ session } = await getSessionFromCookie(setCookieHeader));
+ expect(session).toBeFalsy();
+ });
+ });
+}, 100_000);
+
+async function getSessionFromCookie(setCookieHeader: string) {
+ // Extract the session ID from the cookie.
+ const sessionIdMatch = setCookieHeader.match(/trilium.sid=(?[^;]+)/)?.[1];
+ expect(sessionIdMatch).toBeTruthy();
+
+ // Check the session is stored in the database.
+ const sessionId = decodeURIComponent(sessionIdMatch!).slice(2).split(".")[0];
+ return {
+ session: await getSessionFromStore(sessionId),
+ expiry: sessionStore.getSessionExpiry(sessionId)
+ };
+}
+
+function getSessionFromStore(sessionId: string) {
+ return new Promise((resolve, reject) => {
+ sessionStore.get(sessionId, (err, session) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(session);
+ }
+ });
+ });
+}
diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts
index d9ac2c2f8..73beb28e2 100644
--- a/apps/server/src/routes/routes.ts
+++ b/apps/server/src/routes/routes.ts
@@ -400,6 +400,8 @@ function register(app: express.Application) {
asyncApiRoute(PST, "/api/llm/embeddings/retry-all-failed", embeddingsRoute.retryAllFailedNotes);
asyncApiRoute(PST, "/api/llm/embeddings/rebuild-index", embeddingsRoute.rebuildIndex);
asyncApiRoute(GET, "/api/llm/embeddings/index-rebuild-status", embeddingsRoute.getIndexRebuildStatus);
+ asyncApiRoute(PST, "/api/llm/embeddings/start", embeddingsRoute.startEmbeddings);
+ asyncApiRoute(PST, "/api/llm/embeddings/stop", embeddingsRoute.stopEmbeddings);
// LLM provider endpoints - moved under /api/llm/providers hierarchy
asyncApiRoute(GET, "/api/llm/providers/ollama/models", ollamaRoute.listModels);
diff --git a/apps/server/src/routes/session_parser.ts b/apps/server/src/routes/session_parser.ts
index 240341ef7..b630b0905 100644
--- a/apps/server/src/routes/session_parser.ts
+++ b/apps/server/src/routes/session_parser.ts
@@ -5,7 +5,19 @@ import config from "../services/config.js";
import log from "../services/log.js";
import type express from "express";
-class SQLiteSessionStore extends Store {
+/**
+ * The amount of time in milliseconds after which expired sessions are cleaned up.
+ */
+export const CLEAN_UP_INTERVAL = 60 * 60 * 1000; // 1 hour
+
+/**
+ * The amount of time in milliseconds after which a session cookie expires if "Remember me" is not checked.
+ *
+ * Note that the session is renewed on each request, so the session will last up to this time from the last request.
+ */
+export const SESSION_COOKIE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours
+
+export class SQLiteSessionStore extends Store {
get(sid: string, callback: (err: any, session?: session.SessionData | null) => void): void {
try {
@@ -25,7 +37,7 @@ class SQLiteSessionStore extends Store {
try {
const expires = session.cookie?.expires
? new Date(session.cookie.expires).getTime()
- : Date.now() + 3600000; // fallback to 1 hour
+ : Date.now() + SESSION_COOKIE_EXPIRY;
const data = JSON.stringify(session);
sql.upsert("sessions", "id", {
@@ -50,19 +62,55 @@ class SQLiteSessionStore extends Store {
}
}
+ touch(sid: string, session: session.SessionData, callback?: (err?: any) => void): void {
+ // For now it's only for session cookies ("Remember me" unchecked).
+ if (session.cookie?.expires) {
+ callback?.();
+ return;
+ }
+
+ try {
+ const expires = Date.now() + SESSION_COOKIE_EXPIRY;
+ sql.execute(/*sql*/`UPDATE sessions SET expires = ? WHERE id = ?`, [expires, sid]);
+ callback?.();
+ } catch (e) {
+ log.error(e);
+ callback?.(e);
+ }
+ }
+
+ /**
+ * Given a session ID, returns the expiry date of the session.
+ *
+ * @param sid the session ID to check.
+ * @returns the expiry date of the session or null if the session does not exist.
+ */
+ getSessionExpiry(sid: string): Date | null {
+ try {
+ const expires = sql.getValue(/*sql*/`SELECT expires FROM sessions WHERE id = ?`, sid);
+ return expires !== undefined ? new Date(expires) : null;
+ } catch (e) {
+ log.error(e);
+ return null;
+ }
+ }
+
}
+export const sessionStore = new SQLiteSessionStore();
+
const sessionParser: express.RequestHandler = session({
secret: sessionSecret,
resave: false, // true forces the session to be saved back to the session store, even if the session was never modified during the request.
saveUninitialized: false, // true forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified.
+ rolling: true, // forces the session to be saved back to the session store, resetting the expiration date.
cookie: {
path: "/",
httpOnly: true,
maxAge: config.Session.cookieMaxAge * 1000 // needs value in milliseconds
},
name: "trilium.sid",
- store: new SQLiteSessionStore()
+ store: sessionStore
});
setInterval(() => {
@@ -70,6 +118,6 @@ setInterval(() => {
const now = Date.now();
const result = sql.execute(/*sql*/`DELETE FROM sessions WHERE expires < ?`, now);
console.log("Cleaning up expired sessions: ", result.changes);
-}, 60 * 60 * 1000);
+}, CLEAN_UP_INTERVAL);
export default sessionParser;
diff --git a/apps/server/src/services/llm/ai_service_manager.ts b/apps/server/src/services/llm/ai_service_manager.ts
index d7bbf4cf7..394523d8f 100644
--- a/apps/server/src/services/llm/ai_service_manager.ts
+++ b/apps/server/src/services/llm/ai_service_manager.ts
@@ -1,4 +1,5 @@
import options from '../options.js';
+import eventService from '../events.js';
import type { AIService, ChatCompletionOptions, ChatResponse, Message } from './ai_interface.js';
import { AnthropicService } from './providers/anthropic_service.js';
import { ContextExtractor } from './context/index.js';
@@ -20,9 +21,8 @@ import type { NoteSearchResult } from './interfaces/context_interfaces.js';
// Import new configuration system
import {
- getProviderPrecedence,
- getPreferredProvider,
- getEmbeddingProviderPrecedence,
+ getSelectedProvider,
+ getSelectedEmbeddingProvider,
parseModelIdentifier,
isAIEnabled,
getDefaultModelForProvider,
@@ -43,23 +43,20 @@ interface NoteContext {
}
export class AIServiceManager implements IAIServiceManager {
- private services: Record = {
- openai: new OpenAIService(),
- anthropic: new AnthropicService(),
- ollama: new OllamaService()
- };
+ private services: Partial> = {};
- private providerOrder: ServiceProviders[] = []; // Will be populated from configuration
private initialized = false;
constructor() {
- // Initialize provider order immediately
- this.updateProviderOrder();
-
// Initialize tools immediately
this.initializeTools().catch(error => {
log.error(`Error initializing LLM tools during AIServiceManager construction: ${error.message || String(error)}`);
});
+
+ // Set up event listener for provider changes
+ this.setupProviderChangeListener();
+
+ this.initialized = true;
}
/**
@@ -84,39 +81,18 @@ export class AIServiceManager implements IAIServiceManager {
}
/**
- * Update the provider precedence order using the new configuration system
+ * Get the currently selected provider using the new configuration system
*/
- async updateProviderOrderAsync(): Promise {
+ async getSelectedProviderAsync(): Promise {
try {
- const providers = await getProviderPrecedence();
- this.providerOrder = providers as ServiceProviders[];
- this.initialized = true;
- log.info(`Updated provider order: ${providers.join(', ')}`);
+ const selectedProvider = await getSelectedProvider();
+ return selectedProvider as ServiceProviders || null;
} catch (error) {
- log.error(`Failed to get provider precedence: ${error}`);
- // Keep empty order, will be handled gracefully by other methods
- this.providerOrder = [];
- this.initialized = true;
+ log.error(`Failed to get selected provider: ${error}`);
+ return null;
}
}
- /**
- * Update the provider precedence order (legacy sync version)
- * Returns true if successful, false if options not available yet
- */
- updateProviderOrder(): boolean {
- if (this.initialized) {
- return true;
- }
-
- // Use async version but don't wait
- this.updateProviderOrderAsync().catch(error => {
- log.error(`Error in async provider order update: ${error}`);
- });
-
- return true;
- }
-
/**
* Validate AI configuration using the new configuration system
*/
@@ -158,16 +134,44 @@ export class AIServiceManager implements IAIServiceManager {
* Ensure manager is initialized before using
*/
private ensureInitialized() {
- if (!this.initialized) {
- this.updateProviderOrder();
+ // No longer needed with simplified approach
+ }
+
+ /**
+ * Get or create any available AI service following the simplified pattern
+ * Returns a service or throws a meaningful error
+ */
+ async getOrCreateAnyService(): Promise {
+ this.ensureInitialized();
+
+ // Get the selected provider using the new configuration system
+ const selectedProvider = await this.getSelectedProviderAsync();
+
+
+ if (!selectedProvider) {
+ throw new Error('No AI provider is selected. Please select a provider (OpenAI, Anthropic, or Ollama) in your AI settings.');
+ }
+
+ try {
+ const service = await this.getOrCreateChatProvider(selectedProvider);
+ if (service) {
+ return service;
+ }
+ throw new Error(`Failed to create ${selectedProvider} service`);
+ } catch (error) {
+ log.error(`Provider ${selectedProvider} not available: ${error}`);
+ throw new Error(`Selected AI provider (${selectedProvider}) is not available. Please check your configuration: ${error}`);
}
}
/**
- * Check if any AI service is available
+ * Check if any AI service is available (legacy method for backward compatibility)
*/
isAnyServiceAvailable(): boolean {
- return Object.values(this.services).some(service => service.isAvailable());
+ this.ensureInitialized();
+
+ // Check if we have the selected provider available
+ return this.getAvailableProviders().length > 0;
}
/**
@@ -175,9 +179,42 @@ export class AIServiceManager implements IAIServiceManager {
*/
getAvailableProviders(): ServiceProviders[] {
this.ensureInitialized();
- return Object.entries(this.services)
- .filter(([_, service]) => service.isAvailable())
- .map(([key, _]) => key as ServiceProviders);
+
+ const allProviders: ServiceProviders[] = ['openai', 'anthropic', 'ollama'];
+ const availableProviders: ServiceProviders[] = [];
+
+ for (const providerName of allProviders) {
+ // Use a sync approach - check if we can create the provider
+ const service = this.services[providerName];
+ if (service && service.isAvailable()) {
+ availableProviders.push(providerName);
+ } else {
+ // For providers not yet created, check configuration to see if they would be available
+ try {
+ switch (providerName) {
+ case 'openai':
+ if (options.getOption('openaiApiKey')) {
+ availableProviders.push(providerName);
+ }
+ break;
+ case 'anthropic':
+ if (options.getOption('anthropicApiKey')) {
+ availableProviders.push(providerName);
+ }
+ break;
+ case 'ollama':
+ if (options.getOption('ollamaBaseUrl')) {
+ availableProviders.push(providerName);
+ }
+ break;
+ }
+ } catch (error) {
+ // Ignore configuration errors, provider just won't be available
+ }
+ }
+ }
+
+ return availableProviders;
}
/**
@@ -198,51 +235,54 @@ export class AIServiceManager implements IAIServiceManager {
throw new Error('No messages provided for chat completion');
}
- // Try providers in order of preference
- const availableProviders = this.getAvailableProviders();
-
- if (availableProviders.length === 0) {
- throw new Error('No AI providers are available. Please check your AI settings.');
+ // Get the selected provider
+ const selectedProvider = await this.getSelectedProviderAsync();
+
+ if (!selectedProvider) {
+ throw new Error('No AI provider is selected. Please select a provider in your AI settings.');
+ }
+
+ // Check if the selected provider is available
+ const availableProviders = this.getAvailableProviders();
+ if (!availableProviders.includes(selectedProvider)) {
+ throw new Error(`Selected AI provider (${selectedProvider}) is not available. Please check your configuration.`);
}
-
- // Sort available providers by precedence
- const sortedProviders = this.providerOrder
- .filter(provider => availableProviders.includes(provider));
// If a specific provider is requested and available, use it
if (options.model && options.model.includes(':')) {
// Use the new configuration system to parse model identifier
const modelIdentifier = parseModelIdentifier(options.model);
- if (modelIdentifier.provider && availableProviders.includes(modelIdentifier.provider as ServiceProviders)) {
+ if (modelIdentifier.provider && modelIdentifier.provider === selectedProvider) {
try {
- const modifiedOptions = { ...options, model: modelIdentifier.modelId };
- log.info(`[AIServiceManager] Using provider ${modelIdentifier.provider} from model prefix with modifiedOptions.stream: ${modifiedOptions.stream}`);
- return await this.services[modelIdentifier.provider as ServiceProviders].generateChatCompletion(messages, modifiedOptions);
+ const service = await this.getOrCreateChatProvider(modelIdentifier.provider as ServiceProviders);
+ if (service) {
+ const modifiedOptions = { ...options, model: modelIdentifier.modelId };
+ log.info(`[AIServiceManager] Using provider ${modelIdentifier.provider} from model prefix with modifiedOptions.stream: ${modifiedOptions.stream}`);
+ return await service.generateChatCompletion(messages, modifiedOptions);
+ }
} catch (error) {
log.error(`Error with specified provider ${modelIdentifier.provider}: ${error}`);
- // If the specified provider fails, continue with the fallback providers
+ throw new Error(`Failed to use specified provider ${modelIdentifier.provider}: ${error}`);
}
+ } else if (modelIdentifier.provider && modelIdentifier.provider !== selectedProvider) {
+ throw new Error(`Model specifies provider '${modelIdentifier.provider}' but selected provider is '${selectedProvider}'. Please select the correct provider or use a model without provider prefix.`);
}
// If not a provider prefix, treat the entire string as a model name and continue with normal provider selection
}
- // Try each provider in order until one succeeds
- let lastError: Error | null = null;
-
- for (const provider of sortedProviders) {
- try {
- log.info(`[AIServiceManager] Trying provider ${provider} with options.stream: ${options.stream}`);
- return await this.services[provider].generateChatCompletion(messages, options);
- } catch (error) {
- log.error(`Error with provider ${provider}: ${error}`);
- lastError = error as Error;
- // Continue to the next provider
+ // Use the selected provider
+ try {
+ const service = await this.getOrCreateChatProvider(selectedProvider);
+ if (!service) {
+ throw new Error(`Failed to create selected chat provider: ${selectedProvider}. Please check your configuration.`);
}
+ log.info(`[AIServiceManager] Using selected provider ${selectedProvider} with options.stream: ${options.stream}`);
+ return await service.generateChatCompletion(messages, options);
+ } catch (error) {
+ log.error(`Error with selected provider ${selectedProvider}: ${error}`);
+ throw new Error(`Selected AI provider (${selectedProvider}) failed: ${error}`);
}
-
- // If we get here, all providers failed
- throw new Error(`All AI providers failed: ${lastError?.message || 'Unknown error'}`);
}
setupEventListeners() {
@@ -340,30 +380,64 @@ export class AIServiceManager implements IAIServiceManager {
}
/**
- * Set up embeddings provider using the new configuration system
+ * Get or create a chat provider on-demand with inline validation
*/
- async setupEmbeddingsProvider(): Promise {
- try {
- const aiEnabled = await isAIEnabled();
- if (!aiEnabled) {
- log.info('AI features are disabled');
- return;
- }
-
- // Use the new configuration system - no string parsing!
- const enabledProviders = await getEnabledEmbeddingProviders();
-
- if (enabledProviders.length === 0) {
- log.info('No embedding providers are enabled');
- return;
- }
-
- // Initialize embedding providers
- log.info('Embedding providers initialized successfully');
- } catch (error: any) {
- log.error(`Error setting up embedding providers: ${error.message}`);
- throw error;
+ private async getOrCreateChatProvider(providerName: ServiceProviders): Promise {
+ // Return existing provider if already created
+ if (this.services[providerName]) {
+ return this.services[providerName];
}
+
+ // Create and validate provider on-demand
+ try {
+ let service: AIService | null = null;
+
+ switch (providerName) {
+ case 'openai': {
+ const apiKey = options.getOption('openaiApiKey');
+ const baseUrl = options.getOption('openaiBaseUrl');
+ if (!apiKey && !baseUrl) return null;
+
+ service = new OpenAIService();
+ // Validate by checking if it's available
+ if (!service.isAvailable()) {
+ throw new Error('OpenAI service not available');
+ }
+ break;
+ }
+
+ case 'anthropic': {
+ const apiKey = options.getOption('anthropicApiKey');
+ if (!apiKey) return null;
+
+ service = new AnthropicService();
+ if (!service.isAvailable()) {
+ throw new Error('Anthropic service not available');
+ }
+ break;
+ }
+
+ case 'ollama': {
+ const baseUrl = options.getOption('ollamaBaseUrl');
+ if (!baseUrl) return null;
+
+ service = new OllamaService();
+ if (!service.isAvailable()) {
+ throw new Error('Ollama service not available');
+ }
+ break;
+ }
+ }
+
+ if (service) {
+ this.services[providerName] = service;
+ return service;
+ }
+ } catch (error: any) {
+ log.error(`Failed to create ${providerName} chat provider: ${error.message || 'Unknown error'}`);
+ }
+
+ return null;
}
/**
@@ -381,12 +455,6 @@ export class AIServiceManager implements IAIServiceManager {
return;
}
- // Update provider order from configuration
- await this.updateProviderOrderAsync();
-
- // Set up embeddings provider if AI is enabled
- await this.setupEmbeddingsProvider();
-
// Initialize index service
await this.getIndexService().initialize();
@@ -453,8 +521,8 @@ export class AIServiceManager implements IAIServiceManager {
if (!contextNotes || contextNotes.length === 0) {
try {
// Get the default LLM service for context enhancement
- const provider = this.getPreferredProvider();
- const llmService = this.getService(provider);
+ const provider = this.getSelectedProvider();
+ const llmService = await this.getService(provider);
// Find relevant notes
contextNotes = await contextService.findRelevantNotes(
@@ -495,25 +563,31 @@ export class AIServiceManager implements IAIServiceManager {
/**
* Get AI service for the given provider
*/
- getService(provider?: string): AIService {
+ async getService(provider?: string): Promise {
this.ensureInitialized();
- // If provider is specified, try to use it
- if (provider && this.services[provider as ServiceProviders]?.isAvailable()) {
- return this.services[provider as ServiceProviders];
- }
-
- // Otherwise, use the first available provider in the configured order
- for (const providerName of this.providerOrder) {
- const service = this.services[providerName];
- if (service.isAvailable()) {
+ // If provider is specified, try to get or create it
+ if (provider) {
+ const service = await this.getOrCreateChatProvider(provider as ServiceProviders);
+ if (service && service.isAvailable()) {
return service;
}
+ throw new Error(`Specified provider ${provider} is not available`);
}
- // If no provider is available, use first one anyway (it will throw an error)
- // This allows us to show a proper error message rather than "provider not found"
- return this.services[this.providerOrder[0]];
+ // Otherwise, use the selected provider
+ const selectedProvider = await this.getSelectedProviderAsync();
+ if (!selectedProvider) {
+ throw new Error('No AI provider is selected. Please select a provider in your AI settings.');
+ }
+
+ const service = await this.getOrCreateChatProvider(selectedProvider);
+ if (service && service.isAvailable()) {
+ return service;
+ }
+
+ // If no provider is available, throw a clear error
+ throw new Error(`Selected AI provider (${selectedProvider}) is not available. Please check your AI settings.`);
}
/**
@@ -521,34 +595,37 @@ export class AIServiceManager implements IAIServiceManager {
*/
async getPreferredProviderAsync(): Promise {
try {
- const preferredProvider = await getPreferredProvider();
- if (preferredProvider === null) {
- // No providers configured, fallback to first available
- log.info('No providers configured in precedence, using first available provider');
- return this.providerOrder[0];
+ const selectedProvider = await getSelectedProvider();
+ if (selectedProvider === null) {
+ // No provider selected, fallback to default
+ log.info('No provider selected, using default provider');
+ return 'openai';
}
- return preferredProvider;
+ return selectedProvider;
} catch (error) {
log.error(`Error getting preferred provider: ${error}`);
- return this.providerOrder[0];
+ return 'openai';
}
}
/**
- * Get the preferred provider based on configuration (sync version for compatibility)
+ * Get the selected provider based on configuration (sync version for compatibility)
*/
- getPreferredProvider(): string {
+ getSelectedProvider(): string {
this.ensureInitialized();
- // Return the first available provider in the order
- for (const providerName of this.providerOrder) {
- if (this.services[providerName].isAvailable()) {
- return providerName;
+ // Try to get the selected provider synchronously
+ try {
+ const selectedProvider = options.getOption('aiSelectedProvider');
+ if (selectedProvider) {
+ return selectedProvider;
}
+ } catch (error) {
+ log.error(`Error getting selected provider: ${error}`);
}
- // Return the first provider as fallback
- return this.providerOrder[0];
+ // Return a default if nothing is selected (for backward compatibility)
+ return 'openai';
}
/**
@@ -580,6 +657,7 @@ export class AIServiceManager implements IAIServiceManager {
};
}
+
/**
* Error handler that properly types the error object
*/
@@ -589,6 +667,79 @@ export class AIServiceManager implements IAIServiceManager {
}
return String(error);
}
+
+ /**
+ * Set up event listener for provider changes
+ */
+ private setupProviderChangeListener(): void {
+ // List of AI-related options that should trigger service recreation
+ const aiRelatedOptions = [
+ 'aiEnabled',
+ 'aiSelectedProvider',
+ 'embeddingSelectedProvider',
+ 'openaiApiKey',
+ 'openaiBaseUrl',
+ 'openaiDefaultModel',
+ 'anthropicApiKey',
+ 'anthropicBaseUrl',
+ 'anthropicDefaultModel',
+ 'ollamaBaseUrl',
+ 'ollamaDefaultModel',
+ 'voyageApiKey'
+ ];
+
+ eventService.subscribe(['entityChanged'], async ({ entityName, entity }) => {
+ if (entityName === 'options' && entity && aiRelatedOptions.includes(entity.name)) {
+ log.info(`AI-related option '${entity.name}' changed, recreating LLM services`);
+
+ // Special handling for aiEnabled toggle
+ if (entity.name === 'aiEnabled') {
+ const isEnabled = entity.value === 'true';
+
+ if (isEnabled) {
+ log.info('AI features enabled, initializing AI service and embeddings');
+ // Initialize the AI service
+ await this.initialize();
+ // Initialize embeddings through index service
+ await indexService.startEmbeddingGeneration();
+ } else {
+ log.info('AI features disabled, stopping embeddings and clearing providers');
+ // Stop embeddings through index service
+ await indexService.stopEmbeddingGeneration();
+ // Clear chat providers
+ this.services = {};
+ }
+ } else {
+ // For other AI-related options, recreate services on-demand
+ await this.recreateServices();
+ }
+ }
+ });
+ }
+
+ /**
+ * Recreate LLM services when provider settings change
+ */
+ private async recreateServices(): Promise {
+ try {
+ log.info('Recreating LLM services due to configuration change');
+
+ // Clear configuration cache first
+ clearConfigurationCache();
+
+ // Clear existing chat providers (they will be recreated on-demand)
+ this.services = {};
+
+ // Clear embedding providers (they will be recreated on-demand when needed)
+ const providerManager = await import('./providers/providers.js');
+ providerManager.clearAllEmbeddingProviders();
+
+ log.info('LLM services recreated successfully');
+ } catch (error) {
+ log.error(`Error recreating LLM services: ${this.handleError(error)}`);
+ }
+ }
+
}
// Don't create singleton immediately, use a lazy-loading pattern
@@ -610,6 +761,9 @@ export default {
isAnyServiceAvailable(): boolean {
return getInstance().isAnyServiceAvailable();
},
+ async getOrCreateAnyService(): Promise {
+ return getInstance().getOrCreateAnyService();
+ },
getAvailableProviders() {
return getInstance().getAvailableProviders();
},
@@ -661,11 +815,11 @@ export default {
);
},
// New methods
- getService(provider?: string): AIService {
+ async getService(provider?: string): Promise {
return getInstance().getService(provider);
},
- getPreferredProvider(): string {
- return getInstance().getPreferredProvider();
+ getSelectedProvider(): string {
+ return getInstance().getSelectedProvider();
},
isProviderAvailable(provider: string): boolean {
return getInstance().isProviderAvailable(provider);
diff --git a/apps/server/src/services/llm/chat/rest_chat_service.ts b/apps/server/src/services/llm/chat/rest_chat_service.ts
index 1ad3d7a22..53ea457a1 100644
--- a/apps/server/src/services/llm/chat/rest_chat_service.ts
+++ b/apps/server/src/services/llm/chat/rest_chat_service.ts
@@ -5,7 +5,7 @@
import log from "../../log.js";
import type { Request, Response } from "express";
import type { Message, ChatCompletionOptions } from "../ai_interface.js";
-import { AIServiceManager } from "../ai_service_manager.js";
+import aiServiceManager from "../ai_service_manager.js";
import { ChatPipeline } from "../pipeline/chat_pipeline.js";
import type { ChatPipelineInput } from "../pipeline/interfaces.js";
import options from "../../options.js";
@@ -14,7 +14,7 @@ import type { LLMStreamMessage } from "../interfaces/chat_ws_messages.js";
import chatStorageService from '../chat_storage_service.js';
import {
isAIEnabled,
- getFirstValidModelConfig,
+ getSelectedModelConfig,
} from '../config/configuration_helpers.js';
/**
@@ -33,25 +33,6 @@ class RestChatService {
}
}
- /**
- * Check if AI services are available
- */
- safelyUseAIManager(): boolean {
- if (!this.isDatabaseInitialized()) {
- log.info("AI check failed: Database is not initialized");
- return false;
- }
-
- try {
- const aiManager = new AIServiceManager();
- const isAvailable = aiManager.isAnyServiceAvailable();
- log.info(`AI service availability check result: ${isAvailable}`);
- return isAvailable;
- } catch (error) {
- log.error(`Error accessing AI service manager: ${error}`);
- return false;
- }
- }
/**
* Handle a message sent to an LLM and get a response
@@ -93,10 +74,14 @@ class RestChatService {
return { error: "AI features are disabled. Please enable them in the settings." };
}
- if (!this.safelyUseAIManager()) {
- return { error: "AI services are currently unavailable. Please check your configuration." };
+ // Check database initialization first
+ if (!this.isDatabaseInitialized()) {
+ throw new Error("Database is not initialized");
}
+ // Get or create AI service - will throw meaningful error if not possible
+ await aiServiceManager.getOrCreateAnyService();
+
// Load or create chat directly from storage
let chat = await chatStorageService.getChat(chatNoteId);
@@ -252,14 +237,6 @@ class RestChatService {
// Send WebSocket message
wsService.sendMessageToAllClients(message);
-
- // Send SSE response for compatibility
- const responseData: any = { content: data, done };
- if (rawChunk?.toolExecution) {
- responseData.toolExecution = rawChunk.toolExecution;
- }
-
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
// When streaming is complete, save the accumulated content to the chat note
if (done) {
@@ -281,8 +258,8 @@ class RestChatService {
log.error(`Error saving streaming response: ${error}`);
}
- // End the response
- res.end();
+ // Note: For WebSocket-only streaming, we don't end the HTTP response here
+ // since it was already handled by the calling endpoint
}
}
@@ -419,7 +396,7 @@ class RestChatService {
*/
async getPreferredModel(): Promise {
try {
- const validConfig = await getFirstValidModelConfig();
+ const validConfig = await getSelectedModelConfig();
if (!validConfig) {
log.error('No valid AI model configuration found');
return undefined;
diff --git a/apps/server/src/services/llm/config/configuration_helpers.ts b/apps/server/src/services/llm/config/configuration_helpers.ts
index 88d2cf1da..2635cc35f 100644
--- a/apps/server/src/services/llm/config/configuration_helpers.ts
+++ b/apps/server/src/services/llm/config/configuration_helpers.ts
@@ -1,10 +1,9 @@
import configurationManager from './configuration_manager.js';
+import optionService from '../../options.js';
import type {
ProviderType,
ModelIdentifier,
ModelConfig,
- ProviderPrecedenceConfig,
- EmbeddingProviderPrecedenceConfig
} from '../interfaces/configuration_interfaces.js';
/**
@@ -13,41 +12,19 @@ import type {
*/
/**
- * Get the ordered list of AI providers
+ * Get the selected AI provider
*/
-export async function getProviderPrecedence(): Promise {
- const config = await configurationManager.getProviderPrecedence();
- return config.providers;
+export async function getSelectedProvider(): Promise {
+ const providerOption = optionService.getOption('aiSelectedProvider');
+ return providerOption as ProviderType || null;
}
/**
- * Get the default/preferred AI provider
+ * Get the selected embedding provider
*/
-export async function getPreferredProvider(): Promise {
- const config = await configurationManager.getProviderPrecedence();
- if (config.providers.length === 0) {
- return null; // No providers configured
- }
- return config.defaultProvider || config.providers[0];
-}
-
-/**
- * Get the ordered list of embedding providers
- */
-export async function getEmbeddingProviderPrecedence(): Promise {
- const config = await configurationManager.getEmbeddingProviderPrecedence();
- return config.providers;
-}
-
-/**
- * Get the default embedding provider
- */
-export async function getPreferredEmbeddingProvider(): Promise {
- const config = await configurationManager.getEmbeddingProviderPrecedence();
- if (config.providers.length === 0) {
- return null; // No providers configured
- }
- return config.defaultProvider || config.providers[0];
+export async function getSelectedEmbeddingProvider(): Promise {
+ const providerOption = optionService.getOption('embeddingSelectedProvider');
+ return providerOption || null;
}
/**
@@ -107,22 +84,20 @@ export async function isProviderConfigured(provider: ProviderType): Promise {
- const providers = await getProviderPrecedence();
-
- if (providers.length === 0) {
- return null; // No providers configured
+export async function getAvailableSelectedProvider(): Promise {
+ const selectedProvider = await getSelectedProvider();
+
+ if (!selectedProvider) {
+ return null; // No provider selected
}
- for (const provider of providers) {
- if (await isProviderConfigured(provider)) {
- return provider;
- }
+ if (await isProviderConfigured(selectedProvider)) {
+ return selectedProvider;
}
- return null; // No providers are properly configured
+ return null; // Selected provider is not properly configured
}
/**
@@ -163,17 +138,15 @@ export async function getValidModelConfig(provider: ProviderType): Promise<{ mod
}
/**
- * Get the first valid model configuration from the provider precedence list
+ * Get the model configuration for the currently selected provider
*/
-export async function getFirstValidModelConfig(): Promise<{ model: string; provider: ProviderType } | null> {
- const providers = await getProviderPrecedence();
-
- for (const provider of providers) {
- const config = await getValidModelConfig(provider);
- if (config) {
- return config;
- }
+export async function getSelectedModelConfig(): Promise<{ model: string; provider: ProviderType } | null> {
+ const selectedProvider = await getSelectedProvider();
+
+ if (!selectedProvider) {
+ return null; // No provider selected
}
- return null; // No valid model configuration found
+ return await getValidModelConfig(selectedProvider);
}
+
diff --git a/apps/server/src/services/llm/config/configuration_manager.ts b/apps/server/src/services/llm/config/configuration_manager.ts
index 5bc9611b8..1e082db41 100644
--- a/apps/server/src/services/llm/config/configuration_manager.ts
+++ b/apps/server/src/services/llm/config/configuration_manager.ts
@@ -50,8 +50,8 @@ export class ConfigurationManager {
try {
const config: AIConfig = {
enabled: await this.getAIEnabled(),
- providerPrecedence: await this.getProviderPrecedence(),
- embeddingProviderPrecedence: await this.getEmbeddingProviderPrecedence(),
+ selectedProvider: await this.getSelectedProvider(),
+ selectedEmbeddingProvider: await this.getSelectedEmbeddingProvider(),
defaultModels: await this.getDefaultModels(),
providerSettings: await this.getProviderSettings()
};
@@ -66,46 +66,28 @@ export class ConfigurationManager {
}
/**
- * Parse provider precedence from string option
+ * Get the selected AI provider
*/
- public async getProviderPrecedence(): Promise {
+ public async getSelectedProvider(): Promise {
try {
- const precedenceOption = await options.getOption('aiProviderPrecedence');
- const providers = this.parseProviderList(precedenceOption);
-
- return {
- providers: providers as ProviderType[],
- defaultProvider: providers.length > 0 ? providers[0] as ProviderType : undefined
- };
+ const selectedProvider = options.getOption('aiSelectedProvider');
+ return selectedProvider as ProviderType || null;
} catch (error) {
- log.error(`Error parsing provider precedence: ${error}`);
- // Only return known providers if they exist, don't assume defaults
- return {
- providers: [],
- defaultProvider: undefined
- };
+ log.error(`Error getting selected provider: ${error}`);
+ return null;
}
}
/**
- * Parse embedding provider precedence from string option
+ * Get the selected embedding provider
*/
- public async getEmbeddingProviderPrecedence(): Promise {
+ public async getSelectedEmbeddingProvider(): Promise {
try {
- const precedenceOption = await options.getOption('embeddingProviderPrecedence');
- const providers = this.parseProviderList(precedenceOption);
-
- return {
- providers: providers as EmbeddingProviderType[],
- defaultProvider: providers.length > 0 ? providers[0] as EmbeddingProviderType : undefined
- };
+ const selectedProvider = options.getOption('embeddingSelectedProvider');
+ return selectedProvider as EmbeddingProviderType || null;
} catch (error) {
- log.error(`Error parsing embedding provider precedence: ${error}`);
- // Don't assume defaults, return empty configuration
- return {
- providers: [],
- defaultProvider: undefined
- };
+ log.error(`Error getting selected embedding provider: ${error}`);
+ return null;
}
}
@@ -173,11 +155,9 @@ export class ConfigurationManager {
*/
public async getDefaultModels(): Promise> {
try {
- const [openaiModel, anthropicModel, ollamaModel] = await Promise.all([
- options.getOption('openaiDefaultModel'),
- options.getOption('anthropicDefaultModel'),
- options.getOption('ollamaDefaultModel')
- ]);
+ const openaiModel = options.getOption('openaiDefaultModel');
+ const anthropicModel = options.getOption('anthropicDefaultModel');
+ const ollamaModel = options.getOption('ollamaDefaultModel');
return {
openai: openaiModel || undefined,
@@ -200,20 +180,14 @@ export class ConfigurationManager {
*/
public async getProviderSettings(): Promise {
try {
- const [
- openaiApiKey, openaiBaseUrl, openaiDefaultModel,
- anthropicApiKey, anthropicBaseUrl, anthropicDefaultModel,
- ollamaBaseUrl, ollamaDefaultModel
- ] = await Promise.all([
- options.getOption('openaiApiKey'),
- options.getOption('openaiBaseUrl'),
- options.getOption('openaiDefaultModel'),
- options.getOption('anthropicApiKey'),
- options.getOption('anthropicBaseUrl'),
- options.getOption('anthropicDefaultModel'),
- options.getOption('ollamaBaseUrl'),
- options.getOption('ollamaDefaultModel')
- ]);
+ const openaiApiKey = options.getOption('openaiApiKey');
+ const openaiBaseUrl = options.getOption('openaiBaseUrl');
+ const openaiDefaultModel = options.getOption('openaiDefaultModel');
+ const anthropicApiKey = options.getOption('anthropicApiKey');
+ const anthropicBaseUrl = options.getOption('anthropicBaseUrl');
+ const anthropicDefaultModel = options.getOption('anthropicDefaultModel');
+ const ollamaBaseUrl = options.getOption('ollamaBaseUrl');
+ const ollamaDefaultModel = options.getOption('ollamaDefaultModel');
const settings: ProviderSettings = {};
@@ -265,31 +239,29 @@ export class ConfigurationManager {
return result;
}
- // Validate provider precedence
- if (config.providerPrecedence.providers.length === 0) {
- result.errors.push('No providers configured in precedence list');
+ // Validate selected provider
+ if (!config.selectedProvider) {
+ result.errors.push('No AI provider selected');
result.isValid = false;
- }
+ } else {
+ // Validate selected provider settings
+ const providerConfig = config.providerSettings[config.selectedProvider];
- // Validate provider settings
- for (const provider of config.providerPrecedence.providers) {
- const providerConfig = config.providerSettings[provider];
-
- if (provider === 'openai') {
+ if (config.selectedProvider === 'openai') {
const openaiConfig = providerConfig as OpenAISettings | undefined;
if (!openaiConfig?.apiKey) {
result.warnings.push('OpenAI API key is not configured');
}
}
- if (provider === 'anthropic') {
+ if (config.selectedProvider === 'anthropic') {
const anthropicConfig = providerConfig as AnthropicSettings | undefined;
if (!anthropicConfig?.apiKey) {
result.warnings.push('Anthropic API key is not configured');
}
}
- if (provider === 'ollama') {
+ if (config.selectedProvider === 'ollama') {
const ollamaConfig = providerConfig as OllamaSettings | undefined;
if (!ollamaConfig?.baseUrl) {
result.warnings.push('Ollama base URL is not configured');
@@ -297,6 +269,11 @@ export class ConfigurationManager {
}
}
+ // Validate selected embedding provider
+ if (!config.selectedEmbeddingProvider) {
+ result.warnings.push('No embedding provider selected');
+ }
+
} catch (error) {
result.errors.push(`Configuration validation error: ${error}`);
result.isValid = false;
@@ -317,7 +294,7 @@ export class ConfigurationManager {
private async getAIEnabled(): Promise {
try {
- return await options.getOptionBool('aiEnabled');
+ return options.getOptionBool('aiEnabled');
} catch {
return false;
}
@@ -356,14 +333,8 @@ export class ConfigurationManager {
private getDefaultConfig(): AIConfig {
return {
enabled: false,
- providerPrecedence: {
- providers: [],
- defaultProvider: undefined
- },
- embeddingProviderPrecedence: {
- providers: [],
- defaultProvider: undefined
- },
+ selectedProvider: null,
+ selectedEmbeddingProvider: null,
defaultModels: {
openai: undefined,
anthropic: undefined,
diff --git a/apps/server/src/services/llm/context/index.ts b/apps/server/src/services/llm/context/index.ts
index 258428705..c44eea9cb 100644
--- a/apps/server/src/services/llm/context/index.ts
+++ b/apps/server/src/services/llm/context/index.ts
@@ -33,7 +33,7 @@ async function getSemanticContext(
}
// Get an LLM service
- const llmService = aiServiceManager.getInstance().getService();
+ const llmService = await aiServiceManager.getInstance().getService();
const result = await contextService.processQuery("", llmService, {
maxResults: options.maxSimilarNotes || 5,
@@ -543,7 +543,7 @@ export class ContextExtractor {
try {
const { default: aiServiceManager } = await import('../ai_service_manager.js');
const contextService = aiServiceManager.getInstance().getContextService();
- const llmService = aiServiceManager.getInstance().getService();
+ const llmService = await aiServiceManager.getInstance().getService();
if (!contextService) {
return "Context service not available.";
diff --git a/apps/server/src/services/llm/context/modules/provider_manager.ts b/apps/server/src/services/llm/context/modules/provider_manager.ts
index 8030e3592..56c6437c0 100644
--- a/apps/server/src/services/llm/context/modules/provider_manager.ts
+++ b/apps/server/src/services/llm/context/modules/provider_manager.ts
@@ -1,51 +1,32 @@
-import options from '../../../options.js';
import log from '../../../log.js';
import { getEmbeddingProvider, getEnabledEmbeddingProviders } from '../../providers/providers.js';
+import { getSelectedEmbeddingProvider as getSelectedEmbeddingProviderName } from '../../config/configuration_helpers.js';
/**
* Manages embedding providers for context services
*/
export class ProviderManager {
/**
- * Get the preferred embedding provider based on user settings
- * Tries to use the most appropriate provider in this order:
- * 1. User's configured default provider
- * 2. OpenAI if API key is set
- * 3. Anthropic if API key is set
- * 4. Ollama if configured
- * 5. Any available provider
- * 6. Local provider as fallback
+ * Get the selected embedding provider based on user settings
+ * Uses the single provider selection approach
*
- * @returns The preferred embedding provider or null if none available
+ * @returns The selected embedding provider or null if none available
*/
- async getPreferredEmbeddingProvider(): Promise {
+ async getSelectedEmbeddingProvider(): Promise {
try {
- // Try to get providers based on precedence list
- const precedenceOption = await options.getOption('embeddingProviderPrecedence');
- let precedenceList: string[] = [];
-
- if (precedenceOption) {
- if (precedenceOption.startsWith('[') && precedenceOption.endsWith(']')) {
- precedenceList = JSON.parse(precedenceOption);
- } else if (typeof precedenceOption === 'string') {
- if (precedenceOption.includes(',')) {
- precedenceList = precedenceOption.split(',').map(p => p.trim());
- } else {
- precedenceList = [precedenceOption];
- }
- }
- }
-
- // Try each provider in the precedence list
- for (const providerId of precedenceList) {
- const provider = await getEmbeddingProvider(providerId);
+ // Get the selected embedding provider
+ const selectedProvider = await getSelectedEmbeddingProviderName();
+
+ if (selectedProvider) {
+ const provider = await getEmbeddingProvider(selectedProvider);
if (provider) {
- log.info(`Using embedding provider from precedence list: ${providerId}`);
+ log.info(`Using selected embedding provider: ${selectedProvider}`);
return provider;
}
+ log.info(`Selected embedding provider ${selectedProvider} is not available`);
}
- // If no provider from precedence list is available, try any enabled provider
+ // If no provider is selected or available, try any enabled provider
const providers = await getEnabledEmbeddingProviders();
if (providers.length > 0) {
log.info(`Using available embedding provider: ${providers[0].name}`);
@@ -70,7 +51,7 @@ export class ProviderManager {
async generateQueryEmbedding(query: string): Promise {
try {
// Get the preferred embedding provider
- const provider = await this.getPreferredEmbeddingProvider();
+ const provider = await this.getSelectedEmbeddingProvider();
if (!provider) {
log.error('No embedding provider available');
return null;
diff --git a/apps/server/src/services/llm/context/services/context_service.ts b/apps/server/src/services/llm/context/services/context_service.ts
index a227c3936..b4d4fd613 100644
--- a/apps/server/src/services/llm/context/services/context_service.ts
+++ b/apps/server/src/services/llm/context/services/context_service.ts
@@ -58,7 +58,7 @@ export class ContextService {
this.initPromise = (async () => {
try {
// Initialize provider
- const provider = await providerManager.getPreferredEmbeddingProvider();
+ const provider = await providerManager.getSelectedEmbeddingProvider();
if (!provider) {
throw new Error(`No embedding provider available. Could not initialize context service.`);
}
@@ -224,7 +224,7 @@ export class ContextService {
log.info(`Final combined results: ${relevantNotes.length} relevant notes`);
// Step 4: Build context from the notes
- const provider = await providerManager.getPreferredEmbeddingProvider();
+ const provider = await providerManager.getSelectedEmbeddingProvider();
const providerId = provider?.name || 'default';
const context = await contextFormatter.buildContextFromNotes(
diff --git a/apps/server/src/services/llm/context/services/vector_search_service.ts b/apps/server/src/services/llm/context/services/vector_search_service.ts
index 480ba05bd..98c7b993c 100644
--- a/apps/server/src/services/llm/context/services/vector_search_service.ts
+++ b/apps/server/src/services/llm/context/services/vector_search_service.ts
@@ -79,7 +79,7 @@ export class VectorSearchService {
}
// Get provider information
- const provider = await providerManager.getPreferredEmbeddingProvider();
+ const provider = await providerManager.getSelectedEmbeddingProvider();
if (!provider) {
log.error('No embedding provider available');
return [];
@@ -280,7 +280,7 @@ export class VectorSearchService {
}
// Get provider information
- const provider = await providerManager.getPreferredEmbeddingProvider();
+ const provider = await providerManager.getSelectedEmbeddingProvider();
if (!provider) {
log.error('No embedding provider available');
return [];
diff --git a/apps/server/src/services/llm/embeddings/events.ts b/apps/server/src/services/llm/embeddings/events.ts
index a078b2c32..2b8eac7d9 100644
--- a/apps/server/src/services/llm/embeddings/events.ts
+++ b/apps/server/src/services/llm/embeddings/events.ts
@@ -9,6 +9,9 @@ import becca from "../../../becca/becca.js";
// Add mutex to prevent concurrent processing
let isProcessingEmbeddings = false;
+// Store interval reference for cleanup
+let backgroundProcessingInterval: NodeJS.Timeout | null = null;
+
/**
* Setup event listeners for embedding-related events
*/
@@ -53,9 +56,15 @@ export function setupEmbeddingEventListeners() {
* Setup background processing of the embedding queue
*/
export async function setupEmbeddingBackgroundProcessing() {
+ // Clear any existing interval
+ if (backgroundProcessingInterval) {
+ clearInterval(backgroundProcessingInterval);
+ backgroundProcessingInterval = null;
+ }
+
const interval = parseInt(await options.getOption('embeddingUpdateInterval') || '200', 10);
- setInterval(async () => {
+ backgroundProcessingInterval = setInterval(async () => {
try {
// Skip if already processing
if (isProcessingEmbeddings) {
@@ -78,6 +87,17 @@ export async function setupEmbeddingBackgroundProcessing() {
}, interval);
}
+/**
+ * Stop background processing of the embedding queue
+ */
+export function stopEmbeddingBackgroundProcessing() {
+ if (backgroundProcessingInterval) {
+ clearInterval(backgroundProcessingInterval);
+ backgroundProcessingInterval = null;
+ log.info("Embedding background processing stopped");
+ }
+}
+
/**
* Initialize embeddings system
*/
diff --git a/apps/server/src/services/llm/embeddings/index.ts b/apps/server/src/services/llm/embeddings/index.ts
index 89d0f711e..c4be44a2e 100644
--- a/apps/server/src/services/llm/embeddings/index.ts
+++ b/apps/server/src/services/llm/embeddings/index.ts
@@ -58,12 +58,12 @@ export const processNoteWithChunking = async (
export const {
setupEmbeddingEventListeners,
setupEmbeddingBackgroundProcessing,
+ stopEmbeddingBackgroundProcessing,
initEmbeddings
} = events;
export const {
getEmbeddingStats,
- reprocessAllNotes,
cleanupEmbeddings
} = stats;
@@ -100,11 +100,11 @@ export default {
// Event handling
setupEmbeddingEventListeners: events.setupEmbeddingEventListeners,
setupEmbeddingBackgroundProcessing: events.setupEmbeddingBackgroundProcessing,
+ stopEmbeddingBackgroundProcessing: events.stopEmbeddingBackgroundProcessing,
initEmbeddings: events.initEmbeddings,
// Stats and maintenance
getEmbeddingStats: stats.getEmbeddingStats,
- reprocessAllNotes: stats.reprocessAllNotes,
cleanupEmbeddings: stats.cleanupEmbeddings,
// Index operations
diff --git a/apps/server/src/services/llm/embeddings/init.ts b/apps/server/src/services/llm/embeddings/init.ts
index 50724c05e..75d664e43 100644
--- a/apps/server/src/services/llm/embeddings/init.ts
+++ b/apps/server/src/services/llm/embeddings/init.ts
@@ -4,6 +4,7 @@ import { initEmbeddings } from "./index.js";
import providerManager from "../providers/providers.js";
import sqlInit from "../../sql_init.js";
import sql from "../../sql.js";
+import { validateProviders, logValidationResults, hasWorkingEmbeddingProviders } from "../provider_validation.js";
/**
* Reset any stuck embedding queue items that were left in processing state
@@ -43,13 +44,20 @@ export async function initializeEmbeddings() {
// Reset any stuck embedding queue items from previous server shutdown
await resetStuckEmbeddingQueue();
- // Initialize default embedding providers
- await providerManager.initializeDefaultProviders();
-
// Start the embedding system if AI is enabled
if (await options.getOptionBool('aiEnabled')) {
- await initEmbeddings();
- log.info("Embedding system initialized successfully.");
+ // Validate providers before starting the embedding system
+ log.info("Validating AI providers before starting embedding system...");
+ const validation = await validateProviders();
+ logValidationResults(validation);
+
+ if (await hasWorkingEmbeddingProviders()) {
+ // Embedding providers will be created on-demand when needed
+ await initEmbeddings();
+ log.info("Embedding system initialized successfully.");
+ } else {
+ log.info("Embedding system not started: No working embedding providers found. Please configure at least one AI provider (OpenAI, Ollama, or Voyage) to use embedding features.");
+ }
} else {
log.info("Embedding system disabled (AI features are turned off).");
}
diff --git a/apps/server/src/services/llm/embeddings/queue.ts b/apps/server/src/services/llm/embeddings/queue.ts
index 12f915c81..b002513c5 100644
--- a/apps/server/src/services/llm/embeddings/queue.ts
+++ b/apps/server/src/services/llm/embeddings/queue.ts
@@ -282,8 +282,6 @@ export async function processEmbeddingQueue() {
continue;
}
- // Log that we're starting to process this note
- log.info(`Starting embedding generation for note ${noteId}`);
// Get note context for embedding
const context = await getNoteEmbeddingContext(noteId);
@@ -334,7 +332,6 @@ export async function processEmbeddingQueue() {
"DELETE FROM embedding_queue WHERE noteId = ?",
[noteId]
);
- log.info(`Successfully completed embedding processing for note ${noteId}`);
// Count as successfully processed
processedCount++;
diff --git a/apps/server/src/services/llm/embeddings/stats.ts b/apps/server/src/services/llm/embeddings/stats.ts
index 6154da368..a8b594723 100644
--- a/apps/server/src/services/llm/embeddings/stats.ts
+++ b/apps/server/src/services/llm/embeddings/stats.ts
@@ -1,29 +1,5 @@
import sql from "../../../services/sql.js";
import log from "../../../services/log.js";
-import cls from "../../../services/cls.js";
-import { queueNoteForEmbedding } from "./queue.js";
-
-/**
- * Reprocess all notes to update embeddings
- */
-export async function reprocessAllNotes() {
- log.info("Queueing all notes for embedding updates");
-
- // Get all non-deleted note IDs
- const noteIds = await sql.getColumn(
- "SELECT noteId FROM notes WHERE isDeleted = 0"
- );
-
- log.info(`Adding ${noteIds.length} notes to embedding queue`);
-
- // Process each note ID within a cls context
- for (const noteId of noteIds) {
- // Use cls.init to ensure proper context for each operation
- await cls.init(async () => {
- await queueNoteForEmbedding(noteId as string, 'UPDATE');
- });
- }
-}
/**
* Get current embedding statistics
diff --git a/apps/server/src/services/llm/embeddings/storage.ts b/apps/server/src/services/llm/embeddings/storage.ts
index ac096071f..bbd1eba9c 100644
--- a/apps/server/src/services/llm/embeddings/storage.ts
+++ b/apps/server/src/services/llm/embeddings/storage.ts
@@ -11,7 +11,7 @@ import { SEARCH_CONSTANTS } from '../constants/search_constants.js';
import type { NoteEmbeddingContext } from "./embeddings_interface.js";
import becca from "../../../becca/becca.js";
import { isNoteExcludedFromAIById } from "../utils/ai_exclusion_utils.js";
-import { getEmbeddingProviderPrecedence } from '../config/configuration_helpers.js';
+import { getSelectedEmbeddingProvider } from '../config/configuration_helpers.js';
interface Similarity {
noteId: string;
@@ -277,9 +277,10 @@ export async function findSimilarNotes(
log.info('No embeddings found for specified provider, trying fallback providers...');
// Use the new configuration system - no string parsing!
- const preferredProviders = await getEmbeddingProviderPrecedence();
+ const selectedProvider = await getSelectedEmbeddingProvider();
+ const preferredProviders = selectedProvider ? [selectedProvider] : [];
- log.info(`Using provider precedence: ${preferredProviders.join(', ')}`);
+ log.info(`Using selected provider: ${selectedProvider || 'none'}`);
// Try providers in precedence order
for (const provider of preferredProviders) {
diff --git a/apps/server/src/services/llm/index_service.ts b/apps/server/src/services/llm/index_service.ts
index f1182495d..6887fbc36 100644
--- a/apps/server/src/services/llm/index_service.ts
+++ b/apps/server/src/services/llm/index_service.ts
@@ -12,6 +12,7 @@
import log from "../log.js";
import options from "../options.js";
import becca from "../../becca/becca.js";
+import beccaLoader from "../../becca/becca_loader.js";
import vectorStore from "./embeddings/index.js";
import providerManager from "./providers/providers.js";
import { ContextExtractor } from "./context/index.js";
@@ -21,6 +22,7 @@ import sqlInit from "../sql_init.js";
import { CONTEXT_PROMPTS } from './constants/llm_prompt_constants.js';
import { SEARCH_CONSTANTS } from './constants/search_constants.js';
import { isNoteExcludedFromAI } from "./utils/ai_exclusion_utils.js";
+import { hasWorkingEmbeddingProviders } from "./provider_validation.js";
export class IndexService {
private initialized = false;
@@ -46,47 +48,16 @@ export class IndexService {
async initialize() {
if (this.initialized) return;
- try {
- // Check if database is initialized before proceeding
- if (!sqlInit.isDbInitialized()) {
- log.info("Index service: Database not initialized yet, skipping initialization");
- return;
- }
+ // Setup event listeners for note changes
+ this.setupEventListeners();
- const aiEnabled = options.getOptionOrNull('aiEnabled') === "true";
- if (!aiEnabled) {
- log.info("Index service: AI features disabled, skipping initialization");
- return;
- }
-
- // Check if embedding system is ready
- const providers = await providerManager.getEnabledEmbeddingProviders();
- if (!providers || providers.length === 0) {
- throw new Error("No embedding providers available");
- }
-
- // Check if this instance should process embeddings
- const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client';
- const isSyncServer = await this.isSyncServerForEmbeddings();
- const shouldProcessEmbeddings = embeddingLocation === 'client' || isSyncServer;
-
- // Setup automatic indexing if enabled and this instance should process embeddings
- if (await options.getOptionBool('embeddingAutoUpdateEnabled') && shouldProcessEmbeddings) {
- this.setupAutomaticIndexing();
- log.info(`Index service: Automatic indexing enabled, processing embeddings ${isSyncServer ? 'as sync server' : 'as client'}`);
- } else if (await options.getOptionBool('embeddingAutoUpdateEnabled')) {
- log.info("Index service: Automatic indexing enabled, but this instance is not configured to process embeddings");
- }
-
- // Listen for note changes to update index
- this.setupEventListeners();
-
- this.initialized = true;
- log.info("Index service initialized successfully");
- } catch (error: any) {
- log.error(`Error initializing index service: ${error.message || "Unknown error"}`);
- throw error;
+ // Setup automatic indexing if enabled
+ if (await options.getOptionBool('embeddingAutoUpdateEnabled')) {
+ this.setupAutomaticIndexing();
}
+
+ this.initialized = true;
+ log.info("Index service initialized");
}
/**
@@ -139,23 +110,7 @@ export class IndexService {
this.automaticIndexingInterval = setInterval(async () => {
try {
if (!this.indexingInProgress) {
- // Check if this instance should process embeddings
- const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client';
- const isSyncServer = await this.isSyncServerForEmbeddings();
- const shouldProcessEmbeddings = embeddingLocation === 'client' || isSyncServer;
-
- if (!shouldProcessEmbeddings) {
- // This instance is not configured to process embeddings
- return;
- }
-
- const stats = await vectorStore.getEmbeddingStats();
-
- // Only run automatic indexing if we're below 95% completion
- if (stats.percentComplete < 95) {
- log.info(`Starting automatic indexing (current completion: ${stats.percentComplete}%)`);
- await this.runBatchIndexing(50); // Process 50 notes at a time
- }
+ await this.runBatchIndexing(50); // Processing logic handles sync server checks
}
} catch (error: any) {
log.error(`Error in automatic indexing: ${error.message || "Unknown error"}`);
@@ -265,7 +220,7 @@ export class IndexService {
this.indexRebuildTotal = totalNotes;
log.info("No embeddings found, starting full embedding generation first");
- await vectorStore.reprocessAllNotes();
+ await this.reprocessAllNotes();
log.info("Full embedding generation initiated");
} else {
// For index rebuild, use the number of embeddings as the total
@@ -292,7 +247,7 @@ export class IndexService {
// Only start indexing if we're below 90% completion or if embeddings exist but need optimization
if (stats.percentComplete < 90) {
log.info("Embedding coverage below 90%, starting full embedding generation");
- await vectorStore.reprocessAllNotes();
+ await this.reprocessAllNotes();
log.info("Full embedding generation initiated");
} else {
log.info(`Embedding coverage at ${stats.percentComplete}%, starting index optimization`);
@@ -378,11 +333,10 @@ export class IndexService {
if (!shouldProcessEmbeddings) {
// This instance is not configured to process embeddings
- log.info("Skipping batch indexing as this instance is not configured to process embeddings");
return false;
}
- // Process the embedding queue
+ // Process the embedding queue (batch size is controlled by embeddingBatchSize option)
await vectorStore.processEmbeddingQueue();
return true;
@@ -491,51 +445,14 @@ export class IndexService {
}
try {
- // Get all enabled embedding providers
- const providers = await providerManager.getEnabledEmbeddingProviders();
- if (!providers || providers.length === 0) {
- throw new Error("No embedding providers available");
- }
-
- // Get the embedding provider precedence
- const options = (await import('../options.js')).default;
- let preferredProviders: string[] = [];
-
- const embeddingPrecedence = await options.getOption('embeddingProviderPrecedence');
- let provider;
-
- if (embeddingPrecedence) {
- // Parse the precedence string
- if (embeddingPrecedence.startsWith('[') && embeddingPrecedence.endsWith(']')) {
- preferredProviders = JSON.parse(embeddingPrecedence);
- } else if (typeof embeddingPrecedence === 'string') {
- if (embeddingPrecedence.includes(',')) {
- preferredProviders = embeddingPrecedence.split(',').map(p => p.trim());
- } else {
- preferredProviders = [embeddingPrecedence];
- }
- }
-
- // Find first enabled provider by precedence order
- for (const providerName of preferredProviders) {
- const matchedProvider = providers.find(p => p.name === providerName);
- if (matchedProvider) {
- provider = matchedProvider;
- break;
- }
- }
-
- // If no match found, use first available
- if (!provider && providers.length > 0) {
- provider = providers[0];
- }
- } else {
- // Default to first available provider
- provider = providers[0];
- }
+ // Get the selected embedding provider on-demand
+ const selectedEmbeddingProvider = await options.getOption('embeddingSelectedProvider');
+ const provider = selectedEmbeddingProvider
+ ? await providerManager.getOrCreateEmbeddingProvider(selectedEmbeddingProvider)
+ : (await providerManager.getEnabledEmbeddingProviders())[0];
if (!provider) {
- throw new Error("No suitable embedding provider found");
+ throw new Error("No embedding provider available");
}
log.info(`Searching with embedding provider: ${provider.name}, model: ${provider.getConfig().model}`);
@@ -693,6 +610,12 @@ export class IndexService {
}
try {
+ // Get embedding providers on-demand
+ const providers = await providerManager.getEnabledEmbeddingProviders();
+ if (providers.length === 0) {
+ return "I don't have access to your note embeddings. Please configure an embedding provider in your AI settings.";
+ }
+
// Find similar notes to the query
const similarNotes = await this.findSimilarNotes(
query,
@@ -828,9 +751,13 @@ export class IndexService {
// Get complete note context for indexing
const context = await vectorStore.getNoteEmbeddingContext(noteId);
- // Queue note for embedding with all available providers
- const providers = await providerManager.getEnabledEmbeddingProviders();
- for (const provider of providers) {
+ // Generate embedding with the selected provider
+ const selectedEmbeddingProvider = await options.getOption('embeddingSelectedProvider');
+ const provider = selectedEmbeddingProvider
+ ? await providerManager.getOrCreateEmbeddingProvider(selectedEmbeddingProvider)
+ : (await providerManager.getEnabledEmbeddingProviders())[0];
+
+ if (provider) {
try {
const embedding = await provider.generateNoteEmbeddings(context);
if (embedding) {
@@ -853,6 +780,189 @@ export class IndexService {
return false;
}
}
+
+ /**
+ * Start embedding generation (called when AI is enabled)
+ */
+ async startEmbeddingGeneration() {
+ try {
+ log.info("Starting embedding generation system");
+
+ const aiEnabled = options.getOptionOrNull('aiEnabled') === "true";
+ if (!aiEnabled) {
+ log.error("Cannot start embedding generation - AI features are disabled");
+ throw new Error("AI features must be enabled first");
+ }
+
+ // Re-initialize if needed
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ // Check if this instance should process embeddings
+ const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client';
+ const isSyncServer = await this.isSyncServerForEmbeddings();
+ const shouldProcessEmbeddings = embeddingLocation === 'client' || isSyncServer;
+
+ if (!shouldProcessEmbeddings) {
+ log.info("This instance is not configured to process embeddings");
+ return;
+ }
+
+ // Get embedding providers (will be created on-demand when needed)
+ const providers = await providerManager.getEnabledEmbeddingProviders();
+ if (providers.length === 0) {
+ log.info("No embedding providers configured, but continuing initialization");
+ } else {
+ log.info(`Found ${providers.length} embedding providers: ${providers.map(p => p.name).join(', ')}`);
+ }
+
+ // Setup automatic indexing if enabled
+ if (await options.getOptionBool('embeddingAutoUpdateEnabled')) {
+ this.setupAutomaticIndexing();
+ log.info(`Automatic embedding indexing started ${isSyncServer ? 'as sync server' : 'as client'}`);
+ }
+
+ // Start background processing of the embedding queue
+ const { setupEmbeddingBackgroundProcessing } = await import('./embeddings/events.js');
+ await setupEmbeddingBackgroundProcessing();
+
+ // Re-initialize event listeners
+ this.setupEventListeners();
+
+ // Queue notes that don't have embeddings for current providers
+ await this.queueNotesForMissingEmbeddings();
+
+ // Start processing the queue immediately
+ await this.runBatchIndexing(20);
+
+ log.info("Embedding generation started successfully");
+ } catch (error: any) {
+ log.error(`Error starting embedding generation: ${error.message || "Unknown error"}`);
+ throw error;
+ }
+ }
+
+
+
+ /**
+ * Queue notes that don't have embeddings for current provider settings
+ */
+ async queueNotesForMissingEmbeddings() {
+ try {
+ // Wait for becca to be fully loaded before accessing notes
+ await beccaLoader.beccaLoaded;
+
+ // Get all non-deleted notes
+ const allNotes = Object.values(becca.notes).filter(note => !note.isDeleted);
+
+ // Get enabled providers
+ const providers = await providerManager.getEnabledEmbeddingProviders();
+ if (providers.length === 0) {
+ return;
+ }
+
+ let queuedCount = 0;
+ let excludedCount = 0;
+
+ // Process notes in batches to avoid overwhelming the system
+ const batchSize = 100;
+ for (let i = 0; i < allNotes.length; i += batchSize) {
+ const batch = allNotes.slice(i, i + batchSize);
+
+ for (const note of batch) {
+ try {
+ // Skip notes excluded from AI
+ if (isNoteExcludedFromAI(note)) {
+ excludedCount++;
+ continue;
+ }
+
+ // Check if note needs embeddings for any enabled provider
+ let needsEmbedding = false;
+
+ for (const provider of providers) {
+ const config = provider.getConfig();
+ const existingEmbedding = await vectorStore.getEmbeddingForNote(
+ note.noteId,
+ provider.name,
+ config.model
+ );
+
+ if (!existingEmbedding) {
+ needsEmbedding = true;
+ break;
+ }
+ }
+
+ if (needsEmbedding) {
+ await vectorStore.queueNoteForEmbedding(note.noteId, 'UPDATE');
+ queuedCount++;
+ }
+ } catch (error: any) {
+ log.error(`Error checking embeddings for note ${note.noteId}: ${error.message || 'Unknown error'}`);
+ }
+ }
+
+ }
+ } catch (error: any) {
+ log.error(`Error queuing notes for missing embeddings: ${error.message || 'Unknown error'}`);
+ }
+ }
+
+ /**
+ * Reprocess all notes to update embeddings
+ */
+ async reprocessAllNotes() {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ try {
+ // Get all non-deleted note IDs
+ const noteIds = await sql.getColumn("SELECT noteId FROM notes WHERE isDeleted = 0");
+
+ // Process each note ID
+ for (const noteId of noteIds) {
+ await vectorStore.queueNoteForEmbedding(noteId as string, 'UPDATE');
+ }
+ } catch (error: any) {
+ log.error(`Error reprocessing all notes: ${error.message || 'Unknown error'}`);
+ throw error;
+ }
+ }
+
+ /**
+ * Stop embedding generation (called when AI is disabled)
+ */
+ async stopEmbeddingGeneration() {
+ try {
+ log.info("Stopping embedding generation system");
+
+ // Clear automatic indexing interval
+ if (this.automaticIndexingInterval) {
+ clearInterval(this.automaticIndexingInterval);
+ this.automaticIndexingInterval = undefined;
+ log.info("Automatic indexing stopped");
+ }
+
+ // Stop the background processing from embeddings/events.ts
+ const { stopEmbeddingBackgroundProcessing } = await import('./embeddings/events.js');
+ stopEmbeddingBackgroundProcessing();
+
+ // Clear all embedding providers to clean up resources
+ providerManager.clearAllEmbeddingProviders();
+
+ // Mark as not indexing
+ this.indexingInProgress = false;
+ this.indexRebuildInProgress = false;
+
+ log.info("Embedding generation stopped successfully");
+ } catch (error: any) {
+ log.error(`Error stopping embedding generation: ${error.message || "Unknown error"}`);
+ throw error;
+ }
+ }
}
// Create singleton instance
diff --git a/apps/server/src/services/llm/interfaces/ai_service_interfaces.ts b/apps/server/src/services/llm/interfaces/ai_service_interfaces.ts
index 3126691a4..52736cbd5 100644
--- a/apps/server/src/services/llm/interfaces/ai_service_interfaces.ts
+++ b/apps/server/src/services/llm/interfaces/ai_service_interfaces.ts
@@ -28,9 +28,9 @@ export interface AIServiceManagerConfig {
* Interface for managing AI service providers
*/
export interface IAIServiceManager {
- getService(provider?: string): AIService;
+ getService(provider?: string): Promise;
getAvailableProviders(): string[];
- getPreferredProvider(): string;
+ getSelectedProvider(): string;
isProviderAvailable(provider: string): boolean;
getProviderMetadata(provider: string): ProviderMetadata | null;
getAIEnabled(): boolean;
diff --git a/apps/server/src/services/llm/interfaces/configuration_interfaces.ts b/apps/server/src/services/llm/interfaces/configuration_interfaces.ts
index 5a03dc4f1..6adcac977 100644
--- a/apps/server/src/services/llm/interfaces/configuration_interfaces.ts
+++ b/apps/server/src/services/llm/interfaces/configuration_interfaces.ts
@@ -46,8 +46,8 @@ export interface ModelCapabilities {
*/
export interface AIConfig {
enabled: boolean;
- providerPrecedence: ProviderPrecedenceConfig;
- embeddingProviderPrecedence: EmbeddingProviderPrecedenceConfig;
+ selectedProvider: ProviderType | null;
+ selectedEmbeddingProvider: EmbeddingProviderType | null;
defaultModels: Record;
providerSettings: ProviderSettings;
}
@@ -87,7 +87,7 @@ export type ProviderType = 'openai' | 'anthropic' | 'ollama';
/**
* Valid embedding provider types
*/
-export type EmbeddingProviderType = 'openai' | 'ollama' | 'local';
+export type EmbeddingProviderType = 'openai' | 'voyage' | 'ollama' | 'local';
/**
* Model identifier with provider prefix (e.g., "openai:gpt-4" or "ollama:llama2")
diff --git a/apps/server/src/services/llm/pipeline/chat_pipeline.ts b/apps/server/src/services/llm/pipeline/chat_pipeline.ts
index 947a562e6..50671a809 100644
--- a/apps/server/src/services/llm/pipeline/chat_pipeline.ts
+++ b/apps/server/src/services/llm/pipeline/chat_pipeline.ts
@@ -298,6 +298,9 @@ export class ChatPipeline {
this.updateStageMetrics('llmCompletion', llmStartTime);
log.info(`Received LLM response from model: ${completion.response.model}, provider: ${completion.response.provider}`);
+ // Track whether content has been streamed to prevent duplication
+ let hasStreamedContent = false;
+
// Handle streaming if enabled and available
// Use shouldEnableStream variable which contains our streaming decision
if (shouldEnableStream && completion.response.stream && streamCallback) {
@@ -311,6 +314,9 @@ export class ChatPipeline {
// Forward to callback with original chunk data in case it contains additional information
streamCallback(processedChunk.text, processedChunk.done, chunk);
+
+ // Mark that we have streamed content to prevent duplication
+ hasStreamedContent = true;
});
}
@@ -767,11 +773,15 @@ export class ChatPipeline {
const responseText = currentResponse.text || "";
log.info(`Resuming streaming with final response: ${responseText.length} chars`);
- if (responseText.length > 0) {
- // Resume streaming with the final response text
+ if (responseText.length > 0 && !hasStreamedContent) {
+ // Resume streaming with the final response text only if we haven't already streamed content
// This is where we send the definitive done:true signal with the complete content
streamCallback(responseText, true);
log.info(`Sent final response with done=true signal and text content`);
+ } else if (hasStreamedContent) {
+ log.info(`Content already streamed, sending done=true signal only after tool execution`);
+ // Just send the done signal without duplicating content
+ streamCallback('', true);
} else {
// For Anthropic, sometimes text is empty but response is in stream
if ((currentResponse.provider === 'Anthropic' || currentResponse.provider === 'OpenAI') && currentResponse.stream) {
@@ -803,13 +813,17 @@ export class ChatPipeline {
log.info(`LLM response did not contain any tool calls, skipping tool execution`);
// Handle streaming for responses without tool calls
- if (shouldEnableStream && streamCallback) {
+ if (shouldEnableStream && streamCallback && !hasStreamedContent) {
log.info(`Sending final streaming response without tool calls: ${currentResponse.text.length} chars`);
// Send the final response with done=true to complete the streaming
streamCallback(currentResponse.text, true);
log.info(`Sent final non-tool response with done=true signal`);
+ } else if (shouldEnableStream && streamCallback && hasStreamedContent) {
+ log.info(`Content already streamed, sending done=true signal only`);
+ // Just send the done signal without duplicating content
+ streamCallback('', true);
}
}
diff --git a/apps/server/src/services/llm/pipeline/stages/context_extraction_stage.ts b/apps/server/src/services/llm/pipeline/stages/context_extraction_stage.ts
index 95d7620e2..b1eaa69f9 100644
--- a/apps/server/src/services/llm/pipeline/stages/context_extraction_stage.ts
+++ b/apps/server/src/services/llm/pipeline/stages/context_extraction_stage.ts
@@ -43,7 +43,7 @@ export class ContextExtractionStage {
// Get enhanced context from the context service
const contextService = aiServiceManager.getContextService();
- const llmService = aiServiceManager.getService();
+ const llmService = await aiServiceManager.getService();
if (contextService) {
// Use unified context service to get smart context
diff --git a/apps/server/src/services/llm/pipeline/stages/llm_completion_stage.ts b/apps/server/src/services/llm/pipeline/stages/llm_completion_stage.ts
index 7dd6984c8..6354e4c59 100644
--- a/apps/server/src/services/llm/pipeline/stages/llm_completion_stage.ts
+++ b/apps/server/src/services/llm/pipeline/stages/llm_completion_stage.ts
@@ -104,7 +104,7 @@ export class LLMCompletionStage extends BasePipelineStage SEARCH_CONSTANTS.CONTEXT.CONTENT_LENGTH.HIGH_THRESHOLD ? 'high' : 'medium';
}
- // Set the model and add provider metadata
- updatedOptions.model = modelName;
- this.addProviderMetadata(updatedOptions, preferredProvider as ServiceProviders, modelName);
+ // Add provider metadata (model is already set above)
+ this.addProviderMetadata(updatedOptions, selectedProvider as ServiceProviders, updatedOptions.model);
- log.info(`Selected model: ${modelName} from provider: ${preferredProvider} for query complexity: ${queryComplexity}`);
+ log.info(`Selected model: ${updatedOptions.model} from provider: ${selectedProvider} for query complexity: ${queryComplexity}`);
log.info(`[ModelSelectionStage] Final options: ${JSON.stringify({
model: updatedOptions.model,
stream: updatedOptions.stream,
- provider: preferredProvider,
+ provider: selectedProvider,
enableTools: updatedOptions.enableTools
})}`);
@@ -210,39 +219,41 @@ export class ModelSelectionStage extends BasePipelineStage {
try {
- // Use the new configuration system
- const providers = await getProviderPrecedence();
+ // Use the same logic as the main process method
+ const { getValidModelConfig, getSelectedProvider } = await import('../../config/configuration_helpers.js');
+ const selectedProvider = await getSelectedProvider();
- // Use only providers that are available
- const availableProviders = providers.filter(provider =>
- aiServiceManager.isProviderAvailable(provider));
-
- if (availableProviders.length === 0) {
- throw new Error('No AI providers are available');
+ if (!selectedProvider) {
+ throw new Error('No AI provider is selected. Please select a provider in your AI settings.');
}
- // Get the first available provider and its default model
- const defaultProvider = availableProviders[0];
- const defaultModel = await getDefaultModelForProvider(defaultProvider);
+ // Check if the provider is available through the service manager
+ if (!aiServiceManager.isProviderAvailable(selectedProvider)) {
+ throw new Error(`Selected provider ${selectedProvider} is not available`);
+ }
- if (!defaultModel) {
- throw new Error(`No default model configured for provider ${defaultProvider}. Please configure a default model in your AI settings.`);
+ // Try to get a valid model config
+ const modelConfig = await getValidModelConfig(selectedProvider);
+
+ if (!modelConfig) {
+ throw new Error(`No default model configured for provider ${selectedProvider}. Please configure a default model in your AI settings.`);
}
// Set provider metadata
if (!input.options.providerMetadata) {
input.options.providerMetadata = {
- provider: defaultProvider as 'openai' | 'anthropic' | 'ollama' | 'local',
- modelId: defaultModel
+ provider: selectedProvider as 'openai' | 'anthropic' | 'ollama' | 'local',
+ modelId: modelConfig.model
};
}
- log.info(`Selected default model ${defaultModel} from provider ${defaultProvider}`);
- return defaultModel;
+ log.info(`Selected default model ${modelConfig.model} from provider ${selectedProvider}`);
+ return modelConfig.model;
} catch (error) {
log.error(`Error determining default model: ${error}`);
throw error; // Don't provide fallback defaults, let the error propagate
@@ -271,4 +282,49 @@ export class ModelSelectionStage extends BasePipelineStage {
+ try {
+ log.info(`Getting default model for provider ${provider} using AI service manager`);
+
+ // Use the existing AI service manager instead of duplicating API calls
+ const service = await aiServiceManager.getInstance().getService(provider);
+
+ if (!service || !service.isAvailable()) {
+ log.info(`Provider ${provider} service is not available`);
+ return null;
+ }
+
+ // Check if the service has a method to get available models
+ if (typeof (service as any).getAvailableModels === 'function') {
+ try {
+ const models = await (service as any).getAvailableModels();
+ if (models && models.length > 0) {
+ // Use the first available model - no hardcoded preferences
+ const selectedModel = models[0];
+
+ // Import server-side options to update the default model
+ const optionService = (await import('../../../options.js')).default;
+ const optionKey = `${provider}DefaultModel` as const;
+
+ await optionService.setOption(optionKey, selectedModel);
+ log.info(`Set default ${provider} model to: ${selectedModel}`);
+ return selectedModel;
+ }
+ } catch (modelError) {
+ log.error(`Error fetching models from ${provider} service: ${modelError}`);
+ }
+ }
+
+ log.info(`Provider ${provider} does not support dynamic model fetching`);
+ return null;
+ } catch (error) {
+ log.error(`Error getting default model for provider ${provider}: ${error}`);
+ return null;
+ }
+ }
}
diff --git a/apps/server/src/services/llm/pipeline/stages/semantic_context_extraction_stage.ts b/apps/server/src/services/llm/pipeline/stages/semantic_context_extraction_stage.ts
index bf2cc8fd7..139510663 100644
--- a/apps/server/src/services/llm/pipeline/stages/semantic_context_extraction_stage.ts
+++ b/apps/server/src/services/llm/pipeline/stages/semantic_context_extraction_stage.ts
@@ -50,7 +50,7 @@ export class SemanticContextExtractionStage extends BasePipelineStage {
+ const result: ProviderValidationResult = {
+ hasValidProviders: false,
+ validEmbeddingProviders: [],
+ validChatProviders: [],
+ errors: [],
+ warnings: []
+ };
+
+ try {
+ // Check if AI is enabled
+ const aiEnabled = await options.getOptionBool('aiEnabled');
+ if (!aiEnabled) {
+ result.warnings.push("AI features are disabled");
+ return result;
+ }
+
+ // Check configuration only - don't create providers
+ await checkEmbeddingProviderConfigs(result);
+ await checkChatProviderConfigs(result);
+
+ // Determine if we have any valid providers based on configuration
+ result.hasValidProviders = result.validChatProviders.length > 0;
+
+ if (!result.hasValidProviders) {
+ result.errors.push("No valid AI providers are configured");
+ }
+
+ } catch (error: any) {
+ result.errors.push(`Error during provider validation: ${error.message || 'Unknown error'}`);
+ }
+
+ return result;
+}
+
+/**
+ * Check embedding provider configurations without creating providers
+ */
+async function checkEmbeddingProviderConfigs(result: ProviderValidationResult): Promise {
+ try {
+ // Check OpenAI embedding configuration
+ const openaiApiKey = await options.getOption('openaiApiKey');
+ const openaiBaseUrl = await options.getOption('openaiBaseUrl');
+ if (openaiApiKey || openaiBaseUrl) {
+ if (!openaiApiKey) {
+ result.warnings.push("OpenAI embedding: No API key (may work with compatible endpoints)");
+ }
+ log.info("OpenAI embedding provider configuration available");
+ }
+
+ // Check Ollama embedding configuration
+ const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
+ if (ollamaEmbeddingBaseUrl) {
+ log.info("Ollama embedding provider configuration available");
+ }
+
+ // Check Voyage embedding configuration
+ const voyageApiKey = await options.getOption('voyageApiKey' as any);
+ if (voyageApiKey) {
+ log.info("Voyage embedding provider configuration available");
+ }
+
+ // Local provider is always available
+ log.info("Local embedding provider available as fallback");
+
+ } catch (error: any) {
+ result.errors.push(`Error checking embedding provider configs: ${error.message || 'Unknown error'}`);
+ }
+}
+
+/**
+ * Check chat provider configurations without creating providers
+ */
+async function checkChatProviderConfigs(result: ProviderValidationResult): Promise {
+ try {
+ // Check OpenAI chat provider
+ const openaiApiKey = await options.getOption('openaiApiKey');
+ const openaiBaseUrl = await options.getOption('openaiBaseUrl');
+
+ if (openaiApiKey || openaiBaseUrl) {
+ if (!openaiApiKey) {
+ result.warnings.push("OpenAI chat: No API key (may work with compatible endpoints)");
+ }
+ result.validChatProviders.push('openai');
+ }
+
+ // Check Anthropic chat provider
+ const anthropicApiKey = await options.getOption('anthropicApiKey');
+ if (anthropicApiKey) {
+ result.validChatProviders.push('anthropic');
+ }
+
+ // Check Ollama chat provider
+ const ollamaBaseUrl = await options.getOption('ollamaBaseUrl');
+ if (ollamaBaseUrl) {
+ result.validChatProviders.push('ollama');
+ }
+
+ if (result.validChatProviders.length === 0) {
+ result.warnings.push("No chat providers configured. Please configure at least one provider.");
+ }
+
+ } catch (error: any) {
+ result.errors.push(`Error checking chat provider configs: ${error.message || 'Unknown error'}`);
+ }
+}
+
+
+/**
+ * Check if any chat providers are configured
+ */
+export async function hasWorkingChatProviders(): Promise {
+ const validation = await validateProviders();
+ return validation.validChatProviders.length > 0;
+}
+
+/**
+ * Check if any embedding providers are configured (simplified)
+ */
+export async function hasWorkingEmbeddingProviders(): Promise {
+ if (!(await options.getOptionBool('aiEnabled'))) {
+ return false;
+ }
+
+ // Check if any embedding provider is configured
+ const openaiKey = await options.getOption('openaiApiKey');
+ const openaiBaseUrl = await options.getOption('openaiBaseUrl');
+ const ollamaUrl = await options.getOption('ollamaEmbeddingBaseUrl');
+ const voyageKey = await options.getOption('voyageApiKey' as any);
+
+ // Local provider is always available as fallback
+ return !!(openaiKey || openaiBaseUrl || ollamaUrl || voyageKey) || true;
+}
+
+/**
+ * Log validation results in a user-friendly way
+ */
+export function logValidationResults(validation: ProviderValidationResult): void {
+ if (validation.hasValidProviders) {
+ log.info(`AI provider validation passed: ${validation.validEmbeddingProviders.length} embedding providers, ${validation.validChatProviders.length} chat providers`);
+
+ if (validation.validEmbeddingProviders.length > 0) {
+ log.info(`Working embedding providers: ${validation.validEmbeddingProviders.map(p => p.name).join(', ')}`);
+ }
+
+ if (validation.validChatProviders.length > 0) {
+ log.info(`Working chat providers: ${validation.validChatProviders.join(', ')}`);
+ }
+ } else {
+ log.info("AI provider validation failed: No working providers found");
+ }
+
+ validation.warnings.forEach(warning => log.info(`Provider validation: ${warning}`));
+ validation.errors.forEach(error => log.error(`Provider validation: ${error}`));
+}
\ No newline at end of file
diff --git a/apps/server/src/services/llm/providers/anthropic_service.ts b/apps/server/src/services/llm/providers/anthropic_service.ts
index a533acf4a..ed034bdfd 100644
--- a/apps/server/src/services/llm/providers/anthropic_service.ts
+++ b/apps/server/src/services/llm/providers/anthropic_service.ts
@@ -606,4 +606,12 @@ export class AnthropicService extends BaseAIService {
return convertedTools;
}
+
+ /**
+ * Clear cached Anthropic client to force recreation with new settings
+ */
+ clearCache(): void {
+ this.client = null;
+ log.info('Anthropic client cache cleared');
+ }
}
diff --git a/apps/server/src/services/llm/providers/ollama_service.ts b/apps/server/src/services/llm/providers/ollama_service.ts
index 750118027..4ebbbaa4b 100644
--- a/apps/server/src/services/llm/providers/ollama_service.ts
+++ b/apps/server/src/services/llm/providers/ollama_service.ts
@@ -526,4 +526,13 @@ export class OllamaService extends BaseAIService {
log.info(`Added tool execution feedback: ${toolExecutionStatus.length} statuses`);
return updatedMessages;
}
+
+ /**
+ * Clear cached Ollama client to force recreation with new settings
+ */
+ clearCache(): void {
+ // Ollama service doesn't maintain a persistent client like OpenAI/Anthropic
+ // but we can clear any future cached state here if needed
+ log.info('Ollama client cache cleared (no persistent client to clear)');
+ }
}
diff --git a/apps/server/src/services/llm/providers/openai_service.ts b/apps/server/src/services/llm/providers/openai_service.ts
index e0633ca1f..411f512f7 100644
--- a/apps/server/src/services/llm/providers/openai_service.ts
+++ b/apps/server/src/services/llm/providers/openai_service.ts
@@ -14,7 +14,9 @@ export class OpenAIService extends BaseAIService {
}
override isAvailable(): boolean {
- return super.isAvailable() && !!options.getOption('openaiApiKey');
+ // Make API key optional to support OpenAI-compatible endpoints that don't require authentication
+ // The provider is considered available as long as the parent checks pass
+ return super.isAvailable();
}
private getClient(apiKey: string, baseUrl?: string): OpenAI {
@@ -29,7 +31,7 @@ export class OpenAIService extends BaseAIService {
async generateChatCompletion(messages: Message[], opts: ChatCompletionOptions = {}): Promise {
if (!this.isAvailable()) {
- throw new Error('OpenAI service is not available. Check API key and AI settings.');
+ throw new Error('OpenAI service is not available. Check AI settings.');
}
// Get provider-specific options from the central provider manager
@@ -257,4 +259,12 @@ export class OpenAIService extends BaseAIService {
throw error;
}
}
+
+ /**
+ * Clear cached OpenAI client to force recreation with new settings
+ */
+ clearCache(): void {
+ this.openai = null;
+ log.info('OpenAI client cache cleared');
+ }
}
diff --git a/apps/server/src/services/llm/providers/providers.ts b/apps/server/src/services/llm/providers/providers.ts
index f4d69801c..dae8b34a0 100644
--- a/apps/server/src/services/llm/providers/providers.ts
+++ b/apps/server/src/services/llm/providers/providers.ts
@@ -86,6 +86,29 @@ export function registerEmbeddingProvider(provider: EmbeddingProvider) {
log.info(`Registered embedding provider: ${provider.name}`);
}
+/**
+ * Unregister an embedding provider
+ */
+export function unregisterEmbeddingProvider(name: string): boolean {
+ const existed = providers.has(name);
+ if (existed) {
+ providers.delete(name);
+ log.info(`Unregistered embedding provider: ${name}`);
+ }
+ return existed;
+}
+
+/**
+ * Clear all embedding providers
+ */
+export function clearAllEmbeddingProviders(): void {
+ const providerNames = Array.from(providers.keys());
+ providers.clear();
+ if (providerNames.length > 0) {
+ log.info(`Cleared all embedding providers: ${providerNames.join(', ')}`);
+ }
+}
+
/**
* Get all registered embedding providers
*/
@@ -101,35 +124,126 @@ export function getEmbeddingProvider(name: string): EmbeddingProvider | undefine
}
/**
- * Get all enabled embedding providers
+ * Get or create a specific embedding provider with inline validation
*/
-export async function getEnabledEmbeddingProviders(): Promise {
+export async function getOrCreateEmbeddingProvider(providerName: string): Promise {
+ // Return existing provider if already created and valid
+ const existing = providers.get(providerName);
+ if (existing) {
+ return existing;
+ }
+
+ // Create and validate provider on-demand
+ try {
+ let provider: EmbeddingProvider | null = null;
+
+ switch (providerName) {
+ case 'ollama': {
+ const baseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
+ if (!baseUrl) return null;
+
+ const model = await options.getOption('ollamaEmbeddingModel');
+ provider = new OllamaEmbeddingProvider({
+ model,
+ dimension: 768,
+ type: 'float32',
+ baseUrl
+ });
+
+ // Validate by initializing (if provider supports it)
+ if ('initialize' in provider && typeof provider.initialize === 'function') {
+ await provider.initialize();
+ }
+ break;
+ }
+
+ case 'openai': {
+ const apiKey = await options.getOption('openaiApiKey');
+ const baseUrl = await options.getOption('openaiBaseUrl');
+ if (!apiKey && !baseUrl) return null;
+
+ const model = await options.getOption('openaiEmbeddingModel');
+ provider = new OpenAIEmbeddingProvider({
+ model,
+ dimension: 1536,
+ type: 'float32',
+ apiKey: apiKey || '',
+ baseUrl: baseUrl || 'https://api.openai.com/v1'
+ });
+
+ if (!apiKey) {
+ log.info('OpenAI embedding provider created without API key for compatible endpoints');
+ }
+ break;
+ }
+
+ case 'voyage': {
+ const apiKey = await options.getOption('voyageApiKey' as any);
+ if (!apiKey) return null;
+
+ const model = await options.getOption('voyageEmbeddingModel') || 'voyage-2';
+ provider = new VoyageEmbeddingProvider({
+ model,
+ dimension: 1024,
+ type: 'float32',
+ apiKey,
+ baseUrl: 'https://api.voyageai.com/v1'
+ });
+ break;
+ }
+
+ case 'local': {
+ provider = new SimpleLocalEmbeddingProvider({
+ model: 'local',
+ dimension: 384,
+ type: 'float32'
+ });
+ break;
+ }
+
+ default:
+ return null;
+ }
+
+ if (provider) {
+ registerEmbeddingProvider(provider);
+ log.info(`Created and validated ${providerName} embedding provider`);
+ return provider;
+ }
+ } catch (error: any) {
+ log.error(`Failed to create ${providerName} embedding provider: ${error.message || 'Unknown error'}`);
+ }
+
+ return null;
+}
+
+/**
+ * Get all enabled embedding providers for the specified feature
+ */
+export async function getEnabledEmbeddingProviders(feature: 'embeddings' | 'chat' = 'embeddings'): Promise {
if (!(await options.getOptionBool('aiEnabled'))) {
return [];
}
- // Get providers from database ordered by priority
- const dbProviders = await sql.getRows(`
- SELECT providerId, name, config
- FROM embedding_providers
- ORDER BY priority DESC`
- );
-
const result: EmbeddingProvider[] = [];
- for (const row of dbProviders) {
- const rowData = row as any;
- const provider = providers.get(rowData.name);
+ // Get the selected provider for the feature
+ const selectedProvider = feature === 'embeddings'
+ ? await options.getOption('embeddingSelectedProvider')
+ : await options.getOption('aiSelectedProvider');
- if (provider) {
- result.push(provider);
- } else {
- // Only log error if we haven't logged it before for this provider
- if (!loggedProviderErrors.has(rowData.name)) {
- log.error(`Enabled embedding provider ${rowData.name} not found in registered providers`);
- loggedProviderErrors.add(rowData.name);
- }
+ // Try to get or create the specific selected provider
+ const provider = await getOrCreateEmbeddingProvider(selectedProvider);
+ if (!provider) {
+ throw new Error(`Failed to create selected embedding provider: ${selectedProvider}. Please check your configuration.`);
}
+ result.push(provider);
+
+
+ // Always ensure local provider as fallback
+ const localProvider = await getOrCreateEmbeddingProvider('local');
+ if (localProvider && !result.some(p => p.name === 'local')) {
+ result.push(localProvider);
}
return result;
@@ -232,144 +346,18 @@ export async function getEmbeddingProviderConfigs() {
return await sql.getRows("SELECT * FROM embedding_providers ORDER BY priority DESC");
}
-/**
- * Initialize the default embedding providers
- */
-export async function initializeDefaultProviders() {
- // Register built-in providers
- try {
- // Register OpenAI provider if API key is configured
- const openaiApiKey = await options.getOption('openaiApiKey');
- if (openaiApiKey) {
- const openaiModel = await options.getOption('openaiEmbeddingModel') || 'text-embedding-3-small';
- const openaiBaseUrl = await options.getOption('openaiBaseUrl') || 'https://api.openai.com/v1';
-
- registerEmbeddingProvider(new OpenAIEmbeddingProvider({
- model: openaiModel,
- dimension: 1536, // OpenAI's typical dimension
- type: 'float32',
- apiKey: openaiApiKey,
- baseUrl: openaiBaseUrl
- }));
-
- // Create OpenAI provider config if it doesn't exist
- const existingOpenAI = await sql.getRow(
- "SELECT * FROM embedding_providers WHERE name = ?",
- ['openai']
- );
-
- if (!existingOpenAI) {
- await createEmbeddingProviderConfig('openai', {
- model: openaiModel,
- dimension: 1536,
- type: 'float32'
- }, 100);
- }
- }
-
- // Register Voyage provider if API key is configured
- const voyageApiKey = await options.getOption('voyageApiKey' as any);
- if (voyageApiKey) {
- const voyageModel = await options.getOption('voyageEmbeddingModel') || 'voyage-2';
- const voyageBaseUrl = 'https://api.voyageai.com/v1';
-
- registerEmbeddingProvider(new VoyageEmbeddingProvider({
- model: voyageModel,
- dimension: 1024, // Voyage's embedding dimension
- type: 'float32',
- apiKey: voyageApiKey,
- baseUrl: voyageBaseUrl
- }));
-
- // Create Voyage provider config if it doesn't exist
- const existingVoyage = await sql.getRow(
- "SELECT * FROM embedding_providers WHERE name = ?",
- ['voyage']
- );
-
- if (!existingVoyage) {
- await createEmbeddingProviderConfig('voyage', {
- model: voyageModel,
- dimension: 1024,
- type: 'float32'
- }, 75);
- }
- }
-
- // Register Ollama provider if base URL is configured
- const ollamaBaseUrl = await options.getOption('ollamaBaseUrl');
- if (ollamaBaseUrl) {
- // Use specific embedding models if available
- const embeddingModel = await options.getOption('ollamaEmbeddingModel');
-
- try {
- // Create provider with initial dimension to be updated during initialization
- const ollamaProvider = new OllamaEmbeddingProvider({
- model: embeddingModel,
- dimension: 768, // Initial value, will be updated during initialization
- type: 'float32',
- baseUrl: ollamaBaseUrl
- });
-
- // Register the provider
- registerEmbeddingProvider(ollamaProvider);
-
- // Initialize the provider to detect model capabilities
- await ollamaProvider.initialize();
-
- // Create Ollama provider config if it doesn't exist
- const existingOllama = await sql.getRow(
- "SELECT * FROM embedding_providers WHERE name = ?",
- ['ollama']
- );
-
- if (!existingOllama) {
- await createEmbeddingProviderConfig('ollama', {
- model: embeddingModel,
- dimension: ollamaProvider.getDimension(),
- type: 'float32'
- }, 50);
- }
- } catch (error: any) {
- log.error(`Error initializing Ollama embedding provider: ${error.message || 'Unknown error'}`);
- }
- }
-
- // Always register local provider as fallback
- registerEmbeddingProvider(new SimpleLocalEmbeddingProvider({
- model: 'local',
- dimension: 384,
- type: 'float32'
- }));
-
- // Create local provider config if it doesn't exist
- const existingLocal = await sql.getRow(
- "SELECT * FROM embedding_providers WHERE name = ?",
- ['local']
- );
-
- if (!existingLocal) {
- await createEmbeddingProviderConfig('local', {
- model: 'local',
- dimension: 384,
- type: 'float32'
- }, 10);
- }
- } catch (error: any) {
- log.error(`Error initializing default embedding providers: ${error.message || 'Unknown error'}`);
- }
-}
-
export default {
registerEmbeddingProvider,
+ unregisterEmbeddingProvider,
+ clearAllEmbeddingProviders,
getEmbeddingProviders,
getEmbeddingProvider,
getEnabledEmbeddingProviders,
+ getOrCreateEmbeddingProvider,
createEmbeddingProviderConfig,
updateEmbeddingProviderConfig,
deleteEmbeddingProviderConfig,
- getEmbeddingProviderConfigs,
- initializeDefaultProviders
+ getEmbeddingProviderConfigs
};
/**
@@ -382,7 +370,8 @@ export function getOpenAIOptions(
try {
const apiKey = options.getOption('openaiApiKey');
if (!apiKey) {
- throw new Error('OpenAI API key is not configured');
+ // Log warning but don't throw - some OpenAI-compatible endpoints don't require API keys
+ log.info('OpenAI API key is not configured. This may cause issues with official OpenAI endpoints.');
}
const baseUrl = options.getOption('openaiBaseUrl') || PROVIDER_CONSTANTS.OPENAI.BASE_URL;
@@ -407,7 +396,7 @@ export function getOpenAIOptions(
return {
// Connection settings
- apiKey,
+ apiKey: apiKey || '', // Default to empty string if no API key
baseUrl,
// Provider metadata
diff --git a/apps/server/src/services/llm/tools/note_summarization_tool.ts b/apps/server/src/services/llm/tools/note_summarization_tool.ts
index bc5999e0c..8fa5d39d8 100644
--- a/apps/server/src/services/llm/tools/note_summarization_tool.ts
+++ b/apps/server/src/services/llm/tools/note_summarization_tool.ts
@@ -102,12 +102,7 @@ export class NoteSummarizationTool implements ToolHandler {
const cleanContent = this.cleanHtml(content);
// Generate the summary using the AI service
- const aiService = aiServiceManager.getService();
-
- if (!aiService) {
- log.error('No AI service available for summarization');
- return `Error: No AI service is available for summarization`;
- }
+ const aiService = await aiServiceManager.getService();
log.info(`Using ${aiService.getName()} to generate summary`);
diff --git a/apps/server/src/services/llm/tools/relationship_tool.ts b/apps/server/src/services/llm/tools/relationship_tool.ts
index a7023981d..d0a91c98d 100644
--- a/apps/server/src/services/llm/tools/relationship_tool.ts
+++ b/apps/server/src/services/llm/tools/relationship_tool.ts
@@ -312,16 +312,7 @@ export class RelationshipTool implements ToolHandler {
}
// Get the AI service for relationship suggestion
- const aiService = aiServiceManager.getService();
-
- if (!aiService) {
- log.error('No AI service available for relationship suggestions');
- return {
- success: false,
- message: 'AI service not available for relationship suggestions',
- relatedNotes: relatedResult.relatedNotes
- };
- }
+ const aiService = await aiServiceManager.getService();
log.info(`Using ${aiService.getName()} to suggest relationships for ${relatedResult.relatedNotes.length} related notes`);
diff --git a/apps/server/src/services/llm/tools/search_notes_tool.ts b/apps/server/src/services/llm/tools/search_notes_tool.ts
index 09b3fc645..152187dec 100644
--- a/apps/server/src/services/llm/tools/search_notes_tool.ts
+++ b/apps/server/src/services/llm/tools/search_notes_tool.ts
@@ -122,10 +122,10 @@ export class SearchNotesTool implements ToolHandler {
// If summarization is requested
if (summarize) {
// Try to get an LLM service for summarization
- const llmService = aiServiceManager.getService();
- if (llmService) {
- try {
- const messages = [
+ try {
+ const llmService = await aiServiceManager.getService();
+
+ const messages = [
{
role: "system" as const,
content: "Summarize the following note content concisely while preserving key information. Keep your summary to about 3-4 sentences."
@@ -147,13 +147,12 @@ export class SearchNotesTool implements ToolHandler {
} as Record))
});
- if (result && result.text) {
- return result.text;
- }
- } catch (error) {
- log.error(`Error summarizing content: ${error}`);
- // Fall through to smart truncation if summarization fails
+ if (result && result.text) {
+ return result.text;
}
+ } catch (error) {
+ log.error(`Error summarizing content: ${error}`);
+ // Fall through to smart truncation if summarization fails
}
}
diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts
index 212f47366..5311a67f0 100644
--- a/apps/server/src/services/options_init.ts
+++ b/apps/server/src/services/options_init.ts
@@ -195,26 +195,32 @@ const defaultOptions: DefaultOption[] = [
// AI Options
{ name: "aiEnabled", value: "false", isSynced: true },
{ name: "openaiApiKey", value: "", isSynced: false },
- { name: "openaiDefaultModel", value: "gpt-4o", isSynced: true },
- { name: "openaiEmbeddingModel", value: "text-embedding-3-small", isSynced: true },
+ { name: "openaiDefaultModel", value: "", isSynced: true },
+ { name: "openaiEmbeddingModel", value: "", isSynced: true },
{ name: "openaiBaseUrl", value: "https://api.openai.com/v1", isSynced: true },
{ name: "anthropicApiKey", value: "", isSynced: false },
- { name: "anthropicDefaultModel", value: "claude-3-opus-20240229", isSynced: true },
- { name: "voyageEmbeddingModel", value: "voyage-2", isSynced: true },
+ { name: "anthropicDefaultModel", value: "", isSynced: true },
+ { name: "voyageEmbeddingModel", value: "", isSynced: true },
{ name: "voyageApiKey", value: "", isSynced: false },
{ name: "anthropicBaseUrl", value: "https://api.anthropic.com/v1", isSynced: true },
{ name: "ollamaEnabled", value: "false", isSynced: true },
- { name: "ollamaDefaultModel", value: "llama3", isSynced: true },
+ { name: "ollamaDefaultModel", value: "", isSynced: true },
{ name: "ollamaBaseUrl", value: "http://localhost:11434", isSynced: true },
- { name: "ollamaEmbeddingModel", value: "nomic-embed-text", isSynced: true },
+ { name: "ollamaEmbeddingModel", value: "", isSynced: true },
{ name: "embeddingAutoUpdateEnabled", value: "true", isSynced: true },
+ // Embedding-specific provider options
+ { name: "openaiEmbeddingApiKey", value: "", isSynced: false },
+ { name: "openaiEmbeddingBaseUrl", value: "https://api.openai.com/v1", isSynced: true },
+ { name: "voyageEmbeddingBaseUrl", value: "https://api.voyageai.com/v1", isSynced: true },
+ { name: "ollamaEmbeddingBaseUrl", value: "http://localhost:11434", isSynced: true },
+
// Adding missing AI options
{ name: "aiTemperature", value: "0.7", isSynced: true },
{ name: "aiSystemPrompt", value: "", isSynced: true },
- { name: "aiProviderPrecedence", value: "openai,anthropic,ollama", isSynced: true },
+ { name: "aiSelectedProvider", value: "openai", isSynced: true },
{ name: "embeddingDimensionStrategy", value: "auto", isSynced: true },
- { name: "embeddingProviderPrecedence", value: "openai,voyage,ollama,local", isSynced: true },
+ { name: "embeddingSelectedProvider", value: "openai", isSynced: true },
{ name: "embeddingSimilarityThreshold", value: "0.75", isSynced: true },
{ name: "enableAutomaticIndexing", value: "true", isSynced: true },
{ name: "maxNotesPerLlmQuery", value: "3", isSynced: true },
diff --git a/package.json b/package.json
index 57d53a28e..c5d6308c4 100644
--- a/package.json
+++ b/package.json
@@ -27,16 +27,16 @@
"private": true,
"devDependencies": {
"@electron/rebuild": "4.0.1",
- "@nx/devkit": "21.1.2",
- "@nx/esbuild": "21.1.2",
- "@nx/eslint": "21.1.2",
- "@nx/eslint-plugin": "21.1.2",
- "@nx/express": "21.1.2",
- "@nx/js": "21.1.2",
- "@nx/node": "21.1.2",
- "@nx/playwright": "21.1.2",
- "@nx/vite": "21.1.2",
- "@nx/web": "21.1.2",
+ "@nx/devkit": "21.1.3",
+ "@nx/esbuild": "21.1.3",
+ "@nx/eslint": "21.1.3",
+ "@nx/eslint-plugin": "21.1.3",
+ "@nx/express": "21.1.3",
+ "@nx/js": "21.1.3",
+ "@nx/node": "21.1.3",
+ "@nx/playwright": "21.1.3",
+ "@nx/vite": "21.1.3",
+ "@nx/web": "21.1.3",
"@playwright/test": "^1.36.0",
"@triliumnext/server": "workspace:*",
"@types/express": "^4.17.21",
@@ -53,7 +53,7 @@
"jiti": "2.4.2",
"jsdom": "~26.1.0",
"jsonc-eslint-parser": "^2.1.0",
- "nx": "21.1.2",
+ "nx": "21.1.3",
"react-refresh": "^0.17.0",
"tslib": "^2.3.0",
"tsx": "4.19.4",
diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts
index 32f731936..77c3b2a68 100644
--- a/packages/commons/src/lib/options_interface.ts
+++ b/packages/commons/src/lib/options_interface.ts
@@ -132,26 +132,29 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions=16.0.0}
deprecated: This functionality has been moved to @npmcli/fs
- '@nx/devkit@21.1.2':
- resolution: {integrity: sha512-1dgjwSsNDdp/VXydZnSfzfVwySEB3C9yjzeIw6+3+nRvZfH16a7ggZE7MF5sJTq4d+01hAgIDz3KyvGa6Jf73g==}
+ '@nx/devkit@21.1.3':
+ resolution: {integrity: sha512-NSNXdn+PaNoPcxAKIhnZUbOA91Jzgk68paZEiABzAhkvfmrE5jM6VDMT6sJZ8lHWocrf6QFnzAOon1R4MoBeZw==}
peerDependencies:
- nx: 21.1.2
+ nx: 21.1.3
- '@nx/esbuild@21.1.2':
- resolution: {integrity: sha512-6h3f8mC/5e2JxFAJaE4kLALkaoAs0nVB3aFBV+nd3+0mwywbcnMQ+dibvGCrBz2EPYlWczo43upAFEvvqpdUag==}
+ '@nx/esbuild@21.1.3':
+ resolution: {integrity: sha512-yYbD5wtc0nsSJq7v6F/tbfZwAxvvZfLxowdK4f30RgPdMnIgVW8Aqwyu3czDnLrmORJeuZ10NiGHp5pZtkGeYQ==}
peerDependencies:
esbuild: '>=0.25.0'
peerDependenciesMeta:
esbuild:
optional: true
- '@nx/eslint-plugin@21.1.2':
- resolution: {integrity: sha512-kwhwe6e8dZ0pf5CYPq4OBck15NEJrfuivCEGRTIDZWu3WDYJIw7OvhfyCdGuoZLeHGoCVRjIU6xV5hOzkD9RSw==}
+ '@nx/eslint-plugin@21.1.3':
+ resolution: {integrity: sha512-xmh3bsK7yVQiEm0O5C3cD/J1P++iWQbEUl5rnysNxgHLh6gxkIh+4GLyRS8/05gbd6+JD1WKuzn77/wGq1gohw==}
peerDependencies:
'@typescript-eslint/parser': ^6.13.2 || ^7.0.0 || ^8.0.0
eslint-config-prettier: ^10.0.0
@@ -3339,8 +3339,8 @@ packages:
eslint-config-prettier:
optional: true
- '@nx/eslint@21.1.2':
- resolution: {integrity: sha512-Mp8u0RlkhxYtZ47d2ou6t8XIpRy7N/n23OzikqMro4Wt/DK1irGyShSoNIqdGdwalAE5MG1OFXspttXB+y/wOQ==}
+ '@nx/eslint@21.1.3':
+ resolution: {integrity: sha512-g4Os1AfTjS+51a6+X+5ZgY/J7TGIKdc1byORreaSnLXtN9BU6r4WKzGkT5TAAXS+UXXmSih7QAJhKPur2IHddQ==}
peerDependencies:
'@zkochan/js-yaml': 0.0.7
eslint: ^8.0.0 || ^9.0.0
@@ -3348,97 +3348,97 @@ packages:
'@zkochan/js-yaml':
optional: true
- '@nx/express@21.1.2':
- resolution: {integrity: sha512-YYulIUJY9Hm2U4qWXJXgWFvIY2Hj39jdVs8p+Tsy/LuEV1NfDu1xAZxPdedQlevEvl3+KLCrt2SJ7JVohjvk7g==}
+ '@nx/express@21.1.3':
+ resolution: {integrity: sha512-CbfxgkDJmx6iz8eegcNDa+ygPbfyQ7lIVwEDLGECFEaL5W48IwDlydbBYjbtPD2VfXFHX1z8zbgeuC1zMnWuKA==}
peerDependencies:
express: ^4.21.2
peerDependenciesMeta:
express:
optional: true
- '@nx/jest@21.1.2':
- resolution: {integrity: sha512-y4VZita9LFb6XajulRIwjMcqHU6/f73C4SNSH6IM5BYmkN68ovICmzTGvoaL7wGTaYrA4Moh/WoKwEwQWKxRPQ==}
+ '@nx/jest@21.1.3':
+ resolution: {integrity: sha512-Wn3dqxvJ+O3OYiJ/h0Mmr4huc3JS+nZquUMAm19aJS8y6QVWRzGQGPxEJQ5jzoe407VMIKjiQ4LPhoV/xrNwbA==}
- '@nx/js@21.1.2':
- resolution: {integrity: sha512-ZF6Zf4Ys+RBvH0GoQHio94C/0N07Px/trAvseMuQ8PKc0tSkXycu/EBc1uAZQvgJThR5o3diAKtIQug77pPYMQ==}
+ '@nx/js@21.1.3':
+ resolution: {integrity: sha512-pwn1tgWX8sxh+VKZRZl9VkabXkEyeELFCgkWS/on2Y1J6W2dMBcmyGuZAeLef2GkUNaR79VMWIqvPaK0JLyf4g==}
peerDependencies:
verdaccio: ^6.0.5
peerDependenciesMeta:
verdaccio:
optional: true
- '@nx/node@21.1.2':
- resolution: {integrity: sha512-BCKooOKT04MJDzLy6U4w3mFWhHCsuoMXqUjcd5g/3zf4bFXOK3ooklvVkxjHUQxRXVG/uPJ+ZcgTC1SE0vpS6g==}
+ '@nx/node@21.1.3':
+ resolution: {integrity: sha512-tFsdkQ7RJZjAmivRkmVRAbY3ck7u+RLhcswelx2kXRsRozrWxX4Da+pECUePt2wq/HnrBGujPgGrZz7pMpZrbQ==}
- '@nx/nx-darwin-arm64@21.1.2':
- resolution: {integrity: sha512-9dO32jd+h7SrvQafJph6b7Bsmp2IotTE0w7dAGb4MGBQni3JWCXaxlMMpWUZXWW1pM5uIkFJO5AASW4UOI7w2w==}
+ '@nx/nx-darwin-arm64@21.1.3':
+ resolution: {integrity: sha512-gbBKQrw9ecjXHVs7Kwaht5Dip//NBCgmnkf3GGoA40ad3zyvHDe+MBWMxueRToUVW/mDPh8b5lvLbmFApiY6sQ==}
cpu: [arm64]
os: [darwin]
- '@nx/nx-darwin-x64@21.1.2':
- resolution: {integrity: sha512-5sf+4PRVg9pDVgD53NE1hoPz4lC8Ni34UovQsOrZgDvwU5mqPbIhTzVYRDH86i/086AcCvjT5tEt7rEcuRwlKw==}
+ '@nx/nx-darwin-x64@21.1.3':
+ resolution: {integrity: sha512-yGDWqxwNty1BJcuvZlwGGravAhg8eIRMEIp2omfIxeyfZEVA4b7egwMCqczwU2Li/StNjTtzrUe1HPWgcCVAuQ==}
cpu: [x64]
os: [darwin]
- '@nx/nx-freebsd-x64@21.1.2':
- resolution: {integrity: sha512-E5HR44fimXlQuAgn/tP9esmvxbzt/92AIl0PBT6L3Juh/xYiXKWhda63H4+UNT8AcLRxVXwfZrGPuGCDs+7y/Q==}
+ '@nx/nx-freebsd-x64@21.1.3':
+ resolution: {integrity: sha512-vpZPfSQgNIQ0vmnQA26DlJKZog20ISdS14ir234mvCaJJFdlgWGcpyEOSCU3Vg+32Z/VsSx7kIkBwRhfEZ73Ag==}
cpu: [x64]
os: [freebsd]
- '@nx/nx-linux-arm-gnueabihf@21.1.2':
- resolution: {integrity: sha512-V4n6DE+r12gwJHFjZs+e2GmWYZdhpgA2DYWbsYWRYb1XQCNUg4vPzt+YFzWZ+K2o91k93EBnlLfrag7CqxUslw==}
+ '@nx/nx-linux-arm-gnueabihf@21.1.3':
+ resolution: {integrity: sha512-R2GzEyHvyree2m7w+e/MOZjUY/l99HbW4E/jJl5BBXRGEAnGTIx9fOxSDiOW5QK6U0oZb2YO2b565t+IC+7rBQ==}
cpu: [arm]
os: [linux]
- '@nx/nx-linux-arm64-gnu@21.1.2':
- resolution: {integrity: sha512-NFhsp27O+mS3r7PWLmJgyZy42WQ72c2pTQSpYfhaBbZPTI5DqBHdANa0sEPmV+ON24qkl5CZKvsmhzjsNmyW6A==}
+ '@nx/nx-linux-arm64-gnu@21.1.3':
+ resolution: {integrity: sha512-TlFT0G5gO6ujdkT7KUmvS2bwurvpV3olQwchqW1rQwuZ1eEQ1GVDuyzg49UG7lgESYruFn2HRhBf4V+iaD8WIw==}
cpu: [arm64]
os: [linux]
- '@nx/nx-linux-arm64-musl@21.1.2':
- resolution: {integrity: sha512-BgS9npARwcnw+hoaRsbas6vdBAJRBAj5qSeL57LO8Dva+e/6PYqoNyVJ0BgJ98xPXDpzM/NnpeRsndQGpLyhDw==}
+ '@nx/nx-linux-arm64-musl@21.1.3':
+ resolution: {integrity: sha512-YkdzrZ7p2Y0YpteRyT9lPKhfuz2t5rNFQ87x9WHK2/cFD6H6M42Fg2JldCPIVj2chN9liH+s5ougW5oPQpZyKw==}
cpu: [arm64]
os: [linux]
- '@nx/nx-linux-x64-gnu@21.1.2':
- resolution: {integrity: sha512-tjBINbymQgxnIlNK/m6B0P5eiGRSHSYPNkFdh3+sra80AP/ymHGLRxxZy702Ga2xg8RVr9zEvuXYHI+QBa1YmA==}
+ '@nx/nx-linux-x64-gnu@21.1.3':
+ resolution: {integrity: sha512-nnHxhakNCr4jR1y13g0yS/UOmn5aXkJ+ZA1R6jFQxIwLv3Ocy05i0ZvU7rPOtflluDberxEop8xzoiuEZXDa/w==}
cpu: [x64]
os: [linux]
- '@nx/nx-linux-x64-musl@21.1.2':
- resolution: {integrity: sha512-+0V0YAOWMh1wvpQZuayQ7y+sj2MhE3l7z0JMD9SX/4xv9zLOWGv+EiUmN/fGoU/mwsSkH2wTCo6G6quKF1E8jQ==}
+ '@nx/nx-linux-x64-musl@21.1.3':
+ resolution: {integrity: sha512-poPt/LnFbq54CA3PZ1af8wcdQ4VsWRuA9w1Q1/G1BhCfDUAVIOZ0mhH1NzFpPwCxgVZ1TbNCZWhV2qjVRwQtlw==}
cpu: [x64]
os: [linux]
- '@nx/nx-win32-arm64-msvc@21.1.2':
- resolution: {integrity: sha512-E+ECMQIMJ6R47BMW5YpDyOhTqczvFaL8k24umRkcvlRh3SraczyxBVPkYHDukDp7tCeIszc5EvdWc83C3W8U4w==}
+ '@nx/nx-win32-arm64-msvc@21.1.3':
+ resolution: {integrity: sha512-gBSVMRkXRqxTKgj/dabAD1EaptROy64fEtlU1llPz/RtcJcVhIlDczBF/y2WSD6A72cSv6zF/F1n3NrekNSfBA==}
cpu: [arm64]
os: [win32]
- '@nx/nx-win32-x64-msvc@21.1.2':
- resolution: {integrity: sha512-J9rNTBOS7Ld6CybU/cou1Fg52AHSYsiwpZISM2RNM0XIoVSDk3Jsvh4OJgS2rvV0Sp/cgDg3ieOMAreekH+TKw==}
+ '@nx/nx-win32-x64-msvc@21.1.3':
+ resolution: {integrity: sha512-k3/1b2dLQjnWzrg2UqHDLCoaqEBx2SRgujjYCACRJ12vmYH2gTyFX2UPXikVbbpaTJNeXv8eaCzyCKhuvPK1sQ==}
cpu: [x64]
os: [win32]
- '@nx/playwright@21.1.2':
- resolution: {integrity: sha512-XSfxoB+LeGFVpzzw59pjMjurOXmLEngGMqk+Z/4QT1A2lzBG4HccVrZQ8UiSxAGCbK+O7MFjy1r0k0z80EjYgg==}
+ '@nx/playwright@21.1.3':
+ resolution: {integrity: sha512-6Cq8lgQQsSutx5hZG2RChFQFJ9cVgJf9ymqvBohLCDPcC6/d2QflMdoqT4yjaOd5TqStk3ZC+elll6tnTY+QYA==}
peerDependencies:
'@playwright/test': ^1.36.0
peerDependenciesMeta:
'@playwright/test':
optional: true
- '@nx/vite@21.1.2':
- resolution: {integrity: sha512-qKb3CTPtcs3MsDebNW7PUS10IDB1+w//iXKFobwmclH4uW/HFUMRcdUrIsdcQfdmQPjGNTTM2fwmbgWJC4qmAw==}
+ '@nx/vite@21.1.3':
+ resolution: {integrity: sha512-xd3WFYQDIZFm3DPza1fY52dVa1km1gCJyoE9/2s+m9Jbvxu40BukdSw37SZVgCtVqyNjsl4rrlXOmzOIKLb98g==}
peerDependencies:
vite: ^5.0.0 || ^6.0.0
vitest: ^1.3.1 || ^2.0.0 || ^3.0.0
- '@nx/web@21.1.2':
- resolution: {integrity: sha512-ONw3bEO6rc9DqM9Jnt6Rc5xkSBMzruWA2KvHVlU4qaoUs1VKbnmJ28dM72lFMn8wbOOeq+RG7GC2nBpifBPLHw==}
+ '@nx/web@21.1.3':
+ resolution: {integrity: sha512-9UV3uacxJ6oMYPfXbPDq1jadM6nPMs13QhSEpjQLAxNDi4ay0zTOobbHZG6LYnf69dAFEIppoayiS42Kuk6L3Q==}
- '@nx/workspace@21.1.2':
- resolution: {integrity: sha512-I4e/X/GN0Vx3FDZv/7bFYmXfOPmcMI3cDO/rg+TqudsuxVM7tJ7+8jtwdpU4I2IEpI6oU9FZ7Fu9R2uNqL5rrQ==}
+ '@nx/workspace@21.1.3':
+ resolution: {integrity: sha512-SAObZmW1cx0hRddC2PCFWJBHpzdjsTGNArJta8iyzfrbP9KAxQd8jjDBZvXLpXU6YMOw0fLwm8YAD2E1xvIoyw==}
'@open-draft/deferred-promise@2.2.0':
resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
@@ -4777,10 +4777,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/scope-manager@8.32.1':
- resolution: {integrity: sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/scope-manager@8.33.1':
resolution: {integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -4791,13 +4787,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/type-utils@8.32.1':
- resolution: {integrity: sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
'@typescript-eslint/type-utils@8.33.1':
resolution: {integrity: sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -4805,33 +4794,16 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/types@8.32.1':
- resolution: {integrity: sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/types@8.33.1':
resolution: {integrity: sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.32.1':
- resolution: {integrity: sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
-
'@typescript-eslint/typescript-estree@8.33.1':
resolution: {integrity: sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/utils@8.32.1':
- resolution: {integrity: sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
'@typescript-eslint/utils@8.33.1':
resolution: {integrity: sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -4839,10 +4811,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/visitor-keys@8.32.1':
- resolution: {integrity: sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/visitor-keys@8.33.1':
resolution: {integrity: sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -9495,8 +9463,8 @@ packages:
nwsapi@2.2.20:
resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
- nx@21.1.2:
- resolution: {integrity: sha512-oczAEOOkQHElxCXs2g2jXDRabDRsmub/h5SAgqAUDSJ2CRnYGVVlgZX7l+o+A9kSqfONyLy5FlJ1pSWlvPuG4w==}
+ nx@21.1.3:
+ resolution: {integrity: sha512-GZ7+Bve4xOVIk/hb9nN16fVqVq5PNNyFom1SCQbEGhGkyABJF8kA4JImCKhZpZyg1CtZeUrkPHK4xNO+rw9G5w==}
hasBin: true
peerDependencies:
'@swc-node/register': ^1.8.0
@@ -12900,11 +12868,6 @@ packages:
resolution: {integrity: sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==}
engines: {node: '>= 6'}
- yaml@2.7.1:
- resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
- engines: {node: '>= 14'}
- hasBin: true
-
yaml@2.8.0:
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
engines: {node: '>= 14.6'}
@@ -13058,10 +13021,10 @@ snapshots:
'@babel/helper-compilation-targets': 7.27.0
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10)
'@babel/helpers': 7.27.0
- '@babel/parser': 7.27.2
+ '@babel/parser': 7.27.5
'@babel/template': 7.27.0
'@babel/traverse': 7.27.0
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
convert-source-map: 2.0.0
debug: 4.4.1(supports-color@6.0.0)
gensync: 1.0.0-beta.2
@@ -13072,8 +13035,8 @@ snapshots:
'@babel/generator@7.27.0':
dependencies:
- '@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/parser': 7.27.5
+ '@babel/types': 7.27.6
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
@@ -13192,7 +13155,7 @@ snapshots:
'@babel/helpers@7.27.0':
dependencies:
'@babel/template': 7.27.0
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@babel/parser@7.27.0':
dependencies:
@@ -13200,7 +13163,7 @@ snapshots:
'@babel/parser@7.27.2':
dependencies:
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@babel/parser@7.27.5':
dependencies:
@@ -13795,8 +13758,8 @@ snapshots:
'@babel/template@7.27.0':
dependencies:
'@babel/code-frame': 7.26.2
- '@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/parser': 7.27.5
+ '@babel/types': 7.27.6
'@babel/traverse@7.27.0':
dependencies:
@@ -16204,24 +16167,24 @@ snapshots:
mkdirp: 1.0.4
rimraf: 3.0.2
- '@nx/devkit@21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
+ '@nx/devkit@21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
dependencies:
ejs: 3.1.10
enquirer: 2.3.6
ignore: 5.3.2
minimatch: 9.0.3
- nx: 21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))
+ nx: 21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))
semver: 7.7.2
tmp: 0.2.3
tslib: 2.8.1
yargs-parser: 21.1.1
- '@nx/esbuild@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.25.5)(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
+ '@nx/esbuild@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.25.5)(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
picocolors: 1.1.1
- tinyglobby: 0.2.13
+ tinyglobby: 0.2.14
tsconfig-paths: 4.2.0
tslib: 2.8.1
optionalDependencies:
@@ -16235,13 +16198,13 @@ snapshots:
- supports-color
- verdaccio
- '@nx/eslint-plugin@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)':
+ '@nx/eslint-plugin@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
'@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/type-utils': 8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/utils': 8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
chalk: 4.1.2
confusing-browser-globals: 1.0.11
globals: 15.15.0
@@ -16261,10 +16224,10 @@ snapshots:
- typescript
- verdaccio
- '@nx/eslint@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
+ '@nx/eslint@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
eslint: 9.28.0(jiti@2.4.2)
semver: 7.7.2
tslib: 2.8.1
@@ -16280,11 +16243,11 @@ snapshots:
- supports-color
- verdaccio
- '@nx/express@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.28.0(jiti@2.4.2))(express@4.21.2)(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)':
+ '@nx/express@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.28.0(jiti@2.4.2))(express@4.21.2)(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/node': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/node': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)
tslib: 2.8.1
optionalDependencies:
express: 4.21.2
@@ -16304,12 +16267,12 @@ snapshots:
- typescript
- verdaccio
- '@nx/jest@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(babel-plugin-macros@3.1.0)(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)':
+ '@nx/jest@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(babel-plugin-macros@3.1.0)(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)':
dependencies:
'@jest/reporters': 29.7.0
'@jest/test-result': 29.7.0
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.8.3)
identity-obj-proxy: 3.0.0
jest-config: 29.7.0(@types/node@22.15.30)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))
@@ -16335,7 +16298,7 @@ snapshots:
- typescript
- verdaccio
- '@nx/js@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
+ '@nx/js@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
dependencies:
'@babel/core': 7.26.10
'@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.10)
@@ -16344,8 +16307,8 @@ snapshots:
'@babel/preset-env': 7.26.9(@babel/core@7.26.10)
'@babel/preset-typescript': 7.27.0(@babel/core@7.26.10)
'@babel/runtime': 7.27.1
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/workspace': 21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/workspace': 21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))
'@zkochan/js-yaml': 0.0.7
babel-plugin-const-enum: 1.2.0(@babel/core@7.26.10)
babel-plugin-macros: 3.1.0
@@ -16364,7 +16327,7 @@ snapshots:
picomatch: 4.0.2
semver: 7.7.2
source-map-support: 0.5.19
- tinyglobby: 0.2.13
+ tinyglobby: 0.2.14
tslib: 2.8.1
transitivePeerDependencies:
- '@babel/traverse'
@@ -16374,12 +16337,12 @@ snapshots:
- nx
- supports-color
- '@nx/node@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)':
+ '@nx/node@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/eslint': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/jest': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(babel-plugin-macros@3.1.0)(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/eslint': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/jest': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(babel-plugin-macros@3.1.0)(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.15.30)(typescript@5.8.3))(typescript@5.8.3)
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
kill-port: 1.6.1
tcp-port-used: 1.0.2
tslib: 2.8.1
@@ -16399,41 +16362,41 @@ snapshots:
- typescript
- verdaccio
- '@nx/nx-darwin-arm64@21.1.2':
+ '@nx/nx-darwin-arm64@21.1.3':
optional: true
- '@nx/nx-darwin-x64@21.1.2':
+ '@nx/nx-darwin-x64@21.1.3':
optional: true
- '@nx/nx-freebsd-x64@21.1.2':
+ '@nx/nx-freebsd-x64@21.1.3':
optional: true
- '@nx/nx-linux-arm-gnueabihf@21.1.2':
+ '@nx/nx-linux-arm-gnueabihf@21.1.3':
optional: true
- '@nx/nx-linux-arm64-gnu@21.1.2':
+ '@nx/nx-linux-arm64-gnu@21.1.3':
optional: true
- '@nx/nx-linux-arm64-musl@21.1.2':
+ '@nx/nx-linux-arm64-musl@21.1.3':
optional: true
- '@nx/nx-linux-x64-gnu@21.1.2':
+ '@nx/nx-linux-x64-gnu@21.1.3':
optional: true
- '@nx/nx-linux-x64-musl@21.1.2':
+ '@nx/nx-linux-x64-musl@21.1.3':
optional: true
- '@nx/nx-win32-arm64-msvc@21.1.2':
+ '@nx/nx-win32-arm64-msvc@21.1.3':
optional: true
- '@nx/nx-win32-x64-msvc@21.1.2':
+ '@nx/nx-win32-x64-msvc@21.1.3':
optional: true
- '@nx/playwright@21.1.2(@babel/traverse@7.27.0)(@playwright/test@1.52.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)':
+ '@nx/playwright@21.1.3(@babel/traverse@7.27.0)(@playwright/test@1.52.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/eslint': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/eslint': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.28.0(jiti@2.4.2))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.8.3)
minimatch: 9.0.3
tslib: 2.8.1
@@ -16451,10 +16414,10 @@ snapshots:
- typescript
- verdaccio
- '@nx/vite@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(less@4.1.3)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vitest@3.2.2)':
+ '@nx/vite@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(less@4.1.3)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vitest@3.2.2)':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.8.3)
'@swc/helpers': 0.5.17
ajv: 8.17.1
@@ -16474,10 +16437,10 @@ snapshots:
- typescript
- verdaccio
- '@nx/web@21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
+ '@nx/web@21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
- '@nx/js': 21.1.2(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/js': 21.1.3(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
detect-port: 1.6.1
http-server: 14.1.1
picocolors: 1.1.1
@@ -16491,13 +16454,13 @@ snapshots:
- supports-color
- verdaccio
- '@nx/workspace@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))':
+ '@nx/workspace@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))':
dependencies:
- '@nx/devkit': 21.1.2(nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
+ '@nx/devkit': 21.1.3(nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))
'@zkochan/js-yaml': 0.0.7
chalk: 4.1.2
enquirer: 2.3.6
- nx: 21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))
+ nx: 21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))
picomatch: 4.0.2
tslib: 2.8.1
yargs-parser: 21.1.1
@@ -17861,11 +17824,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.32.1':
- dependencies:
- '@typescript-eslint/types': 8.32.1
- '@typescript-eslint/visitor-keys': 8.32.1
-
'@typescript-eslint/scope-manager@8.33.1':
dependencies:
'@typescript-eslint/types': 8.33.1
@@ -17875,17 +17833,6 @@ snapshots:
dependencies:
typescript: 5.8.3
- '@typescript-eslint/type-utils@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
- dependencies:
- '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
- '@typescript-eslint/utils': 8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
- debug: 4.4.1(supports-color@6.0.0)
- eslint: 9.28.0(jiti@2.4.2)
- ts-api-utils: 2.1.0(typescript@5.8.3)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/type-utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3)
@@ -17897,24 +17844,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.32.1': {}
-
'@typescript-eslint/types@8.33.1': {}
- '@typescript-eslint/typescript-estree@8.32.1(typescript@5.8.3)':
- dependencies:
- '@typescript-eslint/types': 8.32.1
- '@typescript-eslint/visitor-keys': 8.32.1
- debug: 4.4.1(supports-color@6.0.0)
- fast-glob: 3.3.3
- is-glob: 4.0.3
- minimatch: 9.0.5
- semver: 7.7.2
- ts-api-utils: 2.1.0(typescript@5.8.3)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/typescript-estree@8.33.1(typescript@5.8.3)':
dependencies:
'@typescript-eslint/project-service': 8.33.1(typescript@5.8.3)
@@ -17931,17 +17862,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
- dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2))
- '@typescript-eslint/scope-manager': 8.32.1
- '@typescript-eslint/types': 8.32.1
- '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
- eslint: 9.28.0(jiti@2.4.2)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2))
@@ -17953,11 +17873,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.32.1':
- dependencies:
- '@typescript-eslint/types': 8.32.1
- eslint-visitor-keys: 4.2.0
-
'@typescript-eslint/visitor-keys@8.33.1':
dependencies:
'@typescript-eslint/types': 8.33.1
@@ -21429,7 +21344,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
- minimatch: 3.0.4
+ minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
@@ -23674,7 +23589,7 @@ snapshots:
proc-log: 5.0.0
semver: 7.7.2
tar: 7.4.3
- tinyglobby: 0.2.13
+ tinyglobby: 0.2.14
which: 5.0.0
transitivePeerDependencies:
- supports-color
@@ -23761,7 +23676,7 @@ snapshots:
nwsapi@2.2.20: {}
- nx@21.1.2(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)):
+ nx@21.1.3(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)):
dependencies:
'@napi-rs/wasm-runtime': 0.2.4
'@yarnpkg/lockfile': 1.1.0
@@ -23795,20 +23710,20 @@ snapshots:
tree-kill: 1.2.2
tsconfig-paths: 4.2.0
tslib: 2.8.1
- yaml: 2.7.1
+ yaml: 2.8.0
yargs: 17.7.2
yargs-parser: 21.1.1
optionalDependencies:
- '@nx/nx-darwin-arm64': 21.1.2
- '@nx/nx-darwin-x64': 21.1.2
- '@nx/nx-freebsd-x64': 21.1.2
- '@nx/nx-linux-arm-gnueabihf': 21.1.2
- '@nx/nx-linux-arm64-gnu': 21.1.2
- '@nx/nx-linux-arm64-musl': 21.1.2
- '@nx/nx-linux-x64-gnu': 21.1.2
- '@nx/nx-linux-x64-musl': 21.1.2
- '@nx/nx-win32-arm64-msvc': 21.1.2
- '@nx/nx-win32-x64-msvc': 21.1.2
+ '@nx/nx-darwin-arm64': 21.1.3
+ '@nx/nx-darwin-x64': 21.1.3
+ '@nx/nx-freebsd-x64': 21.1.3
+ '@nx/nx-linux-arm-gnueabihf': 21.1.3
+ '@nx/nx-linux-arm64-gnu': 21.1.3
+ '@nx/nx-linux-arm64-musl': 21.1.3
+ '@nx/nx-linux-x64-gnu': 21.1.3
+ '@nx/nx-linux-x64-musl': 21.1.3
+ '@nx/nx-win32-arm64-msvc': 21.1.3
+ '@nx/nx-win32-x64-msvc': 21.1.3
'@swc-node/register': 1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3)
'@swc/core': 1.11.29(@swc/helpers@0.5.17)
transitivePeerDependencies:
@@ -27601,8 +27516,6 @@ snapshots:
yaml@2.0.0-1: {}
- yaml@2.7.1: {}
-
yaml@2.8.0: {}
yargs-parser@13.1.2: