trodo-node 1.1.0 → 2.1.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 +340 -103
- package/dist/cjs/TrodoClient.js +77 -19
- package/dist/cjs/TrodoClient.js.map +1 -1
- package/dist/cjs/api/ApiClient.js +21 -3
- package/dist/cjs/api/ApiClient.js.map +1 -1
- package/dist/cjs/api/endpoints.js +7 -1
- package/dist/cjs/api/endpoints.js.map +1 -1
- package/dist/cjs/index.js +104 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/otel/autoInstrument.js +253 -0
- package/dist/cjs/otel/autoInstrument.js.map +1 -0
- package/dist/cjs/otel/context.js +49 -0
- package/dist/cjs/otel/context.js.map +1 -0
- package/dist/cjs/otel/helpers.js +254 -0
- package/dist/cjs/otel/helpers.js.map +1 -0
- package/dist/cjs/otel/processor.js +129 -0
- package/dist/cjs/otel/processor.js.map +1 -0
- package/dist/cjs/otel/uuid.js +24 -0
- package/dist/cjs/otel/uuid.js.map +1 -0
- package/dist/cjs/otel/wrapAgent.js +399 -0
- package/dist/cjs/otel/wrapAgent.js.map +1 -0
- package/dist/cjs/queue/BatchFlusher.js +1 -1
- package/dist/cjs/queue/BatchFlusher.js.map +1 -1
- package/dist/esm/TrodoClient.d.ts +34 -1
- package/dist/esm/TrodoClient.d.ts.map +1 -1
- package/dist/esm/TrodoClient.js +77 -19
- package/dist/esm/TrodoClient.js.map +1 -1
- package/dist/esm/api/ApiClient.d.ts +9 -1
- package/dist/esm/api/ApiClient.d.ts.map +1 -1
- package/dist/esm/api/ApiClient.js +21 -3
- package/dist/esm/api/ApiClient.js.map +1 -1
- package/dist/esm/api/endpoints.d.ts +6 -1
- package/dist/esm/api/endpoints.d.ts.map +1 -1
- package/dist/esm/api/endpoints.js +7 -1
- package/dist/esm/api/endpoints.js.map +1 -1
- package/dist/esm/index.d.ts +84 -8
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +89 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/otel/autoInstrument.d.ts +61 -0
- package/dist/esm/otel/autoInstrument.d.ts.map +1 -0
- package/dist/esm/otel/autoInstrument.js +248 -0
- package/dist/esm/otel/autoInstrument.js.map +1 -0
- package/dist/esm/otel/context.d.ts +26 -0
- package/dist/esm/otel/context.d.ts.map +1 -0
- package/dist/esm/otel/context.js +44 -0
- package/dist/esm/otel/context.js.map +1 -0
- package/dist/esm/otel/helpers.d.ts +119 -0
- package/dist/esm/otel/helpers.d.ts.map +1 -0
- package/dist/esm/otel/helpers.js +244 -0
- package/dist/esm/otel/helpers.js.map +1 -0
- package/dist/esm/otel/processor.d.ts +94 -0
- package/dist/esm/otel/processor.d.ts.map +1 -0
- package/dist/esm/otel/processor.js +125 -0
- package/dist/esm/otel/processor.js.map +1 -0
- package/dist/esm/otel/uuid.d.ts +7 -0
- package/dist/esm/otel/uuid.d.ts.map +1 -0
- package/dist/esm/otel/uuid.js +21 -0
- package/dist/esm/otel/uuid.js.map +1 -0
- package/dist/esm/otel/wrapAgent.d.ts +100 -0
- package/dist/esm/otel/wrapAgent.d.ts.map +1 -0
- package/dist/esm/otel/wrapAgent.js +389 -0
- package/dist/esm/otel/wrapAgent.js.map +1 -0
- package/dist/esm/queue/BatchFlusher.js +1 -1
- package/dist/esm/queue/BatchFlusher.js.map +1 -1
- package/dist/esm/types/index.d.ts +26 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# trodo-node
|
|
2
2
|
|
|
3
|
-
Server-side Node.js SDK for [Trodo Analytics](https://trodo.ai). Track backend events, identify users,
|
|
3
|
+
Server-side Node.js SDK for [Trodo Analytics](https://trodo.ai). Track backend events, identify users, manage people/groups, and instrument AI agents — all unified with your frontend data under the same `siteId`.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -19,117 +19,81 @@ npm install trodo-node node-fetch
|
|
|
19
19
|
```javascript
|
|
20
20
|
const trodo = require('trodo-node');
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
trodo.init({
|
|
24
|
-
siteId: 'your-site-id',
|
|
25
|
-
debug: false, // optional: log API calls
|
|
26
|
-
autoEvents: true, // optional: capture uncaughtException / unhandledRejection
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// For identified users — one call creates the session AND fires identify
|
|
30
|
-
const user = await trodo.identify('user@example.com');
|
|
31
|
-
// distinctId is now id_user@example.com; subsequent calls return cached context
|
|
32
|
-
|
|
33
|
-
// For anonymous/backend-only users
|
|
34
|
-
const anonUser = trodo.forUser('user-123');
|
|
22
|
+
trodo.init({ siteId: 'your-site-id' });
|
|
35
23
|
|
|
36
|
-
//
|
|
24
|
+
// User-bound context (recommended)
|
|
25
|
+
const user = trodo.forUser('user-123');
|
|
37
26
|
await user.track('purchase_completed', { amount: 99.99, plan: 'pro' });
|
|
38
|
-
|
|
39
|
-
// Update people profile
|
|
40
27
|
await user.people.set({ plan: 'pro', company: 'Acme' });
|
|
41
28
|
|
|
42
|
-
//
|
|
43
|
-
await user.captureError(new Error('payment failed'));
|
|
44
|
-
|
|
45
|
-
// Flush queued events before process exit
|
|
29
|
+
// Flush before process exit if using batching
|
|
46
30
|
await trodo.shutdown();
|
|
47
31
|
```
|
|
48
32
|
|
|
49
|
-
## ESM
|
|
50
|
-
|
|
51
33
|
```javascript
|
|
34
|
+
// ESM
|
|
52
35
|
import trodo from 'trodo-node';
|
|
53
36
|
trodo.init({ siteId: 'your-site-id' });
|
|
54
37
|
```
|
|
55
38
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
Frontend and backend events merge when both sides call `identify()` with the same value:
|
|
59
|
-
|
|
60
|
-
```javascript
|
|
61
|
-
// Browser SDK
|
|
62
|
-
Trodo.identify('user@example.com'); // → id_user@example.com
|
|
63
|
-
|
|
64
|
-
// Node.js SDK (same value)
|
|
65
|
-
await user.identify('user@example.com'); // → id_user@example.com
|
|
66
|
-
|
|
67
|
-
// Both event streams now appear together in the Trodo dashboard
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## API Reference
|
|
39
|
+
## Core API
|
|
71
40
|
|
|
72
41
|
### `trodo.init(config)`
|
|
73
42
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
|
|
87
|
-
|
|
43
|
+
Call once at app startup.
|
|
44
|
+
|
|
45
|
+
| Option | Default | Description |
|
|
46
|
+
|--------|---------|-------------|
|
|
47
|
+
| `siteId` | required | Your Trodo site ID |
|
|
48
|
+
| `apiBase` | `https://sdkapi.trodo.ai` | API base URL |
|
|
49
|
+
| `timeout` | `10000` ms | HTTP request timeout |
|
|
50
|
+
| `retries` | `2` | Retries on network/5xx errors |
|
|
51
|
+
| `autoEvents` | `false` | Hook `uncaughtException` / `unhandledRejection` as `server_error` events |
|
|
52
|
+
| `batchEnabled` | `false` | Queue events and flush in batches |
|
|
53
|
+
| `batchSize` | `50` | Flush when this many events are queued |
|
|
54
|
+
| `batchFlushIntervalMs` | `5000` | Also flush every N milliseconds |
|
|
55
|
+
| `onError` | — | Callback for SDK errors (silent by default) |
|
|
56
|
+
| `debug` | `false` | Log API calls to stderr |
|
|
88
57
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```javascript
|
|
92
|
-
const user = await trodo.identify('user@example.com');
|
|
93
|
-
// distinctId = id_user@example.com — merges with frontend events
|
|
94
|
-
await user.track('purchase_completed', { amount: 99.99 });
|
|
95
|
-
```
|
|
58
|
+
### `trodo.forUser(distinctId, options?)`
|
|
96
59
|
|
|
97
|
-
|
|
60
|
+
Returns a user-bound context. No API call is made until you track an event.
|
|
98
61
|
|
|
99
62
|
```javascript
|
|
100
|
-
const user =
|
|
101
|
-
sessionId: req.cookies.trodo_session,
|
|
63
|
+
const user = trodo.forUser('user-123', {
|
|
64
|
+
sessionId: req.cookies.trodo_session, // optional: correlate with browser session
|
|
102
65
|
});
|
|
103
66
|
```
|
|
104
67
|
|
|
105
|
-
### `trodo.
|
|
68
|
+
### `trodo.identify(identifyId, options?)`
|
|
106
69
|
|
|
107
|
-
|
|
70
|
+
Creates the session and fires `POST /api/sdk/identify`. Use to link a `distinctId` to an external identifier (email, DB id). Returns the user context.
|
|
108
71
|
|
|
109
72
|
```javascript
|
|
110
|
-
const user = trodo.
|
|
111
|
-
sessionId: req.cookies.trodo_session,
|
|
73
|
+
const user = await trodo.identify('user@example.com', {
|
|
74
|
+
sessionId: req.cookies.trodo_session,
|
|
112
75
|
});
|
|
76
|
+
// distinctId is now id_user@example.com — merges with browser events
|
|
77
|
+
await user.track('login');
|
|
113
78
|
```
|
|
114
79
|
|
|
115
|
-
### User
|
|
80
|
+
### User context methods
|
|
116
81
|
|
|
117
82
|
```javascript
|
|
118
|
-
await user.track(eventName, properties?)
|
|
119
|
-
await user.
|
|
120
|
-
await user.
|
|
121
|
-
await user.
|
|
122
|
-
await user.
|
|
123
|
-
await user.captureError(error) // Track server_error event
|
|
83
|
+
await user.track(eventName, properties?) // Custom event
|
|
84
|
+
await user.identify(identifyId) // Merge identity
|
|
85
|
+
await user.walletAddress(address) // Set wallet address
|
|
86
|
+
await user.reset() // Clear session
|
|
87
|
+
await user.captureError(err, severity?) // Track server_error ('critical' | 'error' | 'warning')
|
|
124
88
|
|
|
125
89
|
// People profile
|
|
126
90
|
await user.people.set(properties)
|
|
127
91
|
await user.people.setOnce(properties)
|
|
128
92
|
await user.people.unset(keys)
|
|
129
|
-
await user.people.increment(
|
|
130
|
-
await user.people.append(
|
|
131
|
-
await user.people.union(
|
|
132
|
-
await user.people.remove(
|
|
93
|
+
await user.people.increment(key, amount?)
|
|
94
|
+
await user.people.append(key, values)
|
|
95
|
+
await user.people.union(key, values)
|
|
96
|
+
await user.people.remove(key, values)
|
|
133
97
|
await user.people.trackCharge(amount, properties?)
|
|
134
98
|
await user.people.clearCharges()
|
|
135
99
|
await user.people.deleteUser()
|
|
@@ -141,15 +105,15 @@ await user.remove_group(groupKey, groupId)
|
|
|
141
105
|
const group = user.get_group(groupKey, groupId)
|
|
142
106
|
await group.set(properties)
|
|
143
107
|
await group.set_once(properties)
|
|
144
|
-
await group.
|
|
145
|
-
await group.
|
|
108
|
+
await group.increment(key, amount?)
|
|
109
|
+
await group.append(key, values)
|
|
110
|
+
await group.union(key, values)
|
|
111
|
+
await group.remove(key, values)
|
|
146
112
|
await group.unset(keys)
|
|
147
|
-
await group.increment(properties)
|
|
148
|
-
await group.append(properties)
|
|
149
113
|
await group.delete()
|
|
150
114
|
```
|
|
151
115
|
|
|
152
|
-
### Direct
|
|
116
|
+
### Direct call pattern
|
|
153
117
|
|
|
154
118
|
```javascript
|
|
155
119
|
await trodo.track('user-123', 'event_name', { key: 'value' })
|
|
@@ -157,25 +121,284 @@ await trodo.people.set('user-123', { plan: 'pro' })
|
|
|
157
121
|
await trodo.set_group('user-123', 'company', 'acme')
|
|
158
122
|
```
|
|
159
123
|
|
|
160
|
-
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## AI Agent Tracing (recommended)
|
|
127
|
+
|
|
128
|
+
One wrap around your agent captures every LLM call, tool call, and
|
|
129
|
+
nested step as a tree of spans — token counts, costs, inputs, outputs,
|
|
130
|
+
errors. Works with any stack: OpenAI, Anthropic, LangChain, Vercel AI
|
|
131
|
+
SDK, raw HTTP, custom tools. Cost is derived server-side from
|
|
132
|
+
`(provider, model)` — the SDK only sends tokens.
|
|
133
|
+
|
|
134
|
+
### 30-second quickstart
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import trodo from 'trodo-node';
|
|
138
|
+
trodo.init({ siteId: 'your-site-id' }); // autoInstrument on by default
|
|
139
|
+
|
|
140
|
+
const { result, runId } = await trodo.wrapAgent(
|
|
141
|
+
'customer-support',
|
|
142
|
+
async (run) => {
|
|
143
|
+
run.setInput({ query });
|
|
144
|
+
const answer = await agent.run(query); // OpenAI/Anthropic/LangChain auto-captured
|
|
145
|
+
run.setOutput(answer);
|
|
146
|
+
return answer;
|
|
147
|
+
},
|
|
148
|
+
{ distinctId: userId, conversationId: sessionId },
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Open the Agent Runs dashboard — the row shows tokens in/out, cost,
|
|
153
|
+
span count, tool count, error count, plus the full trace tree.
|
|
154
|
+
|
|
155
|
+
### Auto-instrumentation
|
|
156
|
+
|
|
157
|
+
`trodo.init()` calls `enableAutoInstrument()` which registers every
|
|
158
|
+
installed OpenTelemetry instrumentor — no extra wiring.
|
|
159
|
+
|
|
160
|
+
| Framework | Install |
|
|
161
|
+
|-----------|---------|
|
|
162
|
+
| OpenAI | `npm i @opentelemetry/instrumentation-openai` |
|
|
163
|
+
| Anthropic | `npm i @opentelemetry/instrumentation-anthropic` |
|
|
164
|
+
| LangChain | `npm i @opentelemetry/instrumentation-langchain` |
|
|
165
|
+
| LlamaIndex | `npm i @opentelemetry/instrumentation-llamaindex` |
|
|
166
|
+
| Google Gemini | `npm i @opentelemetry/instrumentation-google-generativeai` |
|
|
167
|
+
| Vertex AI | `npm i @opentelemetry/instrumentation-vertexai` |
|
|
168
|
+
| Bedrock | `npm i @opentelemetry/instrumentation-bedrock` |
|
|
169
|
+
| Cohere | `npm i @opentelemetry/instrumentation-cohere` |
|
|
170
|
+
| Vercel AI SDK | emits OTel via `experimental_telemetry: { isEnabled: true }` |
|
|
171
|
+
| http / fetch | bundled — generic HTTP spans for raw-HTTP callers |
|
|
172
|
+
|
|
173
|
+
Opt out with `trodo.init({ siteId, autoInstrument: false })`.
|
|
174
|
+
|
|
175
|
+
### Span helpers
|
|
176
|
+
|
|
177
|
+
Typed function wrappers for custom code — every call becomes a span
|
|
178
|
+
with args auto-captured as `input`, return value as `output`,
|
|
179
|
+
exception as `error`.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// trace — generic span
|
|
183
|
+
const prepared = trodo.trace('prepare', async (payload) => normalize(payload));
|
|
184
|
+
await prepared({ raw: true });
|
|
185
|
+
|
|
186
|
+
// tool — tool span (name-first OR fn-first)
|
|
187
|
+
const runFunnel = trodo.tool('run_funnel_query', async (teamId, preset) => {
|
|
188
|
+
return await db.funnel(teamId, preset);
|
|
189
|
+
});
|
|
190
|
+
await runFunnel(1, 'day7');
|
|
191
|
+
|
|
192
|
+
// llm — LLM span, auto-extracts OpenAI / Anthropic / Gemini usage
|
|
193
|
+
const answer = trodo.llm('answer', async (messages) => callOpenAI(messages), {
|
|
194
|
+
model: 'gpt-4o-mini',
|
|
195
|
+
provider: 'openai',
|
|
196
|
+
});
|
|
197
|
+
await answer([{ role: 'user', content: 'ping' }]);
|
|
198
|
+
// Records inputTokens / outputTokens from response.usage.
|
|
199
|
+
|
|
200
|
+
// retrieval — vector search / RAG retriever span
|
|
201
|
+
const search = trodo.retrieval('vector_search', async (q) => vecDb.query(q));
|
|
202
|
+
const docs = await search('users dropping off');
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Raw-HTTP escape hatches
|
|
206
|
+
|
|
207
|
+
If your LLM client isn't OTel-instrumented and you can't wrap it as a
|
|
208
|
+
function, record a span post-hoc:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const resp = await fetch(url, { body: JSON.stringify(body) }).then(r => r.json());
|
|
212
|
+
await trodo.trackLlmCall({
|
|
213
|
+
model: 'gemini-2.5-flash',
|
|
214
|
+
provider: 'google',
|
|
215
|
+
inputTokens: resp.usageMetadata.promptTokenCount,
|
|
216
|
+
outputTokens: resp.usageMetadata.candidatesTokenCount,
|
|
217
|
+
prompt: body,
|
|
218
|
+
completion: resp,
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For advanced cases, get a raw OTel tracer — the Trodo processor is
|
|
223
|
+
already subscribed:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const tracer = trodo.getTracer('my.module');
|
|
227
|
+
tracer.startActiveSpan('custom', (span) => {
|
|
228
|
+
span.setAttribute('gen_ai.system', 'my-llm');
|
|
229
|
+
span.end();
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Cross-service runs
|
|
234
|
+
|
|
235
|
+
When one service calls another, the downstream service **joins** the
|
|
236
|
+
caller's run instead of creating its own — all spans nest under a
|
|
237
|
+
single timeline.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Caller — outbound:
|
|
241
|
+
await fetch(url, {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
headers: { ...trodo.propagationHeaders(), 'content-type': 'application/json' },
|
|
244
|
+
body: JSON.stringify(payload),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Downstream (Express):
|
|
248
|
+
import express from 'express';
|
|
249
|
+
const app = express();
|
|
250
|
+
app.use(trodo.expressMiddleware());
|
|
251
|
+
// Every LLM call / tool / trace helper inside handlers now nests under
|
|
252
|
+
// the caller's run.
|
|
253
|
+
|
|
254
|
+
// Or manually:
|
|
255
|
+
await trodo.joinRun(
|
|
256
|
+
req.headers['x-trodo-run-id'] as string,
|
|
257
|
+
req.headers['x-trodo-parent-span-id'] as string,
|
|
258
|
+
async () => { /* ... */ },
|
|
259
|
+
);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Conversation binding & feedback
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const { runId } = await trodo.wrapAgent(
|
|
266
|
+
'chat',
|
|
267
|
+
async (run) => { /* ... */ },
|
|
268
|
+
{ distinctId: userId, conversationId: sessionId },
|
|
269
|
+
);
|
|
270
|
+
await trodo.feedback(runId, { satisfaction: 'positive', rating: 5 });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Agent Analytics (legacy event-based API)
|
|
276
|
+
|
|
277
|
+
The older per-event API below is still supported but superseded by
|
|
278
|
+
`wrapAgent` + span helpers above. Use it only if you're already wired
|
|
279
|
+
into it; new integrations should prefer the tracing API.
|
|
280
|
+
|
|
281
|
+
**Before you start:** register your agent in **Integrations → AI Agents** in the dashboard to get an `agent_id` (`agt_xxxxxxxx`).
|
|
282
|
+
|
|
283
|
+
### `track_agent_call` — inbound message / LLM invocation
|
|
161
284
|
|
|
162
285
|
```javascript
|
|
163
|
-
trodo.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
286
|
+
await trodo.track_agent_call({
|
|
287
|
+
agentId: 'agt_abc12345',
|
|
288
|
+
conversationId: 'conv_xyz',
|
|
289
|
+
messageId: 'msg_001',
|
|
290
|
+
prompt: userMessage,
|
|
291
|
+
model: 'gpt-4o',
|
|
292
|
+
provider: 'openai',
|
|
293
|
+
systemPromptVersion: 'v2', // optional — track prompt iterations
|
|
294
|
+
distinctId: userId, // optional — link to a Trodo user
|
|
295
|
+
metadata: { threadSource: 'slack', locale: 'en' }, // optional — stored in agent_calls.metadata (JSONB)
|
|
296
|
+
});
|
|
167
297
|
```
|
|
168
298
|
|
|
169
|
-
|
|
299
|
+
### `track_tool_use` — tool/function call within a turn
|
|
170
300
|
|
|
171
|
-
|
|
301
|
+
```javascript
|
|
302
|
+
await trodo.track_tool_use({
|
|
303
|
+
agentId: 'agt_abc12345',
|
|
304
|
+
conversationId: 'conv_xyz',
|
|
305
|
+
messageId: 'msg_001',
|
|
306
|
+
toolName: 'fetch_billing_info',
|
|
307
|
+
latencyMs: 143,
|
|
308
|
+
status: 'success', // 'success' | 'failure'
|
|
309
|
+
input: { userId: '123' }, // optional
|
|
310
|
+
output: { plan: 'pro' }, // optional
|
|
311
|
+
});
|
|
312
|
+
```
|
|
172
313
|
|
|
173
|
-
|
|
174
|
-
- `process.on('unhandledRejection')`
|
|
314
|
+
### `track_agent_response` — LLM output and token usage
|
|
175
315
|
|
|
176
|
-
|
|
316
|
+
```javascript
|
|
317
|
+
await trodo.track_agent_response({
|
|
318
|
+
agentId: 'agt_abc12345',
|
|
319
|
+
conversationId: 'conv_xyz',
|
|
320
|
+
messageId: 'msg_001',
|
|
321
|
+
model: 'gpt-4o',
|
|
322
|
+
completionTokens: response.usage.completion_tokens,
|
|
323
|
+
promptTokens: response.usage.prompt_tokens,
|
|
324
|
+
totalTokens: response.usage.total_tokens,
|
|
325
|
+
finishReason: response.choices[0].finish_reason,
|
|
326
|
+
distinctId: userId,
|
|
327
|
+
});
|
|
328
|
+
```
|
|
177
329
|
|
|
178
|
-
|
|
330
|
+
### `track_agent_error` — errors and failures
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
await trodo.track_agent_error({
|
|
334
|
+
agentId: 'agt_abc12345',
|
|
335
|
+
conversationId: 'conv_xyz',
|
|
336
|
+
messageId: 'msg_001',
|
|
337
|
+
errorType: 'rate_limit', // 'timeout' | 'rate_limit' | 'guardrail_block' | ...
|
|
338
|
+
errorMessage: err.message,
|
|
339
|
+
failedTool: 'fetch_billing_info', // optional
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### `track_feedback` — user thumbs up/down
|
|
344
|
+
|
|
345
|
+
```javascript
|
|
346
|
+
await trodo.track_feedback({
|
|
347
|
+
agentId: 'agt_abc12345',
|
|
348
|
+
conversationId: 'conv_xyz',
|
|
349
|
+
messageId: 'msg_001', // same messageId as the response it refers to
|
|
350
|
+
feedback: 'positive', // 'positive' | 'negative' | 'unreact'
|
|
351
|
+
distinctId: userId,
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Full turn example
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
async function runAgentTurn(userId, conversationId, userMessage) {
|
|
359
|
+
const agentId = 'agt_abc12345';
|
|
360
|
+
const messageId = `msg_${Date.now()}`;
|
|
361
|
+
|
|
362
|
+
await trodo.track_agent_call({ agentId, conversationId, messageId, prompt: userMessage, distinctId: userId });
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
await trodo.track_tool_use({ agentId, conversationId, messageId, toolName: 'search', status: 'success', latencyMs: 80 });
|
|
366
|
+
|
|
367
|
+
const response = await llm.complete(userMessage);
|
|
368
|
+
|
|
369
|
+
await trodo.track_agent_response({
|
|
370
|
+
agentId, conversationId, messageId,
|
|
371
|
+
model: response.model,
|
|
372
|
+
completionTokens: response.usage.completion_tokens,
|
|
373
|
+
promptTokens: response.usage.prompt_tokens,
|
|
374
|
+
totalTokens: response.usage.total_tokens,
|
|
375
|
+
distinctId: userId,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return response.text;
|
|
379
|
+
} catch (err) {
|
|
380
|
+
await trodo.track_agent_error({ agentId, conversationId, messageId, errorType: err.type, errorMessage: err.message, distinctId: userId });
|
|
381
|
+
throw err;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Identity Merging (Cross-SDK)
|
|
389
|
+
|
|
390
|
+
Call `identify()` with the **same value** on the browser and server to merge all events under one user profile:
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
// Browser
|
|
394
|
+
Trodo.identify('user@example.com'); // → id_user@example.com
|
|
395
|
+
|
|
396
|
+
// Node.js (same value)
|
|
397
|
+
await user.identify('user@example.com'); // → id_user@example.com
|
|
398
|
+
// Events from both sides now appear together in the dashboard
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
179
402
|
|
|
180
403
|
## Batching
|
|
181
404
|
|
|
@@ -183,26 +406,40 @@ Per-user error capture: `user.captureError(error)` uses the user's own `distinct
|
|
|
183
406
|
trodo.init({
|
|
184
407
|
siteId: 'your-site-id',
|
|
185
408
|
batchEnabled: true,
|
|
186
|
-
batchSize:
|
|
187
|
-
batchFlushIntervalMs:
|
|
409
|
+
batchSize: 50,
|
|
410
|
+
batchFlushIntervalMs: 5000,
|
|
188
411
|
});
|
|
189
412
|
|
|
190
|
-
// Events are queued and flushed every 3s or when 100 events accumulate
|
|
191
|
-
await user.track('page_view');
|
|
192
|
-
|
|
193
413
|
// Always flush before process exit
|
|
194
|
-
process.on('SIGTERM', () => trodo.shutdown());
|
|
414
|
+
process.on('SIGTERM', async () => { await trodo.shutdown(); process.exit(0); });
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Auto Events
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
trodo.init({ siteId: 'your-site-id', autoEvents: true });
|
|
423
|
+
// Hooks process.on('uncaughtException') and process.on('unhandledRejection')
|
|
424
|
+
// Sends server_error events with distinct_id: 'server_global'
|
|
425
|
+
|
|
426
|
+
// Toggle at runtime
|
|
427
|
+
trodo.enableAutoEvents();
|
|
428
|
+
trodo.disableAutoEvents();
|
|
195
429
|
```
|
|
196
430
|
|
|
431
|
+
---
|
|
432
|
+
|
|
197
433
|
## TypeScript
|
|
198
434
|
|
|
199
|
-
Full
|
|
435
|
+
Full type declarations bundled:
|
|
200
436
|
|
|
201
437
|
```typescript
|
|
202
|
-
import trodo, {
|
|
438
|
+
import trodo, { TrodoClient, UserContext } from 'trodo-node';
|
|
439
|
+
import type { AgentCallProps, ToolUseProps, AgentResponseProps, AgentErrorProps, FeedbackProps } from 'trodo-node';
|
|
203
440
|
|
|
204
|
-
|
|
205
|
-
|
|
441
|
+
// Multi-tenant
|
|
442
|
+
const client = new TrodoClient({ siteId: 'your-site-id' });
|
|
206
443
|
```
|
|
207
444
|
|
|
208
445
|
## License
|
package/dist/cjs/TrodoClient.js
CHANGED
|
@@ -7,6 +7,10 @@ const EventQueue_js_1 = require("./queue/EventQueue.js");
|
|
|
7
7
|
const BatchFlusher_js_1 = require("./queue/BatchFlusher.js");
|
|
8
8
|
const AutoEventManager_js_1 = require("./auto/AutoEventManager.js");
|
|
9
9
|
const UserContext_js_1 = require("./UserContext.js");
|
|
10
|
+
const processor_js_1 = require("./otel/processor.js");
|
|
11
|
+
const wrapAgent_js_1 = require("./otel/wrapAgent.js");
|
|
12
|
+
const helpers_js_1 = require("./otel/helpers.js");
|
|
13
|
+
const autoInstrument_js_1 = require("./otel/autoInstrument.js");
|
|
10
14
|
const DEFAULT_API_BASE = 'https://sdkapi.trodo.ai';
|
|
11
15
|
class TrodoClient {
|
|
12
16
|
constructor(config) {
|
|
@@ -30,7 +34,11 @@ class TrodoClient {
|
|
|
30
34
|
if (config.autoEvents) {
|
|
31
35
|
this.autoEventManager.enable();
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
this.spanProcessor = new processor_js_1.TrodoSpanProcessor({ apiClient: this.apiClient });
|
|
38
|
+
// Default ON — matches Python SDK. Opt out with autoInstrument: false.
|
|
39
|
+
if (config.autoInstrument !== false) {
|
|
40
|
+
(0, autoInstrument_js_1.enableAutoInstrument)({ processor: this.spanProcessor });
|
|
41
|
+
}
|
|
34
42
|
const client = this;
|
|
35
43
|
this.people = {
|
|
36
44
|
set: (d, p) => client.forUser(d).people.set(p),
|
|
@@ -49,17 +57,13 @@ class TrodoClient {
|
|
|
49
57
|
// Primary pattern: forUser()
|
|
50
58
|
// --------------------------------------------------------------------------
|
|
51
59
|
forUser(distinctId, options) {
|
|
52
|
-
const
|
|
53
|
-
const cached = this.userContextCache.get(key);
|
|
60
|
+
const cached = this.userContextCache.get(distinctId);
|
|
54
61
|
if (cached)
|
|
55
62
|
return cached;
|
|
56
63
|
const ctx = new UserContext_js_1.UserContext(distinctId, this.siteId, this.apiClient, this.sessionManager, this.eventQueue, this.batchFlusher, this.autoEventManager, options);
|
|
57
|
-
this.userContextCache.set(
|
|
64
|
+
this.userContextCache.set(distinctId, ctx);
|
|
58
65
|
return ctx;
|
|
59
66
|
}
|
|
60
|
-
// --------------------------------------------------------------------------
|
|
61
|
-
// Direct-call pattern (distinctId as first param)
|
|
62
|
-
// --------------------------------------------------------------------------
|
|
63
67
|
async track(distinctId, eventName, properties, options) {
|
|
64
68
|
return this.forUser(distinctId).track(eventName, properties, options);
|
|
65
69
|
}
|
|
@@ -90,21 +94,12 @@ class TrodoClient {
|
|
|
90
94
|
get_group(distinctId, groupKey, groupId) {
|
|
91
95
|
return this.forUser(distinctId).get_group(groupKey, groupId);
|
|
92
96
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// --------------------------------------------------------------------------
|
|
96
|
-
enableAutoEvents() {
|
|
97
|
-
this.autoEventManager.enable();
|
|
98
|
-
}
|
|
99
|
-
disableAutoEvents() {
|
|
100
|
-
this.autoEventManager.disable();
|
|
101
|
-
}
|
|
102
|
-
// --------------------------------------------------------------------------
|
|
103
|
-
// Lifecycle
|
|
104
|
-
// --------------------------------------------------------------------------
|
|
97
|
+
enableAutoEvents() { this.autoEventManager.enable(); }
|
|
98
|
+
disableAutoEvents() { this.autoEventManager.disable(); }
|
|
105
99
|
async flush() {
|
|
106
100
|
if (this.batchFlusher)
|
|
107
101
|
await this.batchFlusher.flush();
|
|
102
|
+
await this.spanProcessor.forceFlush();
|
|
108
103
|
}
|
|
109
104
|
async shutdown() {
|
|
110
105
|
this.autoEventManager.disable();
|
|
@@ -112,6 +107,69 @@ class TrodoClient {
|
|
|
112
107
|
this.batchFlusher.stop();
|
|
113
108
|
await this.batchFlusher.flush();
|
|
114
109
|
}
|
|
110
|
+
await this.spanProcessor.shutdown();
|
|
111
|
+
}
|
|
112
|
+
// --------------------------------------------------------------------------
|
|
113
|
+
// Agent Runs — unified surface
|
|
114
|
+
// --------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Wrap an async function as an agent run. Callback receives a RunHandle
|
|
117
|
+
* — call `handle.setInput(...)` / `handle.setOutput(...)` to populate the
|
|
118
|
+
* run's input/output fields. Every OTel-auto-instrumented LLM call or
|
|
119
|
+
* nested `withSpan` inside `fn` is captured as a child span.
|
|
120
|
+
* Returns { result, runId } — runId is used to attach feedback later.
|
|
121
|
+
*/
|
|
122
|
+
wrapAgent(agentName, fn, options = {}) {
|
|
123
|
+
return (0, wrapAgent_js_1.wrapAgent)(this.spanProcessor, this.siteId, agentName, fn, options);
|
|
124
|
+
}
|
|
125
|
+
/** Create a nested span inside the current run. */
|
|
126
|
+
withSpan(name, fn, options = {}) {
|
|
127
|
+
return (0, wrapAgent_js_1.withSpan)(this.spanProcessor, name, fn, options);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Join an existing run owned by a remote service — opens a span (not a new
|
|
131
|
+
* run) in that run's context. Spans inside `fn` are streamed to the backend
|
|
132
|
+
* via append_spans. Used by the express middleware and for manual cross-
|
|
133
|
+
* service join on any request shape.
|
|
134
|
+
*/
|
|
135
|
+
joinRun(runId, parentSpanId, fn, options = {}) {
|
|
136
|
+
return (0, wrapAgent_js_1.joinRun)(this.spanProcessor, this.siteId, runId, parentSpanId, fn, options);
|
|
137
|
+
}
|
|
138
|
+
/** Higher-order wrapper: decorate a tool-like function so each call becomes a span. */
|
|
139
|
+
tool(fn, options = {}) {
|
|
140
|
+
return (0, helpers_js_1.tool)(fn, options);
|
|
141
|
+
}
|
|
142
|
+
/** Record a one-shot LLM span for a raw-HTTP caller. */
|
|
143
|
+
trackLlmCall(params) {
|
|
144
|
+
return (0, helpers_js_1.trackLlmCall)(params);
|
|
145
|
+
}
|
|
146
|
+
/** Return an Express/Connect middleware that auto-joins inbound runs. */
|
|
147
|
+
expressMiddleware() {
|
|
148
|
+
return (0, helpers_js_1.expressMiddleware)({
|
|
149
|
+
processor: this.spanProcessor,
|
|
150
|
+
teamSiteId: this.siteId,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** Outbound HTTP headers carrying the current run/span context. */
|
|
154
|
+
propagationHeaders() {
|
|
155
|
+
return (0, helpers_js_1.propagationHeaders)();
|
|
156
|
+
}
|
|
157
|
+
/** Expose current run/span ids for bespoke propagation needs. */
|
|
158
|
+
currentRunId() {
|
|
159
|
+
return (0, wrapAgent_js_1.currentRunId)();
|
|
160
|
+
}
|
|
161
|
+
currentSpanId() {
|
|
162
|
+
return (0, wrapAgent_js_1.currentSpanId)();
|
|
163
|
+
}
|
|
164
|
+
/** Attach feedback to a completed run. Call with the runId returned from wrapAgent. */
|
|
165
|
+
async feedback(runId, props) {
|
|
166
|
+
return this.apiClient.postRunFeedback(runId, {
|
|
167
|
+
satisfaction: props.satisfaction ?? null,
|
|
168
|
+
rating: props.rating ?? null,
|
|
169
|
+
comment: props.comment ?? props.feedback ?? null,
|
|
170
|
+
distinct_id: props.distinctId ?? null,
|
|
171
|
+
attributes: props.metadata ?? {},
|
|
172
|
+
});
|
|
115
173
|
}
|
|
116
174
|
}
|
|
117
175
|
exports.TrodoClient = TrodoClient;
|