@loadstrike/loadstrike-sdk 1.0.21101 → 1.0.21401

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.
@@ -0,0 +1,709 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _LoadStrikeAutopilotResult_httpRequest;
13
+ import fs from "node:fs";
14
+ import { LoadStrikeResponse, LoadStrikeScenario, LoadStrikeSimulation, LoadStrikeThreshold } from "./runtime.js";
15
+ import { LoadStrikeAutopilotReadiness } from "./autopilot-contracts.js";
16
+ const REDACTED = "[REDACTED]";
17
+ const ENV_MARKER_PREFIX = "${LOADSTRIKE_ENV:";
18
+ const ENV_MARKER_SUFFIX = "}";
19
+ const TRACE_TO_TEST_AUTOPILOT_FEATURE = "autopilot.trace_to_test";
20
+ const SECRET_KEYS = new Set([
21
+ "authorization",
22
+ "proxy_authorization",
23
+ "proxy-authorization",
24
+ "cookie",
25
+ "set_cookie",
26
+ "set-cookie",
27
+ "x_api_key",
28
+ "x-api-key",
29
+ "token",
30
+ "access_token",
31
+ "refresh_token",
32
+ "password",
33
+ "secret",
34
+ "client_secret",
35
+ "api_key",
36
+ "apikey"
37
+ ]);
38
+ export class LoadStrikeAutopilotResult {
39
+ constructor(values) {
40
+ _LoadStrikeAutopilotResult_httpRequest.set(this, void 0);
41
+ this.Readiness = values.readiness;
42
+ this.Plan = values.plan;
43
+ this.Endpoints = values.endpoints;
44
+ this.Redactions = values.redactions;
45
+ this.Warnings = values.warnings;
46
+ this.ReadinessFailures = values.readinessFailures;
47
+ this.PreviewReport = undefined;
48
+ this.ObservedLatencyMs = values.observedLatencyMs;
49
+ __classPrivateFieldSet(this, _LoadStrikeAutopilotResult_httpRequest, values.httpRequest, "f");
50
+ }
51
+ buildScenario() {
52
+ if (this.Readiness !== LoadStrikeAutopilotReadiness.Ready) {
53
+ throw new Error(`Autopilot result is not ready to build a scenario. Current readiness: ${this.Readiness}.`);
54
+ }
55
+ if (!__classPrivateFieldGet(this, _LoadStrikeAutopilotResult_httpRequest, "f")) {
56
+ throw new Error("Autopilot result does not contain a runnable HTTP request.");
57
+ }
58
+ const request = resolveHttpRequestSecrets(__classPrivateFieldGet(this, _LoadStrikeAutopilotResult_httpRequest, "f"));
59
+ let scenario = LoadStrikeScenario
60
+ .create(this.Plan.Scenario.Name || "autopilot-starter", async () => {
61
+ try {
62
+ const response = await fetch(request.url, {
63
+ method: request.method,
64
+ headers: request.headers,
65
+ body: request.body
66
+ });
67
+ const body = await response.arrayBuffer();
68
+ const statusCode = String(response.status);
69
+ return response.ok
70
+ ? LoadStrikeResponse.ok(statusCode, body.byteLength)
71
+ : LoadStrikeResponse.fail(statusCode, `HTTP replay returned ${statusCode}.`, body.byteLength);
72
+ }
73
+ catch (error) {
74
+ return LoadStrikeResponse.fail("autopilot_http_error", error instanceof Error ? error.message : String(error));
75
+ }
76
+ })
77
+ .withoutWarmUp()
78
+ .withLoadSimulations(LoadStrikeSimulation.iterationsForConstant(1, 1));
79
+ const thresholds = this.Plan.ThresholdSuggestions.map(compileThreshold);
80
+ if (thresholds.length > 0) {
81
+ scenario = scenario.withThresholds(...thresholds);
82
+ }
83
+ return scenario.__loadStrikeWithInternalLicenseFeatures(TRACE_TO_TEST_AUTOPILOT_FEATURE);
84
+ }
85
+ BuildScenario() {
86
+ return this.buildScenario();
87
+ }
88
+ }
89
+ _LoadStrikeAutopilotResult_httpRequest = new WeakMap();
90
+ export class LoadStrikeAutopilot {
91
+ static generate(request) {
92
+ const artifact = loadAutopilotArtifact(request);
93
+ const options = request.Options ?? {};
94
+ const result = inferAutopilotResult(artifact, options);
95
+ if (options.IncludePreviewReport) {
96
+ result.PreviewReport = buildPreviewReport(result);
97
+ }
98
+ return result;
99
+ }
100
+ static Generate(request) {
101
+ return LoadStrikeAutopilot.generate(request);
102
+ }
103
+ }
104
+ function loadAutopilotArtifact(request) {
105
+ if (!request || typeof request.Kind !== "string" || !request.Kind.trim()) {
106
+ throw new Error("Autopilot kind must be provided.");
107
+ }
108
+ if (equalsKind(request.Kind, "MessagePair")) {
109
+ return {
110
+ kind: request.Kind,
111
+ sourceMessage: request.SourceMessage,
112
+ destinationMessage: request.DestinationMessage
113
+ };
114
+ }
115
+ let content = request.Content;
116
+ if ((!content || !content.trim()) && request.FilePath) {
117
+ content = fs.readFileSync(request.FilePath, "utf8");
118
+ }
119
+ if (!content || !content.trim()) {
120
+ throw new Error("Autopilot content or file path must be provided.");
121
+ }
122
+ try {
123
+ return { kind: request.Kind, root: JSON.parse(content) };
124
+ }
125
+ catch {
126
+ throw new Error(`Autopilot ${request.Kind} artifact is invalid JSON.`);
127
+ }
128
+ }
129
+ function inferAutopilotResult(artifact, options) {
130
+ if (equalsKind(artifact.kind, "Har")) {
131
+ return fromHar(artifact, options);
132
+ }
133
+ if (equalsKind(artifact.kind, "OtelTrace")) {
134
+ return fromOtelTrace(artifact, options);
135
+ }
136
+ if (equalsKind(artifact.kind, "PlaywrightRecording")) {
137
+ return fromPlaywrightRecording(artifact, options);
138
+ }
139
+ if (equalsKind(artifact.kind, "MessagePair")) {
140
+ return fromMessagePair(artifact, options);
141
+ }
142
+ throw new Error(`Unsupported Autopilot kind '${artifact.kind}'.`);
143
+ }
144
+ function fromHar(artifact, options) {
145
+ const root = asRecord(artifact.root);
146
+ const entries = asArray(asRecord(root.log).entries);
147
+ if (entries.length === 0) {
148
+ return buildReviewResult(options, "HAR artifact does not contain any entries.");
149
+ }
150
+ const entry = asRecord(entries[0]);
151
+ const request = asRecord(entry.request);
152
+ const url = asString(request.url);
153
+ if (!url) {
154
+ return buildReviewResult(options, "HAR request does not contain a URL.");
155
+ }
156
+ const headers = {};
157
+ for (const header of asArray(request.headers)) {
158
+ const item = asRecord(header);
159
+ const name = asString(item.name);
160
+ if (name) {
161
+ headers[name] = asString(item.value) ?? "";
162
+ }
163
+ }
164
+ const postData = asRecord(request.postData);
165
+ return buildHttpResult(options, {
166
+ method: asString(request.method) ?? "GET",
167
+ url,
168
+ headers,
169
+ body: asString(postData.text),
170
+ contentType: headerValue(headers, "content-type") ?? asString(postData.mimeType),
171
+ observedLatencyMs: asNumber(entry.time)
172
+ });
173
+ }
174
+ function fromOtelTrace(artifact, options) {
175
+ const span = findFirstSpan(asRecord(artifact.root));
176
+ if (!span) {
177
+ return buildReviewResult(options, "OpenTelemetry artifact does not contain any spans.");
178
+ }
179
+ const attributes = readOtelAttributes(span);
180
+ const url = attributes["url.full"] ?? attributes["http.url"];
181
+ if (!url) {
182
+ return buildReviewResult(options, "OpenTelemetry span does not contain an HTTP URL.");
183
+ }
184
+ const headers = {};
185
+ for (const [key, value] of Object.entries(attributes)) {
186
+ const prefix = "http.request.header.";
187
+ if (key.toLowerCase().startsWith(prefix)) {
188
+ headers[key.slice(prefix.length)] = value;
189
+ }
190
+ }
191
+ return buildHttpResult(options, {
192
+ method: attributes["http.request.method"] ?? attributes["http.method"] ?? "GET",
193
+ url,
194
+ headers,
195
+ observedLatencyMs: readSpanDurationMs(span)
196
+ });
197
+ }
198
+ function fromPlaywrightRecording(artifact, options) {
199
+ const network = asArray(asRecord(artifact.root).network);
200
+ if (network.length === 0) {
201
+ return buildReviewResult(options, "Playwright recording does not contain any network entries.");
202
+ }
203
+ const entry = asRecord(network[0]);
204
+ const url = asString(entry.url);
205
+ if (!url) {
206
+ return buildReviewResult(options, "Playwright network entry does not contain a URL.");
207
+ }
208
+ const headers = Object.fromEntries(Object.entries(asRecord(entry.requestHeaders)).map(([key, value]) => [key, String(value)]));
209
+ const body = entry.requestBody == null
210
+ ? undefined
211
+ : typeof entry.requestBody === "string" ? entry.requestBody : JSON.stringify(entry.requestBody);
212
+ return buildHttpResult(options, {
213
+ method: asString(entry.method) ?? "GET",
214
+ url,
215
+ headers,
216
+ body,
217
+ contentType: headerValue(headers, "content-type"),
218
+ observedLatencyMs: asNumber(entry.durationMs)
219
+ });
220
+ }
221
+ function fromMessagePair(artifact, options) {
222
+ const source = artifact.sourceMessage;
223
+ const destination = artifact.destinationMessage;
224
+ if (!source || !destination) {
225
+ return buildReviewResult(options, "MessagePair input requires both source and destination messages.");
226
+ }
227
+ const inferredSelector = findSharedHeaderSelector(readHeaders(source), readHeaders(destination));
228
+ const selector = inferredSelector ?? options.TrackingSelector?.trim();
229
+ const { endpoints, missing } = applyEndpointBindings([
230
+ { Kind: "DelegateStream", Name: source.name ?? source.Name ?? "source" },
231
+ { Kind: "DelegateStream", Name: destination.name ?? destination.Name ?? "destination" }
232
+ ], options.EndpointBindings ?? []);
233
+ const failures = [];
234
+ if (!selector) {
235
+ failures.push(readinessFailure(LoadStrikeAutopilotReadiness.RequiresTrackingSelector, "No stable shared tracking ID was found across the source and destination messages.", "TrackingSelector"));
236
+ }
237
+ if (missing.length > 0) {
238
+ failures.push(readinessFailure(LoadStrikeAutopilotReadiness.RequiresEndpointBinding, "Message pair input requires explicit endpoint binding before it can run.", "EndpointBindings", missing));
239
+ }
240
+ if (failures.length === 0) {
241
+ failures.push(readinessFailure(LoadStrikeAutopilotReadiness.RequiresReview, "Message pair endpoint bindings were supplied, but automatic runnable scenario generation currently supports HTTP replay artifacts only.", "ManualReview"));
242
+ }
243
+ const readiness = failures[0].Readiness;
244
+ return buildResult({
245
+ readiness,
246
+ options,
247
+ endpoints,
248
+ trackingSuggestions: selector ? [{ Expression: selector, Confidence: inferredSelector ? 0.9 : 1 }] : [],
249
+ thresholdSuggestions: seedThresholds(undefined),
250
+ redactions: [],
251
+ warnings: failures.map((failure) => failure.Reason),
252
+ readinessFailures: failures,
253
+ confidence: 0.7
254
+ });
255
+ }
256
+ function buildHttpResult(options, input) {
257
+ const redactions = [];
258
+ const secretBindings = secretBindingMap(options.SecretBindings ?? []);
259
+ const unresolvedSecretLocations = [];
260
+ const headers = redactHeaders(input.headers, redactions, secretBindings, (location) => {
261
+ unresolvedSecretLocations.push(location);
262
+ });
263
+ const body = redactBody(input.body, redactions, secretBindings, (location) => {
264
+ unresolvedSecretLocations.push(location);
265
+ });
266
+ const url = redactUrl(input.url, redactions, secretBindings, (location) => {
267
+ unresolvedSecretLocations.push(location);
268
+ });
269
+ const target = resolveReplayUrl(url, options);
270
+ const selector = inferTrackingSelector(headers, body) ?? options.TrackingSelector?.trim();
271
+ const failures = [];
272
+ if (unresolvedSecretLocations.length > 0) {
273
+ failures.push(readinessFailure(LoadStrikeAutopilotReadiness.RequiresSecrets, "Secret bindings are required for every redacted header, query parameter, or body field before replay can run.", "SecretBindings", unresolvedSecretLocations));
274
+ }
275
+ if (!target.ready) {
276
+ failures.push(readinessFailure(LoadStrikeAutopilotReadiness.RequiresTargetBinding, target.warning ?? "Captured target requires explicit replay binding before replay can run.", "AllowedReplayHosts|BaseUrlRewrite|EndpointBindings[source-http]", ["source-http"]));
277
+ }
278
+ if (!selector) {
279
+ failures.push(readinessFailure(LoadStrikeAutopilotReadiness.RequiresTrackingSelector, "No stable tracking selector could be inferred from the artifact.", "TrackingSelector"));
280
+ }
281
+ const readiness = failures.length === 0 ? LoadStrikeAutopilotReadiness.Ready : failures[0].Readiness;
282
+ const warnings = failures.map((failure) => failure.Reason);
283
+ const endpoints = [{
284
+ Kind: "Http",
285
+ Name: "source-http",
286
+ Method: input.method.toUpperCase(),
287
+ Url: target.url
288
+ }];
289
+ return buildResult({
290
+ readiness,
291
+ options,
292
+ endpoints,
293
+ trackingSuggestions: selector ? [{ Expression: selector, Confidence: 0.9 }] : [],
294
+ thresholdSuggestions: seedThresholds(input.observedLatencyMs),
295
+ redactions,
296
+ warnings,
297
+ readinessFailures: failures,
298
+ httpRequest: readiness === LoadStrikeAutopilotReadiness.Ready ? {
299
+ method: input.method.toUpperCase(),
300
+ url: target.url,
301
+ headers,
302
+ body,
303
+ contentType: input.contentType
304
+ } : undefined,
305
+ observedLatencyMs: input.observedLatencyMs,
306
+ confidence: readiness === LoadStrikeAutopilotReadiness.Ready ? 0.9 : 0.5
307
+ });
308
+ }
309
+ function buildReviewResult(options, warning) {
310
+ return buildResult({
311
+ readiness: LoadStrikeAutopilotReadiness.RequiresReview,
312
+ options,
313
+ endpoints: [],
314
+ trackingSuggestions: [],
315
+ thresholdSuggestions: seedThresholds(undefined),
316
+ redactions: [],
317
+ warnings: [warning],
318
+ readinessFailures: [readinessFailure(LoadStrikeAutopilotReadiness.RequiresReview, warning, "ManualReview")],
319
+ confidence: 0.1
320
+ });
321
+ }
322
+ function buildResult(values) {
323
+ const plan = {
324
+ Version: "trace-to-test-autopilot/v1",
325
+ Scenario: {
326
+ Name: values.options.ScenarioName?.trim() || "autopilot-starter",
327
+ WithoutWarmUp: true,
328
+ LoadSimulationSuggestions: [{ Kind: "IterationsForConstant", Copies: 1, Iterations: 1 }]
329
+ },
330
+ Endpoints: values.endpoints,
331
+ TrackingSelectorSuggestions: values.trackingSuggestions,
332
+ ThresholdSuggestions: values.thresholdSuggestions,
333
+ Redactions: values.redactions,
334
+ Warnings: values.warnings,
335
+ Confidence: values.confidence
336
+ };
337
+ return new LoadStrikeAutopilotResult({
338
+ readiness: values.readiness,
339
+ plan,
340
+ endpoints: values.endpoints,
341
+ redactions: values.redactions,
342
+ warnings: values.warnings,
343
+ readinessFailures: values.readinessFailures,
344
+ httpRequest: values.httpRequest,
345
+ observedLatencyMs: values.observedLatencyMs
346
+ });
347
+ }
348
+ function buildPreviewReport(result) {
349
+ return {
350
+ ScenarioName: result.Plan.Scenario.Name,
351
+ Readiness: result.Readiness,
352
+ Redactions: result.Redactions,
353
+ Warnings: result.Warnings,
354
+ ReadinessFailures: result.ReadinessFailures,
355
+ Signals: [
356
+ `readiness:${result.Readiness}`,
357
+ `endpoints:${result.Endpoints.length}`,
358
+ `trackingSelectors:${result.Plan.TrackingSelectorSuggestions.length}`,
359
+ `thresholdSuggestions:${result.Plan.ThresholdSuggestions.length}`
360
+ ]
361
+ };
362
+ }
363
+ function seedThresholds(observedLatencyMs) {
364
+ const latencyBudget = Math.max(100, Math.ceil((observedLatencyMs ?? 100) * 1.5));
365
+ return [
366
+ { Kind: "AllFailCount", Maximum: 0 },
367
+ { Kind: "LatencyP95Ms", Maximum: latencyBudget },
368
+ { Kind: "LatencyP99Ms", Maximum: Math.ceil(latencyBudget * 1.25) }
369
+ ];
370
+ }
371
+ function compileThreshold(suggestion) {
372
+ if (suggestion.Kind === "AllFailCount") {
373
+ return LoadStrikeThreshold.scenario("AllFailCount", "<=", suggestion.Maximum);
374
+ }
375
+ return LoadStrikeThreshold.scenario(suggestion.Kind, "<=", suggestion.Maximum);
376
+ }
377
+ function redactHeaders(headers, redactions, bindings, markUnresolved) {
378
+ const next = {};
379
+ for (const [key, value] of Object.entries(headers)) {
380
+ if (isSecretKey(key)) {
381
+ const location = `header:${key}`;
382
+ next[key] = replacementForLocation(location, bindings, markUnresolved);
383
+ redactions.push({ Location: location, Reason: "secret-like header", Replacement: REDACTED });
384
+ }
385
+ else {
386
+ next[key] = value;
387
+ }
388
+ }
389
+ return next;
390
+ }
391
+ function redactUrl(url, redactions, bindings, markUnresolved) {
392
+ try {
393
+ const parsed = new URL(url);
394
+ let changed = false;
395
+ for (const key of [...parsed.searchParams.keys()]) {
396
+ if (isSecretKey(key)) {
397
+ const location = `query:${key}`;
398
+ parsed.searchParams.set(key, replacementForLocation(location, bindings, markUnresolved));
399
+ redactions.push({ Location: location, Reason: "secret-like query parameter", Replacement: REDACTED });
400
+ changed = true;
401
+ }
402
+ }
403
+ return changed ? parsed.toString() : url;
404
+ }
405
+ catch {
406
+ return url;
407
+ }
408
+ }
409
+ function redactBody(body, redactions, bindings, markUnresolved) {
410
+ if (!body?.trim()) {
411
+ return body;
412
+ }
413
+ try {
414
+ const value = JSON.parse(body);
415
+ const changed = redactJsonValue(value, "$", redactions, bindings, markUnresolved);
416
+ return changed ? JSON.stringify(value) : body;
417
+ }
418
+ catch {
419
+ return body;
420
+ }
421
+ }
422
+ function redactJsonValue(value, path, redactions, bindings, markUnresolved) {
423
+ let changed = false;
424
+ if (Array.isArray(value)) {
425
+ for (let i = 0; i < value.length; i += 1) {
426
+ changed = redactJsonValue(value[i], `${path}[${i}]`, redactions, bindings, markUnresolved) || changed;
427
+ }
428
+ }
429
+ else if (value && typeof value === "object") {
430
+ const record = value;
431
+ for (const key of Object.keys(record)) {
432
+ if (isSecretKey(key)) {
433
+ const location = `body:${path}.${key}`;
434
+ record[key] = replacementForLocation(location, bindings, markUnresolved);
435
+ redactions.push({ Location: location, Reason: "secret-like body key", Replacement: REDACTED });
436
+ changed = true;
437
+ }
438
+ else {
439
+ changed = redactJsonValue(record[key], `${path}.${key}`, redactions, bindings, markUnresolved) || changed;
440
+ }
441
+ }
442
+ }
443
+ return changed;
444
+ }
445
+ function secretBindingMap(bindings) {
446
+ const result = new Map();
447
+ for (const binding of bindings) {
448
+ const location = normalizeBindingLocation(binding.Location);
449
+ const env = binding.ValueFromEnv?.trim();
450
+ if (location && env) {
451
+ result.set(location, env);
452
+ }
453
+ }
454
+ return result;
455
+ }
456
+ function replacementForLocation(location, bindings, markUnresolved) {
457
+ const env = bindings.get(normalizeBindingLocation(location));
458
+ if (env) {
459
+ return `${ENV_MARKER_PREFIX}${env}${ENV_MARKER_SUFFIX}`;
460
+ }
461
+ markUnresolved(location);
462
+ return REDACTED;
463
+ }
464
+ function readinessFailure(Readiness, Reason, RequiredInput, Locations = []) {
465
+ return {
466
+ Readiness,
467
+ Reason,
468
+ RequiredInput,
469
+ Locations: [...new Set(Locations.map((location) => location.trim()).filter(Boolean))]
470
+ };
471
+ }
472
+ function normalizeBindingLocation(location) {
473
+ return (location ?? "").trim().toLowerCase();
474
+ }
475
+ function parseEnvMarker(value) {
476
+ const trimmed = value.trim();
477
+ return trimmed.startsWith(ENV_MARKER_PREFIX) && trimmed.endsWith(ENV_MARKER_SUFFIX)
478
+ ? trimmed.slice(ENV_MARKER_PREFIX.length, -ENV_MARKER_SUFFIX.length).trim() || undefined
479
+ : undefined;
480
+ }
481
+ function resolveSecretMarker(value) {
482
+ const env = parseEnvMarker(value);
483
+ if (!env) {
484
+ return value;
485
+ }
486
+ const secret = process.env[env];
487
+ if (secret === undefined) {
488
+ throw new Error(`Autopilot secret environment variable '${env}' is not set.`);
489
+ }
490
+ return secret;
491
+ }
492
+ function resolveHttpRequestSecrets(request) {
493
+ const headers = {};
494
+ for (const [key, value] of Object.entries(request.headers)) {
495
+ headers[key] = resolveSecretMarker(value);
496
+ }
497
+ return {
498
+ ...request,
499
+ url: resolveUrlSecretMarkers(request.url),
500
+ headers,
501
+ body: resolveBodySecretMarkers(request.body)
502
+ };
503
+ }
504
+ function resolveUrlSecretMarkers(rawUrl) {
505
+ try {
506
+ const parsed = new URL(rawUrl);
507
+ let changed = false;
508
+ for (const [key, value] of [...parsed.searchParams.entries()]) {
509
+ const resolved = resolveSecretMarker(value);
510
+ if (resolved !== value) {
511
+ parsed.searchParams.set(key, resolved);
512
+ changed = true;
513
+ }
514
+ }
515
+ return changed ? parsed.toString() : rawUrl;
516
+ }
517
+ catch {
518
+ return resolveSecretMarker(rawUrl);
519
+ }
520
+ }
521
+ function resolveBodySecretMarkers(body) {
522
+ if (!body?.trim()) {
523
+ return body;
524
+ }
525
+ try {
526
+ const value = JSON.parse(body);
527
+ const changed = resolveJsonSecretMarkers(value);
528
+ return changed ? JSON.stringify(value) : body;
529
+ }
530
+ catch {
531
+ return resolveSecretMarker(body);
532
+ }
533
+ }
534
+ function resolveJsonSecretMarkers(value) {
535
+ let changed = false;
536
+ if (Array.isArray(value)) {
537
+ for (let index = 0; index < value.length; index += 1) {
538
+ if (typeof value[index] === "string") {
539
+ const resolved = resolveSecretMarker(value[index]);
540
+ if (resolved !== value[index]) {
541
+ value[index] = resolved;
542
+ changed = true;
543
+ }
544
+ }
545
+ else {
546
+ changed = resolveJsonSecretMarkers(value[index]) || changed;
547
+ }
548
+ }
549
+ }
550
+ else if (value && typeof value === "object") {
551
+ const record = value;
552
+ for (const key of Object.keys(record)) {
553
+ if (typeof record[key] === "string") {
554
+ const resolved = resolveSecretMarker(record[key]);
555
+ if (resolved !== record[key]) {
556
+ record[key] = resolved;
557
+ changed = true;
558
+ }
559
+ }
560
+ else {
561
+ changed = resolveJsonSecretMarkers(record[key]) || changed;
562
+ }
563
+ }
564
+ }
565
+ return changed;
566
+ }
567
+ function applyEndpointBindings(endpoints, bindings) {
568
+ const byName = new Map();
569
+ for (const binding of bindings) {
570
+ const name = binding.Name?.trim().toLowerCase();
571
+ if (name) {
572
+ byName.set(name, binding);
573
+ }
574
+ }
575
+ const missing = [];
576
+ const next = endpoints.map((endpoint) => {
577
+ const binding = byName.get(endpoint.Name.trim().toLowerCase());
578
+ if (!binding) {
579
+ missing.push(endpoint.Name);
580
+ return endpoint;
581
+ }
582
+ return {
583
+ ...endpoint,
584
+ Kind: binding.Kind?.trim() || endpoint.Kind,
585
+ Method: binding.Method?.trim() || endpoint.Method,
586
+ Url: binding.Url?.trim() || endpoint.Url
587
+ };
588
+ });
589
+ return { endpoints: next, missing };
590
+ }
591
+ function endpointBindingUrl(bindings, name) {
592
+ return bindings.find((binding) => binding.Name?.trim().toLowerCase() === name.toLowerCase())?.Url?.trim() || undefined;
593
+ }
594
+ function resolveReplayUrl(url, options) {
595
+ try {
596
+ const parsed = new URL(url);
597
+ const endpointTarget = endpointBindingUrl(options.EndpointBindings ?? [], "source-http");
598
+ if (endpointTarget) {
599
+ return { url: endpointTarget, ready: true };
600
+ }
601
+ if (options.BaseUrlRewrite) {
602
+ const base = new URL(options.BaseUrlRewrite);
603
+ return { url: new URL(`${parsed.pathname}${parsed.search}`, base).toString(), ready: true };
604
+ }
605
+ const allowed = new Set((options.AllowedReplayHosts ?? []).map((host) => host.toLowerCase()));
606
+ if (isLoopbackHost(parsed.hostname) || allowed.has(parsed.hostname.toLowerCase())) {
607
+ return { url, ready: true };
608
+ }
609
+ return {
610
+ url,
611
+ ready: false,
612
+ warning: `Captured host '${parsed.hostname}' requires AllowedReplayHosts or BaseUrlRewrite before replay.`
613
+ };
614
+ }
615
+ catch {
616
+ return { url, ready: false, warning: "Captured URL is not absolute and requires target binding." };
617
+ }
618
+ }
619
+ function inferTrackingSelector(headers, body) {
620
+ for (const key of ["x-correlation-id", "correlation-id", "traceparent"]) {
621
+ const match = headerValue(headers, key);
622
+ if (match) {
623
+ return `header:${key}`;
624
+ }
625
+ }
626
+ if (!body) {
627
+ return undefined;
628
+ }
629
+ try {
630
+ const parsed = JSON.parse(body);
631
+ return parsed && typeof parsed === "object" && "orderId" in parsed ? "json:$.orderId" : undefined;
632
+ }
633
+ catch {
634
+ return undefined;
635
+ }
636
+ }
637
+ function findSharedHeaderSelector(source, destination) {
638
+ for (const [key, value] of Object.entries(source)) {
639
+ const destinationValue = headerValue(destination, key);
640
+ if (destinationValue === value) {
641
+ return `header:${key}`;
642
+ }
643
+ }
644
+ return undefined;
645
+ }
646
+ function readHeaders(sample) {
647
+ return sample.headers ?? sample.Headers ?? {};
648
+ }
649
+ function readOtelAttributes(span) {
650
+ const result = {};
651
+ for (const attribute of asArray(span.attributes)) {
652
+ const item = asRecord(attribute);
653
+ const key = asString(item.key);
654
+ if (!key) {
655
+ continue;
656
+ }
657
+ const value = asRecord(item.value);
658
+ result[key] = asString(value.stringValue) ?? asString(value.intValue) ?? JSON.stringify(value);
659
+ }
660
+ return result;
661
+ }
662
+ function findFirstSpan(root) {
663
+ for (const resourceSpan of asArray(root.resourceSpans)) {
664
+ for (const scopeSpan of asArray(asRecord(resourceSpan).scopeSpans)) {
665
+ const spans = asArray(asRecord(scopeSpan).spans);
666
+ if (spans.length > 0) {
667
+ return asRecord(spans[0]);
668
+ }
669
+ }
670
+ }
671
+ return undefined;
672
+ }
673
+ function readSpanDurationMs(span) {
674
+ const start = BigInt(asString(span.startTimeUnixNano) ?? "0");
675
+ const end = BigInt(asString(span.endTimeUnixNano) ?? "0");
676
+ return end >= start ? Number(end - start) / 1000000 : undefined;
677
+ }
678
+ function headerValue(headers, name) {
679
+ const match = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase());
680
+ return match?.[1];
681
+ }
682
+ function isSecretKey(key) {
683
+ const lower = key.toLowerCase();
684
+ return SECRET_KEYS.has(lower) || SECRET_KEYS.has(lower.replace(/-/g, "_"));
685
+ }
686
+ function isLoopbackHost(host) {
687
+ return host === "localhost" || host === "127.0.0.1" || host === "::1";
688
+ }
689
+ function equalsKind(actual, expected) {
690
+ return actual.toLowerCase() === expected.toLowerCase();
691
+ }
692
+ function asRecord(value) {
693
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
694
+ }
695
+ function asArray(value) {
696
+ return Array.isArray(value) ? value : [];
697
+ }
698
+ function asString(value) {
699
+ if (typeof value === "string") {
700
+ return value;
701
+ }
702
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
703
+ return String(value);
704
+ }
705
+ return undefined;
706
+ }
707
+ function asNumber(value) {
708
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
709
+ }