@raindrop-ai/ai-sdk 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,6 +79,11 @@ type EventShipperOptions = {
79
79
  libraryName?: string;
80
80
  libraryVersion?: string;
81
81
  defaultEventName?: string;
82
+ /**
83
+ * Explicit Workshop / local debugger URL. Wins over env vars + auto-detect.
84
+ * Pass `null` to opt out of all mirroring (including auto-detect).
85
+ */
86
+ localDebuggerUrl?: string | null;
82
87
  };
83
88
  declare class EventShipper$1 {
84
89
  private baseUrl;
@@ -94,6 +99,8 @@ declare class EventShipper$1 {
94
99
  private sticky;
95
100
  private timers;
96
101
  private inFlight;
102
+ /** URL of the local debugger / Workshop daemon, when one is reachable. */
103
+ private localDebuggerUrl;
97
104
  constructor(opts: EventShipperOptions);
98
105
  isDebugEnabled(): boolean;
99
106
  private authHeaders;
@@ -111,6 +118,82 @@ declare class EventShipper$1 {
111
118
  private flushOne;
112
119
  }
113
120
 
121
+ /**
122
+ * Built-in default list of property names considered secret. Matching is
123
+ * case-insensitive and normalizes camelCase / snake_case / kebab-case (so
124
+ * `apiKey`, `api_key`, and `API-KEY` all match). When `defaultRedactAttribute`
125
+ * (and the recursive `redactSecretsInObject` helper) walk a value, any object
126
+ * key whose normalized form is in this set has its value replaced with the
127
+ * redaction placeholder.
128
+ *
129
+ * Names were chosen to cover the documented Vercel AI Gateway BYOK credential
130
+ * shapes (OpenAI / Anthropic `apiKey`, Bedrock `secretAccessKey` +
131
+ * `sessionToken`, Vertex `privateKey` + `privateKeyId`, etc.) plus generic
132
+ * secret-shaped properties common across other providers. Keys that are not
133
+ * by themselves secret (e.g. AWS `accessKeyId` — analogous to a username, not
134
+ * usable without `secretAccessKey`) are intentionally NOT in this set.
135
+ */
136
+ declare const DEFAULT_SECRET_KEY_NAMES: readonly string[];
137
+ declare const REDACTED_PLACEHOLDER = "[REDACTED]";
138
+ /**
139
+ * Recursively walk `value` and replace any property whose (normalized) name
140
+ * matches one of `secretKeyNames` with `placeholder`. Returns a new object/
141
+ * array; does not mutate the input. Non-object inputs are returned untouched.
142
+ *
143
+ * Cycle-safe via a WeakSet — circular references resolve to `"[CIRCULAR]"`.
144
+ */
145
+ declare function redactSecretsInObject(value: unknown, options?: {
146
+ secretKeyNames?: ReadonlyArray<string>;
147
+ placeholder?: string;
148
+ }): unknown;
149
+ /**
150
+ * Hook fired per OTLP span right before the span is shipped (to the Raindrop
151
+ * API and to a local debugger). Lets callers inspect, rewrite, or drop the
152
+ * entire span — not just individual attributes — which is more flexible than
153
+ * an attribute-level hook (you can rename attributes, add new ones, drop the
154
+ * span outright, etc.).
155
+ *
156
+ * Return values:
157
+ * - `undefined` or the same span: ship the span unchanged.
158
+ * - a new `OtlpSpan`: ship the returned span in place of the original.
159
+ * - `null`: drop the span entirely from every ship path.
160
+ *
161
+ * The hook runs on the hot path — keep it synchronous and side-effect-free.
162
+ * If the hook throws, the span is dropped (fail-closed) so a buggy hook can
163
+ * never accidentally ship raw, un-redacted spans.
164
+ */
165
+ type TransformSpanHook = (span: OtlpSpan) => OtlpSpan | null | undefined;
166
+ /**
167
+ * The built-in OTLP span attributes that carry JSON-serialized user objects
168
+ * the SDK has no schema for (provider options, provider metadata). The
169
+ * default span transformer (`defaultTransformSpan`) parses, recursively
170
+ * scrubs secret-shaped properties, and re-serializes the value of any
171
+ * attribute whose key is in this set. Other attributes — including
172
+ * `ai.prompt.messages` and `ai.toolCall.args` — are passed through unchanged
173
+ * because they may legitimately contain content the caller wants logged;
174
+ * callers who need to scrub those should provide a custom `transformSpan`.
175
+ */
176
+ declare const DEFAULT_REDACT_ATTRIBUTE_KEYS: readonly string[];
177
+ /**
178
+ * Default span transformer. Walks the span's attributes; for every attribute
179
+ * whose key is in `DEFAULT_REDACT_ATTRIBUTE_KEYS`, parses the `stringValue`
180
+ * as JSON, recursively scrubs secret-shaped property names
181
+ * (`DEFAULT_SECRET_KEY_NAMES`), and re-serializes. Returns the same span
182
+ * reference when nothing changed (cheap no-op path); otherwise returns a new
183
+ * `OtlpSpan` with updated `attributes`.
184
+ */
185
+ declare function defaultTransformSpan(span: OtlpSpan): OtlpSpan;
186
+ /**
187
+ * If `key` is one of the JSON-blob attributes we redact by default and
188
+ * `value.stringValue` parses cleanly, return a new `OtlpAnyValue` carrying
189
+ * the scrubbed JSON. Otherwise return `undefined` (signal: no change).
190
+ *
191
+ * Exported because a caller's custom `transformSpan` may want to apply the
192
+ * same per-attribute redaction logic to additional attributes (e.g. their
193
+ * own provider-specific blob).
194
+ */
195
+ declare function redactJsonAttributeValue(key: string, value: OtlpAnyValue): OtlpAnyValue | undefined;
196
+
114
197
  type InternalSpan = {
115
198
  ids: SpanIds;
116
199
  name: string;
@@ -135,6 +218,40 @@ type TraceShipperOptions = {
135
218
  * Pass `null` to opt out of all mirroring (including auto-detect).
136
219
  */
137
220
  localDebuggerUrl?: string | null;
221
+ /**
222
+ * Per-span hook that fires for every OTLP span right before the span is
223
+ * shipped (both to the Raindrop API and to a local debugger). Lets callers
224
+ * inspect, rewrite, or drop entire spans — rename attributes, add new ones,
225
+ * scrub additional secret-shaped values inside `ai.prompt.messages` /
226
+ * `ai.toolCall.args`, etc.
227
+ *
228
+ * Return values:
229
+ * - `undefined` or the same span reference: ship the span unchanged.
230
+ * - a new `OtlpSpan`: ship the returned span in place of the original.
231
+ * - `null`: drop the span entirely from every ship path.
232
+ *
233
+ * The hook runs BEFORE the default redactor (which is the always-on floor
234
+ * for documented BYOK secrets). The default redactor still runs on the
235
+ * post-transform span unless `disableDefaultRedaction` is set, so even if
236
+ * a custom transform overlooks a secret-shaped attribute, the floor catches
237
+ * it.
238
+ *
239
+ * The hook runs on the hot path — keep it synchronous and side-effect-free.
240
+ * If the hook itself throws, the span is dropped (fail-closed) so a buggy
241
+ * hook can never accidentally ship raw, un-redacted spans.
242
+ */
243
+ transformSpan?: TransformSpanHook;
244
+ /**
245
+ * Disable the built-in default span transformer (which scrubs documented
246
+ * secret-shaped properties — `apiKey`, `secretAccessKey`, `privateKey`,
247
+ * etc. — inside `ai.request.providerOptions` and
248
+ * `ai.response.providerMetadata`).
249
+ *
250
+ * Default: `false` (i.e. default redaction is on). Setting this to `true`
251
+ * disables the floor entirely; provide a custom `transformSpan` if you
252
+ * still want some redaction in that case.
253
+ */
254
+ disableDefaultRedaction?: boolean;
138
255
  };
139
256
  declare class TraceShipper$1 {
140
257
  private baseUrl;
@@ -154,7 +271,24 @@ declare class TraceShipper$1 {
154
271
  private inFlight;
155
272
  /** URL of the local debugger / Workshop daemon, when one is reachable. */
156
273
  private localDebuggerUrl;
274
+ private transformSpanHook;
275
+ private disableDefaultRedaction;
157
276
  constructor(opts: TraceShipperOptions);
277
+ /**
278
+ * Apply the user `transformSpan` hook (if any) followed by the default
279
+ * redactor (unless disabled). Returns either the (possibly new) span to
280
+ * ship, or `null` to drop the span entirely.
281
+ *
282
+ * Ordering: user hook runs first so callers can rewrite the span freely
283
+ * (rename attrs, add new ones, scrub things the default doesn't know
284
+ * about). The default redactor then runs on whatever the user produced,
285
+ * acting as the always-on floor for documented BYOK secrets. If the user
286
+ * sets `disableDefaultRedaction: true`, the floor is skipped.
287
+ *
288
+ * Fail-closed: if the user hook throws, the span is dropped — a buggy
289
+ * hook can never accidentally ship raw, un-redacted spans.
290
+ */
291
+ private redactSpan;
158
292
  isDebugEnabled(): boolean;
159
293
  private authHeaders;
160
294
  startSpan(args: {
@@ -168,6 +302,7 @@ declare class TraceShipper$1 {
168
302
  attributes?: Array<OtlpKeyValue | undefined>;
169
303
  startTimeUnixNano?: string;
170
304
  }): InternalSpan;
305
+ private mirrorToLocalDebugger;
171
306
  endSpan(span: InternalSpan, extra?: {
172
307
  attributes?: InternalSpan["attributes"];
173
308
  error?: unknown;
@@ -256,6 +391,19 @@ type RaindropTelemetryIntegrationOptions = {
256
391
  eventShipper: EventShipper;
257
392
  sendTraces?: boolean;
258
393
  sendEvents?: boolean;
394
+ /**
395
+ * When `true` (default), generations started inside a tool's `execute`
396
+ * callback on the same async branch are wrapped in a synthetic `TOOL_CALL`
397
+ * + `agent.subagent` LLM marker so observability backends can render them
398
+ * as an agent block (and so the inner LLM's `track_partial` is suppressed).
399
+ *
400
+ * This is the right semantic for any framework that lowers sub-agents to
401
+ * AI SDK tools (Ash, Mastra, Claude Agent SDK, hand-rolled tool loops),
402
+ * not Ash-specific. Set to `false` if you have a tool whose `execute`
403
+ * happens to call `streamText`/`generateText` for unrelated reasons (e.g.
404
+ * RAG enrichment) and don't want it labelled as a sub-agent.
405
+ */
406
+ subagentWrapping?: boolean;
259
407
  debug?: boolean;
260
408
  context?: {
261
409
  userId?: string;
@@ -270,9 +418,26 @@ declare class RaindropTelemetryIntegration implements TelemetryIntegration {
270
418
  private readonly eventShipper;
271
419
  private readonly sendTraces;
272
420
  private readonly sendEvents;
421
+ private readonly subagentWrapping;
273
422
  private readonly debug;
274
423
  private readonly defaultContext;
275
424
  private readonly callStates;
425
+ /**
426
+ * Per-tool-call snapshot of the parent-tool ALS context taken right
427
+ * before `enterParentToolContext` overwrites it in `toolExecutionStart`.
428
+ * Kept at the integration level (rather than on `CallState`) so the
429
+ * snapshot survives even when the parent generation has no tracked
430
+ * `CallState` — e.g. the AI SDK dispatches a tool callback for a
431
+ * `callId` we never registered via `onStart`. Without this, the
432
+ * unconditional ALS enter in `toolExecutionStart` would leave
433
+ * `toolExecutionEnd` no way to restore the prior context, so it would
434
+ * clear and wipe whatever the outer scope had set.
435
+ *
436
+ * Keyed by `toolCallId` because that's the only identifier guaranteed
437
+ * to round-trip between `toolExecutionStart` and `toolExecutionEnd`
438
+ * (the `event.callId` can be the same for parallel sibling tools).
439
+ */
440
+ private readonly priorParentContexts;
276
441
  constructor(opts: RaindropTelemetryIntegrationOptions);
277
442
  private getState;
278
443
  private cleanup;
@@ -375,6 +540,92 @@ declare function readRaindropCallMetadataFromArgs(args: readonly unknown[]): Rai
375
540
 
376
541
  declare function _resetWarnedMissingUserId(): void;
377
542
 
543
+ /**
544
+ * Parent tool execution context propagation.
545
+ *
546
+ * Problem
547
+ * ───────
548
+ * When a tool's `execute` callback recursively calls `streamText` /
549
+ * `generateText` (which is exactly how Ash lowers sub-agents, but also a
550
+ * common pattern in hand-rolled agent loops, Mastra, Claude Agent SDK,
551
+ * etc.), the inner generation's telemetry events are dispatched with no
552
+ * connection back to the outer tool call. The AI SDK fires `onStart` for
553
+ * the inner generation with its own fresh `callId`; nothing on the event
554
+ * tells the integration "this generation was triggered from inside tool
555
+ * X's execute callback."
556
+ *
557
+ * Without that linkage, observability tools have to fall back to
558
+ * regex-matching the prompt to guess whether a call is a sub-agent
559
+ * dispatch — fragile, framework-specific, and easily spoofable by user
560
+ * input.
561
+ *
562
+ * Solution
563
+ * ────────
564
+ * We maintain an `AsyncLocalStorage` slot that holds the currently-active
565
+ * parent tool call. The integration's `onToolExecutionStart` enters the
566
+ * slot before the AI SDK awaits `tool.execute(...)`; the inner
567
+ * generation's `onStart` reads from the slot to recover parent linkage.
568
+ * `onToolExecutionEnd` clears the slot so subsequent code in the same
569
+ * async branch doesn't see stale state.
570
+ *
571
+ * Async-context scoping
572
+ * ─────────────────────
573
+ * `enterWith` modifies the *current* async context, which propagates
574
+ * downward into any `await` continuation and into child async resources
575
+ * (including the awaited `tool.execute` and anything it calls). Sibling
576
+ * branches are unaffected: when the AI SDK dispatches tool calls via
577
+ * `Promise.all(map(async (tc) => ...))`, each map callback runs in its
578
+ * own async branch, so `enterWith` in one branch is invisible to the
579
+ * others. This is exactly the behaviour we need for parallel sub-agent
580
+ * dispatch.
581
+ *
582
+ * On runtimes that don't expose `AsyncLocalStorage` (browser, Cloudflare
583
+ * Workers without `nodejs_compat`), we fall back to a synchronous stack
584
+ * via `runWithParentToolContext` — see the `SyncFallbackStorage` in
585
+ * `call-metadata.ts` for the established pattern. `enterWith` becomes a
586
+ * silent no-op on the sync fallback, so async tool execution on those
587
+ * runtimes won't have parent linkage and the inner generation will
588
+ * render as a top-level call instead of being grouped under its caller.
589
+ */
590
+ type ParentToolContext = {
591
+ /** The `callId` of the generation whose step kicked off this tool call. */
592
+ parentCallId: string;
593
+ /** The tool-call id from the parent generation's step. */
594
+ toolCallId: string;
595
+ /** The tool name the parent generation invoked. */
596
+ toolName: string;
597
+ };
598
+ /** Test helper — drop the storage so tests start from a fresh slot. */
599
+ declare function _resetParentToolContextStorage(): void;
600
+ /**
601
+ * Returns the active parent tool context, or `undefined` if no tool is
602
+ * currently executing in this async branch.
603
+ */
604
+ declare function getCurrentParentToolContext(): ParentToolContext | undefined;
605
+ /**
606
+ * Enter a parent tool context on the current async branch. Subsequent
607
+ * `await`-ed work (including the parent's `tool.execute` body) inherits
608
+ * the context until {@link clearParentToolContext} runs. No-op on
609
+ * runtimes without `enterWith` support (browser/edge sync fallback).
610
+ */
611
+ declare function enterParentToolContext(ctx: ParentToolContext): void;
612
+ /**
613
+ * Clear the parent tool context on the current async branch. Pairs with
614
+ * {@link enterParentToolContext}. No-op on runtimes without `enterWith`
615
+ * support.
616
+ *
617
+ * `enterWith(undefined)` is supported by Node's `AsyncLocalStorage` at
618
+ * runtime; the `as never` cast bypasses TypeScript's stricter typing on
619
+ * the shared global declaration (`store: T`).
620
+ */
621
+ declare function clearParentToolContext(): void;
622
+ /**
623
+ * Run `fn` with `ctx` bound as the parent tool context. The sync-fallback
624
+ * equivalent of `enter`/`clear` — useful in tests and any code path
625
+ * where you want strict block scoping.
626
+ */
627
+ declare function runWithParentToolContext<R>(ctx: ParentToolContext, fn: () => R): R;
628
+
378
629
  /**
379
630
  * Options for creating event metadata for call-time context override.
380
631
  */
@@ -528,6 +779,40 @@ type RaindropAISDKOptions = {
528
779
  maxQueueSize?: number;
529
780
  debug?: boolean;
530
781
  debugSpans?: boolean;
782
+ /**
783
+ * Per-span hook that fires for every OTLP span right before it's shipped
784
+ * (to the Raindrop API and to a local debugger). Lets callers inspect,
785
+ * rewrite, or drop entire spans — rename attributes, add new ones, scrub
786
+ * additional secret-shaped values inside `ai.prompt.messages` /
787
+ * `ai.toolCall.args`, etc.
788
+ *
789
+ * Return values:
790
+ * - `undefined` or the same span reference: ship the span unchanged.
791
+ * - a new `OtlpSpan`: ship the returned span in place of the original.
792
+ * - `null`: drop the span entirely from every ship path.
793
+ *
794
+ * The hook runs BEFORE the default redactor (the always-on floor for
795
+ * documented BYOK secrets — `apiKey`, `secretAccessKey`, `privateKey`,
796
+ * `accessToken`, etc. — inside `ai.request.providerOptions` and
797
+ * `ai.response.providerMetadata`). The default redactor still runs on
798
+ * the post-transform span unless `disableDefaultRedaction` is set.
799
+ *
800
+ * The hook runs on the hot path — keep it synchronous and
801
+ * side-effect-free. If the hook throws, the span is dropped (fail-closed).
802
+ */
803
+ transformSpan?: TransformSpanHook;
804
+ /**
805
+ * Disable the always-on default redactor that scrubs documented secret-
806
+ * shaped keys (`apiKey`, `secretAccessKey`, `privateKey`, ...) inside
807
+ * `ai.request.providerOptions` and `ai.response.providerMetadata`.
808
+ *
809
+ * Default: `false` (built-in redaction is on — BYOK credentials inside
810
+ * `providerOptions` are stripped automatically). If you set this to
811
+ * `true`, raw provider options will be shipped — only do this if you've
812
+ * verified your callers never put secrets in `providerOptions` or you've
813
+ * supplied your own `transformSpan` hook.
814
+ */
815
+ disableDefaultRedaction?: boolean;
531
816
  };
532
817
  events?: {
533
818
  enabled?: boolean;
@@ -691,8 +976,17 @@ type RaindropAISDKClient = {
691
976
  /**
692
977
  * Create a TelemetryIntegration instance for direct registration with AI SDK v7+.
693
978
  * Use with `registerTelemetryIntegration()` from the `ai` package.
979
+ *
980
+ * Accepts the same `context` it has always accepted, or a full options
981
+ * object exposing `{ context, subagentWrapping }`. Sub-agent wrapping
982
+ * defaults to `true`; pass `subagentWrapping: false` to opt out for
983
+ * consumers whose `tool.execute` happens to call `generateText` for
984
+ * non-agentic reasons (e.g. RAG enrichment).
694
985
  */
695
- createTelemetryIntegration(context?: RaindropTelemetryIntegrationOptions["context"]): RaindropTelemetryIntegration;
986
+ createTelemetryIntegration(contextOrOptions?: RaindropTelemetryIntegrationOptions["context"] | {
987
+ context?: RaindropTelemetryIntegrationOptions["context"];
988
+ subagentWrapping?: boolean;
989
+ }): RaindropTelemetryIntegration;
696
990
  events: {
697
991
  patch(eventId: string, patch: EventPatch): Promise<void>;
698
992
  addAttachments(eventId: string, attachments: Attachment[]): Promise<void>;
@@ -764,4 +1058,4 @@ type RaindropAISDKClient = {
764
1058
  };
765
1059
  declare function createRaindropAISDK(opts: RaindropAISDKOptions): RaindropAISDKClient;
766
1060
 
767
- export { type AISDKChatRequestLike, type AISDKChatRequestMessageLike, type AISDKMessage, type AgentCallMetadata, type AgentWithMetadata, type Attachment, type BuildEventPatch, ContextManager, type ContextSpan, type CreateSpanArgs, type EndSpanArgs, type EventBuilder, type EventMetadataOptions, type IdentifyInput, type RaindropAISDKClient, type RaindropAISDKContext, type RaindropAISDKOptions, type RaindropCallMetadata, RaindropTelemetryIntegration, type RaindropTelemetryIntegrationOptions, type SelfDiagnosticsOptions, type SelfDiagnosticsSignalDefinition, type SelfDiagnosticsSignalDefinitions, type StartSpanArgs, type TraceSpan, type WrapAISDKOptions, type WrappedAI, type WrappedAISDK, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, createRaindropAISDK, currentSpan, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentRaindropCallMetadata, readRaindropCallMetadataFromArgs, runWithRaindropCallMetadata, withCurrent };
1061
+ export { type AISDKChatRequestLike, type AISDKChatRequestMessageLike, type AISDKMessage, type AgentCallMetadata, type AgentWithMetadata, type Attachment, type BuildEventPatch, ContextManager, type ContextSpan, type CreateSpanArgs, DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, type EndSpanArgs, type EventBuilder, type EventMetadataOptions, type IdentifyInput, type OtlpAnyValue, type OtlpSpan, type ParentToolContext, REDACTED_PLACEHOLDER, type RaindropAISDKClient, type RaindropAISDKContext, type RaindropAISDKOptions, type RaindropCallMetadata, RaindropTelemetryIntegration, type RaindropTelemetryIntegrationOptions, type SelfDiagnosticsOptions, type SelfDiagnosticsSignalDefinition, type SelfDiagnosticsSignalDefinitions, type StartSpanArgs, type TraceSpan, type TransformSpanHook, type WrapAISDKOptions, type WrappedAI, type WrappedAISDK, _resetParentToolContextStorage, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, clearParentToolContext, createRaindropAISDK, currentSpan, defaultTransformSpan, enterParentToolContext, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentParentToolContext, getCurrentRaindropCallMetadata, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, runWithParentToolContext, runWithRaindropCallMetadata, withCurrent };