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

188 lines (187 loc) • 8.63 kB
/** * Proxy Request Tracer * * Creates and manages OTel spans for the proxy request lifecycle. * Provides a clean API for claudeProxyRoutes to trace each phase: * receive -> account_selection -> upstream (per retry) -> stream -> end * * Uses the existing instrumentation infrastructure: * - setLangfuseContext() for Langfuse enrichment * - OTel propagation API for context propagation to/from upstream * - SpanAttributes from spanTypes.ts for attribute naming * - calculateCost() from pricing.ts for cost tracking */ import { type Span } from "@opentelemetry/api"; import type { AccountSelectionContext, ProxyRequestContext, ProxyRequestOrigin, ResponseInfoContext, UpstreamAttemptContext, UsageContext, ProxyUsageAttributionOptions, RuntimeRequestMetadata, ProxyLogTraceContext } from "../types/index.js"; declare class ProxyTracer { private readonly rootSpan; private readonly traceContext; private readonly proxyTracer; private readonly requestId; /** * Model used for costing. Starts as the model the client asked for and is * updated by setModelSubstitution(), so cost follows the model that actually * served the request rather than the one that was requested. */ private model; /** * Provider used for costing. See ProxyRequestContext.provider. Mutable for * the same reason `model` is: a fallback can serve the request from a * different provider, and pricing has to follow it. */ private billingProvider; private readonly startTime; private readonly isStream; private ended; private recordRequestMetrics; private recordUsageMetrics; private accountEmail?; private usage?; private mode; /** Where the Claude-shaped request originated, for cache/cost attribution. */ private requestOrigin; private readonly requestedModel; private input?; private output?; private responseModel?; private finishReason?; private firstOutputAt?; private upstreamStartedAt?; private cost?; private statusMessage?; private constructor(); /** * Create a root span for a proxy request and set Langfuse context. * * If the incoming request carries a `traceparent` header, the root span * will be linked to the caller's trace via the OTel propagation API. */ static startRequest(ctx: ProxyRequestContext, incomingHeaders?: Record<string, string>): ProxyTracer; /** Span covering the initial request receive and parse phase. */ startReceive(): Span; /** Span covering account selection logic (fill-first / round-robin). */ startAccountSelection(): Span; /** Span covering a single upstream attempt. One per retry. */ startUpstreamAttempt(ctx: UpstreamAttemptContext): Span; /** Span covering the SSE stream relay phase. */ startStream(): Span; /** Record account selection outcome on the root span. */ setAccountSelection(ctx: AccountSelectionContext): void; /** Attribute fallback metrics to the account that actually served them. */ setServedAccount(account: string, accountType: string): void; /** * Record where the Claude-shaped request this span covers originated, so * the token/cost counters below can be broken down by origin (e.g. to * measure Codex-fallback cache-hit rate separately from native traffic). */ setRequestOrigin(origin: ProxyRequestOrigin): void; /** Record token usage and cost on the root span. */ setUsage(ctx: UsageContext, options?: ProxyUsageAttributionOptions): void; /** * Record response-side details parsed from the upstream reply: the model * that actually answered, the finish reason, and which tools the model * invoked (tool_use blocks). Uses gen_ai.* semantic-convention attributes. */ setResponseInfo(ctx: ResponseInfoContext): void; /** Record an error on the root span. */ setError(errorType: string, errorMessage: string): void; /** Record whether the request was handled in full or passthrough mode. */ setMode(mode: "full" | "passthrough" | "passthrough-cli"): void; /** * Record that the proxy substituted a different model than was requested. * Sets span attributes and increments the substitution metric counter. */ setModelSubstitution(requestedModel: string, actualModel: string, actualProvider?: string): void; setFallbackInfo(info: { triggered: boolean; provider?: string; model?: string; attemptCount: number; reason: string; }): void; /** Log the incoming client request body (redacted). */ logRequestBody(body: string): void; /** Log the incoming client request headers (redacted). */ logRequestHeaders(headers: Record<string, string>): void; /** Log the upstream request body (redacted, as sent to Anthropic). */ logUpstreamRequestBody(body: string): void; /** Log the upstream request headers (redacted). */ logUpstreamRequestHeaders(headers: Record<string, string>): void; /** Log the upstream response headers (redacted). */ logUpstreamResponseHeaders(headers: Record<string, string>): void; /** Log the upstream response body (redacted). */ logUpstreamResponseBody(body: string): void; /** Log SSE stream events (each event has type, timestamp, data). */ logStreamEvents(events: Array<{ type: string; timestamp: number; data: string; }>): void; /** Record an upstream retry attempt. */ recordRetry(account: string, reason: string): void; /** Record request and/or response body sizes for bandwidth tracking. */ recordBodySizes(requestBytes?: number, responseBytes?: number): void; /** Return the OTel trace/span IDs for this request (for log correlation). */ getTraceContext(): ProxyLogTraceContext; /** Return the captured usage (set by setUsage). */ getUsage(): UsageContext | undefined; /** End the root span with final HTTP status and duration, and emit OTEL metrics. */ end(responseStatus: number, durationMs: number): void; private levelFor; private observeOutcome; /** * Get trace context headers for propagation to the upstream Anthropic request. * Injects the current trace's `traceparent` / `tracestate` into a new header map. */ getTraceHeaders(): Record<string, string>; } export declare function recordFallbackAttempt(attrs: { provider: string; model: string; status: "success" | "failure"; errorMessage?: string; durationMs: number; /** Namespaces the leg-health key for a fallback direction other than the * existing Claude-engine chain (e.g. `"codex-outbound"`), so a dead-leg * alert for `anthropic/claude-...` reached via Codex-outbound fallback * never merges its failure streak with the same provider/model reached * through the Claude engine's own configured chain. Omitted entirely by * every existing call site, which keeps their leg key exactly as before. */ direction?: string; }): void; /** * Records one Codex-outbound tool-declaration schema degradation (a * `$ref`/`$defs` flatten that hit a circular reference, a dropped `strict` * flag, or a custom grammar tool wrapped into a one-string schema). `reason` * is a short, stable label — never the schema or tool-name text itself, so a * call site cannot accidentally leak request payload into a metric label. */ export declare function recordCodexOutboundSchemaDegraded(attrs: { toolName: string; reason: string; }): void; /** * Records one Codex-outbound request field dropped during translation * because this repo has no faithful Claude-request representation for it * (e.g. a malformed `function_call.arguments` string wrapped instead of * parsed). Mirrors `recordCodexOutboundSchemaDegraded`'s label discipline. */ export declare function recordCodexOutboundUnsupportedField(attrs: { field: string; reason: string; }): void; /** Reset leg-health state between tests. */ export declare const __fallbackLegHealthTestHooks: { threshold: number; maxTrackedLegs: number; trackedCount: () => number; reset: () => void; streakOf: (legKey: string) => number; isReported: (legKey: string) => boolean; }; export { ProxyTracer }; /** Standard SERVER span covers every proxy door, including parsing and admission failures. */ export declare function startProxyHttpTrace(metadata: RuntimeRequestMetadata, headers: Record<string, string>): { run: <T>(fn: () => T) => T; end: (status: number, outcome: string, errorType?: string) => void; };