token-budget-guard 1.0.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/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # token-budget-guard
2
+
3
+ A small utility to enforce token budgets for AI API calls.
4
+
5
+ ## Why
6
+ Tokens affect cost, latency, and reliability.
7
+ This utility makes token usage explicit and enforceable.
8
+
9
+ ## Features
10
+ - token estimation
11
+ - budget enforcement
12
+ - fail-fast / trim / warn strategies
13
+
14
+ ## Token estimation
15
+ Uses a rough heuristic (~4 chars/token). Counts may differ from model-specific tokenizers,
16
+ especially for non-English text or code/JSON.
17
+
18
+ ## Install
19
+ ```bash
20
+ npm install token-budget-guard
21
+ ```
22
+
23
+ ## Usage
24
+ ```ts
25
+ import { withTokenBudget } from "token-budget-guard";
26
+
27
+ await withTokenBudget({
28
+ model: "gpt-4",
29
+ maxTokens: 8000,
30
+ prompt,
31
+ context,
32
+ expectedOutputTokens: 500,
33
+ strategy: "trim_context",
34
+ call: async ({ prompt, context }) => {
35
+ return client.responses.create({
36
+ model: "gpt-4",
37
+ input: [{ role: "user", content: [prompt, ...context] }],
38
+ });
39
+ },
40
+ });
41
+ ```
@@ -0,0 +1,6 @@
1
+ export declare function calculateTokenUsage(prompt: string, context?: string[], expectedOutputTokens?: number): {
2
+ promptTokens: number;
3
+ contextTokens: number;
4
+ expectedOutputTokens: number;
5
+ totalTokens: number;
6
+ };
package/dist/budget.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateTokenUsage = calculateTokenUsage;
4
+ const tokenizer_1 = require("./tokenizer");
5
+ function calculateTokenUsage(prompt, context = [], expectedOutputTokens = 0) {
6
+ const promptTokens = (0, tokenizer_1.estimateTokens)(prompt);
7
+ const contextTokens = context.reduce((sum, c) => sum + (0, tokenizer_1.estimateTokens)(c), 0);
8
+ return {
9
+ promptTokens,
10
+ contextTokens,
11
+ expectedOutputTokens,
12
+ totalTokens: promptTokens + contextTokens + expectedOutputTokens,
13
+ };
14
+ }
@@ -0,0 +1,2 @@
1
+ import { TokenBudgetOptions } from "./types";
2
+ export declare function withTokenBudget<T>(opts: TokenBudgetOptions<T>): Promise<T>;
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTokenBudget = withTokenBudget;
4
+ const budget_1 = require("./budget");
5
+ const tokenizer_1 = require("./tokenizer");
6
+ const strategies_1 = require("./strategies");
7
+ async function withTokenBudget(opts) {
8
+ const { prompt, context = [], expectedOutputTokens = 0, maxTokens, strategy = "fail_fast", call, } = opts;
9
+ const usage = (0, budget_1.calculateTokenUsage)(prompt, context, expectedOutputTokens);
10
+ if (usage.totalTokens <= maxTokens) {
11
+ return call({ prompt, context, expectedOutputTokens });
12
+ }
13
+ if (strategy === "warn_only") {
14
+ console.warn("[token-budget] exceeded", usage);
15
+ return call({ prompt, context, expectedOutputTokens });
16
+ }
17
+ if (strategy === "trim_context") {
18
+ const remaining = maxTokens - usage.promptTokens - expectedOutputTokens;
19
+ if (remaining <= 0) {
20
+ throw new Error("Token budget exceeded: no room for context");
21
+ }
22
+ const trimmedContext = (0, strategies_1.trimContext)(context, remaining, tokenizer_1.estimateTokens);
23
+ const trimmedUsage = (0, budget_1.calculateTokenUsage)(prompt, trimmedContext, expectedOutputTokens);
24
+ if (trimmedUsage.totalTokens > maxTokens) {
25
+ throw new Error(`Token budget exceeded (${trimmedUsage.totalTokens}/${maxTokens})`);
26
+ }
27
+ return call({
28
+ prompt,
29
+ context: trimmedContext,
30
+ expectedOutputTokens,
31
+ });
32
+ }
33
+ throw new Error(`Token budget exceeded (${usage.totalTokens}/${maxTokens})`);
34
+ }
@@ -0,0 +1 @@
1
+ export declare function trimContext(context: string[], maxContextTokens: number, estimate: (text: string) => number): string[];
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trimContext = trimContext;
4
+ function trimContext(context, maxContextTokens, estimate) {
5
+ let tokens = 0;
6
+ const trimmed = [];
7
+ for (let i = context.length - 1; i >= 0; i--) {
8
+ const t = estimate(context[i]);
9
+ if (tokens + t > maxContextTokens)
10
+ break;
11
+ tokens += t;
12
+ trimmed.unshift(context[i]);
13
+ }
14
+ return trimmed;
15
+ }
@@ -0,0 +1 @@
1
+ export declare function estimateTokens(text: string): number;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.estimateTokens = estimateTokens;
4
+ function estimateTokens(text) {
5
+ // Rough heuristic: ~4 chars per token
6
+ return Math.ceil(text.length / 4);
7
+ }
@@ -0,0 +1,14 @@
1
+ export type Strategy = "fail_fast" | "trim_context" | "warn_only";
2
+ export interface TokenBudgetOptions<T> {
3
+ model: string;
4
+ maxTokens: number;
5
+ prompt: string;
6
+ context?: string[];
7
+ expectedOutputTokens?: number;
8
+ strategy?: Strategy;
9
+ call: (payload: {
10
+ prompt: string;
11
+ context: string[];
12
+ expectedOutputTokens: number;
13
+ }) => Promise<T>;
14
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "token-budget-guard",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "prepublishOnly": "npm run build"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [],
14
+ "author": "Mostafa Hanafy",
15
+ "license": "MIT",
16
+ "devDependencies": {
17
+ "@types/node": "^25.2.2",
18
+ "ts-node": "^10.9.2",
19
+ "typescript": "^5.9.3"
20
+ },
21
+ "dependencies": {
22
+ "tsx": "^4.21.0"
23
+ }
24
+ }