agent-inspect 1.2.0 → 1.3.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.
@@ -467,7 +467,7 @@ function stableHash(value) {
467
467
  const h = crypto.createHash("sha256").update(value, "utf8").digest("hex");
468
468
  return h.slice(0, 8);
469
469
  }
470
- function compileRules(rules) {
470
+ function compileRules(rules, extraKeys) {
471
471
  const out = /* @__PURE__ */ new Map();
472
472
  const set = (r) => {
473
473
  const k = toKey(r.key);
@@ -476,6 +476,11 @@ function compileRules(rules) {
476
476
  for (const k of DEFAULT_REDACT_KEYS) {
477
477
  set({ key: k, strategy: "full" });
478
478
  }
479
+ for (const k of extraKeys ?? []) {
480
+ if (typeof k === "string" && k.length > 0) {
481
+ set({ key: k, strategy: "full" });
482
+ }
483
+ }
479
484
  for (const r of rules ?? []) {
480
485
  if (typeof r === "string") {
481
486
  set({ key: r, strategy: "full" });
@@ -493,7 +498,7 @@ function compileRules(rules) {
493
498
  var Redactor = class {
494
499
  #rules;
495
500
  constructor(options) {
496
- this.#rules = compileRules(options?.rules);
501
+ this.#rules = compileRules(options?.rules, options?.extraKeys);
497
502
  }
498
503
  redactValue(key, value) {
499
504
  const k = toKey(key);
@@ -1195,6 +1200,91 @@ function warn(message, error) {
1195
1200
  }
1196
1201
  console.warn(`${base}: ${formatError(error).message}`);
1197
1202
  }
1203
+
1204
+ // packages/core/src/redaction-profiles.ts
1205
+ var SHARE_PROFILE_EXTRA_KEYS = [
1206
+ "userEmail",
1207
+ "customerEmail",
1208
+ "phone",
1209
+ "phoneNumber",
1210
+ "address",
1211
+ "ip",
1212
+ "ipAddress",
1213
+ "sessionId",
1214
+ "requestId",
1215
+ "correlationId",
1216
+ "decisionId",
1217
+ "groupId",
1218
+ "customerId",
1219
+ "userId",
1220
+ "accountId",
1221
+ "tenantId",
1222
+ "orgId",
1223
+ "organizationId",
1224
+ "traceId",
1225
+ "spanId",
1226
+ "parentSpanId"
1227
+ ];
1228
+ var STRICT_PROFILE_EXTRA_KEYS = [
1229
+ "prompt",
1230
+ "completion",
1231
+ "input",
1232
+ "output",
1233
+ "inputPreview",
1234
+ "outputPreview",
1235
+ "message",
1236
+ "messages",
1237
+ "transcript",
1238
+ "context",
1239
+ "document",
1240
+ "documents",
1241
+ "chunk",
1242
+ "chunks",
1243
+ "retrieval",
1244
+ "query"
1245
+ ];
1246
+ function resolveRedactionProfile(profile = "local") {
1247
+ switch (profile) {
1248
+ case "local":
1249
+ return { profile: "local", extraKeys: [] };
1250
+ case "share":
1251
+ return {
1252
+ profile: "share",
1253
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS,
1254
+ maxMetadataValueLengthCap: 500,
1255
+ maxPreviewLengthCap: 200
1256
+ };
1257
+ case "strict":
1258
+ return {
1259
+ profile: "strict",
1260
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS, ...STRICT_PROFILE_EXTRA_KEYS],
1261
+ maxMetadataValueLengthCap: 200,
1262
+ maxPreviewLengthCap: 80
1263
+ };
1264
+ default:
1265
+ return { profile: "local", extraKeys: [] };
1266
+ }
1267
+ }
1268
+ function isPreviewKey(key) {
1269
+ return key.toLowerCase().includes("preview");
1270
+ }
1271
+ function applyProfileMetadataCaps(maxMetadataValueLength, maxPreviewLength, resolved) {
1272
+ let meta = maxMetadataValueLength;
1273
+ let preview = maxPreviewLength;
1274
+ if (resolved.maxMetadataValueLengthCap !== void 0) {
1275
+ meta = Math.min(meta, resolved.maxMetadataValueLengthCap);
1276
+ }
1277
+ if (resolved.maxPreviewLengthCap !== void 0) {
1278
+ preview = Math.min(preview, resolved.maxPreviewLengthCap);
1279
+ }
1280
+ return { maxMetadataValueLength: meta, maxPreviewLength: preview };
1281
+ }
1282
+ function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreviewLength) {
1283
+ const max = isPreviewKey(key) ? maxPreviewLength : maxMetadataValueLength;
1284
+ if (max <= 0) return "\u2026";
1285
+ if (value.length <= max) return value;
1286
+ return `${value.slice(0, max)}\u2026`;
1287
+ }
1198
1288
  function isRecord6(value) {
1199
1289
  return typeof value === "object" && value !== null && !Array.isArray(value);
1200
1290
  }
@@ -1345,7 +1435,7 @@ var DEFAULT_MAX_EVENT_BYTES = 65536;
1345
1435
  function isRecord7(value) {
1346
1436
  return typeof value === "object" && value !== null && !Array.isArray(value);
1347
1437
  }
1348
- function isPreviewKey(key) {
1438
+ function isPreviewKey2(key) {
1349
1439
  return key.toLowerCase().includes("preview");
1350
1440
  }
1351
1441
  function truncateString(value, maxLen) {
@@ -1362,11 +1452,28 @@ function resolveTraceSafetyOptions(options) {
1362
1452
  {
1363
1453
  redactEnabled = true;
1364
1454
  }
1455
+ const profile = options?.redactionProfile ?? "local";
1456
+ const resolvedProfile = resolveRedactionProfile(profile);
1457
+ const userMaxMetadata = "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : void 0;
1458
+ const userMaxPreview = "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : void 0;
1459
+ let maxMetadataValueLength = userMaxMetadata ?? DEFAULT_MAX_METADATA_VALUE_LENGTH;
1460
+ let maxPreviewLength = userMaxPreview ?? DEFAULT_MAX_PREVIEW_LENGTH;
1461
+ if (redactEnabled && profile !== "local") {
1462
+ const capped = applyProfileMetadataCaps(
1463
+ maxMetadataValueLength,
1464
+ maxPreviewLength,
1465
+ resolvedProfile
1466
+ );
1467
+ maxMetadataValueLength = capped.maxMetadataValueLength;
1468
+ maxPreviewLength = capped.maxPreviewLength;
1469
+ }
1365
1470
  return {
1366
1471
  redactEnabled,
1367
1472
  redactionRules,
1368
- maxMetadataValueLength: "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : DEFAULT_MAX_METADATA_VALUE_LENGTH,
1369
- maxPreviewLength: "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : DEFAULT_MAX_PREVIEW_LENGTH,
1473
+ redactionProfile: profile,
1474
+ profileExtraKeys: redactEnabled ? resolvedProfile.extraKeys : [],
1475
+ maxMetadataValueLength,
1476
+ maxPreviewLength,
1370
1477
  maxEventBytes: "undefined" === "number" && Number.isFinite(options.maxEventBytes) && options.maxEventBytes > 0 ? Math.floor(options.maxEventBytes) : DEFAULT_MAX_EVENT_BYTES
1371
1478
  };
1372
1479
  }
@@ -1374,7 +1481,7 @@ function boundMetadataValue(key, value, opts, seen, depth) {
1374
1481
  if (depth > 32) return "[MaxDepth]";
1375
1482
  if (value === null || typeof value !== "object") {
1376
1483
  if (typeof value === "string") {
1377
- const max = isPreviewKey(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
1484
+ const max = isPreviewKey2(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
1378
1485
  return truncateString(value, max);
1379
1486
  }
1380
1487
  return value;
@@ -1400,7 +1507,10 @@ function boundMetadataValue(key, value, opts, seen, depth) {
1400
1507
  }
1401
1508
  function redactMetadata(metadata, opts) {
1402
1509
  if (!opts.redactEnabled) return { ...metadata };
1403
- const redactor = new Redactor({ rules: opts.redactionRules });
1510
+ const redactor = new Redactor({
1511
+ rules: opts.redactionRules,
1512
+ extraKeys: opts.profileExtraKeys
1513
+ });
1404
1514
  return redactor.redactRecord(metadata);
1405
1515
  }
1406
1516
  function prepareMetadataForDisk(metadata, opts) {
@@ -3207,6 +3317,134 @@ Object.assign(stepImpl, {
3207
3317
  // packages/core/src/exporters/types.ts
3208
3318
  var EXPORT_PAYLOAD_VERSION = "0.1.2";
3209
3319
 
3320
+ // packages/core/src/exporters/redact-export.ts
3321
+ function isRecord9(value) {
3322
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3323
+ }
3324
+ function deepClone(value) {
3325
+ if (value === null || typeof value !== "object") {
3326
+ return value;
3327
+ }
3328
+ if (Array.isArray(value)) {
3329
+ return value.map((item) => deepClone(item));
3330
+ }
3331
+ const out = {};
3332
+ for (const [k, v] of Object.entries(value)) {
3333
+ out[k] = deepClone(v);
3334
+ }
3335
+ return out;
3336
+ }
3337
+ function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
3338
+ if (depth > 32) {
3339
+ return { truncated: true, reason: "maxDepth" };
3340
+ }
3341
+ const out = {};
3342
+ for (const [key, value] of Object.entries(record)) {
3343
+ out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
3344
+ }
3345
+ return out;
3346
+ }
3347
+ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
3348
+ if (value === null || typeof value !== "object") {
3349
+ if (typeof value === "string") {
3350
+ return truncateStringForProfile(
3351
+ value,
3352
+ key,
3353
+ maxMetadataValueLength,
3354
+ maxPreviewLength
3355
+ );
3356
+ }
3357
+ return value;
3358
+ }
3359
+ if (seen.has(value)) return "[Circular]";
3360
+ seen.add(value);
3361
+ if (Array.isArray(value)) {
3362
+ return value.slice(0, 50).map(
3363
+ (item, index) => boundValue(
3364
+ item,
3365
+ String(index),
3366
+ maxMetadataValueLength,
3367
+ maxPreviewLength,
3368
+ seen,
3369
+ depth + 1
3370
+ )
3371
+ );
3372
+ }
3373
+ return boundAttributeValues(
3374
+ value,
3375
+ maxMetadataValueLength,
3376
+ maxPreviewLength,
3377
+ seen,
3378
+ depth + 1
3379
+ );
3380
+ }
3381
+ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPreviewLength) {
3382
+ if (!attrs || Object.keys(attrs).length === 0) {
3383
+ return attrs;
3384
+ }
3385
+ const redacted = redactor.redactRecord(attrs);
3386
+ const seen = /* @__PURE__ */ new WeakSet();
3387
+ const bounded = boundAttributeValues(
3388
+ redacted,
3389
+ maxMetadataValueLength,
3390
+ maxPreviewLength,
3391
+ seen,
3392
+ 0
3393
+ );
3394
+ const err = bounded.error;
3395
+ if (isRecord9(err) && typeof err.message === "string") {
3396
+ bounded.error = {
3397
+ ...err,
3398
+ message: truncateStringForProfile(
3399
+ err.message,
3400
+ "message",
3401
+ maxMetadataValueLength,
3402
+ maxPreviewLength
3403
+ ),
3404
+ ...typeof err.stack === "string" ? {
3405
+ stack: truncateStringForProfile(
3406
+ err.stack,
3407
+ "stack",
3408
+ maxMetadataValueLength,
3409
+ maxPreviewLength
3410
+ )
3411
+ } : {}
3412
+ };
3413
+ }
3414
+ return bounded;
3415
+ }
3416
+ function redactRunTreeForExport(tree, options) {
3417
+ const profile = options?.redactionProfile ?? "local";
3418
+ if (profile === "local") {
3419
+ return deepClone(tree);
3420
+ }
3421
+ const resolved = resolveRedactionProfile(profile);
3422
+ const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
3423
+ 2e3,
3424
+ 500,
3425
+ resolved
3426
+ );
3427
+ const redactor = new Redactor({ extraKeys: resolved.extraKeys });
3428
+ const clone = deepClone(tree);
3429
+ function walk(nodes) {
3430
+ for (const node of nodes) {
3431
+ if (node.event.attributes !== void 0) {
3432
+ node.event.attributes = redactEventAttributes(
3433
+ node.event.attributes,
3434
+ redactor,
3435
+ maxMetadataValueLength,
3436
+ maxPreviewLength
3437
+ );
3438
+ }
3439
+ if (node.children.length > 0) {
3440
+ walk(node.children);
3441
+ }
3442
+ }
3443
+ }
3444
+ walk(clone.children);
3445
+ return clone;
3446
+ }
3447
+
3210
3448
  // packages/core/src/exporters/html-exporter.ts
3211
3449
  function renderTreeHtml(nodes, ulClass = "tree") {
3212
3450
  if (nodes.length === 0) return "";
@@ -3941,20 +4179,22 @@ function mergeExportDefaults(options) {
3941
4179
  includeErrors: options.includeErrors ?? true,
3942
4180
  pretty: options.pretty,
3943
4181
  redacted: options.redacted,
3944
- maxAttributeLength: options.maxAttributeLength
4182
+ maxAttributeLength: options.maxAttributeLength,
4183
+ redactionProfile: options.redactionProfile ?? "local"
3945
4184
  };
3946
4185
  }
3947
4186
  function exportRunTree(tree, options) {
3948
4187
  const opts = mergeExportDefaults(options);
4188
+ const exportTree = opts.redactionProfile === "local" ? tree : redactRunTreeForExport(tree, { redactionProfile: opts.redactionProfile });
3949
4189
  switch (opts.format) {
3950
4190
  case "markdown":
3951
- return exportMarkdown(tree, opts);
4191
+ return exportMarkdown(exportTree, opts);
3952
4192
  case "html":
3953
- return exportHtml(tree, opts);
4193
+ return exportHtml(exportTree, opts);
3954
4194
  case "openinference":
3955
- return exportOpenInference(tree, opts);
4195
+ return exportOpenInference(exportTree, opts);
3956
4196
  case "otlp-json":
3957
- return exportOtlpJson(tree, opts);
4197
+ return exportOtlpJson(exportTree, opts);
3958
4198
  default: {
3959
4199
  const _x = opts.format;
3960
4200
  throw new Error(`Unsupported export format: ${String(_x)}`);
@@ -4790,6 +5030,15 @@ async function tail(options = {}) {
4790
5030
  process.exitCode = 1;
4791
5031
  }
4792
5032
  }
5033
+ function parseRedactionProfile(s) {
5034
+ const v = (s ?? "local").trim().toLowerCase();
5035
+ if (v === "local" || v === "share" || v === "strict") {
5036
+ return v;
5037
+ }
5038
+ throw new Error(
5039
+ `Unsupported --redaction-profile "${s ?? ""}". Use local, share, or strict.`
5040
+ );
5041
+ }
4793
5042
  function parseExportFormat(s) {
4794
5043
  const v = (s ?? "markdown").trim().toLowerCase();
4795
5044
  if (v === "markdown" || v === "html" || v === "openinference" || v === "otlp-json") {
@@ -4807,8 +5056,10 @@ async function exportCommand(runId, options = {}) {
4807
5056
  return;
4808
5057
  }
4809
5058
  let format;
5059
+ let redactionProfile;
4810
5060
  try {
4811
5061
  format = parseExportFormat(options.format);
5062
+ redactionProfile = parseRedactionProfile(options.redactionProfile);
4812
5063
  } catch (e) {
4813
5064
  const msg = e instanceof Error ? e.message : String(e);
4814
5065
  console.error(msg);
@@ -4847,7 +5098,8 @@ Trace directory: ${traceDir}`);
4847
5098
  includeErrors: options.noErrors === true ? false : true,
4848
5099
  pretty: true,
4849
5100
  redacted: true,
4850
- maxAttributeLength: 500
5101
+ maxAttributeLength: 500,
5102
+ redactionProfile
4851
5103
  };
4852
5104
  const result = exportRunTree(tree, exportOpts);
4853
5105
  const validation = options.validate === true ? validateExport(result) : void 0;
@@ -5080,7 +5332,12 @@ function createCliProgram() {
5080
5332
  "openinference",
5081
5333
  "otlp-json"
5082
5334
  ])
5083
- ).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").action((runId, opts) => {
5335
+ ).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").addOption(
5336
+ new Option(
5337
+ "--redaction-profile <profile>",
5338
+ "redaction profile for exported copies: local, share, strict (default: local)"
5339
+ ).choices(["local", "share", "strict"])
5340
+ ).action((runId, opts) => {
5084
5341
  runCommand(() => exportCommand(runId, opts));
5085
5342
  });
5086
5343
  program.command("diff").description("Compare two local AgentInspect JSONL traces (read-only)").argument("<left-run-id>", "first run id").argument("<right-run-id>", "second run id").option("--dir <path>", "trace directory").option("--json", "print diff result as JSON").option("--ignore-duration", "omit duration comparisons").option(