@rasputin-ai/core 0.2.0 → 0.4.0

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.
Files changed (41) hide show
  1. package/dist/announce-deploy/announce-deploy.d.ts +2 -2
  2. package/dist/announce-deploy/announce-deploy.d.ts.map +1 -1
  3. package/dist/create-client/create-client-types.d.ts +50 -19
  4. package/dist/create-client/create-client-types.d.ts.map +1 -1
  5. package/dist/create-client/create-client.d.ts +1 -1
  6. package/dist/create-client/create-client.d.ts.map +1 -1
  7. package/dist/create-client/is-client-enabled.d.ts.map +1 -1
  8. package/dist/create-client/resolve-endpoint-urls.d.ts +2 -1
  9. package/dist/create-client/resolve-endpoint-urls.d.ts.map +1 -1
  10. package/dist/create-ingest-event/create-ingest-event-types.d.ts +8 -2
  11. package/dist/create-ingest-event/create-ingest-event-types.d.ts.map +1 -1
  12. package/dist/create-ingest-event/create-ingest-event.d.ts.map +1 -1
  13. package/dist/create-ingest-event/runtime-execution-state-types.d.ts +91 -0
  14. package/dist/create-ingest-event/runtime-execution-state-types.d.ts.map +1 -0
  15. package/dist/create-ingest-event/truncate-ingest-message.d.ts +3 -0
  16. package/dist/create-ingest-event/truncate-ingest-message.d.ts.map +1 -0
  17. package/dist/detect-release/detect-release.d.ts +1 -1
  18. package/dist/detect-release/detect-release.d.ts.map +1 -1
  19. package/dist/index.d.ts +10 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +331 -80
  22. package/dist/instrumentation-manifest/instrumentation-manifest-types.d.ts +98 -0
  23. package/dist/instrumentation-manifest/instrumentation-manifest-types.d.ts.map +1 -0
  24. package/dist/instrumentation-manifest/upload-instrumentation-manifest.d.ts +21 -0
  25. package/dist/instrumentation-manifest/upload-instrumentation-manifest.d.ts.map +1 -0
  26. package/dist/normalize-frames/normalize-frames.d.ts +4 -2
  27. package/dist/normalize-frames/normalize-frames.d.ts.map +1 -1
  28. package/dist/repo-root/warn-repo-root-setup.d.ts +0 -1
  29. package/dist/repo-root/warn-repo-root-setup.d.ts.map +1 -1
  30. package/dist/sdk-meta.d.ts +2 -2
  31. package/dist/sdk-meta.d.ts.map +1 -1
  32. package/dist/source-identity/source-identity.d.ts +25 -0
  33. package/dist/source-identity/source-identity.d.ts.map +1 -0
  34. package/dist/suppression/suppression-types.d.ts +2 -2
  35. package/dist/suppression/suppression-types.d.ts.map +1 -1
  36. package/dist/transport/create-transport.d.ts.map +1 -1
  37. package/dist/transport/transport-queue.d.ts +7 -0
  38. package/dist/transport/transport-queue.d.ts.map +1 -1
  39. package/dist/transport/transport-types.d.ts +11 -2
  40. package/dist/transport/transport-types.d.ts.map +1 -1
  41. package/package.json +1 -2
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/announce-deploy/announce-deploy.ts
2
2
  var announceDeploy = async (options) => {
3
3
  if (options.enabled === false) return "skipped";
4
- const release = options.release?.trim();
4
+ const release = options.release?.trim() ?? "";
5
5
  const environment = options.environment.trim();
6
- if (!release || !environment) return "skipped";
6
+ if (!environment) return "skipped";
7
7
  const fetchFn = options.fetch ?? globalThis.fetch;
8
8
  try {
9
9
  const response = await fetchFn(options.deployUrl, {
@@ -22,15 +22,116 @@ var announceDeploy = async (options) => {
22
22
  }
23
23
  };
24
24
 
25
- // src/create-ingest-event/create-ingest-event.ts
25
+ // src/create-ingest-event/truncate-ingest-message.ts
26
26
  var MAX_MESSAGE_BYTES = 8 * 1024;
27
- var MAX_STACK_FRAMES = 50;
27
+ var utf8Bytes = (value) => Buffer.byteLength(value, "utf8");
28
28
  var truncateUtf8 = (value, maxBytes) => {
29
- if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
29
+ if (utf8Bytes(value) <= maxBytes) return value;
30
30
  let end = Math.min(value.length, maxBytes);
31
- while (end > 0 && Buffer.byteLength(value.slice(0, end), "utf8") > maxBytes) end--;
31
+ while (end > 0 && utf8Bytes(value.slice(0, end)) > maxBytes) end--;
32
32
  return value.slice(0, end);
33
33
  };
34
+ var truncation = (reason, extra = {}) => ({
35
+ __rasputin_truncated: true,
36
+ __reason: reason,
37
+ ...extra
38
+ });
39
+ var mapJson = (value, maxStringChars, maxArrayItems, maxObjectKeys) => {
40
+ if (typeof value === "string") {
41
+ if (value.length <= maxStringChars) return value;
42
+ if (maxStringChars <= 1) return value.slice(0, maxStringChars);
43
+ return `${value.slice(0, maxStringChars - 1)}\u2026`;
44
+ }
45
+ if (Array.isArray(value)) {
46
+ const keep = maxArrayItems === null ? value.length : Math.min(value.length, maxArrayItems);
47
+ const items = value.slice(0, keep).map((item) => mapJson(item, maxStringChars, maxArrayItems, maxObjectKeys));
48
+ if (keep < value.length) {
49
+ items.push(truncation("max_array_elements", { __original_length: value.length }));
50
+ }
51
+ return items;
52
+ }
53
+ if (value && typeof value === "object") {
54
+ const entries = Object.entries(value);
55
+ const keep = maxObjectKeys === null ? entries.length : Math.min(entries.length, maxObjectKeys);
56
+ const out = {};
57
+ for (const [key, child] of entries.slice(0, keep)) {
58
+ out[key] = mapJson(child, maxStringChars, maxArrayItems, maxObjectKeys);
59
+ }
60
+ if (keep < entries.length) {
61
+ Object.assign(out, truncation("max_object_keys", { __original_length: entries.length }));
62
+ }
63
+ return out;
64
+ }
65
+ return value;
66
+ };
67
+ var jsonIfFits = (value, maxBytes) => {
68
+ const json = JSON.stringify(value);
69
+ return utf8Bytes(json) <= maxBytes ? json : null;
70
+ };
71
+ var boundJsonMessage = (value, maxBytes) => {
72
+ const fit = (maxStringChars, maxArrayItems, maxObjectKeys) => jsonIfFits(mapJson(value, maxStringChars, maxArrayItems, maxObjectKeys), maxBytes);
73
+ const longestStringsThatFit = (maxArrayItems, maxObjectKeys) => {
74
+ let lo = 0;
75
+ let hi = maxBytes;
76
+ let best = null;
77
+ while (lo <= hi) {
78
+ const mid = lo + hi >> 1;
79
+ const candidate = fit(mid, maxArrayItems, maxObjectKeys);
80
+ if (candidate) {
81
+ best = candidate;
82
+ lo = mid + 1;
83
+ } else {
84
+ hi = mid - 1;
85
+ }
86
+ }
87
+ return best;
88
+ };
89
+ const withAllKeys = longestStringsThatFit(null, null);
90
+ if (withAllKeys) return withAllKeys;
91
+ const searchCount = (emptyFits, fillStrings) => {
92
+ let lo = 0;
93
+ let hi = 1;
94
+ while (!emptyFits(hi) && hi < 1048576) hi *= 2;
95
+ let best = 0;
96
+ while (lo <= hi) {
97
+ const mid = lo + hi >> 1;
98
+ if (emptyFits(mid)) {
99
+ best = mid;
100
+ lo = mid + 1;
101
+ } else {
102
+ hi = mid - 1;
103
+ }
104
+ }
105
+ return fillStrings(best);
106
+ };
107
+ const withFewerArrayItems = searchCount(
108
+ (count) => fit(0, count, null) !== null,
109
+ (count) => longestStringsThatFit(count, null)
110
+ );
111
+ if (withFewerArrayItems) return withFewerArrayItems;
112
+ return searchCount(
113
+ (count) => fit(0, 0, count) !== null,
114
+ (count) => longestStringsThatFit(0, count)
115
+ ) ?? JSON.stringify(
116
+ Array.isArray(value) ? [truncation("max_array_elements", { __original_length: value.length })] : truncation("max_object_keys", { __original_length: Object.keys(value).length })
117
+ );
118
+ };
119
+ var truncateIngestMessage = (value, maxBytes = MAX_MESSAGE_BYTES) => {
120
+ if (utf8Bytes(value) <= maxBytes) return value;
121
+ try {
122
+ const parsed = JSON.parse(value);
123
+ if (parsed !== null && typeof parsed === "object") {
124
+ const compact = JSON.stringify(parsed);
125
+ if (utf8Bytes(compact) <= maxBytes) return compact;
126
+ return boundJsonMessage(parsed, maxBytes);
127
+ }
128
+ } catch {
129
+ }
130
+ return truncateUtf8(value, maxBytes);
131
+ };
132
+
133
+ // src/create-ingest-event/create-ingest-event.ts
134
+ var MAX_STACK_FRAMES = 50;
34
135
  var errorFields = (error) => {
35
136
  if (error instanceof Error) {
36
137
  const code = "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? String(error.code) : void 0;
@@ -52,6 +153,7 @@ var toStackFrame = (frame) => {
52
153
  const fn = frame.function ?? frame.original?.name;
53
154
  return {
54
155
  filename,
156
+ ...frame.source ? { source: frame.source } : {},
55
157
  ...fn ? { function: fn } : {},
56
158
  ...preferred.line !== void 0 ? { lineno: preferred.line } : {},
57
159
  ...preferred.column !== void 0 ? { colno: preferred.column } : {},
@@ -79,18 +181,19 @@ var createIngestEvent = (input) => {
79
181
  const occurred = input.occurredAt === void 0 ? (/* @__PURE__ */ new Date()).toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : input.occurredAt.toISOString();
80
182
  const event = {
81
183
  type: extracted.type,
82
- message: truncateUtf8(extracted.message, MAX_MESSAGE_BYTES),
184
+ message: truncateIngestMessage(extracted.message),
83
185
  stack_frames: framesToStackFrames(input.frames),
84
186
  occurred_at: occurred,
85
187
  environment: input.environment,
86
188
  origin: input.origin,
87
- resolution: input.resolution
189
+ resolution: input.resolution,
190
+ release: input.release
88
191
  };
89
192
  const code = input.code !== void 0 ? input.code : extracted.code;
90
193
  if (code !== void 0) event.code = code;
91
- if (input.release !== void 0) event.release = input.release;
92
194
  const request = sanitizeRequest(input.request);
93
195
  if (request) event.request = request;
196
+ if (input.runtimeState) event.runtime_state = input.runtimeState;
94
197
  return event;
95
198
  };
96
199
 
@@ -166,20 +269,98 @@ var detectRelease = (options = {}) => {
166
269
  const fromGit = readGitHead(options.cwd ?? process.cwd());
167
270
  if (fromGit) return fromGit;
168
271
  console.warn(
169
- "[rasputin] No release detected. Set `RASPUTIN_RELEASE` to your git commit SHA, pass `release` in RasputinInit, or deploy on a platform that exposes a commit SHA env var (e.g. VERCEL_GIT_COMMIT_SHA). Without a release, deploy correlation and diagnosis confidence are degraded."
272
+ "[rasputin] No release detected. Set `RASPUTIN_RELEASE` to your git commit SHA, pass `release` in RasputinInit, or deploy on a platform that exposes a commit SHA env var (e.g. VERCEL_GIT_COMMIT_SHA). Events are not sent without a release."
170
273
  );
171
274
  return void 0;
172
275
  };
173
276
 
174
277
  // src/normalize-frames/normalize-frames.ts
175
- import { isAbsolute, resolve } from "node:path";
278
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
279
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
280
+
281
+ // src/source-identity/source-identity.ts
282
+ import { createHash } from "node:crypto";
283
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
284
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
176
285
  import { fileURLToPath } from "node:url";
177
- var NON_SOURCE = /* @__PURE__ */ new Set(["", "native", "<anonymous>", "unknown location"]);
286
+ var packageBoundaryByDirectory = /* @__PURE__ */ new Map();
178
287
  var toPosix = (path) => path.replaceAll("\\", "/");
288
+ var physicalPath = (file) => {
289
+ if (!file.startsWith("file://")) return resolve(file);
290
+ try {
291
+ return fileURLToPath(file);
292
+ } catch {
293
+ return resolve(file.replace(/^file:\/\//, ""));
294
+ }
295
+ };
296
+ var readPackageBoundary = (directory) => {
297
+ const visited = [];
298
+ let current = resolve(directory);
299
+ while (true) {
300
+ const cached = packageBoundaryByDirectory.get(current);
301
+ if (cached !== void 0) {
302
+ for (const path of visited) packageBoundaryByDirectory.set(path, cached);
303
+ return cached;
304
+ }
305
+ visited.push(current);
306
+ const packageJson = resolve(current, "package.json");
307
+ if (existsSync(packageJson)) {
308
+ let name;
309
+ try {
310
+ const parsed = JSON.parse(readFileSync2(packageJson, "utf8"));
311
+ if (typeof parsed.name === "string" && parsed.name.trim()) name = parsed.name.trim();
312
+ } catch {
313
+ }
314
+ const boundary = { root: current, ...name ? { name } : {} };
315
+ for (const path of visited) packageBoundaryByDirectory.set(path, boundary);
316
+ return boundary;
317
+ }
318
+ const parent = dirname(current);
319
+ if (parent === current) {
320
+ for (const path of visited) packageBoundaryByDirectory.set(path, null);
321
+ return null;
322
+ }
323
+ current = parent;
324
+ }
325
+ };
326
+ var sourceLocatorFromFile = (file) => {
327
+ if (!file || /^(node:|bun:|https?:|webpack:|blob:|data:|native$)/i.test(file)) return void 0;
328
+ const absolute = physicalPath(file.split("?")[0] ?? file);
329
+ const boundary = readPackageBoundary(dirname(absolute));
330
+ if (!boundary) return void 0;
331
+ const fromPackage = relative(boundary.root, absolute);
332
+ if (!fromPackage || fromPackage === ".." || fromPackage.startsWith(`..${sep}`) || isAbsolute(fromPackage)) {
333
+ return { packageRelativePath: basename(absolute) };
334
+ }
335
+ return {
336
+ ...boundary.name ? { packageName: boundary.name } : {},
337
+ packageRelativePath: toPosix(fromPackage)
338
+ };
339
+ };
340
+ var sourceLocatorKey = (source) => `${source.packageName ?? ""}\0${source.packageRelativePath}`;
341
+ var runtimeFunctionId = (definition) => {
342
+ const source = definition.source;
343
+ const identity = [
344
+ definition.name,
345
+ source?.packageName ?? "",
346
+ source?.packageRelativePath ?? "",
347
+ String(source?.line ?? ""),
348
+ String(source?.column ?? "")
349
+ ].join("\0");
350
+ return `rf_${createHash("sha256").update(identity).digest("base64url").slice(0, 24)}`;
351
+ };
352
+ var sourceDisplayPath = (source) => source.repositoryPath ?? source.packageRelativePath;
353
+ var resetSourceIdentityCache = () => {
354
+ packageBoundaryByDirectory.clear();
355
+ };
356
+
357
+ // src/normalize-frames/normalize-frames.ts
358
+ var NON_SOURCE = /* @__PURE__ */ new Set(["", "native", "<anonymous>", "unknown location"]);
359
+ var toPosix2 = (path) => path.replaceAll("\\", "/");
179
360
  var stripFileUrl = (file) => {
180
361
  if (!file.startsWith("file://")) return file;
181
362
  try {
182
- return fileURLToPath(file);
363
+ return fileURLToPath2(file);
183
364
  } catch {
184
365
  return file.replace(/^file:\/\//, "");
185
366
  }
@@ -187,44 +368,55 @@ var stripFileUrl = (file) => {
187
368
  var isSchemePath = (file) => NON_SOURCE.has(file) || /^(node:|bun:|https?:|webpack:|blob:|data:|eval\b)/i.test(file);
188
369
  var normalizeFile = (file, appRoot) => {
189
370
  if (!file || isSchemePath(file)) return { file, underRoot: false };
190
- const cleaned = toPosix(stripFileUrl(file)).split("?")[0]?.trim() ?? "";
371
+ const cleaned = toPosix2(stripFileUrl(file)).split("?")[0]?.trim() ?? "";
191
372
  if (!cleaned || isSchemePath(cleaned)) return { file: cleaned, underRoot: false };
192
- const root = toPosix(resolve(appRoot));
193
- const absolute = toPosix(
194
- isAbsolute(cleaned) || /^[A-Za-z]:\//.test(cleaned) ? resolve(cleaned) : resolve(root, cleaned)
373
+ const root = appRoot ? toPosix2(resolve2(appRoot)) : void 0;
374
+ const absolute = toPosix2(
375
+ isAbsolute2(cleaned) || /^[A-Za-z]:\//.test(cleaned) ? resolve2(cleaned) : resolve2(root ?? process.cwd(), cleaned)
195
376
  );
196
- if (absolute === root) return { file: ".", underRoot: true };
377
+ if (!root) return { file: cleaned, absolute, underRoot: false };
378
+ if (absolute === root) return { file: ".", absolute, underRoot: true };
197
379
  const prefix = root.endsWith("/") ? root : `${root}/`;
198
380
  if (absolute.startsWith(prefix)) {
199
- return { file: absolute.slice(prefix.length), underRoot: true };
381
+ return { file: absolute.slice(prefix.length), absolute, underRoot: true };
200
382
  }
201
- return { file: absolute, underRoot: false };
383
+ return { file: absolute, absolute, underRoot: false };
202
384
  };
203
- var isInApp = (file, underRoot) => {
204
- if (!underRoot || isSchemePath(file)) return false;
205
- if (/(^|\/)node_modules\//.test(file)) return false;
206
- return true;
385
+ var isInApp = (file, absolute, underRoot, source) => {
386
+ if (isSchemePath(file)) return false;
387
+ if (/(^|\/)node_modules\//.test(absolute ?? file)) return false;
388
+ if (/^@rasputin-ai\/(?:core|node|elysia)$/.test(source?.packageName ?? "")) return false;
389
+ return underRoot || source !== void 0;
207
390
  };
208
391
  var normalizeFrames = (frames, options) => {
209
392
  const appRoot = options.appRoot;
210
393
  return frames.map((frame) => {
211
394
  const generated = normalizeFile(frame.generated.file, appRoot);
212
395
  const original = frame.original ? normalizeFile(frame.original.file, appRoot) : void 0;
396
+ const generatedSource = generated.absolute ? sourceLocatorFromFile(generated.absolute) : void 0;
397
+ const originalSource = original?.absolute ? sourceLocatorFromFile(original.absolute) : void 0;
213
398
  const preferred = original ?? generated;
399
+ const locator = originalSource ?? generatedSource;
400
+ const source = locator ? {
401
+ ...locator,
402
+ ...(frame.original?.line ?? frame.generated.line) !== void 0 ? { line: frame.original?.line ?? frame.generated.line } : {},
403
+ ...(frame.original?.column ?? frame.generated.column) !== void 0 ? { column: frame.original?.column ?? frame.generated.column } : {}
404
+ } : void 0;
214
405
  return {
215
406
  generated: {
216
- file: generated.file,
407
+ file: generatedSource && !generated.underRoot ? sourceDisplayPath(generatedSource) : generated.file,
217
408
  line: frame.generated.line,
218
409
  column: frame.generated.column
219
410
  },
220
411
  original: frame.original ? {
221
- file: original?.file ?? frame.original.file,
412
+ file: originalSource && !original?.underRoot ? sourceDisplayPath(originalSource) : original?.file ?? frame.original.file,
222
413
  line: frame.original.line,
223
414
  column: frame.original.column,
224
415
  name: frame.original.name
225
416
  } : void 0,
226
417
  function: frame.function,
227
- in_app: isInApp(preferred.file, preferred.underRoot)
418
+ in_app: isInApp(preferred.file, preferred.absolute, preferred.underRoot, source),
419
+ ...source ? { source } : {}
228
420
  };
229
421
  });
230
422
  };
@@ -288,22 +480,22 @@ var parseStack = (stack) => {
288
480
  };
289
481
 
290
482
  // src/repo-root/repo-root-from.ts
291
- import { existsSync } from "node:fs";
292
- import { dirname, join as join2, resolve as resolve2 } from "node:path";
293
- import { fileURLToPath as fileURLToPath2 } from "node:url";
294
- var isGitRoot = (dir) => existsSync(join2(dir, ".git"));
295
- var hasPackageJson = (dir) => existsSync(join2(dir, "package.json"));
483
+ import { existsSync as existsSync2 } from "node:fs";
484
+ import { dirname as dirname2, join as join2, resolve as resolve3 } from "node:path";
485
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
486
+ var isGitRoot = (dir) => existsSync2(join2(dir, ".git"));
487
+ var hasPackageJson = (dir) => existsSync2(join2(dir, "package.json"));
296
488
  var repoRootFrom = (moduleUrl) => {
297
- let current = resolve2(dirname(fileURLToPath2(moduleUrl)));
489
+ let current = resolve3(dirname2(fileURLToPath3(moduleUrl)));
298
490
  let packageRoot;
299
491
  while (true) {
300
492
  if (isGitRoot(current)) return current;
301
493
  if (hasPackageJson(current)) packageRoot = current;
302
- const parent = dirname(current);
494
+ const parent = dirname2(current);
303
495
  if (parent === current) break;
304
496
  current = parent;
305
497
  }
306
- return packageRoot ?? resolve2(dirname(fileURLToPath2(moduleUrl)));
498
+ return packageRoot ?? resolve3(dirname2(fileURLToPath3(moduleUrl)));
307
499
  };
308
500
 
309
501
  // src/repo-root/resolve-repo-root.ts
@@ -317,34 +509,12 @@ var resolveRepoRoot = (options) => {
317
509
 
318
510
  // src/repo-root/warn-repo-root-setup.ts
319
511
  var BUILD_OUTPUT_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "out", ".next", "coverage"]);
320
- var warnedMissing = false;
321
512
  var warnedSuspicious = false;
322
513
  var BANNER = "======== [rasputin] WARNING";
323
514
  var looksLikeBuildOutputRoot = (root) => {
324
515
  const posix = root.replaceAll("\\", "/").replace(/\/+$/, "");
325
516
  return posix.split("/").some((segment) => BUILD_OUTPUT_SEGMENTS.has(segment));
326
517
  };
327
- var warnIfMissingRepoRoot = () => {
328
- if (warnedMissing) return;
329
- warnedMissing = true;
330
- console.warn(
331
- `
332
-
333
- ${BANNER}: we don't know where your project is ========
334
- Rasputin needs to know which folder your app lives in so errors show
335
- the right file paths in the dashboard.
336
-
337
- Add this to RasputinInit (from the same file you call it in):
338
-
339
- moduleUrl: import.meta.url
340
-
341
- Or set repoRoot to the folder you open in your editor.
342
-
343
- Nothing will be sent until this is set.
344
- ======================================================================
345
- `
346
- );
347
- };
348
518
  var warnIfSuspiciousRepoRoot = (root) => {
349
519
  if (warnedSuspicious || !looksLikeBuildOutputRoot(root)) return;
350
520
  warnedSuspicious = true;
@@ -372,8 +542,8 @@ Or set repoRoot yourself.
372
542
 
373
543
  // src/resolve-source-maps/resolve-source-maps.ts
374
544
  import { readFile } from "node:fs/promises";
375
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3 } from "node:path";
376
- import { fileURLToPath as fileURLToPath3 } from "node:url";
545
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3 } from "node:path";
546
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
377
547
  import { LEAST_UPPER_BOUND, originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
378
548
  var SOURCE_EXT2 = /\.(ts|tsx|mts|cts)$/i;
379
549
  var SOURCEMAP_COMMENT = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
@@ -381,7 +551,7 @@ var mapCache = /* @__PURE__ */ new Map();
381
551
  var toPath = (file) => {
382
552
  if (file.startsWith("file://")) {
383
553
  try {
384
- return fileURLToPath3(file);
554
+ return fileURLToPath4(file);
385
555
  } catch {
386
556
  return file;
387
557
  }
@@ -422,7 +592,7 @@ var readMapPayload = async (generatedPath) => {
422
592
  if (comment?.startsWith("data:")) return decodeDataUrl(comment);
423
593
  if (comment) {
424
594
  try {
425
- return await readFile(join3(dirname2(generatedPath), comment), "utf8");
595
+ return await readFile(join3(dirname3(generatedPath), comment), "utf8");
426
596
  } catch {
427
597
  }
428
598
  }
@@ -469,7 +639,7 @@ var resolveFrame = async (raw) => {
469
639
  return {
470
640
  ...base,
471
641
  original: {
472
- file: isAbsolute2(pos.source) ? pos.source : join3(dirname2(generatedPath), pos.source),
642
+ file: isAbsolute3(pos.source) ? pos.source : join3(dirname3(generatedPath), pos.source),
473
643
  line: pos.line,
474
644
  column: pos.column ?? void 0,
475
645
  name: pos.name ?? void 0
@@ -489,7 +659,7 @@ var resolveSourceMaps = async (frames) => {
489
659
 
490
660
  // src/sdk-meta.ts
491
661
  var SDK_NAME = "@rasputin-ai/core";
492
- var SDK_VERSION = "0.2.0";
662
+ var SDK_VERSION = "0.4.0";
493
663
 
494
664
  // src/suppression/identity-needs-message-template.ts
495
665
  var identityNeedsMessageTemplate = (type, code) => {
@@ -687,7 +857,7 @@ var createSuppression = (options = {}) => {
687
857
  const identity = captureIdentity(input);
688
858
  const captureRawFrames = (input.rawFrames ?? []).slice(0, MAX_STACK_FRAMES);
689
859
  const burstKey = computeBurstKey(input.errorType, input.message, input.code, input.frames);
690
- const release = input.release ?? null;
860
+ const release = input.release;
691
861
  const route = input.route ?? null;
692
862
  let bucket = burstBuckets.get(burstKey);
693
863
  if (!bucket) {
@@ -771,17 +941,17 @@ var createSuppression = (options = {}) => {
771
941
 
772
942
  // src/transport/send-batch.ts
773
943
  import { gzipSync } from "node:zlib";
774
- var sleep = (ms, signal) => new Promise((resolve3) => {
944
+ var sleep = (ms, signal) => new Promise((resolve4) => {
775
945
  if (signal?.aborted) {
776
- resolve3();
946
+ resolve4();
777
947
  return;
778
948
  }
779
- const timer = setTimeout(resolve3, ms);
949
+ const timer = setTimeout(resolve4, ms);
780
950
  signal?.addEventListener(
781
951
  "abort",
782
952
  () => {
783
953
  clearTimeout(timer);
784
- resolve3();
954
+ resolve4();
785
955
  },
786
956
  { once: true }
787
957
  );
@@ -859,6 +1029,9 @@ var RANK = {
859
1029
  };
860
1030
  var createQueue = (maxSize) => {
861
1031
  const items = [];
1032
+ let enqueued = 0;
1033
+ let droppedIncoming = 0;
1034
+ let droppedQueued = 0;
862
1035
  const oldestLowestIndex = () => {
863
1036
  let best = 0;
864
1037
  for (let i = 1; i < items.length; i++) {
@@ -874,16 +1047,28 @@ var createQueue = (maxSize) => {
874
1047
  const victimIndex = oldestLowestIndex();
875
1048
  const victim = items[victimIndex];
876
1049
  if (!victim) break;
877
- if (RANK[item.priority] < RANK[victim.priority]) return;
1050
+ if (RANK[item.priority] < RANK[victim.priority]) {
1051
+ droppedIncoming++;
1052
+ return;
1053
+ }
878
1054
  items.splice(victimIndex, 1);
1055
+ droppedQueued++;
879
1056
  }
880
1057
  items.push(item);
1058
+ enqueued++;
881
1059
  };
882
1060
  const drain = (count) => items.splice(0, count);
883
1061
  return {
884
1062
  enqueue,
885
1063
  drain,
886
1064
  size: () => items.length,
1065
+ getStats: () => ({
1066
+ queued: items.length,
1067
+ enqueued,
1068
+ droppedIncoming,
1069
+ droppedQueued,
1070
+ droppedTotal: droppedIncoming + droppedQueued
1071
+ }),
887
1072
  clear: () => {
888
1073
  items.length = 0;
889
1074
  }
@@ -1058,7 +1243,14 @@ var createTransport = (options) => {
1058
1243
  enqueueSummary,
1059
1244
  flush,
1060
1245
  close,
1061
- size: () => queue.size() + (retryBatch?.items.length ?? 0)
1246
+ size: () => queue.size() + (retryBatch?.items.length ?? 0),
1247
+ getStats: () => {
1248
+ const queueStats = queue.getStats();
1249
+ return {
1250
+ ...queueStats,
1251
+ queued: queueStats.queued + (retryBatch?.items.length ?? 0)
1252
+ };
1253
+ }
1062
1254
  };
1063
1255
  };
1064
1256
 
@@ -1070,16 +1262,26 @@ var noopClient = () => ({
1070
1262
  flush: async () => {
1071
1263
  },
1072
1264
  close: async () => {
1073
- }
1265
+ },
1266
+ getStats: () => ({
1267
+ transport: {
1268
+ queued: 0,
1269
+ enqueued: 0,
1270
+ droppedIncoming: 0,
1271
+ droppedQueued: 0,
1272
+ droppedTotal: 0
1273
+ }
1274
+ })
1074
1275
  });
1075
1276
 
1076
1277
  // src/create-client/resolve-endpoint-urls.ts
1077
- var DEFAULT_API_URL = "https://rasputin-api.fly.dev";
1278
+ var DEFAULT_API_URL = "https://api.rasputinai.dev";
1078
1279
  var resolveEndpointUrls = (apiUrl = DEFAULT_API_URL) => {
1079
1280
  const base = apiUrl.replace(/\/+$/, "");
1080
1281
  return {
1081
1282
  ingestUrl: `${base}/ingest`,
1082
- deployUrl: `${base}/deploy`
1283
+ deployUrl: `${base}/deploy`,
1284
+ instrumentationManifestUrl: `${base}/instrumentationManifest`
1083
1285
  };
1084
1286
  };
1085
1287
 
@@ -1115,12 +1317,19 @@ var createClient = (options, hooks = {}) => {
1115
1317
  const release = detectRelease({ release: options.release });
1116
1318
  const environment = options.environment;
1117
1319
  const { ingestUrl, deployUrl } = resolveEndpointUrls(options.apiUrl);
1118
- const repoRoot = resolveRepoRoot(options);
1119
- if (!repoRoot) {
1120
- warnIfMissingRepoRoot();
1320
+ if (!release) {
1321
+ if (hooks.announceDeploy ?? true) {
1322
+ void announceDeploy({
1323
+ deployUrl,
1324
+ projectApiKey,
1325
+ environment,
1326
+ fetch: hooks.fetch
1327
+ });
1328
+ }
1121
1329
  return noopClient();
1122
1330
  }
1123
- warnIfSuspiciousRepoRoot(repoRoot);
1331
+ const repoRoot = resolveRepoRoot(options);
1332
+ if (repoRoot) warnIfSuspiciousRepoRoot(repoRoot);
1124
1333
  const MAX_STACK_RESOLVE_CACHE = 32;
1125
1334
  const stackResolveCache = /* @__PURE__ */ new Map();
1126
1335
  const stackCacheKey = (rawFrames) => rawFrames.map((frame) => `${frame.file}\0${frame.line}\0${frame.column}\0${frame.function ?? ""}`).join("\n");
@@ -1183,7 +1392,8 @@ var createClient = (options, hooks = {}) => {
1183
1392
  release,
1184
1393
  origin: "server",
1185
1394
  resolution: "none",
1186
- request: context?.request
1395
+ request: context?.request,
1396
+ runtimeState: context?.runtimeState
1187
1397
  });
1188
1398
  const decision = suppression.decide({
1189
1399
  errorType: event.type,
@@ -1221,12 +1431,45 @@ var createClient = (options, hooks = {}) => {
1221
1431
  } catch {
1222
1432
  }
1223
1433
  };
1224
- return { captureException, flush, close };
1434
+ return {
1435
+ captureException,
1436
+ flush,
1437
+ close,
1438
+ getStats: () => ({ transport: transport.getStats() })
1439
+ };
1225
1440
  } catch {
1226
1441
  return noopClient();
1227
1442
  }
1228
1443
  };
1444
+
1445
+ // src/instrumentation-manifest/instrumentation-manifest-types.ts
1446
+ var INSTRUMENTATION_MANIFEST_SCHEMA_VERSION = 2;
1447
+
1448
+ // src/instrumentation-manifest/upload-instrumentation-manifest.ts
1449
+ var uploadInstrumentationManifest = async (options) => {
1450
+ const release = options.release.trim();
1451
+ if (!release) return { ok: false };
1452
+ const fetchFn = options.fetch ?? globalThis.fetch;
1453
+ try {
1454
+ const response = await fetchFn(options.url, {
1455
+ method: "POST",
1456
+ headers: {
1457
+ Authorization: `Bearer ${options.projectApiKey}`,
1458
+ "Content-Type": "application/json",
1459
+ Accept: "application/json"
1460
+ },
1461
+ body: JSON.stringify({ release, manifest: options.manifest })
1462
+ });
1463
+ if (!response.ok) return { ok: false };
1464
+ const body = await response.json();
1465
+ const verification = body.payload?.verification;
1466
+ return verification ? { ok: true, verification } : { ok: false };
1467
+ } catch {
1468
+ return { ok: false };
1469
+ }
1470
+ };
1229
1471
  export {
1472
+ INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
1230
1473
  announceDeploy,
1231
1474
  createClient,
1232
1475
  createIngestEvent,
@@ -1237,5 +1480,13 @@ export {
1237
1480
  normalizeFrames,
1238
1481
  parseStack,
1239
1482
  repoRootFrom,
1240
- resolveSourceMaps
1483
+ resetSourceIdentityCache,
1484
+ resolveEndpointUrls,
1485
+ resolveRepoRoot,
1486
+ resolveSourceMaps,
1487
+ runtimeFunctionId,
1488
+ sourceDisplayPath,
1489
+ sourceLocatorFromFile,
1490
+ sourceLocatorKey,
1491
+ uploadInstrumentationManifest
1241
1492
  };