Remove curated models table, use live provider APIs with filtering
- Remove harness_curated_models table from schema - Remove /api/models/curated route and all curated model functions - Move KNOWN_MODELS pricing table to model-providers.ts and enrich live API results with pricing/context window data - Filter out old models: dated IDs (claude-3-opus-20240229), legacy families (claude-3-*, gpt-3.5-*, gpt-4-*, text-*, etc.) - Update model_list chat tool to use fetchAllModels() instead of DB - Update ModelsTab to fetch from /api/models, remove toggle/delete - Update getUsageSummary to use KNOWN_MODELS for cost lookup - Simplify boot.ts: remove model syncing, keep agent auto-discovery
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
getCuratedModels,
|
||||
getEnabledModels,
|
||||
upsertCuratedModel,
|
||||
removeCuratedModel,
|
||||
toggleModelEnabled,
|
||||
updateModelCost,
|
||||
CuratedModel,
|
||||
} from "@/lib/model-store";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const enabledOnly = request.nextUrl.searchParams.get("enabled") === "true";
|
||||
return NextResponse.json(enabledOnly ? await getEnabledModels() : await getCuratedModels());
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
|
||||
if (body.action === "toggle" && body.id) {
|
||||
const result = await toggleModelEnabled(body.id);
|
||||
if (!result) return NextResponse.json({ error: "not found" }, { status: 404 });
|
||||
return NextResponse.json(result);
|
||||
}
|
||||
|
||||
if (body.action === "update-cost" && body.id) {
|
||||
const result = await updateModelCost(body.id, body.costPer1kInput, body.costPer1kOutput);
|
||||
if (!result) return NextResponse.json({ error: "not found" }, { status: 404 });
|
||||
return NextResponse.json(result);
|
||||
}
|
||||
|
||||
if (!body.id || !body.provider) {
|
||||
return NextResponse.json({ error: "id and provider are required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const model: CuratedModel = {
|
||||
id: body.id,
|
||||
name: body.name || body.id,
|
||||
provider: body.provider,
|
||||
enabled: body.enabled ?? true,
|
||||
contextWindow: body.contextWindow,
|
||||
costPer1kInput: body.costPer1kInput,
|
||||
costPer1kOutput: body.costPer1kOutput,
|
||||
};
|
||||
|
||||
return NextResponse.json(await upsertCuratedModel(model), { status: 201 });
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const id = request.nextUrl.searchParams.get("id");
|
||||
if (!id) return NextResponse.json({ error: "id required" }, { status: 400 });
|
||||
await removeCuratedModel(id);
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -1112,11 +1112,10 @@ function ProjectsTab({ projects, setProjects, mobile }: {
|
||||
|
||||
// ─── MODELS TAB ──────────────────────────────────────────────────────────────
|
||||
|
||||
interface CuratedModelDisplay {
|
||||
interface ModelDisplay {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
enabled: boolean;
|
||||
contextWindow?: number;
|
||||
costPer1kInput?: number;
|
||||
costPer1kOutput?: number;
|
||||
@@ -1163,7 +1162,7 @@ interface AgentRuntimeDisplay {
|
||||
}
|
||||
|
||||
function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
const [models, setModels] = useState<CuratedModelDisplay[]>([]);
|
||||
const [models, setModels] = useState<ModelDisplay[]>([]);
|
||||
const [usage, setUsage] = useState<{ summary: UsageSummaryDisplay[]; log: UsageLogEntry[] }>({ summary: [], log: [] });
|
||||
const [agents, setAgents] = useState<AgentConfigDisplay[]>([]);
|
||||
const [runtimes, setRuntimes] = useState<AgentRuntimeDisplay[]>([]);
|
||||
@@ -1179,7 +1178,9 @@ function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
const [newAgentProvider, setNewAgentProvider] = useState("anthropic");
|
||||
|
||||
const loadModels = () => {
|
||||
fetch("/api/models/curated").then(r => r.json()).then(setModels).catch(() => {});
|
||||
fetch("/api/models").then(r => r.json()).then((data: ModelDisplay[]) => {
|
||||
setModels(Array.isArray(data) ? data : []);
|
||||
}).catch(() => {});
|
||||
};
|
||||
const loadUsage = () => {
|
||||
fetch("/api/models/usage").then(r => r.json()).then(setUsage).catch(() => {});
|
||||
@@ -1193,19 +1194,7 @@ function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
|
||||
useEffect(() => { loadModels(); loadUsage(); loadAgents(); }, []);
|
||||
|
||||
const toggleModel = async (id: string) => {
|
||||
await fetch("/api/models/curated", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ action: "toggle", id }),
|
||||
});
|
||||
loadModels();
|
||||
};
|
||||
|
||||
const deleteModel = async (id: string) => {
|
||||
await fetch(`/api/models/curated?id=${id}`, { method: "DELETE" });
|
||||
loadModels();
|
||||
};
|
||||
// Models are fetched live from providers, no toggle/delete needed
|
||||
|
||||
const providers = ["ALL", ...Array.from(new Set(models.map(m => m.provider)))];
|
||||
const CATALOG_PAGE_SIZE = 25;
|
||||
@@ -1336,7 +1325,7 @@ function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: tokens.space[1] }}>
|
||||
<Label color={tokens.color.text3}>Model</Label>
|
||||
<SearchableDropdown
|
||||
options={models.filter(m => m.provider === newAgentProvider && m.enabled).map(m => ({
|
||||
options={models.filter(m => m.provider === newAgentProvider).map(m => ({
|
||||
value: m.id,
|
||||
label: m.name,
|
||||
detail: `${m.provider}${m.contextWindow ? ` · ${formatTokens(m.contextWindow)} ctx` : ""}`,
|
||||
@@ -1453,8 +1442,7 @@ function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: tokens.space[2] }}>
|
||||
{paginatedModels.map(m => (
|
||||
<Panel key={m.id} style={{
|
||||
borderLeft: `3px solid ${m.enabled ? tokens.color.pass : tokens.color.border0}`,
|
||||
opacity: m.enabled ? 1 : 0.6,
|
||||
borderLeft: `3px solid ${tokens.color.pass}`,
|
||||
}}>
|
||||
<div style={{
|
||||
padding: `${tokens.space[3]}px ${tokens.space[4]}px`,
|
||||
@@ -1466,7 +1454,6 @@ function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
<div style={{ display: "flex", alignItems: "center", gap: tokens.space[2], flexWrap: "wrap" }}>
|
||||
<ProviderBadge provider={m.provider} />
|
||||
<Mono size={tokens.size.base} color={tokens.color.text0}>{m.name}</Mono>
|
||||
{!m.enabled && <StatusBadge status="pending" style={{ fontSize: 8 }} />}
|
||||
</div>
|
||||
<Mono size={tokens.size.xs} color={tokens.color.text3}>{m.id}</Mono>
|
||||
</div>
|
||||
@@ -1489,16 +1476,6 @@ function ModelsTab({ mobile }: { mobile: boolean }) {
|
||||
<Label color={tokens.color.text3}>OUT/1K</Label>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: "flex", gap: tokens.space[1] }}>
|
||||
<Btn variant={m.enabled ? "default" : "primary"} onClick={() => toggleModel(m.id)}
|
||||
style={{ fontSize: tokens.size.xs, minHeight: 32 }}>
|
||||
{m.enabled ? "DISABLE" : "ENABLE"}
|
||||
</Btn>
|
||||
<Btn variant="ghost" onClick={() => deleteModel(m.id)}
|
||||
style={{ fontSize: tokens.size.xs, minHeight: 32, color: tokens.color.fail }}>
|
||||
DELETE
|
||||
</Btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
@@ -3,38 +3,8 @@
|
||||
|
||||
import { readFileSync, existsSync } from "node:fs";
|
||||
import { upsertCredential, getRawCredentialsByProvider, type Provider } from "./credentials";
|
||||
import { upsertCuratedModel, getCuratedModels } from "./model-store";
|
||||
import { upsertAgentConfig, getAllAgentConfigs, type AgentRuntime } from "./agents";
|
||||
import { fetchAllModels } from "./model-providers";
|
||||
|
||||
// Well-known models with pricing (per 1k tokens)
|
||||
const KNOWN_MODELS: Record<string, { name: string; provider: string; contextWindow: number; costPer1kInput: number; costPer1kOutput: number }> = {
|
||||
// Anthropic (direct)
|
||||
"claude-opus-4-20250514": { name: "Claude Opus 4", provider: "anthropic", contextWindow: 200000, costPer1kInput: 0.015, costPer1kOutput: 0.075 },
|
||||
"claude-sonnet-4-20250514": { name: "Claude Sonnet 4", provider: "anthropic", contextWindow: 200000, costPer1kInput: 0.003, costPer1kOutput: 0.015 },
|
||||
"claude-haiku-4-20250514": { name: "Claude Haiku 4", provider: "anthropic", contextWindow: 200000, costPer1kInput: 0.0008, costPer1kOutput: 0.004 },
|
||||
// OpenAI (direct)
|
||||
"gpt-4o": { name: "GPT-4o", provider: "openai", contextWindow: 128000, costPer1kInput: 0.0025, costPer1kOutput: 0.01 },
|
||||
"gpt-4o-mini": { name: "GPT-4o Mini", provider: "openai", contextWindow: 128000, costPer1kInput: 0.00015, costPer1kOutput: 0.0006 },
|
||||
"o3": { name: "o3", provider: "openai", contextWindow: 200000, costPer1kInput: 0.01, costPer1kOutput: 0.04 },
|
||||
"o4-mini": { name: "o4 Mini", provider: "openai", contextWindow: 200000, costPer1kInput: 0.0011, costPer1kOutput: 0.0044 },
|
||||
// Google (direct)
|
||||
"gemini-2.5-pro": { name: "Gemini 2.5 Pro", provider: "google", contextWindow: 1048576, costPer1kInput: 0.00125, costPer1kOutput: 0.01 },
|
||||
"gemini-2.5-flash": { name: "Gemini 2.5 Flash", provider: "google", contextWindow: 1048576, costPer1kInput: 0.00015, costPer1kOutput: 0.0006 },
|
||||
// OpenCode Zen (pricing per 1M from docs, converted to per 1k)
|
||||
"claude-opus-4.6": { name: "Claude Opus 4.6", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.005, costPer1kOutput: 0.025 },
|
||||
"claude-sonnet-4.6": { name: "Claude Sonnet 4.6", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.003, costPer1kOutput: 0.015 },
|
||||
"claude-haiku-4.5": { name: "Claude Haiku 4.5", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.0008, costPer1kOutput: 0.004 },
|
||||
"gpt-5.4": { name: "GPT-5.4", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.0025, costPer1kOutput: 0.015 },
|
||||
"gpt-5.3-codex": { name: "GPT-5.3 Codex", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.002, costPer1kOutput: 0.008 },
|
||||
"gemini-3.1-pro": { name: "Gemini 3.1 Pro", provider: "opencode-zen", contextWindow: 1048576, costPer1kInput: 0.00125, costPer1kOutput: 0.005 },
|
||||
"gemini-3-flash": { name: "Gemini 3 Flash", provider: "opencode-zen", contextWindow: 1048576, costPer1kInput: 0.0005, costPer1kOutput: 0.003 },
|
||||
// OpenCode Go (fixed subscription, no per-token pricing)
|
||||
"glm-5": { name: "GLM-5", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
"kimi-k2.5": { name: "Kimi K2.5", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
"minimax-m2.7": { name: "MiniMax M2.7", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
"minimax-m2.5": { name: "MiniMax M2.5", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
};
|
||||
import { fetchAllModels, KNOWN_MODELS } from "./model-providers";
|
||||
|
||||
// Default agents to create per provider when credentials are available.
|
||||
const DEFAULT_AGENTS: Record<string, { runtime: AgentRuntime; models: string[] }[]> = {
|
||||
@@ -165,34 +135,9 @@ async function loadOpenCodeCredentials() {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── MODEL + AGENT AUTO-DISCOVERY ───────────────────────────
|
||||
|
||||
async function discoverModelsAndAgents() {
|
||||
const liveModels = await fetchAllModels();
|
||||
|
||||
for (const m of liveModels) {
|
||||
const known = KNOWN_MODELS[m.id];
|
||||
await upsertCuratedModel({
|
||||
id: m.id,
|
||||
name: known?.name || m.name,
|
||||
provider: m.provider,
|
||||
enabled: true,
|
||||
contextWindow: m.contextWindow || known?.contextWindow,
|
||||
costPer1kInput: known?.costPer1kInput,
|
||||
costPer1kOutput: known?.costPer1kOutput,
|
||||
});
|
||||
}
|
||||
|
||||
// Add well-known models with credentials that weren't in the API listing
|
||||
const allModels = await getCuratedModels();
|
||||
const existingIds = new Set(allModels.map(m => m.id));
|
||||
for (const [id, info] of Object.entries(KNOWN_MODELS)) {
|
||||
if (existingIds.has(id)) continue;
|
||||
const creds = await getRawCredentialsByProvider(info.provider as Provider);
|
||||
if (creds.length === 0) continue;
|
||||
await upsertCuratedModel({ id, ...info, enabled: true });
|
||||
}
|
||||
// ─── AGENT AUTO-DISCOVERY ────────────────────────────────────
|
||||
|
||||
async function discoverAgents() {
|
||||
// Create default agent configs if none exist
|
||||
const existingAgents = await getAllAgentConfigs();
|
||||
if (existingAgents.length > 0) return;
|
||||
@@ -221,7 +166,7 @@ export async function boot() {
|
||||
await loadCredentialsFromEnv();
|
||||
// 3. Discover models and create agents (best-effort)
|
||||
try {
|
||||
await discoverModelsAndAgents();
|
||||
await discoverAgents();
|
||||
} catch (err) {
|
||||
console.error("[boot] Model/agent discovery failed:", err);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
tasks as tasksTable,
|
||||
iterations as iterationsTable,
|
||||
agentConfigs as agentTable,
|
||||
curatedModels as modelsTable,
|
||||
orchestratorState as orchTable,
|
||||
} from "@homelab/db";
|
||||
import { fetchAllModels } from "./model-providers";
|
||||
import { readFile, writeFile, readdir, mkdir, stat, access } from "node:fs/promises";
|
||||
import { execFile } from "node:child_process";
|
||||
import path from "node:path";
|
||||
@@ -311,15 +311,7 @@ const modelList: ToolDef = {
|
||||
name: "model_list",
|
||||
description: "List available AI models with pricing information",
|
||||
async execute() {
|
||||
const rows = await db.select().from(modelsTable).where(eq(modelsTable.enabled, true));
|
||||
const models = rows.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
provider: r.provider,
|
||||
contextWindow: r.contextWindow,
|
||||
costPer1kInput: r.costPer1kInput,
|
||||
costPer1kOutput: r.costPer1kOutput,
|
||||
}));
|
||||
const models = await fetchAllModels();
|
||||
return { text: JSON.stringify(models, null, 2) };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,52 @@ export interface ModelInfo {
|
||||
name: string;
|
||||
provider: string;
|
||||
contextWindow?: number;
|
||||
costPer1kInput?: number;
|
||||
costPer1kOutput?: number;
|
||||
}
|
||||
|
||||
// Well-known models with pricing (per 1k tokens) and context windows.
|
||||
// Used to enrich live API results with pricing data.
|
||||
export const KNOWN_MODELS: Record<string, { name: string; provider: string; contextWindow: number; costPer1kInput: number; costPer1kOutput: number }> = {
|
||||
// Anthropic
|
||||
"claude-opus-4-20250514": { name: "Claude Opus 4", provider: "anthropic", contextWindow: 200000, costPer1kInput: 0.015, costPer1kOutput: 0.075 },
|
||||
"claude-sonnet-4-20250514": { name: "Claude Sonnet 4", provider: "anthropic", contextWindow: 200000, costPer1kInput: 0.003, costPer1kOutput: 0.015 },
|
||||
"claude-haiku-4-20250514": { name: "Claude Haiku 4", provider: "anthropic", contextWindow: 200000, costPer1kInput: 0.0008, costPer1kOutput: 0.004 },
|
||||
// OpenAI
|
||||
"gpt-4o": { name: "GPT-4o", provider: "openai", contextWindow: 128000, costPer1kInput: 0.0025, costPer1kOutput: 0.01 },
|
||||
"gpt-4o-mini": { name: "GPT-4o Mini", provider: "openai", contextWindow: 128000, costPer1kInput: 0.00015, costPer1kOutput: 0.0006 },
|
||||
"o3": { name: "o3", provider: "openai", contextWindow: 200000, costPer1kInput: 0.01, costPer1kOutput: 0.04 },
|
||||
"o4-mini": { name: "o4 Mini", provider: "openai", contextWindow: 200000, costPer1kInput: 0.0011, costPer1kOutput: 0.0044 },
|
||||
// Google
|
||||
"gemini-2.5-pro": { name: "Gemini 2.5 Pro", provider: "google", contextWindow: 1048576, costPer1kInput: 0.00125, costPer1kOutput: 0.01 },
|
||||
"gemini-2.5-flash": { name: "Gemini 2.5 Flash", provider: "google", contextWindow: 1048576, costPer1kInput: 0.00015, costPer1kOutput: 0.0006 },
|
||||
// OpenCode Zen
|
||||
"claude-opus-4.6": { name: "Claude Opus 4.6", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.005, costPer1kOutput: 0.025 },
|
||||
"claude-sonnet-4.6": { name: "Claude Sonnet 4.6", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.003, costPer1kOutput: 0.015 },
|
||||
"claude-haiku-4.5": { name: "Claude Haiku 4.5", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.0008, costPer1kOutput: 0.004 },
|
||||
"gpt-5.4": { name: "GPT-5.4", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.0025, costPer1kOutput: 0.015 },
|
||||
"gpt-5.3-codex": { name: "GPT-5.3 Codex", provider: "opencode-zen", contextWindow: 200000, costPer1kInput: 0.002, costPer1kOutput: 0.008 },
|
||||
"gemini-3.1-pro": { name: "Gemini 3.1 Pro", provider: "opencode-zen", contextWindow: 1048576, costPer1kInput: 0.00125, costPer1kOutput: 0.005 },
|
||||
"gemini-3-flash": { name: "Gemini 3 Flash", provider: "opencode-zen", contextWindow: 1048576, costPer1kInput: 0.0005, costPer1kOutput: 0.003 },
|
||||
// OpenCode Go (fixed subscription)
|
||||
"glm-5": { name: "GLM-5", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
"kimi-k2.5": { name: "Kimi K2.5", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
"minimax-m2.7": { name: "MiniMax M2.7", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
"minimax-m2.5": { name: "MiniMax M2.5", provider: "opencode-go", contextWindow: 128000, costPer1kInput: 0, costPer1kOutput: 0 },
|
||||
};
|
||||
|
||||
// Filter out old/dated model IDs — keep only current models
|
||||
const DATED_MODEL_RE = /-\d{8}$/; // e.g. claude-3-opus-20240229
|
||||
const OLD_PREFIXES = ["claude-3-", "claude-2", "claude-instant", "gpt-3.5", "gpt-4-", "text-", "babbage", "davinci"];
|
||||
|
||||
function isCurrentModel(id: string): boolean {
|
||||
// Always keep known models
|
||||
if (KNOWN_MODELS[id]) return true;
|
||||
// Filter dated model IDs
|
||||
if (DATED_MODEL_RE.test(id)) return false;
|
||||
// Filter old model families
|
||||
if (OLD_PREFIXES.some((p) => id.startsWith(p))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function fetchAllModels(): Promise<ModelInfo[]> {
|
||||
@@ -17,7 +63,24 @@ export async function fetchAllModels(): Promise<ModelInfo[]> {
|
||||
fetchOpenCodeGoModels(),
|
||||
]);
|
||||
|
||||
return results.flatMap(r => r.status === "fulfilled" ? r.value : []);
|
||||
const all = results.flatMap(r => r.status === "fulfilled" ? r.value : []);
|
||||
|
||||
// Enrich with known pricing/context and filter old models
|
||||
return all
|
||||
.filter((m) => isCurrentModel(m.id))
|
||||
.map((m) => {
|
||||
const known = KNOWN_MODELS[m.id];
|
||||
if (known) {
|
||||
return {
|
||||
...m,
|
||||
name: known.name || m.name,
|
||||
contextWindow: m.contextWindow || known.contextWindow,
|
||||
costPer1kInput: known.costPer1kInput,
|
||||
costPer1kOutput: known.costPer1kOutput,
|
||||
};
|
||||
}
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchAnthropicModels(): Promise<ModelInfo[]> {
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "./db";
|
||||
import { curatedModels as modelsTable, modelUsage as usageTable } from "@homelab/db";
|
||||
|
||||
export interface CuratedModel {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
enabled: boolean;
|
||||
contextWindow?: number;
|
||||
costPer1kInput?: number;
|
||||
costPer1kOutput?: number;
|
||||
}
|
||||
import { modelUsage as usageTable } from "@homelab/db";
|
||||
import { KNOWN_MODELS } from "./model-providers";
|
||||
|
||||
export interface ModelUsageEntry {
|
||||
modelId: string;
|
||||
@@ -34,74 +24,6 @@ export interface ModelUsageSummary {
|
||||
totalDurationMs: number;
|
||||
}
|
||||
|
||||
function rowToModel(row: typeof modelsTable.$inferSelect): CuratedModel {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
provider: row.provider,
|
||||
enabled: row.enabled,
|
||||
contextWindow: row.contextWindow ?? undefined,
|
||||
costPer1kInput: row.costPer1kInput ?? undefined,
|
||||
costPer1kOutput: row.costPer1kOutput ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── CURATED MODELS ─────────────────────────────────────────
|
||||
|
||||
export async function getCuratedModels(): Promise<CuratedModel[]> {
|
||||
const rows = await db.select().from(modelsTable);
|
||||
return rows.map(rowToModel);
|
||||
}
|
||||
|
||||
export async function getEnabledModels(): Promise<CuratedModel[]> {
|
||||
const rows = await db.select().from(modelsTable).where(eq(modelsTable.enabled, true));
|
||||
return rows.map(rowToModel);
|
||||
}
|
||||
|
||||
export async function upsertCuratedModel(model: CuratedModel): Promise<CuratedModel> {
|
||||
await db.insert(modelsTable).values({
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
provider: model.provider,
|
||||
enabled: model.enabled,
|
||||
contextWindow: model.contextWindow,
|
||||
costPer1kInput: model.costPer1kInput,
|
||||
costPer1kOutput: model.costPer1kOutput,
|
||||
}).onConflictDoUpdate({
|
||||
target: modelsTable.id,
|
||||
set: {
|
||||
name: model.name,
|
||||
provider: model.provider,
|
||||
enabled: model.enabled,
|
||||
contextWindow: model.contextWindow,
|
||||
costPer1kInput: model.costPer1kInput,
|
||||
costPer1kOutput: model.costPer1kOutput,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
return model;
|
||||
}
|
||||
|
||||
export async function removeCuratedModel(id: string): Promise<boolean> {
|
||||
const result = await db.delete(modelsTable).where(eq(modelsTable.id, id));
|
||||
return (result as unknown as { rowCount: number }).rowCount > 0;
|
||||
}
|
||||
|
||||
export async function toggleModelEnabled(id: string): Promise<CuratedModel | undefined> {
|
||||
const [row] = await db.select().from(modelsTable).where(eq(modelsTable.id, id));
|
||||
if (!row) return undefined;
|
||||
const newEnabled = !row.enabled;
|
||||
await db.update(modelsTable).set({ enabled: newEnabled, updatedAt: new Date() }).where(eq(modelsTable.id, id));
|
||||
return rowToModel({ ...row, enabled: newEnabled });
|
||||
}
|
||||
|
||||
export async function updateModelCost(id: string, costPer1kInput: number, costPer1kOutput: number): Promise<CuratedModel | undefined> {
|
||||
const [row] = await db.select().from(modelsTable).where(eq(modelsTable.id, id));
|
||||
if (!row) return undefined;
|
||||
await db.update(modelsTable).set({ costPer1kInput, costPer1kOutput, updatedAt: new Date() }).where(eq(modelsTable.id, id));
|
||||
return rowToModel({ ...row, costPer1kInput, costPer1kOutput });
|
||||
}
|
||||
|
||||
// ─── USAGE TRACKING ─────────────────────────────────────────
|
||||
|
||||
export async function recordUsage(entry: ModelUsageEntry): Promise<void> {
|
||||
@@ -134,20 +56,14 @@ export async function getUsageLog(): Promise<ModelUsageEntry[]> {
|
||||
}
|
||||
|
||||
export async function getUsageSummary(): Promise<ModelUsageSummary[]> {
|
||||
// Fetch usage + model cost data, aggregate in JS to match prior behavior
|
||||
const [usage, models] = await Promise.all([
|
||||
db.select().from(usageTable),
|
||||
db.select().from(modelsTable),
|
||||
]);
|
||||
|
||||
const modelMap = new Map(models.map(m => [m.id, m]));
|
||||
const usage = await db.select().from(usageTable);
|
||||
const grouped = new Map<string, ModelUsageSummary>();
|
||||
|
||||
for (const entry of usage) {
|
||||
const key = `${entry.provider}:${entry.modelId}`;
|
||||
const model = modelMap.get(entry.modelId);
|
||||
const inputCost = model?.costPer1kInput ? (entry.inputTokens / 1000) * model.costPer1kInput : 0;
|
||||
const outputCost = model?.costPer1kOutput ? (entry.outputTokens / 1000) * model.costPer1kOutput : 0;
|
||||
const known = KNOWN_MODELS[entry.modelId];
|
||||
const inputCost = known?.costPer1kInput ? (entry.inputTokens / 1000) * known.costPer1kInput : 0;
|
||||
const outputCost = known?.costPer1kOutput ? (entry.outputTokens / 1000) * known.costPer1kOutput : 0;
|
||||
|
||||
const existing = grouped.get(key);
|
||||
if (existing) {
|
||||
|
||||
1
packages/db/drizzle/0004_complete_jack_murdock.sql
Normal file
1
packages/db/drizzle/0004_complete_jack_murdock.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE "harness_curated_models" CASCADE;
|
||||
817
packages/db/drizzle/meta/0004_snapshot.json
Normal file
817
packages/db/drizzle/meta/0004_snapshot.json
Normal file
@@ -0,0 +1,817 @@
|
||||
{
|
||||
"id": "c8c9e029-bf55-4d40-9bd1-f666aee2e08b",
|
||||
"prevId": "b267cedb-22f5-49e9-b3b7-62081da35a43",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.harness_agent_configs": {
|
||||
"name": "harness_agent_configs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"runtime": {
|
||||
"name": "runtime",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"model_id": {
|
||||
"name": "model_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"max_tokens": {
|
||||
"name": "max_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"env": {
|
||||
"name": "env",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_chat_conversations": {
|
||||
"name": "harness_chat_conversations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"label": {
|
||||
"name": "label",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"workspace": {
|
||||
"name": "workspace",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"messages": {
|
||||
"name": "messages",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_credentials": {
|
||||
"name": "harness_credentials",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"label": {
|
||||
"name": "label",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"base_url": {
|
||||
"name": "base_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_event_log": {
|
||||
"name": "harness_event_log",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"trigger_id": {
|
||||
"name": "trigger_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"delivery_id": {
|
||||
"name": "delivery_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"event_type": {
|
||||
"name": "event_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"repo": {
|
||||
"name": "repo",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"commit_sha": {
|
||||
"name": "commit_sha",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"branch": {
|
||||
"name": "branch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'received'"
|
||||
},
|
||||
"task_id": {
|
||||
"name": "task_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"skip_reason": {
|
||||
"name": "skip_reason",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payload": {
|
||||
"name": "payload",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"harness_event_log_delivery_id_unique": {
|
||||
"name": "harness_event_log_delivery_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"delivery_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_event_triggers": {
|
||||
"name": "harness_event_triggers",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"event_type": {
|
||||
"name": "event_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"repo_filter": {
|
||||
"name": "repo_filter",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"state_filter": {
|
||||
"name": "state_filter",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"context_filter": {
|
||||
"name": "context_filter",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"task_template": {
|
||||
"name": "task_template",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"consecutive_failures": {
|
||||
"name": "consecutive_failures",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"max_consecutive_failures": {
|
||||
"name": "max_consecutive_failures",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 3
|
||||
},
|
||||
"disabled_reason": {
|
||||
"name": "disabled_reason",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"webhook_secret": {
|
||||
"name": "webhook_secret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_iterations": {
|
||||
"name": "harness_iterations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"task_id": {
|
||||
"name": "task_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"n": {
|
||||
"name": "n",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"diagnosis": {
|
||||
"name": "diagnosis",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"agent_output": {
|
||||
"name": "agent_output",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"evals": {
|
||||
"name": "evals",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"diff_stats": {
|
||||
"name": "diff_stats",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_model_usage": {
|
||||
"name": "harness_model_usage",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"model_id": {
|
||||
"name": "model_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"task_id": {
|
||||
"name": "task_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"task_slug": {
|
||||
"name": "task_slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"iteration": {
|
||||
"name": "iteration",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"input_tokens": {
|
||||
"name": "input_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"output_tokens": {
|
||||
"name": "output_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"duration_ms": {
|
||||
"name": "duration_ms",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_orchestrator": {
|
||||
"name": "harness_orchestrator",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "'singleton'"
|
||||
},
|
||||
"running": {
|
||||
"name": "running",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"current_task_id": {
|
||||
"name": "current_task_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"heartbeat": {
|
||||
"name": "heartbeat",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_projects": {
|
||||
"name": "harness_projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspaces": {
|
||||
"name": "workspaces",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.harness_tasks": {
|
||||
"name": "harness_tasks",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"goal": {
|
||||
"name": "goal",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"iteration": {
|
||||
"name": "iteration",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"max_iterations": {
|
||||
"name": "max_iterations",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 6
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"project": {
|
||||
"name": "project",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'—'"
|
||||
},
|
||||
"evals": {
|
||||
"name": "evals",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"pr": {
|
||||
"name": "pr",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cancel_requested": {
|
||||
"name": "cancel_requested",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"spec": {
|
||||
"name": "spec",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,13 @@
|
||||
"when": 1774179559510,
|
||||
"tag": "0003_damp_glorian",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1774180634616,
|
||||
"tag": "0004_complete_jack_murdock",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -32,20 +32,6 @@ export const credentials = pgTable("harness_credentials", {
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// ─── HARNESS: CURATED MODELS ───────────────────────────────
|
||||
|
||||
export const curatedModels = pgTable("harness_curated_models", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
enabled: boolean("enabled").default(true).notNull(),
|
||||
contextWindow: integer("context_window"),
|
||||
costPer1kInput: real("cost_per_1k_input"),
|
||||
costPer1kOutput: real("cost_per_1k_output"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// ─── HARNESS: MODEL USAGE ──────────────────────────────────
|
||||
|
||||
export const modelUsage = pgTable("harness_model_usage", {
|
||||
|
||||
Reference in New Issue
Block a user