@raindrop-ai/openai-managed-agents 0.0.4 → 0.0.5

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.
package/dist/index.js CHANGED
@@ -25,7 +25,36 @@ __export(index_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
- // ../core/dist/chunk-BYBMRLZM.js
28
+ // ../core/dist/chunk-ZCB62SIO.js
29
+ var __create = Object.create;
30
+ var __defProp2 = Object.defineProperty;
31
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
32
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
33
+ var __getProtoOf = Object.getPrototypeOf;
34
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
35
+ var __commonJS = (cb, mod) => function __require() {
36
+ try {
37
+ return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
38
+ } catch (e) {
39
+ throw mod = 0, e;
40
+ }
41
+ };
42
+ var __copyProps2 = (to, from, except, desc) => {
43
+ if (from && typeof from === "object" || typeof from === "function") {
44
+ for (let key of __getOwnPropNames2(from))
45
+ if (!__hasOwnProp2.call(to, key) && key !== except)
46
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
47
+ }
48
+ return to;
49
+ };
50
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps2(
51
+ // If the importer is in node compatibility mode or this is not an ESM
52
+ // file that has been converted to a CommonJS file using a Babel-
53
+ // compatible transform (i.e. "__esModule" has not been set), then set
54
+ // "default" to the CommonJS "module.exports" for node compatibility.
55
+ isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
56
+ mod
57
+ ));
29
58
  var EVAL_CORRELATION_ID_ATTRIBUTE = "raindrop.eval_correlation_id";
30
59
  var PROBE_SCOPE = { correlationId: "" };
31
60
  var EvalScopeStorage = class {
@@ -87,21 +116,246 @@ function evalScopeAttributes() {
87
116
  return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
88
117
  }
89
118
 
90
- // ../core/dist/chunk-QH6U6QYP.js
119
+ // ../core/dist/chunk-AX36JKJL.js
120
+ var require_NameRedactor = __commonJS({
121
+ "../redact-pii/dist/built-ins/NameRedactor.js"(exports2) {
122
+ Object.defineProperty(exports2, "__esModule", { value: true });
123
+ exports2.NameRedactor = void 0;
124
+ var greetingRegex = /(^|\.\s+)(dear|hi|hello|greetings|hey|hey there)/gi;
125
+ var closingRegex = /(thx|thanks|thank you|regards|best|[a-z]{1,30}ly|[a-z]{1,30} regards|all the best|happy [a-z]{1,30}ing|take care|have a [a-z]{1,30} (weekend|night|day))/gi;
126
+ var greetingOrClosing = new RegExp("(((" + greetingRegex.source + ")|(" + closingRegex.source + "\\s*[,.!]*))[\\s-]*)", "gi");
127
+ var genericName = new RegExp("( ?(([A-Z][a-z]{1,30})|([A-Z]\\.))){1,10}([,.]|[,.]?$)", "my");
128
+ var NameRedactor = class {
129
+ constructor(replaceWith = "PERSON_NAME") {
130
+ this.replaceWith = replaceWith;
131
+ }
132
+ redact(textToRedact) {
133
+ var _a;
134
+ greetingOrClosing.lastIndex = 0;
135
+ genericName.lastIndex = 0;
136
+ const pieces = [];
137
+ let copiedThrough = 0;
138
+ let changed = false;
139
+ let greetingOrClosingMatch = greetingOrClosing.exec(textToRedact);
140
+ while (greetingOrClosingMatch !== null) {
141
+ genericName.lastIndex = greetingOrClosing.lastIndex;
142
+ const genericNameMatch = genericName.exec(textToRedact);
143
+ if (genericNameMatch !== null && genericNameMatch.index === greetingOrClosing.lastIndex) {
144
+ const suffix = (_a = genericNameMatch[5]) !== null && _a !== void 0 ? _a : "";
145
+ const end = genericNameMatch.index + genericNameMatch[0].length;
146
+ pieces.push(textToRedact.slice(copiedThrough, genericNameMatch.index), this.replaceWith, suffix);
147
+ copiedThrough = end;
148
+ changed = true;
149
+ greetingOrClosing.lastIndex = end - suffix.length;
150
+ }
151
+ greetingOrClosingMatch = greetingOrClosing.exec(textToRedact);
152
+ }
153
+ if (!changed)
154
+ return textToRedact;
155
+ pieces.push(textToRedact.slice(copiedThrough));
156
+ return pieces.join("");
157
+ }
158
+ };
159
+ exports2.NameRedactor = NameRedactor;
160
+ }
161
+ });
162
+ var require_simple_regexp_patterns = __commonJS({
163
+ "../redact-pii/dist/built-ins/simple-regexp-patterns.js"(exports2) {
164
+ Object.defineProperty(exports2, "__esModule", { value: true });
165
+ exports2.url = exports2.digits = exports2.credentials = exports2.password = exports2.username = exports2.emailAddress = exports2.usSocialSecurityNumber = exports2.ipAddress = exports2.phoneNumber = exports2.zipcode = exports2.streetAddress = exports2.creditCardNumber = void 0;
166
+ var aptRegex = /(apt|bldg|dept|fl|hngr|lot|pier|rm|ste|slip|trlr|unit|#)\.? *[a-z0-9-]+\b/gi;
167
+ var poBoxRegex = /P\.? ?O\.? *Box +\d+/gi;
168
+ var roadRegex = /(street|st|road|rd|avenue|ave|drive|dr|loop|court|ct|circle|cir|lane|ln|boulevard|blvd|way)\.?\b/gi;
169
+ exports2.creditCardNumber = /\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4}|\d{4}[ -]?\d{6}[ -]?\d{4}\d?/g;
170
+ exports2.streetAddress = new RegExp("(\\d{1,10}\\s*(\\w{1,30} ){1,2}" + roadRegex.source + "(\\s+" + aptRegex.source + ")?)|(" + poBoxRegex.source + ")", "gi");
171
+ exports2.zipcode = /\b\d{5}\b(-\d{4})?\b/g;
172
+ exports2.phoneNumber = /(\(?\+?[0-9]{1,2}\)?[-. ]?)?(\(?[0-9]{3}\)?|[0-9]{3})[-. ]?([0-9]{3}[-. ]?[0-9]{4}|\b[A-Z0-9]{7}\b)/g;
173
+ exports2.ipAddress = /(\d{1,3}(\.\d{1,3}){3}|[0-9A-F]{4}(:[0-9A-F]{4}){5}(::|(:0000)+))/gi;
174
+ exports2.usSocialSecurityNumber = /\b\d{3}[ -.]\d{2}[ -.]\d{4}\b/g;
175
+ exports2.emailAddress = /([a-z0-9_\-.+]{1,64})@\w+(\.\w+)*/gi;
176
+ exports2.username = /(user( ?name)?|login): \S+/gi;
177
+ exports2.password = /(pass(word|phrase)?|secret): \S+/gi;
178
+ exports2.credentials = /(login( cred(ential)?s| info(rmation)?)?|cred(ential)?s) ?:\s*\S+\s+\/?\s*\S+/gi;
179
+ exports2.digits = /\b\d{4,}\b/g;
180
+ exports2.url = /([^\s:/?#]{1,64}):\/\/([^/?#\s]*)([^?#\s]*)(\?([^#\s]*))?(#([^\s]*))?/g;
181
+ }
182
+ });
183
+ var require_utils = __commonJS({
184
+ "../redact-pii/dist/utils.js"(exports2) {
185
+ Object.defineProperty(exports2, "__esModule", { value: true });
186
+ exports2.snakeCase = exports2.isSyncRedactor = exports2.isSimpleRegexpCustomRedactorConfig = void 0;
187
+ function isSimpleRegexpCustomRedactorConfig(redactor2) {
188
+ return typeof redactor2.regexpPattern !== "undefined";
189
+ }
190
+ exports2.isSimpleRegexpCustomRedactorConfig = isSimpleRegexpCustomRedactorConfig;
191
+ function isSyncRedactor(redactor2) {
192
+ return typeof redactor2.redact === "function";
193
+ }
194
+ exports2.isSyncRedactor = isSyncRedactor;
195
+ function snakeCase(input = "") {
196
+ var _a;
197
+ const words = (_a = input.match(/[A-Z]{2,}(?=[A-Z][a-z]|[0-9]|\b)|[A-Z]?[a-z]+|[A-Z]+|[0-9]+/g)) !== null && _a !== void 0 ? _a : [];
198
+ return words.map((word) => word.toLowerCase()).join("_");
199
+ }
200
+ exports2.snakeCase = snakeCase;
201
+ }
202
+ });
203
+ var require_SimpleRegexpRedactor = __commonJS({
204
+ "../redact-pii/dist/built-ins/SimpleRegexpRedactor.js"(exports2) {
205
+ Object.defineProperty(exports2, "__esModule", { value: true });
206
+ exports2.SimpleRegexpRedactor = void 0;
207
+ var utils_1 = require_utils();
208
+ var SimpleRegexpRedactor = class {
209
+ constructor({ replaceWith = (0, utils_1.snakeCase)().toUpperCase(), regexpPattern: regexpMatcher }) {
210
+ this.replaceWith = replaceWith;
211
+ this.regexpMatcher = regexpMatcher;
212
+ }
213
+ redact(textToRedact) {
214
+ return textToRedact.replace(this.regexpMatcher, this.replaceWith);
215
+ }
216
+ };
217
+ exports2.SimpleRegexpRedactor = SimpleRegexpRedactor;
218
+ }
219
+ });
220
+ var require_composition = __commonJS({
221
+ "../redact-pii/dist/composition.js"(exports2) {
222
+ Object.defineProperty(exports2, "__esModule", { value: true });
223
+ exports2.composeChildRedactors = void 0;
224
+ var NameRedactor_1 = require_NameRedactor();
225
+ var simpleRegexpBuiltIns = require_simple_regexp_patterns();
226
+ var SimpleRegexpRedactor_1 = require_SimpleRegexpRedactor();
227
+ var utils_1 = require_utils();
228
+ function normalizeCustomRedactorConfig(redactorConfig) {
229
+ return (0, utils_1.isSimpleRegexpCustomRedactorConfig)(redactorConfig) ? new SimpleRegexpRedactor_1.SimpleRegexpRedactor({
230
+ regexpPattern: redactorConfig.regexpPattern,
231
+ replaceWith: redactorConfig.replaceWith
232
+ }) : redactorConfig;
233
+ }
234
+ function composeChildRedactors(opts = {}) {
235
+ var _a, _b, _c, _d;
236
+ const childRedactors = [];
237
+ if (opts.customRedactors && opts.customRedactors.before) {
238
+ opts.customRedactors.before.map(normalizeCustomRedactorConfig).forEach((redactor2) => childRedactors.push(redactor2));
239
+ }
240
+ for (const regexpName of Object.keys(simpleRegexpBuiltIns)) {
241
+ if (!opts.builtInRedactors || !opts.builtInRedactors[regexpName] || opts.builtInRedactors[regexpName].enabled !== false) {
242
+ childRedactors.push(new SimpleRegexpRedactor_1.SimpleRegexpRedactor({
243
+ regexpPattern: simpleRegexpBuiltIns[regexpName],
244
+ replaceWith: ((_b = (_a = opts.builtInRedactors) === null || _a === void 0 ? void 0 : _a[regexpName]) === null || _b === void 0 ? void 0 : _b.replaceWith) || opts.globalReplaceWith || (0, utils_1.snakeCase)(regexpName).toUpperCase()
245
+ }));
246
+ }
247
+ }
248
+ if (!opts.builtInRedactors || !opts.builtInRedactors.names || opts.builtInRedactors.names.enabled !== false) {
249
+ childRedactors.push(new NameRedactor_1.NameRedactor(((_d = (_c = opts.builtInRedactors) === null || _c === void 0 ? void 0 : _c.names) === null || _d === void 0 ? void 0 : _d.replaceWith) || opts.globalReplaceWith));
250
+ }
251
+ if (opts.customRedactors && opts.customRedactors.after) {
252
+ opts.customRedactors.after.map(normalizeCustomRedactorConfig).forEach((redactor2) => childRedactors.push(redactor2));
253
+ }
254
+ return childRedactors;
255
+ }
256
+ exports2.composeChildRedactors = composeChildRedactors;
257
+ }
258
+ });
259
+ var require_AsyncCompositeRedactor = __commonJS({
260
+ "../redact-pii/dist/AsyncCompositeRedactor.js"(exports2) {
261
+ var __awaiter = exports2 && exports2.__awaiter || function(thisArg, _arguments, P, generator) {
262
+ function adopt(value) {
263
+ return value instanceof P ? value : new P(function(resolve2) {
264
+ resolve2(value);
265
+ });
266
+ }
267
+ return new (P || (P = Promise))(function(resolve2, reject) {
268
+ function fulfilled(value) {
269
+ try {
270
+ step(generator.next(value));
271
+ } catch (e) {
272
+ reject(e);
273
+ }
274
+ }
275
+ function rejected(value) {
276
+ try {
277
+ step(generator["throw"](value));
278
+ } catch (e) {
279
+ reject(e);
280
+ }
281
+ }
282
+ function step(result) {
283
+ result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
284
+ }
285
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
286
+ });
287
+ };
288
+ Object.defineProperty(exports2, "__esModule", { value: true });
289
+ exports2.AsyncCompositeRedactor = void 0;
290
+ var composition_1 = require_composition();
291
+ var utils_1 = require_utils();
292
+ var AsyncCompositeRedactor = class {
293
+ constructor(opts) {
294
+ this.childRedactors = [];
295
+ this.redactAsync = (textToRedact) => __awaiter(this, void 0, void 0, function* () {
296
+ for (const redactor2 of this.childRedactors) {
297
+ if ((0, utils_1.isSyncRedactor)(redactor2)) {
298
+ textToRedact = redactor2.redact(textToRedact);
299
+ } else {
300
+ textToRedact = yield redactor2.redactAsync(textToRedact);
301
+ }
302
+ }
303
+ return textToRedact;
304
+ });
305
+ this.childRedactors = (0, composition_1.composeChildRedactors)(opts);
306
+ }
307
+ };
308
+ exports2.AsyncCompositeRedactor = AsyncCompositeRedactor;
309
+ }
310
+ });
311
+ var require_SyncCompositeRedactor = __commonJS({
312
+ "../redact-pii/dist/SyncCompositeRedactor.js"(exports2) {
313
+ Object.defineProperty(exports2, "__esModule", { value: true });
314
+ exports2.SyncCompositeRedactor = void 0;
315
+ var composition_1 = require_composition();
316
+ var SyncCompositeRedactor = class {
317
+ constructor(opts) {
318
+ this.childRedactors = [];
319
+ this.redact = (textToRedact) => {
320
+ for (const redactor2 of this.childRedactors) {
321
+ textToRedact = redactor2.redact(textToRedact);
322
+ }
323
+ return textToRedact;
324
+ };
325
+ this.childRedactors = (0, composition_1.composeChildRedactors)(opts);
326
+ }
327
+ };
328
+ exports2.SyncCompositeRedactor = SyncCompositeRedactor;
329
+ }
330
+ });
331
+ var require_dist = __commonJS({
332
+ "../redact-pii/dist/index.js"(exports2) {
333
+ Object.defineProperty(exports2, "__esModule", { value: true });
334
+ exports2.SyncRedactor = exports2.AsyncRedactor = void 0;
335
+ var AsyncCompositeRedactor_1 = require_AsyncCompositeRedactor();
336
+ Object.defineProperty(exports2, "AsyncRedactor", { enumerable: true, get: function() {
337
+ return AsyncCompositeRedactor_1.AsyncCompositeRedactor;
338
+ } });
339
+ var SyncCompositeRedactor_1 = require_SyncCompositeRedactor();
340
+ Object.defineProperty(exports2, "SyncRedactor", { enumerable: true, get: function() {
341
+ return SyncCompositeRedactor_1.SyncCompositeRedactor;
342
+ } });
343
+ }
344
+ });
91
345
  var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
92
346
  function state() {
93
347
  const holder = globalThis;
94
348
  let current = holder[STATE_KEY];
95
349
  if (!current) {
96
- let storage = null;
350
+ let storage2 = null;
97
351
  try {
98
352
  const Ctor = globalThis.RAINDROP_ASYNC_LOCAL_STORAGE;
99
- if (Ctor) storage = new Ctor();
353
+ if (Ctor) storage2 = new Ctor();
100
354
  } catch (e) {
101
- storage = null;
355
+ storage2 = null;
102
356
  }
103
357
  current = {
104
- storage,
358
+ storage: storage2,
105
359
  syncDestination: void 0,
106
360
  spanDestinations: /* @__PURE__ */ new WeakMap()
107
361
  };
@@ -249,12 +503,12 @@ var NO_AMBIENT_ROUTING_BIND_WARNING = "[raindrop] This runtime's AsyncLocalStora
249
503
  var ROUTING_STORAGE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.routingContextStorage");
250
504
  function routingStorage() {
251
505
  const holder = globalThis;
252
- let storage = holder[ROUTING_STORAGE_KEY];
253
- if (!storage) {
254
- storage = new ContextStackStorage(NO_AMBIENT_ROUTING_BIND_WARNING);
255
- holder[ROUTING_STORAGE_KEY] = storage;
506
+ let storage2 = holder[ROUTING_STORAGE_KEY];
507
+ if (!storage2) {
508
+ storage2 = new ContextStackStorage(NO_AMBIENT_ROUTING_BIND_WARNING);
509
+ holder[ROUTING_STORAGE_KEY] = storage2;
256
510
  }
257
- return storage;
511
+ return storage2;
258
512
  }
259
513
  function currentRoutingBinding() {
260
514
  const bound = routingStorage().current();
@@ -540,6 +794,14 @@ function appGitPropertiesFromAttributes(attributes) {
540
794
  }
541
795
  return properties;
542
796
  }
797
+ function normalizeFeatureFlags(input) {
798
+ if (Array.isArray(input)) {
799
+ return Object.fromEntries(input.map((name) => [name, "true"]));
800
+ }
801
+ return Object.fromEntries(
802
+ Object.entries(input).map(([k, v]) => [k, String(v)])
803
+ );
804
+ }
543
805
  function getCrypto() {
544
806
  const c = globalThis.crypto;
545
807
  return c;
@@ -641,11 +903,91 @@ function runWithTracingSuppressed(fn) {
641
903
  return fn();
642
904
  }
643
905
  }
906
+ var MAX_QUOTED_ITEMS = 3;
907
+ function isRecord(value) {
908
+ return typeof value === "object" && value !== null && !Array.isArray(value);
909
+ }
910
+ function itemLabel(item) {
911
+ if (typeof item.event_id === "string" && item.event_id) return `event ${item.event_id}`;
912
+ if (typeof item.index === "number") return `item ${item.index}`;
913
+ return "item";
914
+ }
915
+ function describeRejected(item) {
916
+ const issues = Array.isArray(item.issues) ? item.issues : [];
917
+ const text = issues.filter(isRecord).map((issue) => {
918
+ const path = typeof issue.path === "string" && issue.path ? `${issue.path}: ` : "";
919
+ return `${path}${typeof issue.message === "string" ? issue.message : "invalid"}`;
920
+ }).join("; ");
921
+ return `${itemLabel(item)} (${text || "invalid"})`;
922
+ }
923
+ function describeWarning(item) {
924
+ if (typeof item.message === "string" && item.message) return `${itemLabel(item)}: ${item.message}`;
925
+ const fields = Array.isArray(item.fields) ? item.fields.filter((f) => typeof f === "string") : [];
926
+ return `${itemLabel(item)}: ignored ${fields.join(", ") || "input"}`;
927
+ }
928
+ function summarize(items, describe) {
929
+ const quoted = items.slice(0, MAX_QUOTED_ITEMS).map(describe).join(" | ");
930
+ const more = items.length > MAX_QUOTED_ITEMS ? ` (+${items.length - MAX_QUOTED_ITEMS} more)` : "";
931
+ return `${quoted}${more}`;
932
+ }
933
+ function reportIngestResponse(bodyText, prefix, opName) {
934
+ if (!bodyText) return;
935
+ let body;
936
+ try {
937
+ body = JSON.parse(bodyText);
938
+ } catch (e) {
939
+ return;
940
+ }
941
+ if (!isRecord(body)) return;
942
+ const rejected = Array.isArray(body.rejected) ? body.rejected.filter(isRecord) : [];
943
+ if (rejected.length > 0) {
944
+ rateLimitedLog(
945
+ `${prefix}.ingest_rejected`,
946
+ () => console.warn(
947
+ `${prefix} ${opName}: ingest rejected ${rejected.length} item(s), which were not stored: ${summarize(rejected, describeRejected)}`
948
+ )
949
+ );
950
+ }
951
+ const warnings = Array.isArray(body.warnings) ? body.warnings.filter(isRecord) : [];
952
+ if (warnings.length > 0) {
953
+ rateLimitedLog(
954
+ `${prefix}.ingest_warnings`,
955
+ () => console.warn(
956
+ `${prefix} ${opName}: ingest accepted ${warnings.length} item(s) with changes: ${summarize(warnings, describeWarning)}`
957
+ )
958
+ );
959
+ }
960
+ }
961
+ var DEFAULT_FLUSH_TIMEOUT_MS = 1e4;
644
962
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
645
963
  var MAX_RETRY_DELAY_MS = 3e4;
646
964
  function wait(ms) {
647
965
  return new Promise((resolve2) => setTimeout(resolve2, ms));
648
966
  }
967
+ function waitUnlessAborted(ms, signal) {
968
+ if (!signal) return wait(ms);
969
+ if (signal.aborted) return Promise.resolve();
970
+ return new Promise((resolve2) => {
971
+ const done = () => {
972
+ clearTimeout(timer);
973
+ signal.removeEventListener("abort", done);
974
+ resolve2();
975
+ };
976
+ const timer = setTimeout(done, ms);
977
+ signal.addEventListener("abort", done, { once: true });
978
+ });
979
+ }
980
+ function eitherSignal(a, b) {
981
+ if (!b) return a;
982
+ const any = AbortSignal.any;
983
+ if (typeof any === "function") return any([a, b]);
984
+ const controller = new AbortController();
985
+ const abort = () => controller.abort();
986
+ if (a.aborted || b.aborted) abort();
987
+ a.addEventListener("abort", abort, { once: true });
988
+ b.addEventListener("abort", abort, { once: true });
989
+ return controller.signal;
990
+ }
649
991
  function formatEndpoint(endpoint) {
650
992
  if (!endpoint) return void 0;
651
993
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
@@ -690,6 +1032,26 @@ async function raceWithTimeout(promise, timeoutMs) {
690
1032
  if (timer) clearTimeout(timer);
691
1033
  return settledInTime;
692
1034
  }
1035
+ var InFlightCutOff = class {
1036
+ constructor() {
1037
+ this.controller = new AbortController();
1038
+ }
1039
+ get signal() {
1040
+ return this.controller.signal;
1041
+ }
1042
+ cut() {
1043
+ this.controller.abort();
1044
+ this.controller = new AbortController();
1045
+ }
1046
+ };
1047
+ async function awaitEarlierSends(earlier, deadlineAt, cutOff, isRunning) {
1048
+ if (earlier.length === 0) return { settled: true, stillRunning: 0 };
1049
+ const settled = await raceWithTimeout(Promise.allSettled(earlier), deadlineAt - Date.now());
1050
+ if (settled) return { settled, stillRunning: 0 };
1051
+ const stillRunning = earlier.filter(isRunning).length;
1052
+ if (stillRunning > 0) cutOff.cut();
1053
+ return { settled: stillRunning === 0, stillRunning };
1054
+ }
693
1055
  function parseRetryAfter(headers) {
694
1056
  var _a;
695
1057
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -713,45 +1075,80 @@ function getRetryDelayMs(attemptNumber, previousError) {
713
1075
  const factor = Math.pow(2, attemptNumber - 2);
714
1076
  return Math.min(base * factor, MAX_RETRY_DELAY_MS);
715
1077
  }
716
- async function withRetry(operation, opName, opts) {
717
- const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
718
- let lastError = void 0;
719
- for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
720
- if (attemptNumber > 1) {
721
- const delay = getRetryDelayMs(attemptNumber, lastError);
722
- if (opts.debug) {
723
- console.warn(
724
- `${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
725
- );
726
- }
727
- if (delay > 0) await wait(delay);
728
- } else if (opts.debug) {
729
- console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
730
- }
731
- try {
732
- return await operation();
733
- } catch (err) {
734
- lastError = err;
735
- if (opts.debug) {
736
- const msg = err instanceof Error ? err.message : String(err);
737
- console.warn(
738
- `${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
739
- );
740
- }
741
- if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
742
- break;
743
- if (attemptNumber === opts.maxAttempts) break;
744
- }
1078
+ var sendPausedUntil = /* @__PURE__ */ new Map();
1079
+ function originOf(url) {
1080
+ try {
1081
+ return new URL(url).origin;
1082
+ } catch (e) {
1083
+ return url;
1084
+ }
1085
+ }
1086
+ function noteSendPause(url, status, retryAfterMs) {
1087
+ var _a;
1088
+ if (status !== 429 && status !== 503 || typeof retryAfterMs !== "number") return;
1089
+ const until = Date.now() + Math.min(Math.max(0, retryAfterMs), MAX_RETRY_DELAY_MS);
1090
+ const origin = originOf(url);
1091
+ sendPausedUntil.set(origin, Math.max((_a = sendPausedUntil.get(origin)) != null ? _a : 0, until));
1092
+ }
1093
+ function pausedUntil(url) {
1094
+ const until = sendPausedUntil.get(originOf(url));
1095
+ if (until === void 0) return void 0;
1096
+ if (until <= Date.now()) {
1097
+ sendPausedUntil.delete(originOf(url));
1098
+ return void 0;
745
1099
  }
746
- throw lastError instanceof Error ? lastError : new Error(String(lastError));
1100
+ return Math.min(until, Date.now() + MAX_RETRY_DELAY_MS);
1101
+ }
1102
+ function prefixFor(opts) {
1103
+ return opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
747
1104
  }
748
1105
  async function postJson(url, body, headers, opts) {
1106
+ var _a;
1107
+ const outcome = await sendJson(url, body, headers, { ...opts, deadlineAt: void 0 });
1108
+ if (outcome.status === "delivered") return;
1109
+ throw (_a = outcome.error) != null ? _a : new Error(`${redactUrlForLog(url)}: not sent (${outcome.status})`);
1110
+ }
1111
+ async function sendJson(url, body, headers, opts) {
749
1112
  var _a;
750
1113
  const opName = `POST ${redactUrlForLog(url)}`;
1114
+ const prefix = prefixFor(opts);
751
1115
  const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
752
- await withRetry(
753
- async () => {
754
- const resp = await runWithTracingSuppressed(
1116
+ const deadlineAt = opts.deadlineAt;
1117
+ const remaining = () => deadlineAt === void 0 ? Infinity : deadlineAt - Date.now();
1118
+ let lastError;
1119
+ const cutOff = opts.cutOff;
1120
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
1121
+ if (cutOff == null ? void 0 : cutOff.aborted) return { status: "deferred", error: lastError };
1122
+ if (attempt > 1) {
1123
+ const delay = getRetryDelayMs(attempt, lastError);
1124
+ if (delay >= remaining()) {
1125
+ return { status: "deferred", retryAtMs: Date.now() + delay, error: lastError };
1126
+ }
1127
+ if (opts.debug) {
1128
+ console.warn(`${prefix} ${opName} retry ${attempt}/${opts.maxAttempts} in ${delay}ms`);
1129
+ }
1130
+ if (delay > 0) await waitUnlessAborted(delay, cutOff);
1131
+ if (cutOff == null ? void 0 : cutOff.aborted) return { status: "deferred", error: lastError };
1132
+ } else if (opts.debug) {
1133
+ console.log(`${prefix} ${opName} attempt ${attempt}/${opts.maxAttempts}`);
1134
+ }
1135
+ const pauseEnd = pausedUntil(url);
1136
+ if (pauseEnd !== void 0) {
1137
+ if (deadlineAt !== void 0 && pauseEnd >= deadlineAt) {
1138
+ return { status: "deferred", retryAtMs: pauseEnd, error: lastError };
1139
+ }
1140
+ await waitUnlessAborted(Math.max(0, pauseEnd - Date.now()), cutOff);
1141
+ if (cutOff == null ? void 0 : cutOff.aborted) return { status: "deferred", retryAtMs: pauseEnd, error: lastError };
1142
+ }
1143
+ const left = remaining();
1144
+ if (left <= 0) return { status: "deferred", error: lastError };
1145
+ const attemptTimeoutMs = Math.min(timeoutMs, left);
1146
+ const clampedByDeadline = attemptTimeoutMs < timeoutMs;
1147
+ const timeoutSignal = AbortSignal.timeout(attemptTimeoutMs);
1148
+ const signal = eitherSignal(timeoutSignal, cutOff);
1149
+ let resp;
1150
+ try {
1151
+ resp = await runWithTracingSuppressed(
755
1152
  () => fetch(url, {
756
1153
  method: "POST",
757
1154
  redirect: headers["X-Raindrop-Replay-Id"] !== void 0 ? "error" : "follow",
@@ -760,24 +1157,98 @@ async function postJson(url, body, headers, opts) {
760
1157
  ...headers
761
1158
  },
762
1159
  body: JSON.stringify(body),
763
- signal: AbortSignal.timeout(timeoutMs)
1160
+ signal
764
1161
  })
765
1162
  );
766
- if (!resp.ok) {
767
- const text = await resp.text().catch(() => "");
768
- const err = new Error(
769
- `HTTP ${resp.status} ${resp.statusText}${text ? `: ${text}` : ""}`
770
- );
771
- const retryAfterMs = parseRetryAfter(resp.headers);
772
- if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
773
- err.retryable = resp.status === 429 || resp.status >= 500;
774
- throw err;
1163
+ } catch (err) {
1164
+ const error = err instanceof Error ? err : new Error(String(err));
1165
+ if ((cutOff == null ? void 0 : cutOff.aborted) || timeoutSignal.aborted && clampedByDeadline) {
1166
+ return { status: "cut_off", error };
775
1167
  }
776
- },
777
- opName,
778
- opts
1168
+ lastError = error;
1169
+ logAttemptFailure(opts, prefix, opName, attempt, error);
1170
+ continue;
1171
+ }
1172
+ if (!resp.ok) {
1173
+ const text = await resp.text().catch(() => "");
1174
+ const error = new Error(
1175
+ `HTTP ${resp.status} ${resp.statusText}${text ? `: ${text}` : ""}`
1176
+ );
1177
+ const retryAfterMs = parseRetryAfter(resp.headers);
1178
+ if (typeof retryAfterMs === "number") error.retryAfterMs = retryAfterMs;
1179
+ noteSendPause(url, resp.status, retryAfterMs);
1180
+ error.retryable = resp.status === 408 || resp.status === 429 || resp.status >= 500;
1181
+ if (!error.retryable) return { status: "rejected", error };
1182
+ lastError = error;
1183
+ logAttemptFailure(opts, prefix, opName, attempt, error);
1184
+ continue;
1185
+ }
1186
+ try {
1187
+ reportIngestResponse(await resp.text(), prefix, opName);
1188
+ } catch (e) {
1189
+ }
1190
+ return { status: "delivered" };
1191
+ }
1192
+ return { status: "failed", error: lastError != null ? lastError : new Error(`${opName}: no attempts`) };
1193
+ }
1194
+ function logAttemptFailure(opts, prefix, opName, attempt, error) {
1195
+ if (!opts.debug) return;
1196
+ console.warn(
1197
+ `${prefix} ${opName} attempt ${attempt} failed: ${error.message}${attempt === opts.maxAttempts ? " (no more retries)" : ""}`
1198
+ );
1199
+ }
1200
+ function asCompat(result) {
1201
+ return result;
1202
+ }
1203
+ var DELIVERED = Object.freeze({
1204
+ delivered: true,
1205
+ timedOut: false,
1206
+ dropped: 0,
1207
+ queued: 0,
1208
+ inFlight: 0
1209
+ });
1210
+ function fateOf(outcome, shuttingDown) {
1211
+ switch (outcome.status) {
1212
+ case "delivered":
1213
+ case "rejected":
1214
+ return "delivered";
1215
+ case "deferred":
1216
+ return shuttingDown ? "dropped" : "queued";
1217
+ default:
1218
+ return "dropped";
1219
+ }
1220
+ }
1221
+ function summarize2(fates, inFlight, timedOut) {
1222
+ const dropped = fates.filter((fate) => fate === "dropped").length;
1223
+ const queued = fates.filter((fate) => fate === "queued").length;
1224
+ return {
1225
+ delivered: dropped === 0 && queued === 0 && inFlight === 0,
1226
+ timedOut: timedOut || queued > 0 || inFlight > 0,
1227
+ dropped,
1228
+ queued,
1229
+ inFlight
1230
+ };
1231
+ }
1232
+ function combineResults(...results) {
1233
+ return results.reduce(
1234
+ (acc, r) => ({
1235
+ delivered: acc.delivered && r.delivered,
1236
+ timedOut: acc.timedOut || r.timedOut,
1237
+ dropped: acc.dropped + r.dropped,
1238
+ queued: acc.queued + r.queued,
1239
+ inFlight: acc.inFlight + r.inFlight
1240
+ }),
1241
+ { ...DELIVERED }
779
1242
  );
780
1243
  }
1244
+ function describeUndelivered(what, result) {
1245
+ const parts = [
1246
+ result.dropped > 0 ? `${result.dropped} dropped` : "",
1247
+ result.queued > 0 ? `${result.queued} still queued` : "",
1248
+ result.inFlight > 0 ? `${result.inFlight} still sending` : ""
1249
+ ].filter(Boolean);
1250
+ return `${what} returned before everything was delivered: ${parts.join(", ")}`;
1251
+ }
781
1252
  var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
782
1253
  var TRUNCATION_MARKER = "...[truncated by raindrop]";
783
1254
  var BOUNDED_CLONE_MAX_DEPTH = 12;
@@ -1044,7 +1515,245 @@ function normalizeProjectId(raw, opts) {
1044
1515
  function projectIdHeaders(projectId) {
1045
1516
  return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
1046
1517
  }
1047
- var SHUTDOWN_DEADLINE_MS = 1e4;
1518
+ var import_redact_pii = __toESM(require_dist(), 1);
1519
+ var DEFAULT_PII_REDACTION_OPTIONS = {
1520
+ builtInRedactors: {
1521
+ credentials: { replaceWith: "<REDACTED CREDENTIALS>" },
1522
+ creditCardNumber: { replaceWith: "<REDACTED CREDIT CARD NUMBER>" },
1523
+ // Any run of 4+ digits: too broad.
1524
+ digits: { enabled: false },
1525
+ emailAddress: { replaceWith: "<REDACTED EMAIL>" },
1526
+ ipAddress: { enabled: false },
1527
+ names: { enabled: false },
1528
+ // Matches (pass(word|phrase)|secret): \S+
1529
+ password: { replaceWith: "<REDACTED SECRET>" },
1530
+ phoneNumber: { replaceWith: "<REDACTED PHONE NUMBER>" },
1531
+ streetAddress: { replaceWith: "<REDACTED ADDRESS>" },
1532
+ url: { enabled: false },
1533
+ username: { enabled: false },
1534
+ usSocialSecurityNumber: { replaceWith: "<REDACTED SOCIAL SECURITY NUMBER>" },
1535
+ zipcode: { enabled: false }
1536
+ }
1537
+ };
1538
+ var redactor;
1539
+ function redactPiiText(text) {
1540
+ redactor != null ? redactor : redactor = new import_redact_pii.SyncRedactor(DEFAULT_PII_REDACTION_OPTIONS);
1541
+ return redactor.redact(text);
1542
+ }
1543
+ var KINDS = ["event", "signal", "identify"];
1544
+ var DEFAULT_PII_FIELDS = {
1545
+ event: ["ai_data.input", "ai_data.output"],
1546
+ signal: [],
1547
+ identify: []
1548
+ };
1549
+ var PROTECTED_LOCATIONS = new Set(
1550
+ [
1551
+ ["event_id"],
1552
+ ["user_id"],
1553
+ ["ai_data", "convo_id"],
1554
+ ["event"],
1555
+ ["timestamp"],
1556
+ ["is_pending"],
1557
+ ["signal_name"],
1558
+ ["signal_type"]
1559
+ ].map((keys) => JSON.stringify(keys))
1560
+ );
1561
+ var SIGNAL_PATH_ALIASES = {
1562
+ comment: "properties.comment",
1563
+ after: "properties.after"
1564
+ };
1565
+ function parsePiiFieldPath(path) {
1566
+ if (typeof path !== "string") return null;
1567
+ const segments = [];
1568
+ let i = 0;
1569
+ const n = path.length;
1570
+ let expectSeparator = false;
1571
+ while (i < n) {
1572
+ const ch = path[i];
1573
+ if (ch === "[") {
1574
+ const close = path.indexOf("]", i);
1575
+ if (path[i + 1] === '"') {
1576
+ let j2 = i + 2;
1577
+ let key = "";
1578
+ while (j2 < n && path[j2] !== '"') {
1579
+ if (path[j2] === "\\" && j2 + 1 < n) {
1580
+ key += path[j2 + 1];
1581
+ j2 += 2;
1582
+ } else {
1583
+ key += path[j2];
1584
+ j2 += 1;
1585
+ }
1586
+ }
1587
+ if (j2 >= n || path[j2 + 1] !== "]" || key === "") return null;
1588
+ segments.push({ kind: "key", key });
1589
+ i = j2 + 2;
1590
+ } else {
1591
+ if (close === -1) return null;
1592
+ const inner = path.slice(i + 1, close);
1593
+ if (inner === "") segments.push({ kind: "every" });
1594
+ else if (/^(0|[1-9]\d*)$/.test(inner))
1595
+ segments.push({ kind: "index", index: Number(inner) });
1596
+ else return null;
1597
+ i = close + 1;
1598
+ }
1599
+ expectSeparator = true;
1600
+ continue;
1601
+ }
1602
+ if (ch === ".") {
1603
+ if (!expectSeparator) return null;
1604
+ i += 1;
1605
+ expectSeparator = false;
1606
+ if (i >= n || path[i] === ".") return null;
1607
+ continue;
1608
+ }
1609
+ if (expectSeparator) return null;
1610
+ let j = i;
1611
+ while (j < n && path[j] !== "." && path[j] !== "[") j += 1;
1612
+ const token = path.slice(i, j);
1613
+ if (token === "*") segments.push({ kind: "any_key" });
1614
+ else if (token === "" || /[\]*"\s]/.test(token)) return null;
1615
+ else segments.push({ kind: "key", key: token });
1616
+ i = j;
1617
+ expectSeparator = true;
1618
+ }
1619
+ return segments.length > 0 && expectSeparator ? segments : null;
1620
+ }
1621
+ function mergePiiFieldMaps(...maps) {
1622
+ const out = { event: [], signal: [], identify: [] };
1623
+ for (const kind of KINDS) {
1624
+ const seen = /* @__PURE__ */ new Set();
1625
+ for (const map of maps) {
1626
+ const paths = map == null ? void 0 : map[kind];
1627
+ if (!Array.isArray(paths)) continue;
1628
+ for (const path of paths) {
1629
+ if (typeof path !== "string" || seen.has(path)) continue;
1630
+ seen.add(path);
1631
+ out[kind].push(path);
1632
+ }
1633
+ }
1634
+ }
1635
+ return out;
1636
+ }
1637
+ function isPlainObject(value) {
1638
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1639
+ const proto = Object.getPrototypeOf(value);
1640
+ return proto === Object.prototype || proto === null;
1641
+ }
1642
+ function isProtected(location) {
1643
+ return PROTECTED_LOCATIONS.has(JSON.stringify(location));
1644
+ }
1645
+ function redactAll(value, location) {
1646
+ if (location !== null && isProtected(location)) return value;
1647
+ if (typeof value === "string") return redactPiiText(value);
1648
+ if (Array.isArray(value)) return value.map((item) => redactAll(item, null));
1649
+ if (isPlainObject(value)) {
1650
+ const out = {};
1651
+ for (const [key, child] of Object.entries(value)) {
1652
+ out[key] = redactAll(child, location === null ? null : [...location, key]);
1653
+ }
1654
+ return out;
1655
+ }
1656
+ return value;
1657
+ }
1658
+ function applyPath(value, segments, location) {
1659
+ if (segments.length === 0) return redactAll(value, location);
1660
+ const [head, ...rest] = segments;
1661
+ if (head.kind === "key" || head.kind === "any_key") {
1662
+ if (!isPlainObject(value)) return value;
1663
+ const keys = head.kind === "key" ? [head.key] : Object.keys(value);
1664
+ let out2;
1665
+ for (const key of keys) {
1666
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
1667
+ const child = value[key];
1668
+ const next = applyPath(child, rest, location === null ? null : [...location, key]);
1669
+ if (next !== child) {
1670
+ out2 != null ? out2 : out2 = { ...value };
1671
+ out2[key] = next;
1672
+ }
1673
+ }
1674
+ return out2 != null ? out2 : value;
1675
+ }
1676
+ if (!Array.isArray(value)) return value;
1677
+ const indices = head.kind === "index" ? head.index < value.length ? [head.index] : [] : value.keys();
1678
+ let out;
1679
+ for (const index of indices) {
1680
+ const child = value[index];
1681
+ const next = applyPath(child, rest, null);
1682
+ if (next !== child) {
1683
+ out != null ? out : out = [...value];
1684
+ out[index] = next;
1685
+ }
1686
+ }
1687
+ return out != null ? out : value;
1688
+ }
1689
+ var PiiFieldRedactor = class {
1690
+ constructor(options = {}) {
1691
+ var _a;
1692
+ this.startupMap = options.startupFieldMap;
1693
+ this.prefix = (_a = options.logPrefix) != null ? _a : "[raindrop-ai/core]";
1694
+ }
1695
+ /** Replace the server-provided layer (CONTRACT 11.2). Additive: it can only add fields. */
1696
+ setServerFieldMap(map) {
1697
+ this.serverMap = map;
1698
+ this.compiled = void 0;
1699
+ }
1700
+ /** The paths in effect for a kind, defaults first. */
1701
+ effectivePaths(kind) {
1702
+ return mergePiiFieldMaps(DEFAULT_PII_FIELDS, this.startupMap, this.serverMap)[kind];
1703
+ }
1704
+ compile() {
1705
+ if (this.compiled) return this.compiled;
1706
+ const out = { event: [], signal: [], identify: [] };
1707
+ for (const kind of KINDS) {
1708
+ for (const path of this.effectivePaths(kind)) {
1709
+ const variants = [path];
1710
+ if (kind === "signal" && SIGNAL_PATH_ALIASES[path])
1711
+ variants.push(SIGNAL_PATH_ALIASES[path]);
1712
+ for (const variant of variants) {
1713
+ const segments = parsePiiFieldPath(variant);
1714
+ if (segments) {
1715
+ out[kind].push(segments);
1716
+ } else {
1717
+ rateLimitedLog(
1718
+ `${this.prefix}.pii_field_path_invalid`,
1719
+ () => console.warn(
1720
+ `${this.prefix} redactPiiFields: ignoring invalid ${kind} path ${JSON.stringify(variant)}`
1721
+ )
1722
+ );
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ this.compiled = out;
1728
+ return out;
1729
+ }
1730
+ /** The payload with every selected field redacted (a copy where changed). */
1731
+ redact(kind, payload) {
1732
+ let out = payload;
1733
+ for (const segments of this.compile()[kind]) out = applyPath(out, segments, []);
1734
+ return out;
1735
+ }
1736
+ };
1737
+ function detectRuntime() {
1738
+ if (typeof process === "object" && process && typeof process.env === "object" && process.env && typeof process.version === "string") {
1739
+ return "node";
1740
+ }
1741
+ if (typeof window === "object") return "browser";
1742
+ if (typeof WebSocketPair !== "undefined") return "cloudflare-worker";
1743
+ if (typeof EdgeRuntime === "string") return "vercel-edge";
1744
+ if (typeof WorkerGlobalScope !== "undefined" && typeof importScripts === "function") {
1745
+ return "web-worker";
1746
+ }
1747
+ return "unknown";
1748
+ }
1749
+ var PLACEHOLDER_USER_IDS = /* @__PURE__ */ new Set(["null", "undefined"]);
1750
+ function isMissingUserId(value) {
1751
+ if (value === void 0 || value === null) return true;
1752
+ if (typeof value !== "string") return false;
1753
+ const trimmed = value.trim();
1754
+ return trimmed === "" || PLACEHOLDER_USER_IDS.has(trimmed);
1755
+ }
1756
+ var MAX_DEFERRED_SENDS = 1e3;
1048
1757
  var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
1049
1758
  function appGitOwnerId(prefix) {
1050
1759
  try {
@@ -1053,18 +1762,52 @@ function appGitOwnerId(prefix) {
1053
1762
  return `${prefix}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
1054
1763
  }
1055
1764
  }
1765
+ function earliestIsoTimestamp(...values) {
1766
+ let earliest;
1767
+ for (const value of values) {
1768
+ if (value === void 0) continue;
1769
+ const date = new Date(value);
1770
+ if (Number.isNaN(date.getTime())) continue;
1771
+ if (earliest === void 0 || date < earliest) earliest = date;
1772
+ }
1773
+ return earliest == null ? void 0 : earliest.toISOString();
1774
+ }
1775
+ function isPlainObject2(value) {
1776
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1777
+ const proto = Object.getPrototypeOf(value);
1778
+ return proto === Object.prototype || proto === null;
1779
+ }
1780
+ var UNSAFE_MERGE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1781
+ function deepMergeProperties(target, source) {
1782
+ const out = { ...target };
1783
+ for (const [key, value] of Object.entries(source)) {
1784
+ if (value === void 0 || UNSAFE_MERGE_KEYS.has(key)) continue;
1785
+ const existing = out[key];
1786
+ out[key] = isPlainObject2(existing) && isPlainObject2(value) ? deepMergeProperties(existing, value) : value;
1787
+ }
1788
+ return out;
1789
+ }
1056
1790
  function mergePatches(target, source) {
1057
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1058
- const out = { ...target, ...source };
1791
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1792
+ const defined2 = Object.fromEntries(
1793
+ Object.entries(source).filter(([, value]) => value !== void 0)
1794
+ );
1795
+ const out = { ...target, ...defined2 };
1059
1796
  if (target.properties || source.properties) {
1060
- out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
1797
+ out.properties = deepMergeProperties((_a = target.properties) != null ? _a : {}, (_b = source.properties) != null ? _b : {});
1798
+ }
1799
+ if (target.callerProperties || source.callerProperties) {
1800
+ out.callerProperties = deepMergeProperties(
1801
+ (_c = target.callerProperties) != null ? _c : {},
1802
+ (_d = source.callerProperties) != null ? _d : {}
1803
+ );
1061
1804
  }
1062
1805
  if (target.usage || source.usage) {
1063
- out.usage = { ...(_c = target.usage) != null ? _c : {} };
1064
- if (((_d = source.usage) == null ? void 0 : _d.promptTokens) !== void 0) {
1806
+ out.usage = { ...(_e = target.usage) != null ? _e : {} };
1807
+ if (((_f = source.usage) == null ? void 0 : _f.promptTokens) !== void 0) {
1065
1808
  out.usage.promptTokens = source.usage.promptTokens;
1066
1809
  }
1067
- if (((_e = source.usage) == null ? void 0 : _e.completionTokens) !== void 0) {
1810
+ if (((_g = source.usage) == null ? void 0 : _g.completionTokens) !== void 0) {
1068
1811
  out.usage.completionTokens = source.usage.completionTokens;
1069
1812
  }
1070
1813
  }
@@ -1072,10 +1815,10 @@ function mergePatches(target, source) {
1072
1815
  out.error = target.error;
1073
1816
  }
1074
1817
  if (target.featureFlags || source.featureFlags) {
1075
- out.featureFlags = { ...(_f = target.featureFlags) != null ? _f : {}, ...(_g = source.featureFlags) != null ? _g : {} };
1818
+ out.featureFlags = { ...(_h = target.featureFlags) != null ? _h : {}, ...(_i = source.featureFlags) != null ? _i : {} };
1076
1819
  }
1077
1820
  if (target.attachments || source.attachments) {
1078
- out.attachments = [...(_h = target.attachments) != null ? _h : [], ...(_i = source.attachments) != null ? _i : []];
1821
+ out.attachments = [...(_j = target.attachments) != null ? _j : [], ...(_k = source.attachments) != null ? _k : []];
1079
1822
  }
1080
1823
  return out;
1081
1824
  }
@@ -1100,6 +1843,15 @@ function eventTelemetryProperties(patch) {
1100
1843
  }
1101
1844
  return properties;
1102
1845
  }
1846
+ var FINISHED_EVENT_MEMORY = 1e4;
1847
+ var MANUAL_UPDATES = /* @__PURE__ */ new WeakSet();
1848
+ function markManualUpdate(patch) {
1849
+ try {
1850
+ MANUAL_UPDATES.add(patch);
1851
+ } catch (e) {
1852
+ }
1853
+ return patch;
1854
+ }
1103
1855
  var EventShipper = class {
1104
1856
  constructor(opts) {
1105
1857
  this.appGitOwner = appGitOwnerId("event");
@@ -1107,6 +1859,12 @@ var EventShipper = class {
1107
1859
  this.sticky = /* @__PURE__ */ new Map();
1108
1860
  this.timers = /* @__PURE__ */ new Map();
1109
1861
  this.inFlight = /* @__PURE__ */ new Set();
1862
+ this.background = /* @__PURE__ */ new Set();
1863
+ this.backgroundDrops = 0;
1864
+ this.cutOff = new InFlightCutOff();
1865
+ this.finishedEventIds = /* @__PURE__ */ new Set();
1866
+ this.sendChains = /* @__PURE__ */ new Map();
1867
+ this.deferredSends = /* @__PURE__ */ new Set();
1110
1868
  this.hasShutdown = false;
1111
1869
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1112
1870
  this.appGitContext = (_a = opts.appGitContext) != null ? _a : createAppGitContext(opts.appGit);
@@ -1120,6 +1878,7 @@ var EventShipper = class {
1120
1878
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
1121
1879
  this.defaultEventName = (_f = opts.defaultEventName) != null ? _f : "ai_generation";
1122
1880
  this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
1881
+ this.flushTimeoutMs = resolveFlushTimeoutMs(opts.flushTimeoutMs);
1123
1882
  this.localDebuggerUrl = (_g = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _g : void 0;
1124
1883
  if (this.debug && this.localDebuggerUrl) {
1125
1884
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
@@ -1128,15 +1887,29 @@ var EventShipper = class {
1128
1887
  debug: this.debug,
1129
1888
  prefix: this.prefix
1130
1889
  });
1131
- const isNode = typeof process !== "undefined" && typeof process.version === "string";
1890
+ this.redactPii = opts.redactPii === true;
1891
+ this.piiFields = new PiiFieldRedactor({
1892
+ startupFieldMap: opts.redactPiiFields,
1893
+ logPrefix: this.prefix
1894
+ });
1895
+ if (opts.redactPiiFields && !this.redactPii) {
1896
+ rateLimitedLog(
1897
+ `${this.prefix}.pii_fields_without_redaction`,
1898
+ () => console.warn(
1899
+ `${this.prefix} redactPiiFields is set but redactPii is off; nothing is redacted. Set redactPii: true to use it.`
1900
+ )
1901
+ );
1902
+ }
1903
+ this.wizardSession = opts.wizardSession || void 0;
1904
+ const runtime = detectRuntime();
1132
1905
  this.context = {
1133
1906
  library: {
1134
1907
  name: (_h = opts.libraryName) != null ? _h : "@raindrop-ai/core",
1135
1908
  version: (_i = opts.libraryVersion) != null ? _i : "0.0.0"
1136
1909
  },
1137
1910
  metadata: {
1138
- jsRuntime: isNode ? "node" : "web",
1139
- ...isNode ? { nodeVersion: process.version } : {}
1911
+ jsRuntime: runtime,
1912
+ ...runtime === "node" ? { nodeVersion: process.version } : {}
1140
1913
  }
1141
1914
  };
1142
1915
  }
@@ -1153,28 +1926,21 @@ var EventShipper = class {
1153
1926
  return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
1154
1927
  }
1155
1928
  /**
1156
- * Build the retry/timeout options for one POST, honoring the shutdown
1157
- * deadline. Returns `null` when the shutdown drain window is exhausted —
1158
- * the caller must drop the payload (with a rate-limited warning) instead
1159
- * of issuing a request that could outlive process exit.
1160
- *
1161
- * Checked fresh on EVERY send, so a shutdown that begins while the flush
1162
- * path is mid-drain takes effect immediately: no further retries, and the
1163
- * per-attempt timeout is clamped to the remaining window. After
1164
- * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
1165
- * sends — late callers, or flush work the deadline abandoned mid-drain —
1166
- * run as a single short attempt rather than regaining the full retry
1167
- * schedule.
1929
+ * Retry/timeout options for one POST. While `shutdown()` drains, sends retry
1930
+ * within its window (deadline = the shutdown deadline); a flush passes its
1931
+ * own deadline. `null` when the shutdown window is spent: the caller drops
1932
+ * the payload with a warning. After `shutdown()` returns, late sends get a
1933
+ * single short attempt rather than the full retry schedule.
1168
1934
  */
1169
- requestOpts() {
1935
+ requestOpts(deadlineAt) {
1170
1936
  if (this.shutdownDeadlineAt !== void 0) {
1171
- const remainingMs = this.shutdownDeadlineAt - Date.now();
1172
- if (remainingMs <= 0) return null;
1937
+ if (this.shutdownDeadlineAt - Date.now() <= 0) return null;
1173
1938
  return {
1174
- maxAttempts: 1,
1939
+ maxAttempts: 3,
1175
1940
  debug: this.debug,
1176
1941
  sdkName: this.sdkName,
1177
- timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1942
+ deadlineAt: Math.min(this.shutdownDeadlineAt, deadlineAt != null ? deadlineAt : Infinity),
1943
+ cutOff: this.cutOff.signal
1178
1944
  };
1179
1945
  }
1180
1946
  if (this.hasShutdown) {
@@ -1182,15 +1948,94 @@ var EventShipper = class {
1182
1948
  maxAttempts: 1,
1183
1949
  debug: this.debug,
1184
1950
  sdkName: this.sdkName,
1185
- timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1951
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS,
1952
+ cutOff: this.cutOff.signal
1186
1953
  };
1187
1954
  }
1188
- return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1955
+ return {
1956
+ maxAttempts: 3,
1957
+ debug: this.debug,
1958
+ sdkName: this.sdkName,
1959
+ deadlineAt,
1960
+ cutOff: this.cutOff.signal
1961
+ };
1962
+ }
1963
+ shuttingDown() {
1964
+ return this.shutdownDeadlineAt !== void 0 || this.hasShutdown;
1189
1965
  }
1190
1966
  async patch(eventId, patch) {
1191
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1192
- if (!this.enabled) return;
1193
- if (!eventId || !eventId.trim()) return;
1967
+ await this.patchWithFate(eventId, patch);
1968
+ }
1969
+ /**
1970
+ * Queue an update without waiting for any network send: for integrations,
1971
+ * whose captured call must return as soon as the update is queued (rule C9,
1972
+ * hot path). A finishing update starts its send in the background; `flush()`
1973
+ * and `shutdown()` wait for it within their deadline and count it in their
1974
+ * result. Never throws or rejects.
1975
+ */
1976
+ enqueue(eventId, patch) {
1977
+ let op;
1978
+ try {
1979
+ op = this.patchWithFate(eventId, patch).catch((err) => {
1980
+ this.warnBackgroundFailure(eventId, err);
1981
+ return "dropped";
1982
+ }).then((fate) => {
1983
+ if (fate === "dropped") this.backgroundDrops += 1;
1984
+ return fate;
1985
+ });
1986
+ } catch (err) {
1987
+ this.warnBackgroundFailure(eventId, err);
1988
+ return;
1989
+ }
1990
+ this.background.add(op);
1991
+ void op.finally(() => this.background.delete(op));
1992
+ }
1993
+ /** `finish()` without waiting for the send; see `enqueue`. */
1994
+ enqueueFinish(eventId, patch = {}) {
1995
+ this.enqueue(eventId, { ...patch, isPending: false });
1996
+ }
1997
+ /**
1998
+ * Caller properties win over everything the integration derived (W6). A
1999
+ * clash is intended by the caller, so it is only logged in debug mode.
2000
+ */
2001
+ withCallerProperties(eventId, derived, caller) {
2002
+ if (!caller || Object.keys(caller).length === 0) return derived;
2003
+ if (this.debug) {
2004
+ try {
2005
+ const keys = overriddenKeys(derived, caller);
2006
+ if (keys.length > 0) {
2007
+ console.log(
2008
+ `${this.prefix} event ${eventId}: caller properties override the integration's value for ${keys.map((k) => `"${k}"`).join(", ")}`
2009
+ );
2010
+ }
2011
+ } catch (e) {
2012
+ }
2013
+ }
2014
+ return deepMergeProperties(derived, caller);
2015
+ }
2016
+ warnBackgroundFailure(eventId, err) {
2017
+ rateLimitedLog(
2018
+ `${this.prefix}.background_update`,
2019
+ () => console.warn(`${this.prefix} update for event ${eventId} failed:`, err)
2020
+ );
2021
+ }
2022
+ /**
2023
+ * Id of the last event an integration finished in this process (manual
2024
+ * events API finishes excluded): what integrations expose as `lastEventId`
2025
+ * (wrapper profile W10).
2026
+ */
2027
+ get lastFinishedEventId() {
2028
+ return this.lastFinished;
2029
+ }
2030
+ async patchWithFate(eventId, patch) {
2031
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2032
+ const manual = MANUAL_UPDATES.has(patch);
2033
+ if (!this.enabled) return void 0;
2034
+ if (!eventId || !eventId.trim()) return void 0;
2035
+ if (patch.userId !== void 0 && isMissingUserId(patch.userId)) {
2036
+ const { userId: _placeholder, ...rest } = patch;
2037
+ patch = rest;
2038
+ }
1194
2039
  const capturedSticky = this.sticky.get(eventId);
1195
2040
  const eventAlreadyKnown = capturedSticky !== void 0 || this.buffers.has(eventId);
1196
2041
  const destination = currentReplayTraceDestination();
@@ -1198,7 +2043,7 @@ var EventShipper = class {
1198
2043
  if ((capturedSticky == null ? void 0 : capturedSticky.replayOnly) === true || !eventAlreadyKnown && cloudReplay) {
1199
2044
  if (patch.isPending === false) this.sticky.delete(eventId);
1200
2045
  else this.sticky.set(eventId, { ...capturedSticky, replayOnly: true });
1201
- return;
2046
+ return void 0;
1202
2047
  }
1203
2048
  const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
1204
2049
  if (typeof patch.input === "string" && patch.input.length > maxChars) {
@@ -1221,11 +2066,12 @@ var EventShipper = class {
1221
2066
  }
1222
2067
  const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
1223
2068
  const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
2069
+ const explicitProperties = patch.callerProperties !== void 0 ? { ...patch.properties, ...patch.callerProperties } : patch.properties;
1224
2070
  patch = {
1225
2071
  ...patch,
1226
2072
  properties: withAppGitProperties(
1227
2073
  patch.properties,
1228
- this.appGitContext.forOperation(eventId, patch.properties, this.appGitOwner)
2074
+ this.appGitContext.forOperation(eventId, explicitProperties, this.appGitOwner)
1229
2075
  )
1230
2076
  };
1231
2077
  const merged = mergePatches(existing, patch);
@@ -1235,47 +2081,165 @@ var EventShipper = class {
1235
2081
  userId: (_h = merged.userId) != null ? _h : sticky.userId,
1236
2082
  convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
1237
2083
  eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
1238
- isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
2084
+ isPending: (_k = merged.isPending) != null ? _k : sticky.isPending,
2085
+ startedAt: (_l = earliestIsoTimestamp(sticky.startedAt, patch.timestamp)) != null ? _l : (/* @__PURE__ */ new Date()).toISOString(),
2086
+ resumed: sticky.resumed,
2087
+ hasContent: sticky.hasContent === true || patchHasContent(patch),
2088
+ callerProperties: patch.callerProperties !== void 0 ? deepMergeProperties((_m = sticky.callerProperties) != null ? _m : {}, patch.callerProperties) : sticky.callerProperties
1239
2089
  });
1240
2090
  const t = this.timers.get(eventId);
1241
2091
  if (t) clearTimeout(t);
1242
2092
  if (merged.isPending === false) {
1243
- await this.flushOne(eventId);
2093
+ if (!manual) this.lastFinished = eventId;
2094
+ this.noteFinish(eventId);
2095
+ this.noteEmptyFinish(eventId);
2096
+ const fate = await this.flushOne(eventId);
1244
2097
  if (this.appGitContext.releaseOperation) this.appGitContext.releaseOperation(eventId, this.appGitOwner);
1245
2098
  else this.appGitContext.finishOperation(eventId);
1246
- return;
2099
+ return fate;
1247
2100
  }
1248
2101
  const timeout = setTimeout(() => {
1249
2102
  void this.flushOne(eventId).catch(() => {
1250
2103
  });
1251
2104
  }, this.partialFlushMs);
1252
2105
  this.timers.set(eventId, timeout);
2106
+ return void 0;
2107
+ }
2108
+ /**
2109
+ * A second finish for an event is still sent (ingest finalizes once and
2110
+ * treats it as an update, CONTRACT §10.2), but it usually means several
2111
+ * calls share one event id by mistake, so it is warned about.
2112
+ */
2113
+ noteFinish(eventId) {
2114
+ if (this.finishedEventIds.has(eventId)) {
2115
+ rateLimitedLog(
2116
+ `${this.prefix}.repeated_finish`,
2117
+ () => console.warn(
2118
+ `${this.prefix} event ${eventId} was already finished in this process; sending the update anyway (ingest finalizes an event once). Use a separate event id per interaction, or keep the event pending until its last call.`
2119
+ )
2120
+ );
2121
+ return;
2122
+ }
2123
+ this.finishedEventIds.add(eventId);
2124
+ if (this.finishedEventIds.size > FINISHED_EVENT_MEMORY) {
2125
+ const oldest = this.finishedEventIds.values().next().value;
2126
+ if (oldest !== void 0) this.finishedEventIds.delete(oldest);
2127
+ }
2128
+ }
2129
+ /**
2130
+ * An event finished with no input, output or attachments is almost always a
2131
+ * caller bug (CONTRACT §14.2, rule C5); it is sent anyway and warned about.
2132
+ * Not for a resumed event: its content may have been sent by another process.
2133
+ */
2134
+ noteEmptyFinish(eventId) {
2135
+ const sticky = this.sticky.get(eventId);
2136
+ if ((sticky == null ? void 0 : sticky.resumed) === true || (sticky == null ? void 0 : sticky.hasContent) === true) return;
2137
+ rateLimitedLog(
2138
+ `${this.prefix}.empty_finish`,
2139
+ () => console.warn(
2140
+ `${this.prefix} event ${eventId} finished with no input, output or attachments; sending it anyway. Pass the interaction's input at begin or its output at finish.`
2141
+ )
2142
+ );
2143
+ }
2144
+ /**
2145
+ * Continue an event that was begun elsewhere (another process, a queue
2146
+ * worker, a later request) using only its id. No request is sent; later
2147
+ * `patch` / `finish` calls for this id are sent even without a `userId`,
2148
+ * and ingest keeps the user, conversation and start time it already has.
2149
+ * Pass `userId` / `convoId` / `eventName` if you have them.
2150
+ */
2151
+ resume(eventId, options = {}) {
2152
+ var _a, _b, _c;
2153
+ if (!this.enabled) return;
2154
+ if (!eventId || !eventId.trim()) return;
2155
+ const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
2156
+ this.sticky.set(eventId, {
2157
+ ...sticky,
2158
+ userId: isMissingUserId(options.userId) ? sticky.userId : options.userId,
2159
+ convoId: (_b = options.convoId) != null ? _b : sticky.convoId,
2160
+ eventName: (_c = options.eventName) != null ? _c : sticky.eventName,
2161
+ resumed: true
2162
+ });
1253
2163
  }
1254
2164
  async finish(eventId, patch) {
1255
- await this.patch(eventId, { ...patch, isPending: false });
2165
+ const update = { ...patch, isPending: false };
2166
+ if (MANUAL_UPDATES.has(patch)) MANUAL_UPDATES.add(update);
2167
+ await this.patch(eventId, update);
1256
2168
  }
2169
+ /**
2170
+ * Send everything buffered, open (pending) events included, within
2171
+ * `flushTimeoutMs` (CONTRACT 1.2.1). Resolves, never rejects, to whether it
2172
+ * all reached ingest; when it didn't, it also warns. At the deadline,
2173
+ * payloads not yet sent stay queued for a later send and a request cut off
2174
+ * mid-flight is dropped. Sends already running when it was called are
2175
+ * waited for up to the deadline and then cut off.
2176
+ */
1257
2177
  async flush() {
1258
- if (!this.enabled) return;
2178
+ const result = await this.drainAll(Date.now() + this.flushTimeoutMs);
2179
+ if (!result.delivered && this.shutdownDeadlineAt === void 0) {
2180
+ rateLimitedLog(
2181
+ `${this.prefix}.flush_undelivered`,
2182
+ () => console.warn(`${this.prefix} ${describeUndelivered("flush()", result)}`)
2183
+ );
2184
+ }
2185
+ return asCompat(result);
2186
+ }
2187
+ async drainAll(deadlineAt) {
2188
+ if (!this.enabled) return { ...DELIVERED };
2189
+ const deferred = [...this.deferredSends];
2190
+ this.deferredSends.clear();
2191
+ for (const entry of deferred) if (entry.timer) clearTimeout(entry.timer);
1259
2192
  const ids = [...this.buffers.keys()];
1260
- await Promise.all(ids.map((id) => this.flushOne(id)));
1261
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1262
- })));
2193
+ const fates = await Promise.all([
2194
+ ...ids.map((id) => this.flushOne(id, deadlineAt)),
2195
+ ...deferred.map((entry) => entry.send(deadlineAt))
2196
+ ]);
2197
+ const background = [...this.background];
2198
+ if (background.length > 0) {
2199
+ await raceWithTimeout(Promise.all(background), deadlineAt - Date.now());
2200
+ }
2201
+ for (; this.backgroundDrops > 0; this.backgroundDrops -= 1) fates.push("dropped");
2202
+ const running = [...this.inFlight];
2203
+ const { settled, stillRunning } = await awaitEarlierSends(
2204
+ running,
2205
+ deadlineAt,
2206
+ this.cutOff,
2207
+ (p) => this.inFlight.has(p)
2208
+ );
2209
+ const unresolved = background.filter((op) => this.background.has(op)).length;
2210
+ return summarize2(
2211
+ fates.filter((fate) => fate !== void 0),
2212
+ Math.max(stillRunning, unresolved),
2213
+ !settled || unresolved > 0
2214
+ );
1263
2215
  }
2216
+ /**
2217
+ * Flush within `flushTimeoutMs` and stop. Sends retry while the window
2218
+ * allows; whatever is left at the deadline is dropped with a warning, so a
2219
+ * dead network can never wedge process exit. Resolves, never rejects.
2220
+ */
1264
2221
  async shutdown() {
1265
2222
  this.hasShutdown = true;
1266
- this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
2223
+ const deadlineAt = Date.now() + this.flushTimeoutMs;
2224
+ this.shutdownDeadlineAt = deadlineAt;
2225
+ let result = { ...DELIVERED };
1267
2226
  try {
1268
2227
  for (const t of this.timers.values()) clearTimeout(t);
1269
2228
  this.timers.clear();
1270
2229
  for (const [eventId, sticky] of this.sticky) {
1271
2230
  if (sticky.replayOnly) this.sticky.delete(eventId);
1272
2231
  }
1273
- const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
1274
- if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
2232
+ result = await this.drainAll(deadlineAt);
2233
+ if (!result.delivered) {
2234
+ this.warnShutdownDrop(
2235
+ `${result.dropped + result.queued + result.inFlight} payload(s) not delivered`
2236
+ );
2237
+ }
1275
2238
  } finally {
1276
2239
  this.shutdownDeadlineAt = void 0;
1277
2240
  if (this.ownsAppGitContext) this.appGitContext.dispose();
1278
2241
  }
2242
+ return asCompat(result);
1279
2243
  }
1280
2244
  async trackSignal(signal) {
1281
2245
  var _a, _b;
@@ -1296,23 +2260,9 @@ var EventShipper = class {
1296
2260
  ...signal.after ? { after: signal.after } : {}
1297
2261
  }
1298
2262
  }
1299
- ];
2263
+ ].map((item) => this.redactFields("signal", item));
1300
2264
  if (!this.writeKey) return;
1301
- const url = `${this.baseUrl}signals/track`;
1302
- const opts = this.requestOpts();
1303
- if (!opts) {
1304
- this.warnShutdownDrop("signal");
1305
- return;
1306
- }
1307
- try {
1308
- await postJson(url, body, this.requestHeaders(), opts);
1309
- } catch (err) {
1310
- const msg = err instanceof Error ? err.message : String(err);
1311
- rateLimitedLog(
1312
- `${this.prefix}.send_signal_failed`,
1313
- () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
1314
- );
1315
- }
2265
+ await this.sendDirect("signal", `${this.baseUrl}signals/track`, body);
1316
2266
  }
1317
2267
  async identify(users) {
1318
2268
  if (!this.enabled) return;
@@ -1327,30 +2277,80 @@ var EventShipper = class {
1327
2277
  return false;
1328
2278
  }
1329
2279
  return true;
1330
- }).map((user) => {
1331
- var _a;
1332
- return {
1333
- user_id: user.userId,
1334
- traits: (_a = user.traits) != null ? _a : {}
1335
- };
1336
- });
2280
+ }).map(
2281
+ (user) => {
2282
+ var _a;
2283
+ return this.redactFields("identify", {
2284
+ user_id: user.userId,
2285
+ traits: (_a = user.traits) != null ? _a : {}
2286
+ });
2287
+ }
2288
+ );
1337
2289
  if (!this.writeKey) return;
1338
2290
  if (body.length === 0) return;
1339
- const url = `${this.baseUrl}users/identify`;
1340
- const opts = this.requestOpts();
2291
+ await this.sendDirect("identify", `${this.baseUrl}users/identify`, body);
2292
+ }
2293
+ /**
2294
+ * Send a signal or identify body. Tracked so `flush()` waits for it. A send
2295
+ * a flush deadline deferred is kept and retried later (outside shutdown).
2296
+ */
2297
+ async sendDirect(kind, url, body, deadlineAt) {
2298
+ const opts = this.requestOpts(deadlineAt);
1341
2299
  if (!opts) {
1342
- this.warnShutdownDrop("identify");
1343
- return;
2300
+ this.warnShutdownDrop(kind);
2301
+ return "dropped";
1344
2302
  }
2303
+ const sending = sendJson(url, body, this.requestHeaders(), opts);
2304
+ const tracked = sending.then(() => void 0);
2305
+ this.inFlight.add(tracked);
2306
+ let outcome;
1345
2307
  try {
1346
- await postJson(url, body, this.requestHeaders(), opts);
1347
- } catch (err) {
1348
- const msg = err instanceof Error ? err.message : String(err);
2308
+ outcome = await sending;
2309
+ } finally {
2310
+ this.inFlight.delete(tracked);
2311
+ }
2312
+ const fate = fateOf(outcome, this.shuttingDown());
2313
+ if (fate === "queued") {
2314
+ this.deferSend(
2315
+ (later) => this.sendDirect(kind, url, body, later),
2316
+ outcome.status === "deferred" ? outcome.retryAtMs : void 0
2317
+ );
2318
+ } else if (fate === "dropped") {
2319
+ this.warnSendDropped(kind, outcome);
2320
+ }
2321
+ return fate;
2322
+ }
2323
+ deferSend(send, retryAtMs) {
2324
+ var _a, _b;
2325
+ if (this.deferredSends.size >= MAX_DEFERRED_SENDS) {
1349
2326
  rateLimitedLog(
1350
- `${this.prefix}.send_identify_failed`,
1351
- () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
2327
+ `${this.prefix}.deferred_full`,
2328
+ () => console.warn(`${this.prefix} too many sends waiting for a retry; dropping one`)
1352
2329
  );
2330
+ return;
1353
2331
  }
2332
+ const entry = { send };
2333
+ const delay = Math.max(this.partialFlushMs, (retryAtMs != null ? retryAtMs : 0) - Date.now());
2334
+ entry.timer = setTimeout(() => {
2335
+ if (!this.deferredSends.delete(entry)) return;
2336
+ void send().catch(() => {
2337
+ });
2338
+ }, delay);
2339
+ (_b = (_a = entry.timer).unref) == null ? void 0 : _b.call(_a);
2340
+ this.deferredSends.add(entry);
2341
+ }
2342
+ warnSendDropped(what, outcome) {
2343
+ var _a, _b;
2344
+ if (outcome.status === "delivered" || outcome.status === "rejected") return;
2345
+ const reason = outcome.status === "cut_off" ? "cut off at the flush deadline (not resent: it may have reached ingest)" : outcome.status === "deferred" ? "not sent before the shutdown deadline" : (_b = (_a = outcome.error) == null ? void 0 : _a.message) != null ? _b : outcome.status;
2346
+ rateLimitedLog(
2347
+ `${this.prefix}.send_${what}_dropped`,
2348
+ () => console.warn(`${this.prefix} failed to send ${what} (dropping): ${reason}`)
2349
+ );
2350
+ }
2351
+ /** The payload with its PII fields redacted when `redactPii` is on (CONTRACT 11.2). */
2352
+ redactFields(kind, payload) {
2353
+ return this.redactPii ? this.piiFields.redact(kind, payload) : payload;
1354
2354
  }
1355
2355
  warnShutdownDrop(what) {
1356
2356
  rateLimitedLog(
@@ -1358,9 +2358,8 @@ var EventShipper = class {
1358
2358
  () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
1359
2359
  );
1360
2360
  }
1361
- async flushOne(eventId) {
1362
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1363
- if (!this.enabled) return;
2361
+ async flushOne(eventId, deadlineAt) {
2362
+ if (!this.enabled) return void 0;
1364
2363
  const timer = this.timers.get(eventId);
1365
2364
  if (timer) {
1366
2365
  clearTimeout(timer);
@@ -1368,30 +2367,91 @@ var EventShipper = class {
1368
2367
  }
1369
2368
  const accumulated = this.buffers.get(eventId);
1370
2369
  this.buffers.delete(eventId);
1371
- if (!accumulated) return;
2370
+ if (!accumulated) return void 0;
2371
+ const previous = this.sendChains.get(eventId);
2372
+ let release = () => {
2373
+ };
2374
+ const current = new Promise((resolve2) => {
2375
+ release = resolve2;
2376
+ });
2377
+ this.sendChains.set(eventId, current);
2378
+ try {
2379
+ if (previous) {
2380
+ const ready = deadlineAt === void 0 ? (await previous, true) : await raceWithTimeout(previous, deadlineAt - Date.now());
2381
+ if (!ready) {
2382
+ return this.requeue(eventId, accumulated, void 0);
2383
+ }
2384
+ }
2385
+ return await this.sendAccumulated(eventId, accumulated, deadlineAt);
2386
+ } finally {
2387
+ release();
2388
+ if (this.sendChains.get(eventId) === current) this.sendChains.delete(eventId);
2389
+ }
2390
+ }
2391
+ /**
2392
+ * Put an unsent update back under any newer one and send it later
2393
+ * (CONTRACT 1.2.1). During shutdown it is dropped instead.
2394
+ */
2395
+ requeue(eventId, accumulated, retryAtMs) {
2396
+ var _a, _b;
2397
+ if (this.shuttingDown()) {
2398
+ this.warnShutdownDrop(`track_partial ${eventId}`);
2399
+ if (accumulated.isPending === false) this.sticky.delete(eventId);
2400
+ return "dropped";
2401
+ }
2402
+ const newer = this.buffers.get(eventId);
2403
+ const merged = newer ? mergePatches(accumulated, newer) : accumulated;
2404
+ merged.isPending = (_a = newer == null ? void 0 : newer.isPending) != null ? _a : accumulated.isPending;
2405
+ this.buffers.set(eventId, merged);
2406
+ const existing = this.timers.get(eventId);
2407
+ if (existing) clearTimeout(existing);
2408
+ const delay = Math.max(this.partialFlushMs, (retryAtMs != null ? retryAtMs : 0) - Date.now());
2409
+ const timeout = setTimeout(() => {
2410
+ void this.flushOne(eventId).catch(() => {
2411
+ });
2412
+ }, delay);
2413
+ (_b = timeout.unref) == null ? void 0 : _b.call(timeout);
2414
+ this.timers.set(eventId, timeout);
2415
+ return "queued";
2416
+ }
2417
+ async sendAccumulated(eventId, accumulated, deadlineAt) {
2418
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
1372
2419
  const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
1373
2420
  const isPending = (_c = (_b = accumulated.isPending) != null ? _b : sticky.isPending) != null ? _c : true;
1374
- const eventName = (_e = (_d = accumulated.eventName) != null ? _d : sticky.eventName) != null ? _e : this.defaultEventName;
1375
- const userId = (_f = accumulated.userId) != null ? _f : sticky.userId;
1376
- if (!userId) {
1377
- if (this.debug) {
1378
- console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
1379
- }
2421
+ const knownEventName = (_d = accumulated.eventName) != null ? _d : sticky.eventName;
2422
+ const eventName = knownEventName != null ? knownEventName : sticky.resumed ? void 0 : this.defaultEventName;
2423
+ const userId = (_e = accumulated.userId) != null ? _e : sticky.userId;
2424
+ if (!userId && !sticky.resumed) {
2425
+ rateLimitedLog(
2426
+ `${this.prefix}.track_partial_missing_user`,
2427
+ () => console.warn(
2428
+ `${this.prefix} skipping track_partial for ${eventId}: missing userId (pass userId, or call resume(eventId) to continue an event begun elsewhere)`
2429
+ )
2430
+ );
1380
2431
  this.sticky.delete(eventId);
1381
- return;
2432
+ return void 0;
1382
2433
  }
1383
- const { wizardSession, ...restProperties } = (_g = accumulated.properties) != null ? _g : {};
2434
+ if (!userId) {
2435
+ rateLimitedLog(
2436
+ `${this.prefix}.track_partial_resumed_without_user`,
2437
+ () => console.warn(
2438
+ `${this.prefix} sending track_partial for ${eventId} without a userId; ingest will use the user stored for this event (pass userId to resume() to set it explicitly)`
2439
+ )
2440
+ );
2441
+ }
2442
+ const { wizardSession, ...restProperties } = (_f = accumulated.properties) != null ? _f : {};
1384
2443
  let telemetryProperties = {};
1385
2444
  try {
1386
2445
  telemetryProperties = eventTelemetryProperties(accumulated);
1387
2446
  } catch (e) {
1388
2447
  }
1389
- const convoId = (_h = accumulated.convoId) != null ? _h : sticky.convoId;
1390
- const payload = {
2448
+ const convoId = (_g = accumulated.convoId) != null ? _g : sticky.convoId;
2449
+ const sessionTag = wizardSession != null ? wizardSession : this.wizardSession;
2450
+ const unredacted = {
1391
2451
  event_id: eventId,
1392
- user_id: userId,
1393
- event: eventName,
1394
- timestamp: (_i = accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
2452
+ ...userId ? { user_id: userId } : {},
2453
+ ...eventName ? { event: eventName } : {},
2454
+ timestamp: (_i = (_h = sticky.startedAt) != null ? _h : accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
1395
2455
  ai_data: {
1396
2456
  input: accumulated.input,
1397
2457
  output: accumulated.error !== void 0 && accumulated.error !== null ? void 0 : accumulated.output,
@@ -1399,32 +2459,38 @@ var EventShipper = class {
1399
2459
  convo_id: convoId
1400
2460
  },
1401
2461
  properties: {
1402
- ...restProperties,
1403
- ...telemetryProperties,
1404
- ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
2462
+ ...this.withCallerProperties(
2463
+ eventId,
2464
+ { ...restProperties, ...telemetryProperties },
2465
+ (_j = sticky.callerProperties) != null ? _j : accumulated.callerProperties
2466
+ ),
2467
+ ...sessionTag ? { "raindrop.wizardSession": sessionTag } : {},
1405
2468
  $context: this.context
1406
2469
  },
1407
2470
  ...accumulated.featureFlags && Object.keys(accumulated.featureFlags).length > 0 ? { feature_flags: accumulated.featureFlags } : {},
1408
2471
  attachments: accumulated.attachments,
1409
2472
  is_pending: isPending
1410
2473
  };
2474
+ const payload = this.redactFields("event", unredacted);
1411
2475
  const url = `${this.baseUrl}events/track_partial`;
1412
2476
  if (this.debug) {
2477
+ const sent = payload;
2478
+ const preview = (v, n) => typeof v === "string" ? v.slice(0, n) : void 0;
1413
2479
  console.log(`${this.prefix} sending track_partial`, {
1414
2480
  eventId,
1415
2481
  eventName,
1416
2482
  userId,
1417
2483
  convoId,
1418
2484
  isPending,
1419
- inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
1420
- outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
1421
- attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
1422
- attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
2485
+ inputPreview: preview((_k = sent.ai_data) == null ? void 0 : _k.input, 120),
2486
+ outputPreview: preview((_l = sent.ai_data) == null ? void 0 : _l.output, 120),
2487
+ attachments: (_n = (_m = sent.attachments) == null ? void 0 : _m.length) != null ? _n : 0,
2488
+ attachmentKinds: (_p = (_o = sent.attachments) == null ? void 0 : _o.map((a) => ({
1423
2489
  type: a.type,
1424
2490
  role: a.role,
1425
2491
  name: a.name,
1426
- valuePreview: a.value.slice(0, 60)
1427
- }))) != null ? _m : [],
2492
+ valuePreview: preview(a.value, 60)
2493
+ }))) != null ? _p : [],
1428
2494
  endpoint: url
1429
2495
  });
1430
2496
  }
@@ -1437,39 +2503,94 @@ var EventShipper = class {
1437
2503
  if (!this.writeKey) {
1438
2504
  if (!isPending) this.sticky.delete(eventId);
1439
2505
  if (mirror) await mirror;
1440
- return;
2506
+ return void 0;
1441
2507
  }
1442
- const opts = this.requestOpts();
2508
+ const opts = this.requestOpts(deadlineAt);
1443
2509
  if (!opts) {
1444
2510
  this.warnShutdownDrop(`track_partial ${eventId}`);
1445
2511
  if (!isPending) this.sticky.delete(eventId);
1446
2512
  if (mirror) await mirror;
1447
- return;
2513
+ return "dropped";
1448
2514
  }
1449
- const p = postJson(url, payload, this.requestHeaders(), opts);
1450
- const combined = mirror ? Promise.all([mirror, p]).then(() => void 0) : p;
2515
+ const sending = sendJson(url, payload, this.requestHeaders(), opts);
2516
+ const combined = Promise.all([mirror, sending.then(() => void 0)]).then(() => void 0);
1451
2517
  this.inFlight.add(combined);
2518
+ let outcome;
1452
2519
  try {
1453
- try {
1454
- await combined;
1455
- if (this.debug) {
1456
- console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
1457
- }
1458
- } catch (err) {
1459
- const msg = err instanceof Error ? err.message : String(err);
1460
- rateLimitedLog(
1461
- `${this.prefix}.send_track_partial_failed`,
1462
- () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
1463
- );
1464
- }
2520
+ outcome = await sending;
2521
+ if (mirror) await mirror.catch(() => {
2522
+ });
1465
2523
  } finally {
1466
2524
  this.inFlight.delete(combined);
1467
2525
  }
2526
+ const fate = fateOf(outcome, this.shuttingDown());
2527
+ if (fate === "queued") {
2528
+ return this.requeue(
2529
+ eventId,
2530
+ accumulated,
2531
+ outcome.status === "deferred" ? outcome.retryAtMs : void 0
2532
+ );
2533
+ }
2534
+ if (fate === "dropped") this.warnSendDropped("track_partial", outcome);
2535
+ else if (this.debug && outcome.status === "delivered") {
2536
+ console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
2537
+ }
1468
2538
  if (!isPending) {
1469
2539
  this.sticky.delete(eventId);
1470
2540
  }
2541
+ return fate;
1471
2542
  }
1472
2543
  };
2544
+ function overriddenKeys(derived, caller) {
2545
+ const keys = [];
2546
+ for (const [key, value] of Object.entries(caller)) {
2547
+ if (value === void 0 || !(key in derived)) continue;
2548
+ const existing = derived[key];
2549
+ if (isPlainObject2(existing) && isPlainObject2(value)) {
2550
+ if (overriddenKeys(existing, value).length > 0) keys.push(key);
2551
+ } else if (JSON.stringify(existing) !== JSON.stringify(value)) {
2552
+ keys.push(key);
2553
+ }
2554
+ }
2555
+ return keys;
2556
+ }
2557
+ function patchHasContent(patch) {
2558
+ var _a, _b;
2559
+ return typeof patch.input === "string" && patch.input.length > 0 || typeof patch.output === "string" && patch.output.length > 0 || ((_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
2560
+ }
2561
+ function resolveFlushTimeoutMs(value) {
2562
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : DEFAULT_FLUSH_TIMEOUT_MS;
2563
+ }
2564
+ function createEventsApi(shipper) {
2565
+ return {
2566
+ async patch(eventId, patch) {
2567
+ await (shipper == null ? void 0 : shipper.patch(eventId, markManualUpdate(asCallerPatch(patch))));
2568
+ },
2569
+ async finish(eventId, patch = {}) {
2570
+ await (shipper == null ? void 0 : shipper.finish(eventId, markManualUpdate(asCallerPatch(patch))));
2571
+ },
2572
+ async addAttachments(eventId, attachments) {
2573
+ await (shipper == null ? void 0 : shipper.patch(eventId, markManualUpdate({ attachments })));
2574
+ },
2575
+ async setProperties(eventId, properties) {
2576
+ await (shipper == null ? void 0 : shipper.patch(eventId, markManualUpdate({ properties, callerProperties: properties })));
2577
+ },
2578
+ async setFeatureFlags(eventId, featureFlags) {
2579
+ await (shipper == null ? void 0 : shipper.patch(eventId, markManualUpdate({ featureFlags: normalizeFeatureFlags(featureFlags) })));
2580
+ },
2581
+ resume(eventId, options) {
2582
+ shipper == null ? void 0 : shipper.resume(eventId, options);
2583
+ }
2584
+ };
2585
+ }
2586
+ function asCallerPatch(patch) {
2587
+ var _a;
2588
+ if (!patch.properties) return patch;
2589
+ return {
2590
+ ...patch,
2591
+ callerProperties: { ...patch.properties, ...(_a = patch.callerProperties) != null ? _a : {} }
2592
+ };
2593
+ }
1473
2594
  var MODEL_USAGE_ATTRIBUTES = {
1474
2595
  usageSource: "raindrop.usage.source",
1475
2596
  providerName: "gen_ai.provider.name",
@@ -1529,6 +2650,21 @@ function buildRawModelUsageAttributes(usage) {
1529
2650
  attrDouble(MODEL_USAGE_ATTRIBUTES.providerReportedCost, providerReportedCost)
1530
2651
  ].filter((attribute) => attribute !== void 0);
1531
2652
  }
2653
+ var KNOWN_MODEL_PROVIDERS = /* @__PURE__ */ new Set([
2654
+ ...MODEL_PROVIDER_NAMES.map(([, family]) => family),
2655
+ "vercel",
2656
+ // Further vendors Dawn's pricing resolves (apps/dawn/lib/spend).
2657
+ "deepseek",
2658
+ "fireworks-ai",
2659
+ "groq",
2660
+ "inception",
2661
+ "mistral",
2662
+ "moonshotai",
2663
+ "perplexity",
2664
+ "togetherai",
2665
+ "xai",
2666
+ "zai"
2667
+ ]);
1532
2668
  var STRING_ALIASES = {
1533
2669
  [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
1534
2670
  };
@@ -1781,6 +2917,7 @@ var TraceShipper = class {
1781
2917
  this.appGitAdded = /* @__PURE__ */ new WeakMap();
1782
2918
  this.queue = [];
1783
2919
  this.inFlight = /* @__PURE__ */ new Set();
2920
+ this.cutOff = new InFlightCutOff();
1784
2921
  this.hasShutdown = false;
1785
2922
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1786
2923
  this.appGitContext = (_a = opts.appGitContext) != null ? _a : createAppGitContext(opts.appGit);
@@ -1793,6 +2930,7 @@ var TraceShipper = class {
1793
2930
  this.flushIntervalMs = (_d = opts.flushIntervalMs) != null ? _d : 1e3;
1794
2931
  this.maxBatchSize = (_e = opts.maxBatchSize) != null ? _e : 50;
1795
2932
  this.maxQueueSize = (_f = opts.maxQueueSize) != null ? _f : 5e3;
2933
+ this.flushTimeoutMs = resolveFlushTimeoutMs(opts.flushTimeoutMs);
1796
2934
  this.sdkName = (_g = opts.sdkName) != null ? _g : "core";
1797
2935
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
1798
2936
  this.serviceName = (_h = opts.serviceName) != null ? _h : "raindrop.core";
@@ -2071,25 +3209,51 @@ var TraceShipper = class {
2071
3209
  }
2072
3210
  this.queue.push(redacted);
2073
3211
  if (this.queue.length >= this.maxBatchSize) {
2074
- void this.flush().catch(() => {
3212
+ void this.drain().catch(() => {
2075
3213
  });
2076
3214
  return;
2077
3215
  }
2078
- if (!this.timer) {
2079
- this.timer = setTimeout(() => {
2080
- this.timer = void 0;
2081
- void this.flush().catch(() => {
2082
- });
2083
- }, this.flushIntervalMs);
2084
- }
3216
+ this.scheduleDrain(this.flushIntervalMs);
3217
+ }
3218
+ scheduleDrain(delayMs) {
3219
+ if (this.timer) return;
3220
+ this.timer = setTimeout(() => {
3221
+ this.timer = void 0;
3222
+ void this.drain().catch(() => {
3223
+ });
3224
+ }, delayMs);
2085
3225
  }
3226
+ /**
3227
+ * Send every queued span within `flushTimeoutMs` (CONTRACT 1.2.1). Resolves,
3228
+ * never rejects, to whether they all reached ingest, and warns when they
3229
+ * didn't. Spans not yet sent at the deadline stay queued.
3230
+ */
2086
3231
  async flush() {
2087
- var _a;
2088
- if (!this.enabled) {
2089
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
2090
- })));
2091
- return;
3232
+ const result = await this.drainWithin(Date.now() + this.flushTimeoutMs);
3233
+ if (!result.delivered && this.shutdownDeadlineAt === void 0) {
3234
+ rateLimitedLog(
3235
+ `${this.prefix}.flush_undelivered`,
3236
+ () => console.warn(`${this.prefix} ${describeUndelivered("flush()", result)}`)
3237
+ );
2092
3238
  }
3239
+ return asCompat(result);
3240
+ }
3241
+ async drainWithin(deadlineAt) {
3242
+ const fates = this.enabled ? await this.drain(deadlineAt) : [];
3243
+ const running = [...this.inFlight];
3244
+ const { settled, stillRunning } = await awaitEarlierSends(
3245
+ running,
3246
+ deadlineAt,
3247
+ this.cutOff,
3248
+ (p) => this.inFlight.has(p)
3249
+ );
3250
+ return summarize2(fates, stillRunning, !settled);
3251
+ }
3252
+ /** Send queued batches; with a deadline, a deferred batch goes back on the queue. */
3253
+ async drain(deadlineAt) {
3254
+ var _a;
3255
+ const fates = [];
3256
+ if (!this.enabled) return fates;
2093
3257
  if (this.timer) {
2094
3258
  clearTimeout(this.timer);
2095
3259
  this.timer = void 0;
@@ -2103,10 +3267,12 @@ var TraceShipper = class {
2103
3267
  if (spans) spans.push(span);
2104
3268
  else groups.set(destination, [span]);
2105
3269
  }
3270
+ const requeue = [];
3271
+ let retryAtMs;
2106
3272
  for (const [destination, spans] of groups) {
2107
3273
  if (!destination && !this.writeKey) continue;
2108
3274
  if ((destination == null ? void 0 : destination.kind) === "workshop") continue;
2109
- const opts = this.requestOpts();
3275
+ const opts = this.requestOpts(deadlineAt);
2110
3276
  if (!opts) {
2111
3277
  rateLimitedLog(
2112
3278
  `${this.prefix}.shutdown_deadline`,
@@ -2114,6 +3280,7 @@ var TraceShipper = class {
2114
3280
  `${this.prefix} shutdown flush deadline exceeded; dropping ${spans.length} spans`
2115
3281
  )
2116
3282
  );
3283
+ fates.push("dropped");
2117
3284
  continue;
2118
3285
  }
2119
3286
  const body = buildExportTraceServiceRequest(spans, this.serviceName, this.serviceVersion);
@@ -2129,35 +3296,56 @@ var TraceShipper = class {
2129
3296
  endpoint: url
2130
3297
  });
2131
3298
  }
2132
- const p = postJson(url, body, headers, opts);
2133
- this.inFlight.add(p);
3299
+ const sending = sendJson(url, body, headers, opts);
3300
+ const tracked = sending.then(() => void 0);
3301
+ this.inFlight.add(tracked);
3302
+ let outcome;
2134
3303
  try {
2135
- try {
2136
- await p;
2137
- if (this.debug) console.log(`${this.prefix} sent ${spans.length} spans`);
2138
- } catch (err) {
2139
- const msg = err instanceof Error ? err.message : String(err);
2140
- rateLimitedLog(
2141
- `${this.prefix}.send_spans_failed`,
2142
- () => console.warn(`${this.prefix} failed to send ${spans.length} spans: ${msg}`)
2143
- );
2144
- }
3304
+ outcome = await sending;
2145
3305
  } finally {
2146
- this.inFlight.delete(p);
3306
+ this.inFlight.delete(tracked);
3307
+ }
3308
+ const fate = fateOf(outcome, this.shutdownDeadlineAt !== void 0 || this.hasShutdown);
3309
+ fates.push(fate);
3310
+ if (fate === "queued") {
3311
+ requeue.push(...spans);
3312
+ if (outcome.status === "deferred" && outcome.retryAtMs !== void 0) {
3313
+ retryAtMs = Math.max(retryAtMs != null ? retryAtMs : 0, outcome.retryAtMs);
3314
+ }
3315
+ } else if (fate === "dropped") {
3316
+ const reason = outcome.status === "cut_off" ? "cut off at the flush deadline (not resent: it may have reached ingest)" : outcome.status === "deferred" ? "not sent before the shutdown deadline" : "error" in outcome && outcome.error ? outcome.error.message : outcome.status;
3317
+ rateLimitedLog(
3318
+ `${this.prefix}.send_spans_failed`,
3319
+ () => console.warn(`${this.prefix} failed to send ${spans.length} spans: ${reason}`)
3320
+ );
3321
+ } else if (this.debug) {
3322
+ console.log(`${this.prefix} sent ${spans.length} spans`);
2147
3323
  }
2148
3324
  }
3325
+ if (requeue.length > 0) {
3326
+ this.queue.unshift(...requeue);
3327
+ if (this.queue.length > this.maxQueueSize) {
3328
+ this.queue.splice(this.maxQueueSize);
3329
+ }
3330
+ this.scheduleDrain(Math.max(this.flushIntervalMs, (retryAtMs != null ? retryAtMs : 0) - Date.now()));
3331
+ break;
3332
+ }
2149
3333
  }
3334
+ return fates;
2150
3335
  }
2151
- /** See EventShipper.requestOpts — same shutdown-budget semantics. */
2152
- requestOpts() {
3336
+ /** See EventShipper.requestOpts — same deadline semantics. */
3337
+ requestOpts(deadlineAt) {
3338
+ const opts = this.scheduleFor(deadlineAt);
3339
+ return opts && { ...opts, cutOff: this.cutOff.signal };
3340
+ }
3341
+ scheduleFor(deadlineAt) {
2153
3342
  if (this.shutdownDeadlineAt !== void 0) {
2154
- const remainingMs = this.shutdownDeadlineAt - Date.now();
2155
- if (remainingMs <= 0) return null;
3343
+ if (this.shutdownDeadlineAt - Date.now() <= 0) return null;
2156
3344
  return {
2157
- maxAttempts: 1,
3345
+ maxAttempts: 3,
2158
3346
  debug: this.debug,
2159
3347
  sdkName: this.sdkName,
2160
- timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
3348
+ deadlineAt: Math.min(this.shutdownDeadlineAt, deadlineAt != null ? deadlineAt : Infinity)
2161
3349
  };
2162
3350
  }
2163
3351
  if (this.hasShutdown) {
@@ -2168,27 +3356,28 @@ var TraceShipper = class {
2168
3356
  timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
2169
3357
  };
2170
3358
  }
2171
- return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
3359
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName, deadlineAt };
2172
3360
  }
3361
+ /**
3362
+ * Flush within `flushTimeoutMs` and stop; spans left at the deadline are
3363
+ * dropped with a warning. Resolves, never rejects.
3364
+ */
2173
3365
  async shutdown() {
2174
3366
  this.hasShutdown = true;
2175
- this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
3367
+ const deadlineAt = Date.now() + this.flushTimeoutMs;
3368
+ this.shutdownDeadlineAt = deadlineAt;
3369
+ let result = { ...DELIVERED };
2176
3370
  try {
2177
3371
  if (this.timer) {
2178
3372
  clearTimeout(this.timer);
2179
3373
  this.timer = void 0;
2180
3374
  }
2181
- const drain = async () => {
2182
- await this.flush();
2183
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
2184
- })));
2185
- };
2186
- const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
2187
- if (!settled) {
3375
+ result = await this.drainWithin(deadlineAt);
3376
+ if (!result.delivered) {
2188
3377
  rateLimitedLog(
2189
3378
  `${this.prefix}.shutdown_deadline`,
2190
3379
  () => console.warn(
2191
- `${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`
3380
+ `${this.prefix} shutdown flush deadline exceeded; ${describeUndelivered("shutdown()", result)}`
2192
3381
  )
2193
3382
  );
2194
3383
  }
@@ -2196,10 +3385,74 @@ var TraceShipper = class {
2196
3385
  this.shutdownDeadlineAt = void 0;
2197
3386
  if (this.ownsAppGitContext) this.appGitContext.dispose();
2198
3387
  }
3388
+ return asCompat(result);
2199
3389
  }
2200
3390
  };
3391
+ function eventShipperOptions(client, extras = {}) {
3392
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3393
+ return {
3394
+ ...extras,
3395
+ writeKey: (_a = extras.writeKey) != null ? _a : client.writeKey,
3396
+ endpoint: (_b = extras.endpoint) != null ? _b : client.endpoint,
3397
+ projectId: (_c = extras.projectId) != null ? _c : client.projectId,
3398
+ debug: (_e = (_d = extras.debug) != null ? _d : client.debug) != null ? _e : false,
3399
+ maxTextFieldChars: (_f = extras.maxTextFieldChars) != null ? _f : client.maxTextFieldChars,
3400
+ flushTimeoutMs: (_g = extras.flushTimeoutMs) != null ? _g : client.flushTimeoutMs,
3401
+ redactPii: (_h = extras.redactPii) != null ? _h : client.redactPii,
3402
+ redactPiiFields: (_i = extras.redactPiiFields) != null ? _i : client.redactPiiFields
3403
+ };
3404
+ }
3405
+ function traceShipperOptions(client, extras = {}) {
3406
+ var _a, _b, _c, _d, _e, _f, _g;
3407
+ return {
3408
+ ...extras,
3409
+ writeKey: (_a = extras.writeKey) != null ? _a : client.writeKey,
3410
+ endpoint: (_b = extras.endpoint) != null ? _b : client.endpoint,
3411
+ projectId: (_c = extras.projectId) != null ? _c : client.projectId,
3412
+ debug: (_e = (_d = extras.debug) != null ? _d : client.debug) != null ? _e : false,
3413
+ maxTextFieldChars: (_f = extras.maxTextFieldChars) != null ? _f : client.maxTextFieldChars,
3414
+ flushTimeoutMs: (_g = extras.flushTimeoutMs) != null ? _g : client.flushTimeoutMs
3415
+ };
3416
+ }
3417
+ function createShippers(client, setup = {}) {
3418
+ return {
3419
+ eventShipper: new EventShipper(eventShipperOptions(client, { ...setup.common, ...setup.event })),
3420
+ traceShipper: new TraceShipper(traceShipperOptions(client, { ...setup.common, ...setup.trace }))
3421
+ };
3422
+ }
3423
+ function isFlushResult(value) {
3424
+ return typeof value === "object" && value !== null && "delivered" in value && typeof value.delivered === "boolean" && "dropped" in value && typeof value.dropped === "number" && "queued" in value && typeof value.queued === "number" && "inFlight" in value && typeof value.inFlight === "number" && "timedOut" in value && typeof value.timedOut === "boolean";
3425
+ }
3426
+ var UNDELIVERED = Object.freeze({
3427
+ delivered: false,
3428
+ timedOut: false,
3429
+ dropped: 0,
3430
+ queued: 0,
3431
+ inFlight: 0
3432
+ });
3433
+ async function settle(call, what) {
3434
+ try {
3435
+ const result = await call();
3436
+ return isFlushResult(result) ? result : DELIVERED;
3437
+ } catch (error) {
3438
+ console.warn(`[raindrop-ai] ${what} failed: ${error instanceof Error ? error.message : String(error)}`);
3439
+ return UNDELIVERED;
3440
+ }
3441
+ }
3442
+ async function flushShippers(...shippers) {
3443
+ const results = await Promise.all(
3444
+ shippers.flatMap((s) => s ? [settle(() => s.flush(), "flush()")] : [])
3445
+ );
3446
+ return asCompat(combineResults(...results));
3447
+ }
3448
+ async function shutdownShippers(...shippers) {
3449
+ const results = await Promise.all(
3450
+ shippers.flatMap((s) => s ? [settle(() => s.shutdown(), "shutdown()")] : [])
3451
+ );
3452
+ return asCompat(combineResults(...results));
3453
+ }
2201
3454
  var PROMPT_TOOLS_ATTRIBUTE = "ai.prompt.tools";
2202
- function isRecord(value) {
3455
+ function isRecord3(value) {
2203
3456
  return typeof value === "object" && value !== null && !Array.isArray(value);
2204
3457
  }
2205
3458
  var JSON_SCHEMA_KEYS = [
@@ -2217,17 +3470,17 @@ var JSON_SCHEMA_KEYS = [
2217
3470
  "required"
2218
3471
  ];
2219
3472
  function isJsonSchema(value) {
2220
- if (!isRecord(value)) return false;
3473
+ if (!isRecord3(value)) return false;
2221
3474
  if (typeof value["~standard"] === "object") return false;
2222
- if (isRecord(value._def) || isRecord(value._zod)) return false;
3475
+ if (isRecord3(value._def) || isRecord3(value._zod)) return false;
2223
3476
  if (Object.keys(value).length === 0) return true;
2224
3477
  return JSON_SCHEMA_KEYS.some((key) => key in value);
2225
3478
  }
2226
3479
  function toJsonSchema(value, options) {
2227
3480
  if (value === void 0 || value === null) return void 0;
2228
3481
  if (isJsonSchema(value)) return value;
2229
- if (isRecord(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
2230
- if (isRecord(value) && isJsonSchema(value.json)) return value.json;
3482
+ if (isRecord3(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
3483
+ if (isRecord3(value) && isJsonSchema(value.json)) return value.json;
2231
3484
  if (options == null ? void 0 : options.schema) {
2232
3485
  try {
2233
3486
  return options.schema(value);
@@ -2250,23 +3503,23 @@ function functionTool(name, description, schema, options) {
2250
3503
  }
2251
3504
  function normalizeToolDeclaration(tool, options, fallbackName) {
2252
3505
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2253
- if (!isRecord(tool)) return void 0;
3506
+ if (!isRecord3(tool)) return void 0;
2254
3507
  if (tool.type === "provider-defined" || tool.type === "provider") {
2255
3508
  const name2 = (_b = (_a = pickString(tool.name)) != null ? _a : pickString(tool.id)) != null ? _b : fallbackName;
2256
3509
  if (name2 === void 0) return void 0;
2257
3510
  const declaration = { type: "provider-defined", name: name2 };
2258
3511
  const id = pickString(tool.id);
2259
3512
  if (id !== void 0) declaration.id = id;
2260
- if (isRecord(tool.args)) declaration.args = tool.args;
3513
+ if (isRecord3(tool.args)) declaration.args = tool.args;
2261
3514
  return declaration;
2262
3515
  }
2263
- if (isRecord(tool.toolSpec)) {
3516
+ if (isRecord3(tool.toolSpec)) {
2264
3517
  const spec = tool.toolSpec;
2265
3518
  const name2 = (_c = pickString(spec.name)) != null ? _c : fallbackName;
2266
3519
  if (name2 === void 0) return void 0;
2267
3520
  return functionTool(name2, spec.description, spec.inputSchema, options);
2268
3521
  }
2269
- if (isRecord(tool.function)) {
3522
+ if (isRecord3(tool.function)) {
2270
3523
  const fn = tool.function;
2271
3524
  const name2 = (_d = pickString(fn.name)) != null ? _d : fallbackName;
2272
3525
  if (name2 === void 0) return void 0;
@@ -2292,7 +3545,7 @@ function isGeminiToolGroup(tool) {
2292
3545
  if (Array.isArray(tool.functionDeclarations)) return true;
2293
3546
  const keys = Object.keys(tool);
2294
3547
  if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool)) return false;
2295
- return keys.every((key) => isRecord(tool[key]));
3548
+ return keys.every((key) => isRecord3(tool[key]));
2296
3549
  }
2297
3550
  function expandGeminiTool(tool, options) {
2298
3551
  const declarations = [];
@@ -2302,7 +3555,7 @@ function expandGeminiTool(tool, options) {
2302
3555
  const normalized = normalizeToolDeclaration(declaration, options);
2303
3556
  if (normalized) declarations.push(normalized);
2304
3557
  }
2305
- } else if (isRecord(value)) {
3558
+ } else if (isRecord3(value)) {
2306
3559
  declarations.push({ type: "provider-defined", id: key, name: key, args: value });
2307
3560
  }
2308
3561
  }
@@ -2312,14 +3565,14 @@ function normalizeToolDeclarations(tools, options) {
2312
3565
  if (tools === void 0 || tools === null) return void 0;
2313
3566
  if (Array.isArray(tools)) {
2314
3567
  return tools.flatMap((tool) => {
2315
- if (isRecord(tool) && isGeminiToolGroup(tool)) {
3568
+ if (isRecord3(tool) && isGeminiToolGroup(tool)) {
2316
3569
  return expandGeminiTool(tool, options);
2317
3570
  }
2318
3571
  const declaration = normalizeToolDeclaration(tool, options);
2319
3572
  return declaration ? [declaration] : [];
2320
3573
  });
2321
3574
  }
2322
- if (!isRecord(tools)) return void 0;
3575
+ if (!isRecord3(tools)) return void 0;
2323
3576
  return Object.entries(tools).flatMap(([key, tool]) => {
2324
3577
  const declaration = normalizeToolDeclaration(tool, options, key);
2325
3578
  return declaration ? [declaration] : [];
@@ -2465,39 +3718,6 @@ var resolveLocal = (sourceDirectory, detectBranch, completed) => {
2465
3718
  function createAppGitContext2(options, runtime = {}) {
2466
3719
  return new AppGitContext(options, { ...runtime, resolveLocal });
2467
3720
  }
2468
- function hasDestination(options) {
2469
- var _a;
2470
- if (options.enabled === false) return false;
2471
- return Boolean(((_a = options.writeKey) == null ? void 0 : _a.trim()) || resolveLocalDebuggerBaseUrl(options.localDebuggerUrl));
2472
- }
2473
- var EventShipper2 = class extends EventShipper {
2474
- constructor(options) {
2475
- var _a;
2476
- const ownsAppGitContext = options.appGitContext === void 0;
2477
- const appGitContext = (_a = options.appGitContext) != null ? _a : createAppGitContext2(options.appGit, { allowDiscovery: hasDestination(options) });
2478
- try {
2479
- super({ ...options, appGitContext });
2480
- } catch (error) {
2481
- if (ownsAppGitContext) appGitContext.dispose();
2482
- throw error;
2483
- }
2484
- if (ownsAppGitContext) this.ownAppGitContextForShutdown();
2485
- }
2486
- };
2487
- var TraceShipper2 = class extends TraceShipper {
2488
- constructor(options) {
2489
- var _a;
2490
- const ownsAppGitContext = options.appGitContext === void 0;
2491
- const appGitContext = (_a = options.appGitContext) != null ? _a : createAppGitContext2(options.appGit, { allowDiscovery: hasDestination(options) });
2492
- try {
2493
- super({ ...options, appGitContext });
2494
- } catch (error) {
2495
- if (ownsAppGitContext) appGitContext.dispose();
2496
- throw error;
2497
- }
2498
- this.ownsAppGitContext = ownsAppGitContext;
2499
- }
2500
- };
2501
3721
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
2502
3722
  var SUPPRESS_TRACING_KEY = /* @__PURE__ */ Symbol.for(
2503
3723
  "OpenTelemetry SDK Context Key SUPPRESS_TRACING"
@@ -2635,6 +3855,8 @@ var RaindropManagedAgentsHandler = class {
2635
3855
  eventName: (_e = context.eventName) != null ? _e : "ai_generation",
2636
3856
  input: void 0,
2637
3857
  model: void 0,
3858
+ // Caller-supplied properties win over the derived keys below (W6).
3859
+ callerProperties: context.properties,
2638
3860
  properties: {
2639
3861
  ...context.properties,
2640
3862
  "ai.provider": "openai",
@@ -2955,7 +4177,7 @@ function isRecord2(value) {
2955
4177
  // package.json
2956
4178
  var package_default = {
2957
4179
  name: "@raindrop-ai/openai-managed-agents",
2958
- version: "0.0.4",
4180
+ version: "0.0.5",
2959
4181
  description: "Raindrop tracing for the OpenAI Agents API",
2960
4182
  main: "dist/index.js",
2961
4183
  module: "dist/index.mjs",
@@ -3123,7 +4345,7 @@ function wrapManagedAgents(client, handler, context) {
3123
4345
 
3124
4346
  // src/index.ts
3125
4347
  function createRaindropOpenAIManagedAgents(opts = {}) {
3126
- var _a, _b;
4348
+ var _a;
3127
4349
  const enabled = Boolean((_a = opts.writeKey) == null ? void 0 : _a.trim());
3128
4350
  if (!enabled)
3129
4351
  console.warn(
@@ -3132,44 +4354,33 @@ function createRaindropOpenAIManagedAgents(opts = {}) {
3132
4354
  const appGitContext = createAppGitContext2(enabled ? opts.appGit : false, {
3133
4355
  allowLocalGit: false
3134
4356
  });
3135
- const shipperOptions = {
3136
- writeKey: opts.writeKey,
3137
- endpoint: opts.endpoint,
3138
- projectId: opts.projectId,
3139
- enabled,
3140
- debug: (_b = opts.debug) != null ? _b : false,
3141
- sdkName: "openai-managed-agents",
3142
- libraryName,
3143
- libraryVersion,
3144
- appGitContext,
3145
- maxTextFieldChars: opts.maxTextFieldChars,
3146
- localDebuggerUrl: opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl
3147
- };
3148
- const events = new EventShipper2(shipperOptions);
3149
- const traces = new TraceShipper2({
3150
- ...shipperOptions,
3151
- serviceName: "raindrop.openai-managed-agents",
3152
- serviceVersion: libraryVersion
4357
+ const { eventShipper: events, traceShipper: traces } = createShippers(opts, {
4358
+ common: {
4359
+ enabled,
4360
+ sdkName: "openai-managed-agents",
4361
+ appGitContext,
4362
+ localDebuggerUrl: opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl
4363
+ },
4364
+ event: { libraryName, libraryVersion },
4365
+ trace: { serviceName: "raindrop.openai-managed-agents", serviceVersion: libraryVersion }
3153
4366
  });
3154
4367
  const handler = new RaindropManagedAgentsHandler(events, traces, opts);
3155
4368
  return {
3156
4369
  handler,
3157
4370
  wrap: (client, context) => wrapManagedAgents(client, handler, context),
3158
- events: {
3159
- patch: (id, patch) => events.patch(id, patch),
3160
- finish: (id, patch) => events.finish(id, patch),
3161
- addAttachments: (id, attachments) => events.patch(id, { attachments }),
3162
- setProperties: (id, properties) => events.patch(id, { properties })
4371
+ get lastEventId() {
4372
+ return events == null ? void 0 : events.lastFinishedEventId;
3163
4373
  },
4374
+ events: createEventsApi(events),
3164
4375
  users: { identify: (users) => events.identify(users) },
3165
4376
  signals: { track: (signal) => events.trackSignal(signal) },
3166
- async flush() {
3167
- await Promise.all([events.flush(), traces.flush()]);
4377
+ flush() {
4378
+ return flushShippers(events, traces);
3168
4379
  },
3169
4380
  async shutdown() {
3170
4381
  try {
3171
4382
  handler.shutdown();
3172
- await Promise.all([events.shutdown(), traces.shutdown()]);
4383
+ return await shutdownShippers(events, traces);
3173
4384
  } finally {
3174
4385
  appGitContext.dispose();
3175
4386
  }