Auto-discover credentials, models, and agents on harness startup
All checks were successful
CI / lint-and-test (push) Successful in 20s
Deploy Production / deploy (push) Successful in 59s
CI / build (push) Successful in 1m13s

Read mounted secret files (Claude OAuth, OpenCode auth.json) and env
vars on boot, register them as credentials, fetch available models
from provider APIs, and create default agent configs for each viable
runtime+provider+model combination.
This commit is contained in:
Julia McGhee
2026-03-21 19:53:29 +00:00
parent e97614d568
commit 25b4769ff8
3 changed files with 203 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: "standalone", output: "standalone",
instrumentationHook: true,
}; };
module.exports = nextConfig; module.exports = nextConfig;

View File

@@ -0,0 +1,6 @@
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { boot } = await import("./lib/boot");
await boot();
}
}

View File

@@ -0,0 +1,196 @@
// Auto-discovery: load credentials from mounted secrets + env vars,
// discover models from providers, and create default agent configs.
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";
let booted = false;
// Well-known models with pricing (used as fallback when API discovery returns
// models without pricing info, and to enable cost tracking from the start).
const KNOWN_MODELS: Record<string, { name: string; provider: string; contextWindow: number; costPer1kInput: number; costPer1kOutput: number }> = {
"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 },
"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 },
"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 },
};
// Default agents to create per provider when credentials are available.
// Maps provider → [{ runtime, models[] }].
const DEFAULT_AGENTS: Record<string, { runtime: AgentRuntime; models: string[] }[]> = {
anthropic: [
{ runtime: "claude-code", models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514"] },
{ runtime: "opencode", models: ["claude-sonnet-4-20250514"] },
],
openai: [
{ runtime: "codex", models: ["o3", "o4-mini"] },
],
google: [
{ runtime: "opencode", models: ["gemini-2.5-pro", "gemini-2.5-flash"] },
],
openrouter: [
{ runtime: "opencode", models: ["claude-sonnet-4-20250514"] },
],
};
const RUNTIME_LABELS: Record<AgentRuntime, string> = {
"claude-code": "Claude Code",
"codex": "Codex",
"opencode": "OpenCode",
};
// ─── CREDENTIAL LOADING ─────────────────────────────────────
function loadCredentialsFromEnv() {
const envMap: [string, Provider, string][] = [
["ANTHROPIC_API_KEY", "anthropic", "Anthropic (env)"],
["OPENAI_API_KEY", "openai", "OpenAI (env)"],
["GOOGLE_API_KEY", "google", "Google (env)"],
["OPENROUTER_API_KEY", "openrouter", "OpenRouter (env)"],
["OPENCODE_ZEN_API_KEY", "opencode-zen", "OpenCode Zen (env)"],
["GITHUB_TOKEN", "github", "GitHub (env)"],
["GH_TOKEN", "github", "GitHub (env)"],
["GITLAB_TOKEN", "gitlab", "GitLab (env)"],
];
for (const [envVar, provider, label] of envMap) {
const token = process.env[envVar];
if (!token) continue;
// Don't overwrite if already loaded from file
if (getRawCredentialsByProvider(provider).length > 0) continue;
upsertCredential({ id: `env-${provider}`, provider, label, token });
}
}
function loadClaudeCredentials() {
const configDir = process.env.CLAUDE_CONFIG_DIR;
if (!configDir) return;
const credPath = `${configDir}/.credentials.json`;
if (!existsSync(credPath)) return;
try {
const raw = JSON.parse(readFileSync(credPath, "utf-8"));
// Claude Code OAuth credentials → extract access token for Anthropic API
if (raw.claudeAiOauth?.accessToken) {
upsertCredential({
id: "file-anthropic",
provider: "anthropic",
label: `Claude ${raw.claudeAiOauth.subscriptionType || "API"} (mounted)`,
token: raw.claudeAiOauth.accessToken,
});
}
} catch {
// ignore parse errors
}
}
function loadOpenCodeCredentials() {
const configDir = process.env.OPENCODE_CONFIG_DIR;
if (!configDir) return;
const authPath = `${configDir}/auth.json`;
if (!existsSync(authPath)) return;
try {
const raw = JSON.parse(readFileSync(authPath, "utf-8"));
// OpenCode auth.json: { "provider": { "type": "api", "key": "..." } }
const providerMap: Record<string, Provider> = {
anthropic: "anthropic",
openai: "openai",
google: "google",
openrouter: "openrouter",
opencode: "opencode-zen",
};
for (const [key, entry] of Object.entries(raw)) {
const provider = providerMap[key];
if (!provider || typeof entry !== "object" || !entry) continue;
const token = (entry as Record<string, unknown>).key;
if (typeof token !== "string" || !token) continue;
// Don't overwrite credentials already loaded from Claude config
if (getRawCredentialsByProvider(provider).length > 0) continue;
upsertCredential({
id: `file-${provider}`,
provider,
label: `${key} (mounted)`,
token,
});
}
} catch {
// ignore parse errors
}
}
// ─── MODEL + AGENT AUTO-DISCOVERY ───────────────────────────
async function discoverModelsAndAgents() {
// Fetch live models from all providers with credentials
const liveModels = await fetchAllModels();
// Upsert discovered models into curated store, enriched with known pricing
for (const m of liveModels) {
const known = KNOWN_MODELS[m.id];
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,
});
}
// Also add well-known models that have credentials but weren't returned by API
// (e.g. newer models not yet in /v1/models listing)
for (const [id, info] of Object.entries(KNOWN_MODELS)) {
if (getCuratedModels().some(m => m.id === id)) continue;
if (getRawCredentialsByProvider(info.provider as Provider).length === 0) continue;
upsertCuratedModel({ id, ...info, enabled: true });
}
// Create default agent configs if none exist yet
if (getAllAgentConfigs().length > 0) return;
for (const [provider, runtimes] of Object.entries(DEFAULT_AGENTS)) {
if (getRawCredentialsByProvider(provider as Provider).length === 0) continue;
for (const { runtime, models } of runtimes) {
for (const modelId of models) {
const known = KNOWN_MODELS[modelId];
const name = `${RUNTIME_LABELS[runtime]} · ${known?.name || modelId}`;
const id = `auto-${runtime}-${modelId}`.replace(/[^a-z0-9-]/g, "-");
upsertAgentConfig({ id, name, runtime, modelId, provider });
}
}
}
}
// ─── BOOT ───────────────────────────────────────────────────
export async function boot() {
if (booted) return;
booted = true;
// 1. Load credentials from mounted secrets (files take priority)
loadClaudeCredentials();
loadOpenCodeCredentials();
// 2. Fill gaps from env vars
loadCredentialsFromEnv();
// 3. Discover models and create agents (async, best-effort)
try {
await discoverModelsAndAgents();
} catch {
// non-fatal — models/agents will be empty until manually configured
}
}