Merge remote-tracking branch 'origin/master' into master and resolve conflicts

This commit is contained in:
2026-07-11 16:38:39 +05:30
22 changed files with 1007 additions and 409 deletions

View File

@@ -15,6 +15,8 @@ export interface LLMResponse<T> {
inputTokens: number;
outputTokens: number;
totalTokens: number;
modelName?: string;
providerInstanceName?: string;
};
}
@@ -25,6 +27,8 @@ export interface LLMCallRecord {
inputTokens: number;
outputTokens: number;
totalTokens: number;
modelName?: string;
providerInstanceName?: string;
};
}

View File

@@ -3,6 +3,8 @@ import path from "path";
import fs from "fs";
import type { ModelProviderInstance } from "./llm.js";
let hasBootstrapped = false;
function getWorkspaceRoot() {
let current = process.cwd();
while (current !== "/" && current !== path.parse(current).root) {
@@ -51,6 +53,45 @@ 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 hasInsertedGenerative = false;
if (googleKey && googleKey.trim()) {
const id = "provider-default-google";
db.prepare(`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(id, "Gemini (Env)", "google-genai", googleKey.trim(), 1, "gemini-2.5-flash", "generative");
hasInsertedGenerative = true;
const embedId = "provider-default-google-embed";
db.prepare(`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(embedId, "Gemini Embed (Env)", "google-genai", googleKey.trim(), 1, "gemini-embedding-001", "embedding");
}
if (openRouterKey && openRouterKey.trim()) {
const id = "provider-default-openrouter";
const isActive = hasInsertedGenerative ? 0 : 1;
db.prepare(`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(id, "OpenRouter (Env)", "openrouter", openRouterKey.trim(), isActive, "google/gemini-2.5-flash", "generative");
}
}
hasBootstrapped = true;
}
} catch {
// ignore write lock issues or other DB errors during bootstrap
}
return db;
}
@@ -184,43 +225,79 @@ export class ProviderManager {
if (!row) {
const totalCount = db.prepare(`SELECT COUNT(*) as count FROM provider_instances`).get() as { count: number };
if (totalCount.count === 0) {
const envKey = process.env.GOOGLE_API_KEY;
if (envKey && envKey.trim()) {
const id = "provider-default-env";
const googleKey = process.env.GOOGLE_API_KEY;
const openRouterKey = process.env.OPENROUTER_API_KEY;
let hasInsertedGenerative = false;
if (googleKey && googleKey.trim()) {
const id = "provider-default-google";
db.prepare(`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(id, "Default (Env)", "google-genai", envKey, 1, "gemini-2.5-flash", "generative");
`).run(id, "Gemini (Env)", "google-genai", googleKey.trim(), 1, "gemini-2.5-flash", "generative");
hasInsertedGenerative = true;
const embedId = "provider-default-env-embed";
const embedId = "provider-default-google-embed";
db.prepare(`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(embedId, "Default Embed (Env)", "google-genai", envKey, 1, "gemini-embedding-001", "embedding");
`).run(embedId, "Gemini Embed (Env)", "google-genai", googleKey.trim(), 1, "gemini-embedding-001", "embedding");
}
if (type === "embedding") {
return {
id: embedId,
name: "Default Embed (Env)",
providerName: "google-genai",
apiKey: envKey,
isActive: true,
modelName: "gemini-embedding-001",
type: "embedding",
};
}
if (openRouterKey && openRouterKey.trim()) {
const id = "provider-default-openrouter";
const isActive = hasInsertedGenerative ? 0 : 1;
db.prepare(`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(id, "OpenRouter (Env)", "openrouter", openRouterKey.trim(), isActive, "google/gemini-2.5-flash", "generative");
}
const retryRow = db.prepare(`SELECT * FROM provider_instances WHERE isActive = 1 AND type = ?`).get(type) as {
id: string;
name: string;
providerName: string;
apiKey: string;
isActive: number;
modelName?: string;
type: string;
} | undefined;
if (retryRow) {
return {
id,
name: "Default (Env)",
providerName: "google-genai",
apiKey: envKey,
id: retryRow.id,
name: retryRow.name,
providerName: retryRow.providerName,
apiKey: retryRow.apiKey,
isActive: true,
modelName: "gemini-2.5-flash",
type: "generative",
modelName: retryRow.modelName || undefined,
type: retryRow.type as "generative" | "embedding",
};
}
}
// 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 WHERE type = ? LIMIT 1`).get(type) as {
id: string;
name: string;
providerName: string;
apiKey: string;
isActive: number;
modelName?: string;
type: 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,
type: firstRow.type as "generative" | "embedding",
};
}
return null;
}
@@ -234,29 +311,46 @@ export class ProviderManager {
type: (row.type as "generative" | "embedding") || "generative",
};
} catch {
const envKey = process.env.GOOGLE_API_KEY;
if (envKey) {
if (type === "embedding") {
const googleKey = process.env.GOOGLE_API_KEY;
if (type === "embedding") {
if (googleKey && googleKey.trim()) {
return {
id: "provider-default-env-embed-fallback",
name: "Default Embed (Env Fallback)",
name: "Gemini Embed (Env Fallback)",
providerName: "google-genai",
apiKey: envKey,
apiKey: googleKey.trim(),
isActive: true,
modelName: "gemini-embedding-001",
type: "embedding",
};
}
return null;
}
// generative fallback
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",
type: "generative",
};
}
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",
type: "generative",
};
}
return null;
} finally {
db.close();

View File

@@ -12,33 +12,43 @@ export class GeminiProvider implements ILLMProvider {
providerName = "Gemini";
private model: ChatGoogleGenerativeAI;
private modelNameUsed: string;
private providerInstanceName?: string;
lastCalls: LLMCallRecord[] = [];
constructor(apiKey?: string, modelName?: string) {
constructor(apiKey?: string, modelName?: string, providerInstanceName?: string) {
let key = apiKey;
let model = modelName;
this.providerInstanceName = providerInstanceName;
if (!key) {
const active = ProviderManager.getActive("generative");
if (active) {
if (active && active.providerName === GeminiProvider.providerId) {
key = active.apiKey;
if (!model) {
model = active.modelName;
}
if (!this.providerInstanceName) {
this.providerInstanceName = active.name;
}
}
}
if (!key) {
key = llmConfig.GOOGLE_API_KEY;
if (!this.providerInstanceName && key) {
this.providerInstanceName = "Environment Variable";
}
}
if (!key) {
throw new Error("GOOGLE_API_KEY is required to initialize GeminiProvider");
}
this.modelNameUsed = model || "gemini-2.5-flash";
this.model = new ChatGoogleGenerativeAI({
apiKey: key,
model: model || "gemini-2.5-flash",
model: this.modelNameUsed,
});
}
@@ -63,11 +73,13 @@ export class GeminiProvider implements ILLMProvider {
const parsed = result?.parsed;
const raw = result?.raw;
const usage = raw?.usage_metadata ? {
inputTokens: raw.usage_metadata.input_tokens || 0,
outputTokens: raw.usage_metadata.output_tokens || 0,
totalTokens: raw.usage_metadata.total_tokens || 0,
} : undefined;
const usage = {
inputTokens: raw?.usage_metadata?.input_tokens || 0,
outputTokens: raw?.usage_metadata?.output_tokens || 0,
totalTokens: raw?.usage_metadata?.total_tokens || 0,
modelName: this.modelNameUsed,
providerInstanceName: this.providerInstanceName || "Default",
};
this.lastCalls.push({
systemPrompt: request.systemPrompt,

View File

@@ -12,33 +12,43 @@ export class OpenRouterProvider implements ILLMProvider {
providerName = "OpenRouter";
private model: ChatOpenRouter;
private modelNameUsed: string;
private providerInstanceName?: string;
lastCalls: LLMCallRecord[] = [];
constructor(apiKey?: string, modelName?: string) {
constructor(apiKey?: string, modelName?: string, providerInstanceName?: string) {
let key = apiKey;
let model = modelName;
this.providerInstanceName = providerInstanceName;
if (!key) {
const active = ProviderManager.getActive("generative");
if (active) {
if (active && active.providerName === OpenRouterProvider.providerId) {
key = active.apiKey;
if (!model) {
model = active.modelName;
}
if (!this.providerInstanceName) {
this.providerInstanceName = active.name;
}
}
}
if (!key) {
key = llmConfig.OPENROUTER_API_KEY;
if (!this.providerInstanceName && key) {
this.providerInstanceName = "Environment Variable";
}
}
if (!key) {
throw new Error("OPENROUTER_API_KEY is required to initialize OpenRouterProvider");
}
this.modelNameUsed = model || "google/gemini-2.5-flash";
this.model = new ChatOpenRouter({
apiKey: key,
model: model || "google/gemini-2.5-flash",
model: this.modelNameUsed,
});
}
@@ -63,11 +73,13 @@ export class OpenRouterProvider implements ILLMProvider {
const parsed = result?.parsed;
const raw = result?.raw;
const usage = raw?.usage_metadata ? {
inputTokens: raw.usage_metadata.input_tokens || 0,
outputTokens: raw.usage_metadata.output_tokens || 0,
totalTokens: raw.usage_metadata.total_tokens || 0,
} : undefined;
const usage = {
inputTokens: raw?.usage_metadata?.input_tokens || 0,
outputTokens: raw?.usage_metadata?.output_tokens || 0,
totalTokens: raw?.usage_metadata?.total_tokens || 0,
modelName: this.modelNameUsed,
providerInstanceName: this.providerInstanceName || "Default",
};
this.lastCalls.push({
systemPrompt: request.systemPrompt,

View File

@@ -91,6 +91,8 @@ describe("OpenRouterProvider Unit Tests (Tier 1)", () => {
inputTokens: 10,
outputTokens: 5,
totalTokens: 15,
modelName: "google/gemini-2.5-flash",
providerInstanceName: "Default",
});
expect(provider.lastCalls.length).toBe(1);
@@ -101,6 +103,8 @@ describe("OpenRouterProvider Unit Tests (Tier 1)", () => {
inputTokens: 10,
outputTokens: 5,
totalTokens: 15,
modelName: "google/gemini-2.5-flash",
providerInstanceName: "Default",
},
});
});

View 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);
});
});