@lelemondev/sdk 0.3.0 → 0.5.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 (46) hide show
  1. package/README.md +536 -93
  2. package/dist/express-Dt5wT6_n.d.mts +67 -0
  3. package/dist/express-Dt5wT6_n.d.ts +67 -0
  4. package/dist/express.d.mts +1 -0
  5. package/dist/express.d.ts +1 -0
  6. package/dist/express.js +21 -0
  7. package/dist/express.js.map +1 -0
  8. package/dist/express.mjs +19 -0
  9. package/dist/express.mjs.map +1 -0
  10. package/dist/hono-Dzmu77iW.d.mts +80 -0
  11. package/dist/hono-Dzmu77iW.d.ts +80 -0
  12. package/dist/hono.d.mts +1 -0
  13. package/dist/hono.d.ts +1 -0
  14. package/dist/hono.js +23 -0
  15. package/dist/hono.js.map +1 -0
  16. package/dist/hono.mjs +21 -0
  17. package/dist/hono.mjs.map +1 -0
  18. package/dist/index.d.mts +2 -2
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +949 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +949 -3
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/integrations.d.mts +4 -0
  25. package/dist/integrations.d.ts +4 -0
  26. package/dist/integrations.js +93 -0
  27. package/dist/integrations.js.map +1 -0
  28. package/dist/integrations.mjs +88 -0
  29. package/dist/integrations.mjs.map +1 -0
  30. package/dist/lambda-CAuiF9dH.d.mts +79 -0
  31. package/dist/lambda-CAuiF9dH.d.ts +79 -0
  32. package/dist/lambda.d.mts +1 -0
  33. package/dist/lambda.d.ts +1 -0
  34. package/dist/lambda.js +21 -0
  35. package/dist/lambda.js.map +1 -0
  36. package/dist/lambda.mjs +19 -0
  37. package/dist/lambda.mjs.map +1 -0
  38. package/dist/next-BC9PmEho.d.mts +100 -0
  39. package/dist/next-BC9PmEho.d.ts +100 -0
  40. package/dist/next.d.mts +1 -0
  41. package/dist/next.d.ts +1 -0
  42. package/dist/next.js +33 -0
  43. package/dist/next.js.map +1 -0
  44. package/dist/next.mjs +30 -0
  45. package/dist/next.mjs.map +1 -0
  46. package/package.json +59 -11
package/README.md CHANGED
@@ -4,16 +4,22 @@
4
4
  [![CI](https://github.com/lelemondev/lelemondev-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/lelemondev/lelemondev-sdk/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- Automatic LLM observability for Node.js. Track your AI agents with zero code changes.
7
+ Automatic LLM observability for Node.js. Wrap your client, everything is traced.
8
+
9
+ ## LLM Agent Docs
10
+
11
+ ```
12
+ https://lelemondev.github.io/lelemondev-sdk/llms.txt
13
+ https://lelemondev.github.io/lelemondev-sdk/llms-full.txt
14
+ ```
8
15
 
9
16
  ## Features
10
17
 
11
- - **Automatic Tracing** - Wrap your client, everything is traced
12
- - **Fire-and-forget** - Never blocks your code
13
- - **Auto-batching** - Efficient network usage
18
+ - **Automatic Tracing** - Wrap your client, all calls are traced
19
+ - **Zero Config** - Works out of the box
20
+ - **Framework Integrations** - Next.js, Express, Lambda, Hono
14
21
  - **Streaming Support** - Full support for streaming responses
15
22
  - **Type-safe** - Preserves your client's TypeScript types
16
- - **Serverless-ready** - Built-in flush for Lambda/Vercel
17
23
 
18
24
  ## Installation
19
25
 
@@ -24,33 +30,262 @@ npm install @lelemondev/sdk
24
30
  ## Quick Start
25
31
 
26
32
  ```typescript
27
- import { init, observe, flush } from '@lelemondev/sdk';
33
+ import { init, observe } from '@lelemondev/sdk';
28
34
  import OpenAI from 'openai';
29
35
 
30
- // 1. Initialize once at app startup
36
+ // 1. Initialize once
31
37
  init({ apiKey: process.env.LELEMON_API_KEY });
32
38
 
33
39
  // 2. Wrap your client
34
40
  const openai = observe(new OpenAI());
35
41
 
36
- // 3. Use normally - all calls are traced automatically
42
+ // 3. Use normally - all calls traced automatically
37
43
  const response = await openai.chat.completions.create({
38
44
  model: 'gpt-4',
39
45
  messages: [{ role: 'user', content: 'Hello!' }],
40
46
  });
41
-
42
- // 4. For serverless: flush before response
43
- await flush();
44
47
  ```
45
48
 
46
- That's it! No manual tracing code needed.
47
-
48
49
  ## Supported Providers
49
50
 
50
51
  | Provider | Status | Methods |
51
52
  |----------|--------|---------|
52
- | OpenAI | Supported | `chat.completions.create()`, `completions.create()`, `embeddings.create()` |
53
- | Anthropic | Supported | `messages.create()`, `messages.stream()` |
53
+ | OpenAI | | `chat.completions.create()`, `responses.create()`, `completions.create()`, `embeddings.create()` |
54
+ | OpenRouter | | `chat.completions.create()` (access to 400+ models) |
55
+ | Anthropic | ✅ | `messages.create()`, `messages.stream()` |
56
+ | AWS Bedrock | ✅ | `ConverseCommand`, `ConverseStreamCommand`, `InvokeModelCommand` |
57
+ | Google Gemini | ✅ | `generateContent()`, `generateContentStream()`, `chat.sendMessage()` |
58
+
59
+ ### OpenRouter
60
+
61
+ [OpenRouter](https://openrouter.ai) provides unified access to 400+ models from OpenAI, Anthropic, Google, Meta, Mistral, and more through a single API.
62
+
63
+ ```typescript
64
+ import { init, observe } from '@lelemondev/sdk';
65
+ import OpenAI from 'openai';
66
+
67
+ init({ apiKey: process.env.LELEMON_API_KEY });
68
+
69
+ // Configure OpenAI SDK to use OpenRouter
70
+ const openrouter = observe(new OpenAI({
71
+ baseURL: 'https://openrouter.ai/api/v1',
72
+ apiKey: process.env.OPENROUTER_API_KEY,
73
+ defaultHeaders: {
74
+ 'HTTP-Referer': 'https://your-app.com', // Optional: for OpenRouter rankings
75
+ 'X-Title': 'Your App Name', // Optional: for OpenRouter rankings
76
+ },
77
+ }));
78
+
79
+ // Access any model through OpenRouter
80
+ const response = await openrouter.chat.completions.create({
81
+ model: 'anthropic/claude-3-opus', // or 'openai/gpt-4', 'google/gemini-pro', etc.
82
+ messages: [{ role: 'user', content: 'Hello!' }],
83
+ });
84
+ ```
85
+
86
+ Models are specified in `provider/model` format (e.g., `anthropic/claude-3-opus`, `openai/gpt-4`, `meta-llama/llama-3-70b`). See [OpenRouter Models](https://openrouter.ai/models) for the full list.
87
+
88
+ ## Usage Without Framework Integrations
89
+
90
+ The SDK works with **any Node.js application**. Framework integrations are optional - they just automate the `flush()` call.
91
+
92
+ ### Long-running Processes (Servers, Workers)
93
+
94
+ For long-running processes, the SDK auto-flushes every second (configurable via `flushIntervalMs`):
95
+
96
+ ```typescript
97
+ import { init, observe } from '@lelemondev/sdk';
98
+ import OpenAI from 'openai';
99
+
100
+ init({ apiKey: process.env.LELEMON_API_KEY });
101
+ const openai = observe(new OpenAI());
102
+
103
+ // In your HTTP server, WebSocket handler, queue worker, etc.
104
+ async function handleRequest(userId: string, message: string) {
105
+ const client = observe(new OpenAI(), { userId });
106
+
107
+ const result = await client.chat.completions.create({
108
+ model: 'gpt-4',
109
+ messages: [{ role: 'user', content: message }],
110
+ });
111
+
112
+ return result.choices[0].message;
113
+ // Traces are auto-flushed in the background (every 1s by default)
114
+ }
115
+ ```
116
+
117
+ ### Short-lived Processes (Scripts, Serverless, CLI)
118
+
119
+ For scripts or serverless functions, call `flush()` before the process exits:
120
+
121
+ ```typescript
122
+ import { init, observe, flush } from '@lelemondev/sdk';
123
+ import OpenAI from 'openai';
124
+
125
+ init({ apiKey: process.env.LELEMON_API_KEY });
126
+ const openai = observe(new OpenAI());
127
+
128
+ async function main() {
129
+ const result = await openai.chat.completions.create({
130
+ model: 'gpt-4',
131
+ messages: [{ role: 'user', content: 'Hello!' }],
132
+ });
133
+
134
+ console.log(result.choices[0].message.content);
135
+
136
+ // IMPORTANT: Flush before exit to ensure traces are sent
137
+ await flush();
138
+ }
139
+
140
+ main();
141
+ ```
142
+
143
+ ### Custom HTTP Server (No Framework)
144
+
145
+ ```typescript
146
+ import http from 'http';
147
+ import { init, observe, flush } from '@lelemondev/sdk';
148
+ import OpenAI from 'openai';
149
+
150
+ init({ apiKey: process.env.LELEMON_API_KEY });
151
+
152
+ const server = http.createServer(async (req, res) => {
153
+ if (req.method === 'POST' && req.url === '/chat') {
154
+ const openai = observe(new OpenAI(), {
155
+ userId: req.headers['x-user-id'] as string,
156
+ });
157
+
158
+ const result = await openai.chat.completions.create({
159
+ model: 'gpt-4',
160
+ messages: [{ role: 'user', content: 'Hello!' }],
161
+ });
162
+
163
+ res.writeHead(200, { 'Content-Type': 'application/json' });
164
+ res.end(JSON.stringify(result.choices[0].message));
165
+ // Auto-flush handles this in background
166
+ }
167
+ });
168
+
169
+ // Graceful shutdown - flush remaining traces
170
+ process.on('SIGTERM', async () => {
171
+ await flush();
172
+ server.close();
173
+ });
174
+
175
+ server.listen(3000);
176
+ ```
177
+
178
+ ### When to Use Framework Integrations vs Manual
179
+
180
+ | Scenario | Recommendation |
181
+ |----------|----------------|
182
+ | Next.js, Express, Hono, Lambda | Use framework integration (auto-flush) |
183
+ | Custom HTTP server | Auto-flush works, add `flush()` on shutdown |
184
+ | CLI scripts | Always call `flush()` before exit |
185
+ | Background workers (Bull, BullMQ) | Auto-flush works, add `flush()` on shutdown |
186
+ | One-off scripts | Always call `flush()` before exit |
187
+ | Long-running daemons | Auto-flush works |
188
+
189
+ ## Framework Integrations
190
+
191
+ Framework integrations automate the `flush()` call so you don't have to think about it.
192
+
193
+ ### Next.js App Router
194
+
195
+ ```typescript
196
+ // app/api/chat/route.ts
197
+ import { init, observe } from '@lelemondev/sdk';
198
+ import { withObserve } from '@lelemondev/sdk/next';
199
+ import { after } from 'next/server';
200
+ import OpenAI from 'openai';
201
+
202
+ init({ apiKey: process.env.LELEMON_API_KEY });
203
+ const openai = observe(new OpenAI());
204
+
205
+ export const POST = withObserve(
206
+ async (req) => {
207
+ const { message } = await req.json();
208
+ const result = await openai.chat.completions.create({
209
+ model: 'gpt-4',
210
+ messages: [{ role: 'user', content: message }],
211
+ });
212
+ return Response.json(result.choices[0].message);
213
+ },
214
+ { after } // Non-blocking flush (Next.js 15+)
215
+ );
216
+ ```
217
+
218
+ ### Express
219
+
220
+ ```typescript
221
+ import express from 'express';
222
+ import { init, observe } from '@lelemondev/sdk';
223
+ import { createMiddleware } from '@lelemondev/sdk/express';
224
+ import OpenAI from 'openai';
225
+
226
+ init({ apiKey: process.env.LELEMON_API_KEY });
227
+ const openai = observe(new OpenAI());
228
+
229
+ const app = express();
230
+ app.use(createMiddleware()); // Auto-flush on response finish
231
+
232
+ app.post('/chat', async (req, res) => {
233
+ const result = await openai.chat.completions.create({
234
+ model: 'gpt-4',
235
+ messages: [{ role: 'user', content: req.body.message }],
236
+ });
237
+ res.json(result.choices[0].message);
238
+ });
239
+ ```
240
+
241
+ ### AWS Lambda
242
+
243
+ ```typescript
244
+ import { init, observe } from '@lelemondev/sdk';
245
+ import { withObserve } from '@lelemondev/sdk/lambda';
246
+ import OpenAI from 'openai';
247
+
248
+ init({ apiKey: process.env.LELEMON_API_KEY });
249
+ const openai = observe(new OpenAI());
250
+
251
+ export const handler = withObserve(async (event) => {
252
+ const body = JSON.parse(event.body);
253
+ const result = await openai.chat.completions.create({
254
+ model: 'gpt-4',
255
+ messages: [{ role: 'user', content: body.message }],
256
+ });
257
+ return {
258
+ statusCode: 200,
259
+ body: JSON.stringify(result.choices[0].message),
260
+ };
261
+ });
262
+ ```
263
+
264
+ ### Hono (Cloudflare Workers, Deno, Bun)
265
+
266
+ ```typescript
267
+ import { Hono } from 'hono';
268
+ import { init, observe } from '@lelemondev/sdk';
269
+ import { createMiddleware } from '@lelemondev/sdk/hono';
270
+ import OpenAI from 'openai';
271
+
272
+ init({ apiKey: process.env.LELEMON_API_KEY });
273
+ const openai = observe(new OpenAI());
274
+
275
+ const app = new Hono();
276
+ app.use(createMiddleware()); // Uses waitUntil on Workers
277
+
278
+ app.post('/chat', async (c) => {
279
+ const { message } = await c.req.json();
280
+ const result = await openai.chat.completions.create({
281
+ model: 'gpt-4',
282
+ messages: [{ role: 'user', content: message }],
283
+ });
284
+ return c.json(result.choices[0].message);
285
+ });
286
+
287
+ export default app;
288
+ ```
54
289
 
55
290
  ## API Reference
56
291
 
@@ -60,147 +295,348 @@ Initialize the SDK. Call once at app startup.
60
295
 
61
296
  ```typescript
62
297
  init({
63
- apiKey: 'le_xxx', // Required (or set LELEMON_API_KEY env var)
298
+ apiKey: 'le_xxx', // Required (or LELEMON_API_KEY env var)
64
299
  endpoint: 'https://...', // Optional, custom endpoint
65
300
  debug: false, // Optional, enable debug logs
66
- disabled: false, // Optional, disable all tracing
301
+ disabled: false, // Optional, disable tracing
67
302
  batchSize: 10, // Optional, items per batch
68
303
  flushIntervalMs: 1000, // Optional, auto-flush interval
69
- requestTimeoutMs: 10000, // Optional, HTTP request timeout
70
304
  });
71
305
  ```
72
306
 
73
307
  ### `observe(client, options?)`
74
308
 
75
- Wrap an LLM client with automatic tracing. Returns the same client type.
309
+ Wrap an LLM client with automatic tracing.
76
310
 
77
311
  ```typescript
78
- import Anthropic from '@anthropic-ai/sdk';
79
-
80
- const anthropic = observe(new Anthropic(), {
81
- sessionId: 'session-123', // Optional, group related traces
82
- userId: 'user-456', // Optional, identify the user
83
- metadata: { source: 'api' }, // Optional, custom metadata
84
- tags: ['production'], // Optional, tags for filtering
85
- });
86
- ```
87
-
88
- ### `createObserve(defaultOptions)`
89
-
90
- Create a scoped observe function with preset options.
91
-
92
- ```typescript
93
- const observeWithSession = createObserve({
312
+ const openai = observe(new OpenAI(), {
94
313
  sessionId: 'session-123',
95
314
  userId: 'user-456',
315
+ metadata: { source: 'api' },
316
+ tags: ['production'],
96
317
  });
97
-
98
- const openai = observeWithSession(new OpenAI());
99
- const anthropic = observeWithSession(new Anthropic());
100
318
  ```
101
319
 
102
320
  ### `flush()`
103
321
 
104
- Wait for all pending traces to be sent. Use in serverless environments.
322
+ Manually flush pending traces. Use in serverless without framework integration.
105
323
 
106
324
  ```typescript
107
325
  await flush();
108
326
  ```
109
327
 
110
- ### `isEnabled()`
328
+ ## User & Session Tracking
111
329
 
112
- Check if tracing is enabled.
330
+ Track which user generated each trace and group related conversations.
113
331
 
332
+ ### Available Fields
333
+
334
+ | Field | Purpose | In Dashboard |
335
+ |-------|---------|--------------|
336
+ | `userId` | Identify the end user | Shown in "User" column, searchable |
337
+ | `sessionId` | Group related traces (e.g., a conversation) | Shown in "Session" column, searchable |
338
+ | `metadata` | Custom data attached to the trace | Visible in trace detail view |
339
+ | `tags` | Labels for categorization | Shown as badges, filterable dropdown |
340
+
341
+ #### Field Details
342
+
343
+ **`userId`** - Identifies who made the request
344
+ ```typescript
345
+ userId: req.user.id // From your auth system
346
+ userId: 'user-123' // Any string identifier
347
+ userId: req.user.email // Email works too
348
+ ```
349
+
350
+ **`sessionId`** - Groups multiple calls into one conversation/session
114
351
  ```typescript
115
- if (isEnabled()) {
116
- console.log('Tracing is active');
352
+ sessionId: req.body.conversationId // Chat conversation ID
353
+ sessionId: req.headers['x-session-id'] // From client header
354
+ sessionId: crypto.randomUUID() // Generate per session
355
+ ```
356
+
357
+ **`metadata`** - Any extra data you want to attach (stored as JSON)
358
+ ```typescript
359
+ metadata: {
360
+ plan: 'premium', // User's subscription plan
361
+ feature: 'chat', // Which feature triggered this
362
+ version: '2.1.0', // Your app version
363
+ environment: 'production', // Environment
364
+ ip: req.ip, // Client IP (for debugging)
365
+ customField: 'any value', // Anything you need
117
366
  }
118
367
  ```
119
368
 
120
- ## Streaming Support
369
+ **`tags`** - Quick labels for filtering (array of strings)
370
+ ```typescript
371
+ tags: ['production'] // Environment
372
+ tags: ['chat', 'premium'] // Feature + plan
373
+ tags: ['api', 'v2'] // Service + version
374
+ tags: [tenantId, 'high-priority'] // Multi-tenant + priority
375
+ ```
121
376
 
122
- Both OpenAI and Anthropic streaming are fully supported:
377
+ ### Basic Usage
123
378
 
124
379
  ```typescript
125
- // OpenAI streaming
126
- const stream = await openai.chat.completions.create({
127
- model: 'gpt-4',
128
- messages: [{ role: 'user', content: 'Hello!' }],
129
- stream: true,
380
+ const openai = observe(new OpenAI(), {
381
+ userId: 'user-123',
382
+ sessionId: 'conversation-abc',
130
383
  });
384
+ ```
131
385
 
132
- for await (const chunk of stream) {
133
- process.stdout.write(chunk.choices[0]?.delta?.content || '');
134
- }
135
- // Trace is captured automatically when stream completes
386
+ ### In an API Endpoint
136
387
 
137
- // Anthropic streaming
138
- const stream = anthropic.messages.stream({
139
- model: 'claude-3-opus-20240229',
140
- max_tokens: 1024,
141
- messages: [{ role: 'user', content: 'Hello!' }],
142
- });
388
+ ```typescript
389
+ // Express
390
+ app.post('/chat', async (req, res) => {
391
+ // Create client with user context from the request
392
+ const openai = observe(new OpenAI(), {
393
+ userId: req.user.id,
394
+ sessionId: req.headers['x-session-id'],
395
+ metadata: {
396
+ plan: req.user.plan,
397
+ endpoint: '/chat'
398
+ },
399
+ });
143
400
 
144
- for await (const event of stream) {
145
- // Process events
146
- }
147
- // Trace is captured automatically
401
+ const result = await openai.chat.completions.create({
402
+ model: 'gpt-4',
403
+ messages: [{ role: 'user', content: req.body.message }],
404
+ });
405
+
406
+ res.json(result.choices[0].message);
407
+ });
148
408
  ```
149
409
 
150
- ## Serverless Usage
410
+ ### Reusable Context with createObserve()
151
411
 
152
- ### Vercel (Next.js)
412
+ When you have multiple LLM clients or make calls from different places, use `createObserve()` to avoid repeating context:
153
413
 
154
414
  ```typescript
155
- import { waitUntil } from '@vercel/functions';
156
- import { init, observe, flush } from '@lelemondev/sdk';
415
+ import { createObserve } from '@lelemondev/sdk';
157
416
  import OpenAI from 'openai';
417
+ import { GoogleGenerativeAI } from '@google/generative-ai';
418
+ import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
419
+
420
+ // Create a scoped observe function with user context
421
+ const observeForUser = createObserve({
422
+ userId: 'user-123',
423
+ sessionId: 'session-456',
424
+ tags: ['premium'],
425
+ });
158
426
 
159
- init({ apiKey: process.env.LELEMON_API_KEY });
160
- const openai = observe(new OpenAI());
427
+ // All clients inherit the same context
428
+ const openai = observeForUser(new OpenAI());
429
+ const gemini = observeForUser(new GoogleGenerativeAI(apiKey));
430
+ const bedrock = observeForUser(new BedrockRuntimeClient({}));
161
431
 
162
- export async function POST(req: Request) {
163
- const { message } = await req.json();
432
+ // All these calls will be associated with user-123
433
+ await openai.chat.completions.create({ ... });
434
+ await gemini.getGenerativeModel({ model: 'gemini-pro' }).generateContent('...');
435
+ ```
164
436
 
165
- const response = await openai.chat.completions.create({
166
- model: 'gpt-4',
167
- messages: [{ role: 'user', content: message }],
437
+ ### In Middleware (Express)
438
+
439
+ Set up user context once, use it everywhere:
440
+
441
+ ```typescript
442
+ import { createObserve } from '@lelemondev/sdk';
443
+
444
+ // Middleware to attach observe function to request
445
+ app.use((req, res, next) => {
446
+ req.observe = createObserve({
447
+ userId: req.user?.id,
448
+ sessionId: req.headers['x-session-id'] || crypto.randomUUID(),
449
+ metadata: {
450
+ ip: req.ip,
451
+ userAgent: req.headers['user-agent'],
452
+ },
168
453
  });
454
+ next();
455
+ });
456
+
457
+ // In any route handler
458
+ app.post('/chat', async (req, res) => {
459
+ const openai = req.observe(new OpenAI());
460
+ // Calls are automatically associated with the user
461
+ });
462
+
463
+ app.post('/summarize', async (req, res) => {
464
+ const gemini = req.observe(new GoogleGenerativeAI(apiKey));
465
+ // Same user context, different endpoint
466
+ });
467
+ ```
468
+
469
+ ### Multi-tenant Application
470
+
471
+ ```typescript
472
+ app.post('/api/:tenantId/chat', async (req, res) => {
473
+ const openai = observe(new OpenAI(), {
474
+ userId: req.user.id,
475
+ sessionId: req.body.conversationId,
476
+ metadata: {
477
+ tenantId: req.params.tenantId,
478
+ environment: process.env.NODE_ENV,
479
+ },
480
+ tags: [req.params.tenantId, req.user.plan],
481
+ });
482
+
483
+ // Traces can be filtered by tenant, user, or conversation
484
+ });
485
+ ```
486
+
487
+ ### Distributed Systems (API + WebSocket)
488
+
489
+ When your application spans multiple services (REST API, WebSocket server, background workers), use consistent identifiers across all of them:
169
490
 
170
- waitUntil(flush()); // Flush after response
171
- return Response.json(response.choices[0].message);
491
+ ```typescript
492
+ // Shared utility for creating observe context
493
+ // utils/observe-context.ts
494
+ import { createObserve } from '@lelemondev/sdk';
495
+
496
+ export function createUserObserve(userId: string, sessionId: string, service: string) {
497
+ return createObserve({
498
+ userId,
499
+ sessionId,
500
+ metadata: { service },
501
+ tags: [service],
502
+ });
172
503
  }
173
504
  ```
174
505
 
175
- ### AWS Lambda
506
+ **REST API Server:**
176
507
 
177
508
  ```typescript
178
- import { init, observe, flush } from '@lelemondev/sdk';
179
- import OpenAI from 'openai';
509
+ // api-server.ts
510
+ import { createUserObserve } from './utils/observe-context';
180
511
 
181
- init({ apiKey: process.env.LELEMON_API_KEY });
182
- const openai = observe(new OpenAI());
512
+ app.post('/chat/start', async (req, res) => {
513
+ const { userId, sessionId } = req.body;
514
+
515
+ const observe = createUserObserve(userId, sessionId, 'api');
516
+ const openai = observe(new OpenAI());
183
517
 
184
- export const handler = async (event) => {
185
- const response = await openai.chat.completions.create({
518
+ const result = await openai.chat.completions.create({
186
519
  model: 'gpt-4',
187
- messages: [{ role: 'user', content: event.body }],
520
+ messages: [{ role: 'user', content: req.body.message }],
521
+ });
522
+
523
+ res.json({
524
+ response: result.choices[0].message,
525
+ sessionId, // Return sessionId for client to use in WebSocket
526
+ });
527
+ });
528
+ ```
529
+
530
+ **WebSocket Server:**
531
+
532
+ ```typescript
533
+ // ws-server.ts
534
+ import { createUserObserve } from './utils/observe-context';
535
+
536
+ wss.on('connection', (ws, req) => {
537
+ // Get userId and sessionId from connection (query params, auth token, etc.)
538
+ const userId = getUserFromToken(req);
539
+ const sessionId = new URL(req.url, 'http://localhost').searchParams.get('sessionId');
540
+
541
+ // Create observe function for this connection
542
+ const observe = createUserObserve(userId, sessionId, 'websocket');
543
+ const gemini = observe(new GoogleGenerativeAI(apiKey));
544
+
545
+ ws.on('message', async (data) => {
546
+ const { message } = JSON.parse(data);
547
+
548
+ // This trace will be linked to the same session as the API calls
549
+ const model = gemini.getGenerativeModel({ model: 'gemini-pro' });
550
+ const result = await model.generateContent(message);
551
+
552
+ ws.send(JSON.stringify({ response: result.response.text() }));
553
+ });
554
+ });
555
+ ```
556
+
557
+ **Background Worker / Queue Consumer:**
558
+
559
+ ```typescript
560
+ // worker.ts
561
+ import { createUserObserve } from './utils/observe-context';
562
+
563
+ queue.process('ai-task', async (job) => {
564
+ const { userId, sessionId, prompt } = job.data;
565
+
566
+ const observe = createUserObserve(userId, sessionId, 'worker');
567
+ const bedrock = observe(new BedrockRuntimeClient({}));
568
+
569
+ const command = new ConverseCommand({
570
+ modelId: 'anthropic.claude-3-sonnet-20240229-v1:0',
571
+ messages: [{ role: 'user', content: [{ text: prompt }] }],
188
572
  });
189
573
 
190
- await flush(); // Always flush before Lambda ends
191
- return { statusCode: 200, body: JSON.stringify(response) };
192
- };
574
+ const result = await bedrock.send(command);
575
+ return result.output.message.content[0].text;
576
+ });
577
+ ```
578
+
579
+ **Client-side (passing sessionId between services):**
580
+
581
+ ```typescript
582
+ // Frontend - maintains sessionId across API and WebSocket
583
+ const sessionId = crypto.randomUUID(); // Or from your session management
584
+
585
+ // REST API call
586
+ const response = await fetch('/chat/start', {
587
+ method: 'POST',
588
+ body: JSON.stringify({ userId, sessionId, message: 'Hello' }),
589
+ });
590
+
591
+ // WebSocket connection with same sessionId
592
+ const ws = new WebSocket(`wss://your-app.com/ws?sessionId=${sessionId}`);
593
+
594
+ // Both API and WebSocket traces will be grouped under the same session
595
+ ```
596
+
597
+ **Viewing in Dashboard:**
598
+
599
+ With consistent `userId` and `sessionId` across services, you can:
600
+ - See a user's complete journey across API → WebSocket → Background jobs
601
+ - Filter by service (`metadata.service`) to isolate issues
602
+ - Track a conversation that spans multiple services
603
+
604
+ ### What This Enables
605
+
606
+ With proper user/session tracking you can:
607
+
608
+ - **Filter traces by user** - See all LLM calls from a specific user
609
+ - **View full conversations** - Group all calls in a session together
610
+ - **Debug issues** - Find exactly what happened for a specific user request
611
+ - **Analyze usage patterns** - Understand how different user segments use your AI features
612
+ - **Cost attribution** - Track token usage per user, tenant, or feature
613
+
614
+ ## Streaming
615
+
616
+ All providers support streaming:
617
+
618
+ ```typescript
619
+ const stream = await openai.chat.completions.create({
620
+ model: 'gpt-4',
621
+ messages: [{ role: 'user', content: 'Hello!' }],
622
+ stream: true,
623
+ });
624
+
625
+ for await (const chunk of stream) {
626
+ process.stdout.write(chunk.choices[0]?.delta?.content || '');
627
+ }
628
+ // Trace captured automatically when stream completes
193
629
  ```
194
630
 
195
631
  ## What Gets Traced
196
632
 
197
633
  Each LLM call automatically captures:
198
634
 
199
- - **Provider** - openai, anthropic
200
- - **Model** - gpt-4, claude-3-opus, etc.
635
+ - **Provider** - openai, anthropic, bedrock, gemini
636
+ - **Model** - gpt-4, claude-3-opus, gemini-pro, etc.
201
637
  - **Input** - Messages/prompt (sanitized)
202
638
  - **Output** - Response content
203
- - **Tokens** - Input and output token counts
639
+ - **Tokens** - Input and output counts
204
640
  - **Duration** - Request latency in ms
205
641
  - **Status** - success or error
206
642
  - **Streaming** - Whether streaming was used
@@ -211,14 +647,21 @@ The SDK automatically sanitizes sensitive data:
211
647
 
212
648
  - API keys and tokens are redacted
213
649
  - Large payloads are truncated
214
- - Errors are captured without stack traces
650
+ - Errors are captured safely
215
651
 
216
652
  ## Environment Variables
217
653
 
218
654
  | Variable | Description |
219
655
  |----------|-------------|
220
- | `LELEMON_API_KEY` | Your Lelemon API key (starts with `le_`) |
656
+ | `LELEMON_API_KEY` | Your API key (starts with `le_`) |
221
657
 
222
658
  ## License
223
659
 
224
660
  MIT
661
+
662
+ ## Sources
663
+
664
+ Framework integration patterns based on:
665
+ - [Next.js after() docs](https://nextjs.org/docs/app/api-reference/functions/after)
666
+ - [Vercel waitUntil](https://www.inngest.com/blog/vercel-cloudflare-wait-until)
667
+ - [Langfuse SDK patterns](https://langfuse.com/docs/observability/sdk/typescript/advanced-usage)