add provider_list

This commit is contained in:
Dax Raad
2025-05-28 12:53:22 -04:00
parent 4132fcc1b2
commit 55a6fcdd3f
14 changed files with 458 additions and 76 deletions

View File

@@ -1,9 +1,13 @@
import { App } from "../app";
import { Log } from "../util/log";
import { mergeDeep } from "remeda";
import path from "node:path";
import { createAnthropic } from "@ai-sdk/anthropic";
import type { LanguageModel, Provider } from "ai";
import { NoSuchModelError } from "ai";
import type { Config } from "../app/config";
import { BunProc } from "../bun";
import { Global } from "../global";
export namespace LLM {
const log = Log.create({ service: "llm" });
@@ -14,17 +18,67 @@ export namespace LLM {
}
}
const state = App.state("llm", async (app) => {
const providers: Provider[] = [];
const NATIVE_PROVIDERS: Record<string, Config.Provider> = {
anthropic: {
models: {
"claude-sonnet-4-20250514": {
name: "Claude 4 Sonnet",
cost: {
input: 3.0,
inputCached: 3.75,
output: 15.0,
outputCached: 0.3,
},
contextWindow: 200000,
maxTokens: 50000,
attachment: true,
},
},
},
};
if (process.env["ANTHROPIC_API_KEY"] || app.config.providers?.anthropic) {
log.info("loaded anthropic");
const provider = createAnthropic({
apiKey: app.config.providers?.anthropic?.apiKey,
baseURL: app.config.providers?.anthropic?.baseURL,
headers: app.config.providers?.anthropic?.headers,
});
providers.push(provider);
const AUTODETECT: Record<string, string[]> = {
anthropic: ["ANTHROPIC_API_KEY"],
};
const state = App.state("llm", async (app) => {
const providers: Record<
string,
{
info: Config.Provider;
instance: Provider;
}
> = {};
const list = mergeDeep(NATIVE_PROVIDERS, app.config.providers ?? {});
for (const [providerID, providerInfo] of Object.entries(list)) {
if (
!app.config.providers?.[providerID] &&
!AUTODETECT[providerID]?.some((env) => process.env[env])
)
continue;
const dir = path.join(
Global.cache(),
`node_modules`,
`@ai-sdk`,
providerID,
);
if (!(await Bun.file(path.join(dir, "package.json")).exists())) {
BunProc.run(["add", "--exact", `@ai-sdk/${providerID}@alpha`], {
cwd: Global.cache(),
});
}
const mod = await import(
path.join(Global.cache(), `node_modules`, `@ai-sdk`, providerID)
);
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!];
const loaded = fn(providerInfo.options);
log.info("loaded", { provider: providerID });
providers[providerID] = {
info: providerInfo,
instance: loaded,
};
}
return {
@@ -37,23 +91,24 @@ export namespace LLM {
return state().then((state) => state.providers);
}
export async function findModel(model: string) {
export async function findModel(providerID: string, modelID: string) {
const key = `${providerID}/${modelID}`;
const s = await state();
if (s.models.has(model)) {
return s.models.get(model)!;
if (s.models.has(key)) return s.models.get(key)!;
const provider = s.providers[providerID];
if (!provider) throw new ModelNotFoundError(modelID);
log.info("loading", {
providerID,
modelID,
});
try {
const match = provider.instance.languageModel(modelID);
log.info("found", { providerID, modelID });
s.models.set(key, match);
return match;
} catch (e) {
if (e instanceof NoSuchModelError) throw new ModelNotFoundError(modelID);
throw e;
}
log.info("loading", { model });
for (const provider of s.providers) {
try {
const match = provider.languageModel(model);
log.info("found", { model });
s.models.set(model, match);
return match;
} catch (e) {
if (e instanceof NoSuchModelError) continue;
throw e;
}
}
throw new ModelNotFoundError(model);
}
}