@cetusai/sdk 0.2.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +409 -0
- package/dist/index.cjs +910 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +518 -0
- package/dist/index.d.ts +518 -0
- package/dist/index.mjs +880 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@songlines/sdk` are documented here.
|
|
4
|
+
|
|
5
|
+
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## [0.2.0] — Unreleased
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `evaluateGuardrail(params)` — real-time policy evaluation via the Songlines Gateway API
|
|
13
|
+
- `SonglinesClient.evaluateGuardrail()` — synchronous guardrail check before sending to an LLM
|
|
14
|
+
- `GuardrailResult` type with `decision`, `violations`, `modifiedInput`, and `latencyMs`
|
|
15
|
+
- `GuardrailViolation` type with `policyId`, `policyName`, `action`, `reason`, and `field`
|
|
16
|
+
- `GuardrailBlockedError` — thrown when a guardrail returns `decision: "block"` and `throwOnBlock: true`
|
|
17
|
+
- Tests for allow, block, modify, and multiple-violation scenarios
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [0.1.0] — 2026-06-23
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `SonglinesClient` with `trackAIRequest()`, `flush()`, and `shutdown()`
|
|
25
|
+
- `wrapOpenAI()` — transparent proxy for the OpenAI SDK
|
|
26
|
+
- `wrapAnthropic()` — transparent proxy for the Anthropic SDK
|
|
27
|
+
- `BatchQueue` — async batching with configurable `batchSize` and `flushIntervalMs`
|
|
28
|
+
- Exponential backoff retry with jitter (`withRetry()`)
|
|
29
|
+
- Built-in cost estimation for 14+ models (`estimateCost()`, `getModelRates()`)
|
|
30
|
+
- Zero-dependency UUID v4 generator
|
|
31
|
+
- 9 typed error classes surfaced via `onError` callback
|
|
32
|
+
- Dual ESM + CJS build with TypeScript declarations
|
|
33
|
+
- 76 unit tests across 5 test files
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cetus AI Pty 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,409 @@
|
|
|
1
|
+
# @songlines/sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript/JavaScript SDK for **Songlines Control** — AI observability, cost attribution, and governance for enterprise workloads.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@songlines/sdk)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Zero runtime dependencies** in the core ingest path — no supply chain risk, no version conflicts
|
|
14
|
+
- **Fire-and-forget** — `trackAIRequest()` never blocks or slows down your AI calls
|
|
15
|
+
- **Automatic batching** — events are queued and flushed in configurable batches
|
|
16
|
+
- **Exponential backoff** — failed flushes are retried automatically with jitter
|
|
17
|
+
- **OpenAI & Anthropic wrappers** — one-line instrumentation with `wrapOpenAI()` / `wrapAnthropic()`
|
|
18
|
+
- **Prompt-safe by design** — prompt content is never captured or transmitted
|
|
19
|
+
- **Dual ESM + CJS** — works in Node.js, edge runtimes, and bundlers
|
|
20
|
+
- **Full TypeScript types** — end-to-end type safety with declaration maps
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @songlines/sdk
|
|
28
|
+
# or
|
|
29
|
+
pnpm add @songlines/sdk
|
|
30
|
+
# or
|
|
31
|
+
yarn add @songlines/sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Manual tracking
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { SonglinesClient } from "@songlines/sdk";
|
|
42
|
+
|
|
43
|
+
const songlines = new SonglinesClient({
|
|
44
|
+
apiKey: process.env.SONGLINES_API_KEY!,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// After your AI call completes:
|
|
48
|
+
await songlines.trackAIRequest({
|
|
49
|
+
model: "gpt-4o",
|
|
50
|
+
provider: "openai",
|
|
51
|
+
workflow: "invoice-processor",
|
|
52
|
+
inputTokens: 1200,
|
|
53
|
+
outputTokens: 400,
|
|
54
|
+
latencyMs: 1840,
|
|
55
|
+
status: "success",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Flush on graceful shutdown
|
|
59
|
+
process.on("SIGTERM", async () => {
|
|
60
|
+
await songlines.shutdown();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### OpenAI wrapper (recommended)
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import OpenAI from "openai";
|
|
69
|
+
import { SonglinesClient, wrapOpenAI } from "@songlines/sdk";
|
|
70
|
+
|
|
71
|
+
const openai = wrapOpenAI(new OpenAI(), new SonglinesClient({
|
|
72
|
+
apiKey: process.env.SONGLINES_API_KEY!,
|
|
73
|
+
}), {
|
|
74
|
+
workflow: "customer-support",
|
|
75
|
+
environment: "production",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// All calls are automatically tracked — no code changes needed
|
|
79
|
+
const response = await openai.chat.completions.create({
|
|
80
|
+
model: "gpt-4o",
|
|
81
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Anthropic wrapper
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
89
|
+
import { SonglinesClient, wrapAnthropic } from "@songlines/sdk";
|
|
90
|
+
|
|
91
|
+
const anthropic = wrapAnthropic(new Anthropic(), new SonglinesClient({
|
|
92
|
+
apiKey: process.env.SONGLINES_API_KEY!,
|
|
93
|
+
}), {
|
|
94
|
+
workflow: "document-review",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const message = await anthropic.messages.create({
|
|
98
|
+
model: "claude-3-5-sonnet-20241022",
|
|
99
|
+
max_tokens: 1024,
|
|
100
|
+
messages: [{ role: "user", content: "Summarise this document." }],
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const songlines = new SonglinesClient({
|
|
110
|
+
// Required
|
|
111
|
+
apiKey: process.env.SONGLINES_API_KEY!,
|
|
112
|
+
|
|
113
|
+
// Optional — defaults shown
|
|
114
|
+
baseUrl: "https://api.songlinesai.com", // Override for on-premises deployments
|
|
115
|
+
environment: "production", // "production" | "staging" | "development" | "test"
|
|
116
|
+
batchSize: 10, // Flush after N events (1–100)
|
|
117
|
+
flushIntervalMs: 5000, // Flush every N ms (100–60000)
|
|
118
|
+
timeout: 10000, // HTTP request timeout in ms
|
|
119
|
+
retries: 3, // Retry attempts on network failure (0–10)
|
|
120
|
+
debug: false, // Log SDK internals to console.debug
|
|
121
|
+
|
|
122
|
+
// Error callback — called when events are dropped after all retries
|
|
123
|
+
onError: (error) => {
|
|
124
|
+
console.error("[Songlines SDK]", error.code, error.message);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## API Reference
|
|
132
|
+
|
|
133
|
+
### `SonglinesClient`
|
|
134
|
+
|
|
135
|
+
#### `trackAIRequest(params)`
|
|
136
|
+
|
|
137
|
+
Records an AI request event. Returns immediately — the event is queued and sent asynchronously.
|
|
138
|
+
|
|
139
|
+
| Parameter | Type | Required | Description |
|
|
140
|
+
|---|---|---|---|
|
|
141
|
+
| `model` | `string` | Yes | Model identifier (e.g. `"gpt-4o"`, `"claude-3-5-sonnet-20241022"`) |
|
|
142
|
+
| `provider` | `string` | No | Provider name (e.g. `"openai"`, `"anthropic"`, `"azure"`) |
|
|
143
|
+
| `workflow` | `string` | No | Logical workflow or feature name for cost attribution |
|
|
144
|
+
| `step` | `string` | No | Step within a multi-step workflow |
|
|
145
|
+
| `agentId` | `string` | No | Agent identifier for multi-agent systems |
|
|
146
|
+
| `user` | `string` | No | End-user identifier (hashed/anonymised) |
|
|
147
|
+
| `inputTokens` | `number` | No | Prompt token count |
|
|
148
|
+
| `outputTokens` | `number` | No | Completion token count |
|
|
149
|
+
| `latencyMs` | `number` | No | End-to-end latency in milliseconds |
|
|
150
|
+
| `cost` | `number` | No | Actual cost in USD (auto-estimated if omitted) |
|
|
151
|
+
| `status` | `RequestStatus` | No | `"success"` \| `"error"` \| `"blocked"` \| `"pending"` (default: `"success"`) |
|
|
152
|
+
| `requestId` | `string` | No | Idempotency key (UUID auto-generated if omitted) |
|
|
153
|
+
| `timestamp` | `Date` | No | Event timestamp (current time if omitted) |
|
|
154
|
+
|
|
155
|
+
#### `flush()`
|
|
156
|
+
|
|
157
|
+
Forces immediate delivery of all queued events. Resolves when the flush completes.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
await songlines.flush();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `shutdown()`
|
|
164
|
+
|
|
165
|
+
Flushes remaining events and stops the background timer. Call during graceful shutdown.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
await songlines.shutdown();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### `wrapOpenAI(client, songlinesClient, options?)`
|
|
174
|
+
|
|
175
|
+
Returns a transparent proxy of the OpenAI client that automatically calls `trackAIRequest()` after every `chat.completions.create()` call.
|
|
176
|
+
|
|
177
|
+
**Options** (`WrapOptions`):
|
|
178
|
+
|
|
179
|
+
| Option | Type | Description |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `workflow` | `string` | Workflow tag applied to all calls via this wrapper |
|
|
182
|
+
| `step` | `string` | Step tag applied to all calls |
|
|
183
|
+
| `agentId` | `string` | Agent identifier |
|
|
184
|
+
| `user` | `string` | End-user identifier |
|
|
185
|
+
|
|
186
|
+
**Behaviour:**
|
|
187
|
+
- Token counts are read from `response.usage` automatically
|
|
188
|
+
- Latency is measured from call start to response received
|
|
189
|
+
- On API error, `status: "error"` is recorded and the error is re-thrown
|
|
190
|
+
- Streaming calls (`stream: true`) are tracked with `inputTokens: 0, outputTokens: 0` — token counts are not available from the stream
|
|
191
|
+
- Prompt content is never captured
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### `wrapAnthropic(client, songlinesClient, options?)`
|
|
196
|
+
|
|
197
|
+
Returns a transparent proxy of the Anthropic client that automatically calls `trackAIRequest()` after every `messages.create()` call.
|
|
198
|
+
|
|
199
|
+
Behaviour is identical to `wrapOpenAI()` — token counts from `response.usage.input_tokens` / `response.usage.output_tokens`, latency measured end-to-end, errors recorded and re-thrown.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Cost Estimation
|
|
204
|
+
|
|
205
|
+
If `cost` is not provided to `trackAIRequest()`, the SDK estimates it from the model name and token counts using a built-in rate table. The table is updated with each SDK release.
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { estimateCost, getModelRates } from "@songlines/sdk";
|
|
209
|
+
|
|
210
|
+
// Estimate cost for a specific call
|
|
211
|
+
const cost = estimateCost({
|
|
212
|
+
model: "gpt-4o",
|
|
213
|
+
inputTokens: 1000,
|
|
214
|
+
outputTokens: 500,
|
|
215
|
+
});
|
|
216
|
+
// → 0.00750 (USD)
|
|
217
|
+
|
|
218
|
+
// Inspect the full rate table
|
|
219
|
+
const rates = getModelRates();
|
|
220
|
+
// → { "gpt-4o": { input: 2.50, output: 10.00 }, ... }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Supported models (built-in rates):**
|
|
224
|
+
|
|
225
|
+
| Model | Input ($/M tokens) | Output ($/M tokens) |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| gpt-4o | $2.50 | $10.00 |
|
|
228
|
+
| gpt-4o-mini | $0.15 | $0.60 |
|
|
229
|
+
| gpt-4-turbo | $10.00 | $30.00 |
|
|
230
|
+
| gpt-3.5-turbo | $0.50 | $1.50 |
|
|
231
|
+
| o1 | $15.00 | $60.00 |
|
|
232
|
+
| o1-mini | $3.00 | $12.00 |
|
|
233
|
+
| o3-mini | $1.10 | $4.40 |
|
|
234
|
+
| claude-3-5-sonnet | $3.00 | $15.00 |
|
|
235
|
+
| claude-3-5-haiku | $0.80 | $4.00 |
|
|
236
|
+
| claude-3-opus | $15.00 | $75.00 |
|
|
237
|
+
| gemini-1.5-pro | $1.25 | $5.00 |
|
|
238
|
+
| gemini-1.5-flash | $0.075 | $0.30 |
|
|
239
|
+
| Unknown models | $1.00 | $3.00 |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Error Handling
|
|
244
|
+
|
|
245
|
+
The SDK never throws. All errors are surfaced via the `onError` callback.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import {
|
|
249
|
+
InvalidApiKeyError,
|
|
250
|
+
NetworkError,
|
|
251
|
+
RateLimitedError,
|
|
252
|
+
ServerError,
|
|
253
|
+
QueueOverflowError,
|
|
254
|
+
PartialFailureError,
|
|
255
|
+
} from "@songlines/sdk";
|
|
256
|
+
|
|
257
|
+
const songlines = new SonglinesClient({
|
|
258
|
+
apiKey: process.env.SONGLINES_API_KEY!,
|
|
259
|
+
onError: (error) => {
|
|
260
|
+
if (error instanceof InvalidApiKeyError) {
|
|
261
|
+
// API key is invalid — alert immediately
|
|
262
|
+
alertOps("Invalid Songlines API key");
|
|
263
|
+
} else if (error instanceof RateLimitedError) {
|
|
264
|
+
// Back off — error.retryAfterMs is available if the server sent Retry-After
|
|
265
|
+
console.warn(`Rate limited. Retry after ${error.retryAfterMs}ms`);
|
|
266
|
+
} else if (error instanceof QueueOverflowError) {
|
|
267
|
+
// Events were dropped — queue is full
|
|
268
|
+
metrics.increment("songlines.events.dropped", error.droppedCount);
|
|
269
|
+
} else if (error instanceof NetworkError) {
|
|
270
|
+
// Transient network failure after all retries exhausted
|
|
271
|
+
metrics.increment("songlines.flush.failed");
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Framework Examples
|
|
280
|
+
|
|
281
|
+
### Express.js middleware
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import express from "express";
|
|
285
|
+
import OpenAI from "openai";
|
|
286
|
+
import { SonglinesClient, wrapOpenAI } from "@songlines/sdk";
|
|
287
|
+
|
|
288
|
+
const songlines = new SonglinesClient({ apiKey: process.env.SONGLINES_API_KEY! });
|
|
289
|
+
const openai = wrapOpenAI(new OpenAI(), songlines);
|
|
290
|
+
|
|
291
|
+
const app = express();
|
|
292
|
+
|
|
293
|
+
app.post("/chat", async (req, res) => {
|
|
294
|
+
const { message, userId } = req.body;
|
|
295
|
+
|
|
296
|
+
const response = await openai.chat.completions.create({
|
|
297
|
+
model: "gpt-4o",
|
|
298
|
+
messages: [{ role: "user", content: message }],
|
|
299
|
+
// Songlines metadata passed via options at wrap time
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
res.json({ reply: response.choices[0]?.message.content });
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
process.on("SIGTERM", async () => {
|
|
306
|
+
await songlines.shutdown();
|
|
307
|
+
process.exit(0);
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Next.js API route
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// app/api/chat/route.ts
|
|
315
|
+
import { SonglinesClient, wrapOpenAI } from "@songlines/sdk";
|
|
316
|
+
import OpenAI from "openai";
|
|
317
|
+
|
|
318
|
+
// Instantiate once per cold start
|
|
319
|
+
const songlines = new SonglinesClient({ apiKey: process.env.SONGLINES_API_KEY! });
|
|
320
|
+
const openai = wrapOpenAI(new OpenAI(), songlines, { workflow: "chat" });
|
|
321
|
+
|
|
322
|
+
export async function POST(req: Request) {
|
|
323
|
+
const { messages } = await req.json();
|
|
324
|
+
const response = await openai.chat.completions.create({ model: "gpt-4o", messages });
|
|
325
|
+
return Response.json(response);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### LangChain callback
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { SonglinesClient } from "@songlines/sdk";
|
|
333
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
334
|
+
|
|
335
|
+
const songlines = new SonglinesClient({ apiKey: process.env.SONGLINES_API_KEY! });
|
|
336
|
+
|
|
337
|
+
const llm = new ChatOpenAI({ model: "gpt-4o" });
|
|
338
|
+
|
|
339
|
+
// After each LangChain call, track manually:
|
|
340
|
+
const result = await llm.invoke("Hello");
|
|
341
|
+
await songlines.trackAIRequest({
|
|
342
|
+
model: "gpt-4o",
|
|
343
|
+
provider: "openai",
|
|
344
|
+
workflow: "langchain-agent",
|
|
345
|
+
inputTokens: result.usage_metadata?.input_tokens,
|
|
346
|
+
outputTokens: result.usage_metadata?.output_tokens,
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Privacy & Security
|
|
353
|
+
|
|
354
|
+
**Prompt content is never captured.** The `wrapOpenAI()` and `wrapAnthropic()` proxies read only:
|
|
355
|
+
- `response.usage` (token counts)
|
|
356
|
+
- `response.model` (model name)
|
|
357
|
+
- Request start/end timestamps (latency)
|
|
358
|
+
|
|
359
|
+
The message content, system prompts, and completion text are never accessed, stored, or transmitted by the SDK. This is a hard architectural boundary, not a configuration option.
|
|
360
|
+
|
|
361
|
+
**API key security:**
|
|
362
|
+
- Always load from environment variables — never hardcode
|
|
363
|
+
- The API key is sent only in the `Authorization: Bearer` header over HTTPS
|
|
364
|
+
- The SDK does not log the API key, even in `debug` mode
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Batching & Performance
|
|
369
|
+
|
|
370
|
+
The SDK uses an in-memory queue with automatic batching to minimise API calls:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
trackAIRequest() → queue.push() → [returns immediately]
|
|
374
|
+
↓
|
|
375
|
+
[batch reaches batchSize]
|
|
376
|
+
[OR flushIntervalMs elapses]
|
|
377
|
+
↓
|
|
378
|
+
POST /api/ingest (batch)
|
|
379
|
+
[retry with backoff on failure]
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Default behaviour:**
|
|
383
|
+
- Events are batched up to 10 at a time
|
|
384
|
+
- A partial batch is flushed every 5 seconds
|
|
385
|
+
- Failed flushes are retried 3 times with exponential backoff (500ms base, 2× multiplier, ±20% jitter)
|
|
386
|
+
- Events that fail all retries are dropped and reported via `onError`
|
|
387
|
+
- The queue holds up to 1,000 events; overflow events are dropped and reported
|
|
388
|
+
|
|
389
|
+
**Memory impact:** Each event is approximately 200–400 bytes. At the default queue cap of 1,000 events, the maximum memory footprint is approximately 400 KB.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Changelog
|
|
394
|
+
|
|
395
|
+
### 0.1.0 (2026-06-23)
|
|
396
|
+
- Initial release
|
|
397
|
+
- `SonglinesClient` with `trackAIRequest()`, `flush()`, `shutdown()`
|
|
398
|
+
- `wrapOpenAI()` proxy wrapper
|
|
399
|
+
- `wrapAnthropic()` proxy wrapper
|
|
400
|
+
- Built-in cost estimation for 14+ models
|
|
401
|
+
- Exponential backoff retry with jitter
|
|
402
|
+
- Dual ESM + CJS build
|
|
403
|
+
- Full TypeScript declarations
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
MIT © Cetus AI Pty Ltd
|