trickle-observe 0.2.124 → 0.2.126
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/llm-observer.js +23 -9
- package/package.json +1 -1
- package/src/llm-observer.ts +23 -10
package/dist/llm-observer.js
CHANGED
|
@@ -54,10 +54,10 @@ let llmFile = null;
|
|
|
54
54
|
let eventCount = 0;
|
|
55
55
|
const MAX_LLM_EVENTS = 500;
|
|
56
56
|
const TRUNCATE_LEN = 500;
|
|
57
|
-
//
|
|
57
|
+
// Graduated token budget enforcement: alert 50%, warn 80%, exceeded 100%
|
|
58
58
|
let cumulativeTokens = 0;
|
|
59
59
|
let cumulativeCost = 0;
|
|
60
|
-
let
|
|
60
|
+
let budgetLevel = 0; // 0=ok, 1=alert(50%), 2=warn(80%), 3=exceeded(100%)
|
|
61
61
|
const TOKEN_BUDGET = parseInt(process.env.TRICKLE_TOKEN_BUDGET || '0', 10);
|
|
62
62
|
const COST_BUDGET = parseFloat(process.env.TRICKLE_COST_BUDGET || '0');
|
|
63
63
|
// Approximate pricing per 1M tokens (USD) — used for cost estimation
|
|
@@ -102,16 +102,26 @@ function writeLlmEvent(event) {
|
|
|
102
102
|
// Track cumulative usage for budget enforcement
|
|
103
103
|
cumulativeTokens += event.totalTokens || 0;
|
|
104
104
|
cumulativeCost += event.estimatedCostUsd || 0;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
// Graduated budget: alert at 50%, warn at 80%, exceeded at 100%
|
|
106
|
+
function checkBudget(current, budget, unit) {
|
|
107
|
+
if (budget <= 0)
|
|
108
|
+
return;
|
|
109
|
+
const pct = current / budget;
|
|
110
|
+
if (pct >= 1.0 && budgetLevel < 3) {
|
|
111
|
+
budgetLevel = 3;
|
|
112
|
+
console.warn(`[trickle] ❌ ${unit} budget EXCEEDED: ${current.toFixed(4)} / ${budget} (100%+). Consider stopping.`);
|
|
113
|
+
}
|
|
114
|
+
else if (pct >= 0.8 && budgetLevel < 2) {
|
|
115
|
+
budgetLevel = 2;
|
|
116
|
+
console.warn(`[trickle] ⚠ ${unit} budget at 80%: ${current.toFixed(4)} / ${budget}. Approaching limit.`);
|
|
109
117
|
}
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
else if (pct >= 0.5 && budgetLevel < 1) {
|
|
119
|
+
budgetLevel = 1;
|
|
120
|
+
console.warn(`[trickle] ℹ ${unit} budget at 50%: ${current.toFixed(4)} / ${budget}.`);
|
|
113
121
|
}
|
|
114
122
|
}
|
|
123
|
+
checkBudget(cumulativeTokens, TOKEN_BUDGET, 'Token');
|
|
124
|
+
checkBudget(cumulativeCost, COST_BUDGET, 'Cost ($)');
|
|
115
125
|
try {
|
|
116
126
|
fs.appendFileSync(getLlmFile(), JSON.stringify(event) + '\n');
|
|
117
127
|
}
|
|
@@ -486,12 +496,16 @@ function handleAnthropicStream(stream, params, startTime, debug) {
|
|
|
486
496
|
function captureAnthropicResponse(params, response, startTime, debug) {
|
|
487
497
|
const usage = response.usage || {};
|
|
488
498
|
const outputText = response.content?.map((c) => c.text || '').join('') || '';
|
|
499
|
+
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
500
|
+
const cacheWrite = usage.cache_creation_input_tokens || 0;
|
|
489
501
|
const event = {
|
|
490
502
|
kind: 'llm_call', provider: 'anthropic', model: response.model || params.model || 'unknown',
|
|
491
503
|
durationMs: round(performance.now() - startTime),
|
|
492
504
|
inputTokens: usage.input_tokens || 0, outputTokens: usage.output_tokens || 0,
|
|
493
505
|
totalTokens: (usage.input_tokens || 0) + (usage.output_tokens || 0),
|
|
494
506
|
estimatedCostUsd: estimateCost(response.model || params.model || '', usage.input_tokens || 0, usage.output_tokens || 0),
|
|
507
|
+
...(cacheRead > 0 ? { cacheReadTokens: cacheRead } : {}),
|
|
508
|
+
...(cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}),
|
|
495
509
|
stream: false, finishReason: response.stop_reason || 'unknown',
|
|
496
510
|
temperature: params.temperature, maxTokens: params.max_tokens,
|
|
497
511
|
systemPrompt: typeof params.system === 'string' ? truncate(params.system, 200) : undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-observe",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.126",
|
|
4
4
|
"description": "Zero-code runtime observability for JavaScript/TypeScript. Auto-instruments Express, OpenAI, Anthropic, Gemini, MCP. Captures functions, variables, LLM calls, agent workflows.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/src/llm-observer.ts
CHANGED
|
@@ -18,10 +18,10 @@ let eventCount = 0;
|
|
|
18
18
|
const MAX_LLM_EVENTS = 500;
|
|
19
19
|
const TRUNCATE_LEN = 500;
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Graduated token budget enforcement: alert 50%, warn 80%, exceeded 100%
|
|
22
22
|
let cumulativeTokens = 0;
|
|
23
23
|
let cumulativeCost = 0;
|
|
24
|
-
let
|
|
24
|
+
let budgetLevel = 0; // 0=ok, 1=alert(50%), 2=warn(80%), 3=exceeded(100%)
|
|
25
25
|
const TOKEN_BUDGET = parseInt(process.env.TRICKLE_TOKEN_BUDGET || '0', 10);
|
|
26
26
|
const COST_BUDGET = parseFloat(process.env.TRICKLE_COST_BUDGET || '0');
|
|
27
27
|
|
|
@@ -67,6 +67,8 @@ interface LlmEvent {
|
|
|
67
67
|
outputTokens: number;
|
|
68
68
|
totalTokens: number;
|
|
69
69
|
estimatedCostUsd: number;
|
|
70
|
+
cacheReadTokens?: number;
|
|
71
|
+
cacheWriteTokens?: number;
|
|
70
72
|
stream: boolean;
|
|
71
73
|
finishReason: string;
|
|
72
74
|
temperature?: number;
|
|
@@ -88,16 +90,23 @@ function writeLlmEvent(event: LlmEvent): void {
|
|
|
88
90
|
cumulativeTokens += event.totalTokens || 0;
|
|
89
91
|
cumulativeCost += event.estimatedCostUsd || 0;
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.warn(`[trickle]
|
|
98
|
-
|
|
93
|
+
// Graduated budget: alert at 50%, warn at 80%, exceeded at 100%
|
|
94
|
+
function checkBudget(current: number, budget: number, unit: string): void {
|
|
95
|
+
if (budget <= 0) return;
|
|
96
|
+
const pct = current / budget;
|
|
97
|
+
if (pct >= 1.0 && budgetLevel < 3) {
|
|
98
|
+
budgetLevel = 3;
|
|
99
|
+
console.warn(`[trickle] ❌ ${unit} budget EXCEEDED: ${current.toFixed(4)} / ${budget} (100%+). Consider stopping.`);
|
|
100
|
+
} else if (pct >= 0.8 && budgetLevel < 2) {
|
|
101
|
+
budgetLevel = 2;
|
|
102
|
+
console.warn(`[trickle] ⚠ ${unit} budget at 80%: ${current.toFixed(4)} / ${budget}. Approaching limit.`);
|
|
103
|
+
} else if (pct >= 0.5 && budgetLevel < 1) {
|
|
104
|
+
budgetLevel = 1;
|
|
105
|
+
console.warn(`[trickle] ℹ ${unit} budget at 50%: ${current.toFixed(4)} / ${budget}.`);
|
|
99
106
|
}
|
|
100
107
|
}
|
|
108
|
+
checkBudget(cumulativeTokens, TOKEN_BUDGET, 'Token');
|
|
109
|
+
checkBudget(cumulativeCost, COST_BUDGET, 'Cost ($)');
|
|
101
110
|
try {
|
|
102
111
|
fs.appendFileSync(getLlmFile(), JSON.stringify(event) + '\n');
|
|
103
112
|
} catch {}
|
|
@@ -479,12 +488,16 @@ function handleAnthropicStream(stream: any, params: any, startTime: number, debu
|
|
|
479
488
|
function captureAnthropicResponse(params: any, response: any, startTime: number, debug: boolean): void {
|
|
480
489
|
const usage = response.usage || {};
|
|
481
490
|
const outputText = response.content?.map((c: any) => c.text || '').join('') || '';
|
|
491
|
+
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
492
|
+
const cacheWrite = usage.cache_creation_input_tokens || 0;
|
|
482
493
|
const event: LlmEvent = {
|
|
483
494
|
kind: 'llm_call', provider: 'anthropic', model: response.model || params.model || 'unknown',
|
|
484
495
|
durationMs: round(performance.now() - startTime),
|
|
485
496
|
inputTokens: usage.input_tokens || 0, outputTokens: usage.output_tokens || 0,
|
|
486
497
|
totalTokens: (usage.input_tokens || 0) + (usage.output_tokens || 0),
|
|
487
498
|
estimatedCostUsd: estimateCost(response.model || params.model || '', usage.input_tokens || 0, usage.output_tokens || 0),
|
|
499
|
+
...(cacheRead > 0 ? { cacheReadTokens: cacheRead } : {}),
|
|
500
|
+
...(cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}),
|
|
488
501
|
stream: false, finishReason: response.stop_reason || 'unknown',
|
|
489
502
|
temperature: params.temperature, maxTokens: params.max_tokens,
|
|
490
503
|
systemPrompt: typeof params.system === 'string' ? truncate(params.system, 200) : undefined,
|