@rasputin-ai/node 0.4.1 → 0.5.0-alpha.1

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 (35) hide show
  1. package/README.md +185 -159
  2. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +2 -37
  3. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -1
  4. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +17 -0
  5. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -0
  6. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +40 -0
  7. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -0
  8. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +4 -1
  9. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  10. package/dist/auto-instrumentation/transform-source.d.ts +1 -1
  11. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  12. package/dist/execution-recorder/automatic-runtime.d.ts +12 -10
  13. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  14. package/dist/execution-recorder/call-aware-event-buffer.d.ts +2 -1
  15. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +1 -1
  16. package/dist/execution-recorder/execution-recorder-types.d.ts +17 -0
  17. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  18. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  19. package/dist/execution-recorder/function-plan.d.ts +26 -0
  20. package/dist/execution-recorder/function-plan.d.ts.map +1 -0
  21. package/dist/execution-recorder/recorder-memory-budget.d.ts +7 -1
  22. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  23. package/dist/execution-recorder/safe-serialize.d.ts +7 -0
  24. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  25. package/dist/index.js +439 -259
  26. package/dist/instrument/build.d.ts +2 -0
  27. package/dist/instrument/build.d.ts.map +1 -0
  28. package/dist/instrument/build.js +707 -0
  29. package/dist/instrument/bun.js +41 -21
  30. package/dist/instrument/node.js +41 -21
  31. package/dist/rasputin-init.d.ts +5 -5
  32. package/dist/rasputin-init.d.ts.map +1 -1
  33. package/dist/sdk-meta.d.ts +1 -1
  34. package/dist/sdk-meta.d.ts.map +1 -1
  35. package/package.json +10 -2
package/dist/index.js CHANGED
@@ -104,76 +104,38 @@ var runtimeLabel = () => {
104
104
 
105
105
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
106
106
  import { createHash } from "node:crypto";
107
+ import { existsSync, readFileSync } from "node:fs";
107
108
  import {
108
109
  INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
109
110
  sourceLocatorFromFile as sourceLocatorFromFile2
110
111
  } from "@rasputin-ai/core";
111
112
 
112
- // src/execution-recorder/automatic-runtime.ts
113
+ // src/execution-recorder/function-plan.ts
113
114
  import {
114
115
  runtimeFunctionId,
115
116
  sourceLocatorFromFile
116
117
  } from "@rasputin-ai/core";
117
- var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
118
- var AUTOMATIC_SOURCE_PREFIX = "rasputin-source-v2:";
119
- var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
120
- var decodeAutomaticFunctionSource = (sourceToken) => {
121
- if (!sourceToken.startsWith(AUTOMATIC_SOURCE_PREFIX)) return void 0;
122
- try {
123
- const parsed = JSON.parse(
124
- sourceToken.slice(AUTOMATIC_SOURCE_PREFIX.length)
125
- );
126
- if (typeof parsed.name !== "string" || !parsed.name) return void 0;
127
- const definition = {
128
- name: parsed.name,
129
- ...parsed.source ? { source: parsed.source } : {}
130
- };
131
- return { ...definition, functionId: runtimeFunctionId(definition) };
132
- } catch {
133
- return void 0;
134
- }
135
- };
136
- var getRegistry = () => {
137
- const existing = Reflect.get(globalThis, runtimeSymbol);
138
- if (existing?.__rasputinRuntimeRegistry && Array.isArray(existing.runtimes)) {
139
- return existing;
140
- }
141
- const runtimes = [];
142
- const registry = {
143
- __rasputinRuntimeRegistry: true,
144
- runtimes,
145
- run: (sourceToken, args, callback) => {
146
- const runtime = runtimes.at(-1);
147
- return runtime ? runtime.run(sourceToken, args, callback) : callback();
148
- }
149
- };
150
- Reflect.set(globalThis, runtimeSymbol, registry);
151
- return registry;
152
- };
153
- var installAutomaticExecutionRuntime = (execution) => {
154
- const registry = getRegistry();
155
- const definitions = /* @__PURE__ */ new Map();
156
- const runtime = {
157
- run: (sourceToken, args, callback) => {
158
- let definition = definitions.get(sourceToken);
159
- if (!definitions.has(sourceToken)) {
160
- definition = decodeAutomaticFunctionSource(sourceToken);
161
- definitions.set(sourceToken, definition);
162
- }
163
- return definition ? execution.runFunction(definition, args, callback) : callback();
164
- }
165
- };
166
- registry.runtimes.push(runtime);
167
- return () => {
168
- const index = registry.runtimes.lastIndexOf(runtime);
169
- if (index >= 0) registry.runtimes.splice(index, 1);
118
+ var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
119
+ var definitionFromEncodedPlan = (plan) => {
120
+ const [functionId, name, packageName, packageRelativePath, line, column] = plan;
121
+ const source = packageRelativePath ? {
122
+ ...packageName ? { packageName } : {},
123
+ packageRelativePath,
124
+ ...line == null ? {} : { line },
125
+ ...column == null ? {} : { column }
126
+ } : void 0;
127
+ return {
128
+ functionId,
129
+ name,
130
+ ...source ? { source } : {}
170
131
  };
171
132
  };
133
+ var estimatedCallBytesFor = (functionId) => EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
172
134
 
173
135
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
174
136
  var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v2";
175
137
  var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
176
- var getRegistry2 = () => {
138
+ var getRegistry = () => {
177
139
  const existing = Reflect.get(globalThis, registrySymbol);
178
140
  if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
179
141
  return existing;
@@ -192,20 +154,46 @@ var sourceAt = (filePath, line, column) => {
192
154
  return source ? { ...source, line, column } : void 0;
193
155
  };
194
156
  var stableId = (kind, ...parts) => `${kind}_${createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 24)}`;
157
+ var recordPrebuiltInstrumentationManifest = (manifest) => {
158
+ const registry = getRegistry();
159
+ registry.prebuilt = manifest;
160
+ registry.generation += 1;
161
+ registry.onDirty?.();
162
+ };
163
+ var loadInstrumentationManifestFile = (path) => {
164
+ try {
165
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
166
+ if (parsed.schema_version !== INSTRUMENTATION_MANIFEST_SCHEMA_VERSION) return false;
167
+ if (!Array.isArray(parsed.functions)) return false;
168
+ recordPrebuiltInstrumentationManifest(parsed);
169
+ return true;
170
+ } catch {
171
+ return false;
172
+ }
173
+ };
174
+ var loadInstrumentationManifestIfPresent = (path) => {
175
+ if (!path || !existsSync(path)) return;
176
+ loadInstrumentationManifestFile(path);
177
+ };
195
178
  var isInstrumentationManifestDirty = () => {
196
- const registry = getRegistry2();
197
- return registry.generation !== registry.uploadedGeneration && registry.files.size > 0;
179
+ const registry = getRegistry();
180
+ return registry.generation !== registry.uploadedGeneration && (registry.files.size > 0 || registry.prebuilt !== void 0);
181
+ };
182
+ var instrumentationManifestGeneration = () => getRegistry().generation;
183
+ var instrumentationManifestModuleCount = () => {
184
+ const registry = getRegistry();
185
+ if (registry.files.size > 0) return registry.files.size;
186
+ if (!registry.prebuilt) return 0;
187
+ return new Set(registry.prebuilt.functions.map((fn) => fn.source.packageRelativePath)).size;
198
188
  };
199
- var instrumentationManifestGeneration = () => getRegistry2().generation;
200
- var instrumentationManifestModuleCount = () => getRegistry2().files.size;
201
189
  var snapshotInstrumentationManifest = (options = {}) => {
202
190
  const functions = [];
203
191
  const callSites = [];
204
192
  const destructures = [];
205
- for (const delta of getRegistry2().files.values()) {
193
+ for (const delta of getRegistry().files.values()) {
206
194
  for (const fn of delta.functions) {
207
- const definition = decodeAutomaticFunctionSource(fn.sourceToken);
208
- if (!definition?.source) continue;
195
+ const definition = definitionFromEncodedPlan(fn.plan);
196
+ if (!definition.source) continue;
209
197
  functions.push({
210
198
  functionId: definition.functionId,
211
199
  source: definition.source,
@@ -218,19 +206,18 @@ var snapshotInstrumentationManifest = (options = {}) => {
218
206
  });
219
207
  }
220
208
  for (const site of delta.callSites) {
221
- const caller = decodeAutomaticFunctionSource(site.callerSourceToken);
222
209
  const source = sourceAt(site.filePath, site.line, site.column);
223
- if (!caller || !source) continue;
210
+ if (!source) continue;
224
211
  callSites.push({
225
212
  id: stableId(
226
213
  "cs",
227
- caller.functionId,
214
+ site.callerFunctionId,
228
215
  source.packageName ?? "",
229
216
  source.packageRelativePath,
230
217
  site.line,
231
218
  site.column
232
219
  ),
233
- callerFunctionId: caller.functionId,
220
+ callerFunctionId: site.callerFunctionId,
234
221
  calleeName: site.calleeName,
235
222
  calleeText: site.calleeText,
236
223
  source,
@@ -243,11 +230,10 @@ var snapshotInstrumentationManifest = (options = {}) => {
243
230
  });
244
231
  }
245
232
  for (const destructure of delta.destructures) {
246
- const caller = decodeAutomaticFunctionSource(destructure.callerSourceToken);
247
233
  const source = sourceAt(destructure.filePath, destructure.line, destructure.column);
248
- if (!caller || !source) continue;
234
+ if (!source) continue;
249
235
  destructures.push({
250
- callerFunctionId: caller.functionId,
236
+ callerFunctionId: destructure.callerFunctionId,
251
237
  sourceParam: destructure.sourceParam,
252
238
  source,
253
239
  line: destructure.line,
@@ -256,6 +242,18 @@ var snapshotInstrumentationManifest = (options = {}) => {
256
242
  });
257
243
  }
258
244
  }
245
+ const registry = getRegistry();
246
+ if (registry.prebuilt && registry.files.size === 0) {
247
+ const prebuilt = registry.prebuilt;
248
+ if (!options.sourceRoot && !options.sourceRoots) return prebuilt;
249
+ return {
250
+ ...prebuilt,
251
+ sourceRoots: {
252
+ ...options.sourceRoot ? { default: options.sourceRoot } : {},
253
+ packages: { ...options.sourceRoots ?? {} }
254
+ }
255
+ };
256
+ }
259
257
  if (functions.length === 0 && callSites.length === 0 && destructures.length === 0) {
260
258
  return void 0;
261
259
  }
@@ -274,11 +272,11 @@ var snapshotInstrumentationManifest = (options = {}) => {
274
272
  };
275
273
  };
276
274
  var markInstrumentationManifestUploaded = (generation) => {
277
- const registry = getRegistry2();
275
+ const registry = getRegistry();
278
276
  if (registry.generation === generation) registry.uploadedGeneration = generation;
279
277
  };
280
278
  var setInstrumentationManifestOnDirty = (onDirty) => {
281
- getRegistry2().onDirty = onDirty;
279
+ getRegistry().onDirty = onDirty;
282
280
  };
283
281
 
284
282
  // src/auto-instrumentation/schedule-instrumentation-manifest-upload.ts
@@ -395,6 +393,7 @@ var CallAwareEventBuffer = class {
395
393
  }
396
394
  items = [];
397
395
  openCallIds = /* @__PURE__ */ new Set();
396
+ retainedSummaries = /* @__PURE__ */ new WeakSet();
398
397
  dropped = 0;
399
398
  canStartCall() {
400
399
  return this.items.length + this.openCallIds.size + 2 <= this.capacity;
@@ -417,8 +416,12 @@ var CallAwareEventBuffer = class {
417
416
  return false;
418
417
  }
419
418
  this.items.push(event);
419
+ if (event.type === "function_calls_suppressed") this.retainedSummaries.add(event);
420
420
  return true;
421
421
  }
422
+ hasSummary(event) {
423
+ return this.retainedSummaries.has(event);
424
+ }
422
425
  forceCompletedCall(enter, terminal) {
423
426
  while (this.items.length + this.openCallIds.size + 2 > this.capacity) {
424
427
  if (!this.evictCompletedLeafOrSummary()) {
@@ -432,14 +435,13 @@ var CallAwareEventBuffer = class {
432
435
  values() {
433
436
  return [...this.items];
434
437
  }
435
- contains(target) {
436
- return this.items.includes(target);
437
- }
438
438
  evictCompletedLeafOrSummary() {
439
439
  const summaryIndex = this.items.findIndex(
440
440
  (event) => event.type === "function_calls_suppressed"
441
441
  );
442
442
  if (summaryIndex >= 0) {
443
+ const summary = this.items[summaryIndex];
444
+ if (summary) this.retainedSummaries.delete(summary);
443
445
  this.items.splice(summaryIndex, 1);
444
446
  this.dropped++;
445
447
  return true;
@@ -469,9 +471,7 @@ var CallAwareEventBuffer = class {
469
471
  };
470
472
 
471
473
  // src/execution-recorder/recorder-memory-budget.ts
472
- import { Buffer } from "node:buffer";
473
474
  var EXECUTION_BASE_ESTIMATED_BYTES = 1024;
474
- var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
475
475
  var SUMMARY_EVENT_BASE_ESTIMATED_BYTES = 256;
476
476
  var VALUE_HEAP_ESTIMATE_MULTIPLIER = 2;
477
477
  var MEMORY_BUDGET_VALUE_MARKER = {
@@ -525,12 +525,11 @@ var RecorderMemoryBudget = class {
525
525
  }
526
526
  return serialized.value;
527
527
  }
528
- reserveCall(allocation, functionId) {
528
+ reserveCall(allocation, estimatedBytes) {
529
529
  if (allocation.mode === "metadata-only") {
530
530
  this.dropEvents(allocation, 2);
531
531
  return false;
532
532
  }
533
- const estimatedBytes = EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
534
533
  if (this.reserve(allocation, estimatedBytes)) return true;
535
534
  this.markPressure(allocation, "metadata-only");
536
535
  this.dropEvents(allocation, 2);
@@ -590,7 +589,7 @@ var RecorderMemoryBudget = class {
590
589
 
591
590
  // src/execution-recorder/safe-serialize.ts
592
591
  import { Buffer as Buffer2 } from "node:buffer";
593
- var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
592
+ var DEFAULT_REDACT_KEYS = [
594
593
  "password",
595
594
  "token",
596
595
  "authorization",
@@ -598,7 +597,7 @@ var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
598
597
  "secret",
599
598
  "apikey",
600
599
  "creditcard"
601
- ]);
600
+ ];
602
601
  var defaults = {
603
602
  maxDepth: 3,
604
603
  maxObjectKeys: 30,
@@ -615,135 +614,155 @@ var typeMarker = (type, value) => ({
615
614
  __rasputin_type: type,
616
615
  ...value === void 0 ? {} : { value }
617
616
  });
618
- var redact = (item, redactKeys, seen) => {
619
- if (!item || typeof item !== "object") return item;
620
- if (seen.has(item)) return item;
621
- seen.add(item);
622
- if (Array.isArray(item)) {
623
- return item.map((entry) => redact(entry, redactKeys, seen));
624
- }
625
- for (const [key, child] of Object.entries(item)) {
626
- item[key] = redactKeys.has(key.toLowerCase()) ? "[RASPUTIN_REDACTED]" : redact(child, redactKeys, seen);
627
- }
628
- return item;
629
- };
630
- var errorValue = (error, depth, seen, limits) => ({
631
- __rasputin_type: "Error",
632
- name: error.name,
633
- message: serializeInner(error.message, depth + 1, seen, limits),
634
- ...error.stack ? { stack: serializeInner(error.stack, depth + 1, seen, limits) } : {}
617
+ var REDACTED = "[RASPUTIN_REDACTED]";
618
+ var compileLimits = (options) => ({
619
+ maxDepth: options.maxDepth ?? defaults.maxDepth,
620
+ maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
621
+ maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
622
+ maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
623
+ maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
635
624
  });
636
- var serializeInner = (value, depth, seen, limits) => {
637
- if (value === null || typeof value === "boolean" || typeof value === "number") return value;
638
- if (typeof value === "string") {
639
- if (value.length <= limits.maxStringLength) return value;
640
- return truncation("max_string_length", {
641
- __original_length: value.length,
642
- value: value.slice(0, limits.maxStringLength)
643
- });
644
- }
645
- if (typeof value === "undefined") return typeMarker("undefined");
646
- if (typeof value === "bigint") return typeMarker("bigint", value.toString());
647
- if (typeof value === "symbol") return typeMarker("symbol", String(value));
648
- if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
649
- if (depth >= limits.maxDepth) return truncation("max_depth");
650
- if (typeof value !== "object") return typeMarker(typeof value);
651
- if (seen.has(value)) return typeMarker("circular");
652
- seen.add(value);
653
- try {
654
- if (value instanceof Error) return errorValue(value, depth, seen, limits);
655
- if (value instanceof Date) return typeMarker("Date", value.toISOString());
656
- if (Buffer2.isBuffer(value)) {
657
- return typeMarker("Buffer", {
658
- byteLength: value.byteLength,
659
- base64: value.subarray(0, limits.maxStringLength).toString("base64"),
660
- ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
625
+ var compileRedactKeys = (options) => {
626
+ if (!options.redactKeys?.length) return new Set(DEFAULT_REDACT_KEYS);
627
+ return /* @__PURE__ */ new Set([...DEFAULT_REDACT_KEYS, ...options.redactKeys.map((key) => key.toLowerCase())]);
628
+ };
629
+ var createSafeSerializer = (options = {}) => {
630
+ const limits = compileLimits(options);
631
+ const redactKeys = compileRedactKeys(options);
632
+ const serializeInner = (value, depth, seen, metrics) => {
633
+ metrics.visitedNodes++;
634
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
635
+ if (typeof value === "string") {
636
+ if (value.length <= limits.maxStringLength) return value;
637
+ return truncation("max_string_length", {
638
+ __original_length: value.length,
639
+ value: value.slice(0, limits.maxStringLength)
661
640
  });
662
641
  }
663
- if (value instanceof Promise) return typeMarker("Promise");
664
- if (value instanceof Map) {
665
- const entries = [];
666
- for (const entry of value.entries()) {
667
- if (entries.length >= limits.maxArrayElements) break;
668
- entries.push([
669
- serializeInner(entry[0], depth + 1, seen, limits),
670
- serializeInner(entry[1], depth + 1, seen, limits)
671
- ]);
642
+ if (typeof value === "undefined") return typeMarker("undefined");
643
+ if (typeof value === "bigint") return typeMarker("bigint", value.toString());
644
+ if (typeof value === "symbol") return typeMarker("symbol", String(value));
645
+ if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
646
+ if (depth >= limits.maxDepth) return truncation("max_depth");
647
+ if (typeof value !== "object") return typeMarker(typeof value);
648
+ const tracking = seen ?? /* @__PURE__ */ new WeakSet();
649
+ if (tracking.has(value)) return typeMarker("circular");
650
+ tracking.add(value);
651
+ try {
652
+ if (value instanceof Error) {
653
+ return {
654
+ __rasputin_type: "Error",
655
+ name: value.name,
656
+ message: serializeInner(value.message, depth + 1, tracking, metrics),
657
+ ...value.stack ? { stack: serializeInner(value.stack, depth + 1, tracking, metrics) } : {}
658
+ };
672
659
  }
673
- return {
674
- __rasputin_type: "Map",
675
- entries,
676
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
677
- };
660
+ if (value instanceof Date) return typeMarker("Date", value.toISOString());
661
+ if (Buffer2.isBuffer(value)) {
662
+ return typeMarker("Buffer", {
663
+ byteLength: value.byteLength,
664
+ base64: value.subarray(0, limits.maxStringLength).toString("base64"),
665
+ ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
666
+ });
667
+ }
668
+ if (value instanceof Promise) return typeMarker("Promise");
669
+ if (value instanceof Map) {
670
+ const entries = [];
671
+ for (const entry of value.entries()) {
672
+ if (entries.length >= limits.maxArrayElements) break;
673
+ entries.push([
674
+ serializeInner(entry[0], depth + 1, tracking, metrics),
675
+ serializeInner(entry[1], depth + 1, tracking, metrics)
676
+ ]);
677
+ }
678
+ return {
679
+ __rasputin_type: "Map",
680
+ entries,
681
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
682
+ };
683
+ }
684
+ if (value instanceof Set) {
685
+ const items = [];
686
+ for (const item of value.values()) {
687
+ if (items.length >= limits.maxArrayElements) break;
688
+ items.push(serializeInner(item, depth + 1, tracking, metrics));
689
+ }
690
+ return {
691
+ __rasputin_type: "Set",
692
+ values: items,
693
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
694
+ };
695
+ }
696
+ if (Array.isArray(value)) {
697
+ const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
698
+ const items = [];
699
+ for (let index = 0; index < maxItems; index++) {
700
+ items.push(serializeInner(value[index], depth + 1, tracking, metrics));
701
+ }
702
+ if (value.length > limits.maxArrayElements) {
703
+ items.push(truncation("max_array_elements", { __original_length: value.length }));
704
+ }
705
+ return items;
706
+ }
707
+ const prototype = Object.getPrototypeOf(value);
708
+ const serialized = {};
709
+ if (prototype && prototype !== Object.prototype) {
710
+ serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
711
+ }
712
+ const keys = Object.keys(value);
713
+ const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
714
+ for (let index = 0; index < maxKeys; index++) {
715
+ const key = keys[index];
716
+ if (key === void 0) continue;
717
+ if (redactKeys.has(key.toLowerCase())) {
718
+ serialized[key] = REDACTED;
719
+ continue;
720
+ }
721
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
722
+ serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, tracking, metrics) : typeMarker("accessor");
723
+ }
724
+ if (keys.length > limits.maxObjectKeys) {
725
+ Object.assign(
726
+ serialized,
727
+ truncation("max_object_keys", { __original_length: keys.length })
728
+ );
729
+ }
730
+ return serialized;
731
+ } catch (error) {
732
+ return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
733
+ } finally {
734
+ tracking.delete(value);
678
735
  }
679
- if (value instanceof Set) {
680
- const items = [];
681
- for (const item of value.values()) {
682
- if (items.length >= limits.maxArrayElements) break;
683
- items.push(serializeInner(item, depth + 1, seen, limits));
736
+ };
737
+ const serializeWithBytes = (value) => {
738
+ const metrics = { visitedNodes: 0 };
739
+ try {
740
+ const serialized = serializeInner(value, 0, void 0, metrics);
741
+ const bytes = Buffer2.byteLength(JSON.stringify(serialized), "utf8");
742
+ if (bytes <= limits.maxSerializedValueBytes) {
743
+ return { value: serialized, bytes, visitedNodes: metrics.visitedNodes };
684
744
  }
745
+ const bounded = truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
685
746
  return {
686
- __rasputin_type: "Set",
687
- values: items,
688
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
747
+ value: bounded,
748
+ bytes: Buffer2.byteLength(JSON.stringify(bounded), "utf8"),
749
+ visitedNodes: metrics.visitedNodes
750
+ };
751
+ } catch {
752
+ const failed = typeMarker("unserializable");
753
+ return {
754
+ value: failed,
755
+ bytes: Buffer2.byteLength(JSON.stringify(failed), "utf8"),
756
+ visitedNodes: metrics.visitedNodes
689
757
  };
690
758
  }
691
- if (Array.isArray(value)) {
692
- const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
693
- const items = value.slice(0, maxItems).map((item) => serializeInner(item, depth + 1, seen, limits));
694
- if (value.length > limits.maxArrayElements) {
695
- items.push(truncation("max_array_elements", { __original_length: value.length }));
696
- }
697
- return items;
698
- }
699
- const prototype = Object.getPrototypeOf(value);
700
- const serialized = {};
701
- if (prototype && prototype !== Object.prototype) {
702
- serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
703
- }
704
- const keys = Object.keys(value);
705
- const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
706
- for (const key of keys.slice(0, maxKeys)) {
707
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
708
- serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, seen, limits) : typeMarker("accessor");
709
- }
710
- if (keys.length > limits.maxObjectKeys) {
711
- Object.assign(serialized, truncation("max_object_keys", { __original_length: keys.length }));
712
- }
713
- return serialized;
714
- } catch (error) {
715
- return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
716
- } finally {
717
- seen.delete(value);
718
- }
719
- };
720
- var safeSerializeWithBytes = (value, options = {}) => {
721
- const limits = {
722
- maxDepth: options.maxDepth ?? defaults.maxDepth,
723
- maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
724
- maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
725
- maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
726
- maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
727
759
  };
728
- try {
729
- const redactKeys = /* @__PURE__ */ new Set([
730
- ...DEFAULT_REDACT_KEYS,
731
- ...(options.redactKeys ?? []).map((key) => key.toLowerCase())
732
- ]);
733
- const serialized = serializeInner(value, 0, /* @__PURE__ */ new WeakSet(), limits);
734
- const redacted = redact(serialized, redactKeys, /* @__PURE__ */ new WeakSet());
735
- const bytes = Buffer2.byteLength(JSON.stringify(redacted), "utf8");
736
- const bounded = bytes <= limits.maxSerializedValueBytes ? redacted : truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
737
- return {
738
- value: bounded,
739
- bytes: bounded === redacted ? bytes : Buffer2.byteLength(JSON.stringify(bounded), "utf8")
740
- };
741
- } catch {
742
- const value2 = typeMarker("unserializable");
743
- return { value: value2, bytes: Buffer2.byteLength(JSON.stringify(value2), "utf8") };
744
- }
760
+ return {
761
+ serialize: (value) => serializeWithBytes(value).value,
762
+ serializeWithBytes
763
+ };
745
764
  };
746
- var safeSerialize = (value, options = {}) => safeSerializeWithBytes(value, options).value;
765
+ var defaultSerializer = createSafeSerializer();
747
766
 
748
767
  // src/execution-recorder/source-exclusions.ts
749
768
  var sourcePathFrom = (definition) => definition.source?.packageRelativePath.replaceAll("\\", "/") ?? definition.name;
@@ -896,6 +915,30 @@ var observeRecordedCallback = (recording, recorderStartedAtNs, callback, onRetur
896
915
  recordExitWork(callbackEndedAtNs);
897
916
  return result;
898
917
  };
918
+ var emptyStats = {
919
+ functionCallsObserved: 0,
920
+ functionCallsCaptured: 0,
921
+ functionCallsExcluded: 0,
922
+ serializedValues: 0,
923
+ serializedValueNodes: 0,
924
+ serializedValueBytes: 0,
925
+ errorSnapshots: 0,
926
+ successfulExecutionsDiscarded: 0,
927
+ truncatedEvents: 0,
928
+ repeatedCallsSuppressed: 0,
929
+ activeExecutions: 0,
930
+ peakActiveExecutions: 0,
931
+ memoryPressureExecutions: 0,
932
+ memoryPressureDegradations: 0,
933
+ valuesDroppedByMemoryBudget: 0,
934
+ eventsDroppedByMemoryBudget: 0,
935
+ partialErrorSnapshots: 0,
936
+ measuredExecutions: 0,
937
+ totalInlineWallTimeMs: 0,
938
+ maxInlineWallTimeMs: 0,
939
+ activeEstimatedBytes: 0,
940
+ peakActiveEstimatedBytes: 0
941
+ };
899
942
  var noOpExecution = {
900
943
  createScope: () => ({
901
944
  run: (callback) => callback(),
@@ -906,25 +949,10 @@ var noOpExecution = {
906
949
  run: (_metadata, callback) => callback(),
907
950
  getErrorState: () => void 0,
908
951
  runFunction: (_functionId, _args, callback) => callback(),
952
+ runFunctionPlans: (_plans, _planIndex, _args, callback) => callback(),
953
+ runPreparedFunction: (_plan, _args, callback) => callback(),
909
954
  trace: (_functionId, fn) => fn,
910
- getStats: () => ({
911
- errorSnapshots: 0,
912
- successfulExecutionsDiscarded: 0,
913
- truncatedEvents: 0,
914
- repeatedCallsSuppressed: 0,
915
- activeExecutions: 0,
916
- peakActiveExecutions: 0,
917
- activeEstimatedBytes: 0,
918
- peakActiveEstimatedBytes: 0,
919
- memoryPressureExecutions: 0,
920
- memoryPressureDegradations: 0,
921
- valuesDroppedByMemoryBudget: 0,
922
- eventsDroppedByMemoryBudget: 0,
923
- partialErrorSnapshots: 0,
924
- measuredExecutions: 0,
925
- totalInlineWallTimeMs: 0,
926
- maxInlineWallTimeMs: 0
927
- })
955
+ getStats: () => ({ ...emptyStats })
928
956
  };
929
957
  var mergeExecutionMetadata = (target, metadata) => {
930
958
  if (metadata.kind) target.kind = metadata.kind;
@@ -947,10 +975,20 @@ var createExecutionRecorder = (options) => {
947
975
  maxActiveMemoryBytes,
948
976
  options.maxSerializedValueBytes ?? 8 * 1024
949
977
  );
978
+ const serializer = createSafeSerializer(options);
950
979
  const excludesSource = createSourceExclusionMatcher(options.excludeSources);
951
980
  let executionCounter = 0;
981
+ let nextRuntimePlanId = 1;
982
+ const internedPlans = /* @__PURE__ */ new Map();
983
+ const preparedModules = /* @__PURE__ */ new WeakMap();
952
984
  const errorExecutions = /* @__PURE__ */ new WeakMap();
953
985
  const stats = {
986
+ functionCallsObserved: 0,
987
+ functionCallsCaptured: 0,
988
+ functionCallsExcluded: 0,
989
+ serializedValues: 0,
990
+ serializedValueNodes: 0,
991
+ serializedValueBytes: 0,
954
992
  errorSnapshots: 0,
955
993
  successfulExecutionsDiscarded: 0,
956
994
  truncatedEvents: 0,
@@ -968,9 +1006,52 @@ var createExecutionRecorder = (options) => {
968
1006
  stats.totalInlineWallTimeMs = Math.round((stats.totalInlineWallTimeMs + inlineWallTimeMs) * 1e4) / 1e4;
969
1007
  stats.maxInlineWallTimeMs = Math.max(stats.maxInlineWallTimeMs, inlineWallTimeMs);
970
1008
  };
971
- const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => safeSerializeWithBytes(value, options));
1009
+ const serializeAndCount = (value) => {
1010
+ const serialized = serializer.serializeWithBytes(value);
1011
+ stats.serializedValues++;
1012
+ stats.serializedValueNodes += serialized.visitedNodes;
1013
+ stats.serializedValueBytes += serialized.bytes;
1014
+ return serialized;
1015
+ };
1016
+ const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => serializeAndCount(value));
1017
+ const preparePlan = (definition) => {
1018
+ const existing = internedPlans.get(definition.functionId);
1019
+ if (existing) return existing;
1020
+ const plan = {
1021
+ runtimePlanId: nextRuntimePlanId++,
1022
+ definition,
1023
+ functionId: definition.functionId,
1024
+ excluded: excludesSource(definition),
1025
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1026
+ };
1027
+ internedPlans.set(definition.functionId, plan);
1028
+ return plan;
1029
+ };
1030
+ const preparedPlansFor = (plans) => {
1031
+ const cached = preparedModules.get(plans);
1032
+ if (cached) return cached;
1033
+ const prepared = plans.map((encoded) => {
1034
+ const definition = definitionFromEncodedPlan(encoded);
1035
+ return {
1036
+ runtimePlanId: nextRuntimePlanId++,
1037
+ definition,
1038
+ functionId: definition.functionId,
1039
+ excluded: excludesSource(definition),
1040
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1041
+ };
1042
+ });
1043
+ preparedModules.set(plans, prepared);
1044
+ return prepared;
1045
+ };
972
1046
  const stateFrom = (recording, endedAtNs = process.hrtime.bigint()) => {
973
1047
  const recorderStartedAtNs = process.hrtime.bigint();
1048
+ for (const byParent of recording.repeatedCalls.values()) {
1049
+ for (const group of byParent.values()) {
1050
+ if (group.summary) {
1051
+ group.summary.totalDurationMs = roundedMilliseconds(group.totalDurationNs);
1052
+ }
1053
+ }
1054
+ }
974
1055
  const events = recording.events.values().map((event) => event.type === "function_calls_suppressed" ? { ...event } : event);
975
1056
  const referencedFunctionIds = new Set(
976
1057
  events.flatMap(
@@ -1026,15 +1107,15 @@ var createExecutionRecorder = (options) => {
1026
1107
  recording.events.append(event);
1027
1108
  countDroppedEvents(recording, droppedBefore);
1028
1109
  };
1029
- const repeatedCallGroup = (recording, functionId, parentCallId) => {
1030
- let byParent = recording.repeatedCalls.get(functionId);
1110
+ const repeatedCallGroup = (recording, runtimePlanId, parentCallId) => {
1111
+ let byParent = recording.repeatedCalls.get(runtimePlanId);
1031
1112
  if (!byParent) {
1032
1113
  byParent = /* @__PURE__ */ new Map();
1033
- recording.repeatedCalls.set(functionId, byParent);
1114
+ recording.repeatedCalls.set(runtimePlanId, byParent);
1034
1115
  }
1035
1116
  let group = byParent.get(parentCallId);
1036
1117
  if (!group) {
1037
- group = { observedCalls: 0 };
1118
+ group = { observedCalls: 0, totalDurationNs: 0n };
1038
1119
  byParent.set(parentCallId, group);
1039
1120
  }
1040
1121
  return group;
@@ -1052,14 +1133,14 @@ var createExecutionRecorder = (options) => {
1052
1133
  return {
1053
1134
  // Keep the bounded thrown value even after surrounding capture degrades. A partial
1054
1135
  // snapshot without the exception itself would not be useful for investigation.
1055
- value: safeSerialize(error, options),
1136
+ value: serializeAndCount(error).value,
1056
1137
  commit: () => {
1057
1138
  if (key) recording.capturedErrors.set(key, callId);
1058
1139
  }
1059
1140
  };
1060
1141
  };
1061
- const captureCompletedThrow = (recording, functionId, args, parentCallId, startedAtNs, startedAtMs, error) => {
1062
- if (!memoryBudget.reserveCall(recording.memory, functionId)) {
1142
+ const captureCompletedThrow = (recording, plan, args, parentCallId, startedAtNs, startedAtMs, error) => {
1143
+ if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1063
1144
  rememberError(error, recording);
1064
1145
  return;
1065
1146
  }
@@ -1068,7 +1149,7 @@ var createExecutionRecorder = (options) => {
1068
1149
  const enter = {
1069
1150
  type: "function_enter",
1070
1151
  callId,
1071
- functionId,
1152
+ functionId: plan.functionId,
1072
1153
  timestampMs: startedAtMs,
1073
1154
  args: Array.from(args, (argument) => captureValue(recording, argument)),
1074
1155
  ...parentCallId === void 0 ? {} : { parentCallId }
@@ -1082,13 +1163,17 @@ var createExecutionRecorder = (options) => {
1082
1163
  };
1083
1164
  const droppedBefore = recording.events.dropped;
1084
1165
  const retained = recording.events.forceCompletedCall(enter, terminal);
1085
- if (retained) capturedError.commit();
1166
+ if (retained) {
1167
+ stats.functionCallsCaptured++;
1168
+ capturedError.commit();
1169
+ }
1086
1170
  countDroppedEvents(recording, droppedBefore);
1087
1171
  rememberError(error, recording);
1088
1172
  };
1089
- const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, durationMs) => {
1173
+ const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, startedAtNs, endedAtNs) => {
1090
1174
  stats.repeatedCallsSuppressed++;
1091
- const endedAtMs = startedAtMs + durationMs;
1175
+ group.totalDurationNs += endedAtNs - startedAtNs;
1176
+ const endedAtMs = elapsedMs(recording.startedAtNs, endedAtNs);
1092
1177
  if (!group.summary) {
1093
1178
  group.summary = {
1094
1179
  type: "function_calls_suppressed",
@@ -1096,7 +1181,7 @@ var createExecutionRecorder = (options) => {
1096
1181
  timestampMs: startedAtMs,
1097
1182
  lastTimestampMs: endedAtMs,
1098
1183
  suppressedCallCount: 1,
1099
- totalDurationMs: durationMs,
1184
+ totalDurationMs: 0,
1100
1185
  ...parentCallId === void 0 ? {} : { parentCallId }
1101
1186
  };
1102
1187
  appendStandaloneEvent(recording, group.summary);
@@ -1104,8 +1189,9 @@ var createExecutionRecorder = (options) => {
1104
1189
  }
1105
1190
  group.summary.lastTimestampMs = endedAtMs;
1106
1191
  group.summary.suppressedCallCount++;
1107
- group.summary.totalDurationMs = Math.round((group.summary.totalDurationMs + durationMs) * 1e4) / 1e4;
1108
- if (!recording.events.contains(group.summary)) appendStandaloneEvent(recording, group.summary);
1192
+ if (!recording.events.hasSummary(group.summary)) {
1193
+ appendStandaloneEvent(recording, group.summary);
1194
+ }
1109
1195
  };
1110
1196
  const finish = (store, metadata) => {
1111
1197
  const { recording } = store;
@@ -1202,48 +1288,90 @@ var createExecutionRecorder = (options) => {
1202
1288
  return void 0;
1203
1289
  }
1204
1290
  };
1205
- const runFunction = (input, args, callback) => {
1291
+ const runPreparedFunction = (plan, args, callback) => {
1206
1292
  const store = storage.getStore();
1207
1293
  if (!store || store.recording.finished) return callback();
1208
- const definition = normalizeFunctionDefinition(input);
1209
- const functionId = definition.functionId;
1294
+ stats.functionCallsObserved++;
1210
1295
  const recorderStartedAtNs = process.hrtime.bigint();
1211
- if (store.suppressCapture || excludesSource(definition)) {
1296
+ if (store.suppressCapture || plan.excluded) {
1297
+ stats.functionCallsExcluded++;
1212
1298
  return storage.run({ ...store, suppressCapture: true }, () => {
1213
1299
  addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
1214
1300
  return callback();
1215
1301
  });
1216
1302
  }
1217
- store.recording.functions.set(functionId, definition);
1218
1303
  const parentCallId = store.currentCallId;
1219
- const group = repeatedCallGroup(store.recording, functionId, parentCallId);
1304
+ const group = repeatedCallGroup(store.recording, plan.runtimePlanId, parentCallId);
1220
1305
  group.observedCalls++;
1306
+ if (group.observedCalls === 1) {
1307
+ store.recording.functions.set(plan.functionId, plan.definition);
1308
+ }
1221
1309
  if (group.observedCalls > maxCapturedCallsPerFunction) {
1222
1310
  const startedAtNs2 = process.hrtime.bigint();
1223
1311
  const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs2);
1224
- const captureSuccess = () => recordSuppressedCall(
1225
- store.recording,
1226
- group,
1227
- functionId,
1228
- parentCallId,
1229
- startedAtMs,
1230
- elapsedMs(startedAtNs2)
1231
- );
1232
- return observeRecordedCallback(
1233
- store.recording,
1234
- recorderStartedAtNs,
1235
- callback,
1236
- captureSuccess,
1237
- (error) => captureCompletedThrow(
1312
+ try {
1313
+ const result = callback();
1314
+ if (isPromiseLike(result)) {
1315
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1316
+ return result.then(
1317
+ (value) => {
1318
+ const callbackEndedAtNs2 = process.hrtime.bigint();
1319
+ recordSuppressedCall(
1320
+ store.recording,
1321
+ group,
1322
+ plan.functionId,
1323
+ parentCallId,
1324
+ startedAtMs,
1325
+ startedAtNs2,
1326
+ callbackEndedAtNs2
1327
+ );
1328
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
1329
+ return value;
1330
+ },
1331
+ (error) => {
1332
+ const callbackEndedAtNs2 = process.hrtime.bigint();
1333
+ captureCompletedThrow(
1334
+ store.recording,
1335
+ plan,
1336
+ args,
1337
+ parentCallId,
1338
+ startedAtNs2,
1339
+ startedAtMs,
1340
+ error
1341
+ );
1342
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
1343
+ throw error;
1344
+ }
1345
+ );
1346
+ }
1347
+ const callbackEndedAtNs = process.hrtime.bigint();
1348
+ recordSuppressedCall(
1349
+ store.recording,
1350
+ group,
1351
+ plan.functionId,
1352
+ parentCallId,
1353
+ startedAtMs,
1354
+ startedAtNs2,
1355
+ callbackEndedAtNs
1356
+ );
1357
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1358
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
1359
+ return result;
1360
+ } catch (error) {
1361
+ const callbackEndedAtNs = process.hrtime.bigint();
1362
+ captureCompletedThrow(
1238
1363
  store.recording,
1239
- functionId,
1364
+ plan,
1240
1365
  args,
1241
1366
  parentCallId,
1242
1367
  startedAtNs2,
1243
1368
  startedAtMs,
1244
1369
  error
1245
- )
1246
- );
1370
+ );
1371
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1372
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
1373
+ throw error;
1374
+ }
1247
1375
  }
1248
1376
  const startedAtNs = process.hrtime.bigint();
1249
1377
  if (!store.recording.events.canStartCall()) {
@@ -1262,7 +1390,7 @@ var createExecutionRecorder = (options) => {
1262
1390
  captureSuccess,
1263
1391
  (error) => captureCompletedThrow(
1264
1392
  store.recording,
1265
- functionId,
1393
+ plan,
1266
1394
  args,
1267
1395
  parentCallId,
1268
1396
  startedAtNs,
@@ -1272,7 +1400,7 @@ var createExecutionRecorder = (options) => {
1272
1400
  )
1273
1401
  );
1274
1402
  }
1275
- if (!memoryBudget.reserveCall(store.recording.memory, functionId)) {
1403
+ if (!memoryBudget.reserveCall(store.recording.memory, plan.estimatedCallBytes)) {
1276
1404
  return observeRecordedCallback(
1277
1405
  store.recording,
1278
1406
  recorderStartedAtNs,
@@ -1286,7 +1414,7 @@ var createExecutionRecorder = (options) => {
1286
1414
  const enter = {
1287
1415
  type: "function_enter",
1288
1416
  callId,
1289
- functionId,
1417
+ functionId: plan.functionId,
1290
1418
  timestampMs: elapsedMs(store.recording.startedAtNs),
1291
1419
  args: Array.from(args, (argument) => captureValue(store.recording, argument)),
1292
1420
  ...parentCallId === void 0 ? {} : { parentCallId }
@@ -1307,6 +1435,7 @@ var createExecutionRecorder = (options) => {
1307
1435
  durationMs: elapsedMs(startedAtNs),
1308
1436
  returnValue: captureValue(store.recording, value)
1309
1437
  });
1438
+ stats.functionCallsCaptured++;
1310
1439
  },
1311
1440
  (error) => {
1312
1441
  const capturedError = serializedError(store.recording, error, callId);
@@ -1317,16 +1446,23 @@ var createExecutionRecorder = (options) => {
1317
1446
  durationMs: elapsedMs(startedAtNs),
1318
1447
  error: capturedError.value
1319
1448
  });
1449
+ stats.functionCallsCaptured++;
1320
1450
  capturedError.commit();
1321
1451
  rememberError(error, store.recording);
1322
1452
  }
1323
1453
  )
1324
1454
  );
1325
1455
  };
1456
+ const runFunction = (input, args, callback) => runPreparedFunction(preparePlan(normalizeFunctionDefinition(input)), args, callback);
1457
+ const runFunctionPlans = (plans, planIndex, args, callback) => {
1458
+ const plan = preparedPlansFor(plans)[planIndex];
1459
+ return plan ? runPreparedFunction(plan, args, callback) : callback();
1460
+ };
1326
1461
  function trace(input, fn) {
1327
1462
  const definition = typeof input === "string" ? manualFunctionDefinition(input) : { ...input, functionId: runtimeFunctionId2(input) };
1463
+ const plan = preparePlan(definition);
1328
1464
  return function traced(...args) {
1329
- return runFunction(definition, args, () => fn.apply(this, args));
1465
+ return runPreparedFunction(plan, args, () => fn.apply(this, args));
1330
1466
  };
1331
1467
  }
1332
1468
  return {
@@ -1334,11 +1470,51 @@ var createExecutionRecorder = (options) => {
1334
1470
  run,
1335
1471
  getErrorState,
1336
1472
  runFunction,
1473
+ runFunctionPlans,
1474
+ runPreparedFunction,
1337
1475
  trace,
1338
1476
  getStats: () => ({ ...stats, ...memoryBudget.getStats() })
1339
1477
  };
1340
1478
  };
1341
1479
 
1480
+ // src/execution-recorder/automatic-runtime.ts
1481
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
1482
+ var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
1483
+ var createRegistry = () => {
1484
+ const runtimes = [];
1485
+ return {
1486
+ __rasputinRuntimeRegistry: true,
1487
+ runtimes,
1488
+ register(plans) {
1489
+ return (planIndex, args, callback) => {
1490
+ const runtime = runtimes.at(-1);
1491
+ return runtime ? runtime.run(plans, planIndex, args, callback) : callback();
1492
+ };
1493
+ }
1494
+ };
1495
+ };
1496
+ var getAutomaticRuntimeRegistry = () => {
1497
+ const existing = Reflect.get(globalThis, runtimeSymbol);
1498
+ if (existing && Array.isArray(existing.runtimes) && typeof existing.register === "function") {
1499
+ existing.__rasputinRuntimeRegistry = true;
1500
+ return existing;
1501
+ }
1502
+ const registry = createRegistry();
1503
+ Reflect.set(globalThis, runtimeSymbol, registry);
1504
+ return registry;
1505
+ };
1506
+ var installAutomaticExecutionRuntime = (execution) => {
1507
+ const registry = getAutomaticRuntimeRegistry();
1508
+ const runtime = {
1509
+ run: (plans, planIndex, args, callback) => execution.runFunctionPlans(plans, planIndex, args, callback)
1510
+ };
1511
+ registry.runtimes.push(runtime);
1512
+ return () => {
1513
+ const index = registry.runtimes.lastIndexOf(runtime);
1514
+ if (index >= 0) registry.runtimes.splice(index, 1);
1515
+ };
1516
+ };
1517
+
1342
1518
  // src/install-global-handlers.ts
1343
1519
  var removeProcessListener = process.off.bind(process);
1344
1520
  var installGlobalHandlers = (client, options = {}) => {
@@ -1388,6 +1564,7 @@ var installGlobalHandlers = (client, options = {}) => {
1388
1564
  };
1389
1565
 
1390
1566
  // src/rasputin-init.ts
1567
+ import { join } from "node:path";
1391
1568
  import {
1392
1569
  createClient,
1393
1570
  isClientEnabled
@@ -1395,7 +1572,7 @@ import {
1395
1572
 
1396
1573
  // src/sdk-meta.ts
1397
1574
  var SDK_NAME = "@rasputin-ai/node";
1398
- var SDK_VERSION = "0.4.1";
1575
+ var SDK_VERSION = "0.5.0-alpha.1";
1399
1576
 
1400
1577
  // src/rasputin-init.ts
1401
1578
  var withExecution = (client, execution, installRuntime, manifest) => {
@@ -1436,6 +1613,9 @@ var RasputinInit = (options) => {
1436
1613
  ...options.executionRecorder,
1437
1614
  enabled: recorderEnabled
1438
1615
  });
1616
+ loadInstrumentationManifestIfPresent(
1617
+ options.instrumentationManifestPath ?? process.env.RASPUTIN_INSTRUMENTATION_MANIFEST ?? join(process.cwd(), ".rasputin", "instrumentation-manifest.json")
1618
+ );
1439
1619
  const wrappedClient = withExecution(client, execution, recorderEnabled, {
1440
1620
  projectApiKey: options.projectApiKey,
1441
1621
  release: options.release,