@tangle-network/traces 0.8.15 → 0.8.17

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.
@@ -2726,7 +2726,9 @@ function hashId(value, chars = 16) {
2726
2726
  }
2727
2727
  function isoTime(value) {
2728
2728
  if (typeof value === "string") {
2729
- const ms = Date.parse(value);
2729
+ const trimmed = value.trim();
2730
+ const normalized = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(trimmed) ? `${trimmed.replace(" ", "T")}Z` : trimmed;
2731
+ const ms = Date.parse(normalized);
2730
2732
  return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
2731
2733
  }
2732
2734
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -2768,14 +2770,19 @@ function parseJsonRows(text) {
2768
2770
  const trimmed = text.trim();
2769
2771
  if (!trimmed) throw new Error("input file is empty");
2770
2772
  if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
2771
- const parsed = JSON.parse(trimmed);
2772
- if (Array.isArray(parsed)) return { rows: parsed };
2773
- if (isObject(parsed)) {
2774
- const events = parsed.events;
2775
- if (Array.isArray(events)) return { rows: events, wrapper: parsed };
2776
- return { rows: [parsed] };
2773
+ try {
2774
+ const parsed = JSON.parse(trimmed);
2775
+ if (Array.isArray(parsed)) return { rows: parsed };
2776
+ if (isObject(parsed)) {
2777
+ const events = parsed.events;
2778
+ if (Array.isArray(events)) return { rows: events, wrapper: parsed };
2779
+ return { rows: [parsed] };
2780
+ }
2781
+ throw new Error("input JSON must be an object, array, or JSONL rows");
2782
+ } catch (error) {
2783
+ const hasMultipleRows = /\r?\n/.test(trimmed);
2784
+ if (!hasMultipleRows) throw error;
2777
2785
  }
2778
- throw new Error("input JSON must be an object, array, or JSONL rows");
2779
2786
  }
2780
2787
  return {
2781
2788
  rows: trimmed.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line))
@@ -2788,6 +2795,10 @@ function isOpenInferenceRow(row) {
2788
2795
  if (!isObject(row)) return false;
2789
2796
  return typeof row.trace_id === "string" && typeof row.span_id === "string" && typeof row.name === "string" && typeof row.start_time === "string" && typeof row.end_time === "string" && isObject(row.status) && isObject(row.attributes) && isObject(row.resource) && isObject(row.scope);
2790
2797
  }
2798
+ function isIntelligenceSpanRow(row) {
2799
+ if (!isObject(row)) return false;
2800
+ return typeof row.trace_id === "string" && typeof row.name === "string" && isObject(row.attributes) && (row.start_unix_nano != null || row.start_time != null || row.received_at != null);
2801
+ }
2791
2802
  function eventType(row) {
2792
2803
  return stringValue(row.type) ?? stringValue(row.event) ?? stringValue(row.name) ?? stringValue(row.kind);
2793
2804
  }
@@ -2801,8 +2812,9 @@ function detectFormat(rows, requested) {
2801
2812
  if (rows.length === 0) throw new Error("input contains no rows");
2802
2813
  if (rows.every(isPolicyEvidenceRow)) return "policy-evidence";
2803
2814
  if (rows.every(isOpenInferenceRow)) return "openinference";
2815
+ if (rows.every(isIntelligenceSpanRow)) return "intelligence-spans";
2804
2816
  if (rows.some(looksLikeSandboxEvent)) return "sandbox-events";
2805
- throw new Error("could not detect input format; use --format policy-evidence, sandbox-events, or openinference");
2817
+ throw new Error("could not detect input format; use --format policy-evidence, sandbox-events, openinference, or intelligence-spans");
2806
2818
  }
2807
2819
  function requirePolicyEvidenceRows(rows) {
2808
2820
  if (!rows.every(isPolicyEvidenceRow)) throw new Error("policy-evidence input must contain only traces.policy_evidence.session rows");
@@ -2812,6 +2824,10 @@ function requireOpenInferenceRows(rows) {
2812
2824
  if (!rows.every(isOpenInferenceRow)) throw new Error("openinference input must contain only complete OpenInference span rows");
2813
2825
  return rows;
2814
2826
  }
2827
+ function requireIntelligenceSpanRows(rows) {
2828
+ if (!rows.every(isIntelligenceSpanRow)) throw new Error("intelligence-spans input must contain only Intelligence span rows");
2829
+ return rows;
2830
+ }
2815
2831
  function requireObjectRows(rows) {
2816
2832
  if (!rows.every(isObject)) throw new Error("sandbox-events input must contain only JSON object events");
2817
2833
  return rows;
@@ -3008,6 +3024,112 @@ function sandboxEventsToSpans(rows, wrapper) {
3008
3024
  function statusCode(value) {
3009
3025
  return value === "OK" || value === "ERROR" || value === "UNSET" ? value : "UNSET";
3010
3026
  }
3027
+ function numberValue(value) {
3028
+ if (typeof value === "number" && Number.isFinite(value)) return value;
3029
+ if (typeof value === "string" && value.trim().length > 0) {
3030
+ const parsed = Number(value);
3031
+ return Number.isFinite(parsed) ? parsed : void 0;
3032
+ }
3033
+ return void 0;
3034
+ }
3035
+ function unixNanoTime(value) {
3036
+ if (value == null) return void 0;
3037
+ try {
3038
+ const ns = typeof value === "bigint" ? value : BigInt(String(value));
3039
+ return new Date(Number(ns / 1000000n)).toISOString();
3040
+ } catch {
3041
+ return void 0;
3042
+ }
3043
+ }
3044
+ function firstNumber(row, attrs, keys) {
3045
+ for (const key of keys) {
3046
+ const value = numberValue(row[key]) ?? numberValue(attrs[key]);
3047
+ if (value != null) return value;
3048
+ }
3049
+ return void 0;
3050
+ }
3051
+ var COMPUTED_SPAN_ATTRIBUTES = /* @__PURE__ */ new Set([
3052
+ "openinference.span.kind",
3053
+ "service.name",
3054
+ "agent.name",
3055
+ "llm.model_name",
3056
+ "tool.name",
3057
+ "llm.input_tokens",
3058
+ "llm.output_tokens",
3059
+ "step",
3060
+ "content"
3061
+ ]);
3062
+ function preserveRawAttributes(attrs) {
3063
+ const preserved = {};
3064
+ for (const [key, value] of Object.entries(attrs)) {
3065
+ if (COMPUTED_SPAN_ATTRIBUTES.has(key)) {
3066
+ preserved[`traces.raw_attribute.${key}`] = value;
3067
+ } else {
3068
+ preserved[key] = value;
3069
+ }
3070
+ }
3071
+ return preserved;
3072
+ }
3073
+ function intelligenceSpanKind(name, attrs) {
3074
+ const lowered = name.toLowerCase();
3075
+ const spanType = stringValue(attrs["span.type"])?.toLowerCase();
3076
+ if (spanType === "llm_request" || lowered.includes("llm")) return "LLM";
3077
+ if (spanType === "tool" || lowered.includes("tool") || attrs["tool.name"] != null || attrs.tool_name != null || attrs["mcp.tool.name"] != null || attrs["tool.input"] != null || attrs["tool.output"] != null) {
3078
+ return "TOOL";
3079
+ }
3080
+ if (spanType === "interaction" || lowered.includes("interaction") || lowered.includes("agent")) return "AGENT";
3081
+ return "CHAIN";
3082
+ }
3083
+ function intelligenceSpansToSpans(rows) {
3084
+ return rows.map((row, index) => {
3085
+ const attrs = { ...objectValue(row.attributes) ?? {} };
3086
+ const name = stringValue(row.name) ?? "intelligence.span";
3087
+ const startTime = unixNanoTime(row.start_unix_nano) ?? isoTime(row.start_time) ?? stringValue(row.start_time) ?? isoTime(row.received_at) ?? (/* @__PURE__ */ new Date(0)).toISOString();
3088
+ const endTime = unixNanoTime(row.end_unix_nano) ?? isoTime(row.end_time) ?? stringValue(row.end_time) ?? startTime;
3089
+ const model = stringValue(row.model) ?? stringValue(attrs["llm.model_name"]) ?? stringValue(attrs.model) ?? stringValue(attrs["gen_ai.request.model"]);
3090
+ const tool = stringValue(attrs["tool.name"]) ?? stringValue(attrs.tool_name) ?? stringValue(attrs["mcp.tool.name"]);
3091
+ const sessionId = stringValue(row.session_id) ?? stringValue(attrs["session.id"]);
3092
+ const extra = {
3093
+ ...preserveRawAttributes(attrs),
3094
+ "traces.source_format": "intelligence-spans",
3095
+ "traces.row.index": index
3096
+ };
3097
+ copyDefined(extra, {
3098
+ "traces.row.id": stringValue(row.id),
3099
+ [ATTR.SESSION_ID]: sessionId,
3100
+ "session.id": sessionId,
3101
+ "run.id": stringValue(row.run_id),
3102
+ "thread.id": stringValue(row.thread_id),
3103
+ "scenario.id": stringValue(row.scenario_id),
3104
+ "generation.id": stringValue(row.generation),
3105
+ "cell.id": stringValue(row.cell_id),
3106
+ "project.key": stringValue(row.project_key),
3107
+ "llm.cost_usd": numberValue(row.cost_usd),
3108
+ "redaction.version": stringValue(row.redaction_version),
3109
+ "traces.received_at": isoTime(row.received_at)
3110
+ });
3111
+ return span({
3112
+ traceId: stringValue(row.trace_id) ?? hashId(row, 32),
3113
+ spanId: stringValue(row.id) ?? hashId({ row, field: "span" }),
3114
+ parentSpanId: stringValue(row.parent_span_id) ?? null,
3115
+ name,
3116
+ kind: intelligenceSpanKind(name, attrs),
3117
+ startTime,
3118
+ endTime,
3119
+ status: statusCode(row.status_code),
3120
+ statusMessage: stringValue(row.status_message),
3121
+ service: stringValue(attrs["service.name"]) ?? stringValue(attrs.service_name) ?? "tangle-intelligence",
3122
+ agent: stringValue(attrs["agent.name"]) ?? stringValue(attrs["service.name"]) ?? null,
3123
+ model,
3124
+ tool,
3125
+ inputTokens: firstNumber(row, attrs, ["input_tokens", "llm.input_tokens"]),
3126
+ outputTokens: firstNumber(row, attrs, ["output_tokens", "llm.output_tokens"]),
3127
+ step: numberValue(attrs["interaction.sequence"]) ?? index,
3128
+ content: stringValue(attrs.user_prompt) ?? stringValue(attrs["gen_ai.prompt.messages"]) ?? stringValue(attrs["log.content.task"]) ?? null,
3129
+ extra
3130
+ });
3131
+ });
3132
+ }
3011
3133
  function openInferenceToSpans(rows) {
3012
3134
  return rows.map((row) => {
3013
3135
  const attrs = isObject(row.attributes) ? { ...row.attributes } : {};
@@ -3053,7 +3175,7 @@ function withExportAttributes(spans, attributes) {
3053
3175
  }
3054
3176
  function exportTraceEvidenceRows(rows, opts = {}, wrapper) {
3055
3177
  const format = detectFormat(rows, opts.format ?? "auto");
3056
- const converted = format === "policy-evidence" ? policyEvidenceToSpans(requirePolicyEvidenceRows(rows)) : format === "openinference" ? openInferenceToSpans(requireOpenInferenceRows(rows)) : sandboxEventsToSpans(requireObjectRows(rows), wrapper);
3178
+ const converted = format === "policy-evidence" ? policyEvidenceToSpans(requirePolicyEvidenceRows(rows)) : format === "openinference" ? openInferenceToSpans(requireOpenInferenceRows(rows)) : format === "intelligence-spans" ? intelligenceSpansToSpans(requireIntelligenceSpanRows(rows)) : sandboxEventsToSpans(requireObjectRows(rows), wrapper);
3057
3179
  const spans = withExportAttributes(converted, opts.attributes);
3058
3180
  if (spans.length === 0) throw new Error(`no spans exported from ${format} input`);
3059
3181
  const redacted = redactSpans(spans);
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  writePolicyEvidenceFile,
41
41
  writeSessionIndexFile,
42
42
  writeTraceEvidenceExportFile
43
- } from "./chunk-EOQY2RIK.js";
43
+ } from "./chunk-QM4QAG3V.js";
44
44
 
45
45
  // src/cli.ts
46
46
  import { readFileSync } from "fs";
@@ -326,10 +326,7 @@ async function cmdInspect(args) {
326
326
  }
327
327
  async function cmdExport(args) {
328
328
  if (!args.input) throw new Error("export needs an input file; run `traces export --help` for examples");
329
- const format = args.format ?? "auto";
330
- if (format !== "auto" && format !== "policy-evidence" && format !== "sandbox-events" && format !== "openinference") {
331
- throw new Error("--format must be auto, policy-evidence, sandbox-events, or openinference");
332
- }
329
+ const format = traceEvidenceFormat(args.format);
333
330
  const attributes = await loadExportAttributes(args);
334
331
  const outPath = args.out ?? args.otlp;
335
332
  if (outPath) {
@@ -342,6 +339,13 @@ async function cmdExport(args) {
342
339
  const result = await exportTraceEvidenceFile(args.input, { format, attributes });
343
340
  process.stdout.write(serializeSpans(result.spans));
344
341
  }
342
+ function traceEvidenceFormat(raw) {
343
+ const format = raw ?? "auto";
344
+ if (format !== "auto" && format !== "policy-evidence" && format !== "sandbox-events" && format !== "openinference" && format !== "intelligence-spans") {
345
+ throw new Error("--format must be auto, policy-evidence, sandbox-events, openinference, or intelligence-spans");
346
+ }
347
+ return format;
348
+ }
345
349
  function parseAttributeValue(raw) {
346
350
  if (raw === "true") return true;
347
351
  if (raw === "false") return false;
@@ -368,7 +372,7 @@ async function loadExportAttributes(args) {
368
372
  return attributes;
369
373
  }
370
374
  async function cmdAnalyze(args) {
371
- const { spans, harness, sessionCount, cwds } = await collectSpans(args);
375
+ const { spans, harness, sessionCount, cwds } = args.input ? await collectImportedSpans(args) : await collectSpans(args);
372
376
  if (spans.length === 0) throw new Error("no spans found for the given selection");
373
377
  const ai = args.llm ? await buildAxService() : void 0;
374
378
  const { otlpPath, result } = await analyzeSpans(spans, {
@@ -405,6 +409,21 @@ ${r.ok ? r.output || "(no output)" : `failed: ${r.error}`}`;
405
409
  console.log(report);
406
410
  }
407
411
  }
412
+ async function collectImportedSpans(args) {
413
+ if (!args.input) throw new Error("analyze input missing");
414
+ const attributes = await loadExportAttributes(args);
415
+ const result = await exportTraceEvidenceFile(args.input, {
416
+ format: traceEvidenceFormat(args.format),
417
+ attributes
418
+ });
419
+ const traceIds = new Set(result.spans.map((s) => s.trace_id).filter(Boolean));
420
+ return {
421
+ spans: result.spans,
422
+ harness: result.format,
423
+ sessionCount: traceIds.size || 1,
424
+ cwds: []
425
+ };
426
+ }
408
427
  function externalAnalyzersFromArgs(args) {
409
428
  return args.analyzers.map(
410
429
  (spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt, model: args.model }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
@@ -560,12 +579,14 @@ Input formats:
560
579
  policy-evidence Compact JSONL rows from \`traces evidence --out policy-evidence.jsonl\`
561
580
  sandbox-events Sandbox/OpenCode event arrays with start/raw/result/done/error events
562
581
  openinference Existing OpenInference JSONL; rewrites through traces redaction
582
+ intelligence-spans JSONL rows exported from Tangle Intelligence trace spans
563
583
  auto Detect the format from the file contents (default)
564
584
 
565
585
  Examples:
566
586
  traces evidence --all --since 24h --out policy-evidence.jsonl
567
587
  traces export policy-evidence.jsonl --out spans.openinference.jsonl
568
588
  traces export sandbox-events.json --format sandbox-events --out spans.openinference.jsonl
589
+ traces export intelligence-spans.jsonl --out spans.openinference.jsonl
569
590
  traces export sandbox-events.json --attr task.id=aec-001 --attr outcome.score=1 --out spans.openinference.jsonl
570
591
  halo spans.openinference.jsonl --prompt "Analyze this trace slice" --max-turns 1
571
592
 
@@ -578,7 +599,7 @@ function usage() {
578
599
 
579
600
  Commands:
580
601
  list List discovered sessions
581
- analyze Run analyst suite + loop/waste pipelines, write a markdown report
602
+ analyze Run analyst suite + loop/waste pipelines over sessions or an input file
582
603
  investigate Run typed investigation flow, including BYO config + recommendations
583
604
  improve Write a full improvement artifact directory for review/apply/rerun
584
605
  convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
@@ -599,9 +620,9 @@ Options:
599
620
  --out <path> Write report to a file
600
621
  --dir <path> improve: write artifacts to this directory
601
622
  --otlp <path> OTLP artifact path (also evidence provenance / dry-run upload preview)
602
- --format <kind> export: auto | policy-evidence | sandbox-events | openinference
603
- --metadata <json> export: attach JSON object fields as span attributes
604
- --attr <k=v> export: attach one span attribute (repeatable)
623
+ --format <kind> analyze/export file: auto | policy-evidence | sandbox-events | openinference | intelligence-spans
624
+ --metadata <json> analyze/export file: attach JSON object fields as span attributes
625
+ --attr <k=v> analyze/export file: attach one span attribute (repeatable)
605
626
  --llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
606
627
  --model <id> --llm model id (e.g. a router model like glm-5.2); default is agent-eval's
607
628
  --config <path> investigate/improve: JS config with analysts, external analyzers, or proposal adapter
package/dist/index.d.ts CHANGED
@@ -998,7 +998,7 @@ declare function readSessionIndexFile(path: string): Promise<TraceSessionIndex>;
998
998
  declare function writeInspectionReportFile(report: TraceInspectionReport, outPath?: string): Promise<string>;
999
999
 
1000
1000
  type JsonObject = Record<string, unknown>;
1001
- type TraceEvidenceInputFormat = 'policy-evidence' | 'sandbox-events' | 'openinference';
1001
+ type TraceEvidenceInputFormat = 'policy-evidence' | 'sandbox-events' | 'openinference' | 'intelligence-spans';
1002
1002
  type TraceEvidenceFormatOption = TraceEvidenceInputFormat | 'auto';
1003
1003
  interface TraceEvidenceExportOptions {
1004
1004
  readonly format?: TraceEvidenceFormatOption;
package/dist/index.js CHANGED
@@ -89,7 +89,7 @@ import {
89
89
  writeSessionIndexFile,
90
90
  writeTraceEvidenceExportFile,
91
91
  writeTraceImprovementArtifacts
92
- } from "./chunk-EOQY2RIK.js";
92
+ } from "./chunk-QM4QAG3V.js";
93
93
 
94
94
  // src/collect.ts
95
95
  async function collectSessions(opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.8.15",
3
+ "version": "0.8.17",
4
4
  "description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, …) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite — observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@ax-llm/ax": "^19.0.45",
47
- "@tangle-network/agent-eval": "^0.99.0"
47
+ "@tangle-network/agent-eval": "^0.100.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^22.0.0",