@tangle-network/agent-app 0.60.198 → 0.60.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/chat-routes/index.js +6 -4
  2. package/dist/chat-routes/index.js.map +1 -1
  3. package/dist/chat-routes/turn-routes.d.ts +28 -0
  4. package/dist/{chunk-XJEIGVMQ.js → chunk-2OWT3TNV.js} +2 -2
  5. package/dist/{chunk-XP77DJEF.js → chunk-6XXKFYIM.js} +72 -30
  6. package/dist/chunk-6XXKFYIM.js.map +1 -0
  7. package/dist/chunk-7XQIY2LU.js +151 -0
  8. package/dist/chunk-7XQIY2LU.js.map +1 -0
  9. package/dist/chunk-BKPJFES3.js +229 -0
  10. package/dist/chunk-BKPJFES3.js.map +1 -0
  11. package/dist/{chunk-FPOOBGRG.js → chunk-T4BLISYH.js} +40 -5
  12. package/dist/chunk-T4BLISYH.js.map +1 -0
  13. package/dist/{chunk-SUIH5B55.js → chunk-ZHJ2TDWA.js} +199 -17
  14. package/dist/chunk-ZHJ2TDWA.js.map +1 -0
  15. package/dist/public-consultation/index.d.ts +1 -1
  16. package/dist/public-consultation/index.js +2 -1
  17. package/dist/public-consultation/index.js.map +1 -1
  18. package/dist/runtime/index.js +4 -2
  19. package/dist/runtime/index.js.map +1 -1
  20. package/dist/runtime/router-turn.d.ts +14 -1
  21. package/dist/runtime/router-turn.js +3 -1
  22. package/dist/sandbox/index.d.ts +20 -0
  23. package/dist/sandbox/index.js +4 -1
  24. package/dist/sandbox/prompt-trace.d.ts +26 -0
  25. package/dist/sandbox/sandbox-run-tool.d.ts +8 -0
  26. package/dist/trace/index.d.ts +0 -1
  27. package/dist/trace/index.js +0 -240
  28. package/dist/trace/index.js.map +1 -1
  29. package/dist/turn-trace/index.d.ts +11 -0
  30. package/dist/turn-trace/index.js +121 -0
  31. package/dist/turn-trace/index.js.map +1 -0
  32. package/dist/turn-trace/otel.d.ts +36 -0
  33. package/dist/turn-trace/recorder.d.ts +122 -0
  34. package/dist/turn-trace/server-timing.d.ts +15 -0
  35. package/dist/turn-trace/traced-fetch.d.ts +31 -0
  36. package/package.json +6 -1
  37. package/dist/chunk-FPOOBGRG.js.map +0 -1
  38. package/dist/chunk-SUIH5B55.js.map +0 -1
  39. package/dist/chunk-XP77DJEF.js.map +0 -1
  40. package/dist/trace/stage-timing.d.ts +0 -61
  41. /package/dist/{chunk-XJEIGVMQ.js.map → chunk-2OWT3TNV.js.map} +0 -0
@@ -46,242 +46,6 @@ function randomHex(byteLength) {
46
46
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
47
47
  }
48
48
 
49
- // src/trace/stage-timing.ts
50
- var STAGE_TIMING_EVENT = "stage_timing";
51
- var STAGE_TIMING_VERSION = 1;
52
- var MAX_STAGE_LENGTH = 80;
53
- var MAX_IDENTIFIER_LENGTH = 128;
54
- var MAX_DETAIL_KEYS = 8;
55
- var MAX_DETAIL_KEY_LENGTH = 64;
56
- var MAX_DETAIL_STRING_LENGTH = 64;
57
- var SENSITIVE_KEY = /(?:api|auth|access|refresh|session|private|secret|password|credential|cookie|bearer|token|ssn|ein|email|phone)[_-]?(?:key|token|secret)?/iu;
58
- var CREDENTIAL_VALUE = /\b(?:bearer|basic|digest|token)\s+\S+|\bapi[-_ ]?key\s*[:=]\s*\S+/iu;
59
- function boundedIdentifier(value) {
60
- if (typeof value !== "string") return void 0;
61
- const trimmed = value.trim();
62
- return trimmed ? trimmed.slice(0, MAX_IDENTIFIER_LENGTH) : void 0;
63
- }
64
- function safeProperty(value, key) {
65
- try {
66
- return Reflect.get(value, key);
67
- } catch {
68
- return void 0;
69
- }
70
- }
71
- function isObject(value) {
72
- return value !== null && typeof value === "object";
73
- }
74
- function sanitizeDetail(detail) {
75
- try {
76
- if (!isObject(detail) || Array.isArray(detail)) return void 0;
77
- const entries = [];
78
- for (const [rawKey, value] of Object.entries(detail)) {
79
- if (entries.length >= MAX_DETAIL_KEYS) break;
80
- const normalizedKey = rawKey.trim();
81
- if (!normalizedKey || SENSITIVE_KEY.test(normalizedKey) || value === void 0) {
82
- continue;
83
- }
84
- const key = normalizedKey.slice(0, MAX_DETAIL_KEY_LENGTH);
85
- if (typeof value === "string") {
86
- if (CREDENTIAL_VALUE.test(value)) continue;
87
- entries.push([key, value.slice(0, MAX_DETAIL_STRING_LENGTH)]);
88
- } else if (typeof value === "number") {
89
- entries.push([key, Number.isFinite(value) ? value : null]);
90
- } else if (typeof value === "boolean" || value === null) {
91
- entries.push([key, value]);
92
- }
93
- }
94
- return entries.length > 0 ? Object.fromEntries(entries) : void 0;
95
- } catch {
96
- return void 0;
97
- }
98
- }
99
- function isStageTimingKind(value) {
100
- return value === "leaf" || value === "span";
101
- }
102
- function isStageTimingOutcome(value) {
103
- return value === "ok" || value === "error" || value === "timeout" || value === "skipped";
104
- }
105
- function buildStageTimingRecord(context, stage, startedAt, durationMs, input = {}) {
106
- try {
107
- const name = typeof stage === "string" ? stage.trim() : "";
108
- if (!name || !Number.isFinite(startedAt) || startedAt < 0 || !Number.isFinite(durationMs) || durationMs < 0 || !isObject(context) || !isObject(input)) {
109
- return null;
110
- }
111
- const rawKind = safeProperty(input, "kind");
112
- const rawOutcome = safeProperty(input, "outcome");
113
- const kind = rawKind === void 0 ? "leaf" : rawKind;
114
- const outcome = rawOutcome === void 0 ? "ok" : rawOutcome;
115
- if (!isStageTimingKind(kind) || !isStageTimingOutcome(outcome)) return null;
116
- const runId = boundedIdentifier(safeProperty(context, "runId"));
117
- if (!runId) return null;
118
- const record = {
119
- evt: STAGE_TIMING_EVENT,
120
- v: STAGE_TIMING_VERSION,
121
- stage: name.slice(0, MAX_STAGE_LENGTH),
122
- kind,
123
- startedAt: Math.round(startedAt),
124
- durationMs: Math.round(durationMs),
125
- outcome,
126
- runId
127
- };
128
- for (const key of [
129
- "workspaceId",
130
- "threadId",
131
- "sandboxId",
132
- "model",
133
- "harness",
134
- "path"
135
- ]) {
136
- const value = boundedIdentifier(safeProperty(context, key));
137
- if (value) record[key] = value;
138
- }
139
- const attempt = safeProperty(input, "attempt");
140
- if (typeof attempt === "number" && Number.isSafeInteger(attempt) && attempt >= 0) {
141
- record.attempt = attempt;
142
- }
143
- const detail = sanitizeDetail(safeProperty(input, "detail"));
144
- if (detail) record.detail = detail;
145
- return record;
146
- } catch {
147
- return null;
148
- }
149
- }
150
- function safePrimitiveText(value) {
151
- if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && typeof value !== "bigint" && typeof value !== "symbol") {
152
- return "";
153
- }
154
- try {
155
- return String(value);
156
- } catch {
157
- return "";
158
- }
159
- }
160
- function outcomeForError(error) {
161
- try {
162
- const objectLike = isObject(error) || typeof error === "function";
163
- const code = objectLike ? safePrimitiveText(safeProperty(error, "code")) : "";
164
- const message = objectLike ? safePrimitiveText(safeProperty(error, "message")) : safePrimitiveText(error);
165
- return /timeout|timed ?out|deadline/iu.test(`${code} ${message}`) ? "timeout" : "error";
166
- } catch {
167
- return "error";
168
- }
169
- }
170
- function safeClock(now) {
171
- try {
172
- const value = now();
173
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
174
- } catch {
175
- return void 0;
176
- }
177
- }
178
- function safeObject(value) {
179
- try {
180
- if (!isObject(value) || Array.isArray(value)) return {};
181
- return { ...value };
182
- } catch {
183
- return {};
184
- }
185
- }
186
- function mergeInputs(base, extra) {
187
- const baseObject = safeObject(base);
188
- const extraObject = safeObject(extra);
189
- const detail = {
190
- ...safeObject(baseObject.detail),
191
- ...safeObject(extraObject.detail)
192
- };
193
- return {
194
- ...baseObject,
195
- ...extraObject,
196
- detail
197
- };
198
- }
199
- function withOutcome(extra, outcome) {
200
- return { ...safeObject(extra), outcome };
201
- }
202
- function createStageTiming(options) {
203
- const now = options.now ?? (() => Date.now());
204
- const generatedRunId = () => {
205
- try {
206
- const value = (options.createRunId ?? (() => crypto.randomUUID()))();
207
- return boundedIdentifier(value) ?? crypto.randomUUID();
208
- } catch {
209
- try {
210
- return crypto.randomUUID();
211
- } catch {
212
- return "run-unknown";
213
- }
214
- }
215
- };
216
- const context = {
217
- ...options.context,
218
- runId: boundedIdentifier(options.context?.runId) ?? generatedRunId()
219
- };
220
- const emit = (stage, startedAt, durationMs, input = {}) => {
221
- try {
222
- const record = buildStageTimingRecord(context, stage, startedAt, durationMs, input);
223
- if (!record) return;
224
- const result = options.emit(record);
225
- if (result && typeof result.then === "function") {
226
- void Promise.resolve(result).catch(() => void 0);
227
- }
228
- } catch {
229
- }
230
- };
231
- const start = (stage, input = {}) => {
232
- const observedStart = safeClock(now);
233
- const startedAt = observedStart ?? 0;
234
- let emitted = false;
235
- const finish = (extra = {}) => {
236
- if (emitted) return;
237
- emitted = true;
238
- if (observedStart === void 0) return;
239
- const observedEnd = safeClock(now);
240
- if (observedEnd === void 0) return;
241
- try {
242
- emit(stage, observedStart, observedEnd - observedStart, mergeInputs(input, extra));
243
- } catch {
244
- }
245
- };
246
- return {
247
- startedAt,
248
- done: finish,
249
- fail(error, extra = {}) {
250
- let outcome = "error";
251
- try {
252
- outcome = outcomeForError(error);
253
- } catch {
254
- }
255
- finish(withOutcome(extra, outcome));
256
- }
257
- };
258
- };
259
- return {
260
- context,
261
- setContext(input) {
262
- try {
263
- Object.assign(context, input);
264
- } catch {
265
- }
266
- },
267
- recordDuration(stage, startedAt, durationMs, input = {}) {
268
- emit(stage, startedAt, durationMs, input);
269
- },
270
- start,
271
- async measure(stage, input, operation) {
272
- const handle = start(stage, input);
273
- try {
274
- const value = await operation();
275
- handle.done();
276
- return value;
277
- } catch (error) {
278
- handle.fail(error);
279
- throw error;
280
- }
281
- }
282
- };
283
- }
284
-
285
49
  // src/trace/index.ts
286
50
  function timedEventsFromLines(lines) {
287
51
  const out = [];
@@ -420,14 +184,10 @@ function renderHistogram(values, opts) {
420
184
  return lines.join("\n");
421
185
  }
422
186
  export {
423
- STAGE_TIMING_EVENT,
424
- STAGE_TIMING_VERSION,
425
187
  buildFlowTrace,
426
- buildStageTimingRecord,
427
188
  childSpanContext,
428
189
  composeMissionFlowTrace,
429
190
  createMissionTraceContext,
430
- createStageTiming,
431
191
  delegationActivityToFlowSpans,
432
192
  loopTraceEventsToFlowSpans,
433
193
  renderHistogram,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/trace/mission-trace.ts","../../src/trace/stage-timing.ts","../../src/trace/index.ts"],"sourcesContent":["/**\n * Mission trace context — mint + thread the trace ids that join a mission's\n * step attempts and its delegated agent runs into ONE trace tree.\n *\n * ID formats are byte-compatible with agent-runtime's trace propagation\n * (`readTraceContextFromEnv` / OTLP export): 32 lowercase hex chars for a\n * trace id (16 bytes), 16 for a span id (8 bytes). The env pair from\n * {@link traceEnv} is exactly what agent-runtime's MCP subprocess reads at\n * startup (`TRACE_ID` + `PARENT_SPAN_ID`), so a delegation dispatched with it\n * parents its loop→round→iteration spans under the mission's step span.\n *\n * Pure functions, no deps. Ids are DETERMINISTIC when a key is supplied\n * (missionId / step-attempt seed) so a crashed driver re-mints the identical\n * context on re-dispatch and the re-run joins the same trace instead of\n * forking a new one; without a key they are random.\n */\n\nexport interface MissionTraceContext {\n /** 32-hex trace id shared by every span in the mission's tree. */\n traceId: string\n /** 16-hex span id of the mission root — the parent of every step span. */\n rootSpanId: string\n}\n\n/** Define context information for a step span including trace, span, and parent span identifiers */\nexport interface StepSpanContext {\n traceId: string\n /** 16-hex span id of this step attempt (or any nested unit of work). */\n spanId: string\n /** The span this one nests under. */\n parentSpanId: string\n}\n\n/**\n * Mint a mission's trace context. With `missionId` the ids are a pure\n * function of it; omitted, both ids are random.\n */\nexport function createMissionTraceContext(missionId?: string): MissionTraceContext {\n if (missionId !== undefined && missionId !== '') {\n return {\n traceId: hex64(fnv1a64(`mission-trace:${missionId}`)) + hex64(fnv1a64(`mission-trace:2:${missionId}`)),\n rootSpanId: hex64(fnv1a64(`mission-root-span:${missionId}`)),\n }\n }\n return { traceId: randomHex(16), rootSpanId: randomHex(8) }\n}\n\n/**\n * Derive a child span context under `parent` — one per step attempt (seed\n * e.g. `\"${stepId}#${attempt}\"`), or nested under another step span. With a\n * seed the span id is deterministic for the same parent + seed; omitted, it\n * is random.\n */\nexport function childSpanContext(\n parent: MissionTraceContext | StepSpanContext,\n seed?: string,\n): StepSpanContext {\n const parentSpanId = 'rootSpanId' in parent ? parent.rootSpanId : parent.spanId\n const spanId =\n seed !== undefined && seed !== ''\n ? hex64(fnv1a64(`span:${parent.traceId}:${parentSpanId}:${seed}`))\n : randomHex(8)\n return { traceId: parent.traceId, spanId, parentSpanId }\n}\n\n/**\n * The env pair a delegation subprocess inherits — agent-runtime's\n * `readTraceContextFromEnv` reads exactly these names. `PARENT_SPAN_ID` is\n * the span the dispatched work nests under: the root for a mission context,\n * the step-attempt span for a step context.\n */\nexport function traceEnv(ctx: MissionTraceContext | StepSpanContext): {\n TRACE_ID: string\n PARENT_SPAN_ID: string\n} {\n return {\n TRACE_ID: ctx.traceId,\n PARENT_SPAN_ID: 'rootSpanId' in ctx ? ctx.rootSpanId : ctx.spanId,\n }\n}\n\n// FNV-1a 64-bit over UTF-16 code units — a stable, dependency-free digest for\n// deterministic ids. Not cryptographic; trace ids only need uniqueness within\n// a tenant's missions, not unforgeability.\nconst FNV_OFFSET = 0xcbf29ce484222325n\nconst FNV_PRIME = 0x100000001b3n\nconst MASK_64 = 0xffffffffffffffffn\n\nfunction fnv1a64(input: string): bigint {\n let hash = FNV_OFFSET\n for (let i = 0; i < input.length; i++) {\n hash ^= BigInt(input.charCodeAt(i))\n hash = (hash * FNV_PRIME) & MASK_64\n }\n return hash\n}\n\nfunction hex64(value: bigint): string {\n return value.toString(16).padStart(16, '0')\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength)\n globalThis.crypto.getRandomValues(bytes)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n","/** Structured, bounded latency records for one request or agent turn. */\n\nexport const STAGE_TIMING_EVENT = 'stage_timing'\nexport const STAGE_TIMING_VERSION = 1\n\nexport type StageTimingOutcome = 'ok' | 'error' | 'timeout' | 'skipped'\nexport type StageTimingKind = 'leaf' | 'span'\nexport type StageTimingDetailValue = string | number | boolean | null\n\nexport interface StageTimingContext {\n runId: string\n workspaceId?: string\n threadId?: string\n sandboxId?: string\n model?: string\n harness?: string\n path?: string\n}\n\nexport interface StageTimingRecordInput {\n kind?: StageTimingKind\n outcome?: StageTimingOutcome\n attempt?: number\n /** Bounded primitive operational metadata only. Do not pass user input or credentials. */\n detail?: Record<string, StageTimingDetailValue | undefined>\n}\n\nexport interface StageTimingRecord extends StageTimingContext {\n evt: typeof STAGE_TIMING_EVENT\n v: typeof STAGE_TIMING_VERSION\n stage: string\n kind: StageTimingKind\n startedAt: number\n durationMs: number\n outcome: StageTimingOutcome\n attempt?: number\n detail?: Record<string, StageTimingDetailValue>\n}\n\nexport interface StageTimingHandle {\n readonly startedAt: number\n done(input?: StageTimingRecordInput): void\n fail(error: unknown, input?: StageTimingRecordInput): void\n}\n\nexport interface StageTiming {\n readonly context: Readonly<StageTimingContext>\n setContext(input: Partial<Omit<StageTimingContext, 'runId'>>): void\n recordDuration(\n stage: string,\n startedAt: number,\n durationMs: number,\n input?: StageTimingRecordInput,\n ): void\n start(stage: string, input?: StageTimingRecordInput): StageTimingHandle\n measure<T>(\n stage: string,\n input: StageTimingRecordInput,\n operation: () => Promise<T>,\n ): Promise<T>\n}\n\nexport interface CreateStageTimingOptions {\n context?: Partial<StageTimingContext>\n /** Carrier for a validated record. Throws and rejected promises are contained. */\n emit(record: StageTimingRecord): void | Promise<void>\n /** Epoch-millisecond clock. Defaults to `Date.now`; clock failures drop the record. */\n now?(): number\n /** ID seam used only when `context.runId` is absent. */\n createRunId?(): string\n}\n\nconst MAX_STAGE_LENGTH = 80\nconst MAX_IDENTIFIER_LENGTH = 128\nconst MAX_DETAIL_KEYS = 8\nconst MAX_DETAIL_KEY_LENGTH = 64\nconst MAX_DETAIL_STRING_LENGTH = 64\nconst SENSITIVE_KEY =\n /(?:api|auth|access|refresh|session|private|secret|password|credential|cookie|bearer|token|ssn|ein|email|phone)[_-]?(?:key|token|secret)?/iu\nconst CREDENTIAL_VALUE =\n /\\b(?:bearer|basic|digest|token)\\s+\\S+|\\bapi[-_ ]?key\\s*[:=]\\s*\\S+/iu\n\nfunction boundedIdentifier(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed ? trimmed.slice(0, MAX_IDENTIFIER_LENGTH) : undefined\n}\n\nfunction safeProperty(value: object, key: string): unknown {\n try {\n return Reflect.get(value, key)\n } catch {\n return undefined\n }\n}\n\nfunction isObject(value: unknown): value is object {\n return value !== null && typeof value === 'object'\n}\n\nfunction sanitizeDetail(\n detail: unknown,\n): Record<string, StageTimingDetailValue> | undefined {\n try {\n if (!isObject(detail) || Array.isArray(detail)) return undefined\n const entries: [string, StageTimingDetailValue][] = []\n for (const [rawKey, value] of Object.entries(detail)) {\n if (entries.length >= MAX_DETAIL_KEYS) break\n const normalizedKey = rawKey.trim()\n if (\n !normalizedKey ||\n SENSITIVE_KEY.test(normalizedKey) ||\n value === undefined\n ) {\n continue\n }\n const key = normalizedKey.slice(0, MAX_DETAIL_KEY_LENGTH)\n if (typeof value === 'string') {\n if (CREDENTIAL_VALUE.test(value)) continue\n entries.push([key, value.slice(0, MAX_DETAIL_STRING_LENGTH)])\n } else if (typeof value === 'number') {\n entries.push([key, Number.isFinite(value) ? value : null])\n } else if (typeof value === 'boolean' || value === null) {\n entries.push([key, value])\n }\n }\n return entries.length > 0 ? Object.fromEntries(entries) : undefined\n } catch {\n return undefined\n }\n}\n\nfunction isStageTimingKind(value: unknown): value is StageTimingKind {\n return value === 'leaf' || value === 'span'\n}\n\nfunction isStageTimingOutcome(value: unknown): value is StageTimingOutcome {\n return value === 'ok' || value === 'error' || value === 'timeout' || value === 'skipped'\n}\n\n/** Build one safe record, or return null when its required fields are invalid. */\nexport function buildStageTimingRecord(\n context: StageTimingContext,\n stage: string,\n startedAt: number,\n durationMs: number,\n input: StageTimingRecordInput = {},\n): StageTimingRecord | null {\n try {\n const name = typeof stage === 'string' ? stage.trim() : ''\n if (\n !name ||\n !Number.isFinite(startedAt) ||\n startedAt < 0 ||\n !Number.isFinite(durationMs) ||\n durationMs < 0 ||\n !isObject(context) ||\n !isObject(input)\n ) {\n return null\n }\n\n const rawKind = safeProperty(input, 'kind')\n const rawOutcome = safeProperty(input, 'outcome')\n const kind = rawKind === undefined ? 'leaf' : rawKind\n const outcome = rawOutcome === undefined ? 'ok' : rawOutcome\n if (!isStageTimingKind(kind) || !isStageTimingOutcome(outcome)) return null\n\n const runId = boundedIdentifier(safeProperty(context, 'runId'))\n if (!runId) return null\n const record: StageTimingRecord = {\n evt: STAGE_TIMING_EVENT,\n v: STAGE_TIMING_VERSION,\n stage: name.slice(0, MAX_STAGE_LENGTH),\n kind,\n startedAt: Math.round(startedAt),\n durationMs: Math.round(durationMs),\n outcome,\n runId,\n }\n\n for (const key of [\n 'workspaceId',\n 'threadId',\n 'sandboxId',\n 'model',\n 'harness',\n 'path',\n ] as const) {\n const value = boundedIdentifier(safeProperty(context, key))\n if (value) record[key] = value\n }\n const attempt = safeProperty(input, 'attempt')\n if (\n typeof attempt === 'number' &&\n Number.isSafeInteger(attempt) &&\n attempt >= 0\n ) {\n record.attempt = attempt\n }\n const detail = sanitizeDetail(safeProperty(input, 'detail'))\n if (detail) record.detail = detail\n return record\n } catch {\n return null\n }\n}\n\nfunction safePrimitiveText(value: unknown): string {\n if (\n typeof value !== 'string' &&\n typeof value !== 'number' &&\n typeof value !== 'boolean' &&\n typeof value !== 'bigint' &&\n typeof value !== 'symbol'\n ) {\n return ''\n }\n try {\n return String(value)\n } catch {\n return ''\n }\n}\n\nfunction outcomeForError(error: unknown): StageTimingOutcome {\n try {\n const objectLike = isObject(error) || typeof error === 'function'\n const code = objectLike ? safePrimitiveText(safeProperty(error as object, 'code')) : ''\n const message = objectLike\n ? safePrimitiveText(safeProperty(error as object, 'message'))\n : safePrimitiveText(error)\n return /timeout|timed ?out|deadline/iu.test(`${code} ${message}`)\n ? 'timeout'\n : 'error'\n } catch {\n return 'error'\n }\n}\n\nfunction safeClock(now: () => number): number | undefined {\n try {\n const value = now()\n return typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? value\n : undefined\n } catch {\n return undefined\n }\n}\n\nfunction safeObject(value: unknown): Record<string, unknown> {\n try {\n if (!isObject(value) || Array.isArray(value)) return {}\n return { ...value }\n } catch {\n return {}\n }\n}\n\nfunction mergeInputs(\n base: unknown,\n extra: unknown,\n): StageTimingRecordInput {\n const baseObject = safeObject(base)\n const extraObject = safeObject(extra)\n const detail = {\n ...safeObject(baseObject.detail),\n ...safeObject(extraObject.detail),\n }\n return {\n ...baseObject,\n ...extraObject,\n detail: detail as Record<string, StageTimingDetailValue | undefined>,\n }\n}\n\nfunction withOutcome(extra: unknown, outcome: StageTimingOutcome): StageTimingRecordInput {\n return { ...safeObject(extra), outcome }\n}\n\n/**\n * Create a stage timer with an injected carrier. Invalid records and carrier\n * failures are dropped because telemetry cannot decide request success.\n */\nexport function createStageTiming(options: CreateStageTimingOptions): StageTiming {\n const now = options.now ?? (() => Date.now())\n const generatedRunId = () => {\n try {\n const value = (options.createRunId ?? (() => crypto.randomUUID()))()\n return boundedIdentifier(value) ?? crypto.randomUUID()\n } catch {\n try {\n return crypto.randomUUID()\n } catch {\n return 'run-unknown'\n }\n }\n }\n const context: StageTimingContext = {\n ...options.context,\n runId: boundedIdentifier(options.context?.runId) ?? generatedRunId(),\n }\n\n const emit = (\n stage: string,\n startedAt: number,\n durationMs: number,\n input: StageTimingRecordInput = {},\n ): void => {\n try {\n const record = buildStageTimingRecord(context, stage, startedAt, durationMs, input)\n if (!record) return\n const result = options.emit(record)\n if (result && typeof result.then === 'function') {\n void Promise.resolve(result).catch(() => undefined)\n }\n } catch {\n // Telemetry cannot decide whether the measured operation succeeds.\n }\n }\n\n const start = (\n stage: string,\n input: StageTimingRecordInput = {},\n ): StageTimingHandle => {\n const observedStart = safeClock(now)\n const startedAt = observedStart ?? 0\n let emitted = false\n const finish = (extra: StageTimingRecordInput = {}) => {\n if (emitted) return\n emitted = true\n if (observedStart === undefined) return\n const observedEnd = safeClock(now)\n if (observedEnd === undefined) return\n try {\n emit(stage, observedStart, observedEnd - observedStart, mergeInputs(input, extra))\n } catch {\n // A timer must never change the measured operation.\n }\n }\n return {\n startedAt,\n done: finish,\n fail(error, extra = {}) {\n let outcome: StageTimingOutcome = 'error'\n try {\n outcome = outcomeForError(error)\n } catch {\n // Keep the fallback error outcome when classification is hostile.\n }\n finish(withOutcome(extra, outcome))\n },\n }\n }\n\n return {\n context,\n setContext(input) {\n try {\n Object.assign(context, input)\n } catch {\n // Telemetry context updates cannot affect the measured operation.\n }\n },\n recordDuration(stage, startedAt, durationMs, input = {}) {\n emit(stage, startedAt, durationMs, input)\n },\n start,\n async measure(stage, input, operation) {\n const handle = start(stage, input)\n try {\n const value = await operation()\n handle.done()\n return value\n } catch (error) {\n handle.fail(error)\n throw error\n }\n },\n }\n}\n","/**\n * `@tangle-network/agent-app/trace` — flow observability for agent turns.\n *\n * The turn buffer stamps `_t` (ms since turn start) on every event, so any\n * live stream OR any historical turn replayed from a TurnEventStore can be\n * reconstructed into a span trace: pipeline overhead, model segments (with\n * thinking TTFT), tool executions, token usage, and cost. Renderers turn\n * traces and multi-run samples into ASCII waterfalls and histograms — the\n * default artifact for \"how did this run actually behave\" questions across\n * evals, hill-climbs, and production debugging.\n *\n * Span boundaries derived from a buffered stream are quantized by the\n * pump's flush window and the reader's poll cadence (~100–400ms); spans\n * carry `approx: true` to keep reports honest about that.\n */\n\nexport * from './mission-trace'\nexport * from './flow-types'\nexport * from './mission-flow'\nexport * from './stage-timing'\n\nimport type { FlowSpan, FlowTrace } from './flow-types'\n\n/** Represent a timed event with a timestamp and associated event data */\nexport interface TimedEvent {\n /** ms since turn start (`_t` stamped by pumpBufferedTurn). */\n t: number\n event: Record<string, unknown>\n}\n\n/** Parse stored turn-event lines (JSON strings with `_t`) into TimedEvents. */\nexport function timedEventsFromLines(lines: string[]): TimedEvent[] {\n const out: TimedEvent[] = []\n for (const line of lines) {\n try {\n const parsed = JSON.parse(line) as Record<string, unknown>\n if (typeof parsed._t === 'number') out.push({ t: parsed._t, event: parsed })\n } catch {\n /* skip torn lines */\n }\n }\n return out.sort((a, b) => a.t - b.t)\n}\n\nfunction innerOf(e: Record<string, unknown>): Record<string, unknown> {\n return (e.kind === 'event' ? (e.event as Record<string, unknown>) : e) ?? {}\n}\n\n/**\n * Derive a span trace from timestamped turn events. Model segments are runs\n * of text/reasoning deltas; a tool span opens at the last delta before its\n * tool_call emission and closes at the matching tool_result.\n */\nexport function buildFlowTrace(\n events: TimedEvent[],\n opts?: { pricing?: { prompt?: string | number; completion?: string | number } },\n): FlowTrace {\n const spans: FlowSpan[] = []\n let promptTokens = 0\n let completionTokens = 0\n let toolCalls = 0\n\n const first = events[0]?.t ?? 0\n if (first > 0) {\n spans.push({ kind: 'pipeline', name: 'dispatch → first event', startMs: 0, endMs: first })\n }\n\n let segStart: number | null = null\n let segEnd = 0\n let segKinds = new Set<string>()\n let lastDeltaT = first\n const openCalls = new Map<string, { name: string; emitT: number; lastDeltaT: number }>()\n\n const closeSegment = () => {\n if (segStart !== null) {\n spans.push({\n kind: 'model',\n name: segKinds.has('reasoning') ? 'model turn (reasoning + text)' : 'model turn',\n startMs: segStart,\n endMs: segEnd,\n approx: true,\n })\n segStart = null\n segKinds = new Set()\n }\n }\n\n for (const { t, event } of events) {\n const inner = innerOf(event)\n const type = String(event.kind === 'tool_result' ? 'tool_result' : (inner.type ?? ''))\n\n if (type === 'text' || type === 'reasoning') {\n if (segStart === null) segStart = t\n segEnd = t\n segKinds.add(type)\n lastDeltaT = t\n } else if (type === 'tool_call') {\n closeSegment()\n toolCalls++\n const call = (inner.call ?? inner) as Record<string, unknown>\n const id = String(call.toolCallId ?? `call_${toolCalls}`)\n openCalls.set(id, { name: String(call.toolName ?? 'tool'), emitT: t, lastDeltaT })\n } else if (type === 'tool_result') {\n const id = String(event.toolCallId ?? inner.toolCallId ?? '')\n const open = openCalls.get(id)\n if (open) {\n spans.push({\n kind: 'tool',\n name: open.name,\n // Execution happens between the end of the model turn that emitted\n // the call and the result landing in the buffer.\n startMs: open.lastDeltaT,\n endMs: t,\n approx: true,\n meta: { ok: ((event.outcome ?? inner.outcome) as { ok?: boolean } | undefined)?.ok },\n })\n openCalls.delete(id)\n }\n } else if (type === 'usage') {\n const u = (inner.usage ?? {}) as { promptTokens?: number; completionTokens?: number }\n promptTokens += u.promptTokens ?? 0\n completionTokens += u.completionTokens ?? 0\n }\n }\n closeSegment()\n\n const totalMs = events.length ? events[events.length - 1]!.t : 0\n const trace: FlowTrace = { spans, totalMs, promptTokens, completionTokens, toolCalls }\n const p = opts?.pricing\n if (p && (p.prompt != null || p.completion != null)) {\n trace.costUsd = promptTokens * Number(p.prompt ?? 0) + completionTokens * Number(p.completion ?? 0)\n }\n return trace\n}\n\nconst fmtS = (ms: number) => `${(ms / 1000).toFixed(1)}s`\n\n/** ASCII waterfall cascade — the default artifact for explaining a flow. */\nexport function renderWaterfall(trace: FlowTrace, opts?: { width?: number }): string {\n const width = opts?.width ?? 40\n const scale = trace.totalMs > 0 ? width / trace.totalMs : 0\n const lines: string[] = []\n const spans = [...trace.spans].sort((a, b) => a.startMs - b.startMs)\n for (let i = 0; i < spans.length; i++) {\n const s = spans[i]!\n const offset = Math.round(s.startMs * scale)\n const len = Math.max(1, Math.round((s.endMs - s.startMs) * scale))\n const bar = ' '.repeat(offset) + (s.kind === 'tool' ? '▓' : s.kind === 'pipeline' ? '░' : '█').repeat(len)\n const branch = i === spans.length - 1 ? '└─' : '├─'\n const dur = `${fmtS(s.endMs - s.startMs)}${s.approx ? '~' : ''}`\n lines.push(`${fmtS(s.startMs).padStart(7)} ${branch} ${bar.padEnd(width + 2)} ${s.name} (${dur})`)\n }\n const cost = trace.costUsd != null ? ` $${trace.costUsd.toFixed(trace.costUsd < 0.01 ? 6 : 4)}` : ''\n lines.push(\n `${fmtS(trace.totalMs).padStart(7)} ── total · ${trace.promptTokens}p + ${trace.completionTokens}c tok · ${trace.toolCalls} tool calls${cost}`,\n )\n return lines.join('\\n')\n}\n\n/** Summarize key statistics of a numerical distribution including count, min, percentiles, and max */\nexport interface DistributionSummary {\n n: number\n min: number\n p50: number\n p90: number\n max: number\n}\n\n/** Summarize numeric values into a distribution summary including count, min, median, 90th percentile, and max */\nexport function summarize(values: number[]): DistributionSummary {\n const sorted = [...values].sort((a, b) => a - b)\n const q = (p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))] ?? 0\n return { n: sorted.length, min: sorted[0] ?? 0, p50: q(0.5), p90: q(0.9), max: sorted[sorted.length - 1] ?? 0 }\n}\n\n/** ASCII histogram for multi-run samples (eval latencies, costs, scores). */\nexport function renderHistogram(\n values: number[],\n opts?: { buckets?: number; width?: number; unit?: string; format?: (v: number) => string },\n): string {\n if (!values.length) return '(no samples)'\n const buckets = opts?.buckets ?? 6\n const width = opts?.width ?? 24\n const fmt = opts?.format ?? ((v: number) => `${Math.round(v)}${opts?.unit ?? ''}`)\n const s = summarize(values)\n const lo = s.min\n const hi = s.max === s.min ? s.min + 1 : s.max\n const counts = new Array<number>(buckets).fill(0)\n for (const v of values) {\n counts[Math.min(buckets - 1, Math.floor(((v - lo) / (hi - lo)) * buckets))]!++\n }\n const maxCount = Math.max(...counts)\n const lines = [\n `n=${s.n} min=${fmt(s.min)} p50=${fmt(s.p50)} p90=${fmt(s.p90)} max=${fmt(s.max)}`,\n ]\n for (let i = 0; i < buckets; i++) {\n const a = lo + ((hi - lo) * i) / buckets\n const b = lo + ((hi - lo) * (i + 1)) / buckets\n const bar = '█'.repeat(Math.max(counts[i]! > 0 ? 1 : 0, Math.round((counts[i]! / maxCount) * width)))\n lines.push(`${fmt(a).padStart(8)}-${fmt(b).padEnd(8)} ${bar} ${counts[i]}`)\n }\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/trace/mission-trace.ts","../../src/trace/index.ts"],"sourcesContent":["/**\n * Mission trace context — mint + thread the trace ids that join a mission's\n * step attempts and its delegated agent runs into ONE trace tree.\n *\n * ID formats are byte-compatible with agent-runtime's trace propagation\n * (`readTraceContextFromEnv` / OTLP export): 32 lowercase hex chars for a\n * trace id (16 bytes), 16 for a span id (8 bytes). The env pair from\n * {@link traceEnv} is exactly what agent-runtime's MCP subprocess reads at\n * startup (`TRACE_ID` + `PARENT_SPAN_ID`), so a delegation dispatched with it\n * parents its loop→round→iteration spans under the mission's step span.\n *\n * Pure functions, no deps. Ids are DETERMINISTIC when a key is supplied\n * (missionId / step-attempt seed) so a crashed driver re-mints the identical\n * context on re-dispatch and the re-run joins the same trace instead of\n * forking a new one; without a key they are random.\n */\n\nexport interface MissionTraceContext {\n /** 32-hex trace id shared by every span in the mission's tree. */\n traceId: string\n /** 16-hex span id of the mission root — the parent of every step span. */\n rootSpanId: string\n}\n\n/** Define context information for a step span including trace, span, and parent span identifiers */\nexport interface StepSpanContext {\n traceId: string\n /** 16-hex span id of this step attempt (or any nested unit of work). */\n spanId: string\n /** The span this one nests under. */\n parentSpanId: string\n}\n\n/**\n * Mint a mission's trace context. With `missionId` the ids are a pure\n * function of it; omitted, both ids are random.\n */\nexport function createMissionTraceContext(missionId?: string): MissionTraceContext {\n if (missionId !== undefined && missionId !== '') {\n return {\n traceId: hex64(fnv1a64(`mission-trace:${missionId}`)) + hex64(fnv1a64(`mission-trace:2:${missionId}`)),\n rootSpanId: hex64(fnv1a64(`mission-root-span:${missionId}`)),\n }\n }\n return { traceId: randomHex(16), rootSpanId: randomHex(8) }\n}\n\n/**\n * Derive a child span context under `parent` — one per step attempt (seed\n * e.g. `\"${stepId}#${attempt}\"`), or nested under another step span. With a\n * seed the span id is deterministic for the same parent + seed; omitted, it\n * is random.\n */\nexport function childSpanContext(\n parent: MissionTraceContext | StepSpanContext,\n seed?: string,\n): StepSpanContext {\n const parentSpanId = 'rootSpanId' in parent ? parent.rootSpanId : parent.spanId\n const spanId =\n seed !== undefined && seed !== ''\n ? hex64(fnv1a64(`span:${parent.traceId}:${parentSpanId}:${seed}`))\n : randomHex(8)\n return { traceId: parent.traceId, spanId, parentSpanId }\n}\n\n/**\n * The env pair a delegation subprocess inherits — agent-runtime's\n * `readTraceContextFromEnv` reads exactly these names. `PARENT_SPAN_ID` is\n * the span the dispatched work nests under: the root for a mission context,\n * the step-attempt span for a step context.\n */\nexport function traceEnv(ctx: MissionTraceContext | StepSpanContext): {\n TRACE_ID: string\n PARENT_SPAN_ID: string\n} {\n return {\n TRACE_ID: ctx.traceId,\n PARENT_SPAN_ID: 'rootSpanId' in ctx ? ctx.rootSpanId : ctx.spanId,\n }\n}\n\n// FNV-1a 64-bit over UTF-16 code units — a stable, dependency-free digest for\n// deterministic ids. Not cryptographic; trace ids only need uniqueness within\n// a tenant's missions, not unforgeability.\nconst FNV_OFFSET = 0xcbf29ce484222325n\nconst FNV_PRIME = 0x100000001b3n\nconst MASK_64 = 0xffffffffffffffffn\n\nfunction fnv1a64(input: string): bigint {\n let hash = FNV_OFFSET\n for (let i = 0; i < input.length; i++) {\n hash ^= BigInt(input.charCodeAt(i))\n hash = (hash * FNV_PRIME) & MASK_64\n }\n return hash\n}\n\nfunction hex64(value: bigint): string {\n return value.toString(16).padStart(16, '0')\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength)\n globalThis.crypto.getRandomValues(bytes)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n","/**\n * `@tangle-network/agent-app/trace` — flow observability for agent turns.\n *\n * The turn buffer stamps `_t` (ms since turn start) on every event, so any\n * live stream OR any historical turn replayed from a TurnEventStore can be\n * reconstructed into a span trace: pipeline overhead, model segments (with\n * thinking TTFT), tool executions, token usage, and cost. Renderers turn\n * traces and multi-run samples into ASCII waterfalls and histograms — the\n * default artifact for \"how did this run actually behave\" questions across\n * evals, hill-climbs, and production debugging.\n *\n * Span boundaries derived from a buffered stream are quantized by the\n * pump's flush window and the reader's poll cadence (~100–400ms); spans\n * carry `approx: true` to keep reports honest about that.\n */\n\nexport * from './mission-trace'\nexport * from './flow-types'\nexport * from './mission-flow'\n\nimport type { FlowSpan, FlowTrace } from './flow-types'\n\n/** Represent a timed event with a timestamp and associated event data */\nexport interface TimedEvent {\n /** ms since turn start (`_t` stamped by pumpBufferedTurn). */\n t: number\n event: Record<string, unknown>\n}\n\n/** Parse stored turn-event lines (JSON strings with `_t`) into TimedEvents. */\nexport function timedEventsFromLines(lines: string[]): TimedEvent[] {\n const out: TimedEvent[] = []\n for (const line of lines) {\n try {\n const parsed = JSON.parse(line) as Record<string, unknown>\n if (typeof parsed._t === 'number') out.push({ t: parsed._t, event: parsed })\n } catch {\n /* skip torn lines */\n }\n }\n return out.sort((a, b) => a.t - b.t)\n}\n\nfunction innerOf(e: Record<string, unknown>): Record<string, unknown> {\n return (e.kind === 'event' ? (e.event as Record<string, unknown>) : e) ?? {}\n}\n\n/**\n * Derive a span trace from timestamped turn events. Model segments are runs\n * of text/reasoning deltas; a tool span opens at the last delta before its\n * tool_call emission and closes at the matching tool_result.\n */\nexport function buildFlowTrace(\n events: TimedEvent[],\n opts?: { pricing?: { prompt?: string | number; completion?: string | number } },\n): FlowTrace {\n const spans: FlowSpan[] = []\n let promptTokens = 0\n let completionTokens = 0\n let toolCalls = 0\n\n const first = events[0]?.t ?? 0\n if (first > 0) {\n spans.push({ kind: 'pipeline', name: 'dispatch → first event', startMs: 0, endMs: first })\n }\n\n let segStart: number | null = null\n let segEnd = 0\n let segKinds = new Set<string>()\n let lastDeltaT = first\n const openCalls = new Map<string, { name: string; emitT: number; lastDeltaT: number }>()\n\n const closeSegment = () => {\n if (segStart !== null) {\n spans.push({\n kind: 'model',\n name: segKinds.has('reasoning') ? 'model turn (reasoning + text)' : 'model turn',\n startMs: segStart,\n endMs: segEnd,\n approx: true,\n })\n segStart = null\n segKinds = new Set()\n }\n }\n\n for (const { t, event } of events) {\n const inner = innerOf(event)\n const type = String(event.kind === 'tool_result' ? 'tool_result' : (inner.type ?? ''))\n\n if (type === 'text' || type === 'reasoning') {\n if (segStart === null) segStart = t\n segEnd = t\n segKinds.add(type)\n lastDeltaT = t\n } else if (type === 'tool_call') {\n closeSegment()\n toolCalls++\n const call = (inner.call ?? inner) as Record<string, unknown>\n const id = String(call.toolCallId ?? `call_${toolCalls}`)\n openCalls.set(id, { name: String(call.toolName ?? 'tool'), emitT: t, lastDeltaT })\n } else if (type === 'tool_result') {\n const id = String(event.toolCallId ?? inner.toolCallId ?? '')\n const open = openCalls.get(id)\n if (open) {\n spans.push({\n kind: 'tool',\n name: open.name,\n // Execution happens between the end of the model turn that emitted\n // the call and the result landing in the buffer.\n startMs: open.lastDeltaT,\n endMs: t,\n approx: true,\n meta: { ok: ((event.outcome ?? inner.outcome) as { ok?: boolean } | undefined)?.ok },\n })\n openCalls.delete(id)\n }\n } else if (type === 'usage') {\n const u = (inner.usage ?? {}) as { promptTokens?: number; completionTokens?: number }\n promptTokens += u.promptTokens ?? 0\n completionTokens += u.completionTokens ?? 0\n }\n }\n closeSegment()\n\n const totalMs = events.length ? events[events.length - 1]!.t : 0\n const trace: FlowTrace = { spans, totalMs, promptTokens, completionTokens, toolCalls }\n const p = opts?.pricing\n if (p && (p.prompt != null || p.completion != null)) {\n trace.costUsd = promptTokens * Number(p.prompt ?? 0) + completionTokens * Number(p.completion ?? 0)\n }\n return trace\n}\n\nconst fmtS = (ms: number) => `${(ms / 1000).toFixed(1)}s`\n\n/** ASCII waterfall cascade — the default artifact for explaining a flow. */\nexport function renderWaterfall(trace: FlowTrace, opts?: { width?: number }): string {\n const width = opts?.width ?? 40\n const scale = trace.totalMs > 0 ? width / trace.totalMs : 0\n const lines: string[] = []\n const spans = [...trace.spans].sort((a, b) => a.startMs - b.startMs)\n for (let i = 0; i < spans.length; i++) {\n const s = spans[i]!\n const offset = Math.round(s.startMs * scale)\n const len = Math.max(1, Math.round((s.endMs - s.startMs) * scale))\n const bar = ' '.repeat(offset) + (s.kind === 'tool' ? '▓' : s.kind === 'pipeline' ? '░' : '█').repeat(len)\n const branch = i === spans.length - 1 ? '└─' : '├─'\n const dur = `${fmtS(s.endMs - s.startMs)}${s.approx ? '~' : ''}`\n lines.push(`${fmtS(s.startMs).padStart(7)} ${branch} ${bar.padEnd(width + 2)} ${s.name} (${dur})`)\n }\n const cost = trace.costUsd != null ? ` $${trace.costUsd.toFixed(trace.costUsd < 0.01 ? 6 : 4)}` : ''\n lines.push(\n `${fmtS(trace.totalMs).padStart(7)} ── total · ${trace.promptTokens}p + ${trace.completionTokens}c tok · ${trace.toolCalls} tool calls${cost}`,\n )\n return lines.join('\\n')\n}\n\n/** Summarize key statistics of a numerical distribution including count, min, percentiles, and max */\nexport interface DistributionSummary {\n n: number\n min: number\n p50: number\n p90: number\n max: number\n}\n\n/** Summarize numeric values into a distribution summary including count, min, median, 90th percentile, and max */\nexport function summarize(values: number[]): DistributionSummary {\n const sorted = [...values].sort((a, b) => a - b)\n const q = (p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))] ?? 0\n return { n: sorted.length, min: sorted[0] ?? 0, p50: q(0.5), p90: q(0.9), max: sorted[sorted.length - 1] ?? 0 }\n}\n\n/** ASCII histogram for multi-run samples (eval latencies, costs, scores). */\nexport function renderHistogram(\n values: number[],\n opts?: { buckets?: number; width?: number; unit?: string; format?: (v: number) => string },\n): string {\n if (!values.length) return '(no samples)'\n const buckets = opts?.buckets ?? 6\n const width = opts?.width ?? 24\n const fmt = opts?.format ?? ((v: number) => `${Math.round(v)}${opts?.unit ?? ''}`)\n const s = summarize(values)\n const lo = s.min\n const hi = s.max === s.min ? s.min + 1 : s.max\n const counts = new Array<number>(buckets).fill(0)\n for (const v of values) {\n counts[Math.min(buckets - 1, Math.floor(((v - lo) / (hi - lo)) * buckets))]!++\n }\n const maxCount = Math.max(...counts)\n const lines = [\n `n=${s.n} min=${fmt(s.min)} p50=${fmt(s.p50)} p90=${fmt(s.p90)} max=${fmt(s.max)}`,\n ]\n for (let i = 0; i < buckets; i++) {\n const a = lo + ((hi - lo) * i) / buckets\n const b = lo + ((hi - lo) * (i + 1)) / buckets\n const bar = '█'.repeat(Math.max(counts[i]! > 0 ? 1 : 0, Math.round((counts[i]! / maxCount) * width)))\n lines.push(`${fmt(a).padStart(8)}-${fmt(b).padEnd(8)} ${bar} ${counts[i]}`)\n }\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@tangle-network/agent-app/turn-trace` — one trace per agent turn.
3
+ *
4
+ * The recorder and the Router/Sandbox helpers are runtime-neutral. The OTLP
5
+ * sink imports the agent-runtime root, so a browser bundle must not import this
6
+ * entry; server code imports it for `createIntelligenceTurnTraceSink`.
7
+ */
8
+ export * from './recorder';
9
+ export * from './server-timing';
10
+ export * from './traced-fetch';
11
+ export * from './otel';
@@ -0,0 +1,121 @@
1
+ import {
2
+ nextCallIndex,
3
+ parseServerTiming,
4
+ serverTimingAttrs,
5
+ tracedModelFetch
6
+ } from "../chunk-7XQIY2LU.js";
7
+ import {
8
+ TURN_SPAN_KINDS,
9
+ createTurnTrace,
10
+ isTraceId,
11
+ summarizeTurnSpans,
12
+ turnTraceId
13
+ } from "../chunk-BKPJFES3.js";
14
+
15
+ // src/turn-trace/otel.ts
16
+ import {
17
+ createOtelExporter,
18
+ padSpanId,
19
+ padTraceId,
20
+ toOtelAttributes
21
+ } from "@tangle-network/agent-runtime";
22
+ var SPAN_KIND = { "model.call": "LLM", "tool.call": "TOOL" };
23
+ var msToNs = (ms) => (BigInt(Math.floor(ms)) * 1000000n).toString();
24
+ function buildTurnTraceOtelSpans(snapshot, labels = {}) {
25
+ const traceId = padTraceId(snapshot.traceId);
26
+ const root = padSpanId(snapshot.rootSpanId);
27
+ const { summary } = snapshot;
28
+ const rootAttrs = {
29
+ ...labels,
30
+ "openinference.span.kind": "CHAIN",
31
+ "tangle.execution_id": snapshot.executionId,
32
+ "tangle.trace_id": snapshot.traceId,
33
+ "tangle.started_at_ms": snapshot.startedAt,
34
+ "tangle.completed_at_ms": snapshot.endedAt,
35
+ "tangle.duration_ms": summary.wallMs,
36
+ "tangle.turn.wall_ms": summary.wallMs,
37
+ "tangle.turn.covered_ms": summary.coveredMs,
38
+ "tangle.turn.coverage_pct": summary.coveragePct,
39
+ "tangle.turn.uncovered_ms": summary.uncoveredMs,
40
+ "tangle.turn.largest_gap_ms": summary.largestGapMs,
41
+ "tangle.turn.spans": summary.spans,
42
+ "tangle.turn.model_calls": summary.modelCalls,
43
+ "tangle.turn.tool_calls": summary.toolCalls,
44
+ ...summary.dropped ? { "tangle.turn.spans_dropped": summary.dropped } : {},
45
+ ...Object.fromEntries(TURN_SPAN_KINDS.map((kind) => [`tangle.turn.${kind}_ms`, summary.byKind[kind]]))
46
+ };
47
+ const spans = [{
48
+ traceId,
49
+ spanId: root,
50
+ name: "agent_app.turn",
51
+ kind: 1,
52
+ startTimeUnixNano: msToNs(snapshot.startedAt),
53
+ endTimeUnixNano: msToNs(snapshot.endedAt),
54
+ attributes: toOtelAttributes(rootAttrs),
55
+ status: snapshot.status === "ok" ? { code: 1 } : { code: 2, ...snapshot.error ? { message: snapshot.error } : {} }
56
+ }];
57
+ for (const span of snapshot.spans) {
58
+ const attrs = {
59
+ ...span.attrs,
60
+ "openinference.span.kind": SPAN_KIND[span.name] ?? "CHAIN",
61
+ "tangle.turn.span_kind": span.kind,
62
+ "agent.parent.confidence": "explicit",
63
+ ...span.name === "tool.call" ? { "gen_ai.operation.name": "execute_tool" } : {},
64
+ ...span.unfinished ? { "tangle.turn.unfinished": true } : {},
65
+ ...span.error ? { "error.message": span.error } : {}
66
+ };
67
+ spans.push({
68
+ traceId,
69
+ spanId: padSpanId(span.spanId),
70
+ parentSpanId: padSpanId(span.parentSpanId),
71
+ name: span.name,
72
+ kind: 1,
73
+ startTimeUnixNano: msToNs(span.startMs),
74
+ endTimeUnixNano: msToNs(span.endMs),
75
+ attributes: toOtelAttributes(attrs),
76
+ status: span.error ? { code: 2, message: span.error } : { code: 1 }
77
+ });
78
+ }
79
+ return spans;
80
+ }
81
+ var DEFAULT_INTELLIGENCE_BASE_URL = "https://intelligence.tangle.tools";
82
+ function createIntelligenceTurnTraceSink(options) {
83
+ let exporter;
84
+ const exporterFor = (apiKey) => {
85
+ exporter ??= createOtelExporter({
86
+ endpoint: `${(options.baseUrl ?? DEFAULT_INTELLIGENCE_BASE_URL).replace(/\/+$/, "")}/v1/otlp`,
87
+ headers: { authorization: `Bearer ${apiKey}` },
88
+ serviceName: options.project,
89
+ resourceAttributes: { "tangle.project": options.project }
90
+ });
91
+ return exporter;
92
+ };
93
+ return async (snapshot, labels) => {
94
+ if (!options.apiKey) return;
95
+ try {
96
+ const sink = exporterFor(options.apiKey);
97
+ if (!sink) return;
98
+ for (const span of buildTurnTraceOtelSpans(snapshot, { project: options.project, ...labels })) sink.exportSpan(span);
99
+ await sink.flush();
100
+ } catch (error) {
101
+ try {
102
+ options.onError?.(error instanceof Error ? error.message.slice(0, 160) : "turn trace export failed");
103
+ } catch {
104
+ }
105
+ }
106
+ };
107
+ }
108
+ export {
109
+ TURN_SPAN_KINDS,
110
+ buildTurnTraceOtelSpans,
111
+ createIntelligenceTurnTraceSink,
112
+ createTurnTrace,
113
+ isTraceId,
114
+ nextCallIndex,
115
+ parseServerTiming,
116
+ serverTimingAttrs,
117
+ summarizeTurnSpans,
118
+ tracedModelFetch,
119
+ turnTraceId
120
+ };
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/turn-trace/otel.ts"],"sourcesContent":["/**\n * A finished turn trace as OTLP spans, sent to Tangle Intelligence.\n *\n * The span builders and the exporter are agent-runtime's (`createOtelExporter`,\n * `toOtelAttributes`, `padTraceId`); this file only maps the turn's tree onto\n * them, using the GenAI semantic-convention keys the runtime's own builders\n * write (`gen_ai.operation.name`, `gen_ai.request.model`, `gen_ai.usage.*`,\n * `gen_ai.tool.name`) and OpenInference's span kind.\n *\n * Server only: it imports the runtime root, which a browser bundle must not.\n */\nimport {\n createOtelExporter,\n padSpanId,\n padTraceId,\n toOtelAttributes,\n type OtelExporter,\n type OtelSpan,\n} from '@tangle-network/agent-runtime'\nimport { TURN_SPAN_KINDS, type TurnSpanAttrs, type TurnSpanKind, type TurnTraceSink, type TurnTraceSnapshot } from './recorder'\n\n/** OpenInference span kind per turn span: model calls read as LLM, tool calls as TOOL. */\nconst SPAN_KIND: Partial<Record<string, 'LLM' | 'TOOL'>> = { 'model.call': 'LLM', 'tool.call': 'TOOL' }\n\nconst msToNs = (ms: number) => (BigInt(Math.floor(ms)) * 1_000_000n).toString()\n\n/**\n * The root span (`agent_app.turn`) carries the host's labels and the turn's\n * numbers: wall time, the share inside child spans, and the time credited to\n * each kind of span. Children follow under it.\n */\nexport function buildTurnTraceOtelSpans(snapshot: TurnTraceSnapshot, labels: TurnSpanAttrs = {}): OtelSpan[] {\n const traceId = padTraceId(snapshot.traceId)\n const root = padSpanId(snapshot.rootSpanId)\n const { summary } = snapshot\n const rootAttrs: Record<string, string | number | boolean> = {\n ...labels,\n 'openinference.span.kind': 'CHAIN',\n 'tangle.execution_id': snapshot.executionId,\n 'tangle.trace_id': snapshot.traceId,\n 'tangle.started_at_ms': snapshot.startedAt,\n 'tangle.completed_at_ms': snapshot.endedAt,\n 'tangle.duration_ms': summary.wallMs,\n 'tangle.turn.wall_ms': summary.wallMs,\n 'tangle.turn.covered_ms': summary.coveredMs,\n 'tangle.turn.coverage_pct': summary.coveragePct,\n 'tangle.turn.uncovered_ms': summary.uncoveredMs,\n 'tangle.turn.largest_gap_ms': summary.largestGapMs,\n 'tangle.turn.spans': summary.spans,\n 'tangle.turn.model_calls': summary.modelCalls,\n 'tangle.turn.tool_calls': summary.toolCalls,\n ...(summary.dropped ? { 'tangle.turn.spans_dropped': summary.dropped } : {}),\n ...Object.fromEntries(TURN_SPAN_KINDS.map((kind: TurnSpanKind) => [`tangle.turn.${kind}_ms`, summary.byKind[kind]])),\n }\n const spans: OtelSpan[] = [{\n traceId,\n spanId: root,\n name: 'agent_app.turn',\n kind: 1,\n startTimeUnixNano: msToNs(snapshot.startedAt),\n endTimeUnixNano: msToNs(snapshot.endedAt),\n attributes: toOtelAttributes(rootAttrs),\n status: snapshot.status === 'ok' ? { code: 1 } : { code: 2, ...(snapshot.error ? { message: snapshot.error } : {}) },\n }]\n for (const span of snapshot.spans) {\n const attrs: Record<string, string | number | boolean> = {\n ...span.attrs,\n 'openinference.span.kind': SPAN_KIND[span.name] ?? 'CHAIN',\n 'tangle.turn.span_kind': span.kind,\n 'agent.parent.confidence': 'explicit',\n ...(span.name === 'tool.call' ? { 'gen_ai.operation.name': 'execute_tool' } : {}),\n ...(span.unfinished ? { 'tangle.turn.unfinished': true } : {}),\n ...(span.error ? { 'error.message': span.error } : {}),\n }\n spans.push({\n traceId,\n spanId: padSpanId(span.spanId),\n parentSpanId: padSpanId(span.parentSpanId),\n name: span.name,\n kind: 1,\n startTimeUnixNano: msToNs(span.startMs),\n endTimeUnixNano: msToNs(span.endMs),\n attributes: toOtelAttributes(attrs),\n status: span.error ? { code: 2, message: span.error } : { code: 1 },\n })\n }\n return spans\n}\n\nexport interface IntelligenceTurnTraceSinkOptions {\n /** The Intelligence project the spans belong to, e.g. `legal-agent`. */\n project: string\n /** The tenant key the ingest requires. Without one the sink does nothing. */\n apiKey: string | undefined\n /** Default `https://intelligence.tangle.tools`; spans go to `<base>/v1/otlp`. */\n baseUrl?: string\n /** Called with a short reason when a delivery fails. Never receives span content. */\n onError?: (reason: string) => void\n}\n\nconst DEFAULT_INTELLIGENCE_BASE_URL = 'https://intelligence.tangle.tools'\n\n/**\n * The sink a chat route or a Worker turn hands each finished trace to. The\n * exporter is built on first use, inside a request, because a Worker refuses a\n * timer at module scope. Each trace is flushed before the sink returns, so a\n * host that runs it under `waitUntil` keeps the isolate alive until delivery.\n */\nexport function createIntelligenceTurnTraceSink(options: IntelligenceTurnTraceSinkOptions): TurnTraceSink {\n let exporter: OtelExporter | undefined\n const exporterFor = (apiKey: string): OtelExporter | undefined => {\n exporter ??= createOtelExporter({\n endpoint: `${(options.baseUrl ?? DEFAULT_INTELLIGENCE_BASE_URL).replace(/\\/+$/, '')}/v1/otlp`,\n headers: { authorization: `Bearer ${apiKey}` },\n serviceName: options.project,\n resourceAttributes: { 'tangle.project': options.project },\n })\n return exporter\n }\n return async (snapshot, labels) => {\n if (!options.apiKey) return\n try {\n const sink = exporterFor(options.apiKey)\n if (!sink) return\n for (const span of buildTurnTraceOtelSpans(snapshot, { project: options.project, ...labels })) sink.exportSpan(span)\n await sink.flush()\n } catch (error) {\n try { options.onError?.(error instanceof Error ? error.message.slice(0, 160) : 'turn trace export failed') } catch { /* telemetry only */ }\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A finished turn trace as OTLP spans, sent to Tangle Intelligence.
3
+ *
4
+ * The span builders and the exporter are agent-runtime's (`createOtelExporter`,
5
+ * `toOtelAttributes`, `padTraceId`); this file only maps the turn's tree onto
6
+ * them, using the GenAI semantic-convention keys the runtime's own builders
7
+ * write (`gen_ai.operation.name`, `gen_ai.request.model`, `gen_ai.usage.*`,
8
+ * `gen_ai.tool.name`) and OpenInference's span kind.
9
+ *
10
+ * Server only: it imports the runtime root, which a browser bundle must not.
11
+ */
12
+ import { type OtelSpan } from '@tangle-network/agent-runtime';
13
+ import { type TurnSpanAttrs, type TurnTraceSink, type TurnTraceSnapshot } from './recorder';
14
+ /**
15
+ * The root span (`agent_app.turn`) carries the host's labels and the turn's
16
+ * numbers: wall time, the share inside child spans, and the time credited to
17
+ * each kind of span. Children follow under it.
18
+ */
19
+ export declare function buildTurnTraceOtelSpans(snapshot: TurnTraceSnapshot, labels?: TurnSpanAttrs): OtelSpan[];
20
+ export interface IntelligenceTurnTraceSinkOptions {
21
+ /** The Intelligence project the spans belong to, e.g. `legal-agent`. */
22
+ project: string;
23
+ /** The tenant key the ingest requires. Without one the sink does nothing. */
24
+ apiKey: string | undefined;
25
+ /** Default `https://intelligence.tangle.tools`; spans go to `<base>/v1/otlp`. */
26
+ baseUrl?: string;
27
+ /** Called with a short reason when a delivery fails. Never receives span content. */
28
+ onError?: (reason: string) => void;
29
+ }
30
+ /**
31
+ * The sink a chat route or a Worker turn hands each finished trace to. The
32
+ * exporter is built on first use, inside a request, because a Worker refuses a
33
+ * timer at module scope. Each trace is flushed before the sink returns, so a
34
+ * host that runs it under `waitUntil` keeps the isolate alive until delivery.
35
+ */
36
+ export declare function createIntelligenceTurnTraceSink(options: IntelligenceTurnTraceSinkOptions): TurnTraceSink;