@lelemondev/sdk 0.4.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.
- package/README.md +438 -10
- package/dist/{express-Cmb_A4sI.d.mts → express-Dt5wT6_n.d.mts} +24 -4
- package/dist/{express-Cmb_A4sI.d.ts → express-Dt5wT6_n.d.ts} +24 -4
- package/dist/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/express.js.map +1 -1
- package/dist/express.mjs.map +1 -1
- package/dist/{hono-ChTmQk_V.d.mts → hono-Dzmu77iW.d.mts} +23 -4
- package/dist/{hono-ChTmQk_V.d.ts → hono-Dzmu77iW.d.ts} +23 -4
- package/dist/hono.d.mts +1 -1
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js.map +1 -1
- package/dist/hono.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +948 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +948 -2
- package/dist/index.mjs.map +1 -1
- package/dist/integrations.d.mts +4 -4
- package/dist/integrations.d.ts +4 -4
- package/dist/integrations.js.map +1 -1
- package/dist/integrations.mjs.map +1 -1
- package/dist/{lambda-DQmEfWXC.d.mts → lambda-CAuiF9dH.d.mts} +7 -3
- package/dist/{lambda-DQmEfWXC.d.ts → lambda-CAuiF9dH.d.ts} +7 -3
- package/dist/lambda.d.mts +1 -1
- package/dist/lambda.d.ts +1 -1
- package/dist/lambda.js.map +1 -1
- package/dist/lambda.mjs.map +1 -1
- package/dist/{next-0nso_zEN.d.mts → next-BC9PmEho.d.mts} +8 -2
- package/dist/{next-0nso_zEN.d.ts → next-BC9PmEho.d.ts} +8 -2
- package/dist/next.d.mts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js.map +1 -1
- package/dist/next.mjs.map +1 -1
- package/package.json +31 -8
package/README.md
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
Automatic LLM observability for Node.js. Wrap your client, everything is traced.
|
|
8
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
|
+
```
|
|
15
|
+
|
|
9
16
|
## Features
|
|
10
17
|
|
|
11
18
|
- **Automatic Tracing** - Wrap your client, all calls are traced
|
|
@@ -39,8 +46,150 @@ const response = await openai.chat.completions.create({
|
|
|
39
46
|
});
|
|
40
47
|
```
|
|
41
48
|
|
|
49
|
+
## Supported Providers
|
|
50
|
+
|
|
51
|
+
| Provider | Status | Methods |
|
|
52
|
+
|----------|--------|---------|
|
|
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
|
+
|
|
42
189
|
## Framework Integrations
|
|
43
190
|
|
|
191
|
+
Framework integrations automate the `flush()` call so you don't have to think about it.
|
|
192
|
+
|
|
44
193
|
### Next.js App Router
|
|
45
194
|
|
|
46
195
|
```typescript
|
|
@@ -138,13 +287,6 @@ app.post('/chat', async (c) => {
|
|
|
138
287
|
export default app;
|
|
139
288
|
```
|
|
140
289
|
|
|
141
|
-
## Supported Providers
|
|
142
|
-
|
|
143
|
-
| Provider | Status | Methods |
|
|
144
|
-
|----------|--------|---------|
|
|
145
|
-
| OpenAI | Supported | `chat.completions.create()`, `completions.create()`, `embeddings.create()` |
|
|
146
|
-
| Anthropic | Supported | `messages.create()`, `messages.stream()` |
|
|
147
|
-
|
|
148
290
|
## API Reference
|
|
149
291
|
|
|
150
292
|
### `init(config)`
|
|
@@ -183,9 +325,295 @@ Manually flush pending traces. Use in serverless without framework integration.
|
|
|
183
325
|
await flush();
|
|
184
326
|
```
|
|
185
327
|
|
|
328
|
+
## User & Session Tracking
|
|
329
|
+
|
|
330
|
+
Track which user generated each trace and group related conversations.
|
|
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
|
|
351
|
+
```typescript
|
|
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
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
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
|
+
```
|
|
376
|
+
|
|
377
|
+
### Basic Usage
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
const openai = observe(new OpenAI(), {
|
|
381
|
+
userId: 'user-123',
|
|
382
|
+
sessionId: 'conversation-abc',
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### In an API Endpoint
|
|
387
|
+
|
|
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
|
+
});
|
|
400
|
+
|
|
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
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Reusable Context with createObserve()
|
|
411
|
+
|
|
412
|
+
When you have multiple LLM clients or make calls from different places, use `createObserve()` to avoid repeating context:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import { createObserve } from '@lelemondev/sdk';
|
|
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
|
+
});
|
|
426
|
+
|
|
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({}));
|
|
431
|
+
|
|
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
|
+
```
|
|
436
|
+
|
|
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
|
+
},
|
|
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:
|
|
490
|
+
|
|
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
|
+
});
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**REST API Server:**
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
// api-server.ts
|
|
510
|
+
import { createUserObserve } from './utils/observe-context';
|
|
511
|
+
|
|
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());
|
|
517
|
+
|
|
518
|
+
const result = await openai.chat.completions.create({
|
|
519
|
+
model: 'gpt-4',
|
|
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 }] }],
|
|
572
|
+
});
|
|
573
|
+
|
|
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
|
+
|
|
186
614
|
## Streaming
|
|
187
615
|
|
|
188
|
-
|
|
616
|
+
All providers support streaming:
|
|
189
617
|
|
|
190
618
|
```typescript
|
|
191
619
|
const stream = await openai.chat.completions.create({
|
|
@@ -204,8 +632,8 @@ for await (const chunk of stream) {
|
|
|
204
632
|
|
|
205
633
|
Each LLM call automatically captures:
|
|
206
634
|
|
|
207
|
-
- **Provider** - openai, anthropic
|
|
208
|
-
- **Model** - gpt-4, claude-3-opus, etc.
|
|
635
|
+
- **Provider** - openai, anthropic, bedrock, gemini
|
|
636
|
+
- **Model** - gpt-4, claude-3-opus, gemini-pro, etc.
|
|
209
637
|
- **Input** - Messages/prompt (sanitized)
|
|
210
638
|
- **Output** - Response content
|
|
211
639
|
- **Tokens** - Input and output counts
|
|
@@ -10,15 +10,31 @@
|
|
|
10
10
|
* const app = express();
|
|
11
11
|
* app.use(createMiddleware());
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* Minimal Express request type (avoids requiring express as dependency)
|
|
15
|
+
*/
|
|
13
16
|
interface ExpressRequest {
|
|
14
17
|
[key: string]: unknown;
|
|
15
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Minimal Express response type (avoids requiring express as dependency)
|
|
21
|
+
*/
|
|
16
22
|
interface ExpressResponse {
|
|
17
23
|
on(event: 'finish' | 'close' | 'error', listener: () => void): this;
|
|
18
24
|
[key: string]: unknown;
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Express next function type
|
|
28
|
+
*/
|
|
29
|
+
type ExpressNextFunction = (error?: unknown) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Express middleware function type
|
|
32
|
+
*
|
|
33
|
+
* @param req - Express request object
|
|
34
|
+
* @param res - Express response object
|
|
35
|
+
* @param next - Next function to pass control
|
|
36
|
+
*/
|
|
37
|
+
type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
|
|
22
38
|
/**
|
|
23
39
|
* Create Express middleware for automatic trace flushing
|
|
24
40
|
*
|
|
@@ -39,9 +55,13 @@ type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: NextF
|
|
|
39
55
|
*/
|
|
40
56
|
declare function createMiddleware(): ExpressMiddleware;
|
|
41
57
|
|
|
58
|
+
type express_ExpressMiddleware = ExpressMiddleware;
|
|
59
|
+
type express_ExpressNextFunction = ExpressNextFunction;
|
|
60
|
+
type express_ExpressRequest = ExpressRequest;
|
|
61
|
+
type express_ExpressResponse = ExpressResponse;
|
|
42
62
|
declare const express_createMiddleware: typeof createMiddleware;
|
|
43
63
|
declare namespace express {
|
|
44
|
-
export { express_createMiddleware as createMiddleware };
|
|
64
|
+
export { type express_ExpressMiddleware as ExpressMiddleware, type express_ExpressNextFunction as ExpressNextFunction, type express_ExpressRequest as ExpressRequest, type express_ExpressResponse as ExpressResponse, express_createMiddleware as createMiddleware };
|
|
45
65
|
}
|
|
46
66
|
|
|
47
|
-
export {
|
|
67
|
+
export { type ExpressRequest as E, type ExpressResponse as a, type ExpressNextFunction as b, type ExpressMiddleware as c, createMiddleware as d, express as e };
|
|
@@ -10,15 +10,31 @@
|
|
|
10
10
|
* const app = express();
|
|
11
11
|
* app.use(createMiddleware());
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* Minimal Express request type (avoids requiring express as dependency)
|
|
15
|
+
*/
|
|
13
16
|
interface ExpressRequest {
|
|
14
17
|
[key: string]: unknown;
|
|
15
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Minimal Express response type (avoids requiring express as dependency)
|
|
21
|
+
*/
|
|
16
22
|
interface ExpressResponse {
|
|
17
23
|
on(event: 'finish' | 'close' | 'error', listener: () => void): this;
|
|
18
24
|
[key: string]: unknown;
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Express next function type
|
|
28
|
+
*/
|
|
29
|
+
type ExpressNextFunction = (error?: unknown) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Express middleware function type
|
|
32
|
+
*
|
|
33
|
+
* @param req - Express request object
|
|
34
|
+
* @param res - Express response object
|
|
35
|
+
* @param next - Next function to pass control
|
|
36
|
+
*/
|
|
37
|
+
type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
|
|
22
38
|
/**
|
|
23
39
|
* Create Express middleware for automatic trace flushing
|
|
24
40
|
*
|
|
@@ -39,9 +55,13 @@ type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: NextF
|
|
|
39
55
|
*/
|
|
40
56
|
declare function createMiddleware(): ExpressMiddleware;
|
|
41
57
|
|
|
58
|
+
type express_ExpressMiddleware = ExpressMiddleware;
|
|
59
|
+
type express_ExpressNextFunction = ExpressNextFunction;
|
|
60
|
+
type express_ExpressRequest = ExpressRequest;
|
|
61
|
+
type express_ExpressResponse = ExpressResponse;
|
|
42
62
|
declare const express_createMiddleware: typeof createMiddleware;
|
|
43
63
|
declare namespace express {
|
|
44
|
-
export { express_createMiddleware as createMiddleware };
|
|
64
|
+
export { type express_ExpressMiddleware as ExpressMiddleware, type express_ExpressNextFunction as ExpressNextFunction, type express_ExpressRequest as ExpressRequest, type express_ExpressResponse as ExpressResponse, express_createMiddleware as createMiddleware };
|
|
45
65
|
}
|
|
46
66
|
|
|
47
|
-
export {
|
|
67
|
+
export { type ExpressRequest as E, type ExpressResponse as a, type ExpressNextFunction as b, type ExpressMiddleware as c, createMiddleware as d, express as e };
|
package/dist/express.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { c as createMiddleware } from './express-
|
|
1
|
+
export { c as ExpressMiddleware, b as ExpressNextFunction, E as ExpressRequest, a as ExpressResponse, d as createMiddleware } from './express-Dt5wT6_n.mjs';
|
package/dist/express.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { c as createMiddleware } from './express-
|
|
1
|
+
export { c as ExpressMiddleware, b as ExpressNextFunction, E as ExpressRequest, a as ExpressResponse, d as createMiddleware } from './express-Dt5wT6_n.js';
|
package/dist/express.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/integrations/express.ts"],"names":[],"mappings":";;;;AAuEA,eAAsB,KAAA,GAAuB;AAI7C;;;
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/integrations/express.ts"],"names":[],"mappings":";;;;AAuEA,eAAsB,KAAA,GAAuB;AAI7C;;;ACDO,SAAS,gBAAA,GAAsC;AACpD,EAAA,OAAO,CAAC,IAAA,EAAM,GAAA,EAAK,IAAA,KAAS;AAE1B,IAAA,GAAA,CAAI,EAAA,CAAG,UAAU,MAAM;AACrB,MAAA,KAAA,EAAM,CAAE,MAAM,MAAM;AAAA,MAEpB,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AACF","file":"express.js","sourcesContent":["/**\n * Global Configuration\n *\n * Manages SDK configuration and transport instance.\n */\n\nimport type { LelemonConfig } from './types';\nimport { Transport } from './transport';\n\n// ─────────────────────────────────────────────────────────────\n// Global State\n// ─────────────────────────────────────────────────────────────\n\nlet globalConfig: LelemonConfig = {};\nlet globalTransport: Transport | null = null;\nlet initialized = false;\n\n// ─────────────────────────────────────────────────────────────\n// Configuration\n// ─────────────────────────────────────────────────────────────\n\nconst DEFAULT_ENDPOINT = 'https://api.lelemon.dev';\n\n/**\n * Initialize the SDK\n * Call once at app startup\n */\nexport function init(config: LelemonConfig = {}): void {\n globalConfig = config;\n globalTransport = createTransport(config);\n initialized = true;\n}\n\n/**\n * Get current config\n */\nexport function getConfig(): LelemonConfig {\n return globalConfig;\n}\n\n/**\n * Check if SDK is initialized\n */\nexport function isInitialized(): boolean {\n return initialized;\n}\n\n/**\n * Check if SDK is enabled\n */\nexport function isEnabled(): boolean {\n return getTransport().isEnabled();\n}\n\n// ─────────────────────────────────────────────────────────────\n// Transport\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Get or create transport instance\n */\nexport function getTransport(): Transport {\n if (!globalTransport) {\n globalTransport = createTransport(globalConfig);\n }\n return globalTransport;\n}\n\n/**\n * Flush all pending traces\n */\nexport async function flush(): Promise<void> {\n if (globalTransport) {\n await globalTransport.flush();\n }\n}\n\n/**\n * Create transport instance\n */\nfunction createTransport(config: LelemonConfig): Transport {\n const apiKey = config.apiKey ?? getEnvVar('LELEMON_API_KEY');\n\n if (!apiKey && !config.disabled) {\n console.warn(\n '[Lelemon] No API key provided. Set apiKey in init() or LELEMON_API_KEY env var. Tracing disabled.'\n );\n }\n\n return new Transport({\n apiKey: apiKey ?? '',\n endpoint: config.endpoint ?? DEFAULT_ENDPOINT,\n debug: config.debug ?? false,\n disabled: config.disabled ?? !apiKey,\n batchSize: config.batchSize,\n flushIntervalMs: config.flushIntervalMs,\n requestTimeoutMs: config.requestTimeoutMs,\n });\n}\n\n/**\n * Get environment variable (works in Node and edge)\n */\nfunction getEnvVar(name: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[name];\n }\n return undefined;\n}\n","/**\n * Express Integration\n *\n * Middleware that automatically flushes traces when response finishes.\n *\n * @example\n * import express from 'express';\n * import { createMiddleware } from '@lelemondev/sdk/express';\n *\n * const app = express();\n * app.use(createMiddleware());\n */\n\nimport { flush } from '../core/config';\n\n// ─────────────────────────────────────────────────────────────\n// Types (minimal to avoid requiring express as dependency)\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Minimal Express request type (avoids requiring express as dependency)\n */\nexport interface ExpressRequest {\n [key: string]: unknown;\n}\n\n/**\n * Minimal Express response type (avoids requiring express as dependency)\n */\nexport interface ExpressResponse {\n on(event: 'finish' | 'close' | 'error', listener: () => void): this;\n [key: string]: unknown;\n}\n\n/**\n * Express next function type\n */\nexport type ExpressNextFunction = (error?: unknown) => void;\n\n/**\n * Express middleware function type\n *\n * @param req - Express request object\n * @param res - Express response object\n * @param next - Next function to pass control\n */\nexport type ExpressMiddleware = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: ExpressNextFunction\n) => void;\n\n// ─────────────────────────────────────────────────────────────\n// Middleware\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Create Express middleware for automatic trace flushing\n *\n * Flushes traces when the response finishes (after res.send/res.json).\n * This is fire-and-forget and doesn't block the response.\n *\n * @returns Express middleware function\n *\n * @example\n * // Global middleware\n * app.use(createMiddleware());\n *\n * @example\n * // Per-route middleware\n * app.post('/chat', createMiddleware(), async (req, res) => {\n * res.json({ ok: true });\n * });\n */\nexport function createMiddleware(): ExpressMiddleware {\n return (_req, res, next) => {\n // Flush when response is finished (after headers + body sent)\n res.on('finish', () => {\n flush().catch(() => {\n // Silently ignore flush errors - fire and forget\n });\n });\n\n next();\n };\n}\n"]}
|
package/dist/express.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/integrations/express.ts"],"names":[],"mappings":";;AAuEA,eAAsB,KAAA,GAAuB;AAI7C;;;
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/integrations/express.ts"],"names":[],"mappings":";;AAuEA,eAAsB,KAAA,GAAuB;AAI7C;;;ACDO,SAAS,gBAAA,GAAsC;AACpD,EAAA,OAAO,CAAC,IAAA,EAAM,GAAA,EAAK,IAAA,KAAS;AAE1B,IAAA,GAAA,CAAI,EAAA,CAAG,UAAU,MAAM;AACrB,MAAA,KAAA,EAAM,CAAE,MAAM,MAAM;AAAA,MAEpB,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AACF","file":"express.mjs","sourcesContent":["/**\n * Global Configuration\n *\n * Manages SDK configuration and transport instance.\n */\n\nimport type { LelemonConfig } from './types';\nimport { Transport } from './transport';\n\n// ─────────────────────────────────────────────────────────────\n// Global State\n// ─────────────────────────────────────────────────────────────\n\nlet globalConfig: LelemonConfig = {};\nlet globalTransport: Transport | null = null;\nlet initialized = false;\n\n// ─────────────────────────────────────────────────────────────\n// Configuration\n// ─────────────────────────────────────────────────────────────\n\nconst DEFAULT_ENDPOINT = 'https://api.lelemon.dev';\n\n/**\n * Initialize the SDK\n * Call once at app startup\n */\nexport function init(config: LelemonConfig = {}): void {\n globalConfig = config;\n globalTransport = createTransport(config);\n initialized = true;\n}\n\n/**\n * Get current config\n */\nexport function getConfig(): LelemonConfig {\n return globalConfig;\n}\n\n/**\n * Check if SDK is initialized\n */\nexport function isInitialized(): boolean {\n return initialized;\n}\n\n/**\n * Check if SDK is enabled\n */\nexport function isEnabled(): boolean {\n return getTransport().isEnabled();\n}\n\n// ─────────────────────────────────────────────────────────────\n// Transport\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Get or create transport instance\n */\nexport function getTransport(): Transport {\n if (!globalTransport) {\n globalTransport = createTransport(globalConfig);\n }\n return globalTransport;\n}\n\n/**\n * Flush all pending traces\n */\nexport async function flush(): Promise<void> {\n if (globalTransport) {\n await globalTransport.flush();\n }\n}\n\n/**\n * Create transport instance\n */\nfunction createTransport(config: LelemonConfig): Transport {\n const apiKey = config.apiKey ?? getEnvVar('LELEMON_API_KEY');\n\n if (!apiKey && !config.disabled) {\n console.warn(\n '[Lelemon] No API key provided. Set apiKey in init() or LELEMON_API_KEY env var. Tracing disabled.'\n );\n }\n\n return new Transport({\n apiKey: apiKey ?? '',\n endpoint: config.endpoint ?? DEFAULT_ENDPOINT,\n debug: config.debug ?? false,\n disabled: config.disabled ?? !apiKey,\n batchSize: config.batchSize,\n flushIntervalMs: config.flushIntervalMs,\n requestTimeoutMs: config.requestTimeoutMs,\n });\n}\n\n/**\n * Get environment variable (works in Node and edge)\n */\nfunction getEnvVar(name: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[name];\n }\n return undefined;\n}\n","/**\n * Express Integration\n *\n * Middleware that automatically flushes traces when response finishes.\n *\n * @example\n * import express from 'express';\n * import { createMiddleware } from '@lelemondev/sdk/express';\n *\n * const app = express();\n * app.use(createMiddleware());\n */\n\nimport { flush } from '../core/config';\n\n// ─────────────────────────────────────────────────────────────\n// Types (minimal to avoid requiring express as dependency)\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Minimal Express request type (avoids requiring express as dependency)\n */\nexport interface ExpressRequest {\n [key: string]: unknown;\n}\n\n/**\n * Minimal Express response type (avoids requiring express as dependency)\n */\nexport interface ExpressResponse {\n on(event: 'finish' | 'close' | 'error', listener: () => void): this;\n [key: string]: unknown;\n}\n\n/**\n * Express next function type\n */\nexport type ExpressNextFunction = (error?: unknown) => void;\n\n/**\n * Express middleware function type\n *\n * @param req - Express request object\n * @param res - Express response object\n * @param next - Next function to pass control\n */\nexport type ExpressMiddleware = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: ExpressNextFunction\n) => void;\n\n// ─────────────────────────────────────────────────────────────\n// Middleware\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Create Express middleware for automatic trace flushing\n *\n * Flushes traces when the response finishes (after res.send/res.json).\n * This is fire-and-forget and doesn't block the response.\n *\n * @returns Express middleware function\n *\n * @example\n * // Global middleware\n * app.use(createMiddleware());\n *\n * @example\n * // Per-route middleware\n * app.post('/chat', createMiddleware(), async (req, res) => {\n * res.json({ ok: true });\n * });\n */\nexport function createMiddleware(): ExpressMiddleware {\n return (_req, res, next) => {\n // Flush when response is finished (after headers + body sent)\n res.on('finish', () => {\n flush().catch(() => {\n // Silently ignore flush errors - fire and forget\n });\n });\n\n next();\n };\n}\n"]}
|