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.
@@ -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
- // Token budget enforcement
57
+ // Graduated token budget enforcement: alert 50%, warn 80%, exceeded 100%
58
58
  let cumulativeTokens = 0;
59
59
  let cumulativeCost = 0;
60
- let budgetWarned = false;
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
- if (!budgetWarned) {
106
- if (TOKEN_BUDGET > 0 && cumulativeTokens > TOKEN_BUDGET) {
107
- console.warn(`[trickle] ⚠ Token budget exceeded: ${cumulativeTokens} tokens used (budget: ${TOKEN_BUDGET}). Set TRICKLE_TOKEN_BUDGET=0 to disable.`);
108
- budgetWarned = true;
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 (COST_BUDGET > 0 && cumulativeCost > COST_BUDGET) {
111
- console.warn(`[trickle] ⚠ Cost budget exceeded: $${cumulativeCost.toFixed(4)} spent (budget: $${COST_BUDGET.toFixed(4)}). Set TRICKLE_COST_BUDGET=0 to disable.`);
112
- budgetWarned = true;
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.124",
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",
@@ -18,10 +18,10 @@ let eventCount = 0;
18
18
  const MAX_LLM_EVENTS = 500;
19
19
  const TRUNCATE_LEN = 500;
20
20
 
21
- // Token budget enforcement
21
+ // Graduated token budget enforcement: alert 50%, warn 80%, exceeded 100%
22
22
  let cumulativeTokens = 0;
23
23
  let cumulativeCost = 0;
24
- let budgetWarned = false;
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
- if (!budgetWarned) {
92
- if (TOKEN_BUDGET > 0 && cumulativeTokens > TOKEN_BUDGET) {
93
- console.warn(`[trickle] ⚠ Token budget exceeded: ${cumulativeTokens} tokens used (budget: ${TOKEN_BUDGET}). Set TRICKLE_TOKEN_BUDGET=0 to disable.`);
94
- budgetWarned = true;
95
- }
96
- if (COST_BUDGET > 0 && cumulativeCost > COST_BUDGET) {
97
- console.warn(`[trickle] Cost budget exceeded: $${cumulativeCost.toFixed(4)} spent (budget: $${COST_BUDGET.toFixed(4)}). Set TRICKLE_COST_BUDGET=0 to disable.`);
98
- budgetWarned = true;
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,