@analytix402/sdk 0.1.1 → 0.1.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/README.md +69 -0
- package/dist/chunk-GV5RDHUW.mjs +259 -0
- package/dist/chunk-KSO5EEA2.mjs +156 -0
- package/dist/chunk-MIPQLCB6.mjs +85 -0
- package/dist/chunk-PQO3A7RN.mjs +82 -0
- package/dist/index.d.mts +6 -210
- package/dist/index.d.ts +6 -210
- package/dist/index.js +325 -3
- package/dist/index.mjs +16 -234
- package/dist/types-C67qDpYb.d.mts +290 -0
- package/dist/types-C67qDpYb.d.ts +290 -0
- package/dist/wrappers/anthropic.d.mts +67 -0
- package/dist/wrappers/anthropic.d.ts +67 -0
- package/dist/wrappers/anthropic.js +361 -0
- package/dist/wrappers/anthropic.mjs +9 -0
- package/dist/wrappers/fetch.d.mts +45 -0
- package/dist/wrappers/fetch.d.ts +45 -0
- package/dist/wrappers/fetch.js +434 -0
- package/dist/wrappers/fetch.mjs +7 -0
- package/dist/wrappers/openai.d.mts +69 -0
- package/dist/wrappers/openai.d.ts +69 -0
- package/dist/wrappers/openai.js +364 -0
- package/dist/wrappers/openai.mjs +9 -0
- package/package.json +36 -5
package/README.md
CHANGED
|
@@ -106,6 +106,52 @@ These paths are excluded from tracking by default:
|
|
|
106
106
|
|
|
107
107
|
`/health`, `/healthz`, `/ready`, `/readyz`, `/live`, `/livez`, `/metrics`, `/favicon.ico`, `/.well-known`
|
|
108
108
|
|
|
109
|
+
## Agent Tracking
|
|
110
|
+
|
|
111
|
+
Track AI agent health, tasks, and LLM usage:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { createAnalytix402Client } from '@analytix402/sdk';
|
|
115
|
+
|
|
116
|
+
const client = createAnalytix402Client({
|
|
117
|
+
apiKey: 'ax_live_xxxxx',
|
|
118
|
+
agentId: 'my-trading-agent',
|
|
119
|
+
agentName: 'Trading Bot',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Send heartbeats to show agent is alive
|
|
123
|
+
setInterval(() => {
|
|
124
|
+
client.heartbeat('healthy', { uptime: process.uptime() });
|
|
125
|
+
}, 30000);
|
|
126
|
+
|
|
127
|
+
// Track tasks with automatic duration measurement
|
|
128
|
+
const task = client.startTask('rebalance-portfolio-42');
|
|
129
|
+
try {
|
|
130
|
+
await doRebalance();
|
|
131
|
+
task.end(true, { positions: 12 });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
task.end(false, { error: err.message });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Track LLM token usage
|
|
137
|
+
client.trackLLM({
|
|
138
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
139
|
+
provider: 'anthropic',
|
|
140
|
+
inputTokens: 1500,
|
|
141
|
+
outputTokens: 300,
|
|
142
|
+
costUsd: 0.012,
|
|
143
|
+
durationMs: 820,
|
|
144
|
+
taskId: 'rebalance-portfolio-42',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Report task outcomes directly
|
|
148
|
+
client.reportOutcome('task-123', true, {
|
|
149
|
+
durationMs: 2400,
|
|
150
|
+
cost: 0.05,
|
|
151
|
+
metadata: { pnl: 12.50 },
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
109
155
|
## Manual Tracking
|
|
110
156
|
|
|
111
157
|
For non-Express frameworks or custom tracking:
|
|
@@ -140,6 +186,29 @@ await client.flush();
|
|
|
140
186
|
await client.shutdown();
|
|
141
187
|
```
|
|
142
188
|
|
|
189
|
+
## Proxy URL Integration
|
|
190
|
+
|
|
191
|
+
Instead of the SDK middleware, you can route API calls through Analytix402's proxy:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
https://analytix402.com/api/p/{your-api-slug}/your/endpoint
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Pass your spend key as a header:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const response = await fetch(
|
|
201
|
+
'https://analytix402.com/api/p/weather-api/forecast?city=NYC',
|
|
202
|
+
{
|
|
203
|
+
headers: {
|
|
204
|
+
'X-Spend-Key': 'sk_live_your_spend_key',
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The proxy handles payment, analytics tracking, and circuit breaker enforcement automatically.
|
|
211
|
+
|
|
143
212
|
## Performance
|
|
144
213
|
|
|
145
214
|
- **< 50ms overhead** per request
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var SDK_NAME = "@analytix402/sdk";
|
|
3
|
+
var SDK_VERSION = "0.1.1";
|
|
4
|
+
var DEFAULT_CONFIG = {
|
|
5
|
+
baseUrl: "https://analytix402.com",
|
|
6
|
+
debug: false,
|
|
7
|
+
batchSize: 100,
|
|
8
|
+
flushInterval: 5e3,
|
|
9
|
+
maxRetries: 3,
|
|
10
|
+
maxQueueSize: 1e3,
|
|
11
|
+
timeout: 1e4,
|
|
12
|
+
excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"],
|
|
13
|
+
autoConnect: false,
|
|
14
|
+
heartbeatIntervalMs: 0
|
|
15
|
+
};
|
|
16
|
+
function createClient(config) {
|
|
17
|
+
if (!config.apiKey) {
|
|
18
|
+
throw new Error("Analytix402: apiKey is required");
|
|
19
|
+
}
|
|
20
|
+
if (!config.apiKey.startsWith("ax_live_") && !config.apiKey.startsWith("ax_test_")) {
|
|
21
|
+
console.warn("Analytix402: API key should start with ax_live_ or ax_test_");
|
|
22
|
+
}
|
|
23
|
+
const cfg = {
|
|
24
|
+
...DEFAULT_CONFIG,
|
|
25
|
+
...config
|
|
26
|
+
};
|
|
27
|
+
const agentId = cfg.agentId;
|
|
28
|
+
const queue = [];
|
|
29
|
+
let flushTimer = null;
|
|
30
|
+
let heartbeatTimer = null;
|
|
31
|
+
let isFlushing = false;
|
|
32
|
+
let isShutdown = false;
|
|
33
|
+
const log = (...args) => {
|
|
34
|
+
if (cfg.debug) {
|
|
35
|
+
console.log("[Analytix402]", ...args);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const warn = (...args) => {
|
|
39
|
+
console.warn("[Analytix402]", ...args);
|
|
40
|
+
};
|
|
41
|
+
function enqueue(event) {
|
|
42
|
+
if (isShutdown) {
|
|
43
|
+
warn("Client is shutdown, event dropped");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (queue.length >= cfg.maxQueueSize) {
|
|
47
|
+
warn(`Queue full (${cfg.maxQueueSize}), dropping oldest event`);
|
|
48
|
+
queue.shift();
|
|
49
|
+
}
|
|
50
|
+
queue.push({
|
|
51
|
+
event,
|
|
52
|
+
attempts: 0,
|
|
53
|
+
addedAt: Date.now()
|
|
54
|
+
});
|
|
55
|
+
log(`Event queued (${queue.length} in queue)`);
|
|
56
|
+
if (queue.length >= cfg.batchSize) {
|
|
57
|
+
log("Batch size reached, flushing");
|
|
58
|
+
flush();
|
|
59
|
+
} else if (!flushTimer) {
|
|
60
|
+
flushTimer = setTimeout(() => {
|
|
61
|
+
flushTimer = null;
|
|
62
|
+
flush();
|
|
63
|
+
}, cfg.flushInterval);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function track(event) {
|
|
67
|
+
if (agentId && !event.agentId) {
|
|
68
|
+
event.agentId = agentId;
|
|
69
|
+
}
|
|
70
|
+
enqueue(event);
|
|
71
|
+
}
|
|
72
|
+
function heartbeat(status = "healthy", metadata) {
|
|
73
|
+
if (!agentId) {
|
|
74
|
+
warn("heartbeat() requires agentId in config");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const event = {
|
|
78
|
+
type: "heartbeat",
|
|
79
|
+
agentId,
|
|
80
|
+
status,
|
|
81
|
+
metadata,
|
|
82
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
83
|
+
};
|
|
84
|
+
enqueue(event);
|
|
85
|
+
}
|
|
86
|
+
function reportOutcome(taskId, success, options) {
|
|
87
|
+
const event = {
|
|
88
|
+
type: "task_outcome",
|
|
89
|
+
agentId,
|
|
90
|
+
taskId,
|
|
91
|
+
success,
|
|
92
|
+
durationMs: options?.durationMs,
|
|
93
|
+
cost: options?.cost,
|
|
94
|
+
metadata: options?.metadata,
|
|
95
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
96
|
+
};
|
|
97
|
+
enqueue(event);
|
|
98
|
+
}
|
|
99
|
+
function startTask(taskId) {
|
|
100
|
+
const id = taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
101
|
+
const startTime = Date.now();
|
|
102
|
+
return {
|
|
103
|
+
taskId: id,
|
|
104
|
+
end(success, metadata) {
|
|
105
|
+
const durationMs = Date.now() - startTime;
|
|
106
|
+
reportOutcome(id, success, { durationMs, metadata });
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function trackLLM(usage) {
|
|
111
|
+
const event = {
|
|
112
|
+
type: "llm_usage",
|
|
113
|
+
agentId,
|
|
114
|
+
taskId: usage.taskId,
|
|
115
|
+
model: usage.model,
|
|
116
|
+
provider: usage.provider,
|
|
117
|
+
inputTokens: usage.inputTokens,
|
|
118
|
+
outputTokens: usage.outputTokens,
|
|
119
|
+
totalTokens: usage.inputTokens + usage.outputTokens,
|
|
120
|
+
costUsd: usage.costUsd,
|
|
121
|
+
durationMs: usage.durationMs,
|
|
122
|
+
metadata: usage.metadata,
|
|
123
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
124
|
+
};
|
|
125
|
+
enqueue(event);
|
|
126
|
+
}
|
|
127
|
+
async function sendBatch(events) {
|
|
128
|
+
if (events.length === 0) return true;
|
|
129
|
+
const payload = {
|
|
130
|
+
events,
|
|
131
|
+
sdk: {
|
|
132
|
+
name: SDK_NAME,
|
|
133
|
+
version: SDK_VERSION
|
|
134
|
+
},
|
|
135
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
136
|
+
};
|
|
137
|
+
try {
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
const timeoutId = setTimeout(() => controller.abort(), cfg.timeout);
|
|
140
|
+
const response = await fetch(`${cfg.baseUrl}/api/ingest/batch`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
"X-API-Key": cfg.apiKey,
|
|
145
|
+
"User-Agent": `${SDK_NAME}/${SDK_VERSION}`
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify(payload),
|
|
148
|
+
signal: controller.signal
|
|
149
|
+
});
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
const text = await response.text().catch(() => "Unknown error");
|
|
153
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
154
|
+
}
|
|
155
|
+
log(`Sent ${events.length} events successfully`);
|
|
156
|
+
return true;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
159
|
+
warn("Request timed out");
|
|
160
|
+
} else {
|
|
161
|
+
warn("Failed to send events:", error);
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function flush() {
|
|
167
|
+
if (isFlushing || queue.length === 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
isFlushing = true;
|
|
171
|
+
if (flushTimer) {
|
|
172
|
+
clearTimeout(flushTimer);
|
|
173
|
+
flushTimer = null;
|
|
174
|
+
}
|
|
175
|
+
const batch = queue.splice(0, cfg.batchSize);
|
|
176
|
+
const events = batch.map((q) => q.event);
|
|
177
|
+
log(`Flushing ${events.length} events`);
|
|
178
|
+
const success = await sendBatch(events);
|
|
179
|
+
if (!success) {
|
|
180
|
+
const retriable = batch.filter((q) => q.attempts < cfg.maxRetries);
|
|
181
|
+
if (retriable.length > 0) {
|
|
182
|
+
log(`Re-queuing ${retriable.length} events for retry`);
|
|
183
|
+
for (const item of retriable.reverse()) {
|
|
184
|
+
item.attempts++;
|
|
185
|
+
queue.unshift(item);
|
|
186
|
+
}
|
|
187
|
+
const backoff = Math.min(1e3 * Math.pow(2, retriable[0].attempts), 3e4);
|
|
188
|
+
log(`Retry scheduled in ${backoff}ms`);
|
|
189
|
+
flushTimer = setTimeout(() => {
|
|
190
|
+
flushTimer = null;
|
|
191
|
+
flush();
|
|
192
|
+
}, backoff);
|
|
193
|
+
} else {
|
|
194
|
+
warn(`Dropped ${batch.length} events after ${cfg.maxRetries} retries`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
isFlushing = false;
|
|
198
|
+
if (queue.length > 0 && !flushTimer) {
|
|
199
|
+
flushTimer = setTimeout(() => {
|
|
200
|
+
flushTimer = null;
|
|
201
|
+
flush();
|
|
202
|
+
}, cfg.flushInterval);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function shutdown() {
|
|
206
|
+
log("Shutting down...");
|
|
207
|
+
isShutdown = true;
|
|
208
|
+
if (flushTimer) {
|
|
209
|
+
clearTimeout(flushTimer);
|
|
210
|
+
flushTimer = null;
|
|
211
|
+
}
|
|
212
|
+
if (heartbeatTimer) {
|
|
213
|
+
clearInterval(heartbeatTimer);
|
|
214
|
+
heartbeatTimer = null;
|
|
215
|
+
}
|
|
216
|
+
if (queue.length > 0) {
|
|
217
|
+
log(`Flushing ${queue.length} remaining events`);
|
|
218
|
+
isFlushing = false;
|
|
219
|
+
await flush();
|
|
220
|
+
}
|
|
221
|
+
log("Shutdown complete");
|
|
222
|
+
}
|
|
223
|
+
if (typeof process !== "undefined") {
|
|
224
|
+
const handleExit = () => {
|
|
225
|
+
shutdown().catch(console.error);
|
|
226
|
+
};
|
|
227
|
+
process.on("beforeExit", handleExit);
|
|
228
|
+
process.on("SIGINT", handleExit);
|
|
229
|
+
process.on("SIGTERM", handleExit);
|
|
230
|
+
}
|
|
231
|
+
if (cfg.autoConnect && agentId) {
|
|
232
|
+
log("Auto-connect enabled, sending registration heartbeat");
|
|
233
|
+
heartbeat("healthy", { event: "sdk_connected" });
|
|
234
|
+
}
|
|
235
|
+
if (cfg.heartbeatIntervalMs && cfg.heartbeatIntervalMs > 0 && agentId) {
|
|
236
|
+
log(`Starting periodic heartbeat every ${cfg.heartbeatIntervalMs}ms`);
|
|
237
|
+
heartbeatTimer = setInterval(() => {
|
|
238
|
+
if (!isShutdown) {
|
|
239
|
+
heartbeat("healthy", { event: "periodic" });
|
|
240
|
+
}
|
|
241
|
+
}, cfg.heartbeatIntervalMs);
|
|
242
|
+
if (heartbeatTimer && typeof heartbeatTimer === "object" && "unref" in heartbeatTimer) {
|
|
243
|
+
heartbeatTimer.unref();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
track,
|
|
248
|
+
flush,
|
|
249
|
+
shutdown,
|
|
250
|
+
heartbeat,
|
|
251
|
+
reportOutcome,
|
|
252
|
+
startTask,
|
|
253
|
+
trackLLM
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export {
|
|
258
|
+
createClient
|
|
259
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClient
|
|
3
|
+
} from "./chunk-GV5RDHUW.mjs";
|
|
4
|
+
|
|
5
|
+
// src/wrappers/fetch.ts
|
|
6
|
+
var X402_HEADERS = {
|
|
7
|
+
PAYMENT: "x-payment",
|
|
8
|
+
PAYMENT_RESPONSE: "x-payment-response",
|
|
9
|
+
PAYER: "x-payer",
|
|
10
|
+
AMOUNT: "x-payment-amount",
|
|
11
|
+
CURRENCY: "x-payment-currency",
|
|
12
|
+
TX_HASH: "x-payment-tx",
|
|
13
|
+
FACILITATOR: "x-facilitator"
|
|
14
|
+
};
|
|
15
|
+
function extractPaymentFromResponse(headers) {
|
|
16
|
+
const txHash = headers.get(X402_HEADERS.TX_HASH) || headers.get(X402_HEADERS.PAYMENT_RESPONSE);
|
|
17
|
+
const amount = headers.get(X402_HEADERS.AMOUNT);
|
|
18
|
+
if (!txHash && !amount) return void 0;
|
|
19
|
+
return {
|
|
20
|
+
amount: amount || "0",
|
|
21
|
+
currency: headers.get(X402_HEADERS.CURRENCY) || "USDC",
|
|
22
|
+
wallet: headers.get(X402_HEADERS.PAYER) || "",
|
|
23
|
+
status: "success",
|
|
24
|
+
txHash: txHash || void 0,
|
|
25
|
+
facilitator: headers.get(X402_HEADERS.FACILITATOR) || void 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function extractPaymentFromRequest(headers) {
|
|
29
|
+
const paymentProof = headers[X402_HEADERS.PAYMENT] || headers[X402_HEADERS.TX_HASH];
|
|
30
|
+
if (!paymentProof) return void 0;
|
|
31
|
+
return {
|
|
32
|
+
amount: headers[X402_HEADERS.AMOUNT] || "0",
|
|
33
|
+
currency: headers[X402_HEADERS.CURRENCY] || "USDC",
|
|
34
|
+
wallet: headers[X402_HEADERS.PAYER] || "",
|
|
35
|
+
status: "pending",
|
|
36
|
+
// outbound — not yet confirmed
|
|
37
|
+
txHash: headers[X402_HEADERS.TX_HASH] || void 0,
|
|
38
|
+
facilitator: headers[X402_HEADERS.FACILITATOR] || void 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function shouldExclude(url, patterns) {
|
|
42
|
+
for (const pattern of patterns) {
|
|
43
|
+
if (pattern.includes("*")) {
|
|
44
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
45
|
+
if (regex.test(url)) return true;
|
|
46
|
+
} else if (url.includes(pattern)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function parseUrl(input) {
|
|
53
|
+
let urlStr;
|
|
54
|
+
if (typeof input === "string") {
|
|
55
|
+
urlStr = input;
|
|
56
|
+
} else if (input instanceof URL) {
|
|
57
|
+
urlStr = input.toString();
|
|
58
|
+
} else if (typeof input === "object" && "url" in input) {
|
|
59
|
+
urlStr = input.url;
|
|
60
|
+
} else {
|
|
61
|
+
urlStr = String(input);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const parsed = new URL(urlStr);
|
|
65
|
+
return { url: urlStr, hostname: parsed.hostname, pathname: parsed.pathname };
|
|
66
|
+
} catch {
|
|
67
|
+
return { url: urlStr, hostname: "", pathname: urlStr };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function headersToRecord(init) {
|
|
71
|
+
const raw = init?.headers;
|
|
72
|
+
if (!raw) return {};
|
|
73
|
+
const result = {};
|
|
74
|
+
if (raw instanceof Headers) {
|
|
75
|
+
raw.forEach((v, k) => {
|
|
76
|
+
result[k.toLowerCase()] = v;
|
|
77
|
+
});
|
|
78
|
+
} else if (Array.isArray(raw)) {
|
|
79
|
+
for (const [k, v] of raw) {
|
|
80
|
+
result[k.toLowerCase()] = v;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
84
|
+
result[k.toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function createAnalytix402Fetch(options) {
|
|
90
|
+
const client = createClient(options);
|
|
91
|
+
const onlyPayments = options.onlyPayments ?? false;
|
|
92
|
+
const excludeUrls = [
|
|
93
|
+
...options.excludeUrls || [],
|
|
94
|
+
// Never track calls back to Analytix402 itself
|
|
95
|
+
"*analytix402.com*"
|
|
96
|
+
];
|
|
97
|
+
const wrappedFetch = async function analytix402Fetch(input, init) {
|
|
98
|
+
const { url, pathname } = parseUrl(input);
|
|
99
|
+
if (shouldExclude(url, excludeUrls)) {
|
|
100
|
+
return fetch(input, init);
|
|
101
|
+
}
|
|
102
|
+
const method = init?.method?.toUpperCase() || "GET";
|
|
103
|
+
const requestHeaders = headersToRecord(init);
|
|
104
|
+
const outboundPayment = extractPaymentFromRequest(requestHeaders);
|
|
105
|
+
const start = Date.now();
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = await fetch(input, init);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const event2 = {
|
|
111
|
+
type: "request",
|
|
112
|
+
method,
|
|
113
|
+
path: pathname,
|
|
114
|
+
endpoint: url,
|
|
115
|
+
statusCode: 0,
|
|
116
|
+
responseTimeMs: Date.now() - start,
|
|
117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118
|
+
agentId: options.agentId,
|
|
119
|
+
payment: outboundPayment,
|
|
120
|
+
metadata: {
|
|
121
|
+
error: error instanceof Error ? error.message : String(error)
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
client.track(event2);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
const responseTimeMs = Date.now() - start;
|
|
128
|
+
const responsePayment = extractPaymentFromResponse(response.headers);
|
|
129
|
+
const is402 = response.status === 402;
|
|
130
|
+
const hasPayment = !!(outboundPayment || responsePayment);
|
|
131
|
+
if (onlyPayments && !hasPayment && !is402) {
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
const payment = responsePayment || outboundPayment;
|
|
135
|
+
const event = {
|
|
136
|
+
type: "request",
|
|
137
|
+
method,
|
|
138
|
+
path: pathname,
|
|
139
|
+
endpoint: url,
|
|
140
|
+
statusCode: response.status,
|
|
141
|
+
responseTimeMs,
|
|
142
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
143
|
+
agentId: options.agentId,
|
|
144
|
+
payment: payment || void 0,
|
|
145
|
+
metadata: is402 ? { paymentRequired: true } : void 0
|
|
146
|
+
};
|
|
147
|
+
client.track(event);
|
|
148
|
+
return response;
|
|
149
|
+
};
|
|
150
|
+
wrappedFetch.client = client;
|
|
151
|
+
return wrappedFetch;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
createAnalytix402Fetch
|
|
156
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClient
|
|
3
|
+
} from "./chunk-GV5RDHUW.mjs";
|
|
4
|
+
|
|
5
|
+
// src/wrappers/openai.ts
|
|
6
|
+
var DEFAULT_COSTS = {
|
|
7
|
+
"gpt-4o": { input: 2.5, output: 10 },
|
|
8
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
9
|
+
"gpt-4-turbo": { input: 10, output: 30 },
|
|
10
|
+
"gpt-4": { input: 30, output: 60 },
|
|
11
|
+
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
12
|
+
"o1": { input: 15, output: 60 },
|
|
13
|
+
"o1-mini": { input: 3, output: 12 },
|
|
14
|
+
"o3-mini": { input: 1.1, output: 4.4 }
|
|
15
|
+
};
|
|
16
|
+
function estimateCost(model, inputTokens, outputTokens, customCosts) {
|
|
17
|
+
const costs = customCosts ?? DEFAULT_COSTS;
|
|
18
|
+
let pricing = costs[model];
|
|
19
|
+
if (!pricing) {
|
|
20
|
+
const prefix = Object.keys(costs).find((k) => model.startsWith(k));
|
|
21
|
+
if (prefix) pricing = costs[prefix];
|
|
22
|
+
}
|
|
23
|
+
if (!pricing) return void 0;
|
|
24
|
+
return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
|
|
25
|
+
}
|
|
26
|
+
function withAnalytix(openai, options) {
|
|
27
|
+
const client = createClient(options);
|
|
28
|
+
const customCosts = options.costPerMillionTokens;
|
|
29
|
+
const originalCreate = openai.chat.completions.create.bind(openai.chat.completions);
|
|
30
|
+
const trackedCreate = async function(body, opts) {
|
|
31
|
+
const start = Date.now();
|
|
32
|
+
const model = body.model || "unknown";
|
|
33
|
+
try {
|
|
34
|
+
const result = await originalCreate(body, opts);
|
|
35
|
+
const durationMs = Date.now() - start;
|
|
36
|
+
const inputTokens = result.usage?.prompt_tokens ?? 0;
|
|
37
|
+
const outputTokens = result.usage?.completion_tokens ?? 0;
|
|
38
|
+
const costUsd = estimateCost(model, inputTokens, outputTokens, customCosts);
|
|
39
|
+
client.trackLLM({
|
|
40
|
+
model,
|
|
41
|
+
provider: "openai",
|
|
42
|
+
inputTokens,
|
|
43
|
+
outputTokens,
|
|
44
|
+
costUsd,
|
|
45
|
+
durationMs,
|
|
46
|
+
metadata: {
|
|
47
|
+
completionId: result.id
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const durationMs = Date.now() - start;
|
|
53
|
+
client.trackLLM({
|
|
54
|
+
model,
|
|
55
|
+
provider: "openai",
|
|
56
|
+
inputTokens: 0,
|
|
57
|
+
outputTokens: 0,
|
|
58
|
+
durationMs,
|
|
59
|
+
metadata: {
|
|
60
|
+
error: error instanceof Error ? error.message : String(error)
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const proxiedCompletions = Object.create(openai.chat.completions, {
|
|
67
|
+
create: { value: trackedCreate, writable: true, configurable: true }
|
|
68
|
+
});
|
|
69
|
+
const proxiedChat = Object.create(openai.chat, {
|
|
70
|
+
completions: { value: proxiedCompletions, writable: true, configurable: true }
|
|
71
|
+
});
|
|
72
|
+
const proxied = Object.create(openai, {
|
|
73
|
+
chat: { value: proxiedChat, writable: true, configurable: true }
|
|
74
|
+
});
|
|
75
|
+
proxied.__analytix402 = client;
|
|
76
|
+
return proxied;
|
|
77
|
+
}
|
|
78
|
+
function getAnalytixClient(openai) {
|
|
79
|
+
return openai.__analytix402;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
withAnalytix,
|
|
84
|
+
getAnalytixClient
|
|
85
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClient
|
|
3
|
+
} from "./chunk-GV5RDHUW.mjs";
|
|
4
|
+
|
|
5
|
+
// src/wrappers/anthropic.ts
|
|
6
|
+
var DEFAULT_COSTS = {
|
|
7
|
+
"claude-opus-4-6": { input: 15, output: 75 },
|
|
8
|
+
"claude-sonnet-4-5-20250929": { input: 3, output: 15 },
|
|
9
|
+
"claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
|
|
10
|
+
"claude-3-5-sonnet": { input: 3, output: 15 },
|
|
11
|
+
"claude-3-5-haiku": { input: 0.8, output: 4 },
|
|
12
|
+
"claude-3-opus": { input: 15, output: 75 },
|
|
13
|
+
"claude-3-sonnet": { input: 3, output: 15 },
|
|
14
|
+
"claude-3-haiku": { input: 0.25, output: 1.25 }
|
|
15
|
+
};
|
|
16
|
+
function estimateCost(model, inputTokens, outputTokens, customCosts) {
|
|
17
|
+
const costs = customCosts ?? DEFAULT_COSTS;
|
|
18
|
+
let pricing = costs[model];
|
|
19
|
+
if (!pricing) {
|
|
20
|
+
const prefix = Object.keys(costs).find((k) => model.startsWith(k));
|
|
21
|
+
if (prefix) pricing = costs[prefix];
|
|
22
|
+
}
|
|
23
|
+
if (!pricing) return void 0;
|
|
24
|
+
return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
|
|
25
|
+
}
|
|
26
|
+
function withAnalytix(anthropic, options) {
|
|
27
|
+
const client = createClient(options);
|
|
28
|
+
const customCosts = options.costPerMillionTokens;
|
|
29
|
+
const originalCreate = anthropic.messages.create.bind(anthropic.messages);
|
|
30
|
+
const trackedCreate = async function(body, opts) {
|
|
31
|
+
const start = Date.now();
|
|
32
|
+
const model = body.model || "unknown";
|
|
33
|
+
try {
|
|
34
|
+
const result = await originalCreate(body, opts);
|
|
35
|
+
const durationMs = Date.now() - start;
|
|
36
|
+
const inputTokens = result.usage?.input_tokens ?? 0;
|
|
37
|
+
const outputTokens = result.usage?.output_tokens ?? 0;
|
|
38
|
+
const costUsd = estimateCost(model, inputTokens, outputTokens, customCosts);
|
|
39
|
+
client.trackLLM({
|
|
40
|
+
model,
|
|
41
|
+
provider: "anthropic",
|
|
42
|
+
inputTokens,
|
|
43
|
+
outputTokens,
|
|
44
|
+
costUsd,
|
|
45
|
+
durationMs,
|
|
46
|
+
metadata: {
|
|
47
|
+
messageId: result.id
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const durationMs = Date.now() - start;
|
|
53
|
+
client.trackLLM({
|
|
54
|
+
model,
|
|
55
|
+
provider: "anthropic",
|
|
56
|
+
inputTokens: 0,
|
|
57
|
+
outputTokens: 0,
|
|
58
|
+
durationMs,
|
|
59
|
+
metadata: {
|
|
60
|
+
error: error instanceof Error ? error.message : String(error)
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const proxiedMessages = Object.create(anthropic.messages, {
|
|
67
|
+
create: { value: trackedCreate, writable: true, configurable: true }
|
|
68
|
+
});
|
|
69
|
+
const proxied = Object.create(anthropic, {
|
|
70
|
+
messages: { value: proxiedMessages, writable: true, configurable: true }
|
|
71
|
+
});
|
|
72
|
+
proxied.__analytix402 = client;
|
|
73
|
+
return proxied;
|
|
74
|
+
}
|
|
75
|
+
function getAnalytixClient(anthropic) {
|
|
76
|
+
return anthropic.__analytix402;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
withAnalytix,
|
|
81
|
+
getAnalytixClient
|
|
82
|
+
};
|