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,