UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

576 lines (575 loc) • 21.5 kB
import { emitProxyOtelEvent, isProxyOtelOnly, initializeProxyOtelLogs, } from "./otelLogSink.js"; import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto"; import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { appendFile } from "node:fs/promises"; import { join } from "node:path"; import { performance } from "node:perf_hooks"; import { withTimeout } from "../utils/async/withTimeout.js"; import { logger } from "../utils/logger.js"; import { startProxyRuntimeMetrics } from "./proxyRuntimeMetrics.js"; const SCHEMA_VERSION = 1; const DEFAULT_QUEUE_CAPACITY = 10_000; const DEFAULT_BATCH_SIZE = 256; const DEFAULT_FLUSH_INTERVAL_MS = 25; const DEFAULT_MAX_WRITE_RETRIES = 3; const MAX_WRITE_RETRY_DELAY_MS = 1_000; const LIFECYCLE_APPEND_TIMEOUT_MS = 2_000; const MAX_SHORT_FIELD_LENGTH = 256; const SESSION_KEY_FILE = ".proxy-lifecycle-session-key"; let chmodLifecycleDirectory = chmodSync; let loggerEnabled = false; let loggerRequired = false; let stopRuntimeMetrics; let lifecycleLogDir; let filePrefix = "proxy-lifecycle"; let queueCapacity = DEFAULT_QUEUE_CAPACITY; let batchSize = DEFAULT_BATCH_SIZE; let flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS; let maxWriteRetries = DEFAULT_MAX_WRITE_RETRIES; let processInstanceId = randomUUID(); let sessionHashKey = randomBytes(32); let nextSequence = 1; let attempted = 0; let otelSubmitted = 0; let enqueued = 0; let written = 0; let dropped = 0; let queueDrops = 0; let invalidDrops = 0; let writeDrops = 0; let writeFailures = 0; let writeRetries = 0; let writeTimeouts = 0; let unconfirmedWrites = 0; let inFlight = 0; let queue = []; let flushTimer; let flushInFlight; let nextFlushDelayMs; let appendLifecycleFile = appendFile; function positiveInteger(value, fallback) { return Number.isInteger(value) && (value ?? 0) > 0 ? value : fallback; } function clip(value) { if (typeof value !== "string") { return undefined; } return value.length <= MAX_SHORT_FIELD_LENGTH ? value : value.slice(0, MAX_SHORT_FIELD_LENGTH); } function finiteNonNegative(value) { return value !== undefined && Number.isFinite(value) && value >= 0 ? value : undefined; } function nonNegativeInteger(value) { const finite = finiteNonNegative(value); return finite === undefined ? undefined : Math.floor(finite); } function formatTimestamp(timestampMs) { const candidate = typeof timestampMs === "number" && Number.isFinite(timestampMs) ? timestampMs : Date.now(); const date = new Date(candidate); return Number.isFinite(date.getTime()) ? date.toISOString() : new Date().toISOString(); } function readPersistedSessionHashKey(path) { try { const key = Buffer.from(readFileSync(path, "utf8").trim(), "base64"); if (key.length !== 32) { throw new Error("persisted lifecycle session key must contain 32 bytes"); } chmodSync(path, 0o600); return key; } catch (error) { if (error.code === "ENOENT") { return undefined; } throw error; } } function resolveSessionHashKey(logDir) { const configuredSecret = process.env.NEUROLINK_PROXY_SESSION_SECRET?.trim(); if (configuredSecret) { return createHash("sha256").update(configuredSecret).digest(); } const keyPath = join(logDir, SESSION_KEY_FILE); const existing = readPersistedSessionHashKey(keyPath); if (existing) { return existing; } const generated = randomBytes(32); try { writeFileSync(keyPath, generated.toString("base64"), { encoding: "utf8", flag: "wx", mode: 0o600, }); return generated; } catch (error) { if (error.code === "EEXIST") { const concurrentlyCreated = readPersistedSessionHashKey(keyPath); if (concurrentlyCreated) { return concurrentlyCreated; } } throw error; } } export function hashProxyLifecycleSessionId(sessionId) { if (!sessionId) { return undefined; } return createHmac("sha256", sessionHashKey) .update(sessionId) .digest("hex") .slice(0, 24); } function clearScheduledFlush() { if (flushTimer) { clearTimeout(flushTimer); flushTimer = undefined; } } function scheduleFlush(delayMs = flushIntervalMs) { if (flushTimer || flushInFlight || queue.length === 0) { return; } flushTimer = setTimeout(() => { flushTimer = undefined; void startFlush(); }, delayMs); flushTimer.unref?.(); } /** * Append a bounded metadata batch and confirm each admission without * replaying ambiguous writes. */ async function flushBatch() { if (queue.length === 0) { return; } const batch = queue.splice(0, batchSize); inFlight += batch.length; try { const byPath = new Map(); for (const item of batch) { const path = join(item.logDir, `${item.filePrefix ?? "proxy-lifecycle"}-${item.date}.jsonl`); const items = byPath.get(path) ?? []; items.push(item); byPath.set(path, items); } const retries = []; let retryDelayMs = 0; for (const [path, items] of byPath) { const lines = items.map((item) => `${JSON.stringify(item.record)}\n`); // A timeout does not cancel appendFile. Keep ownership of the original // operation until it settles; retrying it while it is still running can // append the same batch twice. The request path remains non-blocking. const timeout = setTimeout(() => { writeTimeouts += 1; }, LIFECYCLE_APPEND_TIMEOUT_MS); timeout.unref?.(); try { // OS-acknowledged append survives serving-process death. This is not // an fsync/power-loss guarantee. Admission waits on its own record. await appendLifecycleFile(path, lines.join(""), { mode: 0o600 }); written += lines.length; for (const item of items) { item.onPersisted?.(true); } } catch (error) { writeFailures += 1; // These errors prevent opening the destination. Other failures (for // example ENOSPC/EIO) can follow a partial append. Replaying those is // unsafe; expose uncertainty instead of claiming either loss or success. const code = error?.code; const definitelyNotWritten = new Set([ "ENOENT", "EACCES", "EPERM", "EROFS", "EMFILE", "ENFILE", ]).has(code ?? ""); if (!definitelyNotWritten) { unconfirmedWrites += items.length; for (const item of items) { item.onPersisted?.(false); } logger.warn("[proxy] lifecycle metadata append outcome is uncertain", { path, records: items.length, code, }); continue; } const retryable = items.filter((item) => item.writeRetries < maxWriteRetries); const exhausted = items.length - retryable.length; if (retryable.length > 0) { const nextRetries = retryable.map((item) => ({ ...item, writeRetries: item.writeRetries + 1, })); retries.push(...nextRetries); writeRetries += nextRetries.length; retryDelayMs = Math.max(retryDelayMs, Math.min(MAX_WRITE_RETRY_DELAY_MS, flushIntervalMs * 2 ** Math.max(...nextRetries.map((item) => item.writeRetries)))); } if (exhausted > 0) { dropped += exhausted; writeDrops += exhausted; for (const item of items) { if (item.writeRetries >= maxWriteRetries) { item.onPersisted?.(false); } } } logger.warn("[proxy] lifecycle metadata write failed", { path, retrying: retryable.length, dropped: exhausted, error: error instanceof Error ? error.message : String(error), }); } finally { clearTimeout(timeout); } } if (retries.length > 0) { // Keep retried records ahead of newly admitted records. This preserves // per-file sequence order while continuing to keep request paths async. queue.unshift(...retries); nextFlushDelayMs = Math.max(nextFlushDelayMs ?? 0, retryDelayMs || flushIntervalMs); } } finally { inFlight = Math.max(0, inFlight - batch.length); } } /** * Serialize batch ownership so timeouts cannot create overlapping append * retries. */ function startFlush() { if (flushInFlight) { return flushInFlight; } const currentFlush = flushBatch(); flushInFlight = currentFlush; void currentFlush.then(() => { if (flushInFlight === currentFlush) { flushInFlight = undefined; } const delayMs = nextFlushDelayMs; nextFlushDelayMs = undefined; scheduleFlush(delayMs); }, () => { if (flushInFlight === currentFlush) { flushInFlight = undefined; } const delayMs = nextFlushDelayMs; nextFlushDelayMs = undefined; scheduleFlush(delayMs); }); return currentFlush; } /** * Configure private journal storage while retaining required admission * when initialization fails. */ export function configureProxyLifecycleLogger(options) { stopRuntimeMetrics?.(); stopRuntimeMetrics = undefined; clearScheduledFlush(); nextFlushDelayMs = undefined; loggerEnabled = false; loggerRequired = options.enabled; filePrefix = options.filePrefix ?? "proxy-lifecycle"; lifecycleLogDir = undefined; queueCapacity = positiveInteger(options.queueCapacity, DEFAULT_QUEUE_CAPACITY); batchSize = positiveInteger(options.batchSize, DEFAULT_BATCH_SIZE); maxWriteRetries = positiveInteger(options.maxWriteRetries, DEFAULT_MAX_WRITE_RETRIES); flushIntervalMs = positiveInteger(options.flushIntervalMs, DEFAULT_FLUSH_INTERVAL_MS); if (options.enabled && isProxyOtelOnly()) { initializeProxyOtelLogs(options.filePrefix === "proxy-supervisor" ? "supervisor" : "worker"); loggerEnabled = true; sessionHashKey = process.env.NEUROLINK_PROXY_SESSION_SECRET ? createHash("sha256") .update(process.env.NEUROLINK_PROXY_SESSION_SECRET) .digest() : sessionHashKey; stopRuntimeMetrics = startProxyRuntimeMetrics((runtimeSample) => { logProxyLifecycleEvent({ event: "runtime_sample", requestId: "-", method: "-", path: "-", runtimeSample, }); }); return; } if (options.enabled && options.logDir) { try { mkdirSync(options.logDir, { recursive: true, mode: 0o700 }); // mkdir mode does not harden an existing directory. Keep admission // required but the sink disabled if its privacy boundary cannot be set. chmodLifecycleDirectory(options.logDir, 0o700); sessionHashKey = resolveSessionHashKey(options.logDir); lifecycleLogDir = options.logDir; loggerEnabled = true; stopRuntimeMetrics = startProxyRuntimeMetrics((runtimeSample) => { logProxyLifecycleEvent({ event: "runtime_sample", requestId: "-", method: "-", path: "-", runtimeSample, }); }); } catch (error) { logger.warn("[proxy] lifecycle metadata logging disabled", { error: error instanceof Error ? error.message : String(error), }); } } // Queued records retain their enqueue-time destination. Reconfiguration is // therefore atomic for new events while an in-flight or pending batch safely // drains to its original directory. scheduleFlush(); } /** Enqueue fixed-size lifecycle metadata without awaiting filesystem work. */ export function logProxyLifecycleEvent(input) { enqueueLifecycleEvent(input); } /** * Enqueue bounded metadata and resolve admission failure immediately when * capacity is unavailable. */ function enqueueLifecycleEvent(input, onPersisted) { if (!loggerEnabled || (!lifecycleLogDir && !isProxyOtelOnly())) { onPersisted?.(false); return; } try { attempted += 1; const sequence = nextSequence++; if (queue.length + inFlight >= queueCapacity) { dropped += 1; queueDrops += 1; onPersisted?.(false); return; } const timestamp = formatTimestamp(input.timestampMs); const monotonicMs = finiteNonNegative(input.monotonicMs) ?? performance.now(); const toolCount = nonNegativeInteger(input.toolCount); const requestBytes = nonNegativeInteger(input.requestBytes); const responseStatus = nonNegativeInteger(input.responseStatus); const observedBodyBytes = nonNegativeInteger(input.observedBodyBytes); const responseChunks = nonNegativeInteger(input.responseChunks); const elapsedMs = finiteNonNegative(input.elapsedMs); const requestTimeoutMs = finiteNonNegative(input.requestTimeoutMs); const parentRequestId = clip(input.parentRequestId); const model = clip(input.model); const sessionHash = clip(input.sessionHash); const terminalOutcome = clip(input.terminalOutcome); const errorType = clip(input.errorType); const errorCode = clip(input.errorCode); const record = { schemaVersion: SCHEMA_VERSION, timestamp, monotonicMs: Number(monotonicMs.toFixed(3)), processInstanceId, sequence, event: clip(input.event) ?? "unknown", requestId: clip(input.requestId) ?? "unknown", ...(parentRequestId !== undefined ? { parentRequestId } : {}), ...(input.accountingScope !== undefined ? { accountingScope: input.accountingScope } : {}), ...(requestTimeoutMs !== undefined ? { requestTimeoutMs } : {}), method: clip(input.method) ?? "unknown", path: clip(input.path) ?? "unknown", ...(model !== undefined ? { model } : {}), ...(input.stream !== undefined ? { stream: input.stream } : {}), ...(toolCount !== undefined ? { toolCount } : {}), ...(sessionHash !== undefined ? { sessionHash } : {}), ...(requestBytes !== undefined ? { requestBytes } : {}), ...(responseStatus !== undefined ? { responseStatus } : {}), ...(input.finalStatus !== undefined ? { finalStatus: nonNegativeInteger(input.finalStatus) } : {}), ...(input.telemetryStatus ? { telemetryStatus: input.telemetryStatus } : {}), ...(input.transportOutcome ? { transportOutcome: input.transportOutcome } : {}), ...(input.outcomeSource ? { outcomeSource: input.outcomeSource } : {}), ...(observedBodyBytes !== undefined ? { observedBodyBytes } : {}), ...(responseChunks !== undefined ? { responseChunks } : {}), ...(elapsedMs !== undefined ? { elapsedMs: Number(elapsedMs.toFixed(3)) } : {}), ...(terminalOutcome !== undefined ? { terminalOutcome } : {}), ...(errorType !== undefined ? { errorType } : {}), ...(errorCode !== undefined ? { errorCode } : {}), ...(input.supervisorEvent ? { supervisorEvent: { ...input.supervisorEvent, reason: clip(input.supervisorEvent.reason), }, } : {}), ...(input.runtimeSample ? { runtimeSample: input.runtimeSample } : {}), }; if (isProxyOtelOnly()) { emitProxyOtelEvent(filePrefix === "proxy-supervisor" ? "supervisor" : "lifecycle", record); otelSubmitted += 1; return; } queue.push({ filePrefix, logDir: lifecycleLogDir, date: String(record.timestamp).slice(0, 10), record, writeRetries: 0, onPersisted, }); enqueued += 1; scheduleFlush(); } catch { dropped += 1; invalidDrops += 1; onPersisted?.(false); } } /** Confirm admission before upstream dispatch, without waiting for later traffic. * Disabled logging is explicit; an enabled but unhealthy sink refuses dispatch. * A timeout never retries an ambiguous provider request or the pending append. */ export async function persistProxyLifecycleAcceptance(input, timeoutMs = LIFECYCLE_APPEND_TIMEOUT_MS) { if (!loggerRequired) { return; } if (isProxyOtelOnly()) { enqueueLifecycleEvent({ ...input, event: "request_accepted" }); return; } const confirmed = new Promise((resolve) => { enqueueLifecycleEvent({ ...input, event: "request_accepted" }, resolve); }); // Yield one turn for concurrent admissions to share a bounded batch. clearScheduledFlush(); scheduleFlush(0); const persisted = await withTimeout(confirmed, timeoutMs, "Proxy admission metadata append is still pending").catch(() => false); if (!persisted) { throw Object.assign(new Error("Proxy admission metadata could not be confirmed"), { code: "PROXY_TELEMETRY_UNAVAILABLE", }); } } export async function flushProxyLifecycleEvents(timeoutMs = 5_000) { clearScheduledFlush(); const deadline = performance.now() + timeoutMs; while (queue.length > 0 || flushInFlight) { await withTimeout(flushInFlight ?? startFlush(), Math.max(1, deadline - performance.now()), "Timed out flushing proxy lifecycle metadata; writes remain pending"); clearScheduledFlush(); if (performance.now() >= deadline && (queue.length > 0 || flushInFlight)) { throw new Error("Proxy lifecycle flush deadline exceeded; writes remain pending"); } } } /** * Expose journal accounting, process identity, and outstanding writes * without changing them. */ export function getProxyLifecycleLoggerSnapshot() { return { enabled: loggerEnabled, sink: isProxyOtelOnly() ? "otel" : "file", admissionPolicy: isProxyOtelOnly() ? "best-effort" : "durable-file", schemaVersion: SCHEMA_VERSION, processInstanceId, nextSequence, attempted, otelSubmitted, enqueued, written, dropped, queueDrops, invalidDrops, writeDrops, writeFailures, writeRetries, writeTimeouts, unconfirmedWrites, pending: queue.length, inFlight, flushing: flushInFlight !== undefined, }; } /** * Reset timers, accounting, and injected I/O after isolated tests have * drained their work. */ export function resetProxyLifecycleLoggerForTests() { stopRuntimeMetrics?.(); stopRuntimeMetrics = undefined; clearScheduledFlush(); loggerEnabled = false; loggerRequired = false; lifecycleLogDir = undefined; queueCapacity = DEFAULT_QUEUE_CAPACITY; batchSize = DEFAULT_BATCH_SIZE; flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS; maxWriteRetries = DEFAULT_MAX_WRITE_RETRIES; processInstanceId = randomUUID(); sessionHashKey = randomBytes(32); nextSequence = 1; attempted = 0; otelSubmitted = 0; enqueued = 0; written = 0; dropped = 0; queueDrops = 0; invalidDrops = 0; writeDrops = 0; writeFailures = 0; writeRetries = 0; writeTimeouts = 0; unconfirmedWrites = 0; inFlight = 0; queue = []; flushInFlight = undefined; nextFlushDelayMs = undefined; appendLifecycleFile = appendFile; chmodLifecycleDirectory = chmodSync; } /** Isolated failure injection for lifecycle durability tests. */ export const __proxyLifecycleTestHooks = { /** * Inject directory-hardening failures without altering real filesystem permissions. */ setChmodForTests(chmod) { chmodLifecycleDirectory = chmod; }, /** * Inject controlled append outcomes while preserving the production admission and queue paths. */ setAppendFileForTests(append) { appendLifecycleFile = append; }, };