@raindrop-ai/ai-sdk 0.0.33 → 0.0.35

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.
@@ -1,4 +1,4 @@
1
- // ../core/dist/chunk-VUNUOE2X.js
1
+ // ../core/dist/chunk-SK6EJEO7.js
2
2
  function getCrypto() {
3
3
  const c = globalThis.crypto;
4
4
  return c;
@@ -53,6 +53,8 @@ function base64Encode(bytes) {
53
53
  }
54
54
  return out;
55
55
  }
56
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
57
+ var MAX_RETRY_DELAY_MS = 3e4;
56
58
  function wait(ms) {
57
59
  return new Promise((resolve) => setTimeout(resolve, ms));
58
60
  }
@@ -60,6 +62,46 @@ function formatEndpoint(endpoint) {
60
62
  if (!endpoint) return void 0;
61
63
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
62
64
  }
65
+ function redactUrlForLog(url) {
66
+ try {
67
+ const parsed = new URL(url);
68
+ parsed.username = "";
69
+ parsed.password = "";
70
+ parsed.search = "";
71
+ parsed.hash = "";
72
+ return parsed.toString();
73
+ } catch (e) {
74
+ return "<unparseable-url>";
75
+ }
76
+ }
77
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
78
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
79
+ function rateLimitedLog(key, log) {
80
+ const now = Date.now();
81
+ const last = rateLimitedLogLast.get(key);
82
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
83
+ return false;
84
+ }
85
+ rateLimitedLogLast.set(key, now);
86
+ log();
87
+ return true;
88
+ }
89
+ async function raceWithTimeout(promise, timeoutMs) {
90
+ let timer;
91
+ const settledInTime = await Promise.race([
92
+ promise.then(
93
+ () => true,
94
+ () => true
95
+ ),
96
+ new Promise((resolve) => {
97
+ var _a;
98
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
99
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
100
+ })
101
+ ]);
102
+ if (timer) clearTimeout(timer);
103
+ return settledInTime;
104
+ }
63
105
  function parseRetryAfter(headers) {
64
106
  var _a;
65
107
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -76,12 +118,12 @@ function parseRetryAfter(headers) {
76
118
  function getRetryDelayMs(attemptNumber, previousError) {
77
119
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
78
120
  const v = previousError.retryAfterMs;
79
- if (typeof v === "number") return Math.max(0, v);
121
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
80
122
  }
81
123
  if (attemptNumber <= 1) return 0;
82
124
  const base = 500;
83
125
  const factor = Math.pow(2, attemptNumber - 2);
84
- return base * factor;
126
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
85
127
  }
86
128
  async function withRetry(operation, opName2, opts) {
87
129
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -116,7 +158,9 @@ async function withRetry(operation, opName2, opts) {
116
158
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
117
159
  }
118
160
  async function postJson(url, body, headers, opts) {
119
- const opName2 = `POST ${url}`;
161
+ var _a;
162
+ const opName2 = `POST ${redactUrlForLog(url)}`;
163
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
120
164
  await withRetry(
121
165
  async () => {
122
166
  const resp = await fetch(url, {
@@ -125,7 +169,8 @@ async function postJson(url, body, headers, opts) {
125
169
  "Content-Type": "application/json",
126
170
  ...headers
127
171
  },
128
- body: JSON.stringify(body)
172
+ body: JSON.stringify(body),
173
+ signal: AbortSignal.timeout(timeoutMs)
129
174
  });
130
175
  if (!resp.ok) {
131
176
  const text = await resp.text().catch(() => "");
@@ -142,6 +187,27 @@ async function postJson(url, body, headers, opts) {
142
187
  opts
143
188
  );
144
189
  }
190
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
191
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
192
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
193
+ function resolveMaxTextFieldChars(value) {
194
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
195
+ return Math.floor(value);
196
+ }
197
+ return currentDefaultMaxTextFieldChars;
198
+ }
199
+ function truncateToLimit(text, limit) {
200
+ if (limit > TRUNCATION_MARKER.length) {
201
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
202
+ }
203
+ return text.slice(0, Math.max(0, limit));
204
+ }
205
+ function capText(value, limit) {
206
+ if (typeof value !== "string") return value;
207
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
208
+ if (value.length <= max) return value;
209
+ return truncateToLimit(value, max);
210
+ }
145
211
  var SpanStatusCode = {
146
212
  UNSET: 0,
147
213
  ERROR: 2
@@ -329,6 +395,27 @@ function sendLocalDebuggerLiveEvent(event, options = {}) {
329
395
  ).catch(() => {
330
396
  });
331
397
  }
398
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
399
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
400
+ function isValidProjectIdSlug(value) {
401
+ return PROJECT_ID_SLUG_PATTERN.test(value);
402
+ }
403
+ function normalizeProjectId(raw, opts) {
404
+ if (typeof raw !== "string") return void 0;
405
+ const trimmed = raw.trim();
406
+ if (!trimmed) return void 0;
407
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
408
+ console.warn(
409
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
410
+ );
411
+ }
412
+ return trimmed;
413
+ }
414
+ function projectIdHeaders(projectId) {
415
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
416
+ }
417
+ var SHUTDOWN_DEADLINE_MS = 1e4;
418
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
332
419
  function mergePatches(target, source) {
333
420
  var _a, _b, _c, _d;
334
421
  const out = { ...target, ...source };
@@ -346,6 +433,7 @@ var EventShipper = class {
346
433
  this.sticky = /* @__PURE__ */ new Map();
347
434
  this.timers = /* @__PURE__ */ new Map();
348
435
  this.inFlight = /* @__PURE__ */ new Set();
436
+ this.hasShutdown = false;
349
437
  var _a, _b, _c, _d, _e, _f, _g, _h;
350
438
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
351
439
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -355,10 +443,15 @@ var EventShipper = class {
355
443
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
356
444
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
357
445
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
446
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
358
447
  this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
359
448
  if (this.debug && this.localDebuggerUrl) {
360
449
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
361
450
  }
451
+ this.projectId = normalizeProjectId(opts.projectId, {
452
+ debug: this.debug,
453
+ prefix: this.prefix
454
+ });
362
455
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
363
456
  this.context = {
364
457
  library: {
@@ -377,10 +470,55 @@ var EventShipper = class {
377
470
  authHeaders() {
378
471
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
379
472
  }
473
+ requestHeaders() {
474
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
475
+ }
476
+ /**
477
+ * Build the retry/timeout options for one POST, honoring the shutdown
478
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
479
+ * the caller must drop the payload (with a rate-limited warning) instead
480
+ * of issuing a request that could outlive process exit.
481
+ *
482
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
483
+ * path is mid-drain takes effect immediately: no further retries, and the
484
+ * per-attempt timeout is clamped to the remaining window. After
485
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
486
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
487
+ * run as a single short attempt rather than regaining the full retry
488
+ * schedule.
489
+ */
490
+ requestOpts() {
491
+ if (this.shutdownDeadlineAt !== void 0) {
492
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
493
+ if (remainingMs <= 0) return null;
494
+ return {
495
+ maxAttempts: 1,
496
+ debug: this.debug,
497
+ sdkName: this.sdkName,
498
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
499
+ };
500
+ }
501
+ if (this.hasShutdown) {
502
+ return {
503
+ maxAttempts: 1,
504
+ debug: this.debug,
505
+ sdkName: this.sdkName,
506
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
507
+ };
508
+ }
509
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
510
+ }
380
511
  async patch(eventId, patch) {
381
512
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
382
513
  if (!this.enabled) return;
383
514
  if (!eventId || !eventId.trim()) return;
515
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
516
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
517
+ patch = { ...patch, input: capText(patch.input, maxChars) };
518
+ }
519
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
520
+ patch = { ...patch, output: capText(patch.output, maxChars) };
521
+ }
384
522
  if (this.debug) {
385
523
  console.log(`${this.prefix} queue patch`, {
386
524
  eventId,
@@ -427,9 +565,16 @@ var EventShipper = class {
427
565
  })));
428
566
  }
429
567
  async shutdown() {
430
- for (const t of this.timers.values()) clearTimeout(t);
431
- this.timers.clear();
432
- await this.flush();
568
+ this.hasShutdown = true;
569
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
570
+ try {
571
+ for (const t of this.timers.values()) clearTimeout(t);
572
+ this.timers.clear();
573
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
574
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
575
+ } finally {
576
+ this.shutdownDeadlineAt = void 0;
577
+ }
433
578
  }
434
579
  async trackSignal(signal) {
435
580
  var _a, _b;
@@ -451,15 +596,19 @@ var EventShipper = class {
451
596
  ];
452
597
  if (!this.writeKey) return;
453
598
  const url = `${this.baseUrl}signals/track`;
599
+ const opts = this.requestOpts();
600
+ if (!opts) {
601
+ this.warnShutdownDrop("signal");
602
+ return;
603
+ }
454
604
  try {
455
- await postJson(url, body, this.authHeaders(), {
456
- maxAttempts: 3,
457
- debug: this.debug,
458
- sdkName: this.sdkName
459
- });
605
+ await postJson(url, body, this.requestHeaders(), opts);
460
606
  } catch (err) {
461
607
  const msg = err instanceof Error ? err.message : String(err);
462
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
608
+ rateLimitedLog(
609
+ `${this.prefix}.send_signal_failed`,
610
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
611
+ );
463
612
  }
464
613
  }
465
614
  async identify(users) {
@@ -483,17 +632,27 @@ var EventShipper = class {
483
632
  if (!this.writeKey) return;
484
633
  if (body.length === 0) return;
485
634
  const url = `${this.baseUrl}users/identify`;
635
+ const opts = this.requestOpts();
636
+ if (!opts) {
637
+ this.warnShutdownDrop("identify");
638
+ return;
639
+ }
486
640
  try {
487
- await postJson(url, body, this.authHeaders(), {
488
- maxAttempts: 3,
489
- debug: this.debug,
490
- sdkName: this.sdkName
491
- });
641
+ await postJson(url, body, this.requestHeaders(), opts);
492
642
  } catch (err) {
493
643
  const msg = err instanceof Error ? err.message : String(err);
494
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
644
+ rateLimitedLog(
645
+ `${this.prefix}.send_identify_failed`,
646
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
647
+ );
495
648
  }
496
649
  }
650
+ warnShutdownDrop(what) {
651
+ rateLimitedLog(
652
+ `${this.prefix}.shutdown_deadline`,
653
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
654
+ );
655
+ }
497
656
  async flushOne(eventId) {
498
657
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
499
658
  if (!this.enabled) return;
@@ -569,11 +728,13 @@ var EventShipper = class {
569
728
  if (!isPending) this.sticky.delete(eventId);
570
729
  return;
571
730
  }
572
- const p = postJson(url, payload, this.authHeaders(), {
573
- maxAttempts: 3,
574
- debug: this.debug,
575
- sdkName: this.sdkName
576
- });
731
+ const opts = this.requestOpts();
732
+ if (!opts) {
733
+ this.warnShutdownDrop(`track_partial ${eventId}`);
734
+ if (!isPending) this.sticky.delete(eventId);
735
+ return;
736
+ }
737
+ const p = postJson(url, payload, this.requestHeaders(), opts);
577
738
  this.inFlight.add(p);
578
739
  try {
579
740
  try {
@@ -583,7 +744,10 @@ var EventShipper = class {
583
744
  }
584
745
  } catch (err) {
585
746
  const msg = err instanceof Error ? err.message : String(err);
586
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
747
+ rateLimitedLog(
748
+ `${this.prefix}.send_track_partial_failed`,
749
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
750
+ );
587
751
  }
588
752
  } finally {
589
753
  this.inFlight.delete(p);
@@ -682,10 +846,24 @@ function redactJsonAttributeValue(key, value) {
682
846
  if (scrubbedJson === json) return void 0;
683
847
  return { stringValue: scrubbedJson };
684
848
  }
849
+ function applyOtelSpanAttributeLimit(limit) {
850
+ var _a, _b;
851
+ try {
852
+ const raw = (_b = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env) == null ? void 0 : _b.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
853
+ if (!raw) return limit;
854
+ const parsed = Number.parseInt(raw, 10);
855
+ if (Number.isFinite(parsed) && parsed > 0) {
856
+ return Math.min(limit, parsed);
857
+ }
858
+ } catch (e) {
859
+ }
860
+ return limit;
861
+ }
685
862
  var TraceShipper = class {
686
863
  constructor(opts) {
687
864
  this.queue = [];
688
865
  this.inFlight = /* @__PURE__ */ new Set();
866
+ this.hasShutdown = false;
689
867
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
690
868
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
691
869
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -703,8 +881,45 @@ var TraceShipper = class {
703
881
  if (this.debug && this.localDebuggerUrl) {
704
882
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
705
883
  }
884
+ this.projectId = normalizeProjectId(opts.projectId, {
885
+ debug: this.debug,
886
+ prefix: this.prefix
887
+ });
706
888
  this.transformSpanHook = opts.transformSpan;
707
889
  this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
890
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
891
+ }
892
+ /**
893
+ * Cap every string attribute value on the span. O(#attributes) length
894
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
895
+ * pipeline so the default secret-scrub still sees parseable JSON in
896
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
897
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
898
+ * in the surviving prefix).
899
+ *
900
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
901
+ * for span content, matching the Python SDK and the OTel SDK convention.
902
+ */
903
+ capSpanAttributes(span) {
904
+ var _a;
905
+ const maxChars = applyOtelSpanAttributeLimit(
906
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
907
+ );
908
+ const attrs = span.attributes;
909
+ if (!attrs || attrs.length === 0) return span;
910
+ let nextAttrs;
911
+ for (let i = 0; i < attrs.length; i++) {
912
+ const attr = attrs[i];
913
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
914
+ if (typeof value !== "string" || value.length <= maxChars) continue;
915
+ if (!nextAttrs) nextAttrs = attrs.slice();
916
+ nextAttrs[i] = {
917
+ key: attr.key,
918
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
919
+ };
920
+ }
921
+ if (!nextAttrs) return span;
922
+ return { ...span, attributes: nextAttrs };
708
923
  }
709
924
  /**
710
925
  * Apply the user `transformSpan` hook (if any) followed by the default
@@ -738,7 +953,7 @@ var TraceShipper = class {
738
953
  if (!this.disableDefaultRedaction) {
739
954
  current = defaultTransformSpan(current);
740
955
  }
741
- return current;
956
+ return this.capSpanAttributes(current);
742
957
  }
743
958
  isDebugEnabled() {
744
959
  return this.debug;
@@ -746,6 +961,9 @@ var TraceShipper = class {
746
961
  authHeaders() {
747
962
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
748
963
  }
964
+ requestHeaders() {
965
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
966
+ }
749
967
  startSpan(args) {
750
968
  var _a, _b;
751
969
  const ids = createSpanIds(args.parent);
@@ -859,6 +1077,16 @@ var TraceShipper = class {
859
1077
  while (this.queue.length > 0) {
860
1078
  const batch = this.queue.splice(0, this.maxBatchSize);
861
1079
  if (!this.writeKey) continue;
1080
+ const opts = this.requestOpts();
1081
+ if (!opts) {
1082
+ rateLimitedLog(
1083
+ `${this.prefix}.shutdown_deadline`,
1084
+ () => console.warn(
1085
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1086
+ )
1087
+ );
1088
+ continue;
1089
+ }
862
1090
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
863
1091
  const url = `${this.baseUrl}traces`;
864
1092
  if (this.debug) {
@@ -867,11 +1095,7 @@ var TraceShipper = class {
867
1095
  endpoint: url
868
1096
  });
869
1097
  }
870
- const p = postJson(url, body, this.authHeaders(), {
871
- maxAttempts: 3,
872
- debug: this.debug,
873
- sdkName: this.sdkName
874
- });
1098
+ const p = postJson(url, body, this.requestHeaders(), opts);
875
1099
  this.inFlight.add(p);
876
1100
  try {
877
1101
  try {
@@ -879,21 +1103,61 @@ var TraceShipper = class {
879
1103
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
880
1104
  } catch (err) {
881
1105
  const msg = err instanceof Error ? err.message : String(err);
882
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1106
+ rateLimitedLog(
1107
+ `${this.prefix}.send_spans_failed`,
1108
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1109
+ );
883
1110
  }
884
1111
  } finally {
885
1112
  this.inFlight.delete(p);
886
1113
  }
887
1114
  }
888
1115
  }
1116
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1117
+ requestOpts() {
1118
+ if (this.shutdownDeadlineAt !== void 0) {
1119
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1120
+ if (remainingMs <= 0) return null;
1121
+ return {
1122
+ maxAttempts: 1,
1123
+ debug: this.debug,
1124
+ sdkName: this.sdkName,
1125
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1126
+ };
1127
+ }
1128
+ if (this.hasShutdown) {
1129
+ return {
1130
+ maxAttempts: 1,
1131
+ debug: this.debug,
1132
+ sdkName: this.sdkName,
1133
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1134
+ };
1135
+ }
1136
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1137
+ }
889
1138
  async shutdown() {
890
- if (this.timer) {
891
- clearTimeout(this.timer);
892
- this.timer = void 0;
1139
+ this.hasShutdown = true;
1140
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1141
+ try {
1142
+ if (this.timer) {
1143
+ clearTimeout(this.timer);
1144
+ this.timer = void 0;
1145
+ }
1146
+ const drain = async () => {
1147
+ await this.flush();
1148
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1149
+ })));
1150
+ };
1151
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1152
+ if (!settled) {
1153
+ rateLimitedLog(
1154
+ `${this.prefix}.shutdown_deadline`,
1155
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1156
+ );
1157
+ }
1158
+ } finally {
1159
+ this.shutdownDeadlineAt = void 0;
893
1160
  }
894
- await this.flush();
895
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
896
- })));
897
1161
  }
898
1162
  };
899
1163
  var NOOP_SPAN = {
@@ -1017,7 +1281,7 @@ async function* asyncGeneratorWithCurrent(span, gen) {
1017
1281
  // package.json
1018
1282
  var package_default = {
1019
1283
  name: "@raindrop-ai/ai-sdk",
1020
- version: "0.0.33"};
1284
+ version: "0.0.35"};
1021
1285
 
1022
1286
  // src/internal/version.ts
1023
1287
  var libraryName = package_default.name;
@@ -1036,6 +1300,133 @@ var EventShipper2 = class extends EventShipper {
1036
1300
  }
1037
1301
  };
1038
1302
 
1303
+ // src/internal/truncation.ts
1304
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1305
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
1306
+ var MAX_DEPTH = 12;
1307
+ var configuredMaxTextFieldChars;
1308
+ function setMaxTextFieldChars(limit) {
1309
+ if (limit === void 0) return;
1310
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
1311
+ configuredMaxTextFieldChars = Math.floor(limit);
1312
+ } else {
1313
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
1314
+ }
1315
+ }
1316
+ function otelEnvLimit() {
1317
+ var _a;
1318
+ if (typeof process === "undefined") return void 0;
1319
+ const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
1320
+ if (!raw) return void 0;
1321
+ const parsed = Number.parseInt(raw, 10);
1322
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1323
+ }
1324
+ function effectiveMaxTextFieldChars() {
1325
+ const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
1326
+ const env = otelEnvLimit();
1327
+ return env !== void 0 && env < base ? env : base;
1328
+ }
1329
+ function truncateToLimit2(text, limit) {
1330
+ if (limit > TRUNCATION_MARKER2.length) {
1331
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1332
+ }
1333
+ return text.slice(0, limit);
1334
+ }
1335
+ function capText2(value, limit) {
1336
+ if (typeof value !== "string") return value;
1337
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1338
+ if (value.length <= max) return value;
1339
+ return truncateToLimit2(value, max);
1340
+ }
1341
+ function boundedClone2(value, budget, depth) {
1342
+ var _a, _b;
1343
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1344
+ if (typeof value === "string") {
1345
+ if (value.length > budget.remaining) {
1346
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1347
+ budget.remaining = 0;
1348
+ return taken;
1349
+ }
1350
+ budget.remaining -= Math.max(value.length, 1);
1351
+ return value;
1352
+ }
1353
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1354
+ budget.remaining -= 8;
1355
+ return value;
1356
+ }
1357
+ if (typeof value !== "object") {
1358
+ budget.remaining -= 1;
1359
+ return value;
1360
+ }
1361
+ if (value instanceof Uint8Array) {
1362
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
1363
+ if (value.byteLength > maxBytes) {
1364
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
1365
+ budget.remaining = 0;
1366
+ return taken;
1367
+ }
1368
+ const encoded = base64Encode(value);
1369
+ budget.remaining -= Math.max(encoded.length, 1);
1370
+ return encoded;
1371
+ }
1372
+ if (depth >= MAX_DEPTH) {
1373
+ budget.remaining -= 16;
1374
+ const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
1375
+ return `<max depth: ${name}>`;
1376
+ }
1377
+ const toJSON = value.toJSON;
1378
+ if (typeof toJSON === "function") {
1379
+ budget.remaining -= 2;
1380
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
1381
+ }
1382
+ if (Array.isArray(value)) {
1383
+ budget.remaining -= 2;
1384
+ const out2 = [];
1385
+ for (const item of value) {
1386
+ if (budget.remaining <= 0) {
1387
+ out2.push(TRUNCATION_MARKER2);
1388
+ break;
1389
+ }
1390
+ out2.push(boundedClone2(item, budget, depth + 1));
1391
+ }
1392
+ return out2;
1393
+ }
1394
+ budget.remaining -= 2;
1395
+ const out = {};
1396
+ for (const key in value) {
1397
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
1398
+ if (budget.remaining <= 0) {
1399
+ out["..."] = TRUNCATION_MARKER2;
1400
+ break;
1401
+ }
1402
+ let cappedKey = key;
1403
+ if (key.length > budget.remaining) {
1404
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1405
+ budget.remaining = 0;
1406
+ out[cappedKey] = TRUNCATION_MARKER2;
1407
+ break;
1408
+ }
1409
+ budget.remaining -= Math.max(key.length, 1);
1410
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
1411
+ }
1412
+ return out;
1413
+ }
1414
+ function boundedStringify(value, limit) {
1415
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1416
+ try {
1417
+ if (typeof value === "string") {
1418
+ const text2 = JSON.stringify(capText2(value, max));
1419
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
1420
+ }
1421
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
1422
+ const text = JSON.stringify(pruned);
1423
+ if (text === void 0) return void 0;
1424
+ return text.length <= max ? text : truncateToLimit2(text, max);
1425
+ } catch (e) {
1426
+ return void 0;
1427
+ }
1428
+ }
1429
+
1039
1430
  // src/internal/wrap/helpers.ts
1040
1431
  function isRecord(value) {
1041
1432
  return typeof value === "object" && value !== null;
@@ -1060,21 +1451,10 @@ function isModuleNamespace(obj) {
1060
1451
  }
1061
1452
  }
1062
1453
  function safeJson(value) {
1063
- try {
1064
- return JSON.stringify(value);
1065
- } catch (e) {
1066
- return void 0;
1067
- }
1454
+ return boundedStringify(value);
1068
1455
  }
1069
1456
  function safeJsonWithUint8(value) {
1070
- try {
1071
- return JSON.stringify(value, (_key, v) => {
1072
- if (v instanceof Uint8Array) return base64Encode(v);
1073
- return v;
1074
- });
1075
- } catch (e) {
1076
- return void 0;
1077
- }
1457
+ return boundedStringify(value);
1078
1458
  }
1079
1459
  function extractModelInfo(model) {
1080
1460
  if (typeof model === "string") {
@@ -1970,7 +2350,10 @@ var RaindropTelemetryIntegration = class {
1970
2350
  inputAttrs.push(
1971
2351
  attrStringArray(
1972
2352
  "ai.prompt.tools",
1973
- event.stepTools.map((t) => JSON.stringify(t))
2353
+ event.stepTools.map((t) => {
2354
+ var _a;
2355
+ return (_a = safeJsonWithUint8(t)) != null ? _a : "";
2356
+ })
1974
2357
  )
1975
2358
  );
1976
2359
  }
@@ -1978,7 +2361,7 @@ var RaindropTelemetryIntegration = class {
1978
2361
  inputAttrs.push(
1979
2362
  attrString(
1980
2363
  "ai.prompt.toolChoice",
1981
- JSON.stringify(event.stepToolChoice)
2364
+ safeJsonWithUint8(event.stepToolChoice)
1982
2365
  )
1983
2366
  );
1984
2367
  }
@@ -2034,7 +2417,9 @@ var RaindropTelemetryIntegration = class {
2034
2417
  if (chunk.type === "text-delta") {
2035
2418
  const delta = (_c = chunk.textDelta) != null ? _c : chunk.delta;
2036
2419
  if (typeof delta === "string") {
2037
- state.accumulatedText += delta;
2420
+ if (state.accumulatedText.length <= effectiveMaxTextFieldChars()) {
2421
+ state.accumulatedText += delta;
2422
+ }
2038
2423
  this.emitLive(state, "text_delta", delta);
2039
2424
  }
2040
2425
  } else if (chunk.type === "reasoning" || chunk.type === "reasoning-delta") {
@@ -2053,7 +2438,7 @@ var RaindropTelemetryIntegration = class {
2053
2438
  if (state.recordOutputs) {
2054
2439
  outputAttrs.push(
2055
2440
  attrString("ai.response.finishReason", event.finishReason),
2056
- attrString("ai.response.text", (_a = event.text) != null ? _a : void 0),
2441
+ attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2057
2442
  attrString("ai.response.id", (_b = event.response) == null ? void 0 : _b.id),
2058
2443
  attrString("ai.response.model", (_c = event.response) == null ? void 0 : _c.modelId),
2059
2444
  attrString(
@@ -2069,7 +2454,7 @@ var RaindropTelemetryIntegration = class {
2069
2454
  outputAttrs.push(
2070
2455
  attrString(
2071
2456
  "ai.response.toolCalls",
2072
- JSON.stringify(
2457
+ safeJsonWithUint8(
2073
2458
  event.toolCalls.map((tc) => ({
2074
2459
  toolCallId: tc.toolCallId,
2075
2460
  toolName: tc.toolName,
@@ -2082,7 +2467,7 @@ var RaindropTelemetryIntegration = class {
2082
2467
  if (((_g = event.reasoning) == null ? void 0 : _g.length) > 0) {
2083
2468
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2084
2469
  if (reasoningText) {
2085
- outputAttrs.push(attrString("ai.response.reasoning", reasoningText));
2470
+ outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
2086
2471
  }
2087
2472
  }
2088
2473
  }
@@ -2504,7 +2889,7 @@ var RaindropTelemetryIntegration = class {
2504
2889
  if (state.recordOutputs) {
2505
2890
  outputAttrs.push(
2506
2891
  attrString("ai.response.finishReason", event.finishReason),
2507
- attrString("ai.response.text", (_a = event.text) != null ? _a : void 0),
2892
+ attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2508
2893
  attrString(
2509
2894
  "ai.response.providerMetadata",
2510
2895
  event.providerMetadata ? safeJsonWithUint8(event.providerMetadata) : void 0
@@ -2514,7 +2899,7 @@ var RaindropTelemetryIntegration = class {
2514
2899
  outputAttrs.push(
2515
2900
  attrString(
2516
2901
  "ai.response.toolCalls",
2517
- JSON.stringify(
2902
+ safeJsonWithUint8(
2518
2903
  event.toolCalls.map((tc) => ({
2519
2904
  toolCallId: tc.toolCallId,
2520
2905
  toolName: tc.toolName,
@@ -2527,7 +2912,7 @@ var RaindropTelemetryIntegration = class {
2527
2912
  if (((_c = event.reasoning) == null ? void 0 : _c.length) > 0) {
2528
2913
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2529
2914
  if (reasoningText) {
2530
- outputAttrs.push(attrString("ai.response.reasoning", reasoningText));
2915
+ outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
2531
2916
  }
2532
2917
  }
2533
2918
  }
@@ -2565,7 +2950,8 @@ var RaindropTelemetryIntegration = class {
2565
2950
  const userId = (_b = callMeta.userId) != null ? _b : (_a = this.defaultContext) == null ? void 0 : _a.userId;
2566
2951
  if (!userId) return;
2567
2952
  const eventName = (_e = (_d = callMeta.eventName) != null ? _d : (_c = this.defaultContext) == null ? void 0 : _c.eventName) != null ? _e : state.operationId;
2568
- const input = state.inputText;
2953
+ const cappedOutput = capText2(output);
2954
+ const input = capText2(state.inputText);
2569
2955
  const properties = {
2570
2956
  ...(_f = this.defaultContext) == null ? void 0 : _f.properties,
2571
2957
  ...callMeta.properties
@@ -2576,7 +2962,7 @@ var RaindropTelemetryIntegration = class {
2576
2962
  userId,
2577
2963
  convoId,
2578
2964
  input,
2579
- output,
2965
+ output: cappedOutput,
2580
2966
  model,
2581
2967
  properties: Object.keys(properties).length > 0 ? properties : void 0,
2582
2968
  isPending: false
@@ -3049,13 +3435,14 @@ function shouldKeepEventPending(params) {
3049
3435
  return params.finishReason === "tool-calls" || params.finishReason === "tool_calls";
3050
3436
  }
3051
3437
  function normalizePromptAttr(arg) {
3438
+ var _a, _b;
3052
3439
  if (!isRecord(arg)) return arg;
3053
3440
  const system = arg["system"];
3054
3441
  const prompt = arg["prompt"];
3055
3442
  const messages = arg["messages"];
3056
3443
  if (Array.isArray(messages)) {
3057
3444
  if (system) {
3058
- const sysContent = typeof system === "string" ? system : JSON.stringify(system);
3445
+ const sysContent = typeof system === "string" ? system : (_a = safeJsonWithUint8(system)) != null ? _a : String(system);
3059
3446
  return [{ role: "system", content: sysContent }, ...messages];
3060
3447
  }
3061
3448
  return messages;
@@ -3063,7 +3450,10 @@ function normalizePromptAttr(arg) {
3063
3450
  if (typeof prompt === "string") {
3064
3451
  const msgs = [];
3065
3452
  if (system) {
3066
- msgs.push({ role: "system", content: typeof system === "string" ? system : JSON.stringify(system) });
3453
+ msgs.push({
3454
+ role: "system",
3455
+ content: typeof system === "string" ? system : (_b = safeJsonWithUint8(system)) != null ? _b : String(system)
3456
+ });
3067
3457
  }
3068
3458
  msgs.push({ role: "user", content: prompt });
3069
3459
  return msgs;
@@ -3232,7 +3622,8 @@ function createFinalize(params) {
3232
3622
  };
3233
3623
  const built = await maybeBuildEvent(options.buildEvent, allMessages);
3234
3624
  const patch = mergeBuildEventPatch(defaultPatch, built);
3235
- const output = patch.output;
3625
+ const output = capText2(patch.output);
3626
+ const input = capText2(patch.input);
3236
3627
  const finalModel = (_c = patch.model) != null ? _c : model;
3237
3628
  if (setup.rootSpan) {
3238
3629
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -3276,7 +3667,7 @@ function createFinalize(params) {
3276
3667
  eventName: patch.eventName,
3277
3668
  userId: setup.ctx.userId,
3278
3669
  convoId: setup.ctx.convoId,
3279
- input: patch.input,
3670
+ input,
3280
3671
  output,
3281
3672
  model: finalModel,
3282
3673
  properties: patch.properties,
@@ -3791,7 +4182,8 @@ function wrapAgentGenerate(generate, instance, agentSettings, className, aiSDK,
3791
4182
  };
3792
4183
  const built = await maybeBuildEvent(deps.options.buildEvent, allMessages);
3793
4184
  const patch = mergeBuildEventPatch(defaultPatch, built);
3794
- const output = patch.output;
4185
+ const output = capText2(patch.output);
4186
+ const input = capText2(patch.input);
3795
4187
  const finalModel = (_c2 = patch.model) != null ? _c2 : model;
3796
4188
  if (rootSpan) {
3797
4189
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -3849,7 +4241,7 @@ function wrapAgentGenerate(generate, instance, agentSettings, className, aiSDK,
3849
4241
  eventName: patch.eventName,
3850
4242
  userId: ctx.userId,
3851
4243
  convoId: ctx.convoId,
3852
- input: patch.input,
4244
+ input,
3853
4245
  output,
3854
4246
  model: finalModel,
3855
4247
  properties: patch.properties,
@@ -3998,7 +4390,8 @@ function wrapAgentStream(stream, instance, agentSettings, className, aiSDK, deps
3998
4390
  };
3999
4391
  const built = await maybeBuildEvent(deps.options.buildEvent, allMessages);
4000
4392
  const patch = mergeBuildEventPatch(defaultPatch, built);
4001
- const output = patch.output;
4393
+ const output = capText2(patch.output);
4394
+ const input = capText2(patch.input);
4002
4395
  const finalModel = (_c2 = patch.model) != null ? _c2 : model;
4003
4396
  if (rootSpan) {
4004
4397
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -4056,7 +4449,7 @@ function wrapAgentStream(stream, instance, agentSettings, className, aiSDK, deps
4056
4449
  eventName: patch.eventName,
4057
4450
  userId: ctx.userId,
4058
4451
  convoId: ctx.convoId,
4059
- input: patch.input,
4452
+ input,
4060
4453
  output,
4061
4454
  model: finalModel,
4062
4455
  properties: patch.properties,
@@ -4396,7 +4789,7 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4396
4789
  attributes: [
4397
4790
  ...((_a = ctx.telemetry) == null ? void 0 : _a.recordOutputs) === false ? [] : [
4398
4791
  attrString("ai.response.finishReason", finishReason),
4399
- attrString("ai.response.text", activeText.length ? activeText : void 0),
4792
+ attrString("ai.response.text", activeText.length ? capText2(activeText) : void 0),
4400
4793
  attrString(
4401
4794
  "ai.response.toolCalls",
4402
4795
  safeJsonWithUint8(toolCallsLocal.length ? toolCallsLocal : void 0)
@@ -4449,7 +4842,9 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4449
4842
  } else if (typeof value["textDelta"] === "string") {
4450
4843
  textDelta = value["textDelta"];
4451
4844
  }
4452
- if (typeof textDelta === "string") activeText += textDelta;
4845
+ if (typeof textDelta === "string" && activeText.length <= effectiveMaxTextFieldChars()) {
4846
+ activeText += textDelta;
4847
+ }
4453
4848
  }
4454
4849
  if (type === "finish") finishReason = extractFinishReason(value);
4455
4850
  if (type === "tool-call") toolCallsLocal.push(value);
@@ -4722,8 +5117,8 @@ function eventMetadataFromChatRequest(options) {
4722
5117
  });
4723
5118
  }
4724
5119
  function stringify(v) {
4725
- if (typeof v === "string") return v;
4726
- return JSON.stringify(v);
5120
+ if (typeof v === "string") return capText2(v);
5121
+ return boundedStringify(v);
4727
5122
  }
4728
5123
  function userAttrsToOtlp(attrs) {
4729
5124
  if (!attrs) return [];
@@ -4741,6 +5136,7 @@ function createRaindropAISDK(opts) {
4741
5136
  const eventsRequested = ((_a = opts.events) == null ? void 0 : _a.enabled) !== false;
4742
5137
  const tracesRequested = ((_b = opts.traces) == null ? void 0 : _b.enabled) !== false;
4743
5138
  const envDebug = envDebugEnabled();
5139
+ setMaxTextFieldChars(opts.maxTextFieldChars);
4744
5140
  const localWorkshopInput = opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl;
4745
5141
  const resolvedLocalDebuggerUrl = resolveLocalDebuggerBaseUrl(localWorkshopInput);
4746
5142
  const localDebuggerUrl = localWorkshopInput === null ? null : resolvedLocalDebuggerUrl != null ? resolvedLocalDebuggerUrl : void 0;
@@ -4758,6 +5154,7 @@ function createRaindropAISDK(opts) {
4758
5154
  enabled: eventsEnabled,
4759
5155
  debug: ((_c = opts.events) == null ? void 0 : _c.debug) === true || envDebug,
4760
5156
  partialFlushMs: (_d = opts.events) == null ? void 0 : _d.partialFlushMs,
5157
+ projectId: opts.projectId,
4761
5158
  localDebuggerUrl
4762
5159
  });
4763
5160
  const traceShipper = new TraceShipper2({
@@ -4769,6 +5166,7 @@ function createRaindropAISDK(opts) {
4769
5166
  flushIntervalMs: (_g = opts.traces) == null ? void 0 : _g.flushIntervalMs,
4770
5167
  maxBatchSize: (_h = opts.traces) == null ? void 0 : _h.maxBatchSize,
4771
5168
  maxQueueSize: (_i = opts.traces) == null ? void 0 : _i.maxQueueSize,
5169
+ projectId: opts.projectId,
4772
5170
  localDebuggerUrl,
4773
5171
  transformSpan: (_j = opts.traces) == null ? void 0 : _j.transformSpan,
4774
5172
  disableDefaultRedaction: (_k = opts.traces) == null ? void 0 : _k.disableDefaultRedaction
@@ -4927,4 +5325,4 @@ function raindrop(options = {}) {
4927
5325
  return client.createTelemetryIntegration({ context, subagentWrapping });
4928
5326
  }
4929
5327
 
4930
- export { DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, REDACTED_PLACEHOLDER, RaindropTelemetryIntegration, _resetParentToolContextStorage, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, clearParentToolContext, createRaindropAISDK, currentSpan, defaultTransformSpan, enterParentToolContext, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentParentToolContext, getCurrentRaindropCallMetadata, raindrop, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, runWithParentToolContext, runWithRaindropCallMetadata, withCurrent };
5328
+ export { DEFAULT_MAX_TEXT_FIELD_CHARS2 as DEFAULT_MAX_TEXT_FIELD_CHARS, DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, REDACTED_PLACEHOLDER, RaindropTelemetryIntegration, TRUNCATION_MARKER2 as TRUNCATION_MARKER, _resetParentToolContextStorage, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, boundedStringify, capText2 as capText, clearParentToolContext, createRaindropAISDK, currentSpan, defaultTransformSpan, enterParentToolContext, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentParentToolContext, getCurrentRaindropCallMetadata, raindrop, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, runWithParentToolContext, runWithRaindropCallMetadata, withCurrent };