tokenfirewall 1.0.1

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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/dist/adapters/anthropic.d.ts +5 -0
  4. package/dist/adapters/anthropic.js +34 -0
  5. package/dist/adapters/gemini.d.ts +5 -0
  6. package/dist/adapters/gemini.js +42 -0
  7. package/dist/adapters/grok.d.ts +6 -0
  8. package/dist/adapters/grok.js +33 -0
  9. package/dist/adapters/index.d.ts +5 -0
  10. package/dist/adapters/index.js +18 -0
  11. package/dist/adapters/kimi.d.ts +6 -0
  12. package/dist/adapters/kimi.js +33 -0
  13. package/dist/adapters/openai.d.ts +5 -0
  14. package/dist/adapters/openai.js +46 -0
  15. package/dist/core/budgetManager.d.ts +40 -0
  16. package/dist/core/budgetManager.js +116 -0
  17. package/dist/core/costEngine.d.ts +6 -0
  18. package/dist/core/costEngine.js +30 -0
  19. package/dist/core/pricingRegistry.d.ts +27 -0
  20. package/dist/core/pricingRegistry.js +75 -0
  21. package/dist/core/types.d.ts +66 -0
  22. package/dist/core/types.js +2 -0
  23. package/dist/index.d.ts +71 -0
  24. package/dist/index.js +134 -0
  25. package/dist/interceptors/fetchInterceptor.d.ts +13 -0
  26. package/dist/interceptors/fetchInterceptor.js +73 -0
  27. package/dist/interceptors/sdkInterceptor.d.ts +9 -0
  28. package/dist/interceptors/sdkInterceptor.js +37 -0
  29. package/dist/introspection/contextRegistry.d.ts +30 -0
  30. package/dist/introspection/contextRegistry.js +81 -0
  31. package/dist/introspection/modelLister.d.ts +22 -0
  32. package/dist/introspection/modelLister.js +190 -0
  33. package/dist/logger.d.ts +11 -0
  34. package/dist/logger.js +31 -0
  35. package/dist/registry.d.ts +18 -0
  36. package/dist/registry.js +38 -0
  37. package/package.json +46 -0
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BudgetManager = void 0;
4
+ /**
5
+ * Budget manager - encapsulates all budget tracking state
6
+ */
7
+ class BudgetManager {
8
+ constructor(options) {
9
+ this.totalSpent = 0;
10
+ this.trackingLock = Promise.resolve();
11
+ // Validate monthly limit
12
+ if (typeof options.monthlyLimit !== 'number' || isNaN(options.monthlyLimit) || !isFinite(options.monthlyLimit)) {
13
+ throw new Error('TokenFirewall: monthlyLimit must be a valid number');
14
+ }
15
+ if (options.monthlyLimit <= 0) {
16
+ throw new Error('TokenFirewall: monthlyLimit must be greater than 0');
17
+ }
18
+ // Validate mode
19
+ const mode = options.mode ?? "block";
20
+ if (mode !== "warn" && mode !== "block") {
21
+ throw new Error('TokenFirewall: mode must be either "warn" or "block"');
22
+ }
23
+ this.limit = options.monthlyLimit;
24
+ this.mode = mode;
25
+ }
26
+ /**
27
+ * Track a new cost and enforce budget limits
28
+ * Uses locking to prevent race conditions
29
+ * @throws Error if budget exceeded in block mode
30
+ */
31
+ async track(cost) {
32
+ // Validate cost parameter
33
+ if (typeof cost !== 'number' || isNaN(cost) || !isFinite(cost)) {
34
+ throw new Error('TokenFirewall: Cost must be a valid number');
35
+ }
36
+ if (cost < 0) {
37
+ throw new Error('TokenFirewall: Cost cannot be negative');
38
+ }
39
+ // Wait for any pending tracking to complete
40
+ await this.trackingLock;
41
+ // Create new lock for this tracking operation
42
+ let releaseLock;
43
+ this.trackingLock = new Promise((resolve) => {
44
+ releaseLock = resolve;
45
+ });
46
+ try {
47
+ // Check if this would exceed budget BEFORE adding
48
+ const projectedTotal = this.totalSpent + cost;
49
+ if (projectedTotal > this.limit) {
50
+ const message = `TokenFirewall: Budget exceeded! Would spend $${projectedTotal.toFixed(4)} of $${this.limit.toFixed(2)} limit`;
51
+ if (this.mode === "block") {
52
+ // In block mode, DON'T track the cost and throw error
53
+ throw new Error(message);
54
+ }
55
+ else {
56
+ console.warn(message);
57
+ }
58
+ }
59
+ // Only commit the cost if we didn't throw
60
+ this.totalSpent += cost;
61
+ }
62
+ finally {
63
+ // Release the lock
64
+ releaseLock();
65
+ }
66
+ }
67
+ /**
68
+ * Get current budget status
69
+ */
70
+ getStatus() {
71
+ const remaining = Math.max(0, this.limit - this.totalSpent);
72
+ const percentageUsed = (this.totalSpent / this.limit) * 100;
73
+ return {
74
+ totalSpent: this.totalSpent,
75
+ limit: this.limit,
76
+ remaining,
77
+ percentageUsed,
78
+ };
79
+ }
80
+ /**
81
+ * Reset budget tracking (useful for monthly resets)
82
+ */
83
+ reset() {
84
+ this.totalSpent = 0;
85
+ }
86
+ /**
87
+ * Export budget state for persistence
88
+ */
89
+ exportState() {
90
+ return {
91
+ totalSpent: this.totalSpent,
92
+ limit: this.limit,
93
+ mode: this.mode,
94
+ };
95
+ }
96
+ /**
97
+ * Import budget state from persistence
98
+ * Validates state before importing
99
+ */
100
+ importState(state) {
101
+ // Validate totalSpent
102
+ if (typeof state.totalSpent !== 'number' || isNaN(state.totalSpent) || !isFinite(state.totalSpent)) {
103
+ throw new Error('TokenFirewall: Invalid budget state - totalSpent must be a valid number');
104
+ }
105
+ if (state.totalSpent < 0) {
106
+ throw new Error('TokenFirewall: Invalid budget state - totalSpent cannot be negative');
107
+ }
108
+ // Allow importing state that exceeds limit (user may have increased limit)
109
+ // Just warn if it's suspicious
110
+ if (state.totalSpent > this.limit * 10) {
111
+ console.warn(`TokenFirewall: Imported totalSpent (${state.totalSpent}) is much larger than current limit (${this.limit})`);
112
+ }
113
+ this.totalSpent = state.totalSpent;
114
+ }
115
+ }
116
+ exports.BudgetManager = BudgetManager;
@@ -0,0 +1,6 @@
1
+ import { NormalizedUsage, CostBreakdown } from "./types";
2
+ /**
3
+ * Pure function to calculate cost from normalized usage
4
+ * Pricing is per 1M tokens
5
+ */
6
+ export declare function calculateCost(usage: NormalizedUsage): CostBreakdown;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateCost = calculateCost;
4
+ const pricingRegistry_1 = require("./pricingRegistry");
5
+ /**
6
+ * Pure function to calculate cost from normalized usage
7
+ * Pricing is per 1M tokens
8
+ */
9
+ function calculateCost(usage) {
10
+ try {
11
+ const pricing = pricingRegistry_1.pricingRegistry.getPricing(usage.provider, usage.model);
12
+ const inputCost = (usage.inputTokens / 1000000) * pricing.input;
13
+ const outputCost = (usage.outputTokens / 1000000) * pricing.output;
14
+ const totalCost = inputCost + outputCost;
15
+ return {
16
+ inputCost,
17
+ outputCost,
18
+ totalCost,
19
+ };
20
+ }
21
+ catch (error) {
22
+ // If pricing not found, log warning and return zero cost
23
+ console.warn(`TokenFirewall: ${error instanceof Error ? error.message : 'Unknown pricing error'} - tracking with $0 cost`);
24
+ return {
25
+ inputCost: 0,
26
+ outputCost: 0,
27
+ totalCost: 0,
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,27 @@
1
+ import { ModelPricing } from "./types";
2
+ /**
3
+ * Centralized pricing registry for all LLM providers
4
+ */
5
+ declare class PricingRegistry {
6
+ private pricing;
7
+ constructor();
8
+ /**
9
+ * Initialize default pricing for supported providers
10
+ */
11
+ private initializeDefaultPricing;
12
+ /**
13
+ * Register pricing for a provider and model
14
+ */
15
+ register(provider: string, model: string, pricing: ModelPricing): void;
16
+ /**
17
+ * Get pricing for a specific provider and model
18
+ * @throws Error if pricing not found
19
+ */
20
+ getPricing(provider: string, model: string): ModelPricing;
21
+ /**
22
+ * Check if pricing exists for a provider and model
23
+ */
24
+ hasPricing(provider: string, model: string): boolean;
25
+ }
26
+ export declare const pricingRegistry: PricingRegistry;
27
+ export {};
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pricingRegistry = void 0;
4
+ /**
5
+ * Centralized pricing registry for all LLM providers
6
+ */
7
+ class PricingRegistry {
8
+ constructor() {
9
+ this.pricing = new Map();
10
+ this.initializeDefaultPricing();
11
+ }
12
+ /**
13
+ * Initialize default pricing for supported providers
14
+ */
15
+ initializeDefaultPricing() {
16
+ // OpenAI pricing (per 1M tokens)
17
+ this.register("openai", "gpt-4o", { input: 2.5, output: 10.0 });
18
+ this.register("openai", "gpt-4o-mini", { input: 0.15, output: 0.6 });
19
+ this.register("openai", "gpt-4-turbo", { input: 10.0, output: 30.0 });
20
+ this.register("openai", "gpt-3.5-turbo", { input: 0.5, output: 1.5 });
21
+ // Anthropic pricing (per 1M tokens)
22
+ this.register("anthropic", "claude-3-5-sonnet-20241022", { input: 3.0, output: 15.0 });
23
+ this.register("anthropic", "claude-3-5-haiku-20241022", { input: 0.8, output: 4.0 });
24
+ this.register("anthropic", "claude-3-opus-20240229", { input: 15.0, output: 75.0 });
25
+ // Google Gemini pricing (per 1M tokens)
26
+ this.register("gemini", "gemini-2.0-flash-exp", { input: 0.0, output: 0.0 });
27
+ this.register("gemini", "gemini-1.5-pro", { input: 1.25, output: 5.0 });
28
+ this.register("gemini", "gemini-1.5-flash", { input: 0.075, output: 0.3 });
29
+ // Grok pricing (per 1M tokens)
30
+ this.register("grok", "grok-beta", { input: 5.0, output: 15.0 });
31
+ this.register("grok", "grok-vision-beta", { input: 5.0, output: 15.0 });
32
+ this.register("grok", "grok-2-1212", { input: 2.0, output: 10.0 });
33
+ this.register("grok", "grok-2-vision-1212", { input: 2.0, output: 10.0 });
34
+ // Llama models via Grok API (per 1M tokens)
35
+ this.register("grok", "llama-3.1-70b", { input: 0.5, output: 0.8 });
36
+ this.register("grok", "llama-3.1-8b", { input: 0.1, output: 0.1 });
37
+ this.register("grok", "llama-3.2-90b-vision", { input: 0.6, output: 0.9 });
38
+ this.register("grok", "llama-3.3-70b", { input: 0.5, output: 0.8 });
39
+ // Kimi pricing (per 1M tokens)
40
+ this.register("kimi", "moonshot-v1-8k", { input: 0.12, output: 0.12 });
41
+ this.register("kimi", "moonshot-v1-32k", { input: 0.24, output: 0.24 });
42
+ this.register("kimi", "moonshot-v1-128k", { input: 0.6, output: 0.6 });
43
+ }
44
+ /**
45
+ * Register pricing for a provider and model
46
+ */
47
+ register(provider, model, pricing) {
48
+ if (!this.pricing.has(provider)) {
49
+ this.pricing.set(provider, new Map());
50
+ }
51
+ this.pricing.get(provider).set(model, pricing);
52
+ }
53
+ /**
54
+ * Get pricing for a specific provider and model
55
+ * @throws Error if pricing not found
56
+ */
57
+ getPricing(provider, model) {
58
+ const providerPricing = this.pricing.get(provider);
59
+ if (!providerPricing) {
60
+ throw new Error(`TokenFirewall: No pricing found for provider "${provider}"`);
61
+ }
62
+ const modelPricing = providerPricing.get(model);
63
+ if (!modelPricing) {
64
+ throw new Error(`TokenFirewall: No pricing found for model "${model}" from provider "${provider}"`);
65
+ }
66
+ return modelPricing;
67
+ }
68
+ /**
69
+ * Check if pricing exists for a provider and model
70
+ */
71
+ hasPricing(provider, model) {
72
+ return this.pricing.get(provider)?.has(model) ?? false;
73
+ }
74
+ }
75
+ exports.pricingRegistry = new PricingRegistry();
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Unified usage contract that all providers must normalize to
3
+ */
4
+ export interface NormalizedUsage {
5
+ provider: string;
6
+ model: string;
7
+ inputTokens: number;
8
+ outputTokens: number;
9
+ totalTokens: number;
10
+ }
11
+ /**
12
+ * Cost breakdown for a single LLM call
13
+ */
14
+ export interface CostBreakdown {
15
+ inputCost: number;
16
+ outputCost: number;
17
+ totalCost: number;
18
+ }
19
+ /**
20
+ * Provider adapter interface - all adapters must implement this
21
+ */
22
+ export interface ProviderAdapter {
23
+ name: string;
24
+ detect(response: unknown): boolean;
25
+ normalize(response: unknown, request?: unknown): NormalizedUsage;
26
+ }
27
+ /**
28
+ * Pricing configuration for a specific model
29
+ */
30
+ export interface ModelPricing {
31
+ input: number;
32
+ output: number;
33
+ }
34
+ /**
35
+ * Budget guard configuration
36
+ */
37
+ export interface BudgetGuardOptions {
38
+ monthlyLimit: number;
39
+ mode?: "warn" | "block";
40
+ }
41
+ /**
42
+ * Budget status information
43
+ */
44
+ export interface BudgetStatus {
45
+ totalSpent: number;
46
+ limit: number;
47
+ remaining: number;
48
+ percentageUsed: number;
49
+ }
50
+ /**
51
+ * Model information with context limits and budget usage
52
+ */
53
+ export interface ModelInfo {
54
+ model: string;
55
+ contextLimit?: number;
56
+ budgetUsagePercentage?: number;
57
+ }
58
+ /**
59
+ * Options for listing available models
60
+ */
61
+ export interface ListModelsOptions {
62
+ provider: string;
63
+ apiKey: string;
64
+ baseURL?: string;
65
+ includeBudgetUsage?: boolean;
66
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,71 @@
1
+ import { BudgetGuardOptions, ProviderAdapter, ModelPricing, BudgetStatus } from "./core/types";
2
+ import { BudgetManager } from "./core/budgetManager";
3
+ import { patchGlobalFetch } from "./interceptors/fetchInterceptor";
4
+ import { patchProvider } from "./interceptors/sdkInterceptor";
5
+ import { listAvailableModels, ModelInfo, ListModelsOptions } from "./introspection/modelLister";
6
+ /**
7
+ * Create and configure a budget guard
8
+ * @param options - Budget configuration options
9
+ * @returns Budget manager instance
10
+ */
11
+ export declare function createBudgetGuard(options: BudgetGuardOptions): BudgetManager;
12
+ /**
13
+ * Patch global fetch to intercept LLM API calls
14
+ */
15
+ export { patchGlobalFetch };
16
+ /**
17
+ * Patch a specific provider SDK
18
+ * @param providerName - Name of the provider to patch
19
+ */
20
+ export { patchProvider };
21
+ /**
22
+ * Register a custom provider adapter
23
+ * @param adapter - Provider adapter implementation
24
+ */
25
+ export declare function registerAdapter(adapter: ProviderAdapter): void;
26
+ /**
27
+ * Register custom pricing for a provider and model
28
+ * @param provider - Provider name
29
+ * @param model - Model name
30
+ * @param pricing - Pricing configuration (per 1M tokens)
31
+ */
32
+ export declare function registerPricing(provider: string, model: string, pricing: ModelPricing): void;
33
+ /**
34
+ * Register custom context limit for a provider and model
35
+ * @param provider - Provider name
36
+ * @param model - Model name
37
+ * @param contextLimit - Context window size in tokens
38
+ */
39
+ export declare function registerContextLimit(provider: string, model: string, contextLimit: number): void;
40
+ /**
41
+ * Get current budget status
42
+ * @returns Budget status or null if no budget guard is active
43
+ */
44
+ export declare function getBudgetStatus(): BudgetStatus | null;
45
+ /**
46
+ * Reset budget tracking
47
+ */
48
+ export declare function resetBudget(): void;
49
+ /**
50
+ * Export budget state for persistence
51
+ */
52
+ export declare function exportBudgetState(): {
53
+ totalSpent: number;
54
+ limit: number;
55
+ mode: string;
56
+ } | null;
57
+ /**
58
+ * Import budget state from persistence
59
+ */
60
+ export declare function importBudgetState(state: {
61
+ totalSpent: number;
62
+ }): void;
63
+ /**
64
+ * List available models for a provider with context limits and budget usage
65
+ * @param options - Provider configuration and options
66
+ * @returns Array of model information
67
+ */
68
+ export declare function listModels(options: Omit<ListModelsOptions, 'budgetManager'>): Promise<ModelInfo[]>;
69
+ export { listAvailableModels };
70
+ export type { BudgetGuardOptions, ProviderAdapter, ModelPricing, NormalizedUsage, CostBreakdown, BudgetStatus, ModelInfo, ListModelsOptions, } from "./core/types";
71
+ export type { ModelInfo as ModelInfoType, ListModelsOptions as ListModelsOptionsType } from "./introspection/modelLister";
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listAvailableModels = exports.patchProvider = exports.patchGlobalFetch = void 0;
4
+ exports.createBudgetGuard = createBudgetGuard;
5
+ exports.registerAdapter = registerAdapter;
6
+ exports.registerPricing = registerPricing;
7
+ exports.registerContextLimit = registerContextLimit;
8
+ exports.getBudgetStatus = getBudgetStatus;
9
+ exports.resetBudget = resetBudget;
10
+ exports.exportBudgetState = exportBudgetState;
11
+ exports.importBudgetState = importBudgetState;
12
+ exports.listModels = listModels;
13
+ const budgetManager_1 = require("./core/budgetManager");
14
+ const pricingRegistry_1 = require("./core/pricingRegistry");
15
+ const registry_1 = require("./registry");
16
+ const fetchInterceptor_1 = require("./interceptors/fetchInterceptor");
17
+ Object.defineProperty(exports, "patchGlobalFetch", { enumerable: true, get: function () { return fetchInterceptor_1.patchGlobalFetch; } });
18
+ const sdkInterceptor_1 = require("./interceptors/sdkInterceptor");
19
+ Object.defineProperty(exports, "patchProvider", { enumerable: true, get: function () { return sdkInterceptor_1.patchProvider; } });
20
+ const modelLister_1 = require("./introspection/modelLister");
21
+ Object.defineProperty(exports, "listAvailableModels", { enumerable: true, get: function () { return modelLister_1.listAvailableModels; } });
22
+ const contextRegistry_1 = require("./introspection/contextRegistry");
23
+ let globalBudgetManager = null;
24
+ /**
25
+ * Create and configure a budget guard
26
+ * @param options - Budget configuration options
27
+ * @returns Budget manager instance
28
+ */
29
+ function createBudgetGuard(options) {
30
+ // Warn if overwriting existing budget guard
31
+ if (globalBudgetManager) {
32
+ console.warn('TokenFirewall: Creating new budget guard will replace existing one. Previous budget state will be lost.');
33
+ }
34
+ const manager = new budgetManager_1.BudgetManager(options);
35
+ globalBudgetManager = manager;
36
+ (0, fetchInterceptor_1.setBudgetManager)(manager);
37
+ return manager;
38
+ }
39
+ /**
40
+ * Register a custom provider adapter
41
+ * @param adapter - Provider adapter implementation
42
+ */
43
+ function registerAdapter(adapter) {
44
+ registry_1.adapterRegistry.register(adapter);
45
+ }
46
+ /**
47
+ * Register custom pricing for a provider and model
48
+ * @param provider - Provider name
49
+ * @param model - Model name
50
+ * @param pricing - Pricing configuration (per 1M tokens)
51
+ */
52
+ function registerPricing(provider, model, pricing) {
53
+ // Validate inputs
54
+ if (!provider || typeof provider !== 'string') {
55
+ throw new Error('TokenFirewall: Provider must be a non-empty string');
56
+ }
57
+ if (!model || typeof model !== 'string') {
58
+ throw new Error('TokenFirewall: Model must be a non-empty string');
59
+ }
60
+ if (!pricing || typeof pricing !== 'object') {
61
+ throw new Error('TokenFirewall: Pricing must be an object');
62
+ }
63
+ if (typeof pricing.input !== 'number' || pricing.input < 0 || !isFinite(pricing.input)) {
64
+ throw new Error('TokenFirewall: Pricing input must be a non-negative number');
65
+ }
66
+ if (typeof pricing.output !== 'number' || pricing.output < 0 || !isFinite(pricing.output)) {
67
+ throw new Error('TokenFirewall: Pricing output must be a non-negative number');
68
+ }
69
+ pricingRegistry_1.pricingRegistry.register(provider, model, pricing);
70
+ }
71
+ /**
72
+ * Register custom context limit for a provider and model
73
+ * @param provider - Provider name
74
+ * @param model - Model name
75
+ * @param contextLimit - Context window size in tokens
76
+ */
77
+ function registerContextLimit(provider, model, contextLimit) {
78
+ // Validate inputs
79
+ if (!provider || typeof provider !== 'string') {
80
+ throw new Error('TokenFirewall: Provider must be a non-empty string');
81
+ }
82
+ if (!model || typeof model !== 'string') {
83
+ throw new Error('TokenFirewall: Model must be a non-empty string');
84
+ }
85
+ if (typeof contextLimit !== 'number' || contextLimit <= 0 || !isFinite(contextLimit)) {
86
+ throw new Error('TokenFirewall: Context limit must be a positive number');
87
+ }
88
+ contextRegistry_1.contextRegistry.register(provider, model, { tokens: contextLimit });
89
+ }
90
+ /**
91
+ * Get current budget status
92
+ * @returns Budget status or null if no budget guard is active
93
+ */
94
+ function getBudgetStatus() {
95
+ return globalBudgetManager ? globalBudgetManager.getStatus() : null;
96
+ }
97
+ /**
98
+ * Reset budget tracking
99
+ */
100
+ function resetBudget() {
101
+ if (globalBudgetManager) {
102
+ globalBudgetManager.reset();
103
+ }
104
+ }
105
+ /**
106
+ * Export budget state for persistence
107
+ */
108
+ function exportBudgetState() {
109
+ if (globalBudgetManager) {
110
+ return globalBudgetManager.exportState();
111
+ }
112
+ return null;
113
+ }
114
+ /**
115
+ * Import budget state from persistence
116
+ */
117
+ function importBudgetState(state) {
118
+ if (!globalBudgetManager) {
119
+ throw new Error('TokenFirewall: Cannot import budget state - no budget guard exists. Call createBudgetGuard() first.');
120
+ }
121
+ globalBudgetManager.importState(state);
122
+ }
123
+ /**
124
+ * List available models for a provider with context limits and budget usage
125
+ * @param options - Provider configuration and options
126
+ * @returns Array of model information
127
+ */
128
+ async function listModels(options) {
129
+ // Pass the global budget manager to avoid circular dependency
130
+ return (0, modelLister_1.listAvailableModels)({
131
+ ...options,
132
+ budgetManager: globalBudgetManager
133
+ });
134
+ }
@@ -0,0 +1,13 @@
1
+ import { BudgetManager } from "../core/budgetManager";
2
+ /**
3
+ * Set the budget manager for fetch interception
4
+ */
5
+ export declare function setBudgetManager(manager: BudgetManager | null): void;
6
+ /**
7
+ * Patch global fetch to intercept LLM API calls
8
+ */
9
+ export declare function patchGlobalFetch(): void;
10
+ /**
11
+ * Restore original fetch
12
+ */
13
+ export declare function unpatchGlobalFetch(): void;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setBudgetManager = setBudgetManager;
4
+ exports.patchGlobalFetch = patchGlobalFetch;
5
+ exports.unpatchGlobalFetch = unpatchGlobalFetch;
6
+ const registry_1 = require("../registry");
7
+ const costEngine_1 = require("../core/costEngine");
8
+ const logger_1 = require("../logger");
9
+ let isPatched = false;
10
+ let budgetManager = null;
11
+ const originalFetch = globalThis.fetch;
12
+ /**
13
+ * Set the budget manager for fetch interception
14
+ */
15
+ function setBudgetManager(manager) {
16
+ budgetManager = manager;
17
+ }
18
+ /**
19
+ * Patch global fetch to intercept LLM API calls
20
+ */
21
+ function patchGlobalFetch() {
22
+ if (isPatched) {
23
+ return;
24
+ }
25
+ const interceptedFetch = async function (input, init) {
26
+ const response = await originalFetch(input, init);
27
+ // Try to clone response for tracking (may fail for some responses)
28
+ let clonedResponse;
29
+ try {
30
+ clonedResponse = response.clone();
31
+ }
32
+ catch (error) {
33
+ // If cloning fails, just return original response without tracking
34
+ console.warn('TokenFirewall: Failed to clone response for tracking');
35
+ return response;
36
+ }
37
+ // Process response and track budget BEFORE returning
38
+ try {
39
+ const responseData = await clonedResponse.json();
40
+ // Try to process with adapter registry
41
+ const normalizedUsage = registry_1.adapterRegistry.process(responseData);
42
+ if (normalizedUsage) {
43
+ // Calculate cost
44
+ const cost = (0, costEngine_1.calculateCost)(normalizedUsage);
45
+ // Track budget if manager exists - MUST await to enforce blocking
46
+ if (budgetManager) {
47
+ await budgetManager.track(cost.totalCost);
48
+ }
49
+ // Log usage
50
+ logger_1.logger.logUsage(normalizedUsage, cost);
51
+ }
52
+ }
53
+ catch (error) {
54
+ // If it's a budget error, re-throw it
55
+ if (error instanceof Error && error.message.includes('TokenFirewall: Budget exceeded')) {
56
+ throw error;
57
+ }
58
+ // Otherwise, not JSON or not an LLM response - ignore silently
59
+ }
60
+ return response;
61
+ };
62
+ globalThis.fetch = interceptedFetch;
63
+ isPatched = true;
64
+ }
65
+ /**
66
+ * Restore original fetch
67
+ */
68
+ function unpatchGlobalFetch() {
69
+ if (isPatched) {
70
+ globalThis.fetch = originalFetch;
71
+ isPatched = false;
72
+ }
73
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SDK-specific interceptors for providers that need direct patching
3
+ * Currently placeholder - most providers work via fetch interception
4
+ */
5
+ /**
6
+ * Patch a specific provider SDK
7
+ * @param providerName - Name of the provider to patch
8
+ */
9
+ export declare function patchProvider(providerName: string): void;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * SDK-specific interceptors for providers that need direct patching
4
+ * Currently placeholder - most providers work via fetch interception
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.patchProvider = patchProvider;
8
+ const patchedProviders = new Set();
9
+ /**
10
+ * Patch a specific provider SDK
11
+ * @param providerName - Name of the provider to patch
12
+ */
13
+ function patchProvider(providerName) {
14
+ if (patchedProviders.has(providerName)) {
15
+ return;
16
+ }
17
+ switch (providerName.toLowerCase()) {
18
+ case "openai":
19
+ // OpenAI SDK uses fetch internally - covered by fetch interceptor
20
+ break;
21
+ case "anthropic":
22
+ // Anthropic SDK uses fetch internally - covered by fetch interceptor
23
+ break;
24
+ case "gemini":
25
+ // Gemini SDK uses fetch internally - covered by fetch interceptor
26
+ break;
27
+ case "grok":
28
+ // Grok uses fetch - covered by fetch interceptor
29
+ break;
30
+ case "kimi":
31
+ // Kimi uses fetch - covered by fetch interceptor
32
+ break;
33
+ default:
34
+ console.warn(`TokenFirewall: Provider "${providerName}" not recognized`);
35
+ }
36
+ patchedProviders.add(providerName);
37
+ }