@pentatonic-ai/ai-agent-sdk 0.3.0-beta.3
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/LICENSE +21 -0
- package/README.md +401 -0
- package/bin/cli.js +371 -0
- package/build.js +23 -0
- package/dist/index.cjs +699 -0
- package/dist/index.js +677 -0
- package/dist/pentatonic_agent_events-0.2.0b1-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.2.0b1.tar.gz +0 -0
- package/dist/pentatonic_agent_events-0.3.0b1-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.3.0b1.tar.gz +0 -0
- package/dist/pentatonic_agent_events-0.3.0b2-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.3.0b2.tar.gz +0 -0
- package/dist/pentatonic_agent_events-0.3.0b3-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.3.0b3.tar.gz +0 -0
- package/package.json +64 -0
- package/src/client.js +60 -0
- package/src/index.js +4 -0
- package/src/normalizer.js +111 -0
- package/src/session.js +181 -0
- package/src/tracking.js +119 -0
- package/src/transport.js +48 -0
- package/src/wrapper.js +329 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pentatonic Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# @pentatonic-ai/ai-agent-sdk
|
|
2
|
+
|
|
3
|
+
LLM observability SDK — track token usage, tool calls, and conversations via [Pentatonic TES](https://api.pentatonic.com).
|
|
4
|
+
|
|
5
|
+
Provider-agnostic: automatically wraps OpenAI, Anthropic, and Cloudflare Workers AI clients. Available for both **JavaScript** and **Python**.
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
### 1. Create an account and get your API key
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @pentatonic-ai/ai-agent-sdk init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This will walk you through:
|
|
16
|
+
- Creating a Pentatonic account (email, company name, password)
|
|
17
|
+
- Choosing a data region (EU or US)
|
|
18
|
+
- Email verification
|
|
19
|
+
- Generating your API key
|
|
20
|
+
|
|
21
|
+
At the end you'll see your credentials:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
TES_ENDPOINT=https://api.pentatonic.com
|
|
25
|
+
TES_CLIENT_ID=your-company
|
|
26
|
+
TES_API_KEY=tes_your-company_xxxxx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Add these to your environment (`.env`, secrets manager, etc.) and the CLI will install the SDK for you.
|
|
30
|
+
|
|
31
|
+
### 2. Or install manually
|
|
32
|
+
|
|
33
|
+
If you already have an account, install the SDK directly:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @pentatonic-ai/ai-agent-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install pentatonic-ai-agent-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You can create API keys in the [Pentatonic dashboard](https://api.pentatonic.com).
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
#### JavaScript
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { TESClient } from "@pentatonic-ai/ai-agent-sdk";
|
|
51
|
+
|
|
52
|
+
const tes = new TESClient({
|
|
53
|
+
clientId: process.env.TES_CLIENT_ID,
|
|
54
|
+
apiKey: process.env.TES_API_KEY,
|
|
55
|
+
endpoint: process.env.TES_ENDPOINT,
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Python
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from pentatonic_agent_events import TESClient
|
|
63
|
+
import os
|
|
64
|
+
|
|
65
|
+
tes = TESClient(
|
|
66
|
+
client_id=os.environ["TES_CLIENT_ID"],
|
|
67
|
+
api_key=os.environ["TES_API_KEY"],
|
|
68
|
+
endpoint=os.environ["TES_ENDPOINT"],
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Wrap any LLM client (automatic tracking)
|
|
73
|
+
|
|
74
|
+
`tes.wrap()` auto-detects your client and intercepts every call — each one emits a `CHAT_TURN` event automatically. Pass an optional `sessionId` to link events from the same conversation, and `metadata` to attach custom fields.
|
|
75
|
+
|
|
76
|
+
#### JavaScript — OpenAI
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import OpenAI from "openai";
|
|
80
|
+
|
|
81
|
+
const ai = tes.wrap(new OpenAI(), { sessionId: "conv-123", metadata: { userId: "u_1" } });
|
|
82
|
+
|
|
83
|
+
// Every create() call automatically emits a CHAT_TURN event
|
|
84
|
+
const result = await ai.chat.completions.create({
|
|
85
|
+
model: "gpt-4o",
|
|
86
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
ai.sessionId; // "conv-123" — or auto-generated UUID if not provided
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Python — OpenAI
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from openai import OpenAI
|
|
96
|
+
|
|
97
|
+
ai = tes.wrap(OpenAI(), session_id="conv-123", metadata={"user_id": "u_1"})
|
|
98
|
+
|
|
99
|
+
# Every create() call automatically emits a CHAT_TURN event
|
|
100
|
+
result = ai.chat.completions.create(
|
|
101
|
+
model="gpt-4o",
|
|
102
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
ai.session_id # "conv-123" — or auto-generated UUID if not provided
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### JavaScript — Anthropic
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
112
|
+
|
|
113
|
+
const claude = tes.wrap(new Anthropic());
|
|
114
|
+
|
|
115
|
+
const result = await claude.messages.create({
|
|
116
|
+
model: "claude-sonnet-4-6-20250514",
|
|
117
|
+
max_tokens: 1024,
|
|
118
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Python — Anthropic
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from anthropic import Anthropic
|
|
126
|
+
|
|
127
|
+
claude = tes.wrap(Anthropic())
|
|
128
|
+
|
|
129
|
+
result = claude.messages.create(
|
|
130
|
+
model="claude-sonnet-4-6-20250514",
|
|
131
|
+
max_tokens=1024,
|
|
132
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### JavaScript — Cloudflare Workers AI
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// Cloudflare Workers AI binding
|
|
140
|
+
const ai = tes.wrap(env.AI, { sessionId: sid, metadata: { shop: shopDomain } });
|
|
141
|
+
|
|
142
|
+
// run() is intercepted automatically
|
|
143
|
+
const result = await ai.run("@cf/meta/llama-3.1-8b-instruct", {
|
|
144
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **Note:** Workers AI is a Cloudflare-specific binding and is only available in JavaScript.
|
|
149
|
+
|
|
150
|
+
### Tool-calling loops
|
|
151
|
+
|
|
152
|
+
For multi-round tool loops, just keep calling the wrapped client. Each `create()`/`run()` call emits its own event, and they're linked by `sessionId`. The dashboard aggregates tokens, tool calls, and turns per session automatically.
|
|
153
|
+
|
|
154
|
+
#### JavaScript
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
const ai = tes.wrap(new OpenAI(), { sessionId: "conv-101" });
|
|
158
|
+
|
|
159
|
+
// Round 1: AI requests a tool call — emits event with tool_calls
|
|
160
|
+
const r1 = await ai.chat.completions.create({
|
|
161
|
+
model: "gpt-4o",
|
|
162
|
+
messages: [{ role: "user", content: "Find me running shoes" }],
|
|
163
|
+
tools: [searchTool],
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Execute tool, feed results back...
|
|
167
|
+
|
|
168
|
+
// Round 2: AI responds with final answer — emits another event
|
|
169
|
+
const r2 = await ai.chat.completions.create({
|
|
170
|
+
model: "gpt-4o",
|
|
171
|
+
messages: [...messages, { role: "tool", content: toolResult }],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// That's it. No manual emit needed. Both events share sessionId "conv-101".
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Python
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
ai = tes.wrap(OpenAI(), session_id="conv-101")
|
|
181
|
+
|
|
182
|
+
r1 = ai.chat.completions.create(
|
|
183
|
+
model="gpt-4o",
|
|
184
|
+
messages=[{"role": "user", "content": "Find me running shoes"}],
|
|
185
|
+
tools=[search_tool],
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Execute tool, feed results back...
|
|
189
|
+
|
|
190
|
+
r2 = ai.chat.completions.create(
|
|
191
|
+
model="gpt-4o",
|
|
192
|
+
messages=[*messages, {"role": "tool", "content": tool_result}],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# No manual emit needed.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Manual session (full control)
|
|
199
|
+
|
|
200
|
+
If you don't want to use `tes.wrap()`, create a session directly:
|
|
201
|
+
|
|
202
|
+
#### JavaScript
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
const session = tes.session({
|
|
206
|
+
sessionId: "conv-123",
|
|
207
|
+
metadata: { userId: "u_456" },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Call your LLM however you like
|
|
211
|
+
const response = await openai.chat.completions.create({
|
|
212
|
+
model: "gpt-4o",
|
|
213
|
+
messages: [{ role: "user", content: "What is 2+2?" }],
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Record the response (accumulates tokens, tool calls, model)
|
|
217
|
+
session.record(response);
|
|
218
|
+
|
|
219
|
+
// Emit when the turn is complete
|
|
220
|
+
await session.emitChatTurn({
|
|
221
|
+
userMessage: "What is 2+2?",
|
|
222
|
+
assistantResponse: response.choices[0].message.content,
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Python
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
session = tes.session(
|
|
230
|
+
session_id="conv-123",
|
|
231
|
+
metadata={"user_id": "u_456"},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
response = openai.chat.completions.create(
|
|
235
|
+
model="gpt-4o",
|
|
236
|
+
messages=[{"role": "user", "content": "What is 2+2?"}],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
session.record(response)
|
|
240
|
+
|
|
241
|
+
session.emit_chat_turn(
|
|
242
|
+
user_message="What is 2+2?",
|
|
243
|
+
assistant_response=response["choices"][0]["message"]["content"],
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## API Reference
|
|
248
|
+
|
|
249
|
+
### `TESClient`
|
|
250
|
+
|
|
251
|
+
Creates a new client.
|
|
252
|
+
|
|
253
|
+
#### JavaScript
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
new TESClient({ clientId, apiKey, endpoint, headers?, userId?, captureContent?, maxContentLength? })
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Python
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
TESClient(client_id, api_key, endpoint, headers=None, user_id=None, capture_content=True, max_content_length=4096)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
| Param (JS / Python) | Type | Default | Description |
|
|
266
|
+
|----------------------|------|---------|-------------|
|
|
267
|
+
| `clientId` / `client_id` | `string` | *required* | Your application/tenant identifier |
|
|
268
|
+
| `apiKey` / `api_key` | `string` | *required* | TES service API key (sent as `x-service-key` header) |
|
|
269
|
+
| `endpoint` / `endpoint` | `string` | *required* | TES instance URL (must be `https://`, except `localhost` for dev) |
|
|
270
|
+
| `headers` / `headers` | `object` / `dict` | `{}` | Additional headers to include in every request |
|
|
271
|
+
| `userId` / `user_id` | `string` | `null` / `None` | Optional user identifier — included as `data.attributes.userId` on every event. Enables user-scoped memory and attribution. |
|
|
272
|
+
| `captureContent` / `capture_content` | `boolean` / `bool` | `true` / `True` | Whether to include message content in events |
|
|
273
|
+
| `maxContentLength` / `max_content_length` | `number` / `int` | `4096` | Truncate content beyond this length |
|
|
274
|
+
|
|
275
|
+
### `tes.wrap(client, opts?)`
|
|
276
|
+
|
|
277
|
+
Returns a Proxy (JS) or wrapper (Python) around any supported LLM client. Every intercepted call emits a `CHAT_TURN` event automatically.
|
|
278
|
+
|
|
279
|
+
#### JavaScript
|
|
280
|
+
|
|
281
|
+
```js
|
|
282
|
+
const ai = tes.wrap(client, { sessionId, userId, metadata });
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Python
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
ai = tes.wrap(client, session_id=None, user_id=None, metadata=None)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
| Option (JS / Python) | Type | Default | Description |
|
|
292
|
+
|----------------------|------|---------|-------------|
|
|
293
|
+
| `sessionId` / `session_id` | `string` | `crypto.randomUUID()` / `uuid.uuid4()` | Links events from the same conversation |
|
|
294
|
+
| `userId` / `user_id` | `string` | Inherits from client | Override the user identifier for this wrapped instance |
|
|
295
|
+
| `metadata` / `metadata` | `object` / `dict` | `{}` | Custom fields included in every emitted event |
|
|
296
|
+
|
|
297
|
+
Auto-detects the provider:
|
|
298
|
+
|
|
299
|
+
| Client | Detection | Intercepted method |
|
|
300
|
+
|--------|-----------|-------------------|
|
|
301
|
+
| OpenAI | `client.chat.completions.create` | `chat.completions.create()` |
|
|
302
|
+
| Anthropic | `client.messages.create` | `messages.create()` |
|
|
303
|
+
| Workers AI | `client.run` (JS only) | `run()` |
|
|
304
|
+
|
|
305
|
+
All other methods/properties pass through unchanged. The wrapped client exposes `ai.sessionId` (JS) or `ai.session_id` (Python).
|
|
306
|
+
|
|
307
|
+
### `tes.session(opts?)`
|
|
308
|
+
|
|
309
|
+
Returns a `Session` instance.
|
|
310
|
+
|
|
311
|
+
| Option (JS / Python) | Type | Default | Description |
|
|
312
|
+
|----------------------|------|---------|-------------|
|
|
313
|
+
| `sessionId` / `session_id` | `string` | `crypto.randomUUID()` / `uuid.uuid4()` | Conversation/session identifier |
|
|
314
|
+
| `metadata` / `metadata` | `object` / `dict` | `{}` | Extra fields included in every emitted event |
|
|
315
|
+
|
|
316
|
+
### `session.record(rawResponse)`
|
|
317
|
+
|
|
318
|
+
Normalizes an LLM response and accumulates token usage, tool calls, and model info. Accepts responses from any supported provider. Returns the normalized response.
|
|
319
|
+
|
|
320
|
+
### `session.emitChatTurn()` / `session.emit_chat_turn()`
|
|
321
|
+
|
|
322
|
+
Sends a `CHAT_TURN` event to TES with accumulated usage data, then resets counters.
|
|
323
|
+
|
|
324
|
+
| Param (JS / Python) | Type | Description |
|
|
325
|
+
|---------------------|------|-------------|
|
|
326
|
+
| `userMessage` / `user_message` | `string` | The user's message |
|
|
327
|
+
| `assistantResponse` / `assistant_response` | `string` | The assistant's response |
|
|
328
|
+
| `turnNumber` / `turn_number` | `number` / `int` | Optional turn number |
|
|
329
|
+
|
|
330
|
+
### `session.emitToolUse()` / `session.emit_tool_use()`
|
|
331
|
+
|
|
332
|
+
Sends a `TOOL_USE` event for individual tool invocations.
|
|
333
|
+
|
|
334
|
+
| Param (JS / Python) | Type | Description |
|
|
335
|
+
|---------------------|------|-------------|
|
|
336
|
+
| `tool` / `tool` | `string` | Tool name |
|
|
337
|
+
| `args` / `args` | `object` / `dict` | Tool arguments |
|
|
338
|
+
| `resultSummary` / `result_summary` | `string` | Optional result summary |
|
|
339
|
+
| `durationMs` / `duration_ms` | `number` / `int` | Optional duration in milliseconds |
|
|
340
|
+
| `turnNumber` / `turn_number` | `number` / `int` | Optional turn number |
|
|
341
|
+
|
|
342
|
+
### `session.emitSessionStart()` / `session.emit_session_start()`
|
|
343
|
+
|
|
344
|
+
Sends a `SESSION_START` event.
|
|
345
|
+
|
|
346
|
+
### `session.totalUsage` / `session.total_usage`
|
|
347
|
+
|
|
348
|
+
Returns current accumulated usage: `{ prompt_tokens, completion_tokens, total_tokens, ai_rounds }`.
|
|
349
|
+
|
|
350
|
+
### `normalizeResponse(raw)` / `normalize_response(raw)`
|
|
351
|
+
|
|
352
|
+
Standalone utility to normalize any LLM response into a consistent shape:
|
|
353
|
+
|
|
354
|
+
#### JavaScript
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
import { normalizeResponse } from "@pentatonic-ai/ai-agent-sdk";
|
|
358
|
+
|
|
359
|
+
const normalized = normalizeResponse(openaiResponse);
|
|
360
|
+
// { content, model, usage: { prompt_tokens, completion_tokens }, toolCalls: [{ tool, args }] }
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### Python
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from pentatonic_agent_events import normalize_response
|
|
367
|
+
|
|
368
|
+
normalized = normalize_response(openai_response)
|
|
369
|
+
# { "content", "model", "usage": { "prompt_tokens", "completion_tokens" }, "tool_calls": [{ "tool", "args" }] }
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
> **Note:** In Python, the normalized response uses `tool_calls` (snake_case) instead of `toolCalls` (camelCase).
|
|
373
|
+
|
|
374
|
+
## Events Emitted
|
|
375
|
+
|
|
376
|
+
All events are sent to the TES GraphQL API (`emitEvent` mutation) authenticated via `x-service-key` and `x-client-id` headers.
|
|
377
|
+
|
|
378
|
+
| Event Type | Entity Type | When |
|
|
379
|
+
|------------|-------------|------|
|
|
380
|
+
| `CHAT_TURN` | `conversation` | Every `create()`/`run()` call via `wrap()`, or manually via `session.emitChatTurn()` |
|
|
381
|
+
| `TOOL_USE` | `conversation` | Via `session.emitToolUse()` (manual only) |
|
|
382
|
+
| `SESSION_START` | `conversation` | Via `session.emitSessionStart()` (manual only) |
|
|
383
|
+
|
|
384
|
+
## Supported Providers
|
|
385
|
+
|
|
386
|
+
| Provider | Auto-wrap | Manual session | Response normalization |
|
|
387
|
+
|----------|-----------|---------------|----------------------|
|
|
388
|
+
| **OpenAI** (and compatible: Azure, Groq, Together, Mistral) | JS + Python | JS + Python | JS + Python |
|
|
389
|
+
| **Anthropic** | JS + Python | JS + Python | JS + Python |
|
|
390
|
+
| **Cloudflare Workers AI** | JS only | JS only | JS + Python |
|
|
391
|
+
|
|
392
|
+
## Security
|
|
393
|
+
|
|
394
|
+
- **HTTPS enforced:** The SDK rejects non-HTTPS endpoints (except `localhost` for development)
|
|
395
|
+
- **API key protection:** Stored as a non-enumerable property (JS) or private attribute (Python) — won't appear in `JSON.stringify`, `repr()`, or error reporters
|
|
396
|
+
- **Content controls:** Set `captureContent: false` (JS) or `capture_content=False` (Python) to omit message content from events, or use `maxContentLength` / `max_content_length` to truncate
|
|
397
|
+
- **No runtime dependencies:** Both the JavaScript and Python SDKs have zero external runtime dependencies
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
MIT
|