@lupaflow/observability 0.1.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/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@lupaflow/observability",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "require": "./dist/index.js",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch"
22
+ },
23
+ "dependencies": {
24
+ "@lupaflow/core": "workspace:*",
25
+ "@lupaflow/types": "workspace:*",
26
+ "pino": "^9.5.0",
27
+ "pino-pretty": "^13.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "tsup": "^8.3.0",
31
+ "typescript": "^5.6.0"
32
+ }
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { UsageTracker } from "./tracker"
2
+ export { logger } from "./logger"
3
+ export { Tracer, globalTracer } from "./span"
package/src/logger.ts ADDED
@@ -0,0 +1,19 @@
1
+ import pino from "pino"
2
+ import { globalEventBus } from "@lupaflow/core"
3
+
4
+ const logger = pino({
5
+ transport: {
6
+ target: "pino-pretty",
7
+ options: {
8
+ colorize: true,
9
+ translateTime: "HH:MM:ss",
10
+ ignore: "pid,hostname",
11
+ },
12
+ },
13
+ })
14
+
15
+ globalEventBus.onAny((event) => {
16
+ logger.debug({ event: event.name, id: event.id }, `Event: ${event.name}`)
17
+ })
18
+
19
+ export { logger }
package/src/span.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { generateId } from "@lupaflow/core"
2
+
3
+ interface Span {
4
+ id: string
5
+ name: string
6
+ parentId?: string
7
+ startTime: number
8
+ endTime?: number
9
+ status: "open" | "closed" | "error"
10
+ metadata?: Record<string, unknown>
11
+ }
12
+
13
+ export class Tracer {
14
+ private spans: Map<string, Span> = new Map()
15
+ private stack: string[] = []
16
+
17
+ start(name: string, metadata?: Record<string, unknown>): string {
18
+ const id = generateId()
19
+ const parentId = this.stack[this.stack.length - 1]
20
+ const span: Span = {
21
+ id,
22
+ name,
23
+ parentId,
24
+ startTime: Date.now(),
25
+ status: "open",
26
+ metadata,
27
+ }
28
+ this.spans.set(id, span)
29
+ this.stack.push(id)
30
+ return id
31
+ }
32
+
33
+ end(id: string, metadata?: Record<string, unknown>): void {
34
+ const span = this.spans.get(id)
35
+ if (!span) return
36
+ span.endTime = Date.now()
37
+ span.status = "closed"
38
+ span.metadata = { ...span.metadata, ...metadata }
39
+ this.stack = this.stack.filter((s) => s !== id)
40
+ }
41
+
42
+ error(id: string, error: string): void {
43
+ const span = this.spans.get(id)
44
+ if (!span) return
45
+ span.endTime = Date.now()
46
+ span.status = "error"
47
+ span.metadata = { ...span.metadata, error }
48
+ this.stack = this.stack.filter((s) => s !== id)
49
+ }
50
+
51
+ getTrace(id: string): Span | undefined {
52
+ return this.spans.get(id)
53
+ }
54
+
55
+ getActiveSpans(): Span[] {
56
+ return Array.from(this.spans.values()).filter((s) => s.status === "open")
57
+ }
58
+
59
+ getAllSpans(): Span[] {
60
+ return Array.from(this.spans.values())
61
+ }
62
+
63
+ clear(): void {
64
+ this.spans.clear()
65
+ this.stack = []
66
+ }
67
+ }
68
+
69
+ export const globalTracer = new Tracer()
package/src/tracker.ts ADDED
@@ -0,0 +1,88 @@
1
+ import { globalEventBus } from "@lupaflow/core"
2
+ import type { LupaEvent, ProviderCompleteEvent, LLMTokenEvent } from "@lupaflow/types"
3
+
4
+ interface UsageRecord {
5
+ provider: string
6
+ model: string
7
+ tokens: number
8
+ cost: number
9
+ latencyMs: number
10
+ timestamp: number
11
+ }
12
+
13
+ const TOKEN_COST_PER_1K: Record<string, { input: number; output: number }> = {
14
+ "openai/gpt-4o-mini": { input: 0.00015, output: 0.0006 },
15
+ "openai/gpt-4o": { input: 0.0025, output: 0.01 },
16
+ "llama-3.1-70b-versatile": { input: 0.00059, output: 0.00079 },
17
+ "llama-3.1-8b-instant": { input: 0.00005, output: 0.00008 },
18
+ "gemini-1.5-flash": { input: 0.000075, output: 0.0003 },
19
+ "gemini-1.5-pro": { input: 0.00125, output: 0.005 },
20
+ }
21
+
22
+ function estimateCost(provider: string, model: string, tokens: number, isOutput = false): number {
23
+ const key = model
24
+ const rates = TOKEN_COST_PER_1K[key]
25
+ if (!rates) return 0
26
+ const rate = isOutput ? rates.output : rates.input
27
+ return (tokens / 1000) * rate
28
+ }
29
+
30
+ export class UsageTracker {
31
+ private records: UsageRecord[] = []
32
+ private totalTokens = 0
33
+ private totalCost = 0
34
+
35
+ constructor() {
36
+ globalEventBus.on("provider:complete", (event: LupaEvent) => {
37
+ const e = event as ProviderCompleteEvent
38
+ const isOutput = e.name === "provider:complete"
39
+ const cost = estimateCost(e.provider, e.model, e.tokens, isOutput)
40
+
41
+ this.records.push({
42
+ provider: e.provider,
43
+ model: e.model,
44
+ tokens: e.tokens,
45
+ cost,
46
+ latencyMs: e.latencyMs,
47
+ timestamp: e.timestamp,
48
+ })
49
+
50
+ this.totalTokens += e.tokens
51
+ this.totalCost += cost
52
+
53
+ globalEventBus.emit("llm:token", {
54
+ tokens: e.tokens,
55
+ cost,
56
+ } as LLMTokenEvent)
57
+ })
58
+ }
59
+
60
+ getTotalTokens(): number {
61
+ return this.totalTokens
62
+ }
63
+
64
+ getTotalCost(): number {
65
+ return this.totalCost
66
+ }
67
+
68
+ getRecords(): UsageRecord[] {
69
+ return [...this.records]
70
+ }
71
+
72
+ getSummary(): { totalTokens: number; totalCost: number; totalCalls: number; avgLatencyMs: number } {
73
+ const calls = this.records.length
74
+ const avgLatency = calls > 0 ? this.records.reduce((s, r) => s + r.latencyMs, 0) / calls : 0
75
+ return {
76
+ totalTokens: this.totalTokens,
77
+ totalCost: this.totalCost,
78
+ totalCalls: calls,
79
+ avgLatencyMs: Math.round(avgLatency),
80
+ }
81
+ }
82
+
83
+ reset(): void {
84
+ this.records = []
85
+ this.totalTokens = 0
86
+ this.totalCost = 0
87
+ }
88
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsup"
2
+ export default defineConfig({
3
+ entry: { index: "src/index.ts" },
4
+ format: ["cjs", "esm"],
5
+ dts: true,
6
+ sourcemap: true,
7
+ clean: true,
8
+ external: [/node_modules/],
9
+ })