Remove curated models table, use live provider APIs with filtering
Some checks failed
CI / lint-and-test (push) Successful in 34s
Deploy Production / deploy (push) Successful in 1m30s
CI / build (push) Has been cancelled

- 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:
Julia McGhee
2026-03-22 11:58:02 +00:00
parent af9b4b2452
commit 71e3215f89
10 changed files with 909 additions and 259 deletions

View File

@@ -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 });
}

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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) };
},
};

View File

@@ -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[]> {

View File

@@ -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) {

View File

@@ -0,0 +1 @@
DROP TABLE "harness_curated_models" CASCADE;

View 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": {}
}
}

View File

@@ -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
}
]
}

View File

@@ -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", {