UNPKG

@essentialai/cogent-bridge

Version:

Cogent Bridge — cross-agent comms for Claude Code, OpenAI Codex and Slack. Codex users: install with `curl -fsSL https://cogent.tools/install.sh | sh`, then start with `cogent-codex` for real-time peer wake.

109 lines • 4.69 kB
import { logger } from "../logger.js"; /** * Module-scoped cache for the relay's `version` field (from GET /api/health). * * Rationale (VER-02, plan 17-03): * - Operators need to see "is my relay current?" at a glance via cogent_health_check. * - A hot-path HTTP call on every health_check is too expensive. * - A 60s TTL bounds staleness: relay upgrades become visible to the client * within 60s without a client restart, but the fetch is amortized across * N calls within the window. * * Failure semantics: * - On HTTP error: return null, DO NOT cache. Next call retries. * - On malformed payload (no `version` string): return null, DO NOT cache. * - Caller (health-check tool) treats null as "unknown" — does NOT propagate * the fetch failure upward; the rest of the health_check response is intact. * * Test seam: * - `nowFn` parameter (default `Date.now`) lets tests advance the clock * deterministically without coupling to vitest fake-timer state shared * across other suites. * - `__resetCacheForTest()` clears module state between tests. */ const TTL_MS = 60_000; let cached = null; /** * Get the relay's version, using a 60s TTL cache. Returns null on fetch * failure or malformed response. Does not throw. * * @param http Authenticated HttpClient (typically `HttpBackend.http`). * @param nowFn Override for `Date.now()` (test seam). Defaults to `Date.now`. */ export async function getRelayVersion(http, nowFn = Date.now) { const now = nowFn(); if (cached !== null && now - cached.fetchedAt < TTL_MS) { return cached.version; } return (await _refresh(http, now))?.version ?? null; } /** * Fetch and cache /api/health. Returns null (and caches nothing) on transport failure or a * payload with no usable `version`, so the next call retries. * * Shared by getRelayVersion and getRelayCapabilities so gating a wire field costs no extra * request — both read the same 60s-TTL entry. */ async function _refresh(http, now) { try { const resp = await http.get("/api/health"); const version = resp?.version; if (typeof version !== "string" || version.length === 0) { // Malformed response shape — treat as fetch failure. Do not cache. logger.debug(`relay-version-cache: /api/health response missing string 'version' field`); return null; } // A relay older than 2026-09-03 advertises nothing. That is a VALID answer meaning "supports // no new wire contracts", not a failure — so it is cached like any other. Anything that is // not an array of strings is ignored rather than trusted: a malformed field must never be // read as support, because the cost of a false positive is a 400 on every send. const raw = resp?.capabilities; const capabilities = Array.isArray(raw) ? raw.filter((c) => typeof c === "string") : []; cached = { version, capabilities, fetchedAt: now }; return cached; } catch (err) { const msg = err instanceof Error ? err.message : String(err); logger.debug(`relay-version-cache: /api/health fetch failed: ${msg}`); // Do NOT update `cached` — next call retries. return null; } } /** * Wire contracts the relay advertises, or null when the relay could not be reached. * * 🔴 null and [] MEAN DIFFERENT THINGS and callers must treat both as "no support": * - `[]` the relay answered and named nothing (an older build). * - `null` we do not know (unreachable / malformed). * Use relaySupports() rather than inspecting this directly. */ export async function getRelayCapabilities(http, nowFn = Date.now) { const now = nowFn(); if (cached !== null && now - cached.fetchedAt < TTL_MS) { return cached.capabilities; } return (await _refresh(http, now))?.capabilities ?? null; } /** * Does the relay accept this wire contract? * * 🔴 FAILS SAFE. Unknown is NOT support. The message POST schema is `.strict()`, so guessing wrong * in the optimistic direction is a hard 400 on every send — the 2026-08-07 outage and the * 2026-09-02 near-miss were both exactly that. Guessing wrong in this direction merely omits an * optional field. */ export async function relaySupports(http, capability, nowFn = Date.now) { return (await getRelayCapabilities(http, nowFn))?.includes(capability) ?? false; } /** * Test-only helper to clear the module-scoped cache. Call from beforeEach * in test suites that exercise the cache. * * @internal */ export function __resetCacheForTest() { cached = null; } //# sourceMappingURL=relay-version-cache.js.map