mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 20:12:48 +05:30
refactor!(llm): Remove bootstrapper in favor of setup-provider
This commit is contained in:
193
packages/llm/src/bin/setup-provider.ts
Normal file
193
packages/llm/src/bin/setup-provider.ts
Normal 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);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"))) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user