@lelemondev/sdk 0.2.1 → 0.3.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 CHANGED
@@ -4,15 +4,16 @@
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
- Fire-and-forget LLM observability for Node.js. Track your AI agents with 3 lines of code.
7
+ Automatic LLM observability for Node.js. Track your AI agents with zero code changes.
8
8
 
9
9
  ## Features
10
10
 
11
- - 🔥 **Fire-and-forget** - Never blocks your code
12
- - 📦 **Auto-batching** - Efficient network usage
13
- - **Zero config** - Works out of the box
14
- - 🛡️ **Error-safe** - Never crashes your app
15
- - 🌐 **Serverless-ready** - Built-in flush for Lambda/Vercel
11
+ - **Automatic Tracing** - Wrap your client, everything is traced
12
+ - **Fire-and-forget** - Never blocks your code
13
+ - **Auto-batching** - Efficient network usage
14
+ - **Streaming Support** - Full support for streaming responses
15
+ - **Type-safe** - Preserves your client's TypeScript types
16
+ - **Serverless-ready** - Built-in flush for Lambda/Vercel
16
17
 
17
18
  ## Installation
18
19
 
@@ -23,26 +24,34 @@ npm install @lelemondev/sdk
23
24
  ## Quick Start
24
25
 
25
26
  ```typescript
26
- import { init, trace, flush } from '@lelemondev/sdk';
27
+ import { init, observe, flush } from '@lelemondev/sdk';
28
+ import OpenAI from 'openai';
27
29
 
28
- // Initialize once at app startup
30
+ // 1. Initialize once at app startup
29
31
  init({ apiKey: process.env.LELEMON_API_KEY });
30
32
 
31
- // Trace your agent (fire-and-forget, no awaits needed!)
32
- const t = trace({ input: userMessage });
33
+ // 2. Wrap your client
34
+ const openai = observe(new OpenAI());
33
35
 
34
- try {
35
- const result = await myAgent(userMessage);
36
- t.success(result.messages); // Sync, doesn't block
37
- } catch (error) {
38
- t.error(error); // Sync, doesn't block
39
- throw error;
40
- }
36
+ // 3. Use normally - all calls are traced automatically
37
+ const response = await openai.chat.completions.create({
38
+ model: 'gpt-4',
39
+ messages: [{ role: 'user', content: 'Hello!' }],
40
+ });
41
41
 
42
- // For serverless: flush before response
42
+ // 4. For serverless: flush before response
43
43
  await flush();
44
44
  ```
45
45
 
46
+ That's it! No manual tracing code needed.
47
+
48
+ ## Supported Providers
49
+
50
+ | Provider | Status | Methods |
51
+ |----------|--------|---------|
52
+ | OpenAI | Supported | `chat.completions.create()`, `completions.create()`, `embeddings.create()` |
53
+ | Anthropic | Supported | `messages.create()`, `messages.stream()` |
54
+
46
55
  ## API Reference
47
56
 
48
57
  ### `init(config)`
@@ -54,58 +63,88 @@ init({
54
63
  apiKey: 'le_xxx', // Required (or set LELEMON_API_KEY env var)
55
64
  endpoint: 'https://...', // Optional, custom endpoint
56
65
  debug: false, // Optional, enable debug logs
66
+ disabled: false, // Optional, disable all tracing
57
67
  batchSize: 10, // Optional, items per batch
58
68
  flushIntervalMs: 1000, // Optional, auto-flush interval
69
+ requestTimeoutMs: 10000, // Optional, HTTP request timeout
59
70
  });
60
71
  ```
61
72
 
62
- ### `trace(options)`
73
+ ### `observe(client, options?)`
63
74
 
64
- Start a new trace. Returns a `Trace` object.
75
+ Wrap an LLM client with automatic tracing. Returns the same client type.
65
76
 
66
77
  ```typescript
67
- const t = trace({
68
- input: userMessage, // Required, the input to your agent
78
+ import Anthropic from '@anthropic-ai/sdk';
79
+
80
+ const anthropic = observe(new Anthropic(), {
69
81
  sessionId: 'session-123', // Optional, group related traces
70
82
  userId: 'user-456', // Optional, identify the user
71
- name: 'chat-agent', // Optional, name for this trace
72
- metadata: { ... }, // Optional, custom metadata
73
- tags: ['prod', 'v2'], // Optional, tags for filtering
83
+ metadata: { source: 'api' }, // Optional, custom metadata
84
+ tags: ['production'], // Optional, tags for filtering
74
85
  });
75
86
  ```
76
87
 
77
- ### `Trace.success(messages)`
88
+ ### `createObserve(defaultOptions)`
78
89
 
79
- Complete the trace successfully. Fire-and-forget (no await needed).
90
+ Create a scoped observe function with preset options.
80
91
 
81
92
  ```typescript
82
- t.success(result.messages);
93
+ const observeWithSession = createObserve({
94
+ sessionId: 'session-123',
95
+ userId: 'user-456',
96
+ });
97
+
98
+ const openai = observeWithSession(new OpenAI());
99
+ const anthropic = observeWithSession(new Anthropic());
83
100
  ```
84
101
 
85
- ### `Trace.error(error, messages?)`
102
+ ### `flush()`
86
103
 
87
- Complete the trace with an error. Fire-and-forget (no await needed).
104
+ Wait for all pending traces to be sent. Use in serverless environments.
88
105
 
89
106
  ```typescript
90
- t.error(error);
91
- t.error(error, partialMessages); // Include messages up to failure
107
+ await flush();
92
108
  ```
93
109
 
94
- ### `Trace.log(response)`
110
+ ### `isEnabled()`
95
111
 
96
- Log an LLM response for token tracking (optional).
112
+ Check if tracing is enabled.
97
113
 
98
114
  ```typescript
99
- const response = await openai.chat.completions.create(...);
100
- t.log(response); // Extracts model, tokens automatically
115
+ if (isEnabled()) {
116
+ console.log('Tracing is active');
117
+ }
101
118
  ```
102
119
 
103
- ### `flush()`
120
+ ## Streaming Support
104
121
 
105
- Wait for all pending traces to be sent. Use in serverless environments.
122
+ Both OpenAI and Anthropic streaming are fully supported:
106
123
 
107
124
  ```typescript
108
- await flush();
125
+ // OpenAI streaming
126
+ const stream = await openai.chat.completions.create({
127
+ model: 'gpt-4',
128
+ messages: [{ role: 'user', content: 'Hello!' }],
129
+ stream: true,
130
+ });
131
+
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
136
+
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
+ });
143
+
144
+ for await (const event of stream) {
145
+ // Process events
146
+ }
147
+ // Trace is captured automatically
109
148
  ```
110
149
 
111
150
  ## Serverless Usage
@@ -114,53 +153,65 @@ await flush();
114
153
 
115
154
  ```typescript
116
155
  import { waitUntil } from '@vercel/functions';
117
- import { trace, flush } from '@lelemondev/sdk';
156
+ import { init, observe, flush } from '@lelemondev/sdk';
157
+ import OpenAI from 'openai';
158
+
159
+ init({ apiKey: process.env.LELEMON_API_KEY });
160
+ const openai = observe(new OpenAI());
118
161
 
119
162
  export async function POST(req: Request) {
120
- const t = trace({ input: message });
121
-
122
- try {
123
- const result = await myAgent(message);
124
- t.success(result);
125
- return Response.json(result);
126
- } catch (error) {
127
- t.error(error);
128
- throw error;
129
- } finally {
130
- waitUntil(flush()); // Flush after response
131
- }
163
+ const { message } = await req.json();
164
+
165
+ const response = await openai.chat.completions.create({
166
+ model: 'gpt-4',
167
+ messages: [{ role: 'user', content: message }],
168
+ });
169
+
170
+ waitUntil(flush()); // Flush after response
171
+ return Response.json(response.choices[0].message);
132
172
  }
133
173
  ```
134
174
 
135
175
  ### AWS Lambda
136
176
 
137
177
  ```typescript
138
- import { trace, flush } from '@lelemondev/sdk';
178
+ import { init, observe, flush } from '@lelemondev/sdk';
179
+ import OpenAI from 'openai';
180
+
181
+ init({ apiKey: process.env.LELEMON_API_KEY });
182
+ const openai = observe(new OpenAI());
139
183
 
140
184
  export const handler = async (event) => {
141
- const t = trace({ input: event.body });
142
-
143
- try {
144
- const result = await myAgent(event.body);
145
- t.success(result);
146
- return { statusCode: 200, body: JSON.stringify(result) };
147
- } catch (error) {
148
- t.error(error);
149
- throw error;
150
- } finally {
151
- await flush(); // Always flush before Lambda ends
152
- }
185
+ const response = await openai.chat.completions.create({
186
+ model: 'gpt-4',
187
+ messages: [{ role: 'user', content: event.body }],
188
+ });
189
+
190
+ await flush(); // Always flush before Lambda ends
191
+ return { statusCode: 200, body: JSON.stringify(response) };
153
192
  };
154
193
  ```
155
194
 
156
- ## Supported Providers
195
+ ## What Gets Traced
196
+
197
+ Each LLM call automatically captures:
198
+
199
+ - **Provider** - openai, anthropic
200
+ - **Model** - gpt-4, claude-3-opus, etc.
201
+ - **Input** - Messages/prompt (sanitized)
202
+ - **Output** - Response content
203
+ - **Tokens** - Input and output token counts
204
+ - **Duration** - Request latency in ms
205
+ - **Status** - success or error
206
+ - **Streaming** - Whether streaming was used
207
+
208
+ ## Security
209
+
210
+ The SDK automatically sanitizes sensitive data:
157
211
 
158
- | Provider | Auto-detected |
159
- |----------|---------------|
160
- | OpenAI | |
161
- | Anthropic | ✅ |
162
- | Google Gemini | ✅ |
163
- | AWS Bedrock | ✅ |
212
+ - API keys and tokens are redacted
213
+ - Large payloads are truncated
214
+ - Errors are captured without stack traces
164
215
 
165
216
  ## Environment Variables
166
217
 
@@ -170,4 +221,4 @@ export const handler = async (event) => {
170
221
 
171
222
  ## License
172
223
 
173
- MIT © [Lelemon](https://lelemon.dev)
224
+ MIT
package/dist/index.d.mts CHANGED
@@ -1,316 +1,88 @@
1
1
  /**
2
- * Lelemon SDK Types
3
- * Minimal, low-friction API for LLM observability
2
+ * Core types for Lelemon SDK
4
3
  */
5
4
  interface LelemonConfig {
6
- /**
7
- * API key for authentication (starts with 'le_')
8
- * Can also be set via LELEMON_API_KEY env var
9
- */
5
+ /** API key (or set LELEMON_API_KEY env var) */
10
6
  apiKey?: string;
11
- /**
12
- * API endpoint (default: https://api.lelemon.dev)
13
- */
7
+ /** API endpoint (default: https://api.lelemon.dev) */
14
8
  endpoint?: string;
15
- /**
16
- * Enable debug logging
17
- */
9
+ /** Enable debug logging */
18
10
  debug?: boolean;
19
- /**
20
- * Disable tracing (useful for testing)
21
- */
11
+ /** Disable tracing */
22
12
  disabled?: boolean;
23
- /**
24
- * Number of items to batch before sending (default: 10)
25
- */
13
+ /** Batch size before flush (default: 10) */
26
14
  batchSize?: number;
27
- /**
28
- * Interval in ms to flush pending items (default: 1000)
29
- */
15
+ /** Auto-flush interval in ms (default: 1000) */
30
16
  flushIntervalMs?: number;
31
- /**
32
- * Request timeout in ms (default: 10000)
33
- */
17
+ /** Request timeout in ms (default: 10000) */
34
18
  requestTimeoutMs?: number;
35
19
  }
36
- interface TraceOptions {
37
- /**
38
- * Initial input (user message, prompt, etc.)
39
- */
40
- input: unknown;
41
- /**
42
- * Session ID to group related traces
43
- */
20
+ type ProviderName = 'openai' | 'anthropic' | 'google' | 'bedrock' | 'unknown';
21
+ interface ObserveOptions {
22
+ /** Session ID to group related calls */
44
23
  sessionId?: string;
45
- /**
46
- * User ID for the end user
47
- */
24
+ /** User ID for the end user */
48
25
  userId?: string;
49
- /**
50
- * Custom metadata
51
- */
26
+ /** Custom metadata added to all traces */
52
27
  metadata?: Record<string, unknown>;
53
- /**
54
- * Tags for filtering
55
- */
28
+ /** Tags for filtering */
56
29
  tags?: string[];
57
- /**
58
- * Name for this trace (e.g., 'chat-agent', 'summarizer')
59
- */
60
- name?: string;
61
- }
62
- interface OpenAIMessage {
63
- role: 'system' | 'user' | 'assistant' | 'tool';
64
- content: string | null;
65
- tool_calls?: OpenAIToolCall[];
66
- tool_call_id?: string;
67
- }
68
- interface OpenAIToolCall {
69
- id: string;
70
- type: 'function';
71
- function: {
72
- name: string;
73
- arguments: string;
74
- };
75
- }
76
- interface AnthropicMessage {
77
- role: 'user' | 'assistant';
78
- content: string | AnthropicContent[];
79
- }
80
- interface AnthropicContent {
81
- type: 'text' | 'tool_use' | 'tool_result';
82
- text?: string;
83
- id?: string;
84
- name?: string;
85
- input?: unknown;
86
- tool_use_id?: string;
87
- content?: string;
88
- }
89
- type Message = OpenAIMessage | AnthropicMessage | Record<string, unknown>;
90
- interface ParsedTrace {
91
- systemPrompt?: string;
92
- userInput?: string;
93
- output?: string;
94
- llmCalls: ParsedLLMCall[];
95
- toolCalls: ParsedToolCall[];
96
- totalInputTokens: number;
97
- totalOutputTokens: number;
98
- models: string[];
99
- provider?: 'openai' | 'anthropic' | 'gemini' | 'bedrock' | 'unknown';
100
- }
101
- interface ParsedLLMCall {
102
- model?: string;
103
- provider?: string;
104
- inputTokens?: number;
105
- outputTokens?: number;
106
- input?: unknown;
107
- output?: unknown;
108
- toolCalls?: ParsedToolCall[];
109
- }
110
- interface ParsedToolCall {
111
- name: string;
112
- input: unknown;
113
- output?: unknown;
114
- }
115
- interface CreateTraceRequest {
116
- name?: string;
117
- sessionId?: string;
118
- userId?: string;
119
- input?: unknown;
120
- metadata?: Record<string, unknown>;
121
- tags?: string[];
122
- }
123
- interface CompleteTraceRequest {
124
- status: 'completed' | 'error';
125
- output?: unknown;
126
- errorMessage?: string;
127
- errorStack?: string;
128
- systemPrompt?: string;
129
- llmCalls?: ParsedLLMCall[];
130
- toolCalls?: ParsedToolCall[];
131
- models?: string[];
132
- totalInputTokens?: number;
133
- totalOutputTokens?: number;
134
- totalCostUsd?: number;
135
- durationMs?: number;
136
- metadata?: Record<string, unknown>;
137
- }
138
-
139
- /**
140
- * Transport layer with queue-based batching
141
- *
142
- * Features:
143
- * - Fire-and-forget API (sync enqueue)
144
- * - Automatic batching (by size or interval)
145
- * - Single flush promise (no duplicate requests)
146
- * - Graceful error handling (never crashes caller)
147
- * - Request timeout protection
148
- */
149
-
150
- interface TransportConfig {
151
- apiKey: string;
152
- endpoint: string;
153
- debug: boolean;
154
- disabled: boolean;
155
- batchSize?: number;
156
- flushIntervalMs?: number;
157
- requestTimeoutMs?: number;
158
- }
159
- declare class Transport {
160
- private readonly config;
161
- private queue;
162
- private flushPromise;
163
- private flushTimer;
164
- private pendingResolvers;
165
- private idCounter;
166
- constructor(config: TransportConfig);
167
- /**
168
- * Check if transport is enabled
169
- */
170
- isEnabled(): boolean;
171
- /**
172
- * Enqueue trace creation (returns promise that resolves to trace ID)
173
- */
174
- enqueueCreate(data: CreateTraceRequest): Promise<string | null>;
175
- /**
176
- * Enqueue trace completion (fire-and-forget)
177
- */
178
- enqueueComplete(traceId: string, data: CompleteTraceRequest): void;
179
- /**
180
- * Flush all pending items
181
- * Safe to call multiple times (deduplicates)
182
- */
183
- flush(): Promise<void>;
184
- /**
185
- * Get pending item count (for testing/debugging)
186
- */
187
- getPendingCount(): number;
188
- private generateTempId;
189
- private enqueue;
190
- private scheduleFlush;
191
- private cancelScheduledFlush;
192
- private sendBatch;
193
- private request;
194
- private log;
195
30
  }
196
31
 
197
32
  /**
198
- * Lelemon Tracer - Fire-and-forget LLM observability
199
- *
200
- * Usage:
201
- * const t = trace({ input: userMessage });
202
- * try {
203
- * const result = await myAgent(userMessage);
204
- * t.success(result.messages);
205
- * } catch (error) {
206
- * t.error(error);
207
- * throw error;
208
- * }
33
+ * Global Configuration
209
34
  *
210
- * For serverless:
211
- * await flush(); // Before response
35
+ * Manages SDK configuration and transport instance.
212
36
  */
213
37
 
214
38
  /**
215
- * Initialize the SDK (optional, will auto-init with env vars)
216
- *
217
- * @example
218
- * init({ apiKey: 'le_xxx' });
219
- * init({ apiKey: 'le_xxx', debug: true });
39
+ * Initialize the SDK
40
+ * Call once at app startup
220
41
  */
221
42
  declare function init(config?: LelemonConfig): void;
222
- /**
223
- * Start a new trace
224
- *
225
- * @example
226
- * const t = trace({ input: userMessage });
227
- * try {
228
- * const result = await myAgent(userMessage);
229
- * t.success(result.messages);
230
- * } catch (error) {
231
- * t.error(error);
232
- * throw error;
233
- * }
234
- */
235
- declare function trace(options: TraceOptions): Trace;
236
- /**
237
- * Flush all pending traces to the server
238
- * Call this before process exit in serverless environments
239
- *
240
- * @example
241
- * // In Next.js API route
242
- * export async function POST(req: Request) {
243
- * // ... your code with traces ...
244
- * await flush();
245
- * return Response.json(result);
246
- * }
247
- *
248
- * // With Vercel waitUntil
249
- * import { waitUntil } from '@vercel/functions';
250
- * waitUntil(flush());
251
- */
252
- declare function flush(): Promise<void>;
253
43
  /**
254
44
  * Check if SDK is enabled
255
45
  */
256
46
  declare function isEnabled(): boolean;
257
- declare class Trace {
258
- private id;
259
- private idPromise;
260
- private readonly transport;
261
- private readonly startTime;
262
- private readonly debug;
263
- private readonly disabled;
264
- private completed;
265
- private llmCalls;
266
- constructor(options: TraceOptions, transport: Transport, debug: boolean, disabled: boolean);
267
- /**
268
- * Log an LLM response for token tracking
269
- * Optional - use if you want per-call token counts
270
- */
271
- log(response: unknown): this;
272
- /**
273
- * Complete trace successfully (fire-and-forget)
274
- *
275
- * @param messages - Full message history (OpenAI/Anthropic format)
276
- */
277
- success(messages: unknown): void;
278
- /**
279
- * Complete trace with error (fire-and-forget)
280
- *
281
- * @param error - The error that occurred
282
- * @param messages - Optional message history up to failure
283
- */
284
- error(error: Error | unknown, messages?: unknown): void;
285
- /**
286
- * Get the trace ID (may be null if not yet created or failed)
287
- */
288
- getId(): string | null;
289
- /**
290
- * Wait for trace ID to be available
291
- */
292
- waitForId(): Promise<string | null>;
293
- private aggregateCalls;
294
- }
295
-
296
47
  /**
297
- * Message Parser
298
- * Auto-detects OpenAI/Anthropic/Gemini message formats and extracts relevant data
48
+ * Flush all pending traces
299
49
  */
50
+ declare function flush(): Promise<void>;
300
51
 
301
52
  /**
302
- * Parse messages array and extract structured data
53
+ * Observe Function
54
+ *
55
+ * Main entry point for wrapping LLM clients with automatic tracing.
303
56
  */
304
- declare function parseMessages(messages: unknown): ParsedTrace;
57
+
305
58
  /**
306
- * Extract data from an OpenAI/Anthropic/Bedrock response object
307
- * This handles the raw API response (not the messages array)
59
+ * Wrap an LLM client with automatic tracing
60
+ *
61
+ * @param client - OpenAI or Anthropic client instance
62
+ * @param options - Optional context (sessionId, userId, etc.)
63
+ * @returns The wrapped client with the same type
64
+ *
65
+ * @example
66
+ * import { observe } from '@lelemondev/sdk';
67
+ * import OpenAI from 'openai';
68
+ *
69
+ * const openai = observe(new OpenAI());
70
+ *
71
+ * // All calls are now automatically traced
72
+ * const response = await openai.chat.completions.create({...});
308
73
  */
309
- declare function parseResponse(response: unknown): Partial<ParsedLLMCall>;
74
+ declare function observe<T>(client: T, options?: ObserveOptions): T;
310
75
  /**
311
- * Parse Bedrock InvokeModel response body
312
- * Call this with the parsed JSON body from Bedrock
76
+ * Create a scoped observe function with preset context
77
+ *
78
+ * @example
79
+ * const observeWithSession = createObserve({
80
+ * sessionId: 'session-123',
81
+ * userId: 'user-456',
82
+ * });
83
+ *
84
+ * const openai = observeWithSession(new OpenAI());
313
85
  */
314
- declare function parseBedrockResponse(body: unknown): Partial<ParsedLLMCall>;
86
+ declare function createObserve(defaultOptions: ObserveOptions): <T>(client: T, options?: ObserveOptions) => T;
315
87
 
316
- export { type AnthropicMessage, type LelemonConfig, type Message, type OpenAIMessage, type ParsedLLMCall, type ParsedToolCall, type ParsedTrace, Trace, type TraceOptions, flush, init, isEnabled, parseBedrockResponse, parseMessages, parseResponse, trace };
88
+ export { type LelemonConfig, type ObserveOptions, type ProviderName, createObserve, flush, init, isEnabled, observe };