@farzanhossans/agentlens 0.2.0 → 0.2.1

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.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { getCurrentSpanId, getCurrentTrace, getCurrentTraceId, runWithTrace } from '@farzanhossans/agentlens-core';
2
2
 
3
3
  type ParserName = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mistral';
4
+ type SpanStatus = 'success' | 'error' | 'timeout';
4
5
  interface LLMEndpoint {
5
6
  provider: string;
6
7
  parser: ParserName;
@@ -20,19 +21,46 @@ interface ParsedSpan {
20
21
  }
21
22
  interface AgentLensConfig {
22
23
  apiKey: string;
24
+ /**
25
+ * Project UUID. Required by the AgentLens ingest contract — every span
26
+ * carries this so the server can route to the right project.
27
+ */
28
+ projectId: string;
23
29
  endpoint?: string;
24
30
  debug?: boolean;
25
31
  pii?: boolean;
26
32
  flushIntervalMs?: number;
27
33
  maxBatchSize?: number;
28
34
  }
29
- interface OutboundSpan extends ParsedSpan {
35
+ /**
36
+ * Wire-format span sent to the AgentLens ingest endpoint. Matches the
37
+ * server-side Zod schema used by both the API controller and the
38
+ * Cloudflare ingest worker.
39
+ */
40
+ interface OutboundSpan {
30
41
  spanId: string;
31
42
  traceId: string;
32
43
  parentSpanId?: string;
33
- timestamp: string;
34
- latency: number;
35
- status: number;
44
+ projectId: string;
45
+ /** Human-friendly span name (e.g. "openai.chat", "anthropic.messages"). */
46
+ name: string;
47
+ model: string;
48
+ provider: string;
49
+ /** Raw LLM prompt text (PII-scrubbed if pii=true). */
50
+ input: string;
51
+ /** Raw LLM completion text (PII-scrubbed if pii=true). */
52
+ output: string;
53
+ inputTokens: number;
54
+ outputTokens: number;
55
+ totalTokens: number;
56
+ costUsd: number;
57
+ latencyMs: number;
58
+ status: SpanStatus;
59
+ errorMessage?: string;
60
+ metadata: Record<string, unknown>;
61
+ startedAt: string;
62
+ endedAt: string;
63
+ isStream: boolean;
36
64
  }
37
65
 
38
66
  declare const LLM_REGISTRY: Record<string, LLMEndpoint>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { getCurrentSpanId, getCurrentTrace, getCurrentTraceId, runWithTrace } from '@farzanhossans/agentlens-core';
2
2
 
3
3
  type ParserName = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mistral';
4
+ type SpanStatus = 'success' | 'error' | 'timeout';
4
5
  interface LLMEndpoint {
5
6
  provider: string;
6
7
  parser: ParserName;
@@ -20,19 +21,46 @@ interface ParsedSpan {
20
21
  }
21
22
  interface AgentLensConfig {
22
23
  apiKey: string;
24
+ /**
25
+ * Project UUID. Required by the AgentLens ingest contract — every span
26
+ * carries this so the server can route to the right project.
27
+ */
28
+ projectId: string;
23
29
  endpoint?: string;
24
30
  debug?: boolean;
25
31
  pii?: boolean;
26
32
  flushIntervalMs?: number;
27
33
  maxBatchSize?: number;
28
34
  }
29
- interface OutboundSpan extends ParsedSpan {
35
+ /**
36
+ * Wire-format span sent to the AgentLens ingest endpoint. Matches the
37
+ * server-side Zod schema used by both the API controller and the
38
+ * Cloudflare ingest worker.
39
+ */
40
+ interface OutboundSpan {
30
41
  spanId: string;
31
42
  traceId: string;
32
43
  parentSpanId?: string;
33
- timestamp: string;
34
- latency: number;
35
- status: number;
44
+ projectId: string;
45
+ /** Human-friendly span name (e.g. "openai.chat", "anthropic.messages"). */
46
+ name: string;
47
+ model: string;
48
+ provider: string;
49
+ /** Raw LLM prompt text (PII-scrubbed if pii=true). */
50
+ input: string;
51
+ /** Raw LLM completion text (PII-scrubbed if pii=true). */
52
+ output: string;
53
+ inputTokens: number;
54
+ outputTokens: number;
55
+ totalTokens: number;
56
+ costUsd: number;
57
+ latencyMs: number;
58
+ status: SpanStatus;
59
+ errorMessage?: string;
60
+ metadata: Record<string, unknown>;
61
+ startedAt: string;
62
+ endedAt: string;
63
+ isStream: boolean;
36
64
  }
37
65
 
38
66
  declare const LLM_REGISTRY: Record<string, LLMEndpoint>;
package/dist/index.js CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  var crypto = require('crypto');
4
4
  var agentlensCore = require('@farzanhossans/agentlens-core');
5
+ var http = require('http');
6
+ var https = require('https');
5
7
 
6
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
- }) : x)(function(x) {
9
- if (typeof require !== "undefined") return require.apply(this, arguments);
10
- throw Error('Dynamic require of "' + x + '" is not supported');
11
- });
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var http__default = /*#__PURE__*/_interopDefault(http);
11
+ var https__default = /*#__PURE__*/_interopDefault(https);
12
+
13
+ // src/index.ts
12
14
 
13
15
  // src/registry.ts
14
16
  var LLM_REGISTRY = {
@@ -345,25 +347,40 @@ function patchFetch(transport2) {
345
347
  patched.__agentlens_patched = true;
346
348
  globalThis.fetch = patched;
347
349
  }
348
-
349
- // src/interceptors/https.ts
350
+ var responseChunkRegistry = /* @__PURE__ */ new WeakMap();
351
+ var incomingMessagePatched = false;
350
352
  function patchHttps(transport2) {
351
- if (typeof process === "undefined" || typeof __require === "undefined") {
352
- return;
353
- }
354
- try {
355
- patchModule("https", transport2);
356
- patchModule("http", transport2);
357
- } catch {
358
- }
353
+ patchIncomingMessage();
354
+ patchModule(http__default.default, "http", transport2);
355
+ patchModule(https__default.default, "https", transport2);
359
356
  }
360
- function patchModule(name, transport2) {
361
- let mod;
362
- try {
363
- mod = __require(name);
364
- } catch {
365
- return;
366
- }
357
+ function patchIncomingMessage() {
358
+ if (incomingMessagePatched) return;
359
+ const IncomingMessage = http__default.default.IncomingMessage;
360
+ if (!IncomingMessage?.prototype) return;
361
+ const proto = IncomingMessage.prototype;
362
+ const originalPush = proto.push;
363
+ proto.push = function patchedPush(chunk, encoding) {
364
+ if (chunk !== null) {
365
+ const chunks = responseChunkRegistry.get(this);
366
+ if (chunks) {
367
+ try {
368
+ if (Buffer.isBuffer(chunk)) {
369
+ chunks.push(chunk);
370
+ } else if (typeof chunk === "string") {
371
+ chunks.push(Buffer.from(chunk, encoding ?? "utf8"));
372
+ } else if (chunk && typeof chunk === "object") {
373
+ chunks.push(Buffer.from(chunk));
374
+ }
375
+ } catch {
376
+ }
377
+ }
378
+ }
379
+ return originalPush.call(this, chunk, encoding);
380
+ };
381
+ incomingMessagePatched = true;
382
+ }
383
+ function patchModule(mod, name, transport2) {
367
384
  if (mod.__agentlens_patched) return;
368
385
  const original = mod.request.bind(mod);
369
386
  mod.request = function patchedRequest(...args) {
@@ -371,7 +388,6 @@ function patchModule(name, transport2) {
371
388
  if (!ctx) return original(...args);
372
389
  const start = Date.now();
373
390
  const requestChunks = [];
374
- const responseChunks = [];
375
391
  const req = original(...args);
376
392
  const originalWrite = req.write.bind(req);
377
393
  req.write = function(chunk, ...rest) {
@@ -383,16 +399,12 @@ function patchModule(name, transport2) {
383
399
  };
384
400
  req.on("response", (...resArgs) => {
385
401
  const res = resArgs[0];
386
- res.on("data", (chunk) => {
387
- try {
388
- responseChunks.push(toBuffer(chunk));
389
- } catch {
390
- }
391
- });
402
+ const chunks = [];
403
+ responseChunkRegistry.set(res, chunks);
392
404
  res.on("end", () => {
393
405
  const latency = Date.now() - start;
394
406
  const requestText = Buffer.concat(requestChunks).toString("utf8");
395
- const responseText = Buffer.concat(responseChunks).toString("utf8");
407
+ const responseText = Buffer.concat(chunks).toString("utf8");
396
408
  const requestBody = safeParse(requestText);
397
409
  const responseBody = safeParse(responseText);
398
410
  if (responseBody) {
@@ -406,6 +418,7 @@ function patchModule(name, transport2) {
406
418
  isStream: requestBody?.stream === true
407
419
  });
408
420
  }
421
+ responseChunkRegistry.delete(res);
409
422
  });
410
423
  });
411
424
  req.on("error", (...errArgs) => {
@@ -839,6 +852,7 @@ var Transport = class {
839
852
  constructor(config) {
840
853
  this.config = {
841
854
  apiKey: config.apiKey,
855
+ projectId: config.projectId,
842
856
  endpoint: config.endpoint,
843
857
  flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
844
858
  maxBatchSize: config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
@@ -849,6 +863,8 @@ var Transport = class {
849
863
  this.registerExitHandler();
850
864
  }
851
865
  push(payload) {
866
+ const startedAt = new Date(Date.now() - payload.latency).toISOString();
867
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
852
868
  let parsed;
853
869
  try {
854
870
  parsed = parseSpan({
@@ -863,28 +879,34 @@ var Transport = class {
863
879
  }
864
880
  return;
865
881
  }
866
- const outbound = this.toOutbound(parsed, payload.latency, payload.status);
882
+ const outbound = this.toOutbound(parsed, payload, startedAt, endedAt);
867
883
  this.enqueue(outbound);
868
884
  }
869
885
  pushError(payload) {
870
886
  const ids = this.resolveIds();
887
+ const startedAt = new Date(Date.now() - payload.latency).toISOString();
888
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
871
889
  const span = {
872
890
  spanId: ids.spanId,
873
891
  traceId: ids.traceId,
874
892
  parentSpanId: ids.parentSpanId,
893
+ projectId: this.config.projectId,
894
+ name: `${payload.provider}.error`,
875
895
  model: "unknown",
876
896
  provider: payload.provider,
897
+ input: this.scrub(this.stringifyRequest(payload.request)),
898
+ output: "",
877
899
  inputTokens: 0,
878
900
  outputTokens: 0,
879
901
  totalTokens: 0,
880
902
  costUsd: 0,
881
- inputText: this.scrub(this.stringifyRequest(payload.request)),
882
- outputText: "",
883
- isStream: false,
884
- error: payload.error,
885
- latency: payload.latency,
886
- status: 0,
887
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
903
+ latencyMs: payload.latency,
904
+ status: "error",
905
+ errorMessage: payload.error,
906
+ metadata: { httpStatus: 0 },
907
+ startedAt,
908
+ endedAt,
909
+ isStream: false
888
910
  };
889
911
  this.enqueue(span);
890
912
  }
@@ -906,18 +928,30 @@ var Transport = class {
906
928
  this.timer = null;
907
929
  }
908
930
  }
909
- toOutbound(parsed, latency, status) {
931
+ toOutbound(parsed, payload, startedAt, endedAt) {
910
932
  const ids = this.resolveIds();
933
+ const status = payload.status >= 200 && payload.status < 400 ? "success" : "error";
911
934
  return {
912
- ...parsed,
913
935
  spanId: ids.spanId,
914
936
  traceId: ids.traceId,
915
937
  parentSpanId: ids.parentSpanId,
916
- inputText: this.scrub(parsed.inputText),
917
- outputText: this.scrub(parsed.outputText),
918
- latency,
938
+ projectId: this.config.projectId,
939
+ name: spanNameFor(payload),
940
+ model: parsed.model,
941
+ provider: parsed.provider,
942
+ input: this.scrub(parsed.inputText),
943
+ output: this.scrub(parsed.outputText),
944
+ inputTokens: parsed.inputTokens,
945
+ outputTokens: parsed.outputTokens,
946
+ totalTokens: parsed.totalTokens,
947
+ costUsd: parsed.costUsd,
948
+ latencyMs: payload.latency,
919
949
  status,
920
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
950
+ errorMessage: parsed.error,
951
+ metadata: { httpStatus: payload.status },
952
+ startedAt,
953
+ endedAt,
954
+ isStream: parsed.isStream
921
955
  };
922
956
  }
923
957
  /**
@@ -956,12 +990,13 @@ var Transport = class {
956
990
  enqueue(span) {
957
991
  if (this.config.debug) {
958
992
  console.log("[agentlens] span", {
993
+ name: span.name,
959
994
  model: span.model,
960
995
  provider: span.provider,
961
996
  inputTokens: span.inputTokens,
962
997
  outputTokens: span.outputTokens,
963
998
  costUsd: span.costUsd,
964
- latency: span.latency,
999
+ latencyMs: span.latencyMs,
965
1000
  isStream: span.isStream
966
1001
  });
967
1002
  }
@@ -1022,6 +1057,9 @@ var Transport = class {
1022
1057
  method: "POST",
1023
1058
  headers: {
1024
1059
  "content-type": "application/json",
1060
+ // Hosted ingest expects X-API-Key. Send Authorization too for any
1061
+ // gateways that may sit in front (some Cloudflare workers want it).
1062
+ "x-api-key": this.config.apiKey,
1025
1063
  authorization: `Bearer ${this.config.apiKey}`
1026
1064
  },
1027
1065
  body
@@ -1029,6 +1067,17 @@ var Transport = class {
1029
1067
  return { ok: res.ok, status: res.status };
1030
1068
  }
1031
1069
  };
1070
+ function spanNameFor(payload) {
1071
+ const req = payload.request;
1072
+ if (req && Array.isArray(req.messages)) return `${payload.provider}.chat`;
1073
+ if (req && (typeof req.prompt === "string" || typeof req.input === "string")) {
1074
+ return `${payload.provider}.completion`;
1075
+ }
1076
+ if (req && (req.input !== void 0 || req.contents !== void 0)) {
1077
+ return `${payload.provider}.embedding`;
1078
+ }
1079
+ return `${payload.provider}.call`;
1080
+ }
1032
1081
  function sleep(ms) {
1033
1082
  return new Promise((resolve) => setTimeout(resolve, ms));
1034
1083
  }
@@ -1041,9 +1090,13 @@ var AgentLens = {
1041
1090
  if (!config?.apiKey) {
1042
1091
  throw new Error("AgentLens.init requires an apiKey");
1043
1092
  }
1093
+ if (!config?.projectId) {
1094
+ throw new Error("AgentLens.init requires a projectId");
1095
+ }
1044
1096
  initialized = true;
1045
1097
  transport = new Transport({
1046
1098
  apiKey: config.apiKey,
1099
+ projectId: config.projectId,
1047
1100
  endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
1048
1101
  debug: config.debug ?? false,
1049
1102
  pii: config.pii ?? true,