@senpi/runtime 1.1.0-dev.runtime-phase-2.20260610130051 → 1.1.0-dev.runtime-phase-2.20260611115723
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/dist/actions/close-position/close-position.action.d.ts.map +1 -1
- package/dist/actions/close-position/close-position.action.js +7 -2
- package/dist/actions/close-position/close-position.action.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +20 -0
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts +6 -0
- package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.js +39 -0
- package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
- package/dist/dsl/engine-client/event-mapping.d.ts +2 -0
- package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/event-mapping.js +3 -0
- package/dist/dsl/engine-client/event-mapping.js.map +1 -1
- package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
- package/dist/dsl/events/build-closed-event.js +1 -0
- package/dist/dsl/events/build-closed-event.js.map +1 -1
- package/dist/dsl/events/guards.d.ts.map +1 -1
- package/dist/dsl/events/guards.js +10 -0
- package/dist/dsl/events/guards.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +3 -0
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/events/initial-state.d.ts.map +1 -1
- package/dist/dsl/events/initial-state.js +3 -1
- package/dist/dsl/events/initial-state.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +36 -2
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/recording/otel/mappers.d.ts.map +1 -1
- package/dist/recording/otel/mappers.js +5 -0
- package/dist/recording/otel/mappers.js.map +1 -1
- package/dist/recording/records.d.ts +14 -0
- package/dist/recording/records.d.ts.map +1 -1
- package/dist/recording/wiring/record-builders.d.ts +8 -2
- package/dist/recording/wiring/record-builders.d.ts.map +1 -1
- package/dist/recording/wiring/record-builders.js +25 -8
- package/dist/recording/wiring/record-builders.js.map +1 -1
- package/dist/recording/wiring/subscriber.d.ts +7 -0
- package/dist/recording/wiring/subscriber.d.ts.map +1 -1
- package/dist/recording/wiring/subscriber.js +2 -2
- package/dist/recording/wiring/subscriber.js.map +1 -1
- package/dist/runtime/create-runtime-context.d.ts +2 -0
- package/dist/runtime/create-runtime-context.d.ts.map +1 -1
- package/dist/runtime/create-runtime-context.js +1 -0
- package/dist/runtime/create-runtime-context.js.map +1 -1
- package/dist/runtime/runtime.d.ts +6 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +21 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/senpi/client.d.ts +19 -0
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +22 -0
- package/dist/senpi/client.js.map +1 -1
- package/dist/senpi/index.d.ts +2 -1
- package/dist/senpi/index.d.ts.map +1 -1
- package/dist/senpi/index.js +1 -1
- package/dist/senpi/index.js.map +1 -1
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +5 -0
- package/dist/state/state-manager.js.map +1 -1
- package/dist/tracking/build-rehydrate-items.d.ts +15 -0
- package/dist/tracking/build-rehydrate-items.d.ts.map +1 -0
- package/dist/tracking/build-rehydrate-items.js +19 -0
- package/dist/tracking/build-rehydrate-items.js.map +1 -0
- package/dist/tracking/index.d.ts +3 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +2 -0
- package/dist/tracking/index.js.map +1 -1
- package/dist/tracking/position-id-registry.d.ts +60 -0
- package/dist/tracking/position-id-registry.d.ts.map +1 -0
- package/dist/tracking/position-id-registry.js +133 -0
- package/dist/tracking/position-id-registry.js.map +1 -0
- package/dist/tracking/spans/fan-out-span-transport.d.ts +22 -0
- package/dist/tracking/spans/fan-out-span-transport.d.ts.map +1 -0
- package/dist/tracking/spans/fan-out-span-transport.js +49 -0
- package/dist/tracking/spans/fan-out-span-transport.js.map +1 -0
- package/dist/tracking/spans/otel/map-span-to-trace-row.d.ts +16 -0
- package/dist/tracking/spans/otel/map-span-to-trace-row.d.ts.map +1 -0
- package/dist/tracking/spans/otel/map-span-to-trace-row.js +137 -0
- package/dist/tracking/spans/otel/map-span-to-trace-row.js.map +1 -0
- package/dist/tracking/spans/otel/recording-span-transport.d.ts +43 -0
- package/dist/tracking/spans/otel/recording-span-transport.d.ts.map +1 -0
- package/dist/tracking/spans/otel/recording-span-transport.js +64 -0
- package/dist/tracking/spans/otel/recording-span-transport.js.map +1 -0
- package/dist/tracking/spans/otel/span-ids.d.ts +22 -0
- package/dist/tracking/spans/otel/span-ids.d.ts.map +1 -0
- package/dist/tracking/spans/otel/span-ids.js +32 -0
- package/dist/tracking/spans/otel/span-ids.js.map +1 -0
- package/dist/tracking/spans/plugin-span-tracker.d.ts +6 -0
- package/dist/tracking/spans/plugin-span-tracker.d.ts.map +1 -1
- package/dist/tracking/spans/plugin-span-tracker.js +44 -7
- package/dist/tracking/spans/plugin-span-tracker.js.map +1 -1
- package/dist/tracking/spans/span-transport.d.ts +6 -0
- package/dist/tracking/spans/span-transport.d.ts.map +1 -1
- package/dist/tracking/spans/span-transport.js +1 -1
- package/dist/tracking/spans/span-transport.js.map +1 -1
- package/dist/types/dsl/index.d.ts +23 -0
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/runtime.d.ts +6 -0
- package/dist/types/runtime.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PositionIdRegistry: cross-flow position.id state machine.
|
|
3
|
+
*
|
|
4
|
+
* Sibling of the intra-flow correlation_id in src/tracking/context.ts. Keyed on
|
|
5
|
+
* (wallet, dex, asset).
|
|
6
|
+
*
|
|
7
|
+
* **Mint-at-intent.** A stable position.id is minted the moment the runtime decides to
|
|
8
|
+
* open ({@link mintOnIntent}), so it is already on the `action.open` emit — the open
|
|
9
|
+
* trace correlates by position.id directly, and even a sub-minute open+close (which the
|
|
10
|
+
* tracker poll never observes) still has a lifecycle.
|
|
11
|
+
*
|
|
12
|
+
* Two internal phases:
|
|
13
|
+
* - `pending` — minted at intent, awaiting a confirmed open. In-memory only: the id is
|
|
14
|
+
* a best-effort realtime grouping hint, not durable truth (post-hoc correlation
|
|
15
|
+
* anchors on order ids against the venue ledger). A stale pending (order submitted
|
|
16
|
+
* but never observed filling within {@link PENDING_OPEN_STALE_MS}) is lazily
|
|
17
|
+
* discarded at the next read, so an abandoned intent never chains onto an unrelated
|
|
18
|
+
* lifecycle hours later.
|
|
19
|
+
* - `entries` — confirmed open. Reused for INCREASED/DECREASED/FLIPPED and DSL reads;
|
|
20
|
+
* cleared on POSITION_CLOSED. Never staled.
|
|
21
|
+
*
|
|
22
|
+
* {@link mintOnOpened} promotes a pending intent-mint to a confirmed entry (reusing the
|
|
23
|
+
* id), or mints fresh for an externally-opened position with no prior intent.
|
|
24
|
+
*
|
|
25
|
+
* **Restart behavior.** Confirmed entries are rehydrated at boot from persisted DSL
|
|
26
|
+
* states ({@link rehydrate}). Pendings are not persisted: a restart inside the
|
|
27
|
+
* submit→observe window (~one tracker poll) loses the intent-mint and the tracker mints
|
|
28
|
+
* fresh on observation — a rare, accepted lifecycle split that downstream ledger
|
|
29
|
+
* reconciliation can heal via order ids.
|
|
30
|
+
*/
|
|
31
|
+
import { randomUUID } from "node:crypto";
|
|
32
|
+
import { createLogger } from "../utils/logger.js";
|
|
33
|
+
const logger = createLogger("[tracking][position-id]");
|
|
34
|
+
/** Age beyond which an unconfirmed intent-mint is considered abandoned (order never filled). */
|
|
35
|
+
export const PENDING_OPEN_STALE_MS = 30 * 60 * 1000;
|
|
36
|
+
/**
|
|
37
|
+
* Build the registry key for a position. Normalizes dex the same way the
|
|
38
|
+
* position-tracker scanner's buildPositionKey does (empty/whitespace → "main"),
|
|
39
|
+
* scoped by wallet so the map is correct under multi-wallet deployment.
|
|
40
|
+
*/
|
|
41
|
+
export function positionRegistryKey(wallet, dex, asset) {
|
|
42
|
+
const normalizedDex = dex.trim() || "main";
|
|
43
|
+
return `${wallet}|${normalizedDex}:${asset}`;
|
|
44
|
+
}
|
|
45
|
+
export class PositionIdRegistry {
|
|
46
|
+
entries = new Map();
|
|
47
|
+
/** Positions minted at intent, awaiting a confirmed open. */
|
|
48
|
+
pending = new Map();
|
|
49
|
+
now;
|
|
50
|
+
staleMs;
|
|
51
|
+
constructor(opts = {}) {
|
|
52
|
+
this.now = opts.now ?? Date.now;
|
|
53
|
+
this.staleMs = opts.staleMs ?? PENDING_OPEN_STALE_MS;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mint a position.id at the moment the runtime decides to open, before the position
|
|
57
|
+
* is confirmed open. The id is returned immediately so callers can stamp it on the
|
|
58
|
+
* `action.open` emit. Idempotent: a re-submit (or an already-confirmed entry) reuses
|
|
59
|
+
* the existing id rather than minting a new one.
|
|
60
|
+
*/
|
|
61
|
+
mintOnIntent(key) {
|
|
62
|
+
const confirmed = this.entries.get(key);
|
|
63
|
+
if (confirmed)
|
|
64
|
+
return confirmed;
|
|
65
|
+
const existing = this.livePending(key);
|
|
66
|
+
if (existing)
|
|
67
|
+
return { positionId: existing.positionId };
|
|
68
|
+
const positionId = randomUUID();
|
|
69
|
+
this.pending.set(key, { positionId, ts: this.now() });
|
|
70
|
+
return { positionId };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Confirm the open. Idempotent: if a confirmed entry already exists it is returned.
|
|
74
|
+
* If the position was minted at intent (and the intent is not stale), that id is
|
|
75
|
+
* promoted (reused, never re-minted) and the pending record dropped. Otherwise — an
|
|
76
|
+
* externally-opened position with no prior runtime intent — a fresh id is minted.
|
|
77
|
+
*/
|
|
78
|
+
mintOnOpened(key) {
|
|
79
|
+
const existing = this.entries.get(key);
|
|
80
|
+
if (existing)
|
|
81
|
+
return existing;
|
|
82
|
+
const pending = this.livePending(key);
|
|
83
|
+
const entry = pending
|
|
84
|
+
? { positionId: pending.positionId }
|
|
85
|
+
: { positionId: randomUUID() };
|
|
86
|
+
this.entries.set(key, entry);
|
|
87
|
+
this.pending.delete(key);
|
|
88
|
+
return entry;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read the current entry for a key (INCREASED/DECREASED/FLIPPED, DSL start, recording
|
|
92
|
+
* reads). Surfaces an intent-mint that has not yet been confirmed open, so reads in
|
|
93
|
+
* the open window still resolve the position.id.
|
|
94
|
+
*/
|
|
95
|
+
get(key) {
|
|
96
|
+
const confirmed = this.entries.get(key);
|
|
97
|
+
if (confirmed)
|
|
98
|
+
return confirmed;
|
|
99
|
+
const pending = this.livePending(key);
|
|
100
|
+
return pending ? { positionId: pending.positionId } : undefined;
|
|
101
|
+
}
|
|
102
|
+
/** Clear on confirmed close. The next open on this key mints a fresh id. */
|
|
103
|
+
clearOnClosed(key) {
|
|
104
|
+
this.entries.delete(key);
|
|
105
|
+
this.pending.delete(key);
|
|
106
|
+
}
|
|
107
|
+
/** Repopulate confirmed entries at boot from persisted DSL states. */
|
|
108
|
+
rehydrate(items) {
|
|
109
|
+
for (const item of items) {
|
|
110
|
+
this.entries.set(item.key, item.entry);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The pending record for a key, unless it has gone stale — an intent whose order
|
|
115
|
+
* never filled within the stale window is discarded on read (and a fresh mint
|
|
116
|
+
* follows), so an abandoned submit's id never attaches to a later, unrelated open.
|
|
117
|
+
*/
|
|
118
|
+
livePending(key) {
|
|
119
|
+
const pending = this.pending.get(key);
|
|
120
|
+
if (!pending)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (this.now() - pending.ts > this.staleMs) {
|
|
123
|
+
this.pending.delete(key);
|
|
124
|
+
logger.warn("discarding stale pending position.id (order never confirmed filling); minting fresh", {
|
|
125
|
+
key,
|
|
126
|
+
ageMs: this.now() - pending.ts,
|
|
127
|
+
});
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
return pending;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=position-id-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-id-registry.js","sourceRoot":"","sources":["../../src/tracking/position-id-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-out span transport — ships every span to N child transports.
|
|
3
|
+
*
|
|
4
|
+
* Used during the dual-write phase of the OTEL v2 cutover: the legacy
|
|
5
|
+
* `BatchingSpanTransport` (→ /api/v1/tracking/spans) and the new
|
|
6
|
+
* `RecordingSpanTransport` (→ /v1/traces) both receive every span.
|
|
7
|
+
*
|
|
8
|
+
* Isolation is load-bearing: a throw/rejection in one child must never starve
|
|
9
|
+
* the other, and `enqueue` must never throw back into the SpanTracker (which
|
|
10
|
+
* runs on the hook path). `flush`/`shutdown` await all children and never
|
|
11
|
+
* reject — a failing child is logged and swallowed.
|
|
12
|
+
*/
|
|
13
|
+
import type { SpanTransport, TrackingSpan } from "./types.js";
|
|
14
|
+
export declare class FanOutSpanTransport implements SpanTransport {
|
|
15
|
+
private readonly children;
|
|
16
|
+
constructor(children: SpanTransport[]);
|
|
17
|
+
enqueue(span: TrackingSpan): void;
|
|
18
|
+
flush(): Promise<void>;
|
|
19
|
+
shutdown(): Promise<void>;
|
|
20
|
+
private allSettled;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=fan-out-span-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fan-out-span-transport.d.ts","sourceRoot":"","sources":["../../../src/tracking/spans/fan-out-span-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK9D,qBAAa,mBAAoB,YAAW,aAAa;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,aAAa,EAAE;IAEtD,OAAO,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAY3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAIjB,UAAU;CAazB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-out span transport — ships every span to N child transports.
|
|
3
|
+
*
|
|
4
|
+
* Used during the dual-write phase of the OTEL v2 cutover: the legacy
|
|
5
|
+
* `BatchingSpanTransport` (→ /api/v1/tracking/spans) and the new
|
|
6
|
+
* `RecordingSpanTransport` (→ /v1/traces) both receive every span.
|
|
7
|
+
*
|
|
8
|
+
* Isolation is load-bearing: a throw/rejection in one child must never starve
|
|
9
|
+
* the other, and `enqueue` must never throw back into the SpanTracker (which
|
|
10
|
+
* runs on the hook path). `flush`/`shutdown` await all children and never
|
|
11
|
+
* reject — a failing child is logged and swallowed.
|
|
12
|
+
*/
|
|
13
|
+
import { createLogger } from "../../utils/logger.js";
|
|
14
|
+
const logger = createLogger("[senpi-core][fan-out-span-transport]");
|
|
15
|
+
export class FanOutSpanTransport {
|
|
16
|
+
children;
|
|
17
|
+
constructor(children) {
|
|
18
|
+
this.children = children;
|
|
19
|
+
}
|
|
20
|
+
enqueue(span) {
|
|
21
|
+
for (const child of this.children) {
|
|
22
|
+
try {
|
|
23
|
+
child.enqueue(span);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
logger.warn("child transport enqueue threw; swallowed", {
|
|
27
|
+
err: err instanceof Error ? err.message : String(err),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async flush() {
|
|
33
|
+
await this.allSettled((c) => c.flush(), "flush");
|
|
34
|
+
}
|
|
35
|
+
async shutdown() {
|
|
36
|
+
await this.allSettled((c) => c.shutdown(), "shutdown");
|
|
37
|
+
}
|
|
38
|
+
async allSettled(op, label) {
|
|
39
|
+
const results = await Promise.allSettled(this.children.map((c) => op(c)));
|
|
40
|
+
for (const r of results) {
|
|
41
|
+
if (r.status === "rejected") {
|
|
42
|
+
logger.warn(`child transport ${label} rejected; swallowed`, {
|
|
43
|
+
err: r.reason instanceof Error ? r.reason.message : String(r.reason),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=fan-out-span-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fan-out-span-transport.js","sourceRoot":"","sources":["../../../src/tracking/spans/fan-out-span-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,MAAM,GAAG,YAAY,CAAC,sCAAsC,CAAC,CAAC;AAEpE,MAAM,OAAO,mBAAmB;IACD;IAA7B,YAA6B,QAAyB;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAG,CAAC;IAE1D,OAAO,CAAC,IAAkB;QACxB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE;oBACtD,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACtD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,UAAU,CAAC,CAAC;IACzD,CAAC;IAEO,KAAK,CAAC,UAAU,CACtB,EAA2C,EAC3C,KAAa;QAEb,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1E,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,mBAAmB,KAAK,sBAAsB,EAAE;oBAC1D,GAAG,EAAE,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;iBACrE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure `TrackingSpan → TraceRow` mapper.
|
|
3
|
+
*
|
|
4
|
+
* Maps the agent-span envelope onto the OTEL v2 HyperDX-row shape so agent spans
|
|
5
|
+
* land in `otel_traces` alongside scanner/runtime traces. The caller is expected
|
|
6
|
+
* to have already redacted + truncated `span.data` (see RecordingSpanTransport);
|
|
7
|
+
* this function performs no redaction and no I/O.
|
|
8
|
+
*
|
|
9
|
+
* Bounding: per-field size is the caller's responsibility (truncateSpanData,
|
|
10
|
+
* 2 MB/field) and the batcher's 8 MB body cap — so this does NOT impose the
|
|
11
|
+
* small AttrBuilder caps (4 KB/value), which would gut large LLM payloads.
|
|
12
|
+
*/
|
|
13
|
+
import type { TraceRow } from "../../../recording/transport/rows.js";
|
|
14
|
+
import type { TrackingSpan } from "../types.js";
|
|
15
|
+
export declare function trackingSpanToTraceRow(span: TrackingSpan): TraceRow;
|
|
16
|
+
//# sourceMappingURL=map-span-to-trace-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map-span-to-trace-row.d.ts","sourceRoot":"","sources":["../../../../src/tracking/spans/otel/map-span-to-trace-row.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAwB,MAAM,sCAAsC,CAAC;AAE3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA4EhD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CA8DnE"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure `TrackingSpan → TraceRow` mapper.
|
|
3
|
+
*
|
|
4
|
+
* Maps the agent-span envelope onto the OTEL v2 HyperDX-row shape so agent spans
|
|
5
|
+
* land in `otel_traces` alongside scanner/runtime traces. The caller is expected
|
|
6
|
+
* to have already redacted + truncated `span.data` (see RecordingSpanTransport);
|
|
7
|
+
* this function performs no redaction and no I/O.
|
|
8
|
+
*
|
|
9
|
+
* Bounding: per-field size is the caller's responsibility (truncateSpanData,
|
|
10
|
+
* 2 MB/field) and the batcher's 8 MB body cap — so this does NOT impose the
|
|
11
|
+
* small AttrBuilder caps (4 KB/value), which would gut large LLM payloads.
|
|
12
|
+
*/
|
|
13
|
+
import { msToNs, durationNs } from "../../../recording/otel/attributes.js";
|
|
14
|
+
import { deriveTraceId, deriveSpanId, deriveParentSpanId } from "./span-ids.js";
|
|
15
|
+
const SERVICE_NAME = "senpi-trading-runtime";
|
|
16
|
+
const SCOPE_NAME = "senpi.otel";
|
|
17
|
+
const SCOPE_VERSION = "2.0.0";
|
|
18
|
+
function asRecord(v) {
|
|
19
|
+
return v && typeof v === "object" && !Array.isArray(v)
|
|
20
|
+
? v
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
23
|
+
function nonEmpty(v) {
|
|
24
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Set a string attribute, omitting null/undefined/empty. `0` survives (meaningful). */
|
|
27
|
+
function putStr(map, key, value) {
|
|
28
|
+
if (value === null || value === undefined || value === "")
|
|
29
|
+
return;
|
|
30
|
+
map[key] = String(value);
|
|
31
|
+
}
|
|
32
|
+
/** Set one attribute per top-level `data` key: scalars as String, objects/arrays as JSON. */
|
|
33
|
+
function putDataValue(map, key, value) {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return;
|
|
36
|
+
if (value === null)
|
|
37
|
+
return;
|
|
38
|
+
if (typeof value === "object") {
|
|
39
|
+
try {
|
|
40
|
+
const s = JSON.stringify(value);
|
|
41
|
+
if (s !== undefined)
|
|
42
|
+
map[key] = s;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* unserializable — drop the one key, not the row */
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
map[key] = String(value);
|
|
50
|
+
}
|
|
51
|
+
function spanName(span) {
|
|
52
|
+
const data = asRecord(span.data);
|
|
53
|
+
switch (span.spanType) {
|
|
54
|
+
case "llm":
|
|
55
|
+
return nonEmpty(span.model) ? `llm ${span.model}` : "llm";
|
|
56
|
+
case "tool": {
|
|
57
|
+
const name = nonEmpty(data?.tool_name) ?? nonEmpty(data?.toolName);
|
|
58
|
+
return name ? `tool ${name}` : "tool";
|
|
59
|
+
}
|
|
60
|
+
case "subagent": {
|
|
61
|
+
const agentId = nonEmpty(asRecord(data?.input)?.agentId) ?? nonEmpty(data?.agentId);
|
|
62
|
+
return agentId ? `subagent ${agentId}` : "subagent";
|
|
63
|
+
}
|
|
64
|
+
case "agent_end":
|
|
65
|
+
return "agent.end";
|
|
66
|
+
default:
|
|
67
|
+
return span.spanType;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function spanKind(spanType) {
|
|
71
|
+
return spanType === "llm" || spanType === "tool" ? "Client" : "Internal";
|
|
72
|
+
}
|
|
73
|
+
/** Child run id rides in the delivery patch (subagent_delivery_target) or the spawned patch. */
|
|
74
|
+
function childRunId(data) {
|
|
75
|
+
return (nonEmpty(asRecord(data?.delivery)?.childRunId) ??
|
|
76
|
+
nonEmpty(data?.childRunId) ??
|
|
77
|
+
nonEmpty(data?.runId));
|
|
78
|
+
}
|
|
79
|
+
export function trackingSpanToTraceRow(span) {
|
|
80
|
+
const data = asRecord(span.data);
|
|
81
|
+
const error = nonEmpty(data?.error);
|
|
82
|
+
const address = span.address.toLowerCase();
|
|
83
|
+
const status = error !== undefined ? "Error" : "Ok";
|
|
84
|
+
const resourceAttributes = { "service.name": SERVICE_NAME };
|
|
85
|
+
putStr(resourceAttributes, "strategy.address", address);
|
|
86
|
+
putStr(resourceAttributes, "recipe.name", span.recipeName);
|
|
87
|
+
putStr(resourceAttributes, "runtime.id", span.runtimeId);
|
|
88
|
+
putStr(resourceAttributes, "runtime.version", span.runtimeVersion);
|
|
89
|
+
// user.id is intentionally NOT set — the /v1/traces route server-stamps it.
|
|
90
|
+
const spanAttributes = {};
|
|
91
|
+
putStr(spanAttributes, "span.type", span.spanType);
|
|
92
|
+
putStr(spanAttributes, "emission.source", span.emissionSource);
|
|
93
|
+
putStr(spanAttributes, "session.key", span.sessionKey);
|
|
94
|
+
putStr(spanAttributes, "run.id", span.runId);
|
|
95
|
+
putStr(spanAttributes, "correlation.id", span.correlationId);
|
|
96
|
+
putStr(spanAttributes, "child.session.key", span.childSessionKey);
|
|
97
|
+
putStr(spanAttributes, "tool.call_id", span.toolCallId);
|
|
98
|
+
putStr(spanAttributes, "tool.name", nonEmpty(data?.tool_name) ?? nonEmpty(data?.toolName));
|
|
99
|
+
putStr(spanAttributes, "gen_ai.request.model", span.model);
|
|
100
|
+
putStr(spanAttributes, "gen_ai.usage.input_tokens", span.tokensIn);
|
|
101
|
+
putStr(spanAttributes, "gen_ai.usage.output_tokens", span.tokensOut);
|
|
102
|
+
putStr(spanAttributes, "cost.usd", span.costUsd);
|
|
103
|
+
// Mega-trace breadcrumb: lets a downstream view stitch the subagent's own
|
|
104
|
+
// (separate) trace back onto the host. child.session.key is always present;
|
|
105
|
+
// child.run_id depends on the spawned/delivery hooks having fired.
|
|
106
|
+
if (span.spanType === "subagent") {
|
|
107
|
+
putStr(spanAttributes, "subagent.child_session_key", span.childSessionKey);
|
|
108
|
+
putStr(spanAttributes, "subagent.child_run_id", childRunId(data));
|
|
109
|
+
}
|
|
110
|
+
// Flatten the payload bag one level: each top-level data key → one attribute.
|
|
111
|
+
if (data) {
|
|
112
|
+
for (const [k, v] of Object.entries(data)) {
|
|
113
|
+
putDataValue(spanAttributes, `data.${k}`, v);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
Timestamp: msToNs(span.startedAt),
|
|
118
|
+
TraceId: deriveTraceId(span.runId),
|
|
119
|
+
SpanId: deriveSpanId(span.id),
|
|
120
|
+
ParentSpanId: deriveParentSpanId(span.parentSpanId),
|
|
121
|
+
TraceState: "",
|
|
122
|
+
SpanName: spanName(span),
|
|
123
|
+
SpanKind: spanKind(span.spanType),
|
|
124
|
+
ServiceName: SERVICE_NAME,
|
|
125
|
+
Duration: durationNs(span.startedAt, span.endedAt),
|
|
126
|
+
StatusCode: status,
|
|
127
|
+
StatusMessage: error ?? "",
|
|
128
|
+
SpanAttributes: spanAttributes,
|
|
129
|
+
ResourceAttributes: resourceAttributes,
|
|
130
|
+
Events: [],
|
|
131
|
+
ScopeName: SCOPE_NAME,
|
|
132
|
+
ScopeVersion: SCOPE_VERSION,
|
|
133
|
+
TraceFlags: 1,
|
|
134
|
+
address,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=map-span-to-trace-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map-span-to-trace-row.js","sourceRoot":"","sources":["../../../../src/tracking/spans/otel/map-span-to-trace-row.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecordingSpanTransport — ships agent spans to the OTEL v2 `/v1/traces` path.
|
|
3
|
+
*
|
|
4
|
+
* Implements the same `SpanTransport` seam as `BatchingSpanTransport`, but maps
|
|
5
|
+
* each `TrackingSpan` to a HyperDX `TraceRow` and enqueues it on a dedicated
|
|
6
|
+
* recording `TelemetryBatcher` (one per gateway — built at plugin register(),
|
|
7
|
+
* not borrowed from a per-runtime batcher).
|
|
8
|
+
*
|
|
9
|
+
* Pipeline per span (fire-and-forget, never throws into the SpanTracker):
|
|
10
|
+
* truncate oversized data fields → redact PII → map to TraceRow → enqueueTrace
|
|
11
|
+
*
|
|
12
|
+
* Truncation + redaction mirror BatchingSpanTransport so the new path never
|
|
13
|
+
* ships unredacted or oversized payloads. The recording batcher serializes at
|
|
14
|
+
* enqueue, so (unlike the legacy transport) both run BEFORE the row is built.
|
|
15
|
+
*
|
|
16
|
+
* `flush`/`shutdown` first await any in-flight per-span processing, then the
|
|
17
|
+
* batcher — so a flush is deterministic (no row stranded mid-pipeline).
|
|
18
|
+
*/
|
|
19
|
+
import type { SpanTransport, TrackingSpan } from "../types.js";
|
|
20
|
+
import type { TelemetryBatcher } from "../../../recording/transport/index.js";
|
|
21
|
+
import { type PiiRedactor } from "../redact-pii.js";
|
|
22
|
+
export interface RecordingSpanTransportOptions {
|
|
23
|
+
batcher: TelemetryBatcher;
|
|
24
|
+
/**
|
|
25
|
+
* PII redactor. Omit (`undefined`) to look up the process-wide default at use
|
|
26
|
+
* time (handles async redactor init); pass `null` to disable redaction
|
|
27
|
+
* explicitly (tests/local dev).
|
|
28
|
+
*/
|
|
29
|
+
redactor?: PiiRedactor | null;
|
|
30
|
+
}
|
|
31
|
+
export declare class RecordingSpanTransport implements SpanTransport {
|
|
32
|
+
private readonly batcher;
|
|
33
|
+
private readonly redactorOverride;
|
|
34
|
+
private readonly pending;
|
|
35
|
+
constructor(options: RecordingSpanTransportOptions);
|
|
36
|
+
enqueue(span: TrackingSpan): void;
|
|
37
|
+
flush(): Promise<void>;
|
|
38
|
+
shutdown(): Promise<void>;
|
|
39
|
+
/** Truncate → redact → map → enqueue. Self-contained; never throws. */
|
|
40
|
+
private process;
|
|
41
|
+
private drainPending;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=recording-span-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recording-span-transport.d.ts","sourceRoot":"","sources":["../../../../src/tracking/spans/otel/recording-span-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,EACL,KAAK,WAAW,EAGjB,MAAM,kBAAkB,CAAC;AAM1B,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,sBAAuB,YAAW,aAAa;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiC;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;gBAExC,OAAO,EAAE,6BAA6B;IAKlD,OAAO,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAK3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B,uEAAuE;YACzD,OAAO;YAgBP,YAAY;CAG3B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecordingSpanTransport — ships agent spans to the OTEL v2 `/v1/traces` path.
|
|
3
|
+
*
|
|
4
|
+
* Implements the same `SpanTransport` seam as `BatchingSpanTransport`, but maps
|
|
5
|
+
* each `TrackingSpan` to a HyperDX `TraceRow` and enqueues it on a dedicated
|
|
6
|
+
* recording `TelemetryBatcher` (one per gateway — built at plugin register(),
|
|
7
|
+
* not borrowed from a per-runtime batcher).
|
|
8
|
+
*
|
|
9
|
+
* Pipeline per span (fire-and-forget, never throws into the SpanTracker):
|
|
10
|
+
* truncate oversized data fields → redact PII → map to TraceRow → enqueueTrace
|
|
11
|
+
*
|
|
12
|
+
* Truncation + redaction mirror BatchingSpanTransport so the new path never
|
|
13
|
+
* ships unredacted or oversized payloads. The recording batcher serializes at
|
|
14
|
+
* enqueue, so (unlike the legacy transport) both run BEFORE the row is built.
|
|
15
|
+
*
|
|
16
|
+
* `flush`/`shutdown` first await any in-flight per-span processing, then the
|
|
17
|
+
* batcher — so a flush is deterministic (no row stranded mid-pipeline).
|
|
18
|
+
*/
|
|
19
|
+
import { truncateSpanData } from "../span-transport.js";
|
|
20
|
+
import { getDefaultPiiRedactor, redactSpanData, } from "../redact-pii.js";
|
|
21
|
+
import { trackingSpanToTraceRow } from "./map-span-to-trace-row.js";
|
|
22
|
+
import { createLogger } from "../../../utils/logger.js";
|
|
23
|
+
const logger = createLogger("[senpi-core][recording-span-transport]");
|
|
24
|
+
export class RecordingSpanTransport {
|
|
25
|
+
batcher;
|
|
26
|
+
redactorOverride;
|
|
27
|
+
pending = new Set();
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.batcher = options.batcher;
|
|
30
|
+
this.redactorOverride = options.redactor;
|
|
31
|
+
}
|
|
32
|
+
enqueue(span) {
|
|
33
|
+
const p = this.process(span).finally(() => this.pending.delete(p));
|
|
34
|
+
this.pending.add(p);
|
|
35
|
+
}
|
|
36
|
+
async flush() {
|
|
37
|
+
await this.drainPending();
|
|
38
|
+
await this.batcher.flush();
|
|
39
|
+
}
|
|
40
|
+
async shutdown() {
|
|
41
|
+
await this.drainPending();
|
|
42
|
+
await this.batcher.shutdown();
|
|
43
|
+
}
|
|
44
|
+
/** Truncate → redact → map → enqueue. Self-contained; never throws. */
|
|
45
|
+
async process(span) {
|
|
46
|
+
try {
|
|
47
|
+
const truncated = truncateSpanData(span);
|
|
48
|
+
const redactor = this.redactorOverride === undefined
|
|
49
|
+
? getDefaultPiiRedactor()
|
|
50
|
+
: this.redactorOverride;
|
|
51
|
+
const cleaned = redactor ? await redactSpanData(truncated, redactor) : truncated;
|
|
52
|
+
this.batcher.enqueueTrace(trackingSpanToTraceRow(cleaned));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
logger.warn("span → trace-row pipeline failed; dropped", {
|
|
56
|
+
err: err instanceof Error ? err.message : String(err),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async drainPending() {
|
|
61
|
+
await Promise.allSettled([...this.pending]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=recording-span-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recording-span-transport.js","sourceRoot":"","sources":["../../../../src/tracking/spans/otel/recording-span-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAEL,qBAAqB,EACrB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,MAAM,MAAM,GAAG,YAAY,CAAC,wCAAwC,CAAC,CAAC;AAYtE,MAAM,OAAO,sBAAsB;IAChB,OAAO,CAAmB;IAC1B,gBAAgB,CAAiC;IACjD,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEpD,YAAY,OAAsC;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC3C,CAAC;IAED,OAAO,CAAC,IAAkB;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAED,uEAAuE;IAC/D,KAAK,CAAC,OAAO,CAAC,IAAkB;QACtC,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,QAAQ,GACZ,IAAI,CAAC,gBAAgB,KAAK,SAAS;gBACjC,CAAC,CAAC,qBAAqB,EAAE;gBACzB,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;YAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACjF,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,2CAA2C,EAAE;gBACvD,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACtD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY;QACxB,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic OTEL id derivation for agent spans.
|
|
3
|
+
*
|
|
4
|
+
* Unlike scanner spans (each its own trace, random ids via `cryptoIdGenerator`),
|
|
5
|
+
* agent spans must group by run and preserve their parent/child tree. So ids are
|
|
6
|
+
* *derived* — a hash of the stable source key — rather than random:
|
|
7
|
+
*
|
|
8
|
+
* - `TraceId` = hash(runId) → every span in one agent run shares it
|
|
9
|
+
* - `SpanId` = hash(span.id)
|
|
10
|
+
* - `ParentSpanId` = hash(span.parentSpanId) ("" stays "" for roots)
|
|
11
|
+
*
|
|
12
|
+
* A child's `parentSpanId` holds the parent span's raw id, so
|
|
13
|
+
* `deriveParentSpanId(child.parentSpanId)` equals `deriveSpanId(parent.id)` —
|
|
14
|
+
* the link reconstructs in HyperDX.
|
|
15
|
+
*/
|
|
16
|
+
/** 32 lowercase-hex chars (16 bytes) derived from the agent run id. */
|
|
17
|
+
export declare function deriveTraceId(runId: string): string;
|
|
18
|
+
/** 16 lowercase-hex chars (8 bytes) derived from the span's raw id. */
|
|
19
|
+
export declare function deriveSpanId(id: string): string;
|
|
20
|
+
/** Derived parent SpanId; empty string (root) passes through unchanged. */
|
|
21
|
+
export declare function deriveParentSpanId(parentSpanId: string): string;
|
|
22
|
+
//# sourceMappingURL=span-ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-ids.d.ts","sourceRoot":"","sources":["../../../../src/tracking/spans/otel/span-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE/D"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic OTEL id derivation for agent spans.
|
|
3
|
+
*
|
|
4
|
+
* Unlike scanner spans (each its own trace, random ids via `cryptoIdGenerator`),
|
|
5
|
+
* agent spans must group by run and preserve their parent/child tree. So ids are
|
|
6
|
+
* *derived* — a hash of the stable source key — rather than random:
|
|
7
|
+
*
|
|
8
|
+
* - `TraceId` = hash(runId) → every span in one agent run shares it
|
|
9
|
+
* - `SpanId` = hash(span.id)
|
|
10
|
+
* - `ParentSpanId` = hash(span.parentSpanId) ("" stays "" for roots)
|
|
11
|
+
*
|
|
12
|
+
* A child's `parentSpanId` holds the parent span's raw id, so
|
|
13
|
+
* `deriveParentSpanId(child.parentSpanId)` equals `deriveSpanId(parent.id)` —
|
|
14
|
+
* the link reconstructs in HyperDX.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
function hashHex(input, bytes) {
|
|
18
|
+
return createHash("sha256").update(input).digest("hex").slice(0, bytes * 2);
|
|
19
|
+
}
|
|
20
|
+
/** 32 lowercase-hex chars (16 bytes) derived from the agent run id. */
|
|
21
|
+
export function deriveTraceId(runId) {
|
|
22
|
+
return hashHex(runId, 16);
|
|
23
|
+
}
|
|
24
|
+
/** 16 lowercase-hex chars (8 bytes) derived from the span's raw id. */
|
|
25
|
+
export function deriveSpanId(id) {
|
|
26
|
+
return hashHex(id, 8);
|
|
27
|
+
}
|
|
28
|
+
/** Derived parent SpanId; empty string (root) passes through unchanged. */
|
|
29
|
+
export function deriveParentSpanId(parentSpanId) {
|
|
30
|
+
return parentSpanId === "" ? "" : deriveSpanId(parentSpanId);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=span-ids.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-ids.js","sourceRoot":"","sources":["../../../../src/tracking/spans/otel/span-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,SAAS,OAAO,CAAC,KAAa,EAAE,KAAa;IAC3C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAO,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,YAAoB;IACrD,OAAO,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import { SpanTracker } from "./span-tracker.js";
|
|
2
|
+
import type { SpanTransport } from "./types.js";
|
|
3
|
+
export type SpanSinkMode = "legacy" | "dual" | "otel";
|
|
4
|
+
/** Parse `SENPI_SPANS_SINK`; unknown/unset → `dual` (the validation default). */
|
|
5
|
+
export declare function resolveSpanSinkMode(value: string | undefined): SpanSinkMode;
|
|
6
|
+
/** Compose the active transport from the two children per the sink mode. */
|
|
7
|
+
export declare function composeSpanTransport(mode: SpanSinkMode, legacy: SpanTransport, recording: SpanTransport): SpanTransport;
|
|
2
8
|
export declare function createPluginSpanTracker(apiKey: string | undefined): SpanTracker;
|
|
3
9
|
//# sourceMappingURL=plugin-span-tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-span-tracker.d.ts","sourceRoot":"","sources":["../../../src/tracking/spans/plugin-span-tracker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin-span-tracker.d.ts","sourceRoot":"","sources":["../../../src/tracking/spans/plugin-span-tracker.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAShD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtD,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAI3E;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,aAAa,GACvB,aAAa,CASf;AAgCD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,WAAW,CA2Bb"}
|