refactor!(llm): Remove bootstrapper in favor of setup-provider

This commit is contained in:
Tom Pike
2026-07-16 22:21:59 +05:30
committed by Aditya Gupta
parent 250bb87a8d
commit 61ea9fe237
11 changed files with 398 additions and 255 deletions

View File

@@ -0,0 +1,193 @@
#!/usr/bin/env node
import { ProviderRegistry, ProviderManager } from "../index.js";
import dotenv from "dotenv";
import path from "path";
import fs from "fs";
// Load dotenv from workspace root
function loadEnv() {
let current = process.cwd();
while (current !== "/" && current !== path.parse(current).root) {
if (fs.existsSync(path.join(current, "pnpm-workspace.yaml"))) {
dotenv.config({ path: path.join(current, ".env") });
return;
}
current = path.dirname(current);
}
dotenv.config();
}
loadEnv();
function printHelp() {
console.log(`
Usage:
node packages/llm/dist/bin/setup-provider.js [options]
Options:
--provider <id> ID of the provider (e.g. google-genai, openai, anthropic, groq, etc.)
--name <name> Display name for the instance (default: provider displayName)
--key <key> API key (default: loaded from the provider's env variable, e.g. GOOGLE_API_KEY)
--model <model> Model name (default: provider's default model)
--type <type> "generative" | "embedding" (default: "generative")
--max-context <num> Max context window tokens (default: provider's default max context)
--endpoint <url> Custom endpoint URL (optional)
--all Auto-detect and seed all providers whose environment variables are set
-h, --help Show this help message
Registered Providers:
${ProviderRegistry.all()
.map((p) => ` - ${p.id} (${p.displayName}) [Env: ${p.envVar || "None"}]`)
.join("\n")}
`);
}
async function main() {
const args = process.argv.slice(2);
if (args.includes("-h") || args.includes("--help") || args.length === 0) {
printHelp();
process.exit(0);
}
const options: Record<string, string> = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.startsWith("--")) {
const key = arg.slice(2);
const nextVal = args[i + 1];
if (nextVal && !nextVal.startsWith("--")) {
options[key] = nextVal;
i++;
} else {
options[key] = "true";
}
}
}
if (options.all === "true") {
// Seed all providers from environment variables
const existing = ProviderManager.list();
let seededCount = 0;
for (const def of ProviderRegistry.all()) {
if (!def.envVar) continue;
const key = process.env[def.envVar]?.trim();
if (!key) continue;
if (def.capabilities.generative) {
const hasGen = existing.some(
(p) => p.providerName === def.id && p.type === "generative",
);
if (!hasGen) {
const name = `${def.displayName} (CLI)`;
ProviderManager.create(
name,
def.id,
key,
def.defaultModel,
"generative",
def.defaultMaxContext,
);
console.log(`Created generative instance: ${name}`);
seededCount++;
}
}
if (def.capabilities.embedding) {
const hasEmbed = existing.some(
(p) => p.providerName === def.id && p.type === "embedding",
);
if (!hasEmbed) {
const name = `${def.displayName} Embed (CLI)`;
ProviderManager.create(
name,
def.id,
key,
def.defaultEmbeddingModel || "",
"embedding",
0,
);
console.log(`Created embedding instance: ${name}`);
seededCount++;
}
}
}
if (seededCount === 0) {
console.log(
"No new provider instances seeded. (Either already existed or env vars not set)",
);
} else {
console.log(`Successfully seeded ${seededCount} provider instance(s).`);
}
process.exit(0);
}
const providerId = options.provider;
if (!providerId) {
console.error("Error: --provider <id> or --all is required.");
printHelp();
process.exit(1);
}
const def = ProviderRegistry.get(providerId);
if (!def) {
console.error(`Error: Provider '${providerId}' is not registered.`);
console.error(
`Available providers: ${ProviderRegistry.all()
.map((p) => p.id)
.join(", ")}`,
);
process.exit(1);
}
const type = (options.type === "embedding" ? "embedding" : "generative") as
"generative" | "embedding";
// Resolve key
let apiKey: string | undefined = options.key;
if (!apiKey && def.envVar) {
apiKey = process.env[def.envVar]?.trim();
}
if (!apiKey) {
console.error(
`Error: API Key is required. Please set ${def.envVar || "the environment variable"} or pass --key <apiKey>.`,
);
process.exit(1);
}
// Resolve model
const defaultModel =
type === "embedding" ? def.defaultEmbeddingModel || "" : def.defaultModel;
const modelName = options.model || defaultModel;
// Resolve name
const name = options.name || `${def.displayName} (CLI)`;
// Resolve maxContext
const maxContext = options["max-context"]
? parseInt(options["max-context"], 10)
: type === "embedding"
? 0
: def.defaultMaxContext;
const endpointUrl = options.endpoint;
const instance = ProviderManager.create(
name,
def.id,
apiKey,
modelName,
type,
maxContext,
endpointUrl,
);
console.log(`Successfully created provider instance:`);
console.log(JSON.stringify(instance, null, 2));
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -1,98 +0,0 @@
import type BetterSqlite3 from "better-sqlite3";
import { ProviderRegistry } from "./registry.js";
export function seedFromEnvVars(db: BetterSqlite3.Database): boolean {
const totalCount = db
.prepare("SELECT COUNT(*) as count FROM provider_instances")
.get() as { count: number };
if (totalCount.count > 0) {
return false;
}
let hasActiveGenerative = false;
let hasActiveEmbedding = false;
const insertStmt = db.prepare(
`INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type, maxContext)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
);
const insertMany = db.transaction(
(
entries: {
id: string;
name: string;
providerId: string;
key: string;
isActive: number;
modelName: string;
type: "generative" | "embedding";
maxContext: number;
}[],
) => {
for (const e of entries) {
insertStmt.run(
e.id,
e.name,
e.providerId,
e.key,
e.isActive,
e.modelName,
e.type,
e.maxContext,
);
}
},
);
const entries: {
id: string;
name: string;
providerId: string;
key: string;
isActive: number;
modelName: string;
type: "generative" | "embedding";
maxContext: number;
}[] = [];
for (const def of ProviderRegistry.all()) {
if (!def.envVar) continue;
const key = process.env[def.envVar]?.trim();
if (!key) continue;
if (def.capabilities.generative) {
entries.push({
id: `provider-default-${def.id}`,
name: `${def.displayName} (Env)`,
providerId: def.id,
key,
isActive: hasActiveGenerative ? 0 : 1,
modelName: def.defaultModel,
type: "generative",
maxContext: def.defaultMaxContext,
});
if (!hasActiveGenerative) hasActiveGenerative = true;
}
if (def.capabilities.embedding) {
const embedModel = def.defaultEmbeddingModel || "";
entries.push({
id: `provider-default-${def.id}-embed`,
name: `${def.displayName} Embed (Env)`,
providerId: def.id,
key,
isActive: hasActiveEmbedding ? 0 : 1,
modelName: embedModel,
type: "embedding",
maxContext: 0,
});
if (!hasActiveEmbedding) hasActiveEmbedding = true;
}
}
if (entries.length > 0) {
insertMany(entries);
}
return true;
}

View File

@@ -15,6 +15,13 @@ export function setDbPath(p: string | null) {
}
function findDbPath(): string {
if (process.env.OMNIA_DB_PATH) {
const dir = path.dirname(process.env.OMNIA_DB_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return process.env.OMNIA_DB_PATH;
}
let current = process.cwd();
while (current !== "/" && current !== path.parse(current).root) {
if (fs.existsSync(path.join(current, "pnpm-workspace.yaml"))) {

View File

@@ -1,15 +1,12 @@
import type { ModelProviderInstance } from "./llm.js";
import { getDb } from "./db.js";
import { mapRow, synthInstance, type DbRow } from "./row-mapper.js";
import { seedFromEnvVars } from "./bootstrap.js";
import { ProviderRegistry } from "./registry.js";
import { mapRow, type DbRow } from "./row-mapper.js";
export { setDbPath as setDbPathOverride } from "./db.js";
export class ProviderManager {
static list(): ModelProviderInstance[] {
const db = getDb();
seedFromEnvVars(db);
const rows = db
.prepare("SELECT * FROM provider_instances")
.all() as DbRow[];
@@ -150,8 +147,6 @@ export class ProviderManager {
): ModelProviderInstance | null {
const db = getDb();
try {
seedFromEnvVars(db);
const row = db
.prepare(
"SELECT * FROM provider_instances WHERE isActive = 1 AND type = ?",
@@ -175,7 +170,7 @@ export class ProviderManager {
return null;
} catch {
return envFallback(type);
return null;
}
}
@@ -205,21 +200,3 @@ export class ProviderManager {
}
}
}
function envFallback(
type: "generative" | "embedding",
): ModelProviderInstance | null {
const defs = [...ProviderRegistry.all()].sort(
(a, b) => a.fallbackPriority - b.fallbackPriority,
);
for (const def of defs) {
if (!def.envVar) continue;
if (type === "generative" && !def.capabilities.generative) continue;
if (type === "embedding" && !def.capabilities.embedding) continue;
const key = process.env[def.envVar]?.trim();
if (key) {
return synthInstance(def, type, key);
}
}
return null;
}

View File

@@ -1,5 +1,4 @@
import type { ModelProviderInstance } from "./llm.js";
import type { ProviderDefinition } from "./registry.js";
export type DbRow = {
id: string;
@@ -31,26 +30,3 @@ export function mapRow(r: DbRow): ModelProviderInstance {
endpointUrl: r.endpointUrl || undefined,
};
}
export function synthInstance(
def: ProviderDefinition,
type: "generative" | "embedding",
apiKey: string,
): ModelProviderInstance {
const modelName =
type === "embedding" ? def.defaultEmbeddingModel : def.defaultModel;
return {
id: `provider-default-env-fallback-${def.id}-${type}`,
name:
type === "embedding"
? `${def.displayName} Embed (Env Fallback)`
: `${def.displayName} (Env Fallback)`,
providerName: def.id,
apiKey,
isActive: true,
modelName,
type,
maxContext: type === "embedding" ? 0 : def.defaultMaxContext,
endpointUrl: undefined,
};
}