browser-use 0.0.1 → 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/LICENSE +21 -0
- package/README.md +761 -0
- package/dist/agent/cloud-events.d.ts +264 -0
- package/dist/agent/cloud-events.js +318 -0
- package/dist/agent/gif.d.ts +15 -0
- package/dist/agent/gif.js +215 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/message-manager/service.d.ts +30 -0
- package/dist/agent/message-manager/service.js +208 -0
- package/dist/agent/message-manager/utils.d.ts +2 -0
- package/dist/agent/message-manager/utils.js +41 -0
- package/dist/agent/message-manager/views.d.ts +26 -0
- package/dist/agent/message-manager/views.js +73 -0
- package/dist/agent/prompts.d.ts +52 -0
- package/dist/agent/prompts.js +259 -0
- package/dist/agent/service.d.ts +290 -0
- package/dist/agent/service.js +2200 -0
- package/dist/agent/views.d.ts +741 -0
- package/dist/agent/views.js +537 -0
- package/dist/browser/browser.d.ts +7 -0
- package/dist/browser/browser.js +5 -0
- package/dist/browser/context.d.ts +8 -0
- package/dist/browser/context.js +4 -0
- package/dist/browser/dvd-screensaver.d.ts +101 -0
- package/dist/browser/dvd-screensaver.js +270 -0
- package/dist/browser/extensions.d.ts +63 -0
- package/dist/browser/extensions.js +359 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +9 -0
- package/dist/browser/playwright-manager.d.ts +47 -0
- package/dist/browser/playwright-manager.js +146 -0
- package/dist/browser/profile.d.ts +196 -0
- package/dist/browser/profile.js +815 -0
- package/dist/browser/session.d.ts +505 -0
- package/dist/browser/session.js +3409 -0
- package/dist/browser/types.d.ts +1184 -0
- package/dist/browser/types.js +1 -0
- package/dist/browser/utils.d.ts +1 -0
- package/dist/browser/utils.js +19 -0
- package/dist/browser/views.d.ts +78 -0
- package/dist/browser/views.js +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +44 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.js +430 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/index.js +3 -0
- package/dist/controller/registry/index.d.ts +2 -0
- package/dist/controller/registry/index.js +2 -0
- package/dist/controller/registry/service.d.ts +45 -0
- package/dist/controller/registry/service.js +184 -0
- package/dist/controller/registry/views.d.ts +55 -0
- package/dist/controller/registry/views.js +174 -0
- package/dist/controller/service.d.ts +49 -0
- package/dist/controller/service.js +1176 -0
- package/dist/controller/views.d.ts +241 -0
- package/dist/controller/views.js +88 -0
- package/dist/dom/clickable-element-processor/service.d.ts +11 -0
- package/dist/dom/clickable-element-processor/service.js +60 -0
- package/dist/dom/dom_tree/index.js +1400 -0
- package/dist/dom/history-tree-processor/service.d.ts +14 -0
- package/dist/dom/history-tree-processor/service.js +75 -0
- package/dist/dom/history-tree-processor/view.d.ts +54 -0
- package/dist/dom/history-tree-processor/view.js +56 -0
- package/dist/dom/playground/extraction.d.ts +19 -0
- package/dist/dom/playground/extraction.js +187 -0
- package/dist/dom/playground/process-dom.d.ts +1 -0
- package/dist/dom/playground/process-dom.js +5 -0
- package/dist/dom/playground/test-accessibility.d.ts +44 -0
- package/dist/dom/playground/test-accessibility.js +111 -0
- package/dist/dom/service.d.ts +19 -0
- package/dist/dom/service.js +227 -0
- package/dist/dom/utils.d.ts +1 -0
- package/dist/dom/utils.js +6 -0
- package/dist/dom/views.d.ts +61 -0
- package/dist/dom/views.js +247 -0
- package/dist/event-bus.d.ts +11 -0
- package/dist/event-bus.js +19 -0
- package/dist/exceptions.d.ts +10 -0
- package/dist/exceptions.js +22 -0
- package/dist/filesystem/file-system.d.ts +68 -0
- package/dist/filesystem/file-system.js +412 -0
- package/dist/filesystem/index.d.ts +1 -0
- package/dist/filesystem/index.js +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/dist/integrations/gmail/actions.d.ts +12 -0
- package/dist/integrations/gmail/actions.js +113 -0
- package/dist/integrations/gmail/index.d.ts +2 -0
- package/dist/integrations/gmail/index.js +2 -0
- package/dist/integrations/gmail/service.d.ts +61 -0
- package/dist/integrations/gmail/service.js +260 -0
- package/dist/llm/anthropic/chat.d.ts +28 -0
- package/dist/llm/anthropic/chat.js +126 -0
- package/dist/llm/anthropic/index.d.ts +2 -0
- package/dist/llm/anthropic/index.js +2 -0
- package/dist/llm/anthropic/serializer.d.ts +68 -0
- package/dist/llm/anthropic/serializer.js +285 -0
- package/dist/llm/aws/chat-anthropic.d.ts +61 -0
- package/dist/llm/aws/chat-anthropic.js +176 -0
- package/dist/llm/aws/chat-bedrock.d.ts +15 -0
- package/dist/llm/aws/chat-bedrock.js +80 -0
- package/dist/llm/aws/index.d.ts +3 -0
- package/dist/llm/aws/index.js +3 -0
- package/dist/llm/aws/serializer.d.ts +5 -0
- package/dist/llm/aws/serializer.js +68 -0
- package/dist/llm/azure/chat.d.ts +15 -0
- package/dist/llm/azure/chat.js +83 -0
- package/dist/llm/azure/index.d.ts +1 -0
- package/dist/llm/azure/index.js +1 -0
- package/dist/llm/base.d.ts +16 -0
- package/dist/llm/base.js +1 -0
- package/dist/llm/deepseek/chat.d.ts +15 -0
- package/dist/llm/deepseek/chat.js +51 -0
- package/dist/llm/deepseek/index.d.ts +2 -0
- package/dist/llm/deepseek/index.js +2 -0
- package/dist/llm/deepseek/serializer.d.ts +6 -0
- package/dist/llm/deepseek/serializer.js +57 -0
- package/dist/llm/exceptions.d.ts +10 -0
- package/dist/llm/exceptions.js +18 -0
- package/dist/llm/google/chat.d.ts +20 -0
- package/dist/llm/google/chat.js +144 -0
- package/dist/llm/google/index.d.ts +2 -0
- package/dist/llm/google/index.js +2 -0
- package/dist/llm/google/serializer.d.ts +6 -0
- package/dist/llm/google/serializer.js +64 -0
- package/dist/llm/groq/chat.d.ts +15 -0
- package/dist/llm/groq/chat.js +52 -0
- package/dist/llm/groq/index.d.ts +3 -0
- package/dist/llm/groq/index.js +3 -0
- package/dist/llm/groq/parser.d.ts +32 -0
- package/dist/llm/groq/parser.js +189 -0
- package/dist/llm/groq/serializer.d.ts +6 -0
- package/dist/llm/groq/serializer.js +56 -0
- package/dist/llm/messages.d.ts +77 -0
- package/dist/llm/messages.js +157 -0
- package/dist/llm/ollama/chat.d.ts +15 -0
- package/dist/llm/ollama/chat.js +77 -0
- package/dist/llm/ollama/index.d.ts +2 -0
- package/dist/llm/ollama/index.js +2 -0
- package/dist/llm/ollama/serializer.d.ts +6 -0
- package/dist/llm/ollama/serializer.js +53 -0
- package/dist/llm/openai/chat.d.ts +38 -0
- package/dist/llm/openai/chat.js +174 -0
- package/dist/llm/openai/index.d.ts +3 -0
- package/dist/llm/openai/index.js +3 -0
- package/dist/llm/openai/like.d.ts +17 -0
- package/dist/llm/openai/like.js +19 -0
- package/dist/llm/openai/serializer.d.ts +6 -0
- package/dist/llm/openai/serializer.js +57 -0
- package/dist/llm/openrouter/chat.d.ts +15 -0
- package/dist/llm/openrouter/chat.js +74 -0
- package/dist/llm/openrouter/index.d.ts +2 -0
- package/dist/llm/openrouter/index.js +2 -0
- package/dist/llm/openrouter/serializer.d.ts +3 -0
- package/dist/llm/openrouter/serializer.js +3 -0
- package/dist/llm/schema.d.ts +6 -0
- package/dist/llm/schema.js +77 -0
- package/dist/llm/views.d.ts +15 -0
- package/dist/llm/views.js +12 -0
- package/dist/logging-config.d.ts +25 -0
- package/dist/logging-config.js +89 -0
- package/dist/mcp/client.d.ts +142 -0
- package/dist/mcp/client.js +638 -0
- package/dist/mcp/controller.d.ts +6 -0
- package/dist/mcp/controller.js +38 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/server.d.ts +134 -0
- package/dist/mcp/server.js +759 -0
- package/dist/observability-decorators.d.ts +158 -0
- package/dist/observability-decorators.js +286 -0
- package/dist/observability.d.ts +23 -0
- package/dist/observability.js +58 -0
- package/dist/screenshots/index.d.ts +1 -0
- package/dist/screenshots/index.js +1 -0
- package/dist/screenshots/service.d.ts +6 -0
- package/dist/screenshots/service.js +28 -0
- package/dist/sync/auth.d.ts +27 -0
- package/dist/sync/auth.js +205 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/service.d.ts +21 -0
- package/dist/sync/service.js +146 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/telemetry/service.d.ts +12 -0
- package/dist/telemetry/service.js +85 -0
- package/dist/telemetry/views.d.ts +112 -0
- package/dist/telemetry/views.js +112 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/service.d.ts +35 -0
- package/dist/tokens/service.js +423 -0
- package/dist/tokens/views.d.ts +58 -0
- package/dist/tokens/views.js +1 -0
- package/dist/utils.d.ts +128 -0
- package/dist/utils.js +529 -0
- package/package.json +94 -5
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export class BaseTelemetryEvent {
|
|
2
|
+
properties() {
|
|
3
|
+
const entries = Object.entries(this).filter(([key]) => key !== 'name');
|
|
4
|
+
return Object.fromEntries(entries);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class AgentTelemetryEvent extends BaseTelemetryEvent {
|
|
8
|
+
name = 'agent_event';
|
|
9
|
+
task;
|
|
10
|
+
model;
|
|
11
|
+
model_provider;
|
|
12
|
+
planner_llm;
|
|
13
|
+
max_steps;
|
|
14
|
+
max_actions_per_step;
|
|
15
|
+
use_vision;
|
|
16
|
+
use_validation;
|
|
17
|
+
version;
|
|
18
|
+
source;
|
|
19
|
+
cdp_url;
|
|
20
|
+
action_errors;
|
|
21
|
+
action_history;
|
|
22
|
+
urls_visited;
|
|
23
|
+
steps;
|
|
24
|
+
total_input_tokens;
|
|
25
|
+
total_duration_seconds;
|
|
26
|
+
success;
|
|
27
|
+
final_result_response;
|
|
28
|
+
error_message;
|
|
29
|
+
constructor(payload) {
|
|
30
|
+
super();
|
|
31
|
+
this.task = payload.task;
|
|
32
|
+
this.model = payload.model;
|
|
33
|
+
this.model_provider = payload.model_provider;
|
|
34
|
+
this.planner_llm = payload.planner_llm;
|
|
35
|
+
this.max_steps = payload.max_steps;
|
|
36
|
+
this.max_actions_per_step = payload.max_actions_per_step;
|
|
37
|
+
this.use_vision = payload.use_vision;
|
|
38
|
+
this.use_validation = payload.use_validation;
|
|
39
|
+
this.version = payload.version;
|
|
40
|
+
this.source = payload.source;
|
|
41
|
+
this.cdp_url = payload.cdp_url;
|
|
42
|
+
this.action_errors = payload.action_errors;
|
|
43
|
+
this.action_history = payload.action_history;
|
|
44
|
+
this.urls_visited = payload.urls_visited;
|
|
45
|
+
this.steps = payload.steps;
|
|
46
|
+
this.total_input_tokens = payload.total_input_tokens;
|
|
47
|
+
this.total_duration_seconds = payload.total_duration_seconds;
|
|
48
|
+
this.success = payload.success;
|
|
49
|
+
this.final_result_response = payload.final_result_response;
|
|
50
|
+
this.error_message = payload.error_message;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class MCPClientTelemetryEvent extends BaseTelemetryEvent {
|
|
54
|
+
name = 'mcp_client_event';
|
|
55
|
+
server_name;
|
|
56
|
+
command;
|
|
57
|
+
tools_discovered;
|
|
58
|
+
version;
|
|
59
|
+
action;
|
|
60
|
+
tool_name;
|
|
61
|
+
duration_seconds;
|
|
62
|
+
error_message;
|
|
63
|
+
constructor(payload) {
|
|
64
|
+
super();
|
|
65
|
+
this.server_name = payload.server_name;
|
|
66
|
+
this.command = payload.command;
|
|
67
|
+
this.tools_discovered = payload.tools_discovered;
|
|
68
|
+
this.version = payload.version;
|
|
69
|
+
this.action = payload.action;
|
|
70
|
+
this.tool_name = payload.tool_name ?? null;
|
|
71
|
+
this.duration_seconds = payload.duration_seconds ?? null;
|
|
72
|
+
this.error_message = payload.error_message ?? null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export class MCPServerTelemetryEvent extends BaseTelemetryEvent {
|
|
76
|
+
name = 'mcp_server_event';
|
|
77
|
+
version;
|
|
78
|
+
action;
|
|
79
|
+
tool_name;
|
|
80
|
+
duration_seconds;
|
|
81
|
+
error_message;
|
|
82
|
+
parent_process_cmdline;
|
|
83
|
+
constructor(payload) {
|
|
84
|
+
super();
|
|
85
|
+
this.version = payload.version;
|
|
86
|
+
this.action = payload.action;
|
|
87
|
+
this.tool_name = payload.tool_name ?? null;
|
|
88
|
+
this.duration_seconds = payload.duration_seconds ?? null;
|
|
89
|
+
this.error_message = payload.error_message ?? null;
|
|
90
|
+
this.parent_process_cmdline = payload.parent_process_cmdline ?? null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export class CLITelemetryEvent extends BaseTelemetryEvent {
|
|
94
|
+
name = 'cli_event';
|
|
95
|
+
version;
|
|
96
|
+
action;
|
|
97
|
+
mode;
|
|
98
|
+
model;
|
|
99
|
+
model_provider;
|
|
100
|
+
duration_seconds;
|
|
101
|
+
error_message;
|
|
102
|
+
constructor(payload) {
|
|
103
|
+
super();
|
|
104
|
+
this.version = payload.version;
|
|
105
|
+
this.action = payload.action;
|
|
106
|
+
this.mode = payload.mode;
|
|
107
|
+
this.model = payload.model ?? null;
|
|
108
|
+
this.model_provider = payload.model_provider ?? null;
|
|
109
|
+
this.duration_seconds = payload.duration_seconds ?? null;
|
|
110
|
+
this.error_message = payload.error_message ?? null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BaseChatModel } from '../llm/base.js';
|
|
2
|
+
import type { ChatInvokeUsage } from '../llm/views.js';
|
|
3
|
+
import { ModelPricing, ModelUsageStats, ModelUsageTokens, TokenCostCalculated, TokenUsageEntry, UsageSummary } from './views.js';
|
|
4
|
+
export declare class TokenCost {
|
|
5
|
+
private includeCost;
|
|
6
|
+
private usageHistory;
|
|
7
|
+
private registeredLlms;
|
|
8
|
+
private originalAinvoke;
|
|
9
|
+
private pricingData;
|
|
10
|
+
private initialized;
|
|
11
|
+
private cacheDir;
|
|
12
|
+
constructor(includeCost?: boolean);
|
|
13
|
+
initialize(): Promise<void>;
|
|
14
|
+
private loadPricingData;
|
|
15
|
+
private findValidCache;
|
|
16
|
+
private isCacheValid;
|
|
17
|
+
private loadFromCache;
|
|
18
|
+
private fetchAndCachePricing;
|
|
19
|
+
addUsage(model: string, usage: ChatInvokeUsage): TokenUsageEntry;
|
|
20
|
+
register_llm(llm: BaseChatModel): BaseChatModel;
|
|
21
|
+
private logUsage;
|
|
22
|
+
private buildInputDisplay;
|
|
23
|
+
private formatTokens;
|
|
24
|
+
calculateCost(model: string, usage: ChatInvokeUsage): Promise<TokenCostCalculated | null>;
|
|
25
|
+
getModelPricing(modelName: string): Promise<ModelPricing | null>;
|
|
26
|
+
get_usage_tokens_for_model(model: string): ModelUsageTokens;
|
|
27
|
+
get_usage_summary(model?: string, since?: Date): Promise<UsageSummary>;
|
|
28
|
+
log_usage_summary(): Promise<void>;
|
|
29
|
+
get_cost_by_model: () => Promise<Record<string, ModelUsageStats>>;
|
|
30
|
+
clear_history(): void;
|
|
31
|
+
refresh_pricing_data(): Promise<void>;
|
|
32
|
+
clean_old_caches(keepCount?: number): Promise<void>;
|
|
33
|
+
ensurePricingLoaded(): Promise<void>;
|
|
34
|
+
estimateTokens(text: string): number;
|
|
35
|
+
}
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { encode } from 'gpt-tokenizer';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { CONFIG } from '../config.js';
|
|
6
|
+
import { createLogger } from '../logging-config.js';
|
|
7
|
+
const logger = createLogger('browser_use.tokens');
|
|
8
|
+
const costLogger = createLogger('browser_use.tokens.cost');
|
|
9
|
+
const PRICING_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
|
|
10
|
+
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 1 day
|
|
11
|
+
const CACHE_DIR_NAME = 'browser_use/token_cost';
|
|
12
|
+
const ansi = {
|
|
13
|
+
cyan: '\u001b[96m',
|
|
14
|
+
yellow: '\u001b[93m',
|
|
15
|
+
green: '\u001b[92m',
|
|
16
|
+
blue: '\u001b[94m',
|
|
17
|
+
magenta: '\u001b[95m',
|
|
18
|
+
reset: '\u001b[0m',
|
|
19
|
+
bold: '\u001b[1m',
|
|
20
|
+
};
|
|
21
|
+
const xdgCacheHome = () => {
|
|
22
|
+
const configured = CONFIG.XDG_CACHE_HOME;
|
|
23
|
+
if (configured && path.isAbsolute(configured)) {
|
|
24
|
+
return configured;
|
|
25
|
+
}
|
|
26
|
+
return path.join(process.env.HOME ?? process.cwd(), '.cache');
|
|
27
|
+
};
|
|
28
|
+
const ensureDir = async (dir) => {
|
|
29
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
30
|
+
};
|
|
31
|
+
const parsePricingTimestamp = (data) => new Date(data.timestamp);
|
|
32
|
+
const usagePromptCost = (cost) => {
|
|
33
|
+
if (!cost)
|
|
34
|
+
return 0;
|
|
35
|
+
return (cost.new_prompt_cost +
|
|
36
|
+
(cost.prompt_read_cached_cost ?? 0) +
|
|
37
|
+
(cost.prompt_cache_creation_cost ?? 0));
|
|
38
|
+
};
|
|
39
|
+
export class TokenCost {
|
|
40
|
+
includeCost;
|
|
41
|
+
usageHistory = [];
|
|
42
|
+
registeredLlms = new WeakSet();
|
|
43
|
+
originalAinvoke = new WeakMap();
|
|
44
|
+
pricingData = null;
|
|
45
|
+
initialized = false;
|
|
46
|
+
cacheDir;
|
|
47
|
+
constructor(includeCost) {
|
|
48
|
+
const envFlag = process.env.BROWSER_USE_CALCULATE_COST?.toLowerCase() === 'true';
|
|
49
|
+
this.includeCost = includeCost ?? envFlag ?? false;
|
|
50
|
+
this.cacheDir = path.join(xdgCacheHome(), CACHE_DIR_NAME);
|
|
51
|
+
}
|
|
52
|
+
async initialize() {
|
|
53
|
+
if (this.initialized)
|
|
54
|
+
return;
|
|
55
|
+
if (this.includeCost) {
|
|
56
|
+
await this.loadPricingData();
|
|
57
|
+
}
|
|
58
|
+
this.initialized = true;
|
|
59
|
+
}
|
|
60
|
+
async loadPricingData() {
|
|
61
|
+
try {
|
|
62
|
+
const cacheFile = await this.findValidCache();
|
|
63
|
+
if (cacheFile) {
|
|
64
|
+
await this.loadFromCache(cacheFile);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger.warning(`Failed to load token pricing cache: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
await this.fetchAndCachePricing();
|
|
72
|
+
}
|
|
73
|
+
async findValidCache() {
|
|
74
|
+
try {
|
|
75
|
+
await ensureDir(this.cacheDir);
|
|
76
|
+
const files = await fs.promises.readdir(this.cacheDir);
|
|
77
|
+
const jsonFiles = files.filter((file) => file.endsWith('.json'));
|
|
78
|
+
if (!jsonFiles.length) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const sorted = jsonFiles
|
|
82
|
+
.map((file) => path.join(this.cacheDir, file))
|
|
83
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
84
|
+
for (const file of sorted) {
|
|
85
|
+
const isValid = await this.isCacheValid(file);
|
|
86
|
+
if (isValid) {
|
|
87
|
+
return file;
|
|
88
|
+
}
|
|
89
|
+
await fs.promises.unlink(file).catch(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async isCacheValid(file) {
|
|
98
|
+
try {
|
|
99
|
+
const content = await fs.promises.readFile(file, 'utf-8');
|
|
100
|
+
const parsed = JSON.parse(content);
|
|
101
|
+
const timestamp = parsePricingTimestamp(parsed);
|
|
102
|
+
return Date.now() - timestamp.getTime() < CACHE_DURATION_MS;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async loadFromCache(file) {
|
|
109
|
+
const content = await fs.promises.readFile(file, 'utf-8');
|
|
110
|
+
const cached = JSON.parse(content);
|
|
111
|
+
this.pricingData = cached.data;
|
|
112
|
+
}
|
|
113
|
+
async fetchAndCachePricing() {
|
|
114
|
+
try {
|
|
115
|
+
const response = await axios.get(PRICING_URL, {
|
|
116
|
+
timeout: 30_000,
|
|
117
|
+
});
|
|
118
|
+
this.pricingData = response.data;
|
|
119
|
+
const cached = {
|
|
120
|
+
timestamp: new Date(),
|
|
121
|
+
data: response.data,
|
|
122
|
+
};
|
|
123
|
+
await ensureDir(this.cacheDir);
|
|
124
|
+
const fileName = `pricing_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
|
125
|
+
await fs.promises.writeFile(path.join(this.cacheDir, fileName), JSON.stringify(cached, null, 2));
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
logger.error(`Failed to fetch LiteLLM pricing: ${error.message}`);
|
|
129
|
+
this.pricingData = this.pricingData ?? {};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
addUsage(model, usage) {
|
|
133
|
+
const entry = {
|
|
134
|
+
model,
|
|
135
|
+
timestamp: new Date(),
|
|
136
|
+
usage,
|
|
137
|
+
};
|
|
138
|
+
this.usageHistory.push(entry);
|
|
139
|
+
return entry;
|
|
140
|
+
}
|
|
141
|
+
register_llm(llm) {
|
|
142
|
+
if (this.registeredLlms.has(llm)) {
|
|
143
|
+
return llm;
|
|
144
|
+
}
|
|
145
|
+
this.registeredLlms.add(llm);
|
|
146
|
+
const original = llm.ainvoke.bind(llm);
|
|
147
|
+
this.originalAinvoke.set(llm, original);
|
|
148
|
+
const self = this;
|
|
149
|
+
llm.ainvoke = async function (...args) {
|
|
150
|
+
// @ts-ignore - Spread operator type compatibility
|
|
151
|
+
const result = await original(...args);
|
|
152
|
+
if (result?.usage) {
|
|
153
|
+
const usageEntry = self.addUsage(llm.model, result.usage);
|
|
154
|
+
self.logUsage(llm.model, usageEntry).catch(() => undefined);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
return llm;
|
|
159
|
+
}
|
|
160
|
+
async logUsage(model, entry) {
|
|
161
|
+
if (!this.includeCost) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await this.ensurePricingLoaded();
|
|
165
|
+
const cost = await this.calculateCost(model, entry.usage);
|
|
166
|
+
const inputPart = this.buildInputDisplay(entry.usage, cost);
|
|
167
|
+
const completionTokensFmt = this.formatTokens(entry.usage.completion_tokens);
|
|
168
|
+
const completionSection = this.includeCost && cost && cost.completion_cost > 0
|
|
169
|
+
? `📤 ${ansi.green}${completionTokensFmt} ($${cost.completion_cost.toFixed(4)})${ansi.reset}`
|
|
170
|
+
: `📤 ${ansi.green}${completionTokensFmt}${ansi.reset}`;
|
|
171
|
+
costLogger.info(`🧠 ${ansi.cyan}${model}${ansi.reset} | ${inputPart} | ${completionSection}`);
|
|
172
|
+
}
|
|
173
|
+
buildInputDisplay(usage, cost) {
|
|
174
|
+
const parts = [];
|
|
175
|
+
const cached = usage.prompt_cached_tokens ?? 0;
|
|
176
|
+
const cacheCreation = usage.prompt_cache_creation_tokens ?? 0;
|
|
177
|
+
const uncached = usage.prompt_tokens - cached;
|
|
178
|
+
if (cached || cacheCreation) {
|
|
179
|
+
if (uncached > 0) {
|
|
180
|
+
const formatted = this.formatTokens(uncached);
|
|
181
|
+
const costPart = this.includeCost && cost && cost.new_prompt_cost > 0
|
|
182
|
+
? ` ($${cost.new_prompt_cost.toFixed(4)})`
|
|
183
|
+
: '';
|
|
184
|
+
parts.push(`🆕 ${ansi.yellow}${formatted}${costPart}${ansi.reset}`);
|
|
185
|
+
}
|
|
186
|
+
if (cached) {
|
|
187
|
+
const formatted = this.formatTokens(cached);
|
|
188
|
+
const cacheCost = this.includeCost && cost?.prompt_read_cached_cost
|
|
189
|
+
? ` ($${cost.prompt_read_cached_cost.toFixed(4)})`
|
|
190
|
+
: '';
|
|
191
|
+
parts.push(`💾 ${ansi.blue}${formatted}${cacheCost}${ansi.reset}`);
|
|
192
|
+
}
|
|
193
|
+
if (cacheCreation) {
|
|
194
|
+
const formatted = this.formatTokens(cacheCreation);
|
|
195
|
+
const creationCost = this.includeCost && cost?.prompt_cache_creation_cost
|
|
196
|
+
? ` ($${cost.prompt_cache_creation_cost.toFixed(4)})`
|
|
197
|
+
: '';
|
|
198
|
+
parts.push(`🔧 ${ansi.blue}${formatted}${creationCost}${ansi.reset}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const formatted = this.formatTokens(usage.prompt_tokens);
|
|
203
|
+
const costPart = this.includeCost && cost && cost.new_prompt_cost > 0
|
|
204
|
+
? ` ($${cost.new_prompt_cost.toFixed(4)})`
|
|
205
|
+
: '';
|
|
206
|
+
parts.push(`📥 ${ansi.yellow}${formatted}${costPart}${ansi.reset}`);
|
|
207
|
+
}
|
|
208
|
+
return parts.join(' + ');
|
|
209
|
+
}
|
|
210
|
+
formatTokens(tokens) {
|
|
211
|
+
if (tokens >= 1_000_000_000)
|
|
212
|
+
return `${(tokens / 1_000_000_000).toFixed(1)}B`;
|
|
213
|
+
if (tokens >= 1_000_000)
|
|
214
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
215
|
+
if (tokens >= 1_000)
|
|
216
|
+
return `${(tokens / 1_000).toFixed(1)}k`;
|
|
217
|
+
return `${tokens}`;
|
|
218
|
+
}
|
|
219
|
+
async calculateCost(model, usage) {
|
|
220
|
+
if (!this.includeCost) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
await this.ensurePricingLoaded();
|
|
224
|
+
const pricing = await this.getModelPricing(model);
|
|
225
|
+
if (!pricing) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const cached = usage.prompt_cached_tokens ?? 0;
|
|
229
|
+
const uncachedPrompt = usage.prompt_tokens - cached;
|
|
230
|
+
return {
|
|
231
|
+
new_prompt_tokens: usage.prompt_tokens,
|
|
232
|
+
new_prompt_cost: uncachedPrompt * (pricing.input_cost_per_token ?? 0),
|
|
233
|
+
prompt_read_cached_tokens: cached || null,
|
|
234
|
+
prompt_read_cached_cost: cached
|
|
235
|
+
? cached * (pricing.cache_read_input_token_cost ?? 0)
|
|
236
|
+
: null,
|
|
237
|
+
prompt_cached_creation_tokens: usage.prompt_cache_creation_tokens ?? null,
|
|
238
|
+
prompt_cache_creation_cost: usage.prompt_cache_creation_tokens &&
|
|
239
|
+
pricing.cache_creation_input_token_cost
|
|
240
|
+
? usage.prompt_cache_creation_tokens *
|
|
241
|
+
pricing.cache_creation_input_token_cost
|
|
242
|
+
: null,
|
|
243
|
+
completion_tokens: usage.completion_tokens,
|
|
244
|
+
completion_cost: usage.completion_tokens * (pricing.output_cost_per_token ?? 0),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async getModelPricing(modelName) {
|
|
248
|
+
await this.ensurePricingLoaded();
|
|
249
|
+
if (!this.pricingData) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const pricing = this.pricingData[modelName];
|
|
253
|
+
if (!pricing) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
model: modelName,
|
|
258
|
+
input_cost_per_token: pricing.input_cost_per_token ?? null,
|
|
259
|
+
output_cost_per_token: pricing.output_cost_per_token ?? null,
|
|
260
|
+
cache_read_input_token_cost: pricing.cache_read_input_token_cost ?? null,
|
|
261
|
+
cache_creation_input_token_cost: pricing.cache_creation_input_token_cost ?? null,
|
|
262
|
+
max_tokens: pricing.max_tokens ?? null,
|
|
263
|
+
max_input_tokens: pricing.max_input_tokens ?? null,
|
|
264
|
+
max_output_tokens: pricing.max_output_tokens ?? null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
get_usage_tokens_for_model(model) {
|
|
268
|
+
const filtered = this.usageHistory.filter((entry) => entry.model === model);
|
|
269
|
+
return {
|
|
270
|
+
model,
|
|
271
|
+
prompt_tokens: filtered.reduce((sum, entry) => sum + entry.usage.prompt_tokens, 0),
|
|
272
|
+
prompt_cached_tokens: filtered.reduce((sum, entry) => sum + (entry.usage.prompt_cached_tokens ?? 0), 0),
|
|
273
|
+
completion_tokens: filtered.reduce((sum, entry) => sum + entry.usage.completion_tokens, 0),
|
|
274
|
+
total_tokens: filtered.reduce((sum, entry) => sum + entry.usage.prompt_tokens + entry.usage.completion_tokens, 0),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async get_usage_summary(model, since) {
|
|
278
|
+
let entries = this.usageHistory;
|
|
279
|
+
if (model) {
|
|
280
|
+
entries = entries.filter((entry) => entry.model === model);
|
|
281
|
+
}
|
|
282
|
+
if (since) {
|
|
283
|
+
entries = entries.filter((entry) => entry.timestamp >= since);
|
|
284
|
+
}
|
|
285
|
+
if (!entries.length) {
|
|
286
|
+
return {
|
|
287
|
+
total_prompt_tokens: 0,
|
|
288
|
+
total_prompt_cost: 0,
|
|
289
|
+
total_prompt_cached_tokens: 0,
|
|
290
|
+
total_prompt_cached_cost: 0,
|
|
291
|
+
total_completion_tokens: 0,
|
|
292
|
+
total_completion_cost: 0,
|
|
293
|
+
total_tokens: 0,
|
|
294
|
+
total_cost: 0,
|
|
295
|
+
entry_count: 0,
|
|
296
|
+
by_model: {},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const byModel = {};
|
|
300
|
+
let totalPrompt = 0;
|
|
301
|
+
let totalCompletion = 0;
|
|
302
|
+
let totalPromptCached = 0;
|
|
303
|
+
let totalPromptCachedCost = 0;
|
|
304
|
+
let totalPromptCost = 0;
|
|
305
|
+
let totalCompletionCost = 0;
|
|
306
|
+
for (const entry of entries) {
|
|
307
|
+
const stats = (byModel[entry.model] ||= {
|
|
308
|
+
model: entry.model,
|
|
309
|
+
prompt_tokens: 0,
|
|
310
|
+
completion_tokens: 0,
|
|
311
|
+
total_tokens: 0,
|
|
312
|
+
cost: 0,
|
|
313
|
+
invocations: 0,
|
|
314
|
+
average_tokens_per_invocation: 0,
|
|
315
|
+
});
|
|
316
|
+
stats.prompt_tokens += entry.usage.prompt_tokens;
|
|
317
|
+
stats.completion_tokens += entry.usage.completion_tokens;
|
|
318
|
+
const totalEntryTokens = entry.usage.prompt_tokens + entry.usage.completion_tokens;
|
|
319
|
+
stats.total_tokens += totalEntryTokens;
|
|
320
|
+
stats.invocations += 1;
|
|
321
|
+
totalPrompt += entry.usage.prompt_tokens;
|
|
322
|
+
totalCompletion += entry.usage.completion_tokens;
|
|
323
|
+
totalPromptCached += entry.usage.prompt_cached_tokens ?? 0;
|
|
324
|
+
if (this.includeCost) {
|
|
325
|
+
const cost = await this.calculateCost(entry.model, entry.usage);
|
|
326
|
+
const promptCost = usagePromptCost(cost);
|
|
327
|
+
const completionCost = cost?.completion_cost ?? 0;
|
|
328
|
+
const cachedCost = cost?.prompt_read_cached_cost ?? 0;
|
|
329
|
+
stats.cost += promptCost + completionCost;
|
|
330
|
+
totalPromptCost += promptCost;
|
|
331
|
+
totalCompletionCost += completionCost;
|
|
332
|
+
totalPromptCachedCost += cachedCost;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
Object.values(byModel).forEach((stats) => {
|
|
336
|
+
stats.average_tokens_per_invocation = stats.invocations
|
|
337
|
+
? stats.total_tokens / stats.invocations
|
|
338
|
+
: 0;
|
|
339
|
+
});
|
|
340
|
+
return {
|
|
341
|
+
total_prompt_tokens: totalPrompt,
|
|
342
|
+
total_prompt_cost: totalPromptCost,
|
|
343
|
+
total_prompt_cached_tokens: totalPromptCached,
|
|
344
|
+
total_prompt_cached_cost: totalPromptCachedCost,
|
|
345
|
+
total_completion_tokens: totalCompletion,
|
|
346
|
+
total_completion_cost: totalCompletionCost,
|
|
347
|
+
total_tokens: totalPrompt + totalCompletion,
|
|
348
|
+
total_cost: totalPromptCost + totalCompletionCost,
|
|
349
|
+
entry_count: entries.length,
|
|
350
|
+
by_model: byModel,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
async log_usage_summary() {
|
|
354
|
+
if (!this.usageHistory.length)
|
|
355
|
+
return;
|
|
356
|
+
const summary = await this.get_usage_summary();
|
|
357
|
+
if (!summary.entry_count)
|
|
358
|
+
return;
|
|
359
|
+
const totalTokens = this.formatTokens(summary.total_tokens);
|
|
360
|
+
const totalCostPart = this.includeCost && summary.total_cost > 0
|
|
361
|
+
? ` ($${summary.total_cost.toFixed(4)})`
|
|
362
|
+
: '';
|
|
363
|
+
const promptTokens = this.formatTokens(summary.total_prompt_tokens);
|
|
364
|
+
const promptCostPart = this.includeCost && summary.total_prompt_cost > 0
|
|
365
|
+
? ` ($${summary.total_prompt_cost.toFixed(4)})`
|
|
366
|
+
: '';
|
|
367
|
+
const completionTokens = this.formatTokens(summary.total_completion_tokens);
|
|
368
|
+
const completionCostPart = this.includeCost && summary.total_completion_cost > 0
|
|
369
|
+
? ` ($${summary.total_completion_cost.toFixed(4)})`
|
|
370
|
+
: '';
|
|
371
|
+
costLogger.info(`💲 ${ansi.bold}Total Usage Summary${ansi.reset}: ${ansi.blue}${totalTokens} tokens${ansi.reset}${totalCostPart} | ` +
|
|
372
|
+
`⬅️ ${ansi.yellow}${promptTokens}${promptCostPart}${ansi.reset} | ➡️ ${ansi.green}${completionTokens}${completionCostPart}${ansi.reset}`);
|
|
373
|
+
costLogger.info(`📊 ${ansi.bold}Per-Model Usage Breakdown${ansi.reset}:`);
|
|
374
|
+
for (const [model, stats] of Object.entries(summary.by_model)) {
|
|
375
|
+
const totalFmt = this.formatTokens(stats.total_tokens);
|
|
376
|
+
const promptFmt = this.formatTokens(stats.prompt_tokens);
|
|
377
|
+
const completionFmt = this.formatTokens(stats.completion_tokens);
|
|
378
|
+
const avgFmt = this.formatTokens(Math.round(stats.average_tokens_per_invocation));
|
|
379
|
+
const costPart = this.includeCost && stats.cost > 0
|
|
380
|
+
? ` ($${stats.cost.toFixed(4)})`
|
|
381
|
+
: '';
|
|
382
|
+
costLogger.info(` 🤖 ${ansi.cyan}${model}${ansi.reset}: ${ansi.blue}${totalFmt} tokens${ansi.reset}${costPart} | ` +
|
|
383
|
+
`⬅️ ${ansi.yellow}${promptFmt}${ansi.reset} | ➡️ ${ansi.green}${completionFmt}${ansi.reset} | ` +
|
|
384
|
+
`📞 ${stats.invocations} calls | 📈 ${avgFmt}/call`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
get_cost_by_model = async () => {
|
|
388
|
+
const summary = await this.get_usage_summary();
|
|
389
|
+
return summary.by_model;
|
|
390
|
+
};
|
|
391
|
+
clear_history() {
|
|
392
|
+
this.usageHistory = [];
|
|
393
|
+
}
|
|
394
|
+
async refresh_pricing_data() {
|
|
395
|
+
if (!this.includeCost)
|
|
396
|
+
return;
|
|
397
|
+
await this.fetchAndCachePricing();
|
|
398
|
+
}
|
|
399
|
+
async clean_old_caches(keepCount = 3) {
|
|
400
|
+
try {
|
|
401
|
+
const files = await fs.promises.readdir(this.cacheDir);
|
|
402
|
+
const jsonFiles = files.filter((file) => file.endsWith('.json'));
|
|
403
|
+
if (jsonFiles.length <= keepCount)
|
|
404
|
+
return;
|
|
405
|
+
const sorted = jsonFiles
|
|
406
|
+
.map((file) => path.join(this.cacheDir, file))
|
|
407
|
+
.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
|
408
|
+
const toDelete = sorted.slice(0, sorted.length - keepCount);
|
|
409
|
+
await Promise.all(toDelete.map((file) => fs.promises.unlink(file).catch(() => undefined)));
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
logger.debug(`Failed to clean token cache: ${error.message}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async ensurePricingLoaded() {
|
|
416
|
+
if (!this.includeCost || this.pricingData)
|
|
417
|
+
return;
|
|
418
|
+
await this.loadPricingData();
|
|
419
|
+
}
|
|
420
|
+
estimateTokens(text) {
|
|
421
|
+
return encode(text).length;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ChatInvokeUsage } from '../llm/views.js';
|
|
2
|
+
export interface TokenUsageEntry {
|
|
3
|
+
model: string;
|
|
4
|
+
timestamp: Date;
|
|
5
|
+
usage: ChatInvokeUsage;
|
|
6
|
+
}
|
|
7
|
+
export interface TokenCostCalculated {
|
|
8
|
+
new_prompt_tokens: number;
|
|
9
|
+
new_prompt_cost: number;
|
|
10
|
+
prompt_read_cached_tokens: number | null;
|
|
11
|
+
prompt_read_cached_cost: number | null;
|
|
12
|
+
prompt_cached_creation_tokens: number | null;
|
|
13
|
+
prompt_cache_creation_cost: number | null;
|
|
14
|
+
completion_tokens: number;
|
|
15
|
+
completion_cost: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ModelPricing {
|
|
18
|
+
model: string;
|
|
19
|
+
input_cost_per_token: number | null;
|
|
20
|
+
output_cost_per_token: number | null;
|
|
21
|
+
cache_read_input_token_cost: number | null;
|
|
22
|
+
cache_creation_input_token_cost: number | null;
|
|
23
|
+
max_tokens: number | null;
|
|
24
|
+
max_input_tokens: number | null;
|
|
25
|
+
max_output_tokens: number | null;
|
|
26
|
+
}
|
|
27
|
+
export interface CachedPricingData {
|
|
28
|
+
timestamp: Date;
|
|
29
|
+
data: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
export interface ModelUsageStats {
|
|
32
|
+
model: string;
|
|
33
|
+
prompt_tokens: number;
|
|
34
|
+
completion_tokens: number;
|
|
35
|
+
total_tokens: number;
|
|
36
|
+
cost: number;
|
|
37
|
+
invocations: number;
|
|
38
|
+
average_tokens_per_invocation: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ModelUsageTokens {
|
|
41
|
+
model: string;
|
|
42
|
+
prompt_tokens: number;
|
|
43
|
+
prompt_cached_tokens: number;
|
|
44
|
+
completion_tokens: number;
|
|
45
|
+
total_tokens: number;
|
|
46
|
+
}
|
|
47
|
+
export interface UsageSummary {
|
|
48
|
+
total_prompt_tokens: number;
|
|
49
|
+
total_prompt_cost: number;
|
|
50
|
+
total_prompt_cached_tokens: number;
|
|
51
|
+
total_prompt_cached_cost: number;
|
|
52
|
+
total_completion_tokens: number;
|
|
53
|
+
total_completion_cost: number;
|
|
54
|
+
total_tokens: number;
|
|
55
|
+
total_cost: number;
|
|
56
|
+
entry_count: number;
|
|
57
|
+
by_model: Record<string, ModelUsageStats>;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|