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,30 @@
1
+ /**
2
+ * Context window registry for LLM models
3
+ * Maintains static mapping of model -> context limits
4
+ */
5
+ interface ContextLimit {
6
+ tokens: number;
7
+ }
8
+ declare class ContextRegistry {
9
+ private limits;
10
+ constructor();
11
+ /**
12
+ * Initialize known context limits for supported models
13
+ */
14
+ private initializeContextLimits;
15
+ /**
16
+ * Register context limit for a model
17
+ */
18
+ register(provider: string, model: string, limit: ContextLimit): void;
19
+ /**
20
+ * Get context limit for a model
21
+ * Returns undefined if not found (does not throw)
22
+ */
23
+ getContextLimit(provider: string, model: string): number | undefined;
24
+ /**
25
+ * Check if provider is supported
26
+ */
27
+ isProviderSupported(provider: string): boolean;
28
+ }
29
+ export declare const contextRegistry: ContextRegistry;
30
+ export {};
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Context window registry for LLM models
4
+ * Maintains static mapping of model -> context limits
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.contextRegistry = void 0;
8
+ class ContextRegistry {
9
+ constructor() {
10
+ this.limits = new Map();
11
+ this.initializeContextLimits();
12
+ }
13
+ /**
14
+ * Initialize known context limits for supported models
15
+ */
16
+ initializeContextLimits() {
17
+ // OpenAI context limits
18
+ this.register("openai", "gpt-4o", { tokens: 128000 });
19
+ this.register("openai", "gpt-4o-mini", { tokens: 128000 });
20
+ this.register("openai", "gpt-4-turbo", { tokens: 128000 });
21
+ this.register("openai", "gpt-4", { tokens: 8192 });
22
+ this.register("openai", "gpt-3.5-turbo", { tokens: 16385 });
23
+ this.register("openai", "gpt-3.5-turbo-16k", { tokens: 16385 });
24
+ // Anthropic context limits
25
+ this.register("anthropic", "claude-3-5-sonnet-20241022", { tokens: 200000 });
26
+ this.register("anthropic", "claude-3-5-haiku-20241022", { tokens: 200000 });
27
+ this.register("anthropic", "claude-3-opus-20240229", { tokens: 200000 });
28
+ this.register("anthropic", "claude-3-sonnet-20240229", { tokens: 200000 });
29
+ this.register("anthropic", "claude-3-haiku-20240307", { tokens: 200000 });
30
+ // Gemini context limits (updated with latest models)
31
+ this.register("gemini", "gemini-2.5-flash", { tokens: 1048576 });
32
+ this.register("gemini", "gemini-2.5-pro", { tokens: 2097152 });
33
+ this.register("gemini", "gemini-2.0-flash", { tokens: 1048576 });
34
+ this.register("gemini", "gemini-2.0-flash-exp", { tokens: 1048576 });
35
+ this.register("gemini", "gemini-1.5-pro", { tokens: 2097152 });
36
+ this.register("gemini", "gemini-1.5-flash", { tokens: 1048576 });
37
+ this.register("gemini", "gemini-1.0-pro", { tokens: 32768 });
38
+ // Grok context limits
39
+ this.register("grok", "grok-beta", { tokens: 131072 });
40
+ this.register("grok", "grok-vision-beta", { tokens: 131072 });
41
+ this.register("grok", "grok-2-1212", { tokens: 131072 });
42
+ this.register("grok", "grok-2-vision-1212", { tokens: 131072 });
43
+ // Llama models via Grok
44
+ this.register("grok", "llama-3.1-70b", { tokens: 131072 });
45
+ this.register("grok", "llama-3.1-8b", { tokens: 131072 });
46
+ this.register("grok", "llama-3.2-90b-vision", { tokens: 131072 });
47
+ this.register("grok", "llama-3.3-70b", { tokens: 131072 });
48
+ // Kimi context limits
49
+ this.register("kimi", "moonshot-v1-8k", { tokens: 8192 });
50
+ this.register("kimi", "moonshot-v1-32k", { tokens: 32768 });
51
+ this.register("kimi", "moonshot-v1-128k", { tokens: 131072 });
52
+ }
53
+ /**
54
+ * Register context limit for a model
55
+ */
56
+ register(provider, model, limit) {
57
+ if (!this.limits.has(provider)) {
58
+ this.limits.set(provider, new Map());
59
+ }
60
+ this.limits.get(provider).set(model, limit);
61
+ }
62
+ /**
63
+ * Get context limit for a model
64
+ * Returns undefined if not found (does not throw)
65
+ */
66
+ getContextLimit(provider, model) {
67
+ const providerLimits = this.limits.get(provider);
68
+ if (!providerLimits) {
69
+ return undefined;
70
+ }
71
+ const limit = providerLimits.get(model);
72
+ return limit?.tokens;
73
+ }
74
+ /**
75
+ * Check if provider is supported
76
+ */
77
+ isProviderSupported(provider) {
78
+ return this.limits.has(provider);
79
+ }
80
+ }
81
+ exports.contextRegistry = new ContextRegistry();
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Model information with context limits and budget usage
3
+ */
4
+ export interface ModelInfo {
5
+ model: string;
6
+ contextLimit?: number;
7
+ budgetUsagePercentage?: number;
8
+ }
9
+ /**
10
+ * Options for listing available models
11
+ */
12
+ export interface ListModelsOptions {
13
+ provider: string;
14
+ apiKey: string;
15
+ baseURL?: string;
16
+ includeBudgetUsage?: boolean;
17
+ budgetManager?: any;
18
+ }
19
+ /**
20
+ * List available models for a provider
21
+ */
22
+ export declare function listAvailableModels(options: ListModelsOptions): Promise<ModelInfo[]>;
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listAvailableModels = listAvailableModels;
4
+ const contextRegistry_1 = require("./contextRegistry");
5
+ /**
6
+ * List available models for a provider
7
+ */
8
+ async function listAvailableModels(options) {
9
+ const { provider, apiKey, baseURL, includeBudgetUsage, budgetManager } = options;
10
+ const providerLower = provider.toLowerCase();
11
+ let models = [];
12
+ try {
13
+ switch (providerLower) {
14
+ case "openai":
15
+ models = await listOpenAIModels(apiKey, baseURL);
16
+ break;
17
+ case "anthropic":
18
+ models = await listAnthropicModels();
19
+ break;
20
+ case "gemini":
21
+ models = await listGeminiModels(apiKey, baseURL);
22
+ break;
23
+ case "grok":
24
+ models = await listGrokModels(apiKey, baseURL);
25
+ break;
26
+ case "kimi":
27
+ models = await listKimiModels(apiKey, baseURL);
28
+ break;
29
+ default:
30
+ console.warn(`TokenFirewall: Provider "${provider}" not supported for model listing`);
31
+ return [];
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.warn(`TokenFirewall: Failed to list models for ${provider}:`, error);
36
+ return [];
37
+ }
38
+ return enrichModelsWithMetadata(providerLower, models, includeBudgetUsage, budgetManager);
39
+ }
40
+ /**
41
+ * Enrich model list with context limits and budget usage
42
+ */
43
+ function enrichModelsWithMetadata(provider, models, includeBudgetUsage, budgetManager) {
44
+ let budgetPercentage;
45
+ if (includeBudgetUsage && budgetManager) {
46
+ const status = budgetManager.getStatus();
47
+ budgetPercentage = status ? status.percentageUsed : undefined;
48
+ }
49
+ return models.map((model) => {
50
+ const contextLimit = contextRegistry_1.contextRegistry.getContextLimit(provider, model);
51
+ const info = {
52
+ model,
53
+ };
54
+ if (contextLimit !== undefined) {
55
+ info.contextLimit = contextLimit;
56
+ }
57
+ if (budgetPercentage !== undefined) {
58
+ info.budgetUsagePercentage = budgetPercentage;
59
+ }
60
+ return info;
61
+ });
62
+ }
63
+ // Remove the circular dependency function
64
+ // function getBudgetUsagePercentage(): number | undefined {
65
+ // try {
66
+ // const { getBudgetStatus } = require("../index");
67
+ // const status = getBudgetStatus();
68
+ // return status ? status.percentageUsed : undefined;
69
+ // } catch {
70
+ // return undefined;
71
+ // }
72
+ // }
73
+ /**
74
+ * Fetch with timeout
75
+ */
76
+ async function fetchWithTimeout(url, options, timeoutMs = 10000) {
77
+ const controller = new AbortController();
78
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
79
+ try {
80
+ const response = await fetch(url, {
81
+ ...options,
82
+ signal: controller.signal
83
+ });
84
+ clearTimeout(timeoutId);
85
+ return response;
86
+ }
87
+ catch (error) {
88
+ clearTimeout(timeoutId);
89
+ if (error instanceof Error && error.name === 'AbortError') {
90
+ throw new Error('Request timeout');
91
+ }
92
+ throw error;
93
+ }
94
+ }
95
+ /**
96
+ * List OpenAI models
97
+ */
98
+ async function listOpenAIModels(apiKey, baseURL) {
99
+ const url = baseURL || "https://api.openai.com/v1/models";
100
+ const response = await fetchWithTimeout(url, {
101
+ method: "GET",
102
+ headers: {
103
+ Authorization: `Bearer ${apiKey}`,
104
+ },
105
+ });
106
+ if (!response.ok) {
107
+ throw new Error(`OpenAI API error: ${response.status}`);
108
+ }
109
+ const data = (await response.json());
110
+ if (!data.data || !Array.isArray(data.data)) {
111
+ return [];
112
+ }
113
+ return data.data.map((model) => model.id);
114
+ }
115
+ /**
116
+ * List Anthropic models
117
+ * Note: Anthropic does not provide a models endpoint
118
+ */
119
+ async function listAnthropicModels() {
120
+ console.warn("TokenFirewall: Anthropic does not provide a model listing API");
121
+ return [
122
+ "claude-3-5-sonnet-20241022",
123
+ "claude-3-5-haiku-20241022",
124
+ "claude-3-opus-20240229",
125
+ "claude-3-sonnet-20240229",
126
+ "claude-3-haiku-20240307",
127
+ ];
128
+ }
129
+ /**
130
+ * List Gemini models
131
+ */
132
+ async function listGeminiModels(apiKey, baseURL) {
133
+ const url = baseURL || `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`;
134
+ const response = await fetchWithTimeout(url, {
135
+ method: "GET",
136
+ });
137
+ if (!response.ok) {
138
+ throw new Error(`Gemini API error: ${response.status}`);
139
+ }
140
+ const data = (await response.json());
141
+ if (!data.models || !Array.isArray(data.models)) {
142
+ return [];
143
+ }
144
+ return data.models
145
+ .filter((model) => model.name.includes("gemini"))
146
+ .map((model) => {
147
+ const parts = model.name.split("/");
148
+ return parts[parts.length - 1];
149
+ });
150
+ }
151
+ /**
152
+ * List Grok models
153
+ */
154
+ async function listGrokModels(apiKey, baseURL) {
155
+ const url = baseURL || "https://api.x.ai/v1/models";
156
+ const response = await fetchWithTimeout(url, {
157
+ method: "GET",
158
+ headers: {
159
+ Authorization: `Bearer ${apiKey}`,
160
+ },
161
+ });
162
+ if (!response.ok) {
163
+ throw new Error(`Grok API error: ${response.status}`);
164
+ }
165
+ const data = (await response.json());
166
+ if (!data.data || !Array.isArray(data.data)) {
167
+ return [];
168
+ }
169
+ return data.data.map((model) => model.id);
170
+ }
171
+ /**
172
+ * List Kimi models
173
+ */
174
+ async function listKimiModels(apiKey, baseURL) {
175
+ const url = baseURL || "https://api.moonshot.cn/v1/models";
176
+ const response = await fetchWithTimeout(url, {
177
+ method: "GET",
178
+ headers: {
179
+ Authorization: `Bearer ${apiKey}`,
180
+ },
181
+ });
182
+ if (!response.ok) {
183
+ throw new Error(`Kimi API error: ${response.status}`);
184
+ }
185
+ const data = (await response.json());
186
+ if (!data.data || !Array.isArray(data.data)) {
187
+ return [];
188
+ }
189
+ return data.data.map((model) => model.id);
190
+ }
@@ -0,0 +1,11 @@
1
+ import { NormalizedUsage, CostBreakdown } from "./core/types";
2
+ /**
3
+ * Structured logger for LLM usage and costs
4
+ */
5
+ export declare class Logger {
6
+ /**
7
+ * Log usage and cost information
8
+ */
9
+ logUsage(usage: NormalizedUsage, cost: CostBreakdown): void;
10
+ }
11
+ export declare const logger: Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = exports.Logger = void 0;
4
+ /**
5
+ * Structured logger for LLM usage and costs
6
+ */
7
+ class Logger {
8
+ /**
9
+ * Log usage and cost information
10
+ */
11
+ logUsage(usage, cost) {
12
+ const timestamp = new Date().toISOString();
13
+ console.log(JSON.stringify({
14
+ timestamp,
15
+ provider: usage.provider,
16
+ model: usage.model,
17
+ tokens: {
18
+ input: usage.inputTokens,
19
+ output: usage.outputTokens,
20
+ total: usage.totalTokens,
21
+ },
22
+ cost: {
23
+ input: cost.inputCost.toFixed(6),
24
+ output: cost.outputCost.toFixed(6),
25
+ total: cost.totalCost.toFixed(6),
26
+ },
27
+ }));
28
+ }
29
+ }
30
+ exports.Logger = Logger;
31
+ exports.logger = new Logger();
@@ -0,0 +1,18 @@
1
+ import { ProviderAdapter, NormalizedUsage } from "./core/types";
2
+ /**
3
+ * Adapter registry - manages provider detection and normalization
4
+ */
5
+ declare class AdapterRegistry {
6
+ private adapters;
7
+ /**
8
+ * Register a custom adapter
9
+ */
10
+ register(adapter: ProviderAdapter): void;
11
+ /**
12
+ * Detect and normalize a response using registered adapters
13
+ * @returns NormalizedUsage or null if no adapter matches
14
+ */
15
+ process(response: unknown, request?: unknown): NormalizedUsage | null;
16
+ }
17
+ export declare const adapterRegistry: AdapterRegistry;
18
+ export {};
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.adapterRegistry = void 0;
4
+ const adapters_1 = require("./adapters");
5
+ /**
6
+ * Adapter registry - manages provider detection and normalization
7
+ */
8
+ class AdapterRegistry {
9
+ constructor() {
10
+ this.adapters = [...adapters_1.adapters];
11
+ }
12
+ /**
13
+ * Register a custom adapter
14
+ */
15
+ register(adapter) {
16
+ this.adapters.push(adapter);
17
+ }
18
+ /**
19
+ * Detect and normalize a response using registered adapters
20
+ * @returns NormalizedUsage or null if no adapter matches
21
+ */
22
+ process(response, request) {
23
+ for (const adapter of this.adapters) {
24
+ try {
25
+ if (adapter.detect(response)) {
26
+ return adapter.normalize(response, request);
27
+ }
28
+ }
29
+ catch (error) {
30
+ // If normalize throws, log warning and try next adapter
31
+ console.warn(`TokenFirewall: Adapter "${adapter.name}" failed to normalize response:`, error);
32
+ continue;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ }
38
+ exports.adapterRegistry = new AdapterRegistry();
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "tokenfirewall",
3
+ "version": "1.0.1",
4
+ "description": "Scalable, adapter-driven LLM cost enforcement middleware for Node.js with model discovery and context intelligence",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "llm",
13
+ "cost",
14
+ "budget",
15
+ "openai",
16
+ "anthropic",
17
+ "gemini",
18
+ "grok",
19
+ "middleware",
20
+ "token-tracking",
21
+ "cost-control",
22
+ "model-discovery"
23
+ ],
24
+ "author": "Ruthwik",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/Ruthwik000/tokenfirewall.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/Ruthwik000/tokenfirewall/issues"
32
+ },
33
+ "homepage": "https://github.com/Ruthwik000/tokenfirewall#readme",
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "typescript": "^5.0.0"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "engines": {
44
+ "node": ">=16.0.0"
45
+ }
46
+ }