@salimassili/ai-costguard 1.2.0 → 2.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 (76) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +415 -177
  4. package/benchmarks/run.mjs +229 -0
  5. package/benchmarks/token-accuracy.mjs +86 -0
  6. package/dist/cli.d.ts +50 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +178 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/CostGuard.d.ts +4 -5
  11. package/dist/core/CostGuard.d.ts.map +1 -1
  12. package/dist/core/CostGuard.js +2 -3
  13. package/dist/core/CostGuard.js.map +1 -1
  14. package/dist/core/GuardCore.d.ts +93 -13
  15. package/dist/core/GuardCore.d.ts.map +1 -1
  16. package/dist/core/GuardCore.js +372 -158
  17. package/dist/core/GuardCore.js.map +1 -1
  18. package/dist/core/GuardFree.d.ts +42 -18
  19. package/dist/core/GuardFree.d.ts.map +1 -1
  20. package/dist/core/GuardFree.js +95 -140
  21. package/dist/core/GuardFree.js.map +1 -1
  22. package/dist/core/GuardPro.d.ts +76 -8
  23. package/dist/core/GuardPro.d.ts.map +1 -1
  24. package/dist/core/GuardPro.js +213 -130
  25. package/dist/core/GuardPro.js.map +1 -1
  26. package/dist/core/event-log.d.ts +37 -0
  27. package/dist/core/event-log.d.ts.map +1 -0
  28. package/dist/core/event-log.js +49 -0
  29. package/dist/core/event-log.js.map +1 -0
  30. package/dist/core/events.d.ts +20 -0
  31. package/dist/core/events.d.ts.map +1 -0
  32. package/dist/core/events.js +46 -0
  33. package/dist/core/events.js.map +1 -0
  34. package/dist/core/similarity.d.ts +13 -0
  35. package/dist/core/similarity.d.ts.map +1 -0
  36. package/dist/core/similarity.js +51 -0
  37. package/dist/core/similarity.js.map +1 -0
  38. package/dist/core/tokenizer.d.ts +18 -0
  39. package/dist/core/tokenizer.d.ts.map +1 -0
  40. package/dist/core/tokenizer.js +137 -0
  41. package/dist/core/tokenizer.js.map +1 -0
  42. package/dist/core/types.d.ts +151 -5
  43. package/dist/core/types.d.ts.map +1 -1
  44. package/dist/core/types.js +0 -3
  45. package/dist/core/types.js.map +1 -1
  46. package/dist/core/webhooks.d.ts +15 -0
  47. package/dist/core/webhooks.d.ts.map +1 -0
  48. package/dist/core/webhooks.js +58 -0
  49. package/dist/core/webhooks.js.map +1 -0
  50. package/dist/dashboard.d.ts +73 -0
  51. package/dist/dashboard.d.ts.map +1 -0
  52. package/dist/dashboard.js +201 -0
  53. package/dist/dashboard.js.map +1 -0
  54. package/dist/index.d.ts +4 -5
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/pricing/index.d.ts +26 -2
  59. package/dist/pricing/index.d.ts.map +1 -1
  60. package/dist/pricing/index.js +100 -13
  61. package/dist/pricing/index.js.map +1 -1
  62. package/dist/pro.d.ts +3 -0
  63. package/dist/pro.d.ts.map +1 -0
  64. package/dist/pro.js +2 -0
  65. package/dist/pro.js.map +1 -0
  66. package/docs/BENCHMARKS.md +70 -0
  67. package/docs/DASHBOARD.md +61 -0
  68. package/docs/INTEGRATIONS.md +153 -0
  69. package/examples/integrations/anthropic-workflow-budget.mjs +36 -0
  70. package/examples/integrations/ci-budget-check.mjs +32 -0
  71. package/examples/integrations/crewai-budget-gate.mjs +31 -0
  72. package/examples/integrations/langchain-retry-storm.mjs +32 -0
  73. package/examples/integrations/mastra-agent.mjs +41 -0
  74. package/examples/integrations/openai-agent-loop.mjs +44 -0
  75. package/examples/integrations/vercel-ai-chatbot.mjs +29 -0
  76. package/package.json +76 -46
@@ -0,0 +1,229 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { guardFunction } from '../dist/index.js';
3
+ import { GuardCore, GuardError } from '../dist/core/GuardCore.js';
4
+ import { estimateRequestTokens } from '../dist/core/tokenizer.js';
5
+
6
+ const iterations = readIterations(process.argv.slice(2));
7
+
8
+ const result = {
9
+ generatedAt: new Date().toISOString(),
10
+ node: process.version,
11
+ platform: process.platform,
12
+ iterations,
13
+ runtimeOverhead: await measureRuntimeOverhead(iterations),
14
+ memoryOverhead: await measureMemoryOverhead(Math.max(250, Math.min(iterations, 2000))),
15
+ falsePositiveScenarios: measureFalsePositiveScenarios(),
16
+ loopDetectionBehavior: measureLoopDetectionBehavior(),
17
+ costEstimationBoundaries: measureCostEstimationBoundaries(),
18
+ };
19
+
20
+ console.log(JSON.stringify(result, null, 2));
21
+
22
+ async function measureRuntimeOverhead(count) {
23
+ async function directCall(request) {
24
+ return { ok: true, usage: { prompt_tokens: 12, completion_tokens: request.max_tokens ?? 8 } };
25
+ }
26
+
27
+ const guardedCall = guardFunction(directCall, {
28
+ budget: 1_000,
29
+ behaviorAnalysis: false,
30
+ scope: { projectId: 'benchmark' },
31
+ });
32
+
33
+ const request = {
34
+ model: 'gpt-4o-mini',
35
+ prompt: 'benchmark request',
36
+ max_tokens: 8,
37
+ };
38
+
39
+ await warmup(directCall, guardedCall, request);
40
+
41
+ const directMs = await timeAsync(count, () => directCall(request));
42
+ const guardedMs = await timeAsync(count, () => guardedCall(request));
43
+ const directPerCallMs = directMs / count;
44
+ const guardedPerCallMs = guardedMs / count;
45
+
46
+ return {
47
+ directTotalMs: round(directMs, 3),
48
+ guardedTotalMs: round(guardedMs, 3),
49
+ directPerCallMs: round(directPerCallMs, 6),
50
+ guardedPerCallMs: round(guardedPerCallMs, 6),
51
+ addedPerCallMs: round(guardedPerCallMs - directPerCallMs, 6),
52
+ };
53
+ }
54
+
55
+ async function measureMemoryOverhead(count) {
56
+ const guardedCall = guardFunction(
57
+ async (request) => ({ ok: true, usage: { prompt_tokens: 12, completion_tokens: request.max_tokens ?? 8 } }),
58
+ {
59
+ budget: 1_000,
60
+ maxHistory: count,
61
+ loopMinRepeats: count + 1,
62
+ scope: { projectId: 'memory-benchmark' },
63
+ }
64
+ );
65
+
66
+ collectGarbageIfAvailable();
67
+ const beforeBytes = process.memoryUsage().heapUsed;
68
+
69
+ for (let index = 0; index < count; index++) {
70
+ await guardedCall({
71
+ model: 'gpt-4o-mini',
72
+ prompt: `memory benchmark unique prompt ${index}`,
73
+ max_tokens: 8,
74
+ });
75
+ }
76
+
77
+ collectGarbageIfAvailable();
78
+ const afterBytes = process.memoryUsage().heapUsed;
79
+
80
+ return {
81
+ calls: count,
82
+ heapDeltaBytes: afterBytes - beforeBytes,
83
+ heapDeltaPerCallBytes: round((afterBytes - beforeBytes) / count, 2),
84
+ gcAvailable: typeof globalThis.gc === 'function',
85
+ note: 'Heap measurements are process-local and noisy unless Node is run with --expose-gc.',
86
+ };
87
+ }
88
+
89
+ function measureFalsePositiveScenarios() {
90
+ const core = new GuardCore({
91
+ budget: 1,
92
+ retryThreshold: 2,
93
+ loopSimilarityThreshold: 0.9,
94
+ scope: { projectId: 'false-positive-benchmark' },
95
+ });
96
+ const prompts = [
97
+ 'again compare the two product options',
98
+ 'again summarize the second option with different tradeoffs',
99
+ 'again write a new title for the launch note',
100
+ ];
101
+ let blocked = 0;
102
+
103
+ for (const prompt of prompts) {
104
+ try {
105
+ core.check(context(prompt));
106
+ } catch (error) {
107
+ if (error instanceof GuardError) blocked += 1;
108
+ else throw error;
109
+ }
110
+ }
111
+
112
+ return {
113
+ scenario: 'Repeated benign "again" prompts without failure/error language',
114
+ prompts: prompts.length,
115
+ blocked,
116
+ };
117
+ }
118
+
119
+ function measureLoopDetectionBehavior() {
120
+ const core = new GuardCore({
121
+ budget: 1,
122
+ loopSimilarityThreshold: 0.9,
123
+ loopMinRepeats: 2,
124
+ scope: { projectId: 'loop-benchmark' },
125
+ });
126
+ const prompt = 'summarize the same tool observation and continue the agent plan';
127
+
128
+ for (let step = 1; step <= 5; step++) {
129
+ try {
130
+ core.check(context(prompt));
131
+ } catch (error) {
132
+ if (error instanceof GuardError) {
133
+ return {
134
+ repeatedPrompt: prompt,
135
+ blockedAtStep: step,
136
+ code: error.code,
137
+ reason: error.message,
138
+ };
139
+ }
140
+
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ return {
146
+ repeatedPrompt: prompt,
147
+ blockedAtStep: null,
148
+ };
149
+ }
150
+
151
+ function measureCostEstimationBoundaries() {
152
+ const samples = [
153
+ { label: 'short chat', request: { messages: [{ role: 'user', content: 'hello' }], max_tokens: 32 } },
154
+ {
155
+ label: 'long instruction',
156
+ request: {
157
+ messages: [{ role: 'user', content: 'Summarize the following requirements. '.repeat(120) }],
158
+ max_tokens: 256,
159
+ },
160
+ },
161
+ {
162
+ label: 'code-heavy prompt',
163
+ request: {
164
+ prompt: 'function example(value) { return value.map((item) => item.id).join(","); }\n'.repeat(40),
165
+ max_tokens: 128,
166
+ },
167
+ },
168
+ ];
169
+
170
+ return {
171
+ tokenizer: 'dependency-free estimator',
172
+ doesNotClaimProviderExactness: true,
173
+ samples: samples.map((sample) => {
174
+ const estimate = estimateRequestTokens(sample.request);
175
+ return {
176
+ label: sample.label,
177
+ inputTokens: estimate.inputTokens,
178
+ outputTokens: estimate.outputTokens,
179
+ totalTokens: estimate.tokens,
180
+ };
181
+ }),
182
+ };
183
+ }
184
+
185
+ async function warmup(directCall, guardedCall, request) {
186
+ for (let index = 0; index < 100; index++) {
187
+ await directCall(request);
188
+ await guardedCall(request);
189
+ }
190
+ }
191
+
192
+ async function timeAsync(count, fn) {
193
+ const start = performance.now();
194
+ for (let index = 0; index < count; index++) {
195
+ await fn();
196
+ }
197
+ return performance.now() - start;
198
+ }
199
+
200
+ function context(prompt) {
201
+ return {
202
+ model: 'gpt-4o-mini',
203
+ pricingKnown: true,
204
+ tokens: 100,
205
+ inputTokens: 40,
206
+ outputTokens: 60,
207
+ estimatedCost: 0.0001,
208
+ timestamp: Date.now(),
209
+ prompt,
210
+ };
211
+ }
212
+
213
+ function collectGarbageIfAvailable() {
214
+ if (typeof globalThis.gc === 'function') {
215
+ globalThis.gc();
216
+ }
217
+ }
218
+
219
+ function readIterations(args) {
220
+ const index = args.indexOf('--iterations');
221
+ if (index === -1) return 5000;
222
+ const value = Number(args[index + 1]);
223
+ return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 5000;
224
+ }
225
+
226
+ function round(value, digits) {
227
+ const factor = 10 ** digits;
228
+ return Math.round(value * factor) / factor;
229
+ }
@@ -0,0 +1,86 @@
1
+ import { estimateTokensFromText } from '../dist/core/tokenizer.js';
2
+
3
+ const corpus = [
4
+ { label: 'support summary', text: 'Summarize this support ticket in two bullets.', referenceTokens: 9 },
5
+ {
6
+ label: 'agent instruction',
7
+ text: 'You are an agent. Use the search tool, then cite the result in JSON.',
8
+ referenceTokens: 16,
9
+ },
10
+ {
11
+ label: 'retry guard',
12
+ text: 'The database migration failed with timeout 504. Retry only if the previous step is idempotent.',
13
+ referenceTokens: 19,
14
+ },
15
+ {
16
+ label: 'typescript code',
17
+ text: 'function normalizeUser(user) { return { id: user.id, email: user.email?.toLowerCase() }; }',
18
+ referenceTokens: 24,
19
+ },
20
+ {
21
+ label: 'model comparison',
22
+ text: 'Compare Claude Haiku and GPT-4o mini for a budget-sensitive chatbot.',
23
+ referenceTokens: 17,
24
+ },
25
+ {
26
+ label: 'customer apology',
27
+ text: 'Write a concise customer apology for a delayed shipment and include one next step.',
28
+ referenceTokens: 16,
29
+ },
30
+ {
31
+ label: 'log line',
32
+ text: 'Analyze these logs: ERROR rate_limit_exceeded request_id=req_123 retry_after=2s',
33
+ referenceTokens: 24,
34
+ },
35
+ {
36
+ label: 'interface request',
37
+ text: 'Create a TypeScript interface for a webhook payload with cost, model, and reason fields.',
38
+ referenceTokens: 17,
39
+ },
40
+ ];
41
+
42
+ const samples = corpus.map((sample) => {
43
+ const estimatedTokens = estimateTokensFromText(sample.text);
44
+ const absoluteError = Math.abs(estimatedTokens - sample.referenceTokens);
45
+ const percentError = (absoluteError / sample.referenceTokens) * 100;
46
+
47
+ return {
48
+ label: sample.label,
49
+ estimatedTokens,
50
+ referenceTokens: sample.referenceTokens,
51
+ absoluteError,
52
+ percentError: round(percentError, 2),
53
+ };
54
+ });
55
+
56
+ const percentErrors = samples.map((sample) => sample.percentError).sort((a, b) => a - b);
57
+ const report = {
58
+ generatedAt: new Date().toISOString(),
59
+ reference: {
60
+ tokenizer: 'gpt-tokenizer cl100k_base fixture counts',
61
+ note:
62
+ 'Reference counts are fixed corpus fixtures, not live provider calls. Use this to understand estimator bias, not to claim exact tokenizer parity.',
63
+ },
64
+ sampleCount: samples.length,
65
+ averageErrorPercent: round(average(percentErrors), 2),
66
+ medianErrorPercent: round(median(percentErrors), 2),
67
+ maxErrorPercent: Math.max(...percentErrors),
68
+ samples,
69
+ };
70
+
71
+ console.log(JSON.stringify(report, null, 2));
72
+
73
+ function average(values) {
74
+ return values.reduce((total, value) => total + value, 0) / values.length;
75
+ }
76
+
77
+ function median(values) {
78
+ const midpoint = Math.floor(values.length / 2);
79
+ if (values.length % 2 === 1) return values[midpoint];
80
+ return (values[midpoint - 1] + values[midpoint]) / 2;
81
+ }
82
+
83
+ function round(value, digits) {
84
+ const factor = 10 ** digits;
85
+ return Math.round(value * factor) / factor;
86
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { type DashboardOptions } from './dashboard.js';
3
+ /**
4
+ * IO streams used by the CLI runner.
5
+ */
6
+ export interface CliIO {
7
+ /** Writes standard output. */
8
+ stdout(message: string): void;
9
+ /** Writes standard error. */
10
+ stderr(message: string): void;
11
+ }
12
+ /**
13
+ * Parsed options for the aifw check command.
14
+ */
15
+ export interface CliCheckOptions {
16
+ /** Budget in USD. */
17
+ budget: number;
18
+ /** Model name used for pricing. */
19
+ model: string;
20
+ /** Estimated output tokens per step. */
21
+ tokens: number;
22
+ /** Estimated input tokens per step. Defaults to 0. */
23
+ inputTokens: number;
24
+ /** Maximum number of steps to budget for. */
25
+ maxSteps: number;
26
+ /** Custom input price in USD per 1,000 tokens. */
27
+ inputPricePer1k?: number;
28
+ /** Custom output price in USD per 1,000 tokens. */
29
+ outputPricePer1k?: number;
30
+ }
31
+ /**
32
+ * Parsed options for the local dashboard command.
33
+ */
34
+ export interface CliDashboardOptions extends DashboardOptions {
35
+ once: boolean;
36
+ json: boolean;
37
+ }
38
+ /**
39
+ * Parses arguments for `aifw check`.
40
+ */
41
+ export declare function parseCheckArgs(args: readonly string[]): CliCheckOptions;
42
+ /**
43
+ * Parses arguments for `aifw dashboard`.
44
+ */
45
+ export declare function parseDashboardArgs(args: readonly string[]): CliDashboardOptions;
46
+ /**
47
+ * Runs the aifw CLI and returns a process exit code.
48
+ */
49
+ export declare function runCli(args?: readonly string[], io?: CliIO): number;
50
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAGA,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,8BAA8B;IAC9B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6BAA6B;IAC7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAC3D,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,CA0BvE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,mBAAmB,CAgC/E;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,IAAI,GAAE,SAAS,MAAM,EAA0B,EAAE,EAAE,GAAE,KAAiB,GAAG,MAAM,CA4ErG"}
package/dist/cli.js ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ import { getPricing } from './pricing/index.js';
3
+ import { formatDashboardSummary, getDefaultEventLogPath, startDashboardServer, summarizeDashboard, } from './dashboard.js';
4
+ /**
5
+ * Parses arguments for `aifw check`.
6
+ */
7
+ export function parseCheckArgs(args) {
8
+ const options = new Map();
9
+ for (let index = 0; index < args.length; index++) {
10
+ const arg = args[index];
11
+ if (!arg.startsWith('--'))
12
+ continue;
13
+ const key = arg.slice(2);
14
+ const value = args[index + 1];
15
+ if (!value || value.startsWith('--')) {
16
+ throw new Error(`Missing value for --${key}`);
17
+ }
18
+ options.set(key, value);
19
+ index += 1;
20
+ }
21
+ return {
22
+ budget: readRequiredNumber(options, 'budget'),
23
+ model: readRequiredString(options, 'model'),
24
+ tokens: readRequiredNumber(options, 'tokens'),
25
+ inputTokens: readOptionalNumber(options, 'input-tokens') ?? 0,
26
+ maxSteps: Math.max(1, Math.trunc(readOptionalNumber(options, 'max-steps') ?? 1)),
27
+ inputPricePer1k: readOptionalNumber(options, 'input-price-per-1k'),
28
+ outputPricePer1k: readOptionalNumber(options, 'output-price-per-1k'),
29
+ };
30
+ }
31
+ /**
32
+ * Parses arguments for `aifw dashboard`.
33
+ */
34
+ export function parseDashboardArgs(args) {
35
+ const options = new Map();
36
+ const flags = new Set();
37
+ for (let index = 0; index < args.length; index++) {
38
+ const arg = args[index];
39
+ if (!arg.startsWith('--'))
40
+ continue;
41
+ const key = arg.slice(2);
42
+ if (key === 'once' || key === 'json') {
43
+ flags.add(key);
44
+ continue;
45
+ }
46
+ const value = args[index + 1];
47
+ if (!value || value.startsWith('--')) {
48
+ throw new Error(`Missing value for --${key}`);
49
+ }
50
+ options.set(key, value);
51
+ index += 1;
52
+ }
53
+ return {
54
+ eventLogPath: readOptionalString(options, 'events') ?? getDefaultEventLogPath(),
55
+ budgetUsd: readOptionalNumber(options, 'budget'),
56
+ host: readOptionalString(options, 'host'),
57
+ port: readOptionalNumber(options, 'port'),
58
+ recentLimit: readOptionalNumber(options, 'recent'),
59
+ once: flags.has('once'),
60
+ json: flags.has('json'),
61
+ };
62
+ }
63
+ /**
64
+ * Runs the aifw CLI and returns a process exit code.
65
+ */
66
+ export function runCli(args = process.argv.slice(2), io = defaultIO) {
67
+ const command = args[0];
68
+ if (!command || command === '--help' || command === '-h') {
69
+ io.stdout(helpText());
70
+ return 0;
71
+ }
72
+ if (command !== 'check' && command !== 'dashboard') {
73
+ io.stderr(`Unknown command: ${command}\n\n${helpText()}`);
74
+ return 2;
75
+ }
76
+ try {
77
+ if (command === 'dashboard') {
78
+ const options = parseDashboardArgs(args.slice(1));
79
+ if (options.once || options.json) {
80
+ const summary = summarizeDashboard(options);
81
+ io.stdout(options.json ? JSON.stringify(summary, null, 2) + '\n' : formatDashboardSummary(summary));
82
+ return 0;
83
+ }
84
+ void startDashboardServer(options)
85
+ .then(({ url }) => {
86
+ io.stdout(`AI CostGuard dashboard running at ${url}\nReading events from ${options.eventLogPath}\n`);
87
+ })
88
+ .catch((error) => {
89
+ io.stderr(`${error instanceof Error ? error.message : String(error)}\n`);
90
+ process.exitCode = 2;
91
+ });
92
+ return 0;
93
+ }
94
+ const options = parseCheckArgs(args.slice(1));
95
+ const pricing = getPricing(options.model);
96
+ const hasCustomPricing = options.inputPricePer1k !== undefined || options.outputPricePer1k !== undefined;
97
+ if (hasCustomPricing && (options.inputPricePer1k === undefined || options.outputPricePer1k === undefined)) {
98
+ io.stderr('--input-price-per-1k and --output-price-per-1k must be provided together.\n');
99
+ return 2;
100
+ }
101
+ if (!pricing && !hasCustomPricing) {
102
+ io.stderr(`No pricing found for model "${options.model}". Use a known model or pass --input-price-per-1k and --output-price-per-1k.\n`);
103
+ return 2;
104
+ }
105
+ const inputPer1kTokens = options.inputPricePer1k ?? pricing?.inputPer1kTokens ?? 0;
106
+ const outputPer1kTokens = options.outputPricePer1k ?? pricing?.outputPer1kTokens ?? 0;
107
+ const perStepCost = (options.inputTokens / 1000) * inputPer1kTokens + (options.tokens / 1000) * outputPer1kTokens;
108
+ const projectedCost = perStepCost * options.maxSteps;
109
+ const ok = projectedCost <= options.budget;
110
+ io.stdout(JSON.stringify({
111
+ ok,
112
+ model: options.model,
113
+ inputTokensPerStep: options.inputTokens,
114
+ outputTokensPerStep: options.tokens,
115
+ maxSteps: options.maxSteps,
116
+ estimatedCostUsd: roundMoney(projectedCost),
117
+ budgetUsd: options.budget,
118
+ }, null, 2) + '\n');
119
+ return ok ? 0 : 1;
120
+ }
121
+ catch (error) {
122
+ io.stderr(`${error instanceof Error ? error.message : String(error)}\n\n${helpText()}`);
123
+ return 2;
124
+ }
125
+ }
126
+ const defaultIO = {
127
+ stdout: (message) => process.stdout.write(message),
128
+ stderr: (message) => process.stderr.write(message),
129
+ };
130
+ if (isCliEntry()) {
131
+ process.exitCode = runCli();
132
+ }
133
+ function readRequiredNumber(options, key) {
134
+ const value = Number(options.get(key));
135
+ if (!Number.isFinite(value) || value < 0) {
136
+ throw new Error(`--${key} must be a non-negative number`);
137
+ }
138
+ return value;
139
+ }
140
+ function readOptionalNumber(options, key) {
141
+ if (!options.has(key))
142
+ return undefined;
143
+ return readRequiredNumber(options, key);
144
+ }
145
+ function readRequiredString(options, key) {
146
+ const value = options.get(key);
147
+ if (!value?.trim()) {
148
+ throw new Error(`--${key} is required`);
149
+ }
150
+ return value;
151
+ }
152
+ function roundMoney(value) {
153
+ return Math.round(value * 1_000_000) / 1_000_000;
154
+ }
155
+ function helpText() {
156
+ return [
157
+ 'Usage:',
158
+ ' aifw check --budget <usd> --model <model> --tokens <output-tokens> --max-steps <n>',
159
+ ' aifw dashboard --events .ai-costguard/events.jsonl --budget <usd>',
160
+ ' ai-costguard dashboard --once --json',
161
+ '',
162
+ 'Notes:',
163
+ ' --tokens is per-step output tokens. Use --input-tokens for input token estimates.',
164
+ ' For custom models, pass --input-price-per-1k and --output-price-per-1k.',
165
+ ' The dashboard is local-only and reads an opt-in JSONL event log.',
166
+ ' Exit code 0 means projected cost is within budget; 1 means over budget; 2 means usage/config error.',
167
+ '',
168
+ ].join('\n');
169
+ }
170
+ function readOptionalString(options, key) {
171
+ const value = options.get(key);
172
+ return value?.trim() ? value.trim() : undefined;
173
+ }
174
+ function isCliEntry() {
175
+ const entry = process.argv[1] ?? '';
176
+ return /(^|[/\\])cli\.js$/u.test(entry);
177
+ }
178
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,6 @@
1
- export { guard, GuardError, middleware } from './GuardFree.js';
2
- export { GuardPro, validateLicense, getProGuard } from './GuardPro.js';
3
- export type { GuardProConfig } from './GuardPro.js';
4
- export { getPricing, registerPricing, listPricing } from '../pricing/index.js';
1
+ export { guard, guardFunction, GuardError, middleware } from './GuardFree.js';
2
+ export type { GuardedClient, GuardEventControls } from './GuardFree.js';
3
+ export { BUILTIN_PRICING_LAST_UPDATED, getPricing, registerPricing, listPricing } from '../pricing/index.js';
5
4
  export type { ModelPricing } from '../pricing/index.js';
6
- export type { GuardConfig } from './types.js';
5
+ export type { GuardConfig, GuardErrorCode, GuardEvent, GuardEventHandler, GuardEventName, GuardScope, GuardState, GuardWebhookConfig, RequestContext, } from './types.js';
7
6
  //# sourceMappingURL=CostGuard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CostGuard.d.ts","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"CostGuard.d.ts","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,4BAA4B,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC7G,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,GACf,MAAM,YAAY,CAAC"}
@@ -1,4 +1,3 @@
1
- export { guard, GuardError, middleware } from './GuardFree.js';
2
- export { GuardPro, validateLicense, getProGuard } from './GuardPro.js';
3
- export { getPricing, registerPricing, listPricing } from '../pricing/index.js';
1
+ export { guard, guardFunction, GuardError, middleware } from './GuardFree.js';
2
+ export { BUILTIN_PRICING_LAST_UPDATED, getPricing, registerPricing, listPricing } from '../pricing/index.js';
4
3
  //# sourceMappingURL=CostGuard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CostGuard.js","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"CostGuard.js","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,EAAE,4BAA4B,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC"}