@farzanhossans/agentlens 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/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @farzanhossans/agentlens
2
+
3
+ Universal AI agent observability — **one line** to trace every LLM call.
4
+
5
+ Patches `globalThis.fetch` and Node's `http`/`https` so every call to OpenAI,
6
+ Anthropic, Gemini, Cohere, or Mistral is captured automatically. No SDK
7
+ wrappers. No code changes inside your call sites.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @farzanhossans/agentlens
13
+ # or: npm i @farzanhossans/agentlens
14
+ ```
15
+
16
+ ## Quickstart
17
+
18
+ ### 1. Cloud — zero config (default)
19
+
20
+ ```ts
21
+ import { AgentLens } from '@farzanhossans/agentlens'
22
+
23
+ AgentLens.init({ apiKey: 'proj_xxx' })
24
+ ```
25
+
26
+ ### 2. Self-hosted — point at your own ingest endpoint
27
+
28
+ ```ts
29
+ import { AgentLens } from '@farzanhossans/agentlens'
30
+
31
+ AgentLens.init({
32
+ apiKey: 'proj_xxx', // key from your own AgentLens dashboard
33
+ endpoint: 'https://agentlens.your-company.com/v1/spans',
34
+ })
35
+ ```
36
+
37
+ ### 3. Self-hosted + debug mode (logs each captured span to stdout)
38
+
39
+ ```ts
40
+ AgentLens.init({
41
+ apiKey: 'proj_xxx',
42
+ endpoint: 'https://agentlens.your-company.com/v1/spans',
43
+ debug: true,
44
+ })
45
+ ```
46
+
47
+ ### 4. Disable PII scrubbing (only for trusted internal data)
48
+
49
+ ```ts
50
+ AgentLens.init({
51
+ apiKey: 'proj_xxx',
52
+ pii: false,
53
+ })
54
+ ```
55
+
56
+ ## What gets captured
57
+
58
+ For every matched LLM HTTP call:
59
+
60
+ - `provider` and `model`
61
+ - `inputTokens`, `outputTokens`, `totalTokens`
62
+ - `costUsd` (calculated from a built-in price table per provider)
63
+ - `inputText` and `outputText` (PII-scrubbed by default)
64
+ - `latency` and `status`
65
+ - `isStream` flag — streaming responses are tapped via `ReadableStream.tee()` so
66
+ the stream you read is byte-identical to the original
67
+
68
+ ## Supported providers
69
+
70
+ All five providers support both streaming and non-streaming responses.
71
+
72
+ | Provider | Hosts | Endpoints | Streaming |
73
+ | --------- | -------------------------------------- | -------------------------------------------------------------- | --------- |
74
+ | OpenAI | `api.openai.com` | `/v1/chat/completions`, `/v1/completions`, `/v1/embeddings` | ✅ |
75
+ | Anthropic | `api.anthropic.com` | `/v1/messages` | ✅ |
76
+ | Gemini | `generativelanguage.googleapis.com` | `/v1beta/models`, `/v1/models` | ✅ |
77
+ | Cohere | `api.cohere.com` | `/v1/chat`, `/v1/generate`, `/v2/chat` (v1 + v2 stream shapes) | ✅ |
78
+ | Mistral | `api.mistral.ai` | `/v1/chat/completions` | ✅ |
79
+
80
+ ## Grouping calls with `trace()`
81
+
82
+ Wrap a logical unit of work (an agent step, a multi-call retrieval, etc.) in
83
+ `AgentLens.trace(name, fn)`. Every LLM call inside `fn` — including async ones
84
+ and across nested traces — is auto-tagged with the same `traceId` and gets
85
+ `parentSpanId` set to the trace's span. Built on Node's `AsyncLocalStorage`,
86
+ so it composes with `Promise.all`, `setTimeout`, async generators, etc.
87
+
88
+ ```ts
89
+ import { AgentLens } from '@farzanhossans/agentlens'
90
+
91
+ AgentLens.init({ apiKey: 'proj_xxx' })
92
+
93
+ await AgentLens.trace('classify-then-rephrase', async () => {
94
+ const classification = await openai.chat.completions.create({ ... })
95
+ // ↑ span emitted with parentSpanId = the trace's spanId
96
+ const rephrased = await anthropic.messages.create({ ... })
97
+ // ↑ same trace, same parentSpanId
98
+ })
99
+ ```
100
+
101
+ Nested traces nest:
102
+
103
+ ```ts
104
+ await AgentLens.trace('outer', async () => {
105
+ await AgentLens.trace('inner', async () => {
106
+ await openai.chat.completions.create({ ... })
107
+ // child of `inner`, which is child of `outer`, all sharing one traceId
108
+ })
109
+ })
110
+ ```
111
+
112
+ ## PII scrubbing
113
+
114
+ Enabled by default. Strips emails, phone numbers, SSNs, credit cards, IPv4
115
+ addresses, and obvious API-key shapes from `inputText` / `outputText` before
116
+ spans leave your process. Pass `pii: false` to disable.
117
+
118
+ ## Self-hosted flow
119
+
120
+ 1. Deploy AgentLens via Docker Compose on your own server
121
+ 2. Open your own dashboard → create a project → copy the API key
122
+ 3. `AgentLens.init({ apiKey, endpoint })` pointing at your own ingest URL
123
+ 4. Spans flow: your app → your worker → your DB. Nothing touches AgentLens cloud.
124
+
125
+ ## API
126
+
127
+ ```ts
128
+ AgentLens.init(config: {
129
+ apiKey: string
130
+ endpoint?: string // default: https://ingest.agentlens.dev/v1/spans
131
+ debug?: boolean // default: false
132
+ pii?: boolean // default: true
133
+ flushIntervalMs?: number // default: 500
134
+ maxBatchSize?: number // default: 50
135
+ }): void
136
+
137
+ AgentLens.flush(): Promise<void> // force-flush pending spans
138
+ AgentLens.shutdown(): void // stop the flush timer
139
+
140
+ AgentLens.trace<T>(name: string, fn: () => Promise<T>): Promise<T>
141
+ // re-exports from @farzanhossans/agentlens-core:
142
+ getCurrentTrace(), getCurrentTraceId(), getCurrentSpanId(), runWithTrace(ctx, fn)
143
+ ```
144
+
145
+ ## Requirements
146
+
147
+ Node ≥ 18 (the SDK uses `AsyncLocalStorage`, `globalThis.fetch`, and `crypto.randomUUID`).
148
+
149
+ ## License
150
+
151
+ MIT
@@ -0,0 +1,59 @@
1
+ export { getCurrentSpanId, getCurrentTrace, getCurrentTraceId, runWithTrace } from '@farzanhossans/agentlens-core';
2
+
3
+ type ParserName = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mistral';
4
+ interface LLMEndpoint {
5
+ provider: string;
6
+ parser: ParserName;
7
+ paths: string[];
8
+ }
9
+ interface ParsedSpan {
10
+ model: string;
11
+ provider: string;
12
+ inputTokens: number;
13
+ outputTokens: number;
14
+ totalTokens: number;
15
+ costUsd: number;
16
+ inputText: string;
17
+ outputText: string;
18
+ isStream: boolean;
19
+ error?: string;
20
+ }
21
+ interface AgentLensConfig {
22
+ apiKey: string;
23
+ endpoint?: string;
24
+ debug?: boolean;
25
+ pii?: boolean;
26
+ flushIntervalMs?: number;
27
+ maxBatchSize?: number;
28
+ }
29
+ interface OutboundSpan extends ParsedSpan {
30
+ spanId: string;
31
+ traceId: string;
32
+ parentSpanId?: string;
33
+ timestamp: string;
34
+ latency: number;
35
+ status: number;
36
+ }
37
+
38
+ declare const LLM_REGISTRY: Record<string, LLMEndpoint>;
39
+ declare function matchLLM(url: string): LLMEndpoint | null;
40
+
41
+ declare function scrubPII(text: string): string;
42
+ declare function scrubObject(obj: unknown): unknown;
43
+
44
+ declare const AgentLens: {
45
+ init(config: AgentLensConfig): void;
46
+ flush(): Promise<void>;
47
+ shutdown(): void;
48
+ /**
49
+ * Wraps `fn` in a named trace context. Any LLM calls made inside `fn`
50
+ * (including async ones) are auto-tagged with this trace's `traceId` and
51
+ * get `parentSpanId` set to this trace's `spanId`. Nested `trace()` calls
52
+ * become child spans automatically.
53
+ *
54
+ * Use this to group multiple LLM calls that belong to the same agent run.
55
+ */
56
+ trace<T>(_name: string, fn: () => Promise<T>): Promise<T>;
57
+ };
58
+
59
+ export { AgentLens, type AgentLensConfig, LLM_REGISTRY, type OutboundSpan, type ParsedSpan, matchLLM, scrubObject, scrubPII };
@@ -0,0 +1,59 @@
1
+ export { getCurrentSpanId, getCurrentTrace, getCurrentTraceId, runWithTrace } from '@farzanhossans/agentlens-core';
2
+
3
+ type ParserName = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mistral';
4
+ interface LLMEndpoint {
5
+ provider: string;
6
+ parser: ParserName;
7
+ paths: string[];
8
+ }
9
+ interface ParsedSpan {
10
+ model: string;
11
+ provider: string;
12
+ inputTokens: number;
13
+ outputTokens: number;
14
+ totalTokens: number;
15
+ costUsd: number;
16
+ inputText: string;
17
+ outputText: string;
18
+ isStream: boolean;
19
+ error?: string;
20
+ }
21
+ interface AgentLensConfig {
22
+ apiKey: string;
23
+ endpoint?: string;
24
+ debug?: boolean;
25
+ pii?: boolean;
26
+ flushIntervalMs?: number;
27
+ maxBatchSize?: number;
28
+ }
29
+ interface OutboundSpan extends ParsedSpan {
30
+ spanId: string;
31
+ traceId: string;
32
+ parentSpanId?: string;
33
+ timestamp: string;
34
+ latency: number;
35
+ status: number;
36
+ }
37
+
38
+ declare const LLM_REGISTRY: Record<string, LLMEndpoint>;
39
+ declare function matchLLM(url: string): LLMEndpoint | null;
40
+
41
+ declare function scrubPII(text: string): string;
42
+ declare function scrubObject(obj: unknown): unknown;
43
+
44
+ declare const AgentLens: {
45
+ init(config: AgentLensConfig): void;
46
+ flush(): Promise<void>;
47
+ shutdown(): void;
48
+ /**
49
+ * Wraps `fn` in a named trace context. Any LLM calls made inside `fn`
50
+ * (including async ones) are auto-tagged with this trace's `traceId` and
51
+ * get `parentSpanId` set to this trace's `spanId`. Nested `trace()` calls
52
+ * become child spans automatically.
53
+ *
54
+ * Use this to group multiple LLM calls that belong to the same agent run.
55
+ */
56
+ trace<T>(_name: string, fn: () => Promise<T>): Promise<T>;
57
+ };
58
+
59
+ export { AgentLens, type AgentLensConfig, LLM_REGISTRY, type OutboundSpan, type ParsedSpan, matchLLM, scrubObject, scrubPII };