toad-eye 2.12.0 → 2.13.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 CHANGED
@@ -72,7 +72,9 @@ Open [localhost:3100](http://localhost:3100) (Grafana, admin/admin). 11 dashboar
72
72
 
73
73
  > **Requires:** [Docker Desktop](https://docs.docker.com/get-started/get-docker/) (or Docker Engine + Compose plugin)
74
74
 
75
- <!-- TODO: add Grafana dashboard screenshot here -->
75
+ ![Grafana MCP Server Dashboard](https://raw.githubusercontent.com/vola-trebla/toad-eye/main/demo/grafana-mcp-dashboard.png)
76
+
77
+ ![Jaeger E2E Trace](https://raw.githubusercontent.com/vola-trebla/toad-eye/main/demo/jaeger-e2e-trace.png)
76
78
 
77
79
  ## Why toad-eye
78
80
 
@@ -181,4 +183,4 @@ Full metrics and span attribute reference: [COMPATIBILITY.md](https://github.com
181
183
 
182
184
  ## Tech Stack
183
185
 
184
- TypeScript · OpenTelemetry SDK 2.x · Hono · Docker Compose (Prometheus, Jaeger, Grafana, OTel Collector) · Vitest (306+ tests)
186
+ TypeScript · OpenTelemetry SDK 3.x · Hono · Docker Compose (Prometheus, Jaeger, Grafana, OTel Collector) · Vitest (306+ tests)
@@ -9,7 +9,7 @@
9
9
  * 🐸 chat gpt-4o [1.2s] $0.003 ✅
10
10
  * 🐸 tools/call broken [12ms] ❌ TypeError
11
11
  */
12
- import type { SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
12
+ import type { SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace";
13
13
  import { type ExportResult } from "@opentelemetry/core";
14
14
  export declare class ToadEyeConsoleExporter implements SpanExporter {
15
15
  export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
@@ -4,7 +4,7 @@
4
4
  * Extracts LLM/MCP-relevant attributes from ReadableSpan into a clean
5
5
  * SpanEndData object — user never touches OTel SDK internals.
6
6
  */
7
- import type { SpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace-base";
7
+ import type { SpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace";
8
8
  /** Structured span data passed to the onSpanEnd callback. */
9
9
  export interface SpanEndData {
10
10
  readonly traceId: string;
@@ -3,7 +3,7 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
3
3
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
4
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
5
5
  import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
6
- import { TraceIdRatioBasedSampler, ParentBasedSampler, BatchSpanProcessor, } from "@opentelemetry/sdk-trace-node";
6
+ import { TraceIdRatioBasedSampler, ParentBasedSampler, BatchSpanProcessor, } from "@opentelemetry/sdk-trace";
7
7
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
8
8
  import { initMetrics, resetMetrics } from "./metrics.js";
9
9
  import { resetCustomPricing } from "./pricing.js";
@@ -96,7 +96,7 @@ export function initObservability(config) {
96
96
  const needsCustomProcessors = config.instrument?.includes("ai") || config.onSpanEnd !== undefined;
97
97
  const spanProcessors = needsCustomProcessors
98
98
  ? [
99
- new BatchSpanProcessor(traceExporter),
99
+ new BatchSpanProcessor({ exporter: traceExporter }),
100
100
  ...(config.instrument?.includes("ai")
101
101
  ? [new ToadEyeAISpanProcessor()]
102
102
  : []),
@@ -9,10 +9,8 @@
9
9
  * and strict TypeScript. Wrapping the public API is the stable path.
10
10
  */
11
11
  import type { ToadMcpOptions } from "./types.js";
12
- export interface ToadEyeMiddlewareDispose {
13
- /** Call when the MCP server is shutting down to decrement the active session counter. */
14
- (): void;
15
- }
12
+ /** Call when the MCP server is shutting down to decrement the active session counter. */
13
+ export type ToadEyeMiddlewareDispose = () => void;
16
14
  export declare function toadEyeMiddleware(server: any, options?: ToadMcpOptions): ToadEyeMiddlewareDispose;
17
15
  /**
18
16
  * Trace a sampling/createMessage call (server → client LLM request).
package/dist/vercel.d.ts CHANGED
@@ -20,7 +20,7 @@
20
20
  * });
21
21
  * ```
22
22
  */
23
- import type { SpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace-base";
23
+ import type { SpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace";
24
24
  /**
25
25
  * OTel SpanProcessor that enriches Vercel AI SDK spans with toad-eye
26
26
  * cost attributes and records metrics.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toad-eye",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "Observability for MCP servers and LLM applications — traces, metrics, dashboards. One-line instrumentation for OpenAI, Anthropic, Gemini, and MCP. Self-hosted with Grafana + Prometheus + Jaeger.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -87,23 +87,23 @@
87
87
  ],
88
88
  "license": "ISC",
89
89
  "dependencies": {
90
- "@opentelemetry/api": "^1.9.0",
91
- "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
92
- "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
93
- "@opentelemetry/resources": "^2.6.0",
94
- "@opentelemetry/sdk-metrics": "^2.6.0",
95
- "@opentelemetry/sdk-node": "^0.213.0",
96
- "@opentelemetry/sdk-trace-base": "^2.6.0",
97
- "@opentelemetry/sdk-trace-node": "^2.6.0",
98
- "@opentelemetry/semantic-conventions": "^1.40.0",
99
- "yaml": "^2.7.0"
90
+ "@opentelemetry/api": "^1.10.0",
91
+ "@opentelemetry/core": "^3.1.0",
92
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.301.0",
93
+ "@opentelemetry/exporter-trace-otlp-http": "^0.301.0",
94
+ "@opentelemetry/resources": "^3.1.0",
95
+ "@opentelemetry/sdk-metrics": "^3.1.0",
96
+ "@opentelemetry/sdk-node": "^0.301.0",
97
+ "@opentelemetry/sdk-trace": "^3.1.0",
98
+ "@opentelemetry/semantic-conventions": "^1.44.0",
99
+ "yaml": "^2.9.1"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "@anthropic-ai/sdk": ">=0.30.0",
103
103
  "@google/generative-ai": ">=0.20.0",
104
+ "@modelcontextprotocol/sdk": ">=1.0.0",
104
105
  "nodemailer": ">=8.0.0",
105
- "openai": ">=4.0.0",
106
- "@modelcontextprotocol/sdk": ">=1.0.0"
106
+ "openai": ">=4.0.0"
107
107
  },
108
108
  "peerDependenciesMeta": {
109
109
  "openai": {
@@ -123,7 +123,7 @@
123
123
  }
124
124
  },
125
125
  "devDependencies": {
126
- "@types/nodemailer": "^7.0.11",
127
- "vitest": "^4.1.0"
126
+ "@types/nodemailer": "^8.0.2",
127
+ "vitest": "^5.1.0"
128
128
  }
129
129
  }
@@ -1,15 +0,0 @@
1
- /** Thrown when context guard blockAt threshold is exceeded. */
2
- export declare class ToadContextExceededError extends Error {
3
- readonly utilization: number;
4
- readonly threshold: number;
5
- readonly model: string;
6
- readonly maxContextTokens: number;
7
- readonly inputTokens: number;
8
- constructor(info: {
9
- utilization: number;
10
- threshold: number;
11
- model: string;
12
- maxContextTokens: number;
13
- inputTokens: number;
14
- });
15
- }
@@ -1,17 +0,0 @@
1
- /** Thrown when context guard blockAt threshold is exceeded. */
2
- export class ToadContextExceededError extends Error {
3
- utilization;
4
- threshold;
5
- model;
6
- maxContextTokens;
7
- inputTokens;
8
- constructor(info) {
9
- super(`toad-eye: context window ${(info.utilization * 100).toFixed(0)}% full (${info.inputTokens}/${info.maxContextTokens} tokens) for model ${info.model} — blocked at ${(info.threshold * 100).toFixed(0)}%`);
10
- this.name = "ToadContextExceededError";
11
- this.utilization = info.utilization;
12
- this.threshold = info.threshold;
13
- this.model = info.model;
14
- this.maxContextTokens = info.maxContextTokens;
15
- this.inputTokens = info.inputTokens;
16
- }
17
- }
@@ -1,8 +0,0 @@
1
- export declare function resetContextGuardState(): void;
2
- /**
3
- * Check context utilization before an LLM call.
4
- * - If utilization > blockAt → throw ToadContextExceededError
5
- * - If utilization > warnAt → console.warn (once per model per session)
6
- * - If model unknown or no guard configured → skip silently
7
- */
8
- export declare function checkContextGuard(model: string, inputTokens: number): void;
@@ -1,41 +0,0 @@
1
- import { getModelPricing } from "../core/pricing.js";
2
- import { getConfig } from "../core/tracer.js";
3
- import { ToadContextExceededError } from "./error.js";
4
- let warnedModels = new Set();
5
- export function resetContextGuardState() {
6
- warnedModels = new Set();
7
- }
8
- /**
9
- * Check context utilization before an LLM call.
10
- * - If utilization > blockAt → throw ToadContextExceededError
11
- * - If utilization > warnAt → console.warn (once per model per session)
12
- * - If model unknown or no guard configured → skip silently
13
- */
14
- export function checkContextGuard(model, inputTokens) {
15
- if (inputTokens <= 0)
16
- return;
17
- const config = getConfig();
18
- const guard = config?.contextGuard;
19
- if (!guard)
20
- return;
21
- const pricing = getModelPricing(model);
22
- if (!pricing?.maxContextTokens)
23
- return;
24
- const utilization = inputTokens / pricing.maxContextTokens;
25
- if (guard.blockAt !== undefined && utilization >= guard.blockAt) {
26
- throw new ToadContextExceededError({
27
- utilization,
28
- threshold: guard.blockAt,
29
- model,
30
- maxContextTokens: pricing.maxContextTokens,
31
- inputTokens,
32
- });
33
- }
34
- if (guard.warnAt !== undefined && utilization >= guard.warnAt) {
35
- const key = `${model}:${Math.floor(utilization * 10)}`;
36
- if (!warnedModels.has(key)) {
37
- warnedModels.add(key);
38
- console.warn(`toad-eye: context window ${(utilization * 100).toFixed(0)}% full for ${model} (${inputTokens}/${pricing.maxContextTokens} tokens)`);
39
- }
40
- }
41
- }