ai-functions 2.1.3 → 2.3.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.
Files changed (277) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -1
  3. package/README.md +38 -0
  4. package/dist/ai-promise.d.ts +3 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +135 -64
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +51 -858
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts.map +1 -1
  56. package/dist/budget.js +27 -14
  57. package/dist/budget.js.map +1 -1
  58. package/dist/cache.d.ts +23 -0
  59. package/dist/cache.d.ts.map +1 -1
  60. package/dist/cache.js +36 -15
  61. package/dist/cache.js.map +1 -1
  62. package/dist/context.d.ts +26 -8
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/context.js +64 -62
  65. package/dist/context.js.map +1 -1
  66. package/dist/digital-objects-registry.d.ts +229 -0
  67. package/dist/digital-objects-registry.d.ts.map +1 -0
  68. package/dist/digital-objects-registry.js +617 -0
  69. package/dist/digital-objects-registry.js.map +1 -0
  70. package/dist/embeddings.d.ts +2 -2
  71. package/dist/embeddings.d.ts.map +1 -1
  72. package/dist/errors.d.ts +22 -0
  73. package/dist/errors.d.ts.map +1 -0
  74. package/dist/errors.js +35 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/eval/runner.d.ts +8 -0
  77. package/dist/eval/runner.d.ts.map +1 -1
  78. package/dist/eval/runner.js +41 -35
  79. package/dist/eval/runner.js.map +1 -1
  80. package/dist/eval-log/in-memory.d.ts +34 -0
  81. package/dist/eval-log/in-memory.d.ts.map +1 -0
  82. package/dist/eval-log/in-memory.js +84 -0
  83. package/dist/eval-log/in-memory.js.map +1 -0
  84. package/dist/eval-log/index.d.ts +29 -0
  85. package/dist/eval-log/index.d.ts.map +1 -0
  86. package/dist/eval-log/index.js +39 -0
  87. package/dist/eval-log/index.js.map +1 -0
  88. package/dist/eval-log/types.d.ts +101 -0
  89. package/dist/eval-log/types.d.ts.map +1 -0
  90. package/dist/eval-log/types.js +16 -0
  91. package/dist/eval-log/types.js.map +1 -0
  92. package/dist/function-registry.d.ts +116 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +546 -0
  95. package/dist/function-registry.js.map +1 -0
  96. package/dist/generate.d.ts +9 -3
  97. package/dist/generate.d.ts.map +1 -1
  98. package/dist/generate.js +18 -18
  99. package/dist/generate.js.map +1 -1
  100. package/dist/index.d.ts +18 -11
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +35 -18
  103. package/dist/index.js.map +1 -1
  104. package/dist/logger.d.ts +118 -0
  105. package/dist/logger.d.ts.map +1 -0
  106. package/dist/logger.js +187 -0
  107. package/dist/logger.js.map +1 -0
  108. package/dist/middleware/budget.d.ts +84 -0
  109. package/dist/middleware/budget.d.ts.map +1 -0
  110. package/dist/middleware/budget.js +110 -0
  111. package/dist/middleware/budget.js.map +1 -0
  112. package/dist/middleware/cache.d.ts +103 -0
  113. package/dist/middleware/cache.d.ts.map +1 -0
  114. package/dist/middleware/cache.js +228 -0
  115. package/dist/middleware/cache.js.map +1 -0
  116. package/dist/middleware/embed-cache.d.ts +99 -0
  117. package/dist/middleware/embed-cache.d.ts.map +1 -0
  118. package/dist/middleware/embed-cache.js +128 -0
  119. package/dist/middleware/embed-cache.js.map +1 -0
  120. package/dist/middleware/index.d.ts +11 -0
  121. package/dist/middleware/index.d.ts.map +1 -0
  122. package/dist/middleware/index.js +11 -0
  123. package/dist/middleware/index.js.map +1 -0
  124. package/dist/middleware/trace.d.ts +103 -0
  125. package/dist/middleware/trace.d.ts.map +1 -0
  126. package/dist/middleware/trace.js +176 -0
  127. package/dist/middleware/trace.js.map +1 -0
  128. package/dist/primitives.d.ts +120 -1
  129. package/dist/primitives.d.ts.map +1 -1
  130. package/dist/primitives.js +398 -26
  131. package/dist/primitives.js.map +1 -1
  132. package/dist/retry.d.ts +66 -1
  133. package/dist/retry.d.ts.map +1 -1
  134. package/dist/retry.js +115 -8
  135. package/dist/retry.js.map +1 -1
  136. package/dist/schema.js +2 -2
  137. package/dist/schema.js.map +1 -1
  138. package/dist/telemetry.d.ts +128 -0
  139. package/dist/telemetry.d.ts.map +1 -0
  140. package/dist/telemetry.js +285 -0
  141. package/dist/telemetry.js.map +1 -0
  142. package/dist/template.d.ts.map +1 -1
  143. package/dist/template.js +6 -1
  144. package/dist/template.js.map +1 -1
  145. package/dist/tool-orchestration.d.ts +66 -4
  146. package/dist/tool-orchestration.d.ts.map +1 -1
  147. package/dist/tool-orchestration.js +123 -23
  148. package/dist/tool-orchestration.js.map +1 -1
  149. package/dist/type-guards.d.ts +28 -0
  150. package/dist/type-guards.d.ts.map +1 -0
  151. package/dist/type-guards.js +29 -0
  152. package/dist/type-guards.js.map +1 -0
  153. package/dist/types.d.ts +135 -17
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +36 -1
  156. package/dist/types.js.map +1 -1
  157. package/dist/wrap-for-v3.d.ts +80 -0
  158. package/dist/wrap-for-v3.d.ts.map +1 -0
  159. package/dist/wrap-for-v3.js +89 -0
  160. package/dist/wrap-for-v3.js.map +1 -0
  161. package/examples/00-quickstart.ts +232 -0
  162. package/examples/01-rag-chatbot.ts +212 -0
  163. package/examples/02-multi-agent-research.ts +290 -0
  164. package/examples/03-email-classification.ts +379 -0
  165. package/examples/04-content-moderation.ts +400 -0
  166. package/examples/05-document-extraction.ts +455 -0
  167. package/examples/06-streaming-chat-nextjs.ts +437 -0
  168. package/examples/07-cloudflare-worker.ts +483 -0
  169. package/examples/08-batch-processing.ts +491 -0
  170. package/examples/09-budget-constrained.ts +527 -0
  171. package/examples/10-tool-orchestration.ts +565 -0
  172. package/examples/11-retry-resilience.ts +403 -0
  173. package/examples/12-caching-strategies.ts +422 -0
  174. package/examples/README.md +145 -0
  175. package/package.json +28 -25
  176. package/src/ai-promise.ts +226 -140
  177. package/src/ai-schemas.ts +122 -0
  178. package/src/ai.ts +69 -1176
  179. package/src/batch/anthropic.ts +96 -161
  180. package/src/batch/bedrock.ts +203 -454
  181. package/src/batch/cloudflare.ts +99 -282
  182. package/src/batch/google.ts +91 -297
  183. package/src/batch/index.ts +4 -1
  184. package/src/batch/memory.ts +15 -10
  185. package/src/batch/openai.ts +65 -193
  186. package/src/batch/provider.ts +336 -0
  187. package/src/batch-map.ts +29 -24
  188. package/src/batch-queue.ts +200 -11
  189. package/src/budget.ts +31 -18
  190. package/src/cache.ts +45 -17
  191. package/src/context.ts +106 -77
  192. package/src/digital-objects-registry.ts +750 -0
  193. package/src/errors.ts +37 -0
  194. package/src/eval/runner.ts +60 -36
  195. package/src/eval-log/in-memory.ts +90 -0
  196. package/src/eval-log/index.ts +46 -0
  197. package/src/eval-log/types.ts +110 -0
  198. package/src/function-registry.ts +671 -0
  199. package/src/generate.ts +33 -28
  200. package/src/index.ts +119 -21
  201. package/src/logger.ts +232 -0
  202. package/src/middleware/budget.ts +171 -0
  203. package/src/middleware/cache.ts +299 -0
  204. package/src/middleware/embed-cache.ts +195 -0
  205. package/src/middleware/index.ts +23 -0
  206. package/src/middleware/trace.ts +248 -0
  207. package/src/primitives.ts +589 -62
  208. package/src/retry.ts +144 -18
  209. package/src/schema.ts +8 -8
  210. package/src/telemetry.ts +403 -0
  211. package/src/template.ts +8 -4
  212. package/src/tool-orchestration.ts +213 -48
  213. package/src/type-guards.ts +31 -0
  214. package/src/types.ts +164 -25
  215. package/src/wrap-for-v3.ts +105 -0
  216. package/test/ai-promise.test.ts +1080 -0
  217. package/test/ai-proxy.test.ts +1 -1
  218. package/test/batch-autosubmit-errors.test.ts +49 -37
  219. package/test/batch-blog-posts.test.ts +87 -129
  220. package/test/core-functions.test.ts +183 -579
  221. package/test/decide.test.ts +154 -322
  222. package/test/define.test.ts +211 -8
  223. package/test/digital-objects-registry.test.ts +760 -0
  224. package/test/embedding-cache-middleware.test.ts +140 -0
  225. package/test/generate-core.test.ts +140 -229
  226. package/test/implicit-batch.test.ts +22 -65
  227. package/test/retry-policy-integration.test.ts +117 -0
  228. package/test/schema.test.ts +55 -19
  229. package/test/template.test.ts +1164 -0
  230. package/test/tool-orchestration.test.ts +270 -0
  231. package/test/wrap-for-v3.test.ts +612 -0
  232. package/vitest.config.js +6 -0
  233. package/vitest.config.ts +20 -0
  234. package/LICENSE +0 -21
  235. package/dist/rpc/auth.d.ts +0 -69
  236. package/dist/rpc/auth.d.ts.map +0 -1
  237. package/dist/rpc/auth.js +0 -136
  238. package/dist/rpc/auth.js.map +0 -1
  239. package/dist/rpc/client.d.ts +0 -62
  240. package/dist/rpc/client.d.ts.map +0 -1
  241. package/dist/rpc/client.js +0 -103
  242. package/dist/rpc/client.js.map +0 -1
  243. package/dist/rpc/deferred.d.ts +0 -60
  244. package/dist/rpc/deferred.d.ts.map +0 -1
  245. package/dist/rpc/deferred.js +0 -96
  246. package/dist/rpc/deferred.js.map +0 -1
  247. package/dist/rpc/index.d.ts +0 -22
  248. package/dist/rpc/index.d.ts.map +0 -1
  249. package/dist/rpc/index.js +0 -38
  250. package/dist/rpc/index.js.map +0 -1
  251. package/dist/rpc/local.d.ts +0 -42
  252. package/dist/rpc/local.d.ts.map +0 -1
  253. package/dist/rpc/local.js +0 -50
  254. package/dist/rpc/local.js.map +0 -1
  255. package/dist/rpc/server.d.ts +0 -165
  256. package/dist/rpc/server.d.ts.map +0 -1
  257. package/dist/rpc/server.js +0 -405
  258. package/dist/rpc/server.js.map +0 -1
  259. package/dist/rpc/session.d.ts +0 -32
  260. package/dist/rpc/session.d.ts.map +0 -1
  261. package/dist/rpc/session.js +0 -43
  262. package/dist/rpc/session.js.map +0 -1
  263. package/dist/rpc/transport.d.ts +0 -306
  264. package/dist/rpc/transport.d.ts.map +0 -1
  265. package/dist/rpc/transport.js +0 -731
  266. package/dist/rpc/transport.js.map +0 -1
  267. package/src/batch/anthropic.js +0 -256
  268. package/src/batch/bedrock.js +0 -584
  269. package/src/batch/cloudflare.js +0 -287
  270. package/src/batch/google.js +0 -359
  271. package/src/batch/index.js +0 -30
  272. package/src/batch/memory.js +0 -187
  273. package/src/batch/openai.js +0 -402
  274. package/src/eval/index.js +0 -7
  275. package/src/eval/models.js +0 -119
  276. package/src/eval/runner.js +0 -147
  277. package/test/schema.test.js +0 -96
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Budget-Constrained Generation Example
3
+ *
4
+ * This example demonstrates controlling costs and monitoring token usage.
5
+ * It shows how to:
6
+ * - Set and enforce budget limits
7
+ * - Track costs by model and request
8
+ * - Handle budget alerts
9
+ * - Use cost-effective strategies
10
+ *
11
+ * @example
12
+ * ```bash
13
+ * ANTHROPIC_API_KEY=sk-... npx tsx examples/09-budget-constrained.ts
14
+ * ```
15
+ */
16
+
17
+ import {
18
+ ai,
19
+ write,
20
+ list,
21
+ configure,
22
+ withBudget,
23
+ BudgetTracker,
24
+ BudgetExceededError,
25
+ TokenCounter,
26
+ createRequestContext,
27
+ FallbackChain,
28
+ } from '../src/index.js'
29
+
30
+ // ============================================================================
31
+ // Types
32
+ // ============================================================================
33
+
34
+ interface GenerationTask {
35
+ id: string
36
+ prompt: string
37
+ priority: 'high' | 'medium' | 'low'
38
+ maxTokens?: number
39
+ requiredModel?: string
40
+ }
41
+
42
+ interface CostReport {
43
+ totalCost: number
44
+ costByModel: Record<string, number>
45
+ tokensByModel: Record<string, { input: number; output: number }>
46
+ requestCount: number
47
+ avgCostPerRequest: number
48
+ budgetUtilization: number
49
+ }
50
+
51
+ // ============================================================================
52
+ // Budget Management
53
+ // ============================================================================
54
+
55
+ class BudgetAwareGenerator {
56
+ private tracker: BudgetTracker
57
+ private maxBudget: number
58
+ private alertsReceived: string[] = []
59
+ private requestHistory: { id: string; cost: number; tokens: number }[] = []
60
+
61
+ constructor(maxBudget: number) {
62
+ this.maxBudget = maxBudget
63
+ this.tracker = new BudgetTracker({
64
+ maxCost: maxBudget,
65
+ maxTokens: 1000000,
66
+ alertThresholds: [0.25, 0.5, 0.75, 0.9, 1.0],
67
+ onAlert: (alert) => {
68
+ this.alertsReceived.push(
69
+ `${(alert.threshold * 100).toFixed(0)}% budget used ($${alert.currentUsage.toFixed(4)})`
70
+ )
71
+ console.log(` [ALERT] Budget ${(alert.threshold * 100).toFixed(0)}% consumed`)
72
+ },
73
+ })
74
+ }
75
+
76
+ /**
77
+ * Check if we can afford a generation
78
+ */
79
+ canAfford(estimatedTokens: number, model: string = 'sonnet'): boolean {
80
+ try {
81
+ this.tracker.checkBudget({ estimatedTokens, model })
82
+ return true
83
+ } catch {
84
+ return false
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get remaining budget
90
+ */
91
+ getRemainingBudget(): { cost: number; tokens: number } {
92
+ return this.tracker.getRemainingBudget()
93
+ }
94
+
95
+ /**
96
+ * Generate with budget tracking
97
+ */
98
+ async generate(task: GenerationTask): Promise<string | null> {
99
+ const estimatedInputTokens = Math.ceil(task.prompt.length / 4)
100
+ const estimatedOutputTokens = task.maxTokens || 500
101
+ const estimatedTotal = estimatedInputTokens + estimatedOutputTokens
102
+
103
+ // Check budget before generating
104
+ if (!this.canAfford(estimatedTotal, task.requiredModel || 'sonnet')) {
105
+ console.log(` [SKIP] Task ${task.id}: Insufficient budget`)
106
+ return null
107
+ }
108
+
109
+ console.log(` [GENERATING] Task ${task.id} (est. ${estimatedTotal} tokens)`)
110
+
111
+ try {
112
+ const result = await write`${task.prompt}`
113
+
114
+ // Record actual usage (estimated for this demo)
115
+ const actualInputTokens = estimatedInputTokens
116
+ const actualOutputTokens = Math.ceil(result.length / 4)
117
+
118
+ this.tracker.recordUsage({
119
+ inputTokens: actualInputTokens,
120
+ outputTokens: actualOutputTokens,
121
+ model: task.requiredModel || 'claude-sonnet-4-20250514',
122
+ })
123
+
124
+ // Track request
125
+ const cost = this.estimateCost(actualInputTokens, actualOutputTokens, task.requiredModel)
126
+ this.requestHistory.push({
127
+ id: task.id,
128
+ cost,
129
+ tokens: actualInputTokens + actualOutputTokens,
130
+ })
131
+
132
+ return result
133
+ } catch (error) {
134
+ if (error instanceof BudgetExceededError) {
135
+ console.log(` [BUDGET EXCEEDED] Task ${task.id}`)
136
+ return null
137
+ }
138
+ throw error
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Estimate cost for tokens
144
+ */
145
+ private estimateCost(input: number, output: number, model?: string): number {
146
+ // Approximate pricing per million tokens
147
+ const pricing: Record<string, { input: number; output: number }> = {
148
+ 'claude-sonnet-4-20250514': { input: 3, output: 15 },
149
+ 'claude-3-5-haiku-latest': { input: 0.25, output: 1.25 },
150
+ 'gpt-4o': { input: 2.5, output: 10 },
151
+ 'gpt-4o-mini': { input: 0.15, output: 0.6 },
152
+ }
153
+
154
+ const p = pricing[model || 'claude-sonnet-4-20250514'] || pricing['claude-sonnet-4-20250514']
155
+ return (input * p.input + output * p.output) / 1000000
156
+ }
157
+
158
+ /**
159
+ * Generate cost report
160
+ */
161
+ getReport(): CostReport {
162
+ const totalCost = this.tracker.getTotalCost()
163
+ const costByModel = this.tracker.getCostByModel()
164
+
165
+ return {
166
+ totalCost,
167
+ costByModel,
168
+ tokensByModel: {}, // Would be tracked in full implementation
169
+ requestCount: this.requestHistory.length,
170
+ avgCostPerRequest:
171
+ this.requestHistory.length > 0 ? totalCost / this.requestHistory.length : 0,
172
+ budgetUtilization: (totalCost / this.maxBudget) * 100,
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Get alerts received
178
+ */
179
+ getAlerts(): string[] {
180
+ return [...this.alertsReceived]
181
+ }
182
+ }
183
+
184
+ // ============================================================================
185
+ // Cost Optimization Strategies
186
+ // ============================================================================
187
+
188
+ /**
189
+ * Strategy 1: Use cheaper models for simple tasks
190
+ */
191
+ async function useModelTiering(
192
+ prompt: string,
193
+ complexity: 'simple' | 'medium' | 'complex'
194
+ ): Promise<string> {
195
+ const modelMap = {
196
+ simple: 'claude-3-5-haiku-latest', // Cheapest
197
+ medium: 'gpt-4o-mini', // Mid-tier
198
+ complex: 'claude-sonnet-4-20250514', // Best quality
199
+ }
200
+
201
+ configure({ model: modelMap[complexity] })
202
+ return write`${prompt}`
203
+ }
204
+
205
+ /**
206
+ * Strategy 2: Fallback chain for cost optimization
207
+ */
208
+ async function useFallbackForCost(prompt: string): Promise<string> {
209
+ const chain = new FallbackChain([
210
+ {
211
+ name: 'haiku',
212
+ execute: async () => {
213
+ configure({ model: 'claude-3-5-haiku-latest' })
214
+ return write`${prompt}`
215
+ },
216
+ },
217
+ {
218
+ name: 'sonnet',
219
+ execute: async () => {
220
+ configure({ model: 'sonnet' })
221
+ return write`${prompt}`
222
+ },
223
+ },
224
+ ])
225
+
226
+ return chain.execute()
227
+ }
228
+
229
+ /**
230
+ * Strategy 3: Prompt compression
231
+ */
232
+ function compressPrompt(prompt: string): string {
233
+ // Remove extra whitespace
234
+ let compressed = prompt.replace(/\s+/g, ' ').trim()
235
+
236
+ // Remove filler words (simplified)
237
+ const fillers = ['please', 'kindly', 'just', 'simply', 'basically']
238
+ for (const filler of fillers) {
239
+ compressed = compressed.replace(new RegExp(`\\b${filler}\\b`, 'gi'), '')
240
+ }
241
+
242
+ return compressed.replace(/\s+/g, ' ').trim()
243
+ }
244
+
245
+ /**
246
+ * Strategy 4: Cache similar requests
247
+ */
248
+ class CostAwareCache {
249
+ private cache = new Map<string, { result: string; savedCost: number }>()
250
+
251
+ /**
252
+ * Get cached result or null
253
+ */
254
+ get(prompt: string): { result: string; savedCost: number } | null {
255
+ const key = this.hashPrompt(prompt)
256
+ return this.cache.get(key) || null
257
+ }
258
+
259
+ /**
260
+ * Cache a result
261
+ */
262
+ set(prompt: string, result: string, cost: number): void {
263
+ const key = this.hashPrompt(prompt)
264
+ this.cache.set(key, { result, savedCost: cost })
265
+ }
266
+
267
+ /**
268
+ * Get total savings
269
+ */
270
+ getTotalSavings(): number {
271
+ let total = 0
272
+ for (const entry of this.cache.values()) {
273
+ total += entry.savedCost
274
+ }
275
+ return total
276
+ }
277
+
278
+ private hashPrompt(prompt: string): string {
279
+ // Simple hash for demo
280
+ return prompt.toLowerCase().replace(/\s+/g, ' ').trim().substring(0, 100)
281
+ }
282
+ }
283
+
284
+ // ============================================================================
285
+ // withBudget Usage
286
+ // ============================================================================
287
+
288
+ async function demonstrateWithBudget(): Promise<void> {
289
+ console.log('\n--- Using withBudget() wrapper ---\n')
290
+
291
+ try {
292
+ const result = await withBudget(
293
+ {
294
+ maxCost: 0.01, // $0.01 budget
295
+ maxTokens: 1000,
296
+ userId: 'user-123',
297
+ tenantId: 'tenant-456',
298
+ },
299
+ async (tracker, ctx) => {
300
+ console.log(` Request ID: ${ctx?.requestId}`)
301
+ console.log(` User: ${ctx?.userId}`)
302
+ console.log(` Budget: $0.01 / 1000 tokens`)
303
+
304
+ // Track usage
305
+ tracker.recordUsage({ inputTokens: 100, outputTokens: 50 })
306
+
307
+ const remaining = tracker.getRemainingBudget()
308
+ console.log(` Remaining: $${remaining.cost.toFixed(4)} / ${remaining.tokens} tokens`)
309
+
310
+ // Generate something
311
+ const response = await write`Say hello briefly`
312
+ tracker.recordUsage({ inputTokens: 50, outputTokens: 30 })
313
+
314
+ return response
315
+ }
316
+ )
317
+
318
+ console.log(` Result: "${result.substring(0, 50)}..."`)
319
+ } catch (error) {
320
+ if (error instanceof BudgetExceededError) {
321
+ console.log(' Budget exceeded!')
322
+ } else {
323
+ throw error
324
+ }
325
+ }
326
+ }
327
+
328
+ // ============================================================================
329
+ // Per-User Budget Management
330
+ // ============================================================================
331
+
332
+ class UserBudgetManager {
333
+ private userBudgets = new Map<string, BudgetTracker>()
334
+ private defaultBudget: number
335
+
336
+ constructor(defaultBudget: number = 1.0) {
337
+ this.defaultBudget = defaultBudget
338
+ }
339
+
340
+ /**
341
+ * Get or create budget tracker for user
342
+ */
343
+ getTracker(userId: string): BudgetTracker {
344
+ if (!this.userBudgets.has(userId)) {
345
+ this.userBudgets.set(
346
+ userId,
347
+ new BudgetTracker({
348
+ maxCost: this.defaultBudget,
349
+ alertThresholds: [0.8, 1.0],
350
+ onAlert: (alert) => {
351
+ console.log(` [USER ${userId}] Budget alert: ${(alert.threshold * 100).toFixed(0)}%`)
352
+ },
353
+ })
354
+ )
355
+ }
356
+ return this.userBudgets.get(userId)!
357
+ }
358
+
359
+ /**
360
+ * Check if user can make request
361
+ */
362
+ canRequest(userId: string, estimatedTokens: number): boolean {
363
+ const tracker = this.getTracker(userId)
364
+ try {
365
+ tracker.checkBudget({ estimatedTokens })
366
+ return true
367
+ } catch {
368
+ return false
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Get all user budgets
374
+ */
375
+ getAllUsage(): Record<string, { cost: number; remaining: number }> {
376
+ const usage: Record<string, { cost: number; remaining: number }> = {}
377
+ for (const [userId, tracker] of this.userBudgets) {
378
+ const remaining = tracker.getRemainingBudget()
379
+ usage[userId] = {
380
+ cost: tracker.getTotalCost(),
381
+ remaining: remaining.cost,
382
+ }
383
+ }
384
+ return usage
385
+ }
386
+ }
387
+
388
+ // ============================================================================
389
+ // Main Example
390
+ // ============================================================================
391
+
392
+ async function main() {
393
+ console.log('\n=== Budget-Constrained Generation Example ===\n')
394
+
395
+ // Configure the AI provider
396
+ configure({
397
+ model: 'sonnet',
398
+ provider: 'anthropic',
399
+ })
400
+
401
+ // Example 1: Basic budget tracking
402
+ console.log('=== Example 1: Basic Budget Tracking ===')
403
+
404
+ const generator = new BudgetAwareGenerator(0.1) // $0.10 budget
405
+
406
+ const tasks: GenerationTask[] = [
407
+ { id: 'task-1', prompt: 'Write a haiku about coding', priority: 'high' },
408
+ { id: 'task-2', prompt: 'Explain REST APIs in one sentence', priority: 'medium' },
409
+ { id: 'task-3', prompt: 'List 3 programming languages', priority: 'low' },
410
+ { id: 'task-4', prompt: 'What is TypeScript?', priority: 'medium' },
411
+ { id: 'task-5', prompt: 'Define AI briefly', priority: 'low' },
412
+ ]
413
+
414
+ console.log(`\nProcessing ${tasks.length} tasks with $0.10 budget:\n`)
415
+
416
+ for (const task of tasks) {
417
+ const result = await generator.generate(task)
418
+ if (result) {
419
+ console.log(` Result: "${result.substring(0, 40)}..."\n`)
420
+ }
421
+ }
422
+
423
+ const report = generator.getReport()
424
+ console.log('\nCost Report:')
425
+ console.log(` Total Cost: $${report.totalCost.toFixed(4)}`)
426
+ console.log(` Requests: ${report.requestCount}`)
427
+ console.log(` Avg Cost/Request: $${report.avgCostPerRequest.toFixed(4)}`)
428
+ console.log(` Budget Utilization: ${report.budgetUtilization.toFixed(1)}%`)
429
+
430
+ const alerts = generator.getAlerts()
431
+ if (alerts.length > 0) {
432
+ console.log('\nAlerts:')
433
+ alerts.forEach((a) => console.log(` - ${a}`))
434
+ }
435
+
436
+ // Example 2: withBudget wrapper
437
+ console.log('\n=== Example 2: withBudget() Wrapper ===')
438
+ await demonstrateWithBudget()
439
+
440
+ // Example 3: Cost optimization strategies
441
+ console.log('\n=== Example 3: Cost Optimization Strategies ===')
442
+
443
+ // Prompt compression
444
+ const longPrompt =
445
+ 'Please kindly write me a haiku poem about coding. Just make it simple and basically about programming.'
446
+ const compressed = compressPrompt(longPrompt)
447
+ console.log(`\nPrompt compression:`)
448
+ console.log(` Original: "${longPrompt}" (${longPrompt.length} chars)`)
449
+ console.log(` Compressed: "${compressed}" (${compressed.length} chars)`)
450
+ console.log(
451
+ ` Savings: ${(((longPrompt.length - compressed.length) / longPrompt.length) * 100).toFixed(
452
+ 0
453
+ )}%`
454
+ )
455
+
456
+ // Example 4: Per-user budgets
457
+ console.log('\n=== Example 4: Per-User Budget Management ===')
458
+
459
+ const userManager = new UserBudgetManager(0.05) // $0.05 per user
460
+
461
+ // Simulate user requests
462
+ const users = ['user-1', 'user-2', 'user-3']
463
+ for (const userId of users) {
464
+ const tracker = userManager.getTracker(userId)
465
+ tracker.recordUsage({
466
+ inputTokens: Math.floor(Math.random() * 500) + 100,
467
+ outputTokens: Math.floor(Math.random() * 200) + 50,
468
+ model: 'claude-sonnet-4-20250514',
469
+ })
470
+ }
471
+
472
+ const allUsage = userManager.getAllUsage()
473
+ console.log('\nPer-User Budget Status:')
474
+ for (const [userId, usage] of Object.entries(allUsage)) {
475
+ console.log(
476
+ ` ${userId}: Spent $${usage.cost.toFixed(4)}, Remaining $${usage.remaining.toFixed(4)}`
477
+ )
478
+ }
479
+
480
+ // Example 5: Token estimation
481
+ console.log('\n=== Example 5: Token Estimation ===')
482
+
483
+ const counter = new TokenCounter()
484
+ const sampleText = 'The quick brown fox jumps over the lazy dog.'
485
+ const estimated = counter.estimateTokens(sampleText)
486
+
487
+ console.log(`Text: "${sampleText}"`)
488
+ console.log(`Estimated tokens: ${estimated}`)
489
+ console.log(`Cost estimate (Sonnet): $${((estimated * 3) / 1000000).toFixed(6)} input`)
490
+
491
+ // Summary
492
+ console.log('\n=== Budget Management Summary ===')
493
+ console.log(`
494
+ Key strategies for cost control:
495
+
496
+ 1. Set hard limits with BudgetTracker
497
+ - maxCost: Maximum spend in USD
498
+ - maxTokens: Maximum token usage
499
+ - alertThresholds: Early warnings
500
+
501
+ 2. Use withBudget() for scoped tracking
502
+ - Automatic enforcement
503
+ - Request context tracking
504
+ - Nested budget support
505
+
506
+ 3. Optimize costs with:
507
+ - Model tiering (Haiku < GPT-4o-mini < Sonnet)
508
+ - Prompt compression
509
+ - Caching similar requests
510
+ - Fallback chains
511
+
512
+ 4. Monitor per-user/tenant budgets
513
+ - Fair usage enforcement
514
+ - Quota management
515
+ - Usage reporting
516
+ `)
517
+ }
518
+
519
+ main()
520
+ .then(() => {
521
+ console.log('\n=== Example Complete ===\n')
522
+ process.exit(0)
523
+ })
524
+ .catch((error) => {
525
+ console.error('\nError:', error.message)
526
+ process.exit(1)
527
+ })