From 55a6fcdd3f5b3c55712e5cfc9dd4d994da38d4c8 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 28 May 2025 12:53:22 -0400 Subject: add provider_list --- js/src/llm/llm.ts | 109 +++++++++++++++++++++++++++++++---------- js/src/llm/models/anthropic.ts | 0 js/src/llm/models/index.ts | 1 + js/src/llm/models/model.ts | 11 +++++ 4 files changed, 94 insertions(+), 27 deletions(-) create mode 100644 js/src/llm/models/anthropic.ts create mode 100644 js/src/llm/models/index.ts create mode 100644 js/src/llm/models/model.ts (limited to 'js/src/llm') diff --git a/js/src/llm/llm.ts b/js/src/llm/llm.ts index 5d962219c..c0ab38530 100644 --- a/js/src/llm/llm.ts +++ b/js/src/llm/llm.ts @@ -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 NATIVE_PROVIDERS: Record = { + 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, + }, + }, + }, + }; + + const AUTODETECT: Record = { + anthropic: ["ANTHROPIC_API_KEY"], + }; + const state = App.state("llm", async (app) => { - const providers: Provider[] = []; - - 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 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)!; - } - 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; - } + 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; } - throw new ModelNotFoundError(model); } } diff --git a/js/src/llm/models/anthropic.ts b/js/src/llm/models/anthropic.ts new file mode 100644 index 000000000..e69de29bb diff --git a/js/src/llm/models/index.ts b/js/src/llm/models/index.ts new file mode 100644 index 000000000..f974b4d3e --- /dev/null +++ b/js/src/llm/models/index.ts @@ -0,0 +1 @@ +export * as anthropic from "./anthropic"; diff --git a/js/src/llm/models/model.ts b/js/src/llm/models/model.ts new file mode 100644 index 000000000..e78dbb87f --- /dev/null +++ b/js/src/llm/models/model.ts @@ -0,0 +1,11 @@ +export interface ModelInfo { + cost: { + input: number; + inputCached: number; + output: number; + outputCached: number; + }; + contextWindow: number; + maxTokens: number; + attachment: boolean; +} -- cgit v1.2.3