import type { SpanEmitter } from './emitter.js'; import type { VerlonResponse, ChatRequest } from '../types/index.js'; /** The slice of the Verlon client the tracing handles need. */ export interface TracingClient { getEmitter(): SpanEmitter; getBaseUrl(): string; getJson(path: string): Promise; isStrictTasks(): boolean; chatWithHeaders(request: Record, headers: Record): Promise; chatStreamWithHeaders(request: Record, headers: Record): AsyncGenerator; } export interface TraceLink { /** * Serialized W3C traceparent of the CAUSING trace — take it from that * trace's handle via `t.traceparent()`. */ traceparent: string; /** Edge kind; defaults to 'triggered_by'. */ kind?: 'triggered_by' | (string & {}); } export interface TraceOptions { /** * The id of the CONVERSATION this run belongs to — the id your app * already has for the whole exchange (chat id, thread id, ticket * number). Create it once per conversation and reuse it on every * turn; the dashboard then groups each turn's trace into one session. * * Invariant: two traces share a conversationId exactly when you'd say * they belong to the same conversation. Never generate a fresh id per * message — every session would hold exactly one trace and grouping * silently never happens. Omit it entirely for one-shot runs. */ conversationId?: string; /** Continue a trace started in another process. */ traceparent?: string; /** * References to the run(s) that CAUSED this one — for triggered side * work (title generation, indexing) that runs as its own trace beside * the run, not inside it. A link records causation only: the linked * trace is not this trace's parent, and durations, costs, and * coverage never aggregate across links. To JOIN a trace instead, * use `traceparent`. */ links?: TraceLink[]; } export interface TraceHandle { readonly traceId: string; /** Export point for crossing a process boundary explicitly. */ traceparent(): string; readonly conversationId: string | null; } export interface ProviderClientOptions { baseURL: string; fetch: any; defaultHeaders: Record; } export interface TaskChatRequest { data: ChatRequest; /** * The pass-through model for this call. A model must resolve from * somewhere by call time; the chain is: task's dashboard pin or * delegation → this value → the gate's own model. Omit it when the * dashboard pin should decide; hardcode it only when the code should * own the choice. */ model?: string; metadata?: Record; endUserId?: string; } export declare class TaskHandle { private readonly client; readonly gateId: string; readonly name: string; constructor(client: TracingClient, gateId: string, name: string); /** * Static identity headers for native-SDK clients (`defaultHeaders`). * Deliberately excludes traceparent/session — those are per-call and * injected dynamically by `instrumentFetch()`, so a client * constructed once never pins a stale trace. */ headers(): Record; /** * Everything a native provider SDK needs to route this call site * through Verlon: * * new OpenAI({ apiKey, ...classify.clientOptions('openai') }) * new Anthropic({ apiKey, ...classify.clientOptions('anthropic') }) * * The provider hint sets the right base path: the OpenAI SDK expects * `/v1` inside baseURL (it appends `/chat/completions`), while the * Anthropic SDK appends the full `/v1/messages` itself. */ clientOptions(provider?: 'openai' | 'anthropic'): ProviderClientOptions; /** * @deprecated Removed in 3.0.0, scheduled December 1, 2026. Make the * call with the official provider SDK instead — `clientOptions()` * carries the same gate/task attribution, trace correlation, and * dashboard model-pin behavior: * * const openai = new OpenAI({ apiKey, ...task.clientOptions('openai') }); * await openai.chat.completions.create({ model, messages }); */ chat(request: TaskChatRequest): Promise; /** * @deprecated Removed in 3.0.0, scheduled December 1, 2026. Stream with * the official provider SDK (`stream: true`) via `clientOptions()` — * see the chat() deprecation note. */ chatStream(request: TaskChatRequest): AsyncGenerator; } export declare class AgentHandle { private readonly client; readonly gateId: string; private readonly displayName?; private declaredTasks; private validation; constructor(client: TracingClient, gateId: string, displayName?: string | undefined); /** * Boot-time handshake with the dashboard: warns when a task declared * in code isn't known server-side while other declared tasks exist — * the typo'd-name failure mode, caught at startup instead of showing * up as mystery unconfigured traffic. Never blocks or fails a run * (except in `strictTasks` mode, where a mismatch throws). */ validateTasks(): Promise; /** Provider-SDK options attributed to the agent without a task. */ clientOptions(provider?: 'openai' | 'anthropic'): ProviderClientOptions; task(name: string): TaskHandle; trace(fn: (t: TraceHandle) => Promise | T): Promise; trace(options: TraceOptions, fn: (t: TraceHandle) => Promise | T): Promise; } export interface ToolOptions { captureArgs?: boolean; captureResult?: boolean; redact?: (value: unknown) => unknown; } export declare function wrapTool unknown>(client: TracingClient, name: string, fn: F, options?: ToolOptions): F; /** * Snapshot the active trace scope as HTTP headers (`traceparent`, * `x-verlon-conversation-id`, `x-verlon-client-time`) for a gateway * call made with an HTTP client we can't wrap. Returns `{}` outside a * trace scope. * * Prefer `instrumentFetch()` / `clientOptions()` where the client * accepts a custom fetch — they capture the scope per call, so a * long-lived client never pins a stale trace. Use this only at the * call site itself, in the same tick as the request: * * await axios.post(url, body, { headers: ambientHeaders() }); */ export declare function ambientHeaders(): Record; export declare function instrumentFetch(base?: typeof fetch): typeof fetch; //# sourceMappingURL=handles.d.ts.map