Remove mock data from harness and add agent credential healthchecks
Strip all seed/mock data (fake tasks, models, usage entries, agent configs) so the dashboard starts clean and populates from real API state. Add /api/agents/health endpoint that validates each agent's provider credentials and CLI availability.
This commit is contained in:
131
apps/harness/src/app/api/agents/health/route.ts
Normal file
131
apps/harness/src/app/api/agents/health/route.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getAllAgentConfigs, AGENT_RUNTIMES, AgentConfig } from "@/lib/agents";
|
||||
import { getRawCredentialsByProvider, Provider } from "@/lib/credentials";
|
||||
|
||||
const PROVIDER_ENV_VARS: Record<string, string> = {
|
||||
anthropic: "ANTHROPIC_API_KEY",
|
||||
openai: "OPENAI_API_KEY",
|
||||
google: "GOOGLE_API_KEY",
|
||||
openrouter: "OPENROUTER_API_KEY",
|
||||
"opencode-zen": "OPENCODE_ZEN_API_KEY",
|
||||
};
|
||||
|
||||
const PROVIDER_VALIDATION: Record<string, (token: string, baseUrl?: string) => Promise<boolean>> = {
|
||||
async anthropic(token) {
|
||||
const res = await fetch("https://api.anthropic.com/v1/models", {
|
||||
headers: { "x-api-key": token, "anthropic-version": "2023-06-01" },
|
||||
});
|
||||
return res.ok;
|
||||
},
|
||||
async openai(token, baseUrl) {
|
||||
const res = await fetch(`${baseUrl || "https://api.openai.com"}/v1/models`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
return res.ok;
|
||||
},
|
||||
async google(token) {
|
||||
const res = await fetch(
|
||||
`https://generativelanguage.googleapis.com/v1beta/models?key=${token}`
|
||||
);
|
||||
return res.ok;
|
||||
},
|
||||
async openrouter(token) {
|
||||
const res = await fetch("https://openrouter.ai/api/v1/models", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
return res.ok;
|
||||
},
|
||||
};
|
||||
|
||||
export interface AgentHealthStatus {
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
runtime: string;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
credentialConfigured: boolean;
|
||||
credentialValid: boolean | null; // null = not checked (no credential)
|
||||
cliInstalled: boolean | null; // null = not checked
|
||||
error?: string;
|
||||
}
|
||||
|
||||
async function checkCliInstalled(command: string): Promise<boolean> {
|
||||
try {
|
||||
const { execSync } = require("node:child_process");
|
||||
execSync(`which ${command}`, { stdio: "ignore" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAgent(config: AgentConfig): Promise<AgentHealthStatus> {
|
||||
const runtime = AGENT_RUNTIMES[config.runtime];
|
||||
const status: AgentHealthStatus = {
|
||||
agentId: config.id,
|
||||
agentName: config.name,
|
||||
runtime: config.runtime,
|
||||
provider: config.provider,
|
||||
modelId: config.modelId,
|
||||
credentialConfigured: false,
|
||||
credentialValid: null,
|
||||
cliInstalled: null,
|
||||
};
|
||||
|
||||
// Check CLI
|
||||
try {
|
||||
status.cliInstalled = await checkCliInstalled(runtime.cliCommand);
|
||||
} catch {
|
||||
status.cliInstalled = false;
|
||||
}
|
||||
|
||||
// Check credential exists
|
||||
const creds = getRawCredentialsByProvider(config.provider as Provider);
|
||||
status.credentialConfigured = creds.length > 0;
|
||||
|
||||
if (!status.credentialConfigured) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Validate credential against provider API
|
||||
const validator = PROVIDER_VALIDATION[config.provider];
|
||||
if (validator) {
|
||||
try {
|
||||
status.credentialValid = await validator(creds[0].token, creds[0].baseUrl);
|
||||
} catch (err) {
|
||||
status.credentialValid = false;
|
||||
status.error = err instanceof Error ? err.message : "Validation failed";
|
||||
}
|
||||
} else {
|
||||
// No validator for this provider (e.g. opencode-zen) — just confirm credential exists
|
||||
status.credentialValid = null;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const configs = getAllAgentConfigs();
|
||||
|
||||
if (configs.length === 0) {
|
||||
return NextResponse.json({
|
||||
agents: [],
|
||||
summary: { total: 0, healthy: 0, misconfigured: 0, unchecked: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
const agents = await Promise.all(configs.map(checkAgent));
|
||||
|
||||
const healthy = agents.filter(
|
||||
a => a.credentialConfigured && a.credentialValid === true && a.cliInstalled === true
|
||||
).length;
|
||||
const misconfigured = agents.filter(
|
||||
a => !a.credentialConfigured || a.credentialValid === false || a.cliInstalled === false
|
||||
).length;
|
||||
const unchecked = agents.length - healthy - misconfigured;
|
||||
|
||||
return NextResponse.json({
|
||||
agents,
|
||||
summary: { total: agents.length, healthy, misconfigured, unchecked },
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user