@tangle-network/agent-eval 0.48.0 → 0.49.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.
- package/README.md +7 -0
- package/dist/adapters/{traceai.d.ts → otel.d.ts} +21 -27
- package/dist/adapters/{traceai.js → otel.js} +9 -5
- package/dist/adapters/otel.js.map +1 -0
- package/dist/openapi.json +1 -1
- package/docs/adapters-observability.md +3 -3
- package/package.json +5 -5
- package/dist/adapters/traceai.js.map +0 -1
- /package/docs/design/{substrate-gaps-2026-05-27.md → substrate-gaps.md} +0 -0
package/README.md
CHANGED
|
@@ -215,6 +215,13 @@ const next = await analyzeOptimizationResult(campaign, { researcher })
|
|
|
215
215
|
|
|
216
216
|
| Subpath | Use for |
|
|
217
217
|
| --- | --- |
|
|
218
|
+
| `@tangle-network/agent-eval/contract` | **LAND-tier surface** — `selfImprove`, `runCampaign`, `runImprovementLoop`, `runEval`, `Dispatch`, `Mutator`, `Gate`, `defaultProductionGate`, `gepaDriver`, `diffRuns`, storage backends. New code starts here. |
|
|
219
|
+
| `@tangle-network/agent-eval/hosted` | **EXPAND-tier surface** — `createHostedClient`, wire-format types, `HOSTED_WIRE_VERSION`. Ships eval-run events + trace spans to any orchestrator that speaks the spec. |
|
|
220
|
+
| `@tangle-network/agent-eval/adapters/otel` | OTel→hosted bridge — `createOtelBridge` forwards OTel-shape spans (TraceAI, OpenLLMetry, OTel SDK) into the hosted-tier ingest. |
|
|
221
|
+
| `@tangle-network/agent-eval/adapters/langchain` | LangChain executor adapter — wrap a LangChain runnable as a `Dispatch`. |
|
|
222
|
+
| `@tangle-network/agent-eval/adapters/http` | Distributed driver — `httpDispatch` + `runDispatchServer` for cross-machine campaigns. |
|
|
223
|
+
| `@tangle-network/agent-eval/campaign` | Lower-level campaign primitives — `runCampaign`, driver implementations, storage. |
|
|
224
|
+
| `@tangle-network/agent-eval/multishot` | Multi-shot optimization primitives. |
|
|
218
225
|
| `@tangle-network/agent-eval/control` | `observe → validate → decide → act`, action policy, propose/review loops |
|
|
219
226
|
| `@tangle-network/agent-eval/traces` | trace stores, emitters, TraceAnalyst, replay |
|
|
220
227
|
| `@tangle-network/agent-eval/optimization` | feedback trajectories, multi-shot, prompt evolution, GEPA, EvalCampaign |
|
|
@@ -2,39 +2,35 @@ import { TraceSpanEvent, HostedClient } from '../hosted/index.js';
|
|
|
2
2
|
import '../types-8u72Gc76.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* # `@tangle-network/agent-eval/adapters/
|
|
5
|
+
* # `@tangle-network/agent-eval/adapters/otel` — OTel→hosted bridge.
|
|
6
6
|
*
|
|
7
|
-
* Forwards OpenTelemetry-shaped spans
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* instrumentation library in the TypeScript-agent ecosystem. Partners using
|
|
13
|
-
* traceai for tracing should be able to plug it into Tangle Intelligence
|
|
14
|
-
* with one config line — not rebuild OTel emission from scratch. Adapter
|
|
15
|
-
* shape applies equally to any OTel SpanProcessor pipeline.
|
|
7
|
+
* Forwards OpenTelemetry-shaped spans into the hosted-tier ingest endpoint
|
|
8
|
+
* via `createHostedClient`. Works with anything that emits OTel
|
|
9
|
+
* `ReadableSpan`s — `@opentelemetry/sdk-trace-base` directly, OpenLLMetry,
|
|
10
|
+
* Phoenix's OTel exporter, TraceAI from future-agi, any OTel collector
|
|
11
|
+
* pipeline.
|
|
16
12
|
*
|
|
17
13
|
* **Pattern:**
|
|
18
14
|
*
|
|
19
15
|
* ```ts
|
|
20
16
|
* import { createHostedClient } from '@tangle-network/agent-eval/hosted'
|
|
21
|
-
* import {
|
|
17
|
+
* import { createOtelBridge } from '@tangle-network/agent-eval/adapters/otel'
|
|
22
18
|
*
|
|
23
19
|
* const client = createHostedClient({ endpoint, apiKey, tenantId })
|
|
24
|
-
* const bridge =
|
|
20
|
+
* const bridge = createOtelBridge({ client, defaultRunId: substrateRunId })
|
|
25
21
|
*
|
|
26
22
|
* // Wherever your OTel SpanProcessor hands you a finished span:
|
|
27
|
-
* processor.onEnd = (span) => bridge.ingest([span])
|
|
23
|
+
* processor.onEnd = (span) => { void bridge.ingest([span]) }
|
|
28
24
|
* // …or in a SpanProcessor.onShutdown / batch flush:
|
|
29
25
|
* await bridge.ingest(batchedSpans)
|
|
30
26
|
* ```
|
|
31
27
|
*
|
|
32
28
|
* No `@opentelemetry/*` dependency is declared here — the adapter accepts
|
|
33
29
|
* a structurally-typed `OtelLikeSpan`. This keeps the substrate dep graph
|
|
34
|
-
* lean while remaining compatible with OTel SDK
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
30
|
+
* lean while remaining compatible with any OTel SDK that produces
|
|
31
|
+
* `ReadableSpan`-shaped instances. If a consumer's span shape exposes
|
|
32
|
+
* `parentSpanId` as a top-level field rather than via `parentSpanContext()`,
|
|
33
|
+
* the adapter accepts both forms.
|
|
38
34
|
*/
|
|
39
35
|
|
|
40
36
|
/**
|
|
@@ -47,11 +43,10 @@ type HrTime = [number, number];
|
|
|
47
43
|
declare const OTEL_STATUS_UNSET = 0;
|
|
48
44
|
declare const OTEL_STATUS_OK = 1;
|
|
49
45
|
declare const OTEL_STATUS_ERROR = 2;
|
|
50
|
-
type OtelAttributeValue = string | number | boolean | null | undefined
|
|
46
|
+
type OtelAttributeValue = string | number | boolean | null | undefined | Array<string> | Array<number> | Array<boolean>;
|
|
51
47
|
/**
|
|
52
48
|
* Structural surface compatible with `@opentelemetry/sdk-trace-base`'s
|
|
53
|
-
* `ReadableSpan`. Consumers pass instances they get from their OTel SDK
|
|
54
|
-
* (or from `future-agi/traceai`, which produces spans of this shape).
|
|
49
|
+
* `ReadableSpan`. Consumers pass instances they get from their OTel SDK.
|
|
55
50
|
*/
|
|
56
51
|
interface OtelLikeSpan {
|
|
57
52
|
spanContext: () => {
|
|
@@ -59,9 +54,8 @@ interface OtelLikeSpan {
|
|
|
59
54
|
spanId: string;
|
|
60
55
|
traceFlags?: number;
|
|
61
56
|
};
|
|
62
|
-
/** Set on the span itself by some SDKs (
|
|
63
|
-
*
|
|
64
|
-
* checks both. */
|
|
57
|
+
/** Set on the span itself by some SDKs (OTLP-shape). Other SDKs expose
|
|
58
|
+
* the parent via `parentSpanContext()` instead — the adapter checks both. */
|
|
65
59
|
parentSpanId?: string;
|
|
66
60
|
parentSpanContext?: () => {
|
|
67
61
|
spanId: string;
|
|
@@ -82,7 +76,7 @@ interface OtelLikeSpan {
|
|
|
82
76
|
}
|
|
83
77
|
/** `[seconds, nanoseconds]` → unix-nano number. */
|
|
84
78
|
declare function hrTimeToUnixNano(hr: HrTime): number;
|
|
85
|
-
interface
|
|
79
|
+
interface OtelBridgeOptions {
|
|
86
80
|
/** Hosted client to forward spans to. */
|
|
87
81
|
client: HostedClient;
|
|
88
82
|
/** When set, spans missing a `tangle.runId` attribute receive this value
|
|
@@ -97,13 +91,13 @@ interface TraceAiBridgeOptions {
|
|
|
97
91
|
* this when you need backpressure or to spill to a fallback. */
|
|
98
92
|
onError?: (err: unknown, batch: TraceSpanEvent[]) => void | Promise<void>;
|
|
99
93
|
}
|
|
100
|
-
interface
|
|
94
|
+
interface OtelBridge {
|
|
101
95
|
/** Convert + ingest a batch of OTel-shape spans. */
|
|
102
96
|
ingest(spans: OtelLikeSpan[]): Promise<void>;
|
|
103
97
|
/** Convert one OTel span to the wire-format event. Useful for tests or
|
|
104
98
|
* custom batching pipelines. */
|
|
105
99
|
spanToEvent(span: OtelLikeSpan): TraceSpanEvent;
|
|
106
100
|
}
|
|
107
|
-
declare function
|
|
101
|
+
declare function createOtelBridge(opts: OtelBridgeOptions): OtelBridge;
|
|
108
102
|
|
|
109
|
-
export { type HrTime, OTEL_STATUS_ERROR, OTEL_STATUS_OK, OTEL_STATUS_UNSET, type OtelAttributeValue, type
|
|
103
|
+
export { type HrTime, OTEL_STATUS_ERROR, OTEL_STATUS_OK, OTEL_STATUS_UNSET, type OtelAttributeValue, type OtelBridge, type OtelBridgeOptions, type OtelLikeSpan, createOtelBridge, hrTimeToUnixNano };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../chunk-NSBPE2FW.js";
|
|
2
2
|
|
|
3
|
-
// src/adapters/
|
|
3
|
+
// src/adapters/otel.ts
|
|
4
4
|
var OTEL_STATUS_UNSET = 0;
|
|
5
5
|
var OTEL_STATUS_OK = 1;
|
|
6
6
|
var OTEL_STATUS_ERROR = 2;
|
|
@@ -20,6 +20,10 @@ function cleanAttributes(attrs) {
|
|
|
20
20
|
if (v === null || v === void 0) continue;
|
|
21
21
|
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
22
22
|
out[k] = v;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(v)) {
|
|
26
|
+
out[k] = JSON.stringify(v);
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
return out;
|
|
@@ -37,10 +41,10 @@ function resolveParentSpanId(span) {
|
|
|
37
41
|
const ctx = span.parentSpanContext?.();
|
|
38
42
|
return ctx?.spanId;
|
|
39
43
|
}
|
|
40
|
-
function
|
|
44
|
+
function createOtelBridge(opts) {
|
|
41
45
|
const batchSize = opts.batchSize ?? 200;
|
|
42
46
|
const onError = opts.onError ?? ((err) => {
|
|
43
|
-
console.warn("[
|
|
47
|
+
console.warn("[otel-bridge] ingest batch failed:", err);
|
|
44
48
|
});
|
|
45
49
|
function convert(span) {
|
|
46
50
|
const ctx = span.spanContext();
|
|
@@ -100,7 +104,7 @@ export {
|
|
|
100
104
|
OTEL_STATUS_ERROR,
|
|
101
105
|
OTEL_STATUS_OK,
|
|
102
106
|
OTEL_STATUS_UNSET,
|
|
103
|
-
|
|
107
|
+
createOtelBridge,
|
|
104
108
|
hrTimeToUnixNano
|
|
105
109
|
};
|
|
106
|
-
//# sourceMappingURL=
|
|
110
|
+
//# sourceMappingURL=otel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/otel.ts"],"sourcesContent":["/**\n * # `@tangle-network/agent-eval/adapters/otel` — OTel→hosted bridge.\n *\n * Forwards OpenTelemetry-shaped spans into the hosted-tier ingest endpoint\n * via `createHostedClient`. Works with anything that emits OTel\n * `ReadableSpan`s — `@opentelemetry/sdk-trace-base` directly, OpenLLMetry,\n * Phoenix's OTel exporter, TraceAI from future-agi, any OTel collector\n * pipeline.\n *\n * **Pattern:**\n *\n * ```ts\n * import { createHostedClient } from '@tangle-network/agent-eval/hosted'\n * import { createOtelBridge } from '@tangle-network/agent-eval/adapters/otel'\n *\n * const client = createHostedClient({ endpoint, apiKey, tenantId })\n * const bridge = createOtelBridge({ client, defaultRunId: substrateRunId })\n *\n * // Wherever your OTel SpanProcessor hands you a finished span:\n * processor.onEnd = (span) => { void bridge.ingest([span]) }\n * // …or in a SpanProcessor.onShutdown / batch flush:\n * await bridge.ingest(batchedSpans)\n * ```\n *\n * No `@opentelemetry/*` dependency is declared here — the adapter accepts\n * a structurally-typed `OtelLikeSpan`. This keeps the substrate dep graph\n * lean while remaining compatible with any OTel SDK that produces\n * `ReadableSpan`-shaped instances. If a consumer's span shape exposes\n * `parentSpanId` as a top-level field rather than via `parentSpanContext()`,\n * the adapter accepts both forms.\n */\n\nimport type { HostedClient } from '../hosted/client'\nimport type { TraceSpanEvent } from '../hosted/types'\n\n// ── OTel-compatible structural types ─────────────────────────────────\n\n/**\n * `[seconds, nanoseconds]` — the OTel SDK's `HrTime` shape. Spans emitted\n * by the OTel SDK carry timestamps in this representation; we convert to\n * a single unix-nano number for the wire format.\n */\nexport type HrTime = [number, number]\n\n/** Standard OTel `SpanStatusCode` numeric values: 0 = UNSET, 1 = OK, 2 = ERROR. */\nexport const OTEL_STATUS_UNSET = 0\nexport const OTEL_STATUS_OK = 1\nexport const OTEL_STATUS_ERROR = 2\n\nexport type OtelAttributeValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string>\n | Array<number>\n | Array<boolean>\n\n/**\n * Structural surface compatible with `@opentelemetry/sdk-trace-base`'s\n * `ReadableSpan`. Consumers pass instances they get from their OTel SDK.\n */\nexport interface OtelLikeSpan {\n spanContext: () => { traceId: string; spanId: string; traceFlags?: number }\n /** Set on the span itself by some SDKs (OTLP-shape). Other SDKs expose\n * the parent via `parentSpanContext()` instead — the adapter checks both. */\n parentSpanId?: string\n parentSpanContext?: () => { spanId: string } | undefined\n name: string\n startTime: HrTime\n endTime: HrTime\n attributes: Record<string, OtelAttributeValue>\n events?: Array<{\n name: string\n time: HrTime\n attributes?: Record<string, OtelAttributeValue>\n }>\n status?: { code: number; message?: string }\n}\n\n// ── Conversion ───────────────────────────────────────────────────────\n\n/** `[seconds, nanoseconds]` → unix-nano number. */\nexport function hrTimeToUnixNano(hr: HrTime): number {\n const [seconds, nanos] = hr\n return seconds * 1_000_000_000 + nanos\n}\n\nfunction statusCodeName(code: number | undefined): 'OK' | 'ERROR' | 'UNSET' {\n if (code === OTEL_STATUS_OK) return 'OK'\n if (code === OTEL_STATUS_ERROR) return 'ERROR'\n return 'UNSET'\n}\n\n/**\n * Normalise OTel attributes to the scalar shape the wire format accepts.\n *\n * - null / undefined → dropped (no representation in the wire format)\n * - string / number / boolean → passed through\n * - Array<scalar> → JSON-stringified (the wire format is scalar-only;\n * dropping arrays would silently lose information, so we serialise\n * them deterministically)\n *\n * OTel SDK / TraceAI emit array attributes routinely (e.g. `tool.names`,\n * `http.request.header.set-cookie`). Stringification keeps the data flowing\n * through to the dashboard; consumers parse with `JSON.parse` if they need\n * the structured form back.\n */\nfunction cleanAttributes(\n attrs: Record<string, OtelAttributeValue> | undefined,\n): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n if (!attrs) return out\n for (const [k, v] of Object.entries(attrs)) {\n if (v === null || v === undefined) continue\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n out[k] = v\n continue\n }\n if (Array.isArray(v)) {\n out[k] = JSON.stringify(v)\n }\n }\n return out\n}\n\nfunction readPivotString(\n attrs: Record<string, OtelAttributeValue>,\n key: string,\n): string | undefined {\n const v = attrs[key]\n return typeof v === 'string' ? v : undefined\n}\n\nfunction readPivotNumber(\n attrs: Record<string, OtelAttributeValue>,\n key: string,\n): number | undefined {\n const v = attrs[key]\n return typeof v === 'number' ? v : undefined\n}\n\nfunction resolveParentSpanId(span: OtelLikeSpan): string | undefined {\n if (span.parentSpanId) return span.parentSpanId\n const ctx = span.parentSpanContext?.()\n return ctx?.spanId\n}\n\n// ── Bridge ───────────────────────────────────────────────────────────\n\nexport interface OtelBridgeOptions {\n /** Hosted client to forward spans to. */\n client: HostedClient\n /** When set, spans missing a `tangle.runId` attribute receive this value\n * on the way out. Useful when the OTel emitter doesn't know which\n * substrate run it's serving. */\n defaultRunId?: string\n /** Max spans per ingest call. Default 200. The hosted ingest endpoint\n * caps at 5000 per call; we batch smaller by default to keep individual\n * retries cheap. */\n batchSize?: number\n /** Called when a batch fails to ingest. Defaults to a console.warn. Hook\n * this when you need backpressure or to spill to a fallback. */\n onError?: (err: unknown, batch: TraceSpanEvent[]) => void | Promise<void>\n}\n\nexport interface OtelBridge {\n /** Convert + ingest a batch of OTel-shape spans. */\n ingest(spans: OtelLikeSpan[]): Promise<void>\n /** Convert one OTel span to the wire-format event. Useful for tests or\n * custom batching pipelines. */\n spanToEvent(span: OtelLikeSpan): TraceSpanEvent\n}\n\nexport function createOtelBridge(opts: OtelBridgeOptions): OtelBridge {\n const batchSize = opts.batchSize ?? 200\n const onError =\n opts.onError ??\n ((err) => {\n console.warn('[otel-bridge] ingest batch failed:', err)\n })\n\n function convert(span: OtelLikeSpan): TraceSpanEvent {\n const ctx = span.spanContext()\n const attributes = cleanAttributes(span.attributes)\n // Pull pivot attributes off the cleaned attribute map so they round-trip\n // through the wire format's first-class fields. They REMAIN in\n // `attributes` as well so downstream OTel viewers see the same values.\n const runId = readPivotString(attributes, 'tangle.runId') ?? opts.defaultRunId\n const scenarioId = readPivotString(attributes, 'tangle.scenarioId')\n const cellId = readPivotString(attributes, 'tangle.cellId')\n const generation = readPivotNumber(attributes, 'tangle.generation')\n\n if (runId && !attributes['tangle.runId']) {\n attributes['tangle.runId'] = runId\n }\n\n const event: TraceSpanEvent = {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n name: span.name,\n startTimeUnixNano: hrTimeToUnixNano(span.startTime),\n endTimeUnixNano: hrTimeToUnixNano(span.endTime),\n attributes,\n }\n const parentSpanId = resolveParentSpanId(span)\n if (parentSpanId) event.parentSpanId = parentSpanId\n if (span.events && span.events.length > 0) {\n event.events = span.events.map((e) => {\n const eventAttrs = cleanAttributes(e.attributes)\n const node: {\n timeUnixNano: number\n name: string\n attributes?: Record<string, string | number | boolean>\n } = {\n timeUnixNano: hrTimeToUnixNano(e.time),\n name: e.name,\n }\n if (Object.keys(eventAttrs).length > 0) node.attributes = eventAttrs\n return node\n })\n }\n if (span.status) {\n event.status = { code: statusCodeName(span.status.code), message: span.status.message }\n }\n if (runId) event['tangle.runId'] = runId\n if (scenarioId) event['tangle.scenarioId'] = scenarioId\n if (cellId) event['tangle.cellId'] = cellId\n if (generation !== undefined) event['tangle.generation'] = generation\n return event\n }\n\n async function ingest(spans: OtelLikeSpan[]): Promise<void> {\n if (spans.length === 0) return\n const events = spans.map(convert)\n for (let i = 0; i < events.length; i += batchSize) {\n const batch = events.slice(i, i + batchSize)\n try {\n await opts.client.ingestTraces(batch)\n } catch (err) {\n await onError(err, batch)\n }\n }\n }\n\n return { ingest, spanToEvent: convert }\n}\n"],"mappings":";;;AA6CO,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AACvB,IAAM,oBAAoB;AAqC1B,SAAS,iBAAiB,IAAoB;AACnD,QAAM,CAAC,SAAS,KAAK,IAAI;AACzB,SAAO,UAAU,MAAgB;AACnC;AAEA,SAAS,eAAe,MAAoD;AAC1E,MAAI,SAAS,eAAgB,QAAO;AACpC,MAAI,SAAS,kBAAmB,QAAO;AACvC,SAAO;AACT;AAgBA,SAAS,gBACP,OAC2C;AAC3C,QAAM,MAAiD,CAAC;AACxD,MAAI,CAAC,MAAO,QAAO;AACnB,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,MAAM,QAAQ,MAAM,OAAW;AACnC,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW;AAC5E,UAAI,CAAC,IAAI;AACT;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,UAAI,CAAC,IAAI,KAAK,UAAU,CAAC;AAAA,IAC3B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBACP,OACA,KACoB;AACpB,QAAM,IAAI,MAAM,GAAG;AACnB,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,gBACP,OACA,KACoB;AACpB,QAAM,IAAI,MAAM,GAAG;AACnB,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,oBAAoB,MAAwC;AACnE,MAAI,KAAK,aAAc,QAAO,KAAK;AACnC,QAAM,MAAM,KAAK,oBAAoB;AACrC,SAAO,KAAK;AACd;AA4BO,SAAS,iBAAiB,MAAqC;AACpE,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,UACJ,KAAK,YACJ,CAAC,QAAQ;AACR,YAAQ,KAAK,sCAAsC,GAAG;AAAA,EACxD;AAEF,WAAS,QAAQ,MAAoC;AACnD,UAAM,MAAM,KAAK,YAAY;AAC7B,UAAM,aAAa,gBAAgB,KAAK,UAAU;AAIlD,UAAM,QAAQ,gBAAgB,YAAY,cAAc,KAAK,KAAK;AAClE,UAAM,aAAa,gBAAgB,YAAY,mBAAmB;AAClE,UAAM,SAAS,gBAAgB,YAAY,eAAe;AAC1D,UAAM,aAAa,gBAAgB,YAAY,mBAAmB;AAElE,QAAI,SAAS,CAAC,WAAW,cAAc,GAAG;AACxC,iBAAW,cAAc,IAAI;AAAA,IAC/B;AAEA,UAAM,QAAwB;AAAA,MAC5B,SAAS,IAAI;AAAA,MACb,QAAQ,IAAI;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,mBAAmB,iBAAiB,KAAK,SAAS;AAAA,MAClD,iBAAiB,iBAAiB,KAAK,OAAO;AAAA,MAC9C;AAAA,IACF;AACA,UAAM,eAAe,oBAAoB,IAAI;AAC7C,QAAI,aAAc,OAAM,eAAe;AACvC,QAAI,KAAK,UAAU,KAAK,OAAO,SAAS,GAAG;AACzC,YAAM,SAAS,KAAK,OAAO,IAAI,CAAC,MAAM;AACpC,cAAM,aAAa,gBAAgB,EAAE,UAAU;AAC/C,cAAM,OAIF;AAAA,UACF,cAAc,iBAAiB,EAAE,IAAI;AAAA,UACrC,MAAM,EAAE;AAAA,QACV;AACA,YAAI,OAAO,KAAK,UAAU,EAAE,SAAS,EAAG,MAAK,aAAa;AAC1D,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,SAAS,EAAE,MAAM,eAAe,KAAK,OAAO,IAAI,GAAG,SAAS,KAAK,OAAO,QAAQ;AAAA,IACxF;AACA,QAAI,MAAO,OAAM,cAAc,IAAI;AACnC,QAAI,WAAY,OAAM,mBAAmB,IAAI;AAC7C,QAAI,OAAQ,OAAM,eAAe,IAAI;AACrC,QAAI,eAAe,OAAW,OAAM,mBAAmB,IAAI;AAC3D,WAAO;AAAA,EACT;AAEA,iBAAe,OAAO,OAAsC;AAC1D,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,SAAS,MAAM,IAAI,OAAO;AAChC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW;AACjD,YAAM,QAAQ,OAAO,MAAM,GAAG,IAAI,SAAS;AAC3C,UAAI;AACF,cAAM,KAAK,OAAO,aAAa,KAAK;AAAA,MACtC,SAAS,KAAK;AACZ,cAAM,QAAQ,KAAK,KAAK;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,aAAa,QAAQ;AACxC;","names":[]}
|
package/dist/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "@tangle-network/agent-eval — wire protocol",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.49.0",
|
|
6
6
|
"description": "HTTP and stdio RPC interface to agent-eval. The TypeScript runtime is the source of truth; this spec is the contract that cross-language clients (Python, Rust, Go) generate from.\n\nWire-protocol version: 1.0.0. Bumps on breaking changes to request/response schemas.",
|
|
7
7
|
"contact": {
|
|
8
8
|
"name": "Tangle Network",
|
|
@@ -35,7 +35,7 @@ it*. Unified at the trace level, you see both as one timeline per cell.
|
|
|
35
35
|
- Compose: register TraceAI's instrumentations on the global tracer
|
|
36
36
|
provider, then either point both at your OTLP collector or at
|
|
37
37
|
TraceAI's hosted backend if you want their UI.
|
|
38
|
-
- **Or use the bridge: `@tangle-network/agent-eval/adapters/
|
|
38
|
+
- **Or use the bridge: `@tangle-network/agent-eval/adapters/otel`.**
|
|
39
39
|
Forwards finished OTel spans (`ReadableSpan` shape) directly into the
|
|
40
40
|
hosted-tier ingest, lifting `tangle.runId` / `tangle.scenarioId` /
|
|
41
41
|
`tangle.cellId` / `tangle.generation` to first-class wire fields so
|
|
@@ -43,10 +43,10 @@ it*. Unified at the trace level, you see both as one timeline per cell.
|
|
|
43
43
|
at the substrate; consumers pass spans from their own OTel SDK.
|
|
44
44
|
```ts
|
|
45
45
|
import { createHostedClient } from '@tangle-network/agent-eval/hosted'
|
|
46
|
-
import {
|
|
46
|
+
import { createOtelBridge } from '@tangle-network/agent-eval/adapters/otel'
|
|
47
47
|
|
|
48
48
|
const client = createHostedClient({ endpoint, apiKey, tenantId })
|
|
49
|
-
const bridge =
|
|
49
|
+
const bridge = createOtelBridge({ client, defaultRunId: substrateRunId })
|
|
50
50
|
processor.onEnd = (span) => { void bridge.ingest([span]) }
|
|
51
51
|
// ...or call `bridge.ingest(batch)` from a SpanProcessor.onShutdown.
|
|
52
52
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-eval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Substrate for self-improving agents: traces, verifiable rewards, preferences, GEPA / reflective mutation, auto-research, replay, sequential anytime-valid stats, and release gates.",
|
|
5
5
|
"homepage": "https://github.com/tangle-network/agent-eval#readme",
|
|
6
6
|
"repository": {
|
|
@@ -119,10 +119,10 @@
|
|
|
119
119
|
"import": "./dist/adapters/http.js",
|
|
120
120
|
"default": "./dist/adapters/http.js"
|
|
121
121
|
},
|
|
122
|
-
"./adapters/
|
|
123
|
-
"types": "./dist/adapters/
|
|
124
|
-
"import": "./dist/adapters/
|
|
125
|
-
"default": "./dist/adapters/
|
|
122
|
+
"./adapters/otel": {
|
|
123
|
+
"types": "./dist/adapters/otel.d.ts",
|
|
124
|
+
"import": "./dist/adapters/otel.js",
|
|
125
|
+
"default": "./dist/adapters/otel.js"
|
|
126
126
|
},
|
|
127
127
|
"./hosted": {
|
|
128
128
|
"types": "./dist/hosted/index.d.ts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/traceai.ts"],"sourcesContent":["/**\n * # `@tangle-network/agent-eval/adapters/traceai` — OTel→hosted bridge.\n *\n * Forwards OpenTelemetry-shaped spans (from `future-agi/traceai`, from the\n * OTel SDK directly, or from any library that emits OTel `ReadableSpan`s)\n * into the hosted-tier ingest endpoint via `createHostedClient`.\n *\n * **Why this exists:** future-agi ships the strongest OTel-native\n * instrumentation library in the TypeScript-agent ecosystem. Partners using\n * traceai for tracing should be able to plug it into Tangle Intelligence\n * with one config line — not rebuild OTel emission from scratch. Adapter\n * shape applies equally to any OTel SpanProcessor pipeline.\n *\n * **Pattern:**\n *\n * ```ts\n * import { createHostedClient } from '@tangle-network/agent-eval/hosted'\n * import { createTraceAiBridge } from '@tangle-network/agent-eval/adapters/traceai'\n *\n * const client = createHostedClient({ endpoint, apiKey, tenantId })\n * const bridge = createTraceAiBridge({ client, defaultRunId: substrateRunId })\n *\n * // Wherever your OTel SpanProcessor hands you a finished span:\n * processor.onEnd = (span) => bridge.ingest([span])\n * // …or in a SpanProcessor.onShutdown / batch flush:\n * await bridge.ingest(batchedSpans)\n * ```\n *\n * No `@opentelemetry/*` dependency is declared here — the adapter accepts\n * a structurally-typed `OtelLikeSpan`. This keeps the substrate dep graph\n * lean while remaining compatible with OTel SDK `ReadableSpan` instances\n * and with traceai's emitted spans. If a consumer's span shape differs\n * (e.g. `parentSpanId` as a top-level field rather than via\n * `parentSpanContext()`), the adapter accepts both forms.\n */\n\nimport type { HostedClient } from '../hosted/client'\nimport type { TraceSpanEvent } from '../hosted/types'\n\n// ── OTel-compatible structural types ─────────────────────────────────\n\n/**\n * `[seconds, nanoseconds]` — the OTel SDK's `HrTime` shape. Spans emitted\n * by the OTel SDK carry timestamps in this representation; we convert to\n * a single unix-nano number for the wire format.\n */\nexport type HrTime = [number, number]\n\n/** Standard OTel `SpanStatusCode` numeric values: 0 = UNSET, 1 = OK, 2 = ERROR. */\nexport const OTEL_STATUS_UNSET = 0\nexport const OTEL_STATUS_OK = 1\nexport const OTEL_STATUS_ERROR = 2\n\nexport type OtelAttributeValue = string | number | boolean | null | undefined\n\n/**\n * Structural surface compatible with `@opentelemetry/sdk-trace-base`'s\n * `ReadableSpan`. Consumers pass instances they get from their OTel SDK\n * (or from `future-agi/traceai`, which produces spans of this shape).\n */\nexport interface OtelLikeSpan {\n spanContext: () => { traceId: string; spanId: string; traceFlags?: number }\n /** Set on the span itself by some SDKs (legacy / OTLP-shape). Some SDKs\n * expose the parent via `parentSpanContext()` instead — the adapter\n * checks both. */\n parentSpanId?: string\n parentSpanContext?: () => { spanId: string } | undefined\n name: string\n startTime: HrTime\n endTime: HrTime\n attributes: Record<string, OtelAttributeValue>\n events?: Array<{\n name: string\n time: HrTime\n attributes?: Record<string, OtelAttributeValue>\n }>\n status?: { code: number; message?: string }\n}\n\n// ── Conversion ───────────────────────────────────────────────────────\n\n/** `[seconds, nanoseconds]` → unix-nano number. */\nexport function hrTimeToUnixNano(hr: HrTime): number {\n const [seconds, nanos] = hr\n return seconds * 1_000_000_000 + nanos\n}\n\nfunction statusCodeName(code: number | undefined): 'OK' | 'ERROR' | 'UNSET' {\n if (code === OTEL_STATUS_OK) return 'OK'\n if (code === OTEL_STATUS_ERROR) return 'ERROR'\n return 'UNSET'\n}\n\n/** Drop null/undefined attribute values; keep string/number/boolean. */\nfunction cleanAttributes(\n attrs: Record<string, OtelAttributeValue> | undefined,\n): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n if (!attrs) return out\n for (const [k, v] of Object.entries(attrs)) {\n if (v === null || v === undefined) continue\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n out[k] = v\n }\n }\n return out\n}\n\nfunction readPivotString(\n attrs: Record<string, OtelAttributeValue>,\n key: string,\n): string | undefined {\n const v = attrs[key]\n return typeof v === 'string' ? v : undefined\n}\n\nfunction readPivotNumber(\n attrs: Record<string, OtelAttributeValue>,\n key: string,\n): number | undefined {\n const v = attrs[key]\n return typeof v === 'number' ? v : undefined\n}\n\nfunction resolveParentSpanId(span: OtelLikeSpan): string | undefined {\n if (span.parentSpanId) return span.parentSpanId\n const ctx = span.parentSpanContext?.()\n return ctx?.spanId\n}\n\n// ── Bridge ───────────────────────────────────────────────────────────\n\nexport interface TraceAiBridgeOptions {\n /** Hosted client to forward spans to. */\n client: HostedClient\n /** When set, spans missing a `tangle.runId` attribute receive this value\n * on the way out. Useful when the OTel emitter doesn't know which\n * substrate run it's serving. */\n defaultRunId?: string\n /** Max spans per ingest call. Default 200. The hosted ingest endpoint\n * caps at 5000 per call; we batch smaller by default to keep individual\n * retries cheap. */\n batchSize?: number\n /** Called when a batch fails to ingest. Defaults to a console.warn. Hook\n * this when you need backpressure or to spill to a fallback. */\n onError?: (err: unknown, batch: TraceSpanEvent[]) => void | Promise<void>\n}\n\nexport interface TraceAiBridge {\n /** Convert + ingest a batch of OTel-shape spans. */\n ingest(spans: OtelLikeSpan[]): Promise<void>\n /** Convert one OTel span to the wire-format event. Useful for tests or\n * custom batching pipelines. */\n spanToEvent(span: OtelLikeSpan): TraceSpanEvent\n}\n\nexport function createTraceAiBridge(opts: TraceAiBridgeOptions): TraceAiBridge {\n const batchSize = opts.batchSize ?? 200\n const onError =\n opts.onError ??\n ((err) => {\n console.warn('[traceai-bridge] ingest batch failed:', err)\n })\n\n function convert(span: OtelLikeSpan): TraceSpanEvent {\n const ctx = span.spanContext()\n const attributes = cleanAttributes(span.attributes)\n // Pull pivot attributes off the cleaned attribute map so they round-trip\n // through the wire format's first-class fields. They REMAIN in\n // `attributes` as well so downstream OTel viewers see the same values.\n const runId = readPivotString(attributes, 'tangle.runId') ?? opts.defaultRunId\n const scenarioId = readPivotString(attributes, 'tangle.scenarioId')\n const cellId = readPivotString(attributes, 'tangle.cellId')\n const generation = readPivotNumber(attributes, 'tangle.generation')\n\n if (runId && !attributes['tangle.runId']) {\n attributes['tangle.runId'] = runId\n }\n\n const event: TraceSpanEvent = {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n name: span.name,\n startTimeUnixNano: hrTimeToUnixNano(span.startTime),\n endTimeUnixNano: hrTimeToUnixNano(span.endTime),\n attributes,\n }\n const parentSpanId = resolveParentSpanId(span)\n if (parentSpanId) event.parentSpanId = parentSpanId\n if (span.events && span.events.length > 0) {\n event.events = span.events.map((e) => {\n const eventAttrs = cleanAttributes(e.attributes)\n const node: {\n timeUnixNano: number\n name: string\n attributes?: Record<string, string | number | boolean>\n } = {\n timeUnixNano: hrTimeToUnixNano(e.time),\n name: e.name,\n }\n if (Object.keys(eventAttrs).length > 0) node.attributes = eventAttrs\n return node\n })\n }\n if (span.status) {\n event.status = { code: statusCodeName(span.status.code), message: span.status.message }\n }\n if (runId) event['tangle.runId'] = runId\n if (scenarioId) event['tangle.scenarioId'] = scenarioId\n if (cellId) event['tangle.cellId'] = cellId\n if (generation !== undefined) event['tangle.generation'] = generation\n return event\n }\n\n async function ingest(spans: OtelLikeSpan[]): Promise<void> {\n if (spans.length === 0) return\n const events = spans.map(convert)\n for (let i = 0; i < events.length; i += batchSize) {\n const batch = events.slice(i, i + batchSize)\n try {\n await opts.client.ingestTraces(batch)\n } catch (err) {\n await onError(err, batch)\n }\n }\n }\n\n return { ingest, spanToEvent: convert }\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|