wip: refactoring tui

This commit is contained in:
adamdottv
2025-05-30 15:34:22 -05:00
parent f5e2c596d4
commit c69c9327da
13 changed files with 244 additions and 263 deletions

View File

@@ -14,7 +14,7 @@ export namespace Config {
export const Info = z
.object({
providers: z.record(z.string(), Provider.Info).optional(),
providers: Provider.Info.array().optional(),
})
.strict();

View File

@@ -1,6 +1,6 @@
import { App } from "../app/app";
import { Log } from "../util/log";
import { mergeDeep } from "remeda";
import { concat } from "remeda";
import path from "path";
import { Provider } from "../provider/provider";
@@ -19,26 +19,32 @@ export namespace LLM {
}
}
const NATIVE_PROVIDERS: Record<string, Provider.Info> = {
anthropic: {
models: {
"claude-sonnet-4-20250514": {
name: "Claude 4 Sonnet",
const NATIVE_PROVIDERS: Provider.Info[] = [
{
id: "anthropic",
name: "Anthropic",
models: [
{
id: "claude-sonnet-4-20250514",
name: "Claude Sonnet 4",
cost: {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
inputCached: 3.75 / 1_000_000,
outputCached: 0.3 / 1_000_000,
},
contextWindow: 200000,
maxTokens: 50000,
contextWindow: 200_000,
maxOutputTokens: 50_000,
attachment: true,
},
},
],
},
openai: {
models: {
"codex-mini-latest": {
{
id: "openai",
name: "OpenAI",
models: [
{
id: "codex-mini-latest",
name: "Codex Mini",
cost: {
input: 1.5 / 1_000_000,
@@ -46,16 +52,19 @@ export namespace LLM {
output: 6.0 / 1_000_000,
outputCached: 0.0 / 1_000_000,
},
contextWindow: 200000,
maxTokens: 100000,
contextWindow: 200_000,
maxOutputTokens: 100_000,
attachment: true,
reasoning: true,
},
},
],
},
google: {
models: {
"gemini-2.5-pro-preview-03-25": {
{
id: "google",
name: "Google",
models: [
{
id: "gemini-2.5-pro-preview-03-25",
name: "Gemini 2.5 Pro",
cost: {
input: 1.25 / 1_000_000,
@@ -63,18 +72,18 @@ export namespace LLM {
output: 10 / 1_000_000,
outputCached: 0 / 1_000_000,
},
contextWindow: 1000000,
maxTokens: 50000,
contextWindow: 1_000_000,
maxOutputTokens: 50_000,
attachment: true,
},
},
],
},
};
];
const AUTODETECT: Record<string, string[]> = {
anthropic: ["ANTHROPIC_API_KEY"],
openai: ["OPENAI_API_KEY"],
google: ["GOOGLE_GENERATIVE_AI_API_KEY"],
google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"],
};
const state = App.state("llm", async () => {
@@ -91,33 +100,33 @@ export namespace LLM {
{ info: Provider.Model; instance: LanguageModel }
>();
const list = mergeDeep(NATIVE_PROVIDERS, config.providers ?? {});
const list = concat(NATIVE_PROVIDERS, config.providers ?? []);
for (const [providerID, providerInfo] of Object.entries(list)) {
for (const provider of list) {
if (
!config.providers?.[providerID] &&
!AUTODETECT[providerID]?.some((env) => process.env[env])
!config.providers?.find((p) => p.id === provider.id) &&
!AUTODETECT[provider.id]?.some((env) => process.env[env])
)
continue;
const dir = path.join(
Global.cache(),
`node_modules`,
`@ai-sdk`,
providerID,
provider.id,
);
if (!(await Bun.file(path.join(dir, "package.json")).exists())) {
BunProc.run(["add", "--exact", `@ai-sdk/${providerID}@alpha`], {
BunProc.run(["add", "--exact", `@ai-sdk/${provider.id}@alpha`], {
cwd: Global.cache(),
});
}
const mod = await import(
path.join(Global.cache(), `node_modules`, `@ai-sdk`, providerID)
path.join(Global.cache(), `node_modules`, `@ai-sdk`, provider.id)
);
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,
const loaded = fn(provider.options);
log.info("loaded", { provider: provider.id });
providers[provider.id] = {
info: provider,
instance: loaded,
};
}
@@ -142,7 +151,7 @@ export namespace LLM {
providerID,
modelID,
});
const info = provider.info.models[modelID];
const info = provider.info.models.find((m) => m.id === modelID);
if (!info) throw new ModelNotFoundError(modelID);
try {
const match = provider.instance.languageModel(modelID);

View File

@@ -3,6 +3,7 @@ import z from "zod";
export namespace Provider {
export const Model = z
.object({
id: z.string(),
name: z.string().optional(),
cost: z.object({
input: z.number(),
@@ -22,8 +23,10 @@ export namespace Provider {
export const Info = z
.object({
id: z.string(),
name: z.string(),
options: z.record(z.string(), z.any()).optional(),
models: z.record(z.string(), Model),
models: Model.array(),
})
.openapi({
ref: "Provider.Info",

View File

@@ -263,7 +263,7 @@ export namespace Server {
description: "List of providers",
content: {
"application/json": {
schema: resolver(z.record(z.string(), Provider.Info)),
schema: resolver(Provider.Info.array()),
},
},
},
@@ -271,9 +271,9 @@ export namespace Server {
}),
async (c) => {
const providers = await LLM.providers();
const result: Record<string, Provider.Info> = {};
for (const [providerID, provider] of Object.entries(providers)) {
result[providerID] = provider.info;
const result = [] as (Provider.Info & { key: string })[];
for (const [key, provider] of Object.entries(providers)) {
result.push({ ...provider.info, key });
}
return c.json(result);
},