mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 03:52:48 +05:30
refactor(llm): Unify auto-bootstrapped prover instances and custom instances
This commit is contained in:
@@ -3,6 +3,17 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import type { LLMProviderInstance } from "./llm.js";
|
||||
|
||||
let dbPathOverride: string | null = null;
|
||||
let hasBootstrapped = false;
|
||||
|
||||
export function setDbPathOverride(p: string | null) {
|
||||
dbPathOverride = p;
|
||||
}
|
||||
|
||||
export function resetHasBootstrapped() {
|
||||
hasBootstrapped = false;
|
||||
}
|
||||
|
||||
function getWorkspaceRoot() {
|
||||
let current = process.cwd();
|
||||
while (current !== "/" && current !== path.parse(current).root) {
|
||||
@@ -20,12 +31,17 @@ function getWorkspaceRoot() {
|
||||
}
|
||||
|
||||
function getSettingsDb() {
|
||||
const wsRoot = getWorkspaceRoot();
|
||||
const dbDir = path.resolve(wsRoot, "data");
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
fs.mkdirSync(dbDir, { recursive: true });
|
||||
let dbPath: string;
|
||||
if (dbPathOverride) {
|
||||
dbPath = dbPathOverride;
|
||||
} else {
|
||||
const wsRoot = getWorkspaceRoot();
|
||||
const dbDir = path.resolve(wsRoot, "data");
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
fs.mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
dbPath = path.join(dbDir, "settings.db");
|
||||
}
|
||||
const dbPath = path.join(dbDir, "settings.db");
|
||||
const db = new Database(dbPath);
|
||||
|
||||
db.prepare(`
|
||||
@@ -44,6 +60,39 @@ function getSettingsDb() {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Auto-bootstrap environment variables if DB contains 0 instances
|
||||
try {
|
||||
if (!hasBootstrapped) {
|
||||
const totalCount = db.prepare(`SELECT COUNT(*) as count FROM provider_instances`).get() as { count: number };
|
||||
if (totalCount.count === 0) {
|
||||
const googleKey = process.env.GOOGLE_API_KEY;
|
||||
const openRouterKey = process.env.OPENROUTER_API_KEY;
|
||||
let hasInserted = false;
|
||||
|
||||
if (googleKey && googleKey.trim()) {
|
||||
const id = "provider-default-google";
|
||||
db.prepare(`
|
||||
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(id, "Gemini (Env)", "google-genai", googleKey.trim(), 1, "gemini-2.5-flash");
|
||||
hasInserted = true;
|
||||
}
|
||||
|
||||
if (openRouterKey && openRouterKey.trim()) {
|
||||
const id = "provider-default-openrouter";
|
||||
const isActive = hasInserted ? 0 : 1;
|
||||
db.prepare(`
|
||||
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(id, "OpenRouter (Env)", "openrouter", openRouterKey.trim(), isActive, "google/gemini-2.5-flash");
|
||||
}
|
||||
}
|
||||
hasBootstrapped = true;
|
||||
}
|
||||
} catch {
|
||||
// ignore write lock issues or other DB errors during bootstrap
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
@@ -153,28 +202,25 @@ export class ProviderManager {
|
||||
} | undefined;
|
||||
|
||||
if (!row) {
|
||||
// Check if there are any rows at all
|
||||
const totalCount = db.prepare(`SELECT COUNT(*) as count FROM provider_instances`).get() as { count: number };
|
||||
if (totalCount.count === 0) {
|
||||
// Database is completely empty! Check if GOOGLE_API_KEY env is set.
|
||||
const envKey = process.env.GOOGLE_API_KEY;
|
||||
if (envKey && envKey.trim()) {
|
||||
// Auto-bootstrap default active instance from env
|
||||
const id = "provider-default-env";
|
||||
db.prepare(`
|
||||
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(id, "Default (Env)", "google-genai", envKey, 1, "gemini-2.5-flash");
|
||||
|
||||
return {
|
||||
id,
|
||||
name: "Default (Env)",
|
||||
providerName: "google-genai",
|
||||
apiKey: envKey,
|
||||
isActive: true,
|
||||
modelName: "gemini-2.5-flash",
|
||||
};
|
||||
}
|
||||
// If there's no active row but some rows exist, return the first one as active, or update it
|
||||
const firstRow = db.prepare(`SELECT * FROM provider_instances LIMIT 1`).get() as {
|
||||
id: string;
|
||||
name: string;
|
||||
providerName: string;
|
||||
apiKey: string;
|
||||
isActive: number;
|
||||
modelName?: string;
|
||||
} | undefined;
|
||||
if (firstRow) {
|
||||
db.prepare(`UPDATE provider_instances SET isActive = 1 WHERE id = ?`).run(firstRow.id);
|
||||
return {
|
||||
id: firstRow.id,
|
||||
name: firstRow.name,
|
||||
providerName: firstRow.providerName,
|
||||
apiKey: firstRow.apiKey,
|
||||
isActive: true,
|
||||
modelName: firstRow.modelName || undefined,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -189,17 +235,28 @@ export class ProviderManager {
|
||||
};
|
||||
} catch {
|
||||
// Lock or write issue fallback: return an in-memory active key if env key exists
|
||||
const envKey = process.env.GOOGLE_API_KEY;
|
||||
if (envKey) {
|
||||
const googleKey = process.env.GOOGLE_API_KEY;
|
||||
if (googleKey && googleKey.trim()) {
|
||||
return {
|
||||
id: "provider-default-env-fallback",
|
||||
name: "Default (Env Fallback)",
|
||||
name: "Gemini (Env Fallback)",
|
||||
providerName: "google-genai",
|
||||
apiKey: envKey,
|
||||
apiKey: googleKey.trim(),
|
||||
isActive: true,
|
||||
modelName: "gemini-2.5-flash",
|
||||
};
|
||||
}
|
||||
const openRouterKey = process.env.OPENROUTER_API_KEY;
|
||||
if (openRouterKey && openRouterKey.trim()) {
|
||||
return {
|
||||
id: "provider-default-env-fallback",
|
||||
name: "OpenRouter (Env Fallback)",
|
||||
providerName: "openrouter",
|
||||
apiKey: openRouterKey.trim(),
|
||||
isActive: true,
|
||||
modelName: "google/gemini-2.5-flash",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
db.close();
|
||||
|
||||
@@ -20,7 +20,7 @@ export class GeminiProvider implements ILLMProvider {
|
||||
|
||||
if (!key) {
|
||||
const active = ProviderManager.getActive();
|
||||
if (active) {
|
||||
if (active && active.providerName === GeminiProvider.providerId) {
|
||||
key = active.apiKey;
|
||||
if (!model) {
|
||||
model = active.modelName;
|
||||
|
||||
@@ -20,7 +20,7 @@ export class OpenRouterProvider implements ILLMProvider {
|
||||
|
||||
if (!key) {
|
||||
const active = ProviderManager.getActive();
|
||||
if (active) {
|
||||
if (active && active.providerName === OpenRouterProvider.providerId) {
|
||||
key = active.apiKey;
|
||||
if (!model) {
|
||||
model = active.modelName;
|
||||
|
||||
91
packages/llm/tests/provider-manager.test.ts
Normal file
91
packages/llm/tests/provider-manager.test.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { ProviderManager, setDbPathOverride, resetHasBootstrapped } from "../src/index.js";
|
||||
|
||||
describe("ProviderManager Bootstrapping & CRUD Unit Tests", () => {
|
||||
let tempDbPath: string;
|
||||
let originalGoogle: string | undefined;
|
||||
let originalOpenRouter: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
originalGoogle = process.env.GOOGLE_API_KEY;
|
||||
originalOpenRouter = process.env.OPENROUTER_API_KEY;
|
||||
delete process.env.GOOGLE_API_KEY;
|
||||
delete process.env.OPENROUTER_API_KEY;
|
||||
|
||||
resetHasBootstrapped();
|
||||
|
||||
// Generate a unique temp database path for this test run
|
||||
tempDbPath = path.resolve(process.cwd(), `test-settings-${Date.now()}-${Math.random().toString(36).substring(2)}.db`);
|
||||
setDbPathOverride(tempDbPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setDbPathOverride(null);
|
||||
if (fs.existsSync(tempDbPath)) {
|
||||
try {
|
||||
fs.unlinkSync(tempDbPath);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (originalGoogle !== undefined) {
|
||||
process.env.GOOGLE_API_KEY = originalGoogle;
|
||||
} else {
|
||||
delete process.env.GOOGLE_API_KEY;
|
||||
}
|
||||
if (originalOpenRouter !== undefined) {
|
||||
process.env.OPENROUTER_API_KEY = originalOpenRouter;
|
||||
} else {
|
||||
delete process.env.OPENROUTER_API_KEY;
|
||||
}
|
||||
});
|
||||
|
||||
test("auto-bootstraps Gemini and OpenRouter when database is empty and environment variables are present", () => {
|
||||
process.env.GOOGLE_API_KEY = "mock-google-key-123";
|
||||
process.env.OPENROUTER_API_KEY = "mock-openrouter-key-456";
|
||||
|
||||
const list = ProviderManager.list();
|
||||
expect(list.length).toBe(2);
|
||||
|
||||
const gemini = list.find((p) => p.providerName === "google-genai");
|
||||
expect(gemini).toBeDefined();
|
||||
expect(gemini?.name).toBe("Gemini (Env)");
|
||||
expect(gemini?.apiKey).toBe("mock-google-key-123");
|
||||
expect(gemini?.modelName).toBe("gemini-2.5-flash");
|
||||
expect(gemini?.isActive).toBe(true); // first inserted is active
|
||||
|
||||
const openrouter = list.find((p) => p.providerName === "openrouter");
|
||||
expect(openrouter).toBeDefined();
|
||||
expect(openrouter?.name).toBe("OpenRouter (Env)");
|
||||
expect(openrouter?.apiKey).toBe("mock-openrouter-key-456");
|
||||
expect(openrouter?.modelName).toBe("google/gemini-2.5-flash");
|
||||
expect(openrouter?.isActive).toBe(false); // second inserted is inactive
|
||||
});
|
||||
|
||||
test("treats bootstrapped instances as normal provider instances (editable and deletable)", () => {
|
||||
process.env.GOOGLE_API_KEY = "mock-google-key-123";
|
||||
|
||||
// Trigger bootstrap
|
||||
const list = ProviderManager.list();
|
||||
expect(list.length).toBe(1);
|
||||
const bootstrapped = list[0];
|
||||
expect(bootstrapped.name).toBe("Gemini (Env)");
|
||||
expect(bootstrapped.isActive).toBe(true);
|
||||
|
||||
// Edit name and key
|
||||
ProviderManager.update(bootstrapped.id, "My Gemini Key", "google-genai", "new-secret-key", "gemini-2.5-pro");
|
||||
|
||||
const listAfterUpdate = ProviderManager.list();
|
||||
expect(listAfterUpdate.length).toBe(1);
|
||||
expect(listAfterUpdate[0].name).toBe("My Gemini Key");
|
||||
expect(listAfterUpdate[0].apiKey).toBe("new-secret-key");
|
||||
expect(listAfterUpdate[0].modelName).toBe("gemini-2.5-pro");
|
||||
|
||||
// Delete instance
|
||||
ProviderManager.delete(bootstrapped.id);
|
||||
const listAfterDelete = ProviderManager.list();
|
||||
expect(listAfterDelete.length).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user