@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
TypeScript
/**
* 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;
};