@tangle-network/agent-runtime 0.21.1 → 0.23.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.
Files changed (35) hide show
  1. package/README.md +116 -1
  2. package/dist/agent.d.ts +1 -1
  3. package/dist/chunk-7HN72MF3.js +200 -0
  4. package/dist/chunk-7HN72MF3.js.map +1 -0
  5. package/dist/{chunk-Z5LKAYAS.js → chunk-CBQVID7G.js} +2 -2
  6. package/dist/chunk-IQHYOJU3.js +427 -0
  7. package/dist/chunk-IQHYOJU3.js.map +1 -0
  8. package/dist/{chunk-EDVCVFQB.js → chunk-TZ53F7M7.js} +4 -3
  9. package/dist/chunk-TZ53F7M7.js.map +1 -0
  10. package/dist/{chunk-QDNJLAEU.js → chunk-UNQM6XQO.js} +34 -433
  11. package/dist/chunk-UNQM6XQO.js.map +1 -0
  12. package/dist/{chunk-XLWPTPRP.js → chunk-URDSRUPQ.js} +2 -2
  13. package/dist/{chunk-RZAOYKCO.js → chunk-XZYF3YJN.js} +9 -1
  14. package/dist/{chunk-RZAOYKCO.js.map → chunk-XZYF3YJN.js.map} +1 -1
  15. package/dist/index.d.ts +77 -4
  16. package/dist/index.js +209 -41
  17. package/dist/index.js.map +1 -1
  18. package/dist/loops.d.ts +4 -4
  19. package/dist/loops.js +3 -3
  20. package/dist/mcp/bin.js +6 -5
  21. package/dist/mcp/bin.js.map +1 -1
  22. package/dist/mcp/index.d.ts +54 -4
  23. package/dist/mcp/index.js +60 -11
  24. package/dist/mcp/index.js.map +1 -1
  25. package/dist/otel-export-B33Cy_60.d.ts +114 -0
  26. package/dist/profiles.d.ts +3 -3
  27. package/dist/profiles.js +3 -3
  28. package/dist/{runtime-run-B2j-hvBj.d.ts → runtime-run-D5ItCKl_.d.ts} +1 -1
  29. package/dist/{types-DvJIha6w.d.ts → types-BFgFD_sl.d.ts} +87 -1
  30. package/dist/{types-Cu-SkGa0.d.ts → types-DmkRGTBn.d.ts} +18 -1
  31. package/package.json +1 -1
  32. package/dist/chunk-EDVCVFQB.js.map +0 -1
  33. package/dist/chunk-QDNJLAEU.js.map +0 -1
  34. /package/dist/{chunk-Z5LKAYAS.js.map → chunk-CBQVID7G.js.map} +0 -0
  35. /package/dist/{chunk-XLWPTPRP.js.map → chunk-URDSRUPQ.js.map} +0 -0
package/README.md CHANGED
@@ -213,6 +213,33 @@ Or run the ready-made bin:
213
213
  TANGLE_API_KEY=sk_sandbox_... agent-runtime-mcp
214
214
  ```
215
215
 
216
+ ### Surfacing the tools through `createOpenAICompatibleBackend`
217
+
218
+ Sandbox callers discover MCP tools through the runtime mount. Callers that
219
+ route through the OpenAI-compat backend (tcloud, OpenRouter, cli-bridge,
220
+ OpenAI direct) must hand the model an explicit `tools[]` array — the
221
+ backend does not auto-discover. `mcpToolsForRuntimeMcp()` returns the
222
+ canonical projection so the model can call any of the 5 delegation tools
223
+ through the OpenAI-compat path:
224
+
225
+ ```ts
226
+ import {
227
+ createOpenAICompatibleBackend,
228
+ mcpToolsForRuntimeMcp,
229
+ } from '@tangle-network/agent-runtime'
230
+
231
+ const backend = createOpenAICompatibleBackend({
232
+ apiKey,
233
+ baseUrl,
234
+ model,
235
+ tools: mcpToolsForRuntimeMcp(),
236
+ })
237
+ ```
238
+
239
+ Use `mcpToolsForRuntimeMcpSubset(['delegate_research', 'delegation_status'])`
240
+ when you want a curated subset (e.g. read-only research without the coder
241
+ queue).
242
+
216
243
  The bin auto-wires the coder delegate and, when
217
244
  `@tangle-network/agent-knowledge` is installed as a peer, the researcher
218
245
  delegate. Environment knobs:
@@ -362,6 +389,94 @@ const researcherDelegate: ResearcherDelegate = async (args, ctx) => {
362
389
  createMcpServer({ researcherDelegate })
363
390
  ```
364
391
 
392
+ ## OpenAI-compat backend — tools + fail-loud errors
393
+
394
+ `createOpenAICompatibleBackend` forwards an OpenAI Chat Completions
395
+ `tools[]` array on every request when configured. Streamed tool calls
396
+ (both OpenAI delta shape and the Anthropic `tool_use` shape proxied by
397
+ the router) are assembled across SSE chunks and emitted as a single
398
+ `tool_call` RuntimeStreamEvent per call. The backend does NOT execute
399
+ tools — surfacing the call is the contract; dispatch is the caller's
400
+ problem.
401
+
402
+ ```ts
403
+ import {
404
+ createOpenAICompatibleBackend,
405
+ runAgentTaskStream,
406
+ type OpenAIChatTool,
407
+ } from '@tangle-network/agent-runtime'
408
+
409
+ const delegateResearch: OpenAIChatTool = {
410
+ type: 'function',
411
+ function: {
412
+ name: 'delegate_research',
413
+ description: 'Spin up a researcher loop and return a taskId.',
414
+ parameters: {
415
+ type: 'object',
416
+ properties: { question: { type: 'string' } },
417
+ required: ['question'],
418
+ },
419
+ },
420
+ }
421
+
422
+ const backend = createOpenAICompatibleBackend({
423
+ apiKey: process.env.TANGLE_API_KEY!,
424
+ baseUrl: 'https://router.tangle.tools/v1',
425
+ model: 'claude-sonnet-4-6',
426
+ tools: [delegateResearch /* + delegate_code, delegate_feedback, etc. */],
427
+ toolChoice: 'auto', // or 'none' | 'required' | { type: 'function', function: { name } }
428
+ })
429
+
430
+ for await (const event of runAgentTaskStream({ task, backend, input })) {
431
+ if (event.type === 'tool_call') {
432
+ // Dispatch through your MCP / sandbox runtime. `args` is JSON-parsed
433
+ // when the model produced a valid object, raw string otherwise.
434
+ const result = await dispatch(event.toolName, event.args)
435
+ // Feed `result` back on a follow-up turn via `input.messages`.
436
+ }
437
+ }
438
+ ```
439
+
440
+ Callers integrating with `agent-runtime/mcp` typically project the MCP
441
+ server's `tools/list` response into this shape once at config time and
442
+ pass the array as `tools`. The runtime intentionally does NOT depend on
443
+ `@modelcontextprotocol/sdk` — keeping the backend transport thin lets
444
+ domain repos own MCP plumbing.
445
+
446
+ ### Transport errors fail loud
447
+
448
+ Non-success HTTP responses (4xx/5xx after retry exhaustion) and
449
+ connection failures throw `BackendTransportError` from inside the
450
+ `stream()` generator. `runAgentTaskStream` catches the throw and emits:
451
+
452
+ - `backend_error` event with `error: { kind: 'transport', message, status, body }`
453
+ - terminal `final` event with `status: 'failed'` carrying the same `error` detail
454
+
455
+ Consumers building a `RunRecord` MUST map `final.error` onto
456
+ `RunRecord.error`. Treating an empty `finalText` as "agent produced
457
+ nothing" hides credit exhaustion (HTTP 402), auth failure (401),
458
+ model-not-found (404), and upstream outages (5xx).
459
+
460
+ ```ts
461
+ for await (const event of runAgentTaskStream({ task, backend, input })) {
462
+ run.observe(event)
463
+ if (event.type === 'final') {
464
+ run.complete({
465
+ status: event.status === 'completed' ? 'completed' : 'failed',
466
+ resultSummary: event.text ?? '',
467
+ error: event.error
468
+ ? `${event.error.kind} ${event.error.status ?? ''}: ${event.error.message}`
469
+ : undefined,
470
+ })
471
+ }
472
+ }
473
+ ```
474
+
475
+ The body is captured truncated to 2 KiB. By default the sanitized
476
+ telemetry envelope surfaces `error.kind` + `error.status` but redacts
477
+ `error.body` (it can echo user-visible text from a provider's error
478
+ page). Opt in with `RuntimeTelemetryOptions.includeControlPayloads`.
479
+
365
480
  ## Error taxonomy
366
481
 
367
482
  | Error | When |
@@ -369,7 +484,7 @@ createMcpServer({ researcherDelegate })
369
484
  | `ValidationError` | Caller passed invalid arguments |
370
485
  | `ConfigError` | Required env / config missing |
371
486
  | `NotFoundError` | A named resource does not exist |
372
- | `BackendTransportError` | Backend HTTP / IPC call returned non-success |
487
+ | `BackendTransportError` | Backend HTTP / IPC call returned non-success — carries `status` + truncated `body` |
373
488
  | `SessionMismatchError` | Resume requested against a different backend |
374
489
  | `RuntimeRunStateError` | `RuntimeRunHandle` lifecycle methods called out of order |
375
490
 
package/dist/agent.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _tangle_network_agent_eval from '@tangle-network/agent-eval';
2
2
  import { FindingSubject, TraceAnalystKindSpec, AnalystFinding } from '@tangle-network/agent-eval';
3
- import { R as RuntimeStreamEvent } from './types-DvJIha6w.js';
3
+ import { R as RuntimeStreamEvent } from './types-BFgFD_sl.js';
4
4
  import { I as ImprovementAdapter, K as KnowledgeAdapter, a as RunAnalystLoopResult } from './types-D_MXrmJP.js';
5
5
 
6
6
  /**
@@ -0,0 +1,200 @@
1
+ import {
2
+ DELEGATE_CODE_DESCRIPTION,
3
+ DELEGATE_CODE_INPUT_SCHEMA,
4
+ DELEGATE_CODE_TOOL_NAME,
5
+ DELEGATE_FEEDBACK_DESCRIPTION,
6
+ DELEGATE_FEEDBACK_INPUT_SCHEMA,
7
+ DELEGATE_FEEDBACK_TOOL_NAME,
8
+ DELEGATE_RESEARCH_DESCRIPTION,
9
+ DELEGATE_RESEARCH_INPUT_SCHEMA,
10
+ DELEGATE_RESEARCH_TOOL_NAME,
11
+ DELEGATION_HISTORY_DESCRIPTION,
12
+ DELEGATION_HISTORY_INPUT_SCHEMA,
13
+ DELEGATION_HISTORY_TOOL_NAME,
14
+ DELEGATION_STATUS_DESCRIPTION,
15
+ DELEGATION_STATUS_INPUT_SCHEMA,
16
+ DELEGATION_STATUS_TOOL_NAME
17
+ } from "./chunk-UNQM6XQO.js";
18
+
19
+ // src/mcp/openai-tools.ts
20
+ function buildTool(name, description, parameters) {
21
+ return {
22
+ type: "function",
23
+ function: { name, description, parameters: { ...parameters } }
24
+ };
25
+ }
26
+ function mcpToolsForRuntimeMcp() {
27
+ return [
28
+ buildTool(
29
+ DELEGATE_CODE_TOOL_NAME,
30
+ DELEGATE_CODE_DESCRIPTION,
31
+ DELEGATE_CODE_INPUT_SCHEMA
32
+ ),
33
+ buildTool(
34
+ DELEGATE_RESEARCH_TOOL_NAME,
35
+ DELEGATE_RESEARCH_DESCRIPTION,
36
+ DELEGATE_RESEARCH_INPUT_SCHEMA
37
+ ),
38
+ buildTool(
39
+ DELEGATE_FEEDBACK_TOOL_NAME,
40
+ DELEGATE_FEEDBACK_DESCRIPTION,
41
+ DELEGATE_FEEDBACK_INPUT_SCHEMA
42
+ ),
43
+ buildTool(
44
+ DELEGATION_STATUS_TOOL_NAME,
45
+ DELEGATION_STATUS_DESCRIPTION,
46
+ DELEGATION_STATUS_INPUT_SCHEMA
47
+ ),
48
+ buildTool(
49
+ DELEGATION_HISTORY_TOOL_NAME,
50
+ DELEGATION_HISTORY_DESCRIPTION,
51
+ DELEGATION_HISTORY_INPUT_SCHEMA
52
+ )
53
+ ];
54
+ }
55
+ function mcpToolsForRuntimeMcpSubset(names) {
56
+ const allowed = new Set(names);
57
+ return mcpToolsForRuntimeMcp().filter((tool) => allowed.has(tool.function.name));
58
+ }
59
+
60
+ // src/otel-export.ts
61
+ var SCOPE = { name: "@tangle-network/agent-runtime", version: "0.23.0" };
62
+ function createOtelExporter(config) {
63
+ const resolvedEndpoint = config?.endpoint ?? (typeof process !== "undefined" ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : void 0);
64
+ if (!resolvedEndpoint) return void 0;
65
+ const endpoint = resolvedEndpoint;
66
+ const headers = config?.headers ?? parseHeadersFromEnv();
67
+ const batchSize = config?.batchSize ?? 64;
68
+ const flushIntervalMs = config?.flushIntervalMs ?? 5e3;
69
+ const serviceName = config?.serviceName ?? "agent-runtime";
70
+ const resourceAttrs = config?.resourceAttributes ?? {};
71
+ const pending = [];
72
+ let timer;
73
+ let stopped = false;
74
+ const exporter = {
75
+ exportSpan(span) {
76
+ if (stopped) return;
77
+ pending.push(span);
78
+ if (pending.length >= batchSize) {
79
+ void doFlush();
80
+ }
81
+ },
82
+ async flush() {
83
+ await doFlush();
84
+ },
85
+ async shutdown() {
86
+ stopped = true;
87
+ if (timer !== void 0) {
88
+ clearInterval(timer);
89
+ timer = void 0;
90
+ }
91
+ await doFlush();
92
+ }
93
+ };
94
+ timer = setInterval(() => {
95
+ if (pending.length > 0) void doFlush();
96
+ }, flushIntervalMs);
97
+ if (typeof timer === "object" && "unref" in timer) {
98
+ ;
99
+ timer.unref();
100
+ }
101
+ async function doFlush() {
102
+ if (pending.length === 0) return;
103
+ const batch = pending.splice(0);
104
+ const body = {
105
+ resourceSpans: [
106
+ {
107
+ resource: {
108
+ attributes: toAttributes({
109
+ "service.name": serviceName,
110
+ ...resourceAttrs
111
+ })
112
+ },
113
+ scopeSpans: [{ scope: SCOPE, spans: batch }]
114
+ }
115
+ ]
116
+ };
117
+ const url = endpoint.replace(/\/+$/, "") + "/v1/traces";
118
+ try {
119
+ await fetch(url, {
120
+ method: "POST",
121
+ headers: { "content-type": "application/json", ...headers },
122
+ body: JSON.stringify(body)
123
+ });
124
+ } catch {
125
+ }
126
+ }
127
+ return exporter;
128
+ }
129
+ function loopEventToOtelSpan(event, traceId, parentSpanId) {
130
+ const spanId = generateSpanId();
131
+ const attrs = {
132
+ "loop.event_kind": event.kind,
133
+ "loop.run_id": event.runId
134
+ };
135
+ for (const [k, v] of Object.entries(event.payload)) {
136
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
137
+ attrs[`loop.${k}`] = v;
138
+ }
139
+ }
140
+ const ts = msToNs(event.timestamp);
141
+ return {
142
+ traceId: padTraceId(traceId),
143
+ spanId,
144
+ parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
145
+ name: event.kind,
146
+ kind: 1,
147
+ startTimeUnixNano: ts,
148
+ endTimeUnixNano: ts,
149
+ attributes: toAttributes(attrs),
150
+ status: { code: 1 }
151
+ };
152
+ }
153
+ function parseHeadersFromEnv() {
154
+ if (typeof process === "undefined") return {};
155
+ const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS;
156
+ if (!raw) return {};
157
+ const out = {};
158
+ for (const pair of raw.split(",")) {
159
+ const eq = pair.indexOf("=");
160
+ if (eq < 0) continue;
161
+ const key = pair.slice(0, eq).trim();
162
+ const value = pair.slice(eq + 1).trim();
163
+ if (key) out[key] = value;
164
+ }
165
+ return out;
166
+ }
167
+ function toAttributes(record) {
168
+ return Object.entries(record).map(([key, value]) => ({
169
+ key,
170
+ value: typeof value === "number" ? Number.isInteger(value) ? { intValue: value.toString() } : { doubleValue: value } : typeof value === "boolean" ? { boolValue: value } : { stringValue: value }
171
+ }));
172
+ }
173
+ function msToNs(ms) {
174
+ return (BigInt(Math.floor(ms)) * 1000000n).toString();
175
+ }
176
+ function padSpanId(id) {
177
+ const cleaned = id.replace(/-/g, "");
178
+ return cleaned.slice(0, 16).padEnd(16, "0");
179
+ }
180
+ function padTraceId(id) {
181
+ const cleaned = id.replace(/-/g, "");
182
+ return cleaned.slice(0, 32).padEnd(32, "0");
183
+ }
184
+ function generateSpanId() {
185
+ const bytes = new Uint8Array(8);
186
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
187
+ globalThis.crypto.getRandomValues(bytes);
188
+ } else {
189
+ for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256);
190
+ }
191
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
192
+ }
193
+
194
+ export {
195
+ mcpToolsForRuntimeMcp,
196
+ mcpToolsForRuntimeMcpSubset,
197
+ createOtelExporter,
198
+ loopEventToOtelSpan
199
+ };
200
+ //# sourceMappingURL=chunk-7HN72MF3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/openai-tools.ts","../src/otel-export.ts"],"sourcesContent":["/**\n * @experimental\n *\n * OpenAI Chat Completions `tools[]` projection of the 5 agent-runtime MCP\n * delegation tools.\n *\n * Use when configuring `createOpenAICompatibleBackend({ tools: ... })` so the\n * model can call `delegate_code`, `delegate_research`, `delegate_feedback`,\n * `delegation_status`, and `delegation_history` through the OpenAI-compat\n * transport (tcloud, OpenRouter, OpenAI direct, cli-bridge). The runtime\n * surfaces tool calls as `tool_call` stream events — execution is the\n * caller's responsibility (typically the parent sandbox runtime's MCP\n * mount).\n *\n * Sandbox-SDK callers do NOT need this helper: the sandbox runtime mounts\n * MCP servers natively and the in-sandbox harness discovers tools via the\n * runtime, not via an OpenAI tools array.\n *\n * Tool name + description + JSON-schema are pulled from the canonical\n * `DELEGATE_*` constants exported by `./tools/*` so the projection cannot\n * drift from the server's own validators.\n */\n\nimport type { OpenAIChatTool } from '../types'\nimport {\n DELEGATE_CODE_DESCRIPTION,\n DELEGATE_CODE_INPUT_SCHEMA,\n DELEGATE_CODE_TOOL_NAME,\n} from './tools/delegate-code'\nimport {\n DELEGATE_FEEDBACK_DESCRIPTION,\n DELEGATE_FEEDBACK_INPUT_SCHEMA,\n DELEGATE_FEEDBACK_TOOL_NAME,\n} from './tools/delegate-feedback'\nimport {\n DELEGATE_RESEARCH_DESCRIPTION,\n DELEGATE_RESEARCH_INPUT_SCHEMA,\n DELEGATE_RESEARCH_TOOL_NAME,\n} from './tools/delegate-research'\nimport {\n DELEGATION_HISTORY_DESCRIPTION,\n DELEGATION_HISTORY_INPUT_SCHEMA,\n DELEGATION_HISTORY_TOOL_NAME,\n} from './tools/delegation-history'\nimport {\n DELEGATION_STATUS_DESCRIPTION,\n DELEGATION_STATUS_INPUT_SCHEMA,\n DELEGATION_STATUS_TOOL_NAME,\n} from './tools/delegation-status'\n\nfunction buildTool(\n name: string,\n description: string,\n parameters: Readonly<Record<string, unknown>>,\n): OpenAIChatTool {\n // `parameters` arrives as a deeply-readonly `as const` literal. The\n // OpenAI-compat backend JSON-serializes the body so a shallow copy\n // into a plain object is sufficient — and shields callers that mutate\n // the returned descriptor from corrupting the source constant.\n return {\n type: 'function',\n function: { name, description, parameters: { ...parameters } },\n }\n}\n\n/**\n * @experimental\n *\n * Returns the 5 delegation tools projected into OpenAI Chat Completions\n * `tools[]` shape. The order is stable: `delegate_code`,\n * `delegate_research`, `delegate_feedback`, `delegation_status`,\n * `delegation_history`.\n */\nexport function mcpToolsForRuntimeMcp(): OpenAIChatTool[] {\n return [\n buildTool(\n DELEGATE_CODE_TOOL_NAME,\n DELEGATE_CODE_DESCRIPTION,\n DELEGATE_CODE_INPUT_SCHEMA as Readonly<Record<string, unknown>>,\n ),\n buildTool(\n DELEGATE_RESEARCH_TOOL_NAME,\n DELEGATE_RESEARCH_DESCRIPTION,\n DELEGATE_RESEARCH_INPUT_SCHEMA as Readonly<Record<string, unknown>>,\n ),\n buildTool(\n DELEGATE_FEEDBACK_TOOL_NAME,\n DELEGATE_FEEDBACK_DESCRIPTION,\n DELEGATE_FEEDBACK_INPUT_SCHEMA as Readonly<Record<string, unknown>>,\n ),\n buildTool(\n DELEGATION_STATUS_TOOL_NAME,\n DELEGATION_STATUS_DESCRIPTION,\n DELEGATION_STATUS_INPUT_SCHEMA as Readonly<Record<string, unknown>>,\n ),\n buildTool(\n DELEGATION_HISTORY_TOOL_NAME,\n DELEGATION_HISTORY_DESCRIPTION,\n DELEGATION_HISTORY_INPUT_SCHEMA as Readonly<Record<string, unknown>>,\n ),\n ]\n}\n\n/**\n * @experimental\n *\n * Subset filter — return only the projected tools whose `function.name`\n * appears in `names`. Useful for curated mounts (e.g. only the queue-bound\n * delegation tools, omitting `delegate_feedback`). Unknown names are\n * silently ignored; pass an empty array to get an empty result.\n */\nexport function mcpToolsForRuntimeMcpSubset(names: ReadonlyArray<string>): OpenAIChatTool[] {\n const allowed = new Set(names)\n return mcpToolsForRuntimeMcp().filter((tool) => allowed.has(tool.function.name))\n}\n","/**\n * OTEL span exporter — streams LoopTraceEvents to an OTLP/HTTP collector.\n *\n * Reads OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_HEADERS from env\n * when no explicit config is given. Keeps the runtime dep-free from\n * @opentelemetry/sdk-trace-base — minimal OTLP/JSON serializer.\n *\n * The exporter accepts both raw OtelSpan objects and LoopTraceEvents\n * (which get converted to OTLP spans automatically).\n */\n\nexport interface OtelExportConfig {\n /** OTLP endpoint. Reads OTEL_EXPORTER_OTLP_ENDPOINT env by default. */\n endpoint?: string\n /** OTLP headers. Reads OTEL_EXPORTER_OTLP_HEADERS env by default. */\n headers?: Record<string, string>\n /** Batch size before flush. Default 64. */\n batchSize?: number\n /** Flush interval ms. Default 5000. */\n flushIntervalMs?: number\n /** Resource attributes stamped on every export. */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Service name. Default 'agent-runtime'. */\n serviceName?: string\n}\n\nexport interface OtelExporter {\n /** Export a span. */\n exportSpan(span: OtelSpan): void\n /** Force flush pending spans. */\n flush(): Promise<void>\n /** Shutdown cleanly. */\n shutdown(): Promise<void>\n}\n\nexport interface OtelSpan {\n traceId: string\n spanId: string\n parentSpanId?: string\n name: string\n kind?: number\n startTimeUnixNano: string\n endTimeUnixNano: string\n attributes?: OtelAttribute[]\n status?: { code: number; message?: string }\n}\n\nexport interface OtelAttribute {\n key: string\n value: { stringValue?: string; intValue?: string; doubleValue?: number; boolValue?: boolean }\n}\n\ninterface OtlpResourceSpans {\n resource: { attributes: OtelAttribute[] }\n scopeSpans: Array<{ scope: { name: string; version: string }; spans: OtelSpan[] }>\n}\n\ninterface OtlpExport {\n resourceSpans: OtlpResourceSpans[]\n}\n\nconst SCOPE = { name: '@tangle-network/agent-runtime', version: '0.23.0' }\n\n/**\n * Create an OTEL exporter. Returns undefined when no endpoint is configured.\n */\nexport function createOtelExporter(config?: OtelExportConfig): OtelExporter | undefined {\n const resolvedEndpoint =\n config?.endpoint ?? (typeof process !== 'undefined' ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : undefined)\n if (!resolvedEndpoint) return undefined\n const endpoint: string = resolvedEndpoint\n\n const headers = config?.headers ?? parseHeadersFromEnv()\n const batchSize = config?.batchSize ?? 64\n const flushIntervalMs = config?.flushIntervalMs ?? 5000\n const serviceName = config?.serviceName ?? 'agent-runtime'\n const resourceAttrs = config?.resourceAttributes ?? {}\n\n const pending: OtelSpan[] = []\n let timer: ReturnType<typeof setInterval> | undefined\n let stopped = false\n\n const exporter: OtelExporter = {\n exportSpan(span: OtelSpan): void {\n if (stopped) return\n pending.push(span)\n if (pending.length >= batchSize) {\n void doFlush()\n }\n },\n\n async flush(): Promise<void> {\n await doFlush()\n },\n\n async shutdown(): Promise<void> {\n stopped = true\n if (timer !== undefined) {\n clearInterval(timer)\n timer = undefined\n }\n await doFlush()\n },\n }\n\n timer = setInterval(() => {\n if (pending.length > 0) void doFlush()\n }, flushIntervalMs)\n if (typeof timer === 'object' && 'unref' in timer) {\n ;(timer as NodeJS.Timeout).unref()\n }\n\n async function doFlush(): Promise<void> {\n if (pending.length === 0) return\n const batch = pending.splice(0)\n const body: OtlpExport = {\n resourceSpans: [\n {\n resource: {\n attributes: toAttributes({\n 'service.name': serviceName,\n ...resourceAttrs,\n }),\n },\n scopeSpans: [{ scope: SCOPE, spans: batch }],\n },\n ],\n }\n const url = endpoint.replace(/\\/+$/, '') + '/v1/traces'\n try {\n await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(body),\n })\n } catch {\n // Best-effort — telemetry export must not crash the runtime.\n }\n }\n\n return exporter\n}\n\n/**\n * Convert a LoopTraceEvent into an OtelSpan for export.\n */\nexport function loopEventToOtelSpan(\n event: {\n kind: string\n runId: string\n timestamp: number\n payload: object\n },\n traceId: string,\n parentSpanId?: string,\n): OtelSpan {\n const spanId = generateSpanId()\n const attrs: Record<string, string | number | boolean> = {\n 'loop.event_kind': event.kind,\n 'loop.run_id': event.runId,\n }\n for (const [k, v] of Object.entries(event.payload)) {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n attrs[`loop.${k}`] = v\n }\n }\n const ts = msToNs(event.timestamp)\n return {\n traceId: padTraceId(traceId),\n spanId,\n parentSpanId: parentSpanId ? padSpanId(parentSpanId) : undefined,\n name: event.kind,\n kind: 1,\n startTimeUnixNano: ts,\n endTimeUnixNano: ts,\n attributes: toAttributes(attrs),\n status: { code: 1 },\n }\n}\n\nfunction parseHeadersFromEnv(): Record<string, string> {\n if (typeof process === 'undefined') return {}\n const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS\n if (!raw) return {}\n const out: Record<string, string> = {}\n for (const pair of raw.split(',')) {\n const eq = pair.indexOf('=')\n if (eq < 0) continue\n const key = pair.slice(0, eq).trim()\n const value = pair.slice(eq + 1).trim()\n if (key) out[key] = value\n }\n return out\n}\n\nfunction toAttributes(record: Record<string, string | number | boolean>): OtelAttribute[] {\n return Object.entries(record).map(([key, value]) => ({\n key,\n value:\n typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value.toString() }\n : { doubleValue: value }\n : typeof value === 'boolean'\n ? { boolValue: value }\n : { stringValue: value },\n }))\n}\n\nfunction msToNs(ms: number): string {\n return (BigInt(Math.floor(ms)) * 1_000_000n).toString()\n}\n\nfunction padSpanId(id: string): string {\n const cleaned = id.replace(/-/g, '')\n return cleaned.slice(0, 16).padEnd(16, '0')\n}\n\nfunction padTraceId(id: string): string {\n const cleaned = id.replace(/-/g, '')\n return cleaned.slice(0, 32).padEnd(32, '0')\n}\n\nfunction generateSpanId(): string {\n const bytes = new Uint8Array(8)\n if (typeof globalThis.crypto?.getRandomValues === 'function') {\n globalThis.crypto.getRandomValues(bytes)\n } else {\n for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256)\n }\n return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createFanoutVoteDriver
3
- } from "./chunk-XLWPTPRP.js";
3
+ } from "./chunk-URDSRUPQ.js";
4
4
 
5
5
  // src/profiles/coder.ts
6
6
  var DEFAULT_MAX_DIFF_LINES = 400;
@@ -245,4 +245,4 @@ export {
245
245
  multiHarnessCoderFanout,
246
246
  createCoderValidator
247
247
  };
248
- //# sourceMappingURL=chunk-Z5LKAYAS.js.map
248
+ //# sourceMappingURL=chunk-CBQVID7G.js.map