@analytix402/sdk 0.1.1 → 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.
package/dist/index.js CHANGED
@@ -23,7 +23,10 @@ __export(index_exports, {
23
23
  Analytix402: () => Analytix402,
24
24
  analytix402: () => analytix402,
25
25
  createAnalytix402Client: () => createAnalytix402Client,
26
- createClient: () => createClient
26
+ createAnalytix402Fetch: () => createAnalytix402Fetch,
27
+ createClient: () => createClient,
28
+ withAnalytixAnthropic: () => withAnalytix2,
29
+ withAnalytixOpenAI: () => withAnalytix
27
30
  });
28
31
  module.exports = __toCommonJS(index_exports);
29
32
 
@@ -38,7 +41,9 @@ var DEFAULT_CONFIG = {
38
41
  maxRetries: 3,
39
42
  maxQueueSize: 1e3,
40
43
  timeout: 1e4,
41
- excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"]
44
+ excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"],
45
+ autoConnect: false,
46
+ heartbeatIntervalMs: 0
42
47
  };
43
48
  function createClient(config) {
44
49
  if (!config.apiKey) {
@@ -54,6 +59,7 @@ function createClient(config) {
54
59
  const agentId = cfg.agentId;
55
60
  const queue = [];
56
61
  let flushTimer = null;
62
+ let heartbeatTimer = null;
57
63
  let isFlushing = false;
58
64
  let isShutdown = false;
59
65
  const log = (...args) => {
@@ -235,6 +241,10 @@ function createClient(config) {
235
241
  clearTimeout(flushTimer);
236
242
  flushTimer = null;
237
243
  }
244
+ if (heartbeatTimer) {
245
+ clearInterval(heartbeatTimer);
246
+ heartbeatTimer = null;
247
+ }
238
248
  if (queue.length > 0) {
239
249
  log(`Flushing ${queue.length} remaining events`);
240
250
  isFlushing = false;
@@ -250,6 +260,21 @@ function createClient(config) {
250
260
  process.on("SIGINT", handleExit);
251
261
  process.on("SIGTERM", handleExit);
252
262
  }
263
+ if (cfg.autoConnect && agentId) {
264
+ log("Auto-connect enabled, sending registration heartbeat");
265
+ heartbeat("healthy", { event: "sdk_connected" });
266
+ }
267
+ if (cfg.heartbeatIntervalMs && cfg.heartbeatIntervalMs > 0 && agentId) {
268
+ log(`Starting periodic heartbeat every ${cfg.heartbeatIntervalMs}ms`);
269
+ heartbeatTimer = setInterval(() => {
270
+ if (!isShutdown) {
271
+ heartbeat("healthy", { event: "periodic" });
272
+ }
273
+ }, cfg.heartbeatIntervalMs);
274
+ if (heartbeatTimer && typeof heartbeatTimer === "object" && "unref" in heartbeatTimer) {
275
+ heartbeatTimer.unref();
276
+ }
277
+ }
253
278
  return {
254
279
  track,
255
280
  flush,
@@ -404,10 +429,307 @@ var Analytix402 = analytix402;
404
429
  function createAnalytix402Client(config) {
405
430
  return createClient(config);
406
431
  }
432
+
433
+ // src/wrappers/openai.ts
434
+ var DEFAULT_COSTS = {
435
+ "gpt-4o": { input: 2.5, output: 10 },
436
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
437
+ "gpt-4-turbo": { input: 10, output: 30 },
438
+ "gpt-4": { input: 30, output: 60 },
439
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
440
+ "o1": { input: 15, output: 60 },
441
+ "o1-mini": { input: 3, output: 12 },
442
+ "o3-mini": { input: 1.1, output: 4.4 }
443
+ };
444
+ function estimateCost(model, inputTokens, outputTokens, customCosts) {
445
+ const costs = customCosts ?? DEFAULT_COSTS;
446
+ let pricing = costs[model];
447
+ if (!pricing) {
448
+ const prefix = Object.keys(costs).find((k) => model.startsWith(k));
449
+ if (prefix) pricing = costs[prefix];
450
+ }
451
+ if (!pricing) return void 0;
452
+ return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
453
+ }
454
+ function withAnalytix(openai, options) {
455
+ const client = createClient(options);
456
+ const customCosts = options.costPerMillionTokens;
457
+ const originalCreate = openai.chat.completions.create.bind(openai.chat.completions);
458
+ const trackedCreate = async function(body, opts) {
459
+ const start = Date.now();
460
+ const model = body.model || "unknown";
461
+ try {
462
+ const result = await originalCreate(body, opts);
463
+ const durationMs = Date.now() - start;
464
+ const inputTokens = result.usage?.prompt_tokens ?? 0;
465
+ const outputTokens = result.usage?.completion_tokens ?? 0;
466
+ const costUsd = estimateCost(model, inputTokens, outputTokens, customCosts);
467
+ client.trackLLM({
468
+ model,
469
+ provider: "openai",
470
+ inputTokens,
471
+ outputTokens,
472
+ costUsd,
473
+ durationMs,
474
+ metadata: {
475
+ completionId: result.id
476
+ }
477
+ });
478
+ return result;
479
+ } catch (error) {
480
+ const durationMs = Date.now() - start;
481
+ client.trackLLM({
482
+ model,
483
+ provider: "openai",
484
+ inputTokens: 0,
485
+ outputTokens: 0,
486
+ durationMs,
487
+ metadata: {
488
+ error: error instanceof Error ? error.message : String(error)
489
+ }
490
+ });
491
+ throw error;
492
+ }
493
+ };
494
+ const proxiedCompletions = Object.create(openai.chat.completions, {
495
+ create: { value: trackedCreate, writable: true, configurable: true }
496
+ });
497
+ const proxiedChat = Object.create(openai.chat, {
498
+ completions: { value: proxiedCompletions, writable: true, configurable: true }
499
+ });
500
+ const proxied = Object.create(openai, {
501
+ chat: { value: proxiedChat, writable: true, configurable: true }
502
+ });
503
+ proxied.__analytix402 = client;
504
+ return proxied;
505
+ }
506
+
507
+ // src/wrappers/anthropic.ts
508
+ var DEFAULT_COSTS2 = {
509
+ "claude-opus-4-6": { input: 15, output: 75 },
510
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15 },
511
+ "claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
512
+ "claude-3-5-sonnet": { input: 3, output: 15 },
513
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
514
+ "claude-3-opus": { input: 15, output: 75 },
515
+ "claude-3-sonnet": { input: 3, output: 15 },
516
+ "claude-3-haiku": { input: 0.25, output: 1.25 }
517
+ };
518
+ function estimateCost2(model, inputTokens, outputTokens, customCosts) {
519
+ const costs = customCosts ?? DEFAULT_COSTS2;
520
+ let pricing = costs[model];
521
+ if (!pricing) {
522
+ const prefix = Object.keys(costs).find((k) => model.startsWith(k));
523
+ if (prefix) pricing = costs[prefix];
524
+ }
525
+ if (!pricing) return void 0;
526
+ return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
527
+ }
528
+ function withAnalytix2(anthropic, options) {
529
+ const client = createClient(options);
530
+ const customCosts = options.costPerMillionTokens;
531
+ const originalCreate = anthropic.messages.create.bind(anthropic.messages);
532
+ const trackedCreate = async function(body, opts) {
533
+ const start = Date.now();
534
+ const model = body.model || "unknown";
535
+ try {
536
+ const result = await originalCreate(body, opts);
537
+ const durationMs = Date.now() - start;
538
+ const inputTokens = result.usage?.input_tokens ?? 0;
539
+ const outputTokens = result.usage?.output_tokens ?? 0;
540
+ const costUsd = estimateCost2(model, inputTokens, outputTokens, customCosts);
541
+ client.trackLLM({
542
+ model,
543
+ provider: "anthropic",
544
+ inputTokens,
545
+ outputTokens,
546
+ costUsd,
547
+ durationMs,
548
+ metadata: {
549
+ messageId: result.id
550
+ }
551
+ });
552
+ return result;
553
+ } catch (error) {
554
+ const durationMs = Date.now() - start;
555
+ client.trackLLM({
556
+ model,
557
+ provider: "anthropic",
558
+ inputTokens: 0,
559
+ outputTokens: 0,
560
+ durationMs,
561
+ metadata: {
562
+ error: error instanceof Error ? error.message : String(error)
563
+ }
564
+ });
565
+ throw error;
566
+ }
567
+ };
568
+ const proxiedMessages = Object.create(anthropic.messages, {
569
+ create: { value: trackedCreate, writable: true, configurable: true }
570
+ });
571
+ const proxied = Object.create(anthropic, {
572
+ messages: { value: proxiedMessages, writable: true, configurable: true }
573
+ });
574
+ proxied.__analytix402 = client;
575
+ return proxied;
576
+ }
577
+
578
+ // src/wrappers/fetch.ts
579
+ var X402_HEADERS2 = {
580
+ PAYMENT: "x-payment",
581
+ PAYMENT_RESPONSE: "x-payment-response",
582
+ PAYER: "x-payer",
583
+ AMOUNT: "x-payment-amount",
584
+ CURRENCY: "x-payment-currency",
585
+ TX_HASH: "x-payment-tx",
586
+ FACILITATOR: "x-facilitator"
587
+ };
588
+ function extractPaymentFromResponse(headers) {
589
+ const txHash = headers.get(X402_HEADERS2.TX_HASH) || headers.get(X402_HEADERS2.PAYMENT_RESPONSE);
590
+ const amount = headers.get(X402_HEADERS2.AMOUNT);
591
+ if (!txHash && !amount) return void 0;
592
+ return {
593
+ amount: amount || "0",
594
+ currency: headers.get(X402_HEADERS2.CURRENCY) || "USDC",
595
+ wallet: headers.get(X402_HEADERS2.PAYER) || "",
596
+ status: "success",
597
+ txHash: txHash || void 0,
598
+ facilitator: headers.get(X402_HEADERS2.FACILITATOR) || void 0
599
+ };
600
+ }
601
+ function extractPaymentFromRequest(headers) {
602
+ const paymentProof = headers[X402_HEADERS2.PAYMENT] || headers[X402_HEADERS2.TX_HASH];
603
+ if (!paymentProof) return void 0;
604
+ return {
605
+ amount: headers[X402_HEADERS2.AMOUNT] || "0",
606
+ currency: headers[X402_HEADERS2.CURRENCY] || "USDC",
607
+ wallet: headers[X402_HEADERS2.PAYER] || "",
608
+ status: "pending",
609
+ // outbound — not yet confirmed
610
+ txHash: headers[X402_HEADERS2.TX_HASH] || void 0,
611
+ facilitator: headers[X402_HEADERS2.FACILITATOR] || void 0
612
+ };
613
+ }
614
+ function shouldExclude(url, patterns) {
615
+ for (const pattern of patterns) {
616
+ if (pattern.includes("*")) {
617
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
618
+ if (regex.test(url)) return true;
619
+ } else if (url.includes(pattern)) {
620
+ return true;
621
+ }
622
+ }
623
+ return false;
624
+ }
625
+ function parseUrl(input) {
626
+ let urlStr;
627
+ if (typeof input === "string") {
628
+ urlStr = input;
629
+ } else if (input instanceof URL) {
630
+ urlStr = input.toString();
631
+ } else if (typeof input === "object" && "url" in input) {
632
+ urlStr = input.url;
633
+ } else {
634
+ urlStr = String(input);
635
+ }
636
+ try {
637
+ const parsed = new URL(urlStr);
638
+ return { url: urlStr, hostname: parsed.hostname, pathname: parsed.pathname };
639
+ } catch {
640
+ return { url: urlStr, hostname: "", pathname: urlStr };
641
+ }
642
+ }
643
+ function headersToRecord(init) {
644
+ const raw = init?.headers;
645
+ if (!raw) return {};
646
+ const result = {};
647
+ if (raw instanceof Headers) {
648
+ raw.forEach((v, k) => {
649
+ result[k.toLowerCase()] = v;
650
+ });
651
+ } else if (Array.isArray(raw)) {
652
+ for (const [k, v] of raw) {
653
+ result[k.toLowerCase()] = v;
654
+ }
655
+ } else {
656
+ for (const [k, v] of Object.entries(raw)) {
657
+ result[k.toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
658
+ }
659
+ }
660
+ return result;
661
+ }
662
+ function createAnalytix402Fetch(options) {
663
+ const client = createClient(options);
664
+ const onlyPayments = options.onlyPayments ?? false;
665
+ const excludeUrls = [
666
+ ...options.excludeUrls || [],
667
+ // Never track calls back to Analytix402 itself
668
+ "*analytix402.com*"
669
+ ];
670
+ const wrappedFetch = async function analytix402Fetch(input, init) {
671
+ const { url, pathname } = parseUrl(input);
672
+ if (shouldExclude(url, excludeUrls)) {
673
+ return fetch(input, init);
674
+ }
675
+ const method = init?.method?.toUpperCase() || "GET";
676
+ const requestHeaders = headersToRecord(init);
677
+ const outboundPayment = extractPaymentFromRequest(requestHeaders);
678
+ const start = Date.now();
679
+ let response;
680
+ try {
681
+ response = await fetch(input, init);
682
+ } catch (error) {
683
+ const event2 = {
684
+ type: "request",
685
+ method,
686
+ path: pathname,
687
+ endpoint: url,
688
+ statusCode: 0,
689
+ responseTimeMs: Date.now() - start,
690
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
691
+ agentId: options.agentId,
692
+ payment: outboundPayment,
693
+ metadata: {
694
+ error: error instanceof Error ? error.message : String(error)
695
+ }
696
+ };
697
+ client.track(event2);
698
+ throw error;
699
+ }
700
+ const responseTimeMs = Date.now() - start;
701
+ const responsePayment = extractPaymentFromResponse(response.headers);
702
+ const is402 = response.status === 402;
703
+ const hasPayment = !!(outboundPayment || responsePayment);
704
+ if (onlyPayments && !hasPayment && !is402) {
705
+ return response;
706
+ }
707
+ const payment = responsePayment || outboundPayment;
708
+ const event = {
709
+ type: "request",
710
+ method,
711
+ path: pathname,
712
+ endpoint: url,
713
+ statusCode: response.status,
714
+ responseTimeMs,
715
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
716
+ agentId: options.agentId,
717
+ payment: payment || void 0,
718
+ metadata: is402 ? { paymentRequired: true } : void 0
719
+ };
720
+ client.track(event);
721
+ return response;
722
+ };
723
+ wrappedFetch.client = client;
724
+ return wrappedFetch;
725
+ }
407
726
  // Annotate the CommonJS export names for ESM import in node:
408
727
  0 && (module.exports = {
409
728
  Analytix402,
410
729
  analytix402,
411
730
  createAnalytix402Client,
412
- createClient
731
+ createAnalytix402Fetch,
732
+ createClient,
733
+ withAnalytixAnthropic,
734
+ withAnalytixOpenAI
413
735
  });
package/dist/index.mjs CHANGED
@@ -1,236 +1,15 @@
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
- };
14
- function createClient(config) {
15
- if (!config.apiKey) {
16
- throw new Error("Analytix402: apiKey is required");
17
- }
18
- if (!config.apiKey.startsWith("ax_live_") && !config.apiKey.startsWith("ax_test_")) {
19
- console.warn("Analytix402: API key should start with ax_live_ or ax_test_");
20
- }
21
- const cfg = {
22
- ...DEFAULT_CONFIG,
23
- ...config
24
- };
25
- const agentId = cfg.agentId;
26
- const queue = [];
27
- let flushTimer = null;
28
- let isFlushing = false;
29
- let isShutdown = false;
30
- const log = (...args) => {
31
- if (cfg.debug) {
32
- console.log("[Analytix402]", ...args);
33
- }
34
- };
35
- const warn = (...args) => {
36
- console.warn("[Analytix402]", ...args);
37
- };
38
- function enqueue(event) {
39
- if (isShutdown) {
40
- warn("Client is shutdown, event dropped");
41
- return;
42
- }
43
- if (queue.length >= cfg.maxQueueSize) {
44
- warn(`Queue full (${cfg.maxQueueSize}), dropping oldest event`);
45
- queue.shift();
46
- }
47
- queue.push({
48
- event,
49
- attempts: 0,
50
- addedAt: Date.now()
51
- });
52
- log(`Event queued (${queue.length} in queue)`);
53
- if (queue.length >= cfg.batchSize) {
54
- log("Batch size reached, flushing");
55
- flush();
56
- } else if (!flushTimer) {
57
- flushTimer = setTimeout(() => {
58
- flushTimer = null;
59
- flush();
60
- }, cfg.flushInterval);
61
- }
62
- }
63
- function track(event) {
64
- if (agentId && !event.agentId) {
65
- event.agentId = agentId;
66
- }
67
- enqueue(event);
68
- }
69
- function heartbeat(status = "healthy", metadata) {
70
- if (!agentId) {
71
- warn("heartbeat() requires agentId in config");
72
- return;
73
- }
74
- const event = {
75
- type: "heartbeat",
76
- agentId,
77
- status,
78
- metadata,
79
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
80
- };
81
- enqueue(event);
82
- }
83
- function reportOutcome(taskId, success, options) {
84
- const event = {
85
- type: "task_outcome",
86
- agentId,
87
- taskId,
88
- success,
89
- durationMs: options?.durationMs,
90
- cost: options?.cost,
91
- metadata: options?.metadata,
92
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
93
- };
94
- enqueue(event);
95
- }
96
- function startTask(taskId) {
97
- const id = taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
98
- const startTime = Date.now();
99
- return {
100
- taskId: id,
101
- end(success, metadata) {
102
- const durationMs = Date.now() - startTime;
103
- reportOutcome(id, success, { durationMs, metadata });
104
- }
105
- };
106
- }
107
- function trackLLM(usage) {
108
- const event = {
109
- type: "llm_usage",
110
- agentId,
111
- taskId: usage.taskId,
112
- model: usage.model,
113
- provider: usage.provider,
114
- inputTokens: usage.inputTokens,
115
- outputTokens: usage.outputTokens,
116
- totalTokens: usage.inputTokens + usage.outputTokens,
117
- costUsd: usage.costUsd,
118
- durationMs: usage.durationMs,
119
- metadata: usage.metadata,
120
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
121
- };
122
- enqueue(event);
123
- }
124
- async function sendBatch(events) {
125
- if (events.length === 0) return true;
126
- const payload = {
127
- events,
128
- sdk: {
129
- name: SDK_NAME,
130
- version: SDK_VERSION
131
- },
132
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
133
- };
134
- try {
135
- const controller = new AbortController();
136
- const timeoutId = setTimeout(() => controller.abort(), cfg.timeout);
137
- const response = await fetch(`${cfg.baseUrl}/api/ingest/batch`, {
138
- method: "POST",
139
- headers: {
140
- "Content-Type": "application/json",
141
- "X-API-Key": cfg.apiKey,
142
- "User-Agent": `${SDK_NAME}/${SDK_VERSION}`
143
- },
144
- body: JSON.stringify(payload),
145
- signal: controller.signal
146
- });
147
- clearTimeout(timeoutId);
148
- if (!response.ok) {
149
- const text = await response.text().catch(() => "Unknown error");
150
- throw new Error(`HTTP ${response.status}: ${text}`);
151
- }
152
- log(`Sent ${events.length} events successfully`);
153
- return true;
154
- } catch (error) {
155
- if (error instanceof Error && error.name === "AbortError") {
156
- warn("Request timed out");
157
- } else {
158
- warn("Failed to send events:", error);
159
- }
160
- return false;
161
- }
162
- }
163
- async function flush() {
164
- if (isFlushing || queue.length === 0) {
165
- return;
166
- }
167
- isFlushing = true;
168
- if (flushTimer) {
169
- clearTimeout(flushTimer);
170
- flushTimer = null;
171
- }
172
- const batch = queue.splice(0, cfg.batchSize);
173
- const events = batch.map((q) => q.event);
174
- log(`Flushing ${events.length} events`);
175
- const success = await sendBatch(events);
176
- if (!success) {
177
- const retriable = batch.filter((q) => q.attempts < cfg.maxRetries);
178
- if (retriable.length > 0) {
179
- log(`Re-queuing ${retriable.length} events for retry`);
180
- for (const item of retriable.reverse()) {
181
- item.attempts++;
182
- queue.unshift(item);
183
- }
184
- const backoff = Math.min(1e3 * Math.pow(2, retriable[0].attempts), 3e4);
185
- log(`Retry scheduled in ${backoff}ms`);
186
- flushTimer = setTimeout(() => {
187
- flushTimer = null;
188
- flush();
189
- }, backoff);
190
- } else {
191
- warn(`Dropped ${batch.length} events after ${cfg.maxRetries} retries`);
192
- }
193
- }
194
- isFlushing = false;
195
- if (queue.length > 0 && !flushTimer) {
196
- flushTimer = setTimeout(() => {
197
- flushTimer = null;
198
- flush();
199
- }, cfg.flushInterval);
200
- }
201
- }
202
- async function shutdown() {
203
- log("Shutting down...");
204
- isShutdown = true;
205
- if (flushTimer) {
206
- clearTimeout(flushTimer);
207
- flushTimer = null;
208
- }
209
- if (queue.length > 0) {
210
- log(`Flushing ${queue.length} remaining events`);
211
- isFlushing = false;
212
- await flush();
213
- }
214
- log("Shutdown complete");
215
- }
216
- if (typeof process !== "undefined") {
217
- const handleExit = () => {
218
- shutdown().catch(console.error);
219
- };
220
- process.on("beforeExit", handleExit);
221
- process.on("SIGINT", handleExit);
222
- process.on("SIGTERM", handleExit);
223
- }
224
- return {
225
- track,
226
- flush,
227
- shutdown,
228
- heartbeat,
229
- reportOutcome,
230
- startTask,
231
- trackLLM
232
- };
233
- }
1
+ import {
2
+ withAnalytix as withAnalytix2
3
+ } from "./chunk-PQO3A7RN.mjs";
4
+ import {
5
+ createAnalytix402Fetch
6
+ } from "./chunk-KSO5EEA2.mjs";
7
+ import {
8
+ withAnalytix
9
+ } from "./chunk-MIPQLCB6.mjs";
10
+ import {
11
+ createClient
12
+ } from "./chunk-GV5RDHUW.mjs";
234
13
 
235
14
  // src/middleware.ts
236
15
  var X402_HEADERS = {
@@ -379,5 +158,8 @@ export {
379
158
  Analytix402,
380
159
  analytix402,
381
160
  createAnalytix402Client,
382
- createClient
161
+ createAnalytix402Fetch,
162
+ createClient,
163
+ withAnalytix2 as withAnalytixAnthropic,
164
+ withAnalytix as withAnalytixOpenAI
383
165
  };