type OtlpAnyValue = { stringValue?: string; intValue?: string; doubleValue?: number; boolValue?: boolean; arrayValue?: { values: OtlpAnyValue[]; }; }; type OtlpKeyValue = { key: string; value: OtlpAnyValue; }; declare const SpanStatusCode: { readonly UNSET: 0; readonly OK: 1; readonly ERROR: 2; }; type OtlpSpanStatus = { code: (typeof SpanStatusCode)[keyof typeof SpanStatusCode] | number; message?: string; }; type OtlpSpan = { traceId: string; spanId: string; parentSpanId?: string; name: string; startTimeUnixNano: string; endTimeUnixNano: string; attributes?: OtlpKeyValue[]; status?: OtlpSpanStatus; }; type SpanIds = { traceIdB64: string; spanIdB64: string; parentSpanIdB64?: string; }; type Attachment = { type: string; role: string; name?: string; value: string; }; type IdentifyInput = { userId: string; traits?: Record; }; type Patch = { eventName?: string; userId?: string; convoId?: string; input?: string; output?: string; model?: string; properties?: Record; attachments?: Attachment[]; isPending?: boolean; timestamp?: string; }; type SignalInput = { eventId: string; name: string; type?: "default" | "feedback" | "edit" | "standard" | "agent" | "agent_internal"; sentiment?: "POSITIVE" | "NEGATIVE"; timestamp?: string; properties?: Record; attachmentId?: string; comment?: string; after?: string; }; type EventShipperOptions = { writeKey?: string; endpoint?: string; enabled?: boolean; debug: boolean; partialFlushMs?: number; sdkName?: string; libraryName?: string; libraryVersion?: string; defaultEventName?: string; /** * Explicit Workshop / local debugger URL. Wins over env vars + auto-detect. * Pass `null` to opt out of all mirroring (including auto-detect). */ localDebuggerUrl?: string | null; /** * Optional project slug. When set, every outbound cloud request includes an * `X-Raindrop-Project-Id: ` header. Empty / whitespace-only * values are ignored. Slug format is validated on construction but never * throws — the backend returns 400 on invalid values. */ projectId?: string; /** * Per-field character cap applied to event input/output BEFORE buffering * or serialization, so oversized payloads cost the cap — not the payload — * on the calling code path. Truncated fields end with * `...[truncated by raindrop]` and never exceed the cap, marker included. * Defaults to 1,000,000 (matching the Python SDK). */ maxTextFieldChars?: number; }; declare class EventShipper$1 { private baseUrl; private writeKey?; private enabled; private debug; private partialFlushMs; private sdkName; private prefix; private defaultEventName; private projectId; private context; private buffers; private sticky; private timers; private inFlight; private maxTextFieldCharsOpt; /** * Epoch ms deadline while `shutdown()` is draining; undefined otherwise. * Checked before every POST issued during the final flush. */ private shutdownDeadlineAt; /** * Set once `shutdown()` begins and never cleared. Sends issued after the * drain window (stragglers, or flush work the deadline abandoned * mid-drain) run as a single short attempt instead of regaining the full * retry schedule. */ private hasShutdown; /** URL of the local debugger / Workshop daemon, when one is reachable. */ private localDebuggerUrl; constructor(opts: EventShipperOptions); isDebugEnabled(): boolean; private authHeaders; private requestHeaders; /** * Build the retry/timeout options for one POST, honoring the shutdown * deadline. Returns `null` when the shutdown drain window is exhausted — * the caller must drop the payload (with a rate-limited warning) instead * of issuing a request that could outlive process exit. * * Checked fresh on EVERY send, so a shutdown that begins while the flush * path is mid-drain takes effect immediately: no further retries, and the * per-attempt timeout is clamped to the remaining window. After * `shutdown()` returns (deadline cleared, `hasShutdown` still set), * sends — late callers, or flush work the deadline abandoned mid-drain — * run as a single short attempt rather than regaining the full retry * schedule. */ private requestOpts; patch(eventId: string, patch: Patch): Promise; finish(eventId: string, patch: { output?: string; model?: string; properties?: Record; userId?: string; }): Promise; flush(): Promise; shutdown(): Promise; trackSignal(signal: SignalInput): Promise; identify(users: IdentifyInput | IdentifyInput[]): Promise; private warnShutdownDrop; private flushOne; } /** * Hook fired per OTLP span right before the span is shipped (to the Raindrop * API and to a local debugger). Lets callers inspect, rewrite, or drop the * entire span — not just individual attributes — which is more flexible than * an attribute-level hook (you can rename attributes, add new ones, drop the * span outright, etc.). * * Return values: * - `undefined` or the same span: ship the span unchanged. * - a new `OtlpSpan`: ship the returned span in place of the original. * - `null`: drop the span entirely from every ship path. * * The hook runs on the hot path — keep it synchronous and side-effect-free. * If the hook throws, the span is dropped (fail-closed) so a buggy hook can * never accidentally ship raw, un-redacted spans. */ type TransformSpanHook = (span: OtlpSpan) => OtlpSpan | null | undefined; type InternalSpan = { ids: SpanIds; name: string; startTimeUnixNano: string; endTimeUnixNano?: string; attributes: Array; }; type TraceShipperOptions = { writeKey?: string; endpoint?: string; enabled?: boolean; debug: boolean; debugSpans?: boolean; flushIntervalMs?: number; maxBatchSize?: number; maxQueueSize?: number; sdkName?: string; serviceName?: string; serviceVersion?: string; /** * Explicit Workshop / local debugger URL. Wins over env vars + auto-detect. * Pass `null` to opt out of all mirroring (including auto-detect). */ localDebuggerUrl?: string | null; /** * Optional project slug. When set, every OTLP trace export includes an * `X-Raindrop-Project-Id: ` header. Empty / whitespace-only * values are ignored. Slug format is validated on construction but never * throws — the backend returns 400 on invalid values. */ projectId?: string; /** * Per-span hook that fires for every OTLP span right before the span is * shipped (both to the Raindrop API and to a local debugger). Lets callers * inspect, rewrite, or drop entire spans — rename attributes, add new ones, * scrub additional secret-shaped values inside `ai.prompt.messages` / * `ai.toolCall.args`, etc. * * Return values: * - `undefined` or the same span reference: ship the span unchanged. * - a new `OtlpSpan`: ship the returned span in place of the original. * - `null`: drop the span entirely from every ship path. * * The hook runs BEFORE the default redactor (which is the always-on floor * for documented BYOK secrets). The default redactor still runs on the * post-transform span unless `disableDefaultRedaction` is set, so even if * a custom transform overlooks a secret-shaped attribute, the floor catches * it. * * The hook runs on the hot path — keep it synchronous and side-effect-free. * If the hook itself throws, the span is dropped (fail-closed) so a buggy * hook can never accidentally ship raw, un-redacted spans. */ transformSpan?: TransformSpanHook; /** * Disable the built-in default span transformer (which scrubs documented * secret-shaped properties — `apiKey`, `secretAccessKey`, `privateKey`, * etc. — inside `ai.request.providerOptions` and * `ai.response.providerMetadata`). * * Default: `false` (i.e. default redaction is on). Setting this to `true` * disables the floor entirely; provide a custom `transformSpan` if you * still want some redaction in that case. */ disableDefaultRedaction?: boolean; /** * Per-attribute character cap applied to every span attribute string value * right before the span enters a ship path, so a multi-MB prompt/tool * payload can never make the batch `JSON.stringify` (which runs on the * event loop) cost seconds. Truncated values end with * `...[truncated by raindrop]` and never exceed the cap, marker included. * Defaults to 1,000,000 (matching the Python SDK). */ maxTextFieldChars?: number; }; declare class TraceShipper$1 { private baseUrl; private writeKey?; private enabled; private debug; private debugSpans; private sdkName; private prefix; private serviceName; private serviceVersion; private flushIntervalMs; private maxBatchSize; private maxQueueSize; private projectId; private queue; private timer; private inFlight; /** URL of the local debugger / Workshop daemon, when one is reachable. */ private localDebuggerUrl; private transformSpanHook; private disableDefaultRedaction; private maxTextFieldCharsOpt; /** * Epoch ms deadline while `shutdown()` is draining; undefined otherwise. * Checked before every batch POST issued during the final flush. */ private shutdownDeadlineAt; /** * Set once `shutdown()` begins and never cleared. Sends issued after the * drain window (stragglers, or flush work the deadline abandoned * mid-drain) run as a single short attempt instead of regaining the full * retry schedule. */ private hasShutdown; constructor(opts: TraceShipperOptions); /** * Cap every string attribute value on the span. O(#attributes) length * checks; only oversized values pay a slice. Runs AFTER the redaction * pipeline so the default secret-scrub still sees parseable JSON in * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping * first could cut a JSON blob mid-way, fail the parse, and ship secrets * in the surviving prefix). * * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored * for span content, matching the Python SDK and the OTel SDK convention. */ private capSpanAttributes; /** * Apply the user `transformSpan` hook (if any) followed by the default * redactor (unless disabled). Returns either the (possibly new) span to * ship, or `null` to drop the span entirely. * * Ordering: user hook runs first so callers can rewrite the span freely * (rename attrs, add new ones, scrub things the default doesn't know * about). The default redactor then runs on whatever the user produced, * acting as the always-on floor for documented BYOK secrets. If the user * sets `disableDefaultRedaction: true`, the floor is skipped. * * Fail-closed: if the user hook throws, the span is dropped — a buggy * hook can never accidentally ship raw, un-redacted spans. */ private redactSpan; isDebugEnabled(): boolean; private authHeaders; private requestHeaders; startSpan(args: { name: string; parent?: { traceIdB64: string; spanIdB64: string; }; eventId: string; operationId?: string; attributes?: Array; startTimeUnixNano?: string; }): InternalSpan; private mirrorToLocalDebugger; endSpan(span: InternalSpan, extra?: { attributes?: InternalSpan["attributes"]; error?: unknown; status?: OtlpSpanStatus; endTimeUnixNano?: string; }): void; createSpan(args: { name: string; parent?: { traceIdB64: string; spanIdB64: string; }; eventId: string; startTimeUnixNano: string; endTimeUnixNano: string; attributes?: Array; status?: OtlpSpanStatus; }): void; enqueue(span: OtlpSpan): void; flush(): Promise; /** See EventShipper.requestOpts — same shutdown-budget semantics. */ private requestOpts; shutdown(): Promise; } /** * Run telemetry egress with OpenTelemetry tracing suppressed. * * Why this exists * --------------- * Raindrop integrations ship spans/events over HTTP with the global `fetch` * (see {@link ../http.ts `postJson`}). When the host app also runs an OTel * fetch/undici instrumentation — e.g. `@vercel/otel`'s `registerOTel`, which * every Eve agent installs — that instrumentation wraps *our own* telemetry * POSTs in a `fetch POST ` span. Those spans are then handed to the * very exporter that issued the request, so they get shipped right back to * Raindrop and Workshop as standalone "runs" (and, because each export issues * another fetch, they feed back on themselves). The result is a run list * flooded with `fetch POST .../v1/traces`, `.../events/track_partial` and * `.../live` entries that drown out the real agent turns — especially with * sub-agents, where every sandbox runs its own instrumentation. * * The OTel-blessed fix is to mark the active context as "tracing suppressed" * around the request; instrumentations check `isTracingSuppressed` and return * a no-op span instead of recording one. We do this through a hook stashed on * `globalThis` by the Node entrypoint ({@link ../index.node.ts}) so that: * - `@opentelemetry/api` / `@opentelemetry/core` stay *optional* — core never * hard-depends on them, and the hook is simply absent when they (and thus * any instrumentation to suppress) are not installed; and * - the browser bundle never pulls in `node:module`, mirroring how core * injects `AsyncLocalStorage` via `RAINDROP_ASYNC_LOCAL_STORAGE`. * * When no hook is present the callback runs unchanged, so suppression is a * best-effort no-op rather than a hard requirement. */ /** Hook signature: run `fn` with OTel tracing suppressed, returning its value. */ type SuppressTracingHook = (fn: () => T) => T; declare global { var RAINDROP_SUPPRESS_TRACING: SuppressTracingHook | undefined; } type ParentSpanContext = { traceIdB64: string; spanIdB64: string; eventId: string; }; interface ContextSpan { readonly traceIdB64: string; readonly spanIdB64: string; readonly eventId: string; log?(data: Record): void; } interface AsyncLocalStorageLike { getStore(): T | undefined; run(store: T, callback: () => R): R; enterWith?(store: T): void; } declare abstract class ContextManager { abstract getParentSpanIds(): ParentSpanContext | undefined; abstract runInContext(span: ContextSpan, callback: () => R): R; abstract getCurrentSpan(): ContextSpan | undefined; abstract isReady(): boolean; } declare global { var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined; var RAINDROP_ASYNC_LOCAL_STORAGE: (new () => AsyncLocalStorageLike) | undefined; } declare class EventShipper extends EventShipper$1 { constructor(opts: ConstructorParameters[0]); } declare class TraceShipper extends TraceShipper$1 { constructor(opts: ConstructorParameters[0]); enqueue(span: OtlpSpan): void; } interface SelfDiagnosticsSignalDef { description: string; sentiment?: "POSITIVE" | "NEGATIVE"; } interface SelfDiagnosticsConfig { signals?: Record; guidance?: string; toolName?: string; } interface ConfigFile { write_key?: string; api_url?: string; project_id?: string; user_id?: string; debug?: boolean; enabled?: boolean; event_name?: string; custom_properties?: Record; self_diagnostics?: SelfDiagnosticsConfig; } interface RaindropConfig { writeKey: string; endpoint: string; /** * Optional Raindrop project slug. When set, every outbound cloud request * carries an `X-Raindrop-Project-Id` header. Unset → no header (the project * resolves to `default` server-side; byte-identical to prior behavior). * Sourced from `RAINDROP_PROJECT_ID` or the `project_id` config-file key. */ projectId?: string; userId: string; convoId?: string; debug: boolean; enabled: boolean; eventName: string; customProperties: Record; selfDiagnostics?: SelfDiagnosticsConfig; } /** * Load config with precedence (low -> high): * 1. Defaults * 2. ~/.config/raindrop/config.json * 3. Environment variables */ declare function loadConfig(): RaindropConfig; declare function getConfigPath(): string; declare function updateConfig(patch: Partial): void; type SetupScope = "user" | "project"; interface HookPayload { session_id: string; hook_event_name: string; cwd: string; permission_mode: string; transcript_path?: string; source?: string; model?: string; prompt?: string; tool_name?: string; tool_input?: Record; tool_response?: unknown; tool_use_id?: string; error?: string; is_interrupt?: boolean; stop_hook_active?: boolean; last_assistant_message?: string; error_details?: string; reason?: string; agent_id?: string; agent_type?: string; agent_transcript_path?: string; trigger?: string; compact_summary?: string; file_path?: string; memory_type?: string; load_reason?: string; } interface MapperConfig { userId: string; convoId?: string; debug: boolean; eventName: string; customProperties: Record; } declare function mapHookToRaindrop(payload: HookPayload, config: MapperConfig, eventShipper: EventShipper, traceShipper: TraceShipper): Promise; /** * Parse command-line args for --append-system-prompt and * --append-system-prompt-file flags. Handles both space-separated form * (--flag value) and equals form (--flag=value). Supports both flags * appearing together (they concatenate). * * Exported for testing. */ declare function extractAppendSystemPrompt(args: string[]): string | undefined; declare const PACKAGE_NAME = "@raindrop-ai/claude-code"; declare const PACKAGE_VERSION = "0.0.13"; /** A tool call extracted from an assistant message content block. */ interface LLMToolCall { id?: string; name: string; input?: Record; } /** * One LLM-call phase within a turn. * A phase = one or more contiguous assistant messages before the next * tool-result user entry resets the context. */ interface LLMCallPhase { /** Concatenated assistant text blocks in this phase */ text: string; /** Tool calls the model decided to make in this phase */ toolCalls: LLMToolCall[]; /** Model that produced this phase */ model?: string; /** Token usage accumulated across assistant messages in this phase */ inputTokens: number; outputTokens: number; cacheReadTokens: number; /** Whether a thinking block appeared in this phase */ hasThinking: boolean; /** ISO timestamp of the first assistant message in the phase */ startTimestamp?: string; /** ISO timestamp of the last assistant message in the phase */ endTimestamp?: string; } interface TranscriptSummary { /** Aggregated token usage across all turns */ totalInputTokens: number; totalOutputTokens: number; totalCacheReadTokens: number; totalCacheCreationTokens: number; /** Token usage for the most recent turn only */ lastTurnInputTokens?: number; lastTurnOutputTokens?: number; lastTurnCacheReadTokens?: number; /** Model used (from most recent assistant message) */ model?: string; /** Service tier */ serviceTier?: string; /** Number of API turns (assistant messages) */ turnCount: number; /** Unique tool names used in the session */ toolsUsed: string[]; /** Stop reason from last assistant message */ stopReason?: string; /** Total turn duration in ms (from system entries) */ totalDurationMs?: number; /** Claude Code version */ codeVersion?: string; /** Git branch */ gitBranch?: string; /** Whether thinking/reasoning content was used */ hasThinking: boolean; /** Ordered assistant text blocks for the latest top-level user turn */ lastTurnTextBlocks: string[]; /** Combined assistant text for the latest top-level user turn */ lastTurnFullOutput?: string; /** Ordered LLM-call phases for the latest top-level user turn */ llmCallPhases: LLMCallPhase[]; } /** * Parse a Claude Code transcript JSONL file and extract a summary. * Returns undefined if the file doesn't exist or can't be parsed. */ declare function parseTranscript(transcriptPath: string): TranscriptSummary | undefined; /** * Convert a TranscriptSummary into a flat properties object * suitable for merging into event properties. */ declare function transcriptToProperties(summary: TranscriptSummary): Record; interface LocalDebuggerResult { /** The resolved base URL (e.g. "http://localhost:5899/v1/"), or null if not detected. */ url: string | null; /** Whether the debugger was auto-detected (vs explicitly configured via env var). */ autoDetected: boolean; } /** * Detect whether the local debugger is available. * * Resolution order: * 1. RAINDROP_LOCAL_DEBUGGER env var — use directly (no health check, trust the user) * 2. Cached probe result (within TTL) * 3. Probe http://localhost:5899/health with a short timeout */ declare function detectLocalDebugger(debug: boolean): Promise; /** * Mirror an event payload to the local debugger's track_partial endpoint. * Fire-and-forget — errors are silently swallowed. */ declare function mirrorEventToLocalDebugger(baseUrl: string, payload: Record, debug: boolean): void; interface ResolvedSignal { description: string; sentiment?: "POSITIVE" | "NEGATIVE"; } /** * Normalize and validate user-provided signal definitions. * Returns the default set if input is empty or all entries are invalid. * Always appends `noteworthy` as the last category. */ declare function normalizeSignals(custom?: Record): Record; /** * Resolve the full MCP tool configuration from optional user config. */ declare function resolveToolConfig(diagConfig?: SelfDiagnosticsConfig): { signals: Record; categoryKeys: string[]; toolName: string; toolDescription: string; }; /** Default category keys (before custom signal config is applied). */ declare const DEFAULT_CATEGORY_KEYS: string[]; /** * Find the most recently modified event_* file in the state dir. * Returns the eventId stored in that file, or undefined. */ declare function resolveCurrentEventId(): string | undefined; declare function executeTool(args: Record): Promise<{ content: Array<{ type: string; text: string; }>; isError?: boolean; }>; declare const TOOL_SCHEMA: { name: string; description: string; inputSchema: { type: "object"; properties: { category: { type: "string"; enum: string[]; description: string; }; detail: { type: "string"; description: string; }; }; required: string[]; }; }; declare function startMcpServer(): Promise; export { DEFAULT_CATEGORY_KEYS, EventShipper, type HookPayload, type LLMCallPhase, type LLMToolCall, type LocalDebuggerResult, type MapperConfig, PACKAGE_NAME, PACKAGE_VERSION, type RaindropConfig, type SelfDiagnosticsConfig, type SelfDiagnosticsSignalDef, type SetupScope, TOOL_SCHEMA, TraceShipper, type TranscriptSummary, detectLocalDebugger, executeTool, extractAppendSystemPrompt, getConfigPath, loadConfig, mapHookToRaindrop, mirrorEventToLocalDebugger, normalizeSignals, parseTranscript, resolveCurrentEventId, resolveToolConfig, startMcpServer, transcriptToProperties, updateConfig };