@analytix402/sdk 0.1.0 → 0.1.2

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.
@@ -0,0 +1,434 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/wrappers/fetch.ts
21
+ var fetch_exports = {};
22
+ __export(fetch_exports, {
23
+ createAnalytix402Fetch: () => createAnalytix402Fetch
24
+ });
25
+ module.exports = __toCommonJS(fetch_exports);
26
+
27
+ // src/client.ts
28
+ var SDK_NAME = "@analytix402/sdk";
29
+ var SDK_VERSION = "0.1.1";
30
+ var DEFAULT_CONFIG = {
31
+ baseUrl: "https://analytix402.com",
32
+ debug: false,
33
+ batchSize: 100,
34
+ flushInterval: 5e3,
35
+ maxRetries: 3,
36
+ maxQueueSize: 1e3,
37
+ timeout: 1e4,
38
+ excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"],
39
+ autoConnect: false,
40
+ heartbeatIntervalMs: 0
41
+ };
42
+ function createClient(config) {
43
+ if (!config.apiKey) {
44
+ throw new Error("Analytix402: apiKey is required");
45
+ }
46
+ if (!config.apiKey.startsWith("ax_live_") && !config.apiKey.startsWith("ax_test_")) {
47
+ console.warn("Analytix402: API key should start with ax_live_ or ax_test_");
48
+ }
49
+ const cfg = {
50
+ ...DEFAULT_CONFIG,
51
+ ...config
52
+ };
53
+ const agentId = cfg.agentId;
54
+ const queue = [];
55
+ let flushTimer = null;
56
+ let heartbeatTimer = null;
57
+ let isFlushing = false;
58
+ let isShutdown = false;
59
+ const log = (...args) => {
60
+ if (cfg.debug) {
61
+ console.log("[Analytix402]", ...args);
62
+ }
63
+ };
64
+ const warn = (...args) => {
65
+ console.warn("[Analytix402]", ...args);
66
+ };
67
+ function enqueue(event) {
68
+ if (isShutdown) {
69
+ warn("Client is shutdown, event dropped");
70
+ return;
71
+ }
72
+ if (queue.length >= cfg.maxQueueSize) {
73
+ warn(`Queue full (${cfg.maxQueueSize}), dropping oldest event`);
74
+ queue.shift();
75
+ }
76
+ queue.push({
77
+ event,
78
+ attempts: 0,
79
+ addedAt: Date.now()
80
+ });
81
+ log(`Event queued (${queue.length} in queue)`);
82
+ if (queue.length >= cfg.batchSize) {
83
+ log("Batch size reached, flushing");
84
+ flush();
85
+ } else if (!flushTimer) {
86
+ flushTimer = setTimeout(() => {
87
+ flushTimer = null;
88
+ flush();
89
+ }, cfg.flushInterval);
90
+ }
91
+ }
92
+ function track(event) {
93
+ if (agentId && !event.agentId) {
94
+ event.agentId = agentId;
95
+ }
96
+ enqueue(event);
97
+ }
98
+ function heartbeat(status = "healthy", metadata) {
99
+ if (!agentId) {
100
+ warn("heartbeat() requires agentId in config");
101
+ return;
102
+ }
103
+ const event = {
104
+ type: "heartbeat",
105
+ agentId,
106
+ status,
107
+ metadata,
108
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
109
+ };
110
+ enqueue(event);
111
+ }
112
+ function reportOutcome(taskId, success, options) {
113
+ const event = {
114
+ type: "task_outcome",
115
+ agentId,
116
+ taskId,
117
+ success,
118
+ durationMs: options?.durationMs,
119
+ cost: options?.cost,
120
+ metadata: options?.metadata,
121
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
122
+ };
123
+ enqueue(event);
124
+ }
125
+ function startTask(taskId) {
126
+ const id = taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
127
+ const startTime = Date.now();
128
+ return {
129
+ taskId: id,
130
+ end(success, metadata) {
131
+ const durationMs = Date.now() - startTime;
132
+ reportOutcome(id, success, { durationMs, metadata });
133
+ }
134
+ };
135
+ }
136
+ function trackLLM(usage) {
137
+ const event = {
138
+ type: "llm_usage",
139
+ agentId,
140
+ taskId: usage.taskId,
141
+ model: usage.model,
142
+ provider: usage.provider,
143
+ inputTokens: usage.inputTokens,
144
+ outputTokens: usage.outputTokens,
145
+ totalTokens: usage.inputTokens + usage.outputTokens,
146
+ costUsd: usage.costUsd,
147
+ durationMs: usage.durationMs,
148
+ metadata: usage.metadata,
149
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
150
+ };
151
+ enqueue(event);
152
+ }
153
+ async function sendBatch(events) {
154
+ if (events.length === 0) return true;
155
+ const payload = {
156
+ events,
157
+ sdk: {
158
+ name: SDK_NAME,
159
+ version: SDK_VERSION
160
+ },
161
+ sentAt: (/* @__PURE__ */ new Date()).toISOString()
162
+ };
163
+ try {
164
+ const controller = new AbortController();
165
+ const timeoutId = setTimeout(() => controller.abort(), cfg.timeout);
166
+ const response = await fetch(`${cfg.baseUrl}/api/ingest/batch`, {
167
+ method: "POST",
168
+ headers: {
169
+ "Content-Type": "application/json",
170
+ "X-API-Key": cfg.apiKey,
171
+ "User-Agent": `${SDK_NAME}/${SDK_VERSION}`
172
+ },
173
+ body: JSON.stringify(payload),
174
+ signal: controller.signal
175
+ });
176
+ clearTimeout(timeoutId);
177
+ if (!response.ok) {
178
+ const text = await response.text().catch(() => "Unknown error");
179
+ throw new Error(`HTTP ${response.status}: ${text}`);
180
+ }
181
+ log(`Sent ${events.length} events successfully`);
182
+ return true;
183
+ } catch (error) {
184
+ if (error instanceof Error && error.name === "AbortError") {
185
+ warn("Request timed out");
186
+ } else {
187
+ warn("Failed to send events:", error);
188
+ }
189
+ return false;
190
+ }
191
+ }
192
+ async function flush() {
193
+ if (isFlushing || queue.length === 0) {
194
+ return;
195
+ }
196
+ isFlushing = true;
197
+ if (flushTimer) {
198
+ clearTimeout(flushTimer);
199
+ flushTimer = null;
200
+ }
201
+ const batch = queue.splice(0, cfg.batchSize);
202
+ const events = batch.map((q) => q.event);
203
+ log(`Flushing ${events.length} events`);
204
+ const success = await sendBatch(events);
205
+ if (!success) {
206
+ const retriable = batch.filter((q) => q.attempts < cfg.maxRetries);
207
+ if (retriable.length > 0) {
208
+ log(`Re-queuing ${retriable.length} events for retry`);
209
+ for (const item of retriable.reverse()) {
210
+ item.attempts++;
211
+ queue.unshift(item);
212
+ }
213
+ const backoff = Math.min(1e3 * Math.pow(2, retriable[0].attempts), 3e4);
214
+ log(`Retry scheduled in ${backoff}ms`);
215
+ flushTimer = setTimeout(() => {
216
+ flushTimer = null;
217
+ flush();
218
+ }, backoff);
219
+ } else {
220
+ warn(`Dropped ${batch.length} events after ${cfg.maxRetries} retries`);
221
+ }
222
+ }
223
+ isFlushing = false;
224
+ if (queue.length > 0 && !flushTimer) {
225
+ flushTimer = setTimeout(() => {
226
+ flushTimer = null;
227
+ flush();
228
+ }, cfg.flushInterval);
229
+ }
230
+ }
231
+ async function shutdown() {
232
+ log("Shutting down...");
233
+ isShutdown = true;
234
+ if (flushTimer) {
235
+ clearTimeout(flushTimer);
236
+ flushTimer = null;
237
+ }
238
+ if (heartbeatTimer) {
239
+ clearInterval(heartbeatTimer);
240
+ heartbeatTimer = null;
241
+ }
242
+ if (queue.length > 0) {
243
+ log(`Flushing ${queue.length} remaining events`);
244
+ isFlushing = false;
245
+ await flush();
246
+ }
247
+ log("Shutdown complete");
248
+ }
249
+ if (typeof process !== "undefined") {
250
+ const handleExit = () => {
251
+ shutdown().catch(console.error);
252
+ };
253
+ process.on("beforeExit", handleExit);
254
+ process.on("SIGINT", handleExit);
255
+ process.on("SIGTERM", handleExit);
256
+ }
257
+ if (cfg.autoConnect && agentId) {
258
+ log("Auto-connect enabled, sending registration heartbeat");
259
+ heartbeat("healthy", { event: "sdk_connected" });
260
+ }
261
+ if (cfg.heartbeatIntervalMs && cfg.heartbeatIntervalMs > 0 && agentId) {
262
+ log(`Starting periodic heartbeat every ${cfg.heartbeatIntervalMs}ms`);
263
+ heartbeatTimer = setInterval(() => {
264
+ if (!isShutdown) {
265
+ heartbeat("healthy", { event: "periodic" });
266
+ }
267
+ }, cfg.heartbeatIntervalMs);
268
+ if (heartbeatTimer && typeof heartbeatTimer === "object" && "unref" in heartbeatTimer) {
269
+ heartbeatTimer.unref();
270
+ }
271
+ }
272
+ return {
273
+ track,
274
+ flush,
275
+ shutdown,
276
+ heartbeat,
277
+ reportOutcome,
278
+ startTask,
279
+ trackLLM
280
+ };
281
+ }
282
+
283
+ // src/wrappers/fetch.ts
284
+ var X402_HEADERS = {
285
+ PAYMENT: "x-payment",
286
+ PAYMENT_RESPONSE: "x-payment-response",
287
+ PAYER: "x-payer",
288
+ AMOUNT: "x-payment-amount",
289
+ CURRENCY: "x-payment-currency",
290
+ TX_HASH: "x-payment-tx",
291
+ FACILITATOR: "x-facilitator"
292
+ };
293
+ function extractPaymentFromResponse(headers) {
294
+ const txHash = headers.get(X402_HEADERS.TX_HASH) || headers.get(X402_HEADERS.PAYMENT_RESPONSE);
295
+ const amount = headers.get(X402_HEADERS.AMOUNT);
296
+ if (!txHash && !amount) return void 0;
297
+ return {
298
+ amount: amount || "0",
299
+ currency: headers.get(X402_HEADERS.CURRENCY) || "USDC",
300
+ wallet: headers.get(X402_HEADERS.PAYER) || "",
301
+ status: "success",
302
+ txHash: txHash || void 0,
303
+ facilitator: headers.get(X402_HEADERS.FACILITATOR) || void 0
304
+ };
305
+ }
306
+ function extractPaymentFromRequest(headers) {
307
+ const paymentProof = headers[X402_HEADERS.PAYMENT] || headers[X402_HEADERS.TX_HASH];
308
+ if (!paymentProof) return void 0;
309
+ return {
310
+ amount: headers[X402_HEADERS.AMOUNT] || "0",
311
+ currency: headers[X402_HEADERS.CURRENCY] || "USDC",
312
+ wallet: headers[X402_HEADERS.PAYER] || "",
313
+ status: "pending",
314
+ // outbound — not yet confirmed
315
+ txHash: headers[X402_HEADERS.TX_HASH] || void 0,
316
+ facilitator: headers[X402_HEADERS.FACILITATOR] || void 0
317
+ };
318
+ }
319
+ function shouldExclude(url, patterns) {
320
+ for (const pattern of patterns) {
321
+ if (pattern.includes("*")) {
322
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
323
+ if (regex.test(url)) return true;
324
+ } else if (url.includes(pattern)) {
325
+ return true;
326
+ }
327
+ }
328
+ return false;
329
+ }
330
+ function parseUrl(input) {
331
+ let urlStr;
332
+ if (typeof input === "string") {
333
+ urlStr = input;
334
+ } else if (input instanceof URL) {
335
+ urlStr = input.toString();
336
+ } else if (typeof input === "object" && "url" in input) {
337
+ urlStr = input.url;
338
+ } else {
339
+ urlStr = String(input);
340
+ }
341
+ try {
342
+ const parsed = new URL(urlStr);
343
+ return { url: urlStr, hostname: parsed.hostname, pathname: parsed.pathname };
344
+ } catch {
345
+ return { url: urlStr, hostname: "", pathname: urlStr };
346
+ }
347
+ }
348
+ function headersToRecord(init) {
349
+ const raw = init?.headers;
350
+ if (!raw) return {};
351
+ const result = {};
352
+ if (raw instanceof Headers) {
353
+ raw.forEach((v, k) => {
354
+ result[k.toLowerCase()] = v;
355
+ });
356
+ } else if (Array.isArray(raw)) {
357
+ for (const [k, v] of raw) {
358
+ result[k.toLowerCase()] = v;
359
+ }
360
+ } else {
361
+ for (const [k, v] of Object.entries(raw)) {
362
+ result[k.toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
363
+ }
364
+ }
365
+ return result;
366
+ }
367
+ function createAnalytix402Fetch(options) {
368
+ const client = createClient(options);
369
+ const onlyPayments = options.onlyPayments ?? false;
370
+ const excludeUrls = [
371
+ ...options.excludeUrls || [],
372
+ // Never track calls back to Analytix402 itself
373
+ "*analytix402.com*"
374
+ ];
375
+ const wrappedFetch = async function analytix402Fetch(input, init) {
376
+ const { url, pathname } = parseUrl(input);
377
+ if (shouldExclude(url, excludeUrls)) {
378
+ return fetch(input, init);
379
+ }
380
+ const method = init?.method?.toUpperCase() || "GET";
381
+ const requestHeaders = headersToRecord(init);
382
+ const outboundPayment = extractPaymentFromRequest(requestHeaders);
383
+ const start = Date.now();
384
+ let response;
385
+ try {
386
+ response = await fetch(input, init);
387
+ } catch (error) {
388
+ const event2 = {
389
+ type: "request",
390
+ method,
391
+ path: pathname,
392
+ endpoint: url,
393
+ statusCode: 0,
394
+ responseTimeMs: Date.now() - start,
395
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
396
+ agentId: options.agentId,
397
+ payment: outboundPayment,
398
+ metadata: {
399
+ error: error instanceof Error ? error.message : String(error)
400
+ }
401
+ };
402
+ client.track(event2);
403
+ throw error;
404
+ }
405
+ const responseTimeMs = Date.now() - start;
406
+ const responsePayment = extractPaymentFromResponse(response.headers);
407
+ const is402 = response.status === 402;
408
+ const hasPayment = !!(outboundPayment || responsePayment);
409
+ if (onlyPayments && !hasPayment && !is402) {
410
+ return response;
411
+ }
412
+ const payment = responsePayment || outboundPayment;
413
+ const event = {
414
+ type: "request",
415
+ method,
416
+ path: pathname,
417
+ endpoint: url,
418
+ statusCode: response.status,
419
+ responseTimeMs,
420
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
421
+ agentId: options.agentId,
422
+ payment: payment || void 0,
423
+ metadata: is402 ? { paymentRequired: true } : void 0
424
+ };
425
+ client.track(event);
426
+ return response;
427
+ };
428
+ wrappedFetch.client = client;
429
+ return wrappedFetch;
430
+ }
431
+ // Annotate the CommonJS export names for ESM import in node:
432
+ 0 && (module.exports = {
433
+ createAnalytix402Fetch
434
+ });
@@ -0,0 +1,7 @@
1
+ import {
2
+ createAnalytix402Fetch
3
+ } from "../chunk-KSO5EEA2.mjs";
4
+ import "../chunk-GV5RDHUW.mjs";
5
+ export {
6
+ createAnalytix402Fetch
7
+ };
@@ -0,0 +1,69 @@
1
+ import { A as Analytix402Config, a as Analytix402Client } from '../types-C67qDpYb.mjs';
2
+
3
+ /**
4
+ * Analytix402 OpenAI Wrapper
5
+ *
6
+ * Auto-instruments OpenAI SDK calls to track LLM usage,
7
+ * costs, and latency through Analytix402.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import OpenAI from 'openai';
12
+ * import { withAnalytix } from '@analytix402/sdk/openai';
13
+ *
14
+ * const openai = withAnalytix(new OpenAI(), {
15
+ * apiKey: 'ax_live_your_key_here',
16
+ * });
17
+ *
18
+ * // All calls are now auto-tracked
19
+ * const res = await openai.chat.completions.create({
20
+ * model: 'gpt-4o',
21
+ * messages: [{ role: 'user', content: 'Hello' }],
22
+ * });
23
+ * ```
24
+ */
25
+
26
+ /** Minimal OpenAI SDK shape — avoids hard dependency on the openai package. */
27
+ interface OpenAICompletionUsage {
28
+ prompt_tokens: number;
29
+ completion_tokens: number;
30
+ total_tokens?: number;
31
+ }
32
+ interface OpenAICompletion {
33
+ id?: string;
34
+ model?: string;
35
+ usage?: OpenAICompletionUsage;
36
+ [key: string]: unknown;
37
+ }
38
+ interface OpenAIChatCompletions {
39
+ create(body: Record<string, unknown>, options?: Record<string, unknown>): Promise<OpenAICompletion>;
40
+ }
41
+ interface OpenAIChat {
42
+ completions: OpenAIChatCompletions;
43
+ }
44
+ interface OpenAILike {
45
+ chat: OpenAIChat;
46
+ [key: string]: unknown;
47
+ }
48
+ interface OpenAIWrapperOptions extends Analytix402Config {
49
+ /**
50
+ * Override cost table (USD per 1 million tokens).
51
+ * Keys are model names; values have `input` and `output` fields.
52
+ */
53
+ costPerMillionTokens?: Record<string, {
54
+ input: number;
55
+ output: number;
56
+ }>;
57
+ }
58
+ /**
59
+ * Wrap an OpenAI SDK instance to auto-track LLM usage via Analytix402.
60
+ *
61
+ * Returns a proxied instance — the original is not mutated.
62
+ */
63
+ declare function withAnalytix<T extends OpenAILike>(openai: T, options: OpenAIWrapperOptions): T;
64
+ /**
65
+ * Access the Analytix402 client from a wrapped OpenAI instance.
66
+ */
67
+ declare function getAnalytixClient(openai: OpenAILike): Analytix402Client | undefined;
68
+
69
+ export { type OpenAIWrapperOptions, getAnalytixClient, withAnalytix };
@@ -0,0 +1,69 @@
1
+ import { A as Analytix402Config, a as Analytix402Client } from '../types-C67qDpYb.js';
2
+
3
+ /**
4
+ * Analytix402 OpenAI Wrapper
5
+ *
6
+ * Auto-instruments OpenAI SDK calls to track LLM usage,
7
+ * costs, and latency through Analytix402.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import OpenAI from 'openai';
12
+ * import { withAnalytix } from '@analytix402/sdk/openai';
13
+ *
14
+ * const openai = withAnalytix(new OpenAI(), {
15
+ * apiKey: 'ax_live_your_key_here',
16
+ * });
17
+ *
18
+ * // All calls are now auto-tracked
19
+ * const res = await openai.chat.completions.create({
20
+ * model: 'gpt-4o',
21
+ * messages: [{ role: 'user', content: 'Hello' }],
22
+ * });
23
+ * ```
24
+ */
25
+
26
+ /** Minimal OpenAI SDK shape — avoids hard dependency on the openai package. */
27
+ interface OpenAICompletionUsage {
28
+ prompt_tokens: number;
29
+ completion_tokens: number;
30
+ total_tokens?: number;
31
+ }
32
+ interface OpenAICompletion {
33
+ id?: string;
34
+ model?: string;
35
+ usage?: OpenAICompletionUsage;
36
+ [key: string]: unknown;
37
+ }
38
+ interface OpenAIChatCompletions {
39
+ create(body: Record<string, unknown>, options?: Record<string, unknown>): Promise<OpenAICompletion>;
40
+ }
41
+ interface OpenAIChat {
42
+ completions: OpenAIChatCompletions;
43
+ }
44
+ interface OpenAILike {
45
+ chat: OpenAIChat;
46
+ [key: string]: unknown;
47
+ }
48
+ interface OpenAIWrapperOptions extends Analytix402Config {
49
+ /**
50
+ * Override cost table (USD per 1 million tokens).
51
+ * Keys are model names; values have `input` and `output` fields.
52
+ */
53
+ costPerMillionTokens?: Record<string, {
54
+ input: number;
55
+ output: number;
56
+ }>;
57
+ }
58
+ /**
59
+ * Wrap an OpenAI SDK instance to auto-track LLM usage via Analytix402.
60
+ *
61
+ * Returns a proxied instance — the original is not mutated.
62
+ */
63
+ declare function withAnalytix<T extends OpenAILike>(openai: T, options: OpenAIWrapperOptions): T;
64
+ /**
65
+ * Access the Analytix402 client from a wrapped OpenAI instance.
66
+ */
67
+ declare function getAnalytixClient(openai: OpenAILike): Analytix402Client | undefined;
68
+
69
+ export { type OpenAIWrapperOptions, getAnalytixClient, withAnalytix };