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 +4 -2
- package/dist/core/console-exporter.d.ts +1 -1
- package/dist/core/span-end-processor.d.ts +1 -1
- package/dist/core/tracer.js +2 -2
- package/dist/mcp/middleware.d.ts +2 -4
- package/dist/vercel.d.ts +1 -1
- package/package.json +15 -15
- package/dist/context/error.d.ts +0 -15
- package/dist/context/error.js +0 -17
- package/dist/context/guard.d.ts +0 -8
- package/dist/context/guard.js +0 -41
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
|
-
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+

|
|
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
|
|
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
|
|
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
|
|
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;
|
package/dist/core/tracer.js
CHANGED
|
@@ -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
|
|
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
|
: []),
|
package/dist/mcp/middleware.d.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
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
|
|
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.
|
|
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.
|
|
91
|
-
"@opentelemetry/
|
|
92
|
-
"@opentelemetry/exporter-
|
|
93
|
-
"@opentelemetry/
|
|
94
|
-
"@opentelemetry/
|
|
95
|
-
"@opentelemetry/sdk-
|
|
96
|
-
"@opentelemetry/sdk-
|
|
97
|
-
"@opentelemetry/sdk-trace
|
|
98
|
-
"@opentelemetry/semantic-conventions": "^1.
|
|
99
|
-
"yaml": "^2.
|
|
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": "^
|
|
127
|
-
"vitest": "^
|
|
126
|
+
"@types/nodemailer": "^8.0.2",
|
|
127
|
+
"vitest": "^5.1.0"
|
|
128
128
|
}
|
|
129
129
|
}
|
package/dist/context/error.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/context/error.js
DELETED
|
@@ -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
|
-
}
|
package/dist/context/guard.d.ts
DELETED
|
@@ -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;
|
package/dist/context/guard.js
DELETED
|
@@ -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
|
-
}
|