trodo-node 1.2.0 → 2.2.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 (64) hide show
  1. package/README.md +182 -2
  2. package/dist/cjs/TrodoClient.js +85 -102
  3. package/dist/cjs/TrodoClient.js.map +1 -1
  4. package/dist/cjs/api/ApiClient.js +20 -2
  5. package/dist/cjs/api/ApiClient.js.map +1 -1
  6. package/dist/cjs/api/endpoints.js +7 -1
  7. package/dist/cjs/api/endpoints.js.map +1 -1
  8. package/dist/cjs/index.js +115 -29
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/otel/autoInstrument.js +253 -0
  11. package/dist/cjs/otel/autoInstrument.js.map +1 -0
  12. package/dist/cjs/otel/context.js +49 -0
  13. package/dist/cjs/otel/context.js.map +1 -0
  14. package/dist/cjs/otel/helpers.js +254 -0
  15. package/dist/cjs/otel/helpers.js.map +1 -0
  16. package/dist/cjs/otel/processor.js +153 -0
  17. package/dist/cjs/otel/processor.js.map +1 -0
  18. package/dist/cjs/otel/uuid.js +24 -0
  19. package/dist/cjs/otel/uuid.js.map +1 -0
  20. package/dist/cjs/otel/wrapAgent.js +460 -0
  21. package/dist/cjs/otel/wrapAgent.js.map +1 -0
  22. package/dist/esm/TrodoClient.d.ts +44 -11
  23. package/dist/esm/TrodoClient.d.ts.map +1 -1
  24. package/dist/esm/TrodoClient.js +85 -102
  25. package/dist/esm/TrodoClient.js.map +1 -1
  26. package/dist/esm/api/ApiClient.d.ts +9 -1
  27. package/dist/esm/api/ApiClient.d.ts.map +1 -1
  28. package/dist/esm/api/ApiClient.js +20 -2
  29. package/dist/esm/api/ApiClient.js.map +1 -1
  30. package/dist/esm/api/endpoints.d.ts +6 -1
  31. package/dist/esm/api/endpoints.d.ts.map +1 -1
  32. package/dist/esm/api/endpoints.js +7 -1
  33. package/dist/esm/api/endpoints.js.map +1 -1
  34. package/dist/esm/index.d.ts +96 -19
  35. package/dist/esm/index.d.ts.map +1 -1
  36. package/dist/esm/index.js +98 -23
  37. package/dist/esm/index.js.map +1 -1
  38. package/dist/esm/otel/autoInstrument.d.ts +61 -0
  39. package/dist/esm/otel/autoInstrument.d.ts.map +1 -0
  40. package/dist/esm/otel/autoInstrument.js +248 -0
  41. package/dist/esm/otel/autoInstrument.js.map +1 -0
  42. package/dist/esm/otel/context.d.ts +26 -0
  43. package/dist/esm/otel/context.d.ts.map +1 -0
  44. package/dist/esm/otel/context.js +44 -0
  45. package/dist/esm/otel/context.js.map +1 -0
  46. package/dist/esm/otel/helpers.d.ts +119 -0
  47. package/dist/esm/otel/helpers.d.ts.map +1 -0
  48. package/dist/esm/otel/helpers.js +244 -0
  49. package/dist/esm/otel/helpers.js.map +1 -0
  50. package/dist/esm/otel/processor.d.ts +104 -0
  51. package/dist/esm/otel/processor.d.ts.map +1 -0
  52. package/dist/esm/otel/processor.js +149 -0
  53. package/dist/esm/otel/processor.js.map +1 -0
  54. package/dist/esm/otel/uuid.d.ts +7 -0
  55. package/dist/esm/otel/uuid.d.ts.map +1 -0
  56. package/dist/esm/otel/uuid.js +21 -0
  57. package/dist/esm/otel/uuid.js.map +1 -0
  58. package/dist/esm/otel/wrapAgent.d.ts +133 -0
  59. package/dist/esm/otel/wrapAgent.d.ts.map +1 -0
  60. package/dist/esm/otel/wrapAgent.js +448 -0
  61. package/dist/esm/otel/wrapAgent.js.map +1 -0
  62. package/dist/esm/types/index.d.ts +24 -48
  63. package/dist/esm/types/index.d.ts.map +1 -1
  64. package/package.json +1 -1
@@ -0,0 +1,119 @@
1
+ import { type WithSpanOptions } from './wrapAgent.js';
2
+ import type { TrodoSpanProcessor } from './processor.js';
3
+ export interface ToolOptions {
4
+ name?: string;
5
+ kind?: 'tool' | 'retrieval' | 'generic';
6
+ }
7
+ type AnyFn<Args extends unknown[], R> = (...args: Args) => Promise<R> | R;
8
+ /**
9
+ * Wrap a function as a tool span — supports two forms:
10
+ *
11
+ * // name-first (uselemma-compatible):
12
+ * const fetchUser = trodo.tool('fetch_user', async (uid) => { ... });
13
+ *
14
+ * // function-first (backward-compatible):
15
+ * const fetchUser = trodo.tool(async (uid) => { ... }, { name: 'fetch_user' });
16
+ */
17
+ export declare function tool<Args extends unknown[], R>(name: string, fn: AnyFn<Args, R>): (...args: Args) => Promise<R>;
18
+ export declare function tool<Args extends unknown[], R>(fn: AnyFn<Args, R>, options?: ToolOptions): (...args: Args) => Promise<R>;
19
+ /**
20
+ * Wrap a function as a generic span. Mirrors the uselemma `trace()` helper.
21
+ *
22
+ * const prepare = trodo.trace('prepare', async (input) => { ... });
23
+ */
24
+ export declare function trace<Args extends unknown[], R>(name: string, fn: AnyFn<Args, R>, options?: WithSpanOptions): (...args: Args) => Promise<R>;
25
+ /** Wrap a retriever / vector search as a kind='retrieval' span. */
26
+ export declare function retrieval<Args extends unknown[], R>(name: string, fn: AnyFn<Args, R>, options?: WithSpanOptions): (...args: Args) => Promise<R>;
27
+ export interface LlmOptions extends WithSpanOptions {
28
+ model?: string;
29
+ provider?: string;
30
+ temperature?: number;
31
+ extractUsage?: (result: unknown) => {
32
+ inputTokens?: number;
33
+ outputTokens?: number;
34
+ };
35
+ }
36
+ /**
37
+ * Wrap an LLM call — auto-extracts tokens from OpenAI/Anthropic/Gemini
38
+ * response shapes.
39
+ *
40
+ * const answer = trodo.llm('answer', callOpenAI, {
41
+ * model: 'gpt-4o-mini', provider: 'openai',
42
+ * });
43
+ */
44
+ export declare function llm<Args extends unknown[], R>(name: string, fn: AnyFn<Args, R>, options?: LlmOptions): (...args: Args) => Promise<R>;
45
+ /**
46
+ * Return a raw OTel tracer — the Trodo processor is already subscribed.
47
+ * Use for advanced cases where you want to emit spans with the raw OTel
48
+ * API; they flow through to Trodo like any other span.
49
+ */
50
+ export declare function getTracer(name?: string): unknown;
51
+ export interface TrackLlmCallParams {
52
+ model?: string;
53
+ provider?: string;
54
+ inputTokens?: number;
55
+ outputTokens?: number;
56
+ prompt?: unknown;
57
+ completion?: unknown;
58
+ temperature?: number;
59
+ cost?: number;
60
+ name?: string;
61
+ metadata?: Record<string, unknown>;
62
+ }
63
+ /**
64
+ * Record a one-shot LLM span for a raw-HTTP caller. Opens and immediately
65
+ * closes a span(kind='llm') populated with the model + token counts +
66
+ * prompt/completion. No-op outside an active run context.
67
+ *
68
+ * Usage:
69
+ * const resp = await fetch(url, { body: JSON.stringify(body) }).then(r => r.json());
70
+ * await trodo.trackLlmCall({
71
+ * model: 'gemini-2.5-flash',
72
+ * provider: 'google',
73
+ * inputTokens: resp.usageMetadata.promptTokenCount,
74
+ * outputTokens: resp.usageMetadata.candidatesTokenCount,
75
+ * prompt: body,
76
+ * completion: resp,
77
+ * });
78
+ */
79
+ export declare function trackLlmCall(params: TrackLlmCallParams): Promise<void>;
80
+ /** Minimal subset of Express Request/Response/NextFn we actually touch. */
81
+ interface ExpressReq {
82
+ headers: Record<string, string | string[] | undefined>;
83
+ method?: string;
84
+ url?: string;
85
+ originalUrl?: string;
86
+ path?: string;
87
+ }
88
+ interface ExpressRes {
89
+ statusCode?: number;
90
+ on?: (event: string, cb: () => void) => void;
91
+ }
92
+ type NextFn = (err?: unknown) => void;
93
+ export interface ExpressMiddlewareDeps {
94
+ processor: TrodoSpanProcessor;
95
+ teamSiteId: string;
96
+ }
97
+ /**
98
+ * Return an Express/Connect middleware that auto-joins inbound runs.
99
+ *
100
+ * If the inbound request has X-Trodo-Run-Id, every span created while
101
+ * handling the request nests under the caller's run. Otherwise the
102
+ * handler runs with no active context (no-op tracking).
103
+ *
104
+ * Usage:
105
+ * import express from 'express';
106
+ * import trodo from 'trodo-node';
107
+ * trodo.init({ siteId: '...' });
108
+ * const app = express();
109
+ * app.use(trodo.expressMiddleware());
110
+ */
111
+ export declare function expressMiddleware(deps: ExpressMiddlewareDeps): (req: ExpressReq, res: ExpressRes, next: NextFn) => void;
112
+ /**
113
+ * Return HTTP headers carrying the current run/span context. Use when
114
+ * making outbound HTTP calls to downstream services so they can joinRun
115
+ * instead of creating their own runs.
116
+ */
117
+ export declare function propagationHeaders(): Record<string, string>;
118
+ export {};
119
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/otel/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Span helpers: `trace`, `tool`, `llm`, `retrieval` — typed function
3
+ * wrappers that auto-capture input/output/error. Plus `trackLlmCall` and
4
+ * Express middleware for cross-service run joining.
5
+ *
6
+ * These are the customer-facing surface for custom code: wrap once, get a
7
+ * span with args as input, return value as output, exception as error.
8
+ */
9
+ import { getActiveContext } from './context.js';
10
+ import { joinRun, withSpan, } from './wrapAgent.js';
11
+ function _input(args) {
12
+ if (args.length === 0)
13
+ return undefined;
14
+ if (args.length === 1)
15
+ return args[0];
16
+ return args;
17
+ }
18
+ function _wrap(name, fn, kind, options = {}, hooks) {
19
+ return async (...args) => {
20
+ return withSpan(undefined, name, async (span) => {
21
+ if (kind === 'tool')
22
+ span.setTool(name);
23
+ hooks?.onEnter?.(span);
24
+ const result = await fn(...args);
25
+ try {
26
+ hooks?.onResult?.(span, result);
27
+ }
28
+ catch {
29
+ /* never break user code */
30
+ }
31
+ span.setOutput(result);
32
+ return result;
33
+ }, { kind, input: _input(args), ...options });
34
+ };
35
+ }
36
+ export function tool(a, b) {
37
+ // name-first form: tool(name, fn)
38
+ if (typeof a === 'string' && typeof b === 'function') {
39
+ return _wrap(a, b, 'tool');
40
+ }
41
+ // fn-first form: tool(fn, options?)
42
+ const fn = a;
43
+ const options = (b || {});
44
+ const toolName = options.name || fn.name || 'anonymous-tool';
45
+ const kind = (options.kind || 'tool');
46
+ return _wrap(toolName, fn, kind);
47
+ }
48
+ /**
49
+ * Wrap a function as a generic span. Mirrors the uselemma `trace()` helper.
50
+ *
51
+ * const prepare = trodo.trace('prepare', async (input) => { ... });
52
+ */
53
+ export function trace(name, fn, options = {}) {
54
+ return _wrap(name, fn, 'generic', options);
55
+ }
56
+ /** Wrap a retriever / vector search as a kind='retrieval' span. */
57
+ export function retrieval(name, fn, options = {}) {
58
+ return _wrap(name, fn, 'retrieval', options);
59
+ }
60
+ function _defaultExtractUsage(result) {
61
+ if (!result || typeof result !== 'object')
62
+ return {};
63
+ const r = result;
64
+ // OpenAI / OpenAI-compat: { usage: { prompt_tokens, completion_tokens } }
65
+ const usage = r.usage;
66
+ if (usage) {
67
+ if ('prompt_tokens' in usage || 'completion_tokens' in usage) {
68
+ return {
69
+ inputTokens: usage.prompt_tokens,
70
+ outputTokens: usage.completion_tokens,
71
+ };
72
+ }
73
+ // Anthropic: { usage: { input_tokens, output_tokens } }
74
+ if ('input_tokens' in usage || 'output_tokens' in usage) {
75
+ return {
76
+ inputTokens: usage.input_tokens,
77
+ outputTokens: usage.output_tokens,
78
+ };
79
+ }
80
+ }
81
+ // Gemini: { usageMetadata: { promptTokenCount, candidatesTokenCount } }
82
+ const meta = r.usageMetadata;
83
+ if (meta) {
84
+ return {
85
+ inputTokens: meta.promptTokenCount,
86
+ outputTokens: meta.candidatesTokenCount,
87
+ };
88
+ }
89
+ return {};
90
+ }
91
+ /**
92
+ * Wrap an LLM call — auto-extracts tokens from OpenAI/Anthropic/Gemini
93
+ * response shapes.
94
+ *
95
+ * const answer = trodo.llm('answer', callOpenAI, {
96
+ * model: 'gpt-4o-mini', provider: 'openai',
97
+ * });
98
+ */
99
+ export function llm(name, fn, options = {}) {
100
+ const { model, provider, temperature, extractUsage, ...rest } = options;
101
+ const extractor = extractUsage || _defaultExtractUsage;
102
+ return _wrap(name, fn, 'llm', rest, {
103
+ onEnter: (s) => {
104
+ if (model || provider || temperature !== undefined) {
105
+ s.setLlm({ model, provider, temperature });
106
+ }
107
+ },
108
+ onResult: (s, result) => {
109
+ const { inputTokens, outputTokens } = extractor(result);
110
+ if (inputTokens !== undefined || outputTokens !== undefined) {
111
+ s.setLlm({
112
+ model,
113
+ provider,
114
+ inputTokens,
115
+ outputTokens,
116
+ temperature,
117
+ });
118
+ }
119
+ },
120
+ });
121
+ }
122
+ /**
123
+ * Return a raw OTel tracer — the Trodo processor is already subscribed.
124
+ * Use for advanced cases where you want to emit spans with the raw OTel
125
+ * API; they flow through to Trodo like any other span.
126
+ */
127
+ export function getTracer(name = 'trodo') {
128
+ // Lazy import keeps @opentelemetry/api optional at runtime until used.
129
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
130
+ const api = require('@opentelemetry/api');
131
+ return api.trace.getTracer(name);
132
+ }
133
+ /**
134
+ * Record a one-shot LLM span for a raw-HTTP caller. Opens and immediately
135
+ * closes a span(kind='llm') populated with the model + token counts +
136
+ * prompt/completion. No-op outside an active run context.
137
+ *
138
+ * Usage:
139
+ * const resp = await fetch(url, { body: JSON.stringify(body) }).then(r => r.json());
140
+ * await trodo.trackLlmCall({
141
+ * model: 'gemini-2.5-flash',
142
+ * provider: 'google',
143
+ * inputTokens: resp.usageMetadata.promptTokenCount,
144
+ * outputTokens: resp.usageMetadata.candidatesTokenCount,
145
+ * prompt: body,
146
+ * completion: resp,
147
+ * });
148
+ */
149
+ export async function trackLlmCall(params) {
150
+ if (!getActiveContext())
151
+ return;
152
+ const spanName = params.name || (params.model ? `llm.${params.provider}.${params.model}` : 'llm');
153
+ await withSpan(undefined, spanName, async (span) => {
154
+ span.setLlm({
155
+ model: params.model,
156
+ provider: params.provider,
157
+ inputTokens: params.inputTokens,
158
+ outputTokens: params.outputTokens,
159
+ cost: params.cost,
160
+ temperature: params.temperature,
161
+ });
162
+ if (params.completion !== undefined) {
163
+ span.setOutput(params.completion);
164
+ }
165
+ }, {
166
+ kind: 'llm',
167
+ input: params.prompt,
168
+ attributes: params.metadata,
169
+ });
170
+ }
171
+ // ---------------------------------------------------------------------------
172
+ // Express / Connect middleware for automatic cross-service run joining
173
+ // ---------------------------------------------------------------------------
174
+ const TRODO_RUN_HEADER = 'x-trodo-run-id';
175
+ const TRODO_PARENT_SPAN_HEADER = 'x-trodo-parent-span-id';
176
+ const TRODO_AGENT_HEADER = 'x-trodo-agent-name';
177
+ /**
178
+ * Return an Express/Connect middleware that auto-joins inbound runs.
179
+ *
180
+ * If the inbound request has X-Trodo-Run-Id, every span created while
181
+ * handling the request nests under the caller's run. Otherwise the
182
+ * handler runs with no active context (no-op tracking).
183
+ *
184
+ * Usage:
185
+ * import express from 'express';
186
+ * import trodo from 'trodo-node';
187
+ * trodo.init({ siteId: '...' });
188
+ * const app = express();
189
+ * app.use(trodo.expressMiddleware());
190
+ */
191
+ export function expressMiddleware(deps) {
192
+ const { processor, teamSiteId } = deps;
193
+ return (req, res, next) => {
194
+ const headers = req.headers || {};
195
+ const getHeader = (name) => {
196
+ const raw = headers[name] ?? headers[name.toLowerCase()];
197
+ if (!raw)
198
+ return undefined;
199
+ return Array.isArray(raw) ? raw[0] : raw;
200
+ };
201
+ const runId = getHeader(TRODO_RUN_HEADER);
202
+ if (!runId) {
203
+ next();
204
+ return;
205
+ }
206
+ const parentSpanId = getHeader(TRODO_PARENT_SPAN_HEADER) || null;
207
+ const agentHeader = getHeader(TRODO_AGENT_HEADER);
208
+ const name = agentHeader ||
209
+ `http.${(req.method || 'GET').toUpperCase()}.${req.path || req.url || ''}`;
210
+ const input = {
211
+ method: req.method,
212
+ path: req.path || req.url || req.originalUrl,
213
+ };
214
+ joinRun(processor, teamSiteId, runId, parentSpanId, async (span) => {
215
+ await new Promise((resolve) => {
216
+ if (res.on) {
217
+ res.on('finish', () => {
218
+ span.setOutput({ status_code: res.statusCode });
219
+ resolve();
220
+ });
221
+ res.on('close', () => resolve());
222
+ }
223
+ next();
224
+ });
225
+ }, { name, kind: 'agent', input }).catch(() => {
226
+ /* swallow — SDK never throws into user code */
227
+ });
228
+ };
229
+ }
230
+ /**
231
+ * Return HTTP headers carrying the current run/span context. Use when
232
+ * making outbound HTTP calls to downstream services so they can joinRun
233
+ * instead of creating their own runs.
234
+ */
235
+ export function propagationHeaders() {
236
+ const ctx = getActiveContext();
237
+ if (!ctx)
238
+ return {};
239
+ const headers = { 'X-Trodo-Run-Id': ctx.runId };
240
+ if (ctx.spanId)
241
+ headers['X-Trodo-Parent-Span-Id'] = ctx.spanId;
242
+ return headers;
243
+ }
244
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/otel/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * TrodoSpanProcessor — buffers completed spans and ships them to the Trodo
3
+ * backend in batches.
4
+ *
5
+ * Shape is compatible with OpenTelemetry's SpanProcessor (onStart / onEnd /
6
+ * shutdown / forceFlush) so users can register it with the OTel NodeSDK and
7
+ * have auto-instrumented spans (Anthropic/OpenAI/Vercel AI SDK/LangChain)
8
+ * flow through it into Trodo.
9
+ *
10
+ * Without a real OTel SDK, our wrapAgent/withSpan helpers call onStart/onEnd
11
+ * directly — so the processor handles both paths uniformly.
12
+ */
13
+ import type { ApiClient } from '../api/ApiClient.js';
14
+ export interface TrodoSpan {
15
+ span_id: string;
16
+ run_id: string;
17
+ parent_span_id: string | null;
18
+ kind: 'llm' | 'tool' | 'agent' | 'retrieval' | 'generic';
19
+ name: string;
20
+ status: 'ok' | 'error';
21
+ started_at: string;
22
+ ended_at?: string;
23
+ duration_ms?: number;
24
+ input?: string;
25
+ output?: string;
26
+ error_type?: string;
27
+ error_message?: string;
28
+ model?: string;
29
+ provider?: string;
30
+ input_tokens?: number;
31
+ output_tokens?: number;
32
+ cost?: number;
33
+ temperature?: number;
34
+ tool_name?: string;
35
+ attributes?: Record<string, unknown>;
36
+ }
37
+ export interface TrodoRun {
38
+ run_id: string;
39
+ agent_name: string;
40
+ distinct_id?: string | null;
41
+ conversation_id?: string | null;
42
+ parent_run_id?: string | null;
43
+ status: 'running' | 'ok' | 'error';
44
+ input?: string;
45
+ output?: string;
46
+ error_summary?: string;
47
+ started_at: string;
48
+ ended_at?: string;
49
+ duration_ms?: number;
50
+ metadata?: Record<string, unknown>;
51
+ attributes?: Record<string, unknown>;
52
+ total_tokens_in?: number;
53
+ total_tokens_out?: number;
54
+ total_cost?: number;
55
+ span_count?: number;
56
+ tool_count?: number;
57
+ error_count?: number;
58
+ }
59
+ export interface ProcessorOptions {
60
+ apiClient: ApiClient;
61
+ maxBatchSize?: number;
62
+ flushIntervalMs?: number;
63
+ maxSpansPerRun?: number;
64
+ }
65
+ export declare class TrodoSpanProcessor {
66
+ private readonly apiClient;
67
+ private readonly maxBatchSize;
68
+ private readonly flushIntervalMs;
69
+ private readonly maxSpansPerRun;
70
+ private queue;
71
+ private pendingByRun;
72
+ private joinedRuns;
73
+ private timer;
74
+ private stopped;
75
+ constructor(opts: ProcessorOptions);
76
+ private startTimer;
77
+ /** Mark a run as joined — its spans flush immediately via append_spans. */
78
+ markJoined(runId: string): void;
79
+ unmarkJoined(runId: string): void;
80
+ /** Read (without removing) the buffered spans for a run — used for run aggregation. */
81
+ getPending(runId: string): TrodoSpan[];
82
+ /** Buffer a span for async flush. Called by wrapAgent/withSpan + OTel onEnd. */
83
+ enqueueSpan(span: TrodoSpan): void;
84
+ /** Stream spans for a long-running or joined run without finalising. */
85
+ appendSpans(runId: string, spans: TrodoSpan[]): Promise<void>;
86
+ /**
87
+ * Open a Run row server-side without holding a context manager.
88
+ *
89
+ * Pairs with {@link endRun} for sessions that span multiple processes or
90
+ * HTTP requests (e.g. an MCP server). Spans emitted between startRun and
91
+ * endRun flush incrementally via append_spans (callers should markJoined).
92
+ */
93
+ startRun(run: TrodoRun): Promise<void>;
94
+ /** Finalise a Run opened by {@link startRun}. */
95
+ endRun(runId: string, payload: Record<string, unknown>): Promise<void>;
96
+ /** Fire-and-forget run+spans ingest, used when wrapAgent finalises. */
97
+ ingestRun(run: TrodoRun): Promise<void>;
98
+ private flushSpans;
99
+ forceFlush(): Promise<void>;
100
+ shutdown(): Promise<void>;
101
+ onStart(): void;
102
+ onEnd(span: TrodoSpan): void;
103
+ }
104
+ //# sourceMappingURL=processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../../src/otel/processor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,149 @@
1
+ export class TrodoSpanProcessor {
2
+ constructor(opts) {
3
+ this.queue = [];
4
+ this.pendingByRun = new Map();
5
+ this.joinedRuns = new Set();
6
+ this.timer = null;
7
+ this.stopped = false;
8
+ this.apiClient = opts.apiClient;
9
+ this.maxBatchSize = opts.maxBatchSize ?? 64;
10
+ this.flushIntervalMs = opts.flushIntervalMs ?? 2000;
11
+ this.maxSpansPerRun = opts.maxSpansPerRun ?? 1000;
12
+ this.startTimer();
13
+ }
14
+ startTimer() {
15
+ if (this.timer || this.stopped)
16
+ return;
17
+ this.timer = setInterval(() => {
18
+ this.flushSpans().catch(() => { });
19
+ }, this.flushIntervalMs);
20
+ // Don't block Node from exiting because of our timer.
21
+ if (this.timer && typeof this.timer.unref === 'function') {
22
+ this.timer.unref();
23
+ }
24
+ }
25
+ /** Mark a run as joined — its spans flush immediately via append_spans. */
26
+ markJoined(runId) {
27
+ this.joinedRuns.add(runId);
28
+ }
29
+ unmarkJoined(runId) {
30
+ this.joinedRuns.delete(runId);
31
+ this.pendingByRun.delete(runId);
32
+ }
33
+ /** Read (without removing) the buffered spans for a run — used for run aggregation. */
34
+ getPending(runId) {
35
+ return (this.pendingByRun.get(runId) || []).slice();
36
+ }
37
+ /** Buffer a span for async flush. Called by wrapAgent/withSpan + OTel onEnd. */
38
+ enqueueSpan(span) {
39
+ if (this.joinedRuns.has(span.run_id)) {
40
+ // Owning service is remote — forward each span immediately.
41
+ this.apiClient
42
+ .postSpansAppend(span.run_id, [span])
43
+ .catch(() => { });
44
+ return;
45
+ }
46
+ this.queue.push(span);
47
+ const runBuf = this.pendingByRun.get(span.run_id) || [];
48
+ if (runBuf.length < this.maxSpansPerRun) {
49
+ runBuf.push(span);
50
+ this.pendingByRun.set(span.run_id, runBuf);
51
+ }
52
+ if (this.queue.length >= this.maxBatchSize) {
53
+ this.flushSpans().catch(() => { });
54
+ }
55
+ }
56
+ /** Stream spans for a long-running or joined run without finalising. */
57
+ async appendSpans(runId, spans) {
58
+ if (!spans.length)
59
+ return;
60
+ try {
61
+ await this.apiClient.postSpansAppend(runId, spans);
62
+ }
63
+ catch {
64
+ /* silent */
65
+ }
66
+ }
67
+ /**
68
+ * Open a Run row server-side without holding a context manager.
69
+ *
70
+ * Pairs with {@link endRun} for sessions that span multiple processes or
71
+ * HTTP requests (e.g. an MCP server). Spans emitted between startRun and
72
+ * endRun flush incrementally via append_spans (callers should markJoined).
73
+ */
74
+ async startRun(run) {
75
+ try {
76
+ await this.apiClient.postRunStart({ run: run });
77
+ }
78
+ catch {
79
+ /* silent */
80
+ }
81
+ }
82
+ /** Finalise a Run opened by {@link startRun}. */
83
+ async endRun(runId, payload) {
84
+ try {
85
+ await this.apiClient.postRunEnd(runId, payload);
86
+ }
87
+ catch {
88
+ /* silent */
89
+ }
90
+ }
91
+ /** Fire-and-forget run+spans ingest, used when wrapAgent finalises. */
92
+ async ingestRun(run) {
93
+ const spans = this.pendingByRun.get(run.run_id) || [];
94
+ this.pendingByRun.delete(run.run_id);
95
+ this.queue = this.queue.filter((s) => s.run_id !== run.run_id);
96
+ try {
97
+ await this.apiClient.postRunIngest({
98
+ run: run,
99
+ spans: spans,
100
+ });
101
+ }
102
+ catch {
103
+ /* silent — SDK must never throw into user code */
104
+ }
105
+ }
106
+ async flushSpans() {
107
+ if (this.queue.length === 0)
108
+ return;
109
+ const batch = this.queue.splice(0, this.maxBatchSize);
110
+ // Group pending-only spans by run_id and POST to /runs/:run_id/spans.
111
+ const byRun = new Map();
112
+ for (const span of batch) {
113
+ const arr = byRun.get(span.run_id) || [];
114
+ arr.push(span);
115
+ byRun.set(span.run_id, arr);
116
+ }
117
+ const tasks = [];
118
+ for (const [runId, spans] of byRun.entries()) {
119
+ tasks.push(this.apiClient
120
+ .postSpansAppend(runId, spans)
121
+ .catch(() => {
122
+ // Re-queue for next tick on failure.
123
+ this.queue.push(...spans);
124
+ }));
125
+ }
126
+ await Promise.all(tasks);
127
+ }
128
+ async forceFlush() {
129
+ await this.flushSpans();
130
+ }
131
+ async shutdown() {
132
+ this.stopped = true;
133
+ if (this.timer) {
134
+ clearInterval(this.timer);
135
+ this.timer = null;
136
+ }
137
+ await this.forceFlush();
138
+ }
139
+ // ---------- OpenTelemetry SpanProcessor interface (subset) ----------
140
+ // Implemented so this can be registered with OTel SDK's addSpanProcessor().
141
+ // A thin adapter is in autoInstrument.ts.
142
+ onStart() {
143
+ /* noop — Trodo flushes on span end */
144
+ }
145
+ onEnd(span) {
146
+ this.enqueueSpan(span);
147
+ }
148
+ }
149
+ //# sourceMappingURL=processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processor.js","sourceRoot":"","sources":["../../../src/otel/processor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RFC4122 v4 UUIDs — used for run_id, span_id, parent_span_id.
3
+ * Uses the runtime's crypto.randomUUID when available, falls back to a
4
+ * Math.random-based generator so we don't pull in a dependency for tests.
5
+ */
6
+ export declare function uuidv4(): string;
7
+ //# sourceMappingURL=uuid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../../src/otel/uuid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,MAAM,CAY/B"}