@tktideai/ai-api-cost-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.
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createEmailNotifier = createEmailNotifier;
7
+ const nodemailer_1 = __importDefault(require("nodemailer"));
8
+ const errors_1 = require("../errors");
9
+ // ─── TKTIDE logo — base64 embedded, no hosting needed ────────────────────
10
+ const TKTIDE_LOGO_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAdaklEQVR4nO1d+XtURdbOX+WMoobusAdXJJ0mENYQCEtCSAIBsoCKOq6oDIz7hrgBggsqiriAAh0FBEQdR2c+v5lx5lNA1BHBqe95z711u+7tzr3V6aq+S9cP7xPAmNyueu+pU+e855ya9KQsMzBrkE7IGtSE/QAGZg3ShtCGBMYQZI2FNiTIJn4NjMsRgU0wyBpCGxKYFyFtLLQhQTrha2BcjghsgkHWENqQwLwIaWOhDQnSCV8D43JEYBMMsobQhgTmRUgbC21IkE74GhiXIwKbYJA1hDYkMC9C2lhoQ4J0wtfAuBwR2ASDrCG0IYF5EdLGQhsSpBO+BsbliMAmGGQNoQ0JzIuQNhbakCCd8DWItcuRmtgY+jMYZCO1BjVxJ3NdfZbVjm9kV45rZLVBGO+P0X6YoAapYpg4clhrYa1J3WRrPar5RY8lofMb2cguT2fYpClN7OrsdHZVo40M0MQmAw151ANT85h0g4ApTWwix/XTCBM4rnNjPHBtcYy7xoOr3Rh7ddbCVXmMETE5jzoR9RbENSDYLwRelktrG9gVYzIOqauR2LEkNKE+S5u3blMbe+ebVWzf/6xi+/6Wx9vAXy3s/VrAV73sLY6/9LI3Ob60sAf4s4U3gC9ErGSvf14En61kr3lxaiXbXQyfrmSvcpwEVjh45UQhXj7uxkufrGAvHVvBdgFHe9hOG/jz5l3tLDt/JhuVypM69H0yhA5ehDqbzA1zm9nQD/3s2C8D7KMf+9nHPxXiSDH8PBwGCnBUxH/0AM8/IpwX8MsAO/nbIMud7WPtgy0WqSdXH6njaaFB6LEZNnV2M/vg32uI1IfP9NFmBuJMeTgcYRz6vo9e2EOn+2htRPcj9D0zhPZfBGzUZaMb2P3b29nJi4O0iSA2SIuv1QT+sg790E+kPnFxkD36xjLH9QibZIbQAYtAFqc+y64c28iuyU5n7/1jNfv4x/zGVjOpc/YpBFemdcUcdlmVuR7xdDlsUiMS8PsrG9jaPy6seistnk6Hz/SxT84PsJ1HeuxoR/j7ZQhdCrGvylL04OgvA7SZ1ep6DImkPm25Hr13trLf1zbQy18NVrom9mSenGWXjm5gSwfnkVUSL4fVTOjcGeuCiLAl4uxIHIW9X4bQsotgh/Gefm85O35hkKyTsdL9dFFGKO/OLUuqxkrH2kID2CDc5JExnLFolstSVbOVznkiH40t1RHGiz2hHVLD9ahtYPdvW2ouiD94fOkLg+zxtzrZqHTyw3iJITSF8cY1smumTWfvmzAeEy017hXIJC7snUux+ySH8RJB6IIw3qaF5DtWc7JlyBPGA6F3HeNhvGSSOVGEdog9oZEUbBAHIblgwnh9lh9th/FW372AXvqkXhATRWgnjFfbwDrWtrBPfjVhvJwQxoNYC6pESGmhDw97vwyhSwzjbd3fxY6D1Mb1YNxKwxW755kl9NIn0UonykKLYTzc6GcumcU+OufROVSxP50TfGropi+vS14YL3GEdkhtux6bXmxnJ/87yA5+l78gViOxc4IvjeTTE3s7KXaftDBeYgkNtwN+IkqyUMGCCxE2M2o4HAIOnbaiHm2rkhfGSyShXRnEugwdryhngj/96X8HAwGLDl+zbFy0cKIcXCiO4yPCAH2F1BYRIJRzOQYgAntmCF0CqXEBQuTjtscWsdufWMxuf3IxuwN4ajG7E9iyhHDX0xbu3moBFyrCs0vYhmeXsg3PWbj3eQv3vZAHspSE7UvZxu3tbOOOdvZH4EULm3ZaQO3f5l0dFl7qYH8CXu5gDwCvdLAHgVctPLR7mYOHX7Px+jL2CMcbFh7l2AN0sseANy08Drxl481OqptExAPk7tuQrDBeYi20l9T4ClXe765ooA0MRG0D+eC+GJ3HZX5IZQowyov08LhcRF3puAIYYwF/R3X6C4e76QSAO4YKeQrjJcCfTjyhOanxFb7imBJRVy7qo4N0vdVC4ZJRU9mKP7RSnB5uGE6epITxqoLQXmJXM8bY0R+4TfDFST9+po9Na01GGK+qCF3NSHkuyYh0IEbPw3hP7lvuEDrsZy0HhtBVhDqb0E++3WlZ59OCGu/8AFu0Zl7sw3iG0FWAFE80jW5gS/rnEXl5qZqoxkNnJuNyVGAzwiZEEtYkZQu30IIM5PWqELkab+C+hbEO49XEpcNo1CIGWhEQOSmF1ClBJ75uc1uBTtxR45211Hjv/u9qyq7yMF7cSF0TBzIjTgvfzjfemisMRKJZzZZXKgJPS6LXRBexNSOKfVCGz4boJOTF/1sklT1N9PlLgcACoojioIMTnH44/91KcNiQ4sbtCRFJTIqVRUoJzoUJdGvcbxXwcoySTb6wMJKsT1VT6L6Dujl4iOCFLZhMbFgTcJFI/Kinatt1PtuLmrbI0gbgKs/6q79NTZOS3RvtSn13jmQJeUXgOGYfy109ieP/dS9YrqZ4HBQY2nrshR2YSutW/CSIqoIXThRWZmwFwRMTaNoZkqGxbSVzuMh2QRkkaqNkNXS7RckWgPQqF+eg3xWW57dJGWZwGQoaQupppKvJSE7WClt/NZeBqsCyq8UQg70vZiKsN4SBrBZy/XSrv0GnOtZx7S9Mz3Bow6Fp8F6e2D3xXKfVU8CwwNpu769R0JndA8TduxtsW6zJwJ1//TrU6jUNO8ZiXdhHQlh3JCFyYaRp+x9Ro+7oauE851J/p2Dbt+xgypO1GoFhrgRbDHQ+6ACX8UfYz3fu3u96YyjBfUhFzKOtuGAAo/nXqNNRsk9Bq8PdtKte3ZSo1aRYLQYtyyeTGPW+qROt70gFwYj09Q1dWRE9EcGs9QxtEpaiR06TV2HS1Nr7HrmJ5nwUuCZjzIVeju96FOy2ETaeMOPZklHFkHnCMr+LiHpdbi19v6A3RXSk8qPYxXVCOhur/GGSF2LqnX6EN7Ng3aEe/ccN0SAmWE5tUjCOF5+0aostJY8M0BlwoxjDdv+fAVM2G0yxLX6RqPRkLVC3eI6zVeX2ZFEgL0Grw9G3xt+Nwqn4XHnNHeTZXeuSKE9r7tazepm8TktdQgwOyAdrMA/htI/dBuDZccsaFhiWE8eq7RDWyjQo2E+GwfnesnfXgpeg1U3uss8UJFS6Va8qqvKbR1HbvL0HUENwR3D3D3C0NhY7HBusJ4d28toeWsbZ1RNuZ9pnIJJD7TLY/I6zVw7ylGRFXPguqfSmrKtZRgwVdqHxy58s7vreexadQNyobxbntMZaLAvfF4HtTOyVggbhGhJFS1Nq7L188DlOFDpi9Qr4EXfmyGMoiqI1OuRBRKvCpYyqalSJZbR+iaVUUavLdmlO3ICG3w33WmlRHGe3xvp5OF83sW/oJBx8D7OqsgtBiv77xJXq8htmfTIRWAYKzSnZhqdHYbRbrVu/CVbF1VKeGPzLRX/qLjmEfql+oty3SBCjKqB7rI6sroNaz2bL3q3UI7qgGhWBhTs/R1TtJ84eBidfij2MSg417n6DMQGvFk/tmD1gZAD+ZyQ4rel6F5sb/mRXTD4GfrurgjstTSFU5LXm2E9ivhURmeeuAVuTAeQliqLGP5WbmGsrNy3tOK9wisk9Br6LwoYx0QWeJkToSF1tmg26vGk1FviUTCC6ArjOftcyE11m4EGg4xJOboNRqn+46zcH5vKkM1hVr0GiWemrEitJdMr5zUkFa19bWYp+JHIm6dkGGc0jyDHfiXvjBe0OQolzJwbmFXJJln8VrEPtmTIZWh7KFOvUbYLXkr0/C8zALaIFLjsodLn2wYD4uuK5GAz4dWDn5hPPFZ7ihxzmCB736sNN9955Ee0nno0Gu8XkKJVywJzcFbDiAFqiOMh3Dcni97KTwnE8bTdsO3X64n98mlehGfnTilie39Wr7AV3Rz8Pzwxf1qHVPCy7P6Ho16DUh8I9CSt3JDg+oybPrCmS7/z4K64w6VFrJhPCRmVCUUvJsLC2g1T/G/pPFnQYcmmXmDXlfjkRL0GpMbmsjHJ72GjhKv/XIlXrEntHfzUD2hRY0nqWHgQAoaKXRdYTy08ZLtXipT4Ov9rKgqQXVJkF5jjL3u6DIaZolXoghN4MquRkHZZfvT5VpG0Wo9tLvDN2Qk6hggclIVTiz2LJiYK3tZs3ppDwz7LN7T6FaJ06iOJ7jakODynoyVK/FKJKG16YCLBPUhG5UN423epTKMl3+WYuG04daGKwOHC6d5L18l6TXGQIKgVq8hhirf/UauxCtxhHaRSWNnTRzbEPb7STrzYbxGdt2MGVTrpmq6lOoCVdfnc4RZ8nqNDgihNOg1ZJNJiSa0aBkXr5mnpZZOtpGJeGKgtEunHrhpoXwYb7h2BqJew68CPu2J5qAWU4deI1+1Ew0ih0JoDh71ePLtTk1hvH4asjPx+mlOF36/jR97taaN5xUb78iF8ZwBQV/llYHey2Apeo31muLtsiHDqiB0ub2kZY9DbCTaGciG8XRIKZ0Y7fkBmuEiG8bruc1d4Os0g99Wml7jgyLj9FS5Gkjb+4UMq4bQ3s1DqliXGk8mtMWBYxwNbXS0YcDxjN5/siGtWqHAl4uIaOqujF5jso7Ss8J1RUOfKI5QDo3QtHEaA/6lNPf2ax+r6sTAs6Arv0wYjysD8SLgBfucrSW9RuDlcrJcCLDckw8VQFG6CIZO6IIS+g16UrIAypKwwUHl/LzZONow6FCiFZtt7hs/rsuwZTe1UNXPuk1t0vqI1AQ1WutiJw1eElT+jIfEoEJjOmJDaHED8RUiG11hvB1D/tUT3jYM6KSqQ78tM9uci4jwZ5wasNZ4Gfm/BV0EV93VqkevwUVg6/W0wU0EocUw3kJNI8x43R/0En5NFkVSrNusL4wH0OTWgDAevnpHCA9LIDsLW9/QRK0VdOg1ZKrto4DQLTTfOFgjDJDRFcaDok2mY7zThuGUvjAezTbH9FSfCIGXvMORuRIX7Jx9GcS4jKB+KOlqJ7ToM2KMrkhm1cf9HQETrUT9NqabamnDwFtjDZTfGitYydinrvH8Dv/G81FB6IQWiQSXAI1btKjCEG7CRKu5EuEmsQ2DpjDeqydXKPFDedwZVv+4wtOt6ITfseHNP48VoTngDnjHKitNCEhMtKpYG4aL8rPNg1wNOk006TVOXhykmtCohukiS2hXZcXdmsJ4JVZ50Jy+bXp0xKXMNvdP22dpBJ4uvcYrJSSEooDIELqg9u1oj7YwXtBEK9dU16y+bqp4UYLS2UEvvy5h1WGb0EEp+6ghUoQu1k1eV5uq1XdZmTeZWjy4Bjrmbo+k2qMSFeyHhbHFlWqDm0hCc+geTC+OuAjqKM+JjSm5OtowyMw2L1gfO8X94KsaeoyczT9bY0vl2uAmltCiYowG02OzFLadFX1YWMagCyIP4y0d0NRN1XaDcLELysC59Bpd+pq5n6SxxUticxGMNKELBtNvUR/G4ySa2znbV7jEwV8wXW0YaAbJKfkZJLqm5ubsNriYm4756bqGY1YdoYsOpleQzhVj0h9+564Q93u5eBgPKeuCn6XQKiLlPpxVFH16HXPNh0SZwO2tkdZrxJLQrg28o/wN9F54npKsItHZTbUgjCfMNhfDeKmJ+Rec9BpCnF55PeZQd2SVdLEmdDFJZDktrESLSuOWlwSPWxYJzQVAOrupIpIC+ao3+uLSazyjT69x5KfgivmoI9KEFi9BrWUMplcd+1XZTdVLqmJ1g1yv0bSgUK9R7u8dElsTv5xvTRz23ieS0N42sNR+tkQiuY51u1cGSplGmp3jLxkyaLrCeGirxWcx6ryU5oSXCPFs2RmQUUbkCV3ukHevFcKAyXL0E3U+3VSVRWB+tcJ4v7uigRrK4HmVdm89W3hq3fyg/rHFhtBlDqb36hIw+pf/vHIWl7sAT9sW89D35bsBxaZZYSDmJZc3UN3lG1+o0WvkPMkTawDTStKEhNkGt2osdMFgeqdvRfBg+lJFSaWeGKTftn82SA2ClI0zffSz4B6hRTCqt0Fu/J1+xxl1v2Poh356IeOm10gEob2D6YPaz+rsI+FUZ6czNMQeUQ+cGiDHCUWA23GKDbIv2Tp2iq2lvw/3vcdLxgCtCSI16DMY9SqURBLaa6lR+CrTflZXHwmn/UEqQ6E8RD4whxwzXB4EXu1gDwG7lxHQTgFAT2cALxhhzzL22J5O0q08DrxlAcIg8c9P7VtO30Pf7/m+J97upC5UT+1bTvF1dGrCBRKifwBaETRsxGUTrcTwdxRSoEUZnl/8TGHvbVURulDLULz3RKmN0Mt9HkRMcHLg2I4WMm6k8sDziomlJJA5doR2SOTTfrbY5Uqq9WwZz4PwH4itFTK/o74E2N+fFCLHntC8/WyxeXsU/rJT3MsDZoIrI3UE1iZdJc+dKEIHDaYXExTPfYAERfI2zSCbLEK72s9iMP1f8mE88TJYil7DIJuINYgtod3tZ63B9GhTgEwi/OqN2wtFPgbZxK9BbAktArLKbblu9hlbS2SGDFNmVLBBNnFrEGtCey+IW/d3UUX3/J45xtWYFP7+GEKPkNS8cyjiqvyr8ZuzoZPLELrcWLAgtzRuRjZ0chlCl7EIIoENmbOhE8sQOgKLYZCN/RrE+lJoYNYgbQhtSJBO8BoYCx2BTTDIGkIbEpgXIW0stCFBOuFrYFyOCGyCQdYQ2pDAvAhpY6ENCdIJXwPjckRgEwyyhtCGBOZFSBsLbUiQTvgaGJcjAptgkDWENiQwL0LaWGhDgnTC18C4HBHYBIOsIbQhgXkR0sZCGxKkE74GxuWIwCYYZA2hDQnMi5A2FtqQIJ3wNTAuRwQ2wSCrbA3+HwXffFECw5JOAAAAAElFTkSuQmCC';
11
+ // ─── Locked sender — always TKTIDE ────────────────────────────────────────
12
+ const TKTIDE_NAME = 'TKTIDE Cost Guard';
13
+ const TKTIDE_SITE = 'https://tktide.com';
14
+ const TKTIDE_TAGLINE = 'TKTIDE is here to help';
15
+ // ─── Helpers ──────────────────────────────────────────────────────────────
16
+ /**
17
+ * Escape HTML special characters so a caller-supplied `message` can never
18
+ * inject markup into the rendered email (defence-in-depth — the message is
19
+ * normally a developer config string, but may carry untrusted input).
20
+ */
21
+ function escapeHtml(input) {
22
+ return input
23
+ .replace(/&/g, '&')
24
+ .replace(/</g, '&lt;')
25
+ .replace(/>/g, '&gt;')
26
+ .replace(/"/g, '&quot;')
27
+ .replace(/'/g, '&#39;');
28
+ }
29
+ // ─── Email template ───────────────────────────────────────────────────────
30
+ function buildEmailHtml(type, details, customMessage) {
31
+ const isBlock = type === 'block';
32
+ const color = isBlock ? '#e74c3c' : '#f39c12';
33
+ const icon = isBlock ? '🛑' : '⚠️';
34
+ const title = isBlock ? 'Hard Limit Reached — Requests Blocked' : 'Approaching Budget Limit';
35
+ let projectedTotal;
36
+ let currentSpend;
37
+ let limit;
38
+ let triggerType;
39
+ if (details instanceof errors_1.BudgetExceededError) {
40
+ projectedTotal = `$${details.details.projectedTotal.toFixed(4)}`;
41
+ currentSpend = `$${details.details.currentSpend.toFixed(4)}`;
42
+ limit = `$${details.details.limit.toFixed(4)}`;
43
+ triggerType = details.details.type === 'hard_limit' ? 'Hard Limit' : 'Per-Request Limit';
44
+ }
45
+ else {
46
+ projectedTotal = `$${details.projectedTotal.toFixed(4)}`;
47
+ currentSpend = `$${details.currentSpend.toFixed(4)}`;
48
+ limit = `$${details.softLimit.toFixed(4)}`;
49
+ triggerType = 'Soft Limit Warning';
50
+ }
51
+ const customSection = customMessage
52
+ ? `
53
+ <div style="
54
+ background: #f8f9fa;
55
+ border-left: 4px solid ${color};
56
+ padding: 16px 20px;
57
+ margin-bottom: 24px;
58
+ border-radius: 0 8px 8px 0;
59
+ font-size: 15px;
60
+ color: #2c3e50;
61
+ line-height: 1.6;
62
+ ">
63
+ ${escapeHtml(customMessage)}
64
+ </div>`
65
+ : '';
66
+ return `
67
+ <!DOCTYPE html>
68
+ <html>
69
+ <head>
70
+ <meta charset="utf-8">
71
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
72
+ </head>
73
+ <body style="
74
+ margin: 0;
75
+ padding: 0;
76
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
77
+ background-color: #f0f2f5;
78
+ ">
79
+ <div style="max-width: 600px; margin: 40px auto; padding: 0 16px;">
80
+
81
+ <!-- Header -->
82
+ <div style="
83
+ background: ${color};
84
+ border-radius: 12px 12px 0 0;
85
+ padding: 28px 32px;
86
+ text-align: center;
87
+ ">
88
+ <div style="font-size: 40px; margin-bottom: 8px;">${icon}</div>
89
+ <h1 style="
90
+ color: white;
91
+ margin: 0;
92
+ font-size: 20px;
93
+ font-weight: 700;
94
+ letter-spacing: -0.3px;
95
+ ">${title}</h1>
96
+ </div>
97
+
98
+ <!-- Body -->
99
+ <div style="
100
+ background: white;
101
+ padding: 32px;
102
+ border-left: 1px solid #e1e4e8;
103
+ border-right: 1px solid #e1e4e8;
104
+ ">
105
+
106
+ ${customSection}
107
+
108
+ <!-- Stats -->
109
+ <table style="width: 100%; border-collapse: collapse; margin-bottom: 8px;">
110
+ <tr>
111
+ <td style="
112
+ padding: 14px 16px;
113
+ background: #f8f9fa;
114
+ border-radius: 8px 8px 0 0;
115
+ border-bottom: 1px solid #e1e4e8;
116
+ font-size: 13px;
117
+ color: #6c757d;
118
+ font-weight: 600;
119
+ text-transform: uppercase;
120
+ letter-spacing: 0.5px;
121
+ ">Trigger</td>
122
+ <td style="
123
+ padding: 14px 16px;
124
+ background: #f8f9fa;
125
+ border-radius: 8px 8px 0 0;
126
+ border-bottom: 1px solid #e1e4e8;
127
+ font-size: 14px;
128
+ color: #2c3e50;
129
+ font-weight: 500;
130
+ text-align: right;
131
+ ">${triggerType}</td>
132
+ </tr>
133
+ <tr>
134
+ <td style="
135
+ padding: 14px 16px;
136
+ border-bottom: 1px solid #e1e4e8;
137
+ font-size: 13px;
138
+ color: #6c757d;
139
+ font-weight: 600;
140
+ text-transform: uppercase;
141
+ letter-spacing: 0.5px;
142
+ ">Current Spend</td>
143
+ <td style="
144
+ padding: 14px 16px;
145
+ border-bottom: 1px solid #e1e4e8;
146
+ font-size: 14px;
147
+ color: #2c3e50;
148
+ font-weight: 500;
149
+ text-align: right;
150
+ ">${currentSpend}</td>
151
+ </tr>
152
+ <tr>
153
+ <td style="
154
+ padding: 14px 16px;
155
+ border-bottom: 1px solid #e1e4e8;
156
+ font-size: 13px;
157
+ color: #6c757d;
158
+ font-weight: 600;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.5px;
161
+ ">Projected Total</td>
162
+ <td style="
163
+ padding: 14px 16px;
164
+ border-bottom: 1px solid #e1e4e8;
165
+ font-size: 14px;
166
+ color: ${color};
167
+ font-weight: 700;
168
+ text-align: right;
169
+ ">${projectedTotal}</td>
170
+ </tr>
171
+ <tr>
172
+ <td style="
173
+ padding: 14px 16px;
174
+ background: #f8f9fa;
175
+ border-radius: 0 0 8px 8px;
176
+ font-size: 13px;
177
+ color: #6c757d;
178
+ font-weight: 600;
179
+ text-transform: uppercase;
180
+ letter-spacing: 0.5px;
181
+ ">Limit</td>
182
+ <td style="
183
+ padding: 14px 16px;
184
+ background: #f8f9fa;
185
+ border-radius: 0 0 8px 8px;
186
+ font-size: 14px;
187
+ color: #2c3e50;
188
+ font-weight: 500;
189
+ text-align: right;
190
+ ">${limit}</td>
191
+ </tr>
192
+ </table>
193
+
194
+ </div>
195
+
196
+ <!-- TKTIDE Signature — locked, cannot be removed or modified -->
197
+ <div style="
198
+ background: #2d3748;
199
+ border-radius: 0 0 12px 12px;
200
+ padding: 24px 32px;
201
+ text-align: center;
202
+ border-left: 1px solid #1a202c;
203
+ border-right: 1px solid #1a202c;
204
+ border-bottom: 1px solid #1a202c;
205
+ ">
206
+ <img
207
+ src="data:image/png;base64,${TKTIDE_LOGO_BASE64}"
208
+ alt="TKTIDE"
209
+ width="48"
210
+ height="48"
211
+ style="display: block; margin: 0 auto 12px; border-radius: 8px;"
212
+ />
213
+ <p style="
214
+ margin: 0 0 4px;
215
+ color: #a0aec0;
216
+ font-size: 13px;
217
+ font-weight: 500;
218
+ ">${TKTIDE_TAGLINE}</p>
219
+ <a
220
+ href="${TKTIDE_SITE}"
221
+ style="
222
+ color: #68d391;
223
+ font-size: 13px;
224
+ text-decoration: none;
225
+ font-weight: 600;
226
+ "
227
+ >${TKTIDE_SITE}</a>
228
+ </div>
229
+
230
+ </div>
231
+ </body>
232
+ </html>`;
233
+ }
234
+ // ─── Notifier factory ─────────────────────────────────────────────────────
235
+ /**
236
+ * Creates an email notifier that sends real Gmail alerts when cost limits are hit.
237
+ *
238
+ * @example
239
+ * const notifier = createEmailNotifier({
240
+ * from: 'your@gmail.com',
241
+ * password: process.env.GMAIL_PASS!,
242
+ * to: 'alerts@yourdomain.com',
243
+ * message: 'Your AI spend needs attention.', // optional
244
+ * });
245
+ *
246
+ * const guard = new ApiCostGuard({
247
+ * softLimit: 8,
248
+ * hardLimit: 10,
249
+ * onWarning: notifier,
250
+ * onBlock: notifier,
251
+ * });
252
+ */
253
+ function createEmailNotifier(options) {
254
+ const { from, password, to, message } = options;
255
+ // Create transporter once — reused across all calls
256
+ const transporter = nodemailer_1.default.createTransport({
257
+ service: 'gmail',
258
+ auth: {
259
+ user: from,
260
+ pass: password,
261
+ },
262
+ });
263
+ return async (details) => {
264
+ const isBlock = details instanceof errors_1.BudgetExceededError;
265
+ const type = isBlock ? 'block' : 'warning';
266
+ const subject = isBlock
267
+ ? '🛑 API Cost Alert — Hard Limit Reached | TKTIDE'
268
+ : '⚠️ API Cost Alert — Budget Warning | TKTIDE';
269
+ const html = buildEmailHtml(type, details, message);
270
+ try {
271
+ await transporter.sendMail({
272
+ from: `"${TKTIDE_NAME}" <${from}>`,
273
+ to,
274
+ subject,
275
+ html,
276
+ });
277
+ }
278
+ catch (err) {
279
+ // Non-fatal — never let email failure crash the guard
280
+ console.error('[api-cost-guard] Email notification failed:', err);
281
+ }
282
+ };
283
+ }
284
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/notifiers/email.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { createEmailNotifier } from './email';
2
+ export type { EmailNotifierOptions } from './email';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/notifiers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,YAAY,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEmailNotifier = void 0;
4
+ var email_1 = require("./email");
5
+ Object.defineProperty(exports, "createEmailNotifier", { enumerable: true, get: function () { return email_1.createEmailNotifier; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notifiers/index.ts"],"names":[],"mappings":";;;AAAA,iCAA8C;AAArC,4GAAA,mBAAmB,OAAA"}
@@ -0,0 +1,11 @@
1
+ import type { PricingMap } from './types';
2
+ /**
3
+ * Built-in pricing table (USD per `unit` tokens).
4
+ * All values are strings to avoid floating-point representation errors.
5
+ * Keys are lowercase substrings matched against the model name.
6
+ *
7
+ * Last updated: June 2026
8
+ * Always override stale prices via `customPricing` in options.
9
+ */
10
+ export declare const BUILT_IN_PRICING: PricingMap;
11
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,UAyBrB,CAAC"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILT_IN_PRICING = void 0;
4
+ /**
5
+ * Built-in pricing table (USD per `unit` tokens).
6
+ * All values are strings to avoid floating-point representation errors.
7
+ * Keys are lowercase substrings matched against the model name.
8
+ *
9
+ * Last updated: June 2026
10
+ * Always override stale prices via `customPricing` in options.
11
+ */
12
+ exports.BUILT_IN_PRICING = {
13
+ // ── OpenAI ──────────────────────────────────────────────────────────────
14
+ 'gpt-4o-mini': { input: '0.00015', output: '0.0006', unit: 1000 },
15
+ 'gpt-4o': { input: '0.0025', output: '0.01', unit: 1000 },
16
+ 'gpt-4-turbo': { input: '0.01', output: '0.03', unit: 1000 },
17
+ 'gpt-4': { input: '0.03', output: '0.06', unit: 1000 },
18
+ 'gpt-3.5-turbo': { input: '0.0005', output: '0.0015', unit: 1000 },
19
+ 'o1-pro': { input: '0.15', output: '0.60', unit: 1000 },
20
+ 'o1': { input: '0.015', output: '0.06', unit: 1000 },
21
+ 'o3': { input: '0.01', output: '0.04', unit: 1000 },
22
+ 'o4-mini': { input: '0.0011', output: '0.0044', unit: 1000 },
23
+ // ── Anthropic ────────────────────────────────────────────────────────────
24
+ 'claude-opus-4': { input: '0.015', output: '0.075', unit: 1000 },
25
+ 'claude-sonnet-4': { input: '0.003', output: '0.015', unit: 1000 },
26
+ 'claude-3-5-sonnet': { input: '0.003', output: '0.015', unit: 1000 },
27
+ 'claude-3-5-haiku': { input: '0.0008', output: '0.004', unit: 1000 },
28
+ 'claude-3-opus': { input: '0.015', output: '0.075', unit: 1000 },
29
+ 'claude-3-haiku': { input: '0.00025', output: '0.00125', unit: 1000 },
30
+ // ── Google Gemini ────────────────────────────────────────────────────────
31
+ 'gemini-2.5-pro': { input: '0.00125', output: '0.01', unit: 1000 },
32
+ 'gemini-2.0-flash': { input: '0.0001', output: '0.0004', unit: 1000 },
33
+ 'gemini-1.5-pro': { input: '0.00125', output: '0.005', unit: 1000 },
34
+ 'gemini-1.5-flash': { input: '0.000075', output: '0.0003', unit: 1000 },
35
+ };
36
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":";;;AAEA;;;;;;;GAOG;AACU,QAAA,gBAAgB,GAAe;IAC1C,2EAA2E;IAC3E,aAAa,EAAK,EAAE,KAAK,EAAE,SAAS,EAAG,MAAM,EAAE,QAAQ,EAAG,IAAI,EAAE,IAAI,EAAE;IACtE,QAAQ,EAAU,EAAE,KAAK,EAAE,QAAQ,EAAI,MAAM,EAAE,MAAM,EAAK,IAAI,EAAE,IAAI,EAAE;IACtE,aAAa,EAAK,EAAE,KAAK,EAAE,MAAM,EAAM,MAAM,EAAE,MAAM,EAAK,IAAI,EAAE,IAAI,EAAE;IACtE,OAAO,EAAW,EAAE,KAAK,EAAE,MAAM,EAAM,MAAM,EAAE,MAAM,EAAK,IAAI,EAAE,IAAI,EAAE;IACtE,eAAe,EAAG,EAAE,KAAK,EAAE,QAAQ,EAAI,MAAM,EAAE,QAAQ,EAAG,IAAI,EAAE,IAAI,EAAE;IACtE,QAAQ,EAAU,EAAE,KAAK,EAAE,MAAM,EAAM,MAAM,EAAE,MAAM,EAAK,IAAI,EAAE,IAAI,EAAE;IACtE,IAAI,EAAc,EAAE,KAAK,EAAE,OAAO,EAAK,MAAM,EAAE,MAAM,EAAK,IAAI,EAAE,IAAI,EAAE;IACtE,IAAI,EAAc,EAAE,KAAK,EAAE,MAAM,EAAM,MAAM,EAAE,MAAM,EAAK,IAAI,EAAE,IAAI,EAAE;IACtE,SAAS,EAAS,EAAE,KAAK,EAAE,QAAQ,EAAI,MAAM,EAAE,QAAQ,EAAG,IAAI,EAAE,IAAI,EAAE;IAEtE,4EAA4E;IAC5E,eAAe,EAAQ,EAAE,KAAK,EAAE,OAAO,EAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;IACvE,iBAAiB,EAAM,EAAE,KAAK,EAAE,OAAO,EAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;IACvE,mBAAmB,EAAI,EAAE,KAAK,EAAE,OAAO,EAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;IACvE,kBAAkB,EAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;IACvE,eAAe,EAAQ,EAAE,KAAK,EAAE,OAAO,EAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;IACvE,gBAAgB,EAAO,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,EAAE,SAAS,EAAC,IAAI,EAAE,IAAI,EAAE;IAExE,4EAA4E;IAC5E,gBAAgB,EAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAG,IAAI,EAAE,IAAI,EAAE;IACxE,kBAAkB,EAAK,EAAE,KAAK,EAAE,QAAQ,EAAG,MAAM,EAAE,QAAQ,EAAC,IAAI,EAAE,IAAI,EAAE;IACxE,gBAAgB,EAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;IACxE,kBAAkB,EAAK,EAAE,KAAK,EAAE,UAAU,EAAC,MAAM,EAAE,QAAQ,EAAC,IAAI,EAAE,IAAI,EAAE;CAChE,CAAC"}
@@ -0,0 +1,127 @@
1
+ import Decimal from 'decimal.js';
2
+ export interface ModelPricing {
3
+ /** Cost per `unit` input tokens in USD */
4
+ readonly input: string;
5
+ /** Cost per `unit` output tokens in USD */
6
+ readonly output: string;
7
+ /** Token unit size (e.g. 1000 = per 1k tokens) */
8
+ readonly unit: number;
9
+ }
10
+ export type PricingMap = Record<string, ModelPricing>;
11
+ export interface ApiCostGuardOptions {
12
+ /**
13
+ * Hard stop in USD. All requests are blocked once cumulative
14
+ * spend reaches or would exceed this value.
15
+ */
16
+ hardLimit?: string | number;
17
+ /**
18
+ * Warning threshold in USD. Fires `onWarning` when projected
19
+ * spend crosses this value, but does NOT block.
20
+ */
21
+ softLimit?: string | number;
22
+ /**
23
+ * Maximum cost allowed for a single request in USD.
24
+ * Blocks any request whose estimated cost exceeds this.
25
+ */
26
+ perRequestLimit?: string | number;
27
+ /**
28
+ * Rolling time window in milliseconds.
29
+ * When set, limits apply only to spend within the last N ms.
30
+ * When null (default), limits apply to the entire session.
31
+ */
32
+ windowMs?: number | null;
33
+ /**
34
+ * Called when projected spend crosses `softLimit`.
35
+ * Does not block the request.
36
+ */
37
+ onWarning?: (details: WarningDetails) => void;
38
+ /**
39
+ * Called just before a request is blocked.
40
+ * Useful for alerting, logging, or key rotation hooks.
41
+ * The error argument is a BudgetExceededError instance.
42
+ */
43
+ onBlock?: (error: Error & {
44
+ details: import('./errors').BudgetExceededDetails;
45
+ }) => void;
46
+ /**
47
+ * When true (default), throws `BudgetExceededError` on block.
48
+ * When false, returns null from `wrap()` and `{ allowed: false }` from `preflightCheck()`.
49
+ */
50
+ throwOnBlock?: boolean;
51
+ /**
52
+ * Additional or override pricing for models not in the built-in table,
53
+ * or to update stale prices.
54
+ */
55
+ customPricing?: PricingMap;
56
+ }
57
+ export type BlockReason = 'hard_limit' | 'per_request_limit';
58
+ export interface PreflightAllowed {
59
+ readonly allowed: true;
60
+ /** Estimated cost of this request in USD (null if model unknown) */
61
+ readonly estimatedCost: Decimal | null;
62
+ /** Projected cumulative spend after this request */
63
+ readonly projectedTotal: Decimal;
64
+ }
65
+ export interface PreflightBlocked {
66
+ readonly allowed: false;
67
+ readonly reason: BlockReason;
68
+ readonly estimatedCost: Decimal | null;
69
+ readonly projectedTotal: Decimal;
70
+ /** Current spend before this request */
71
+ readonly currentSpend: Decimal;
72
+ readonly limit: Decimal;
73
+ }
74
+ export type PreflightResult = PreflightAllowed | PreflightBlocked;
75
+ export interface UsageRecord {
76
+ readonly timestamp: number;
77
+ readonly cost: Decimal;
78
+ readonly model: string;
79
+ readonly inputTokens: number;
80
+ readonly outputTokens: number;
81
+ readonly metadata: Record<string, unknown>;
82
+ }
83
+ export interface GuardStats {
84
+ /** Total spend across all recorded usage */
85
+ readonly totalSpend: Decimal;
86
+ /** Spend within the current rolling window (equals totalSpend if no window) */
87
+ readonly windowSpend: Decimal;
88
+ /** Number of recorded requests */
89
+ readonly requestCount: number;
90
+ readonly hardLimit: Decimal | null;
91
+ readonly softLimit: Decimal | null;
92
+ /** Remaining budget before hard limit (null if no hard limit) */
93
+ readonly remainingBudget: Decimal | null;
94
+ /** Percentage of hard limit used e.g. "34.20" (null if no hard limit) */
95
+ readonly percentUsed: string | null;
96
+ readonly ledger: readonly UsageRecord[];
97
+ }
98
+ export interface WarningDetails {
99
+ readonly projectedTotal: Decimal;
100
+ readonly currentSpend: Decimal;
101
+ readonly softLimit: Decimal;
102
+ readonly estimatedCost: Decimal | null;
103
+ }
104
+ export interface WrapOptions<T> {
105
+ model: string;
106
+ estimatedInputTokens?: number;
107
+ estimatedOutputTokens?: number;
108
+ /**
109
+ * Extract actual token usage from the API response so the guard
110
+ * can record real spend instead of estimates.
111
+ */
112
+ extractUsage?: (result: T) => {
113
+ inputTokens: number;
114
+ outputTokens: number;
115
+ };
116
+ metadata?: Record<string, unknown>;
117
+ }
118
+ export interface MiddlewareOptions {
119
+ /** Extract model name from the request. Default: req.body?.model */
120
+ getModel?: (req: any) => string | undefined;
121
+ /** Extract estimated token counts. Default: req.body?.max_tokens for both */
122
+ getTokens?: (req: any) => {
123
+ input: number;
124
+ output: number;
125
+ };
126
+ }
127
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@tktideai/ai-api-cost-guard",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Hard-stop budget limiter for AI API calls. Prevents runaway charges from OpenAI, Anthropic, Google Gemini and more.",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./notifiers": {
17
+ "import": "./dist/notifiers/index.js",
18
+ "require": "./dist/notifiers/index.js",
19
+ "types": "./dist/notifiers/index.d.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "test": "jest --forceExit",
30
+ "test:watch": "jest --watch",
31
+ "prepublishOnly": "npm run build && npm test"
32
+ },
33
+ "keywords": [
34
+ "openai",
35
+ "anthropic",
36
+ "gemini",
37
+ "api",
38
+ "cost",
39
+ "budget",
40
+ "guard",
41
+ "llm",
42
+ "ai",
43
+ "rate-limit"
44
+ ],
45
+ "author": "TKTIDE <hello@tktide.com> (https://tktide.com)",
46
+ "license": "MIT",
47
+ "homepage": "https://gitlab.com/tktide-group/ai-api-cost-guard#readme",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://gitlab.com/tktide-group/ai-api-cost-guard.git"
51
+ },
52
+ "bugs": {
53
+ "url": "https://gitlab.com/tktide-group/ai-api-cost-guard/-/issues"
54
+ },
55
+ "dependencies": {
56
+ "decimal.js": "^10.4.3",
57
+ "nodemailer": "^9.0.1"
58
+ },
59
+ "devDependencies": {
60
+ "@types/jest": "^29.5.12",
61
+ "@types/node": "^20.14.0",
62
+ "@types/nodemailer": "^8.0.1",
63
+ "jest": "^29.7.0",
64
+ "ts-jest": "^29.1.4",
65
+ "typescript": "^5.4.5"
66
+ },
67
+ "jest": {
68
+ "preset": "ts-jest",
69
+ "testEnvironment": "node",
70
+ "testMatch": [
71
+ "**/src/**/*.test.ts"
72
+ ],
73
+ "collectCoverageFrom": [
74
+ "src/**/*.ts",
75
+ "!src/**/*.test.ts"
76
+ ]
77
+ }
78
+ }