/* RFC-1116: core logic for GET /api/agent/health — assembles the AgentHealth payload by self-fetching the deployed agent.json, one knowledge ref, and build-identity.json, plus reporting binding presence (dispatch token, search bindings). Framework-free: no astro imports, vitest-loadable — the Astro adapter in astro.ts only resolves env and wraps the response in CORS headers. Do not resolve astro:env or cloudflare:workers here — the adapter injects env + token via AgentHealthDeps. RFC-1116: initial implementation — runAgentHealth core extracted for testability. */ import { AGENT_SURFACE_VERSION } from "@warpgogol/werkstatt-shared/agent"; import type { AgentHealth, AgentSurfaceManifest } from "@warpgogol/werkstatt-shared/agent"; import { BUILD_IDENTITY_PATH } from "../leitstand/cache-purge.ts"; /** Runtime bindings the health probe reports on — presence only, never values. */ export interface AgentHealthDeps { /** The incoming request URL — self-fetch resolves same-origin paths against it. */ requestUrl: string; fetchFn: typeof fetch; env: { AI?: unknown; SEARCH_INDEX?: unknown; ASSETS?: { fetch(request: Request): Promise }; }; /** UPSTASH_QSTASH_TOKEN value or undefined — presence is all that is reported. */ qstashToken: string | undefined; } /** Self-fetch a same-origin path — ASSETS binding on Workers, plain fetch in dev. */ async function selfFetch(deps: AgentHealthDeps, path: string): Promise { try { const url = new URL(path, deps.requestUrl); return deps.env.ASSETS ? await deps.env.ASSETS.fetch(new Request(url)) : await deps.fetchFn(url); } catch { return null; } } /** * Assemble the AgentHealth payload. `degraded` when the manifest or a declared * knowledge ref is unreachable, the surface declares actions while the * dispatch token is absent, or the surface advertises search while its * bindings are missing. Returns the HTTP status * alongside the body so the adapter stays a thin wrapper. */ export async function runAgentHealth( manifest: AgentSurfaceManifest, deps: AgentHealthDeps, ): Promise<{ status: number; body: AgentHealth }> { const manifestRes = await selfFetch(deps, "/.well-known/agent.json"); const manifestOk = manifestRes?.ok === true; const knowledgeRef = manifest.knowledge[0]?.url; const knowledgeOk = knowledgeRef ? (await selfFetch(deps, knowledgeRef))?.ok === true : true; // vacuous — surface declares no knowledge refs const identityRes = await selfFetch(deps, BUILD_IDENTITY_PATH); let contentHash = ""; if (identityRes?.ok) { try { const identity = (await identityRes.json()) as { distTreeHash?: string }; contentHash = identity.distTreeHash ?? ""; } catch { contentHash = ""; } } // Dispatch (QStash) is only required when the surface declares write // actions — preview-only ("none") and empty actions[] manifests never // dispatch, so an absent UPSTASH_QSTASH_TOKEN must not degrade them // (discovered during nicaragua-projekt dev deploy: a knowledge-only // surface reported 503). const dispatchRequired = manifest.actions.some((a) => a.sideEffect !== "none"); const dispatchConfigured = Boolean(deps.qstashToken); const searchAvailable = Boolean(deps.env.AI && deps.env.SEARCH_INDEX); const searchAdvertised = manifest.interfaces.search !== null; const degraded = !manifestOk || !knowledgeOk || (dispatchRequired && !dispatchConfigured) || (searchAdvertised && !searchAvailable); return { status: degraded ? 503 : 200, body: { status: degraded ? "degraded" : "ok", surfaceVersion: AGENT_SURFACE_VERSION, contentHash, checks: { manifest: manifestOk ? "ok" : "missing", knowledge: knowledgeOk ? "ok" : "missing", dispatch: dispatchConfigured ? "configured" : "unconfigured", search: searchAvailable ? "available" : "unavailable", }, }, }; }