ai-functions 2.0.2 → 2.1.3

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 (130) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/CHANGELOG.md +38 -0
  3. package/LICENSE +21 -0
  4. package/README.md +361 -159
  5. package/dist/ai-promise.d.ts +47 -0
  6. package/dist/ai-promise.d.ts.map +1 -1
  7. package/dist/ai-promise.js +291 -3
  8. package/dist/ai-promise.js.map +1 -1
  9. package/dist/ai.d.ts +17 -18
  10. package/dist/ai.d.ts.map +1 -1
  11. package/dist/ai.js +93 -39
  12. package/dist/ai.js.map +1 -1
  13. package/dist/batch-map.d.ts +46 -4
  14. package/dist/batch-map.d.ts.map +1 -1
  15. package/dist/batch-map.js +35 -2
  16. package/dist/batch-map.js.map +1 -1
  17. package/dist/batch-queue.d.ts +116 -12
  18. package/dist/batch-queue.d.ts.map +1 -1
  19. package/dist/batch-queue.js +47 -2
  20. package/dist/batch-queue.js.map +1 -1
  21. package/dist/budget.d.ts +272 -0
  22. package/dist/budget.d.ts.map +1 -0
  23. package/dist/budget.js +500 -0
  24. package/dist/budget.js.map +1 -0
  25. package/dist/cache.d.ts +272 -0
  26. package/dist/cache.d.ts.map +1 -0
  27. package/dist/cache.js +412 -0
  28. package/dist/cache.js.map +1 -0
  29. package/dist/context.d.ts +32 -1
  30. package/dist/context.d.ts.map +1 -1
  31. package/dist/context.js +16 -1
  32. package/dist/context.js.map +1 -1
  33. package/dist/eval/runner.d.ts +2 -1
  34. package/dist/eval/runner.d.ts.map +1 -1
  35. package/dist/eval/runner.js.map +1 -1
  36. package/dist/generate.d.ts.map +1 -1
  37. package/dist/generate.js +6 -10
  38. package/dist/generate.js.map +1 -1
  39. package/dist/index.d.ts +27 -20
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +72 -42
  42. package/dist/index.js.map +1 -1
  43. package/dist/primitives.d.ts +17 -0
  44. package/dist/primitives.d.ts.map +1 -1
  45. package/dist/primitives.js +19 -1
  46. package/dist/primitives.js.map +1 -1
  47. package/dist/retry.d.ts +303 -0
  48. package/dist/retry.d.ts.map +1 -0
  49. package/dist/retry.js +539 -0
  50. package/dist/retry.js.map +1 -0
  51. package/dist/schema.d.ts.map +1 -1
  52. package/dist/schema.js +1 -9
  53. package/dist/schema.js.map +1 -1
  54. package/dist/tool-orchestration.d.ts +391 -0
  55. package/dist/tool-orchestration.d.ts.map +1 -0
  56. package/dist/tool-orchestration.js +663 -0
  57. package/dist/tool-orchestration.js.map +1 -0
  58. package/dist/types.d.ts +50 -33
  59. package/dist/types.d.ts.map +1 -1
  60. package/evalite.config.js +14 -0
  61. package/evals/classification.eval.js +97 -0
  62. package/evals/marketing.eval.js +289 -0
  63. package/evals/math.eval.js +83 -0
  64. package/evals/run-evals.js +151 -0
  65. package/evals/structured-output.eval.js +131 -0
  66. package/evals/writing.eval.js +105 -0
  67. package/examples/batch-blog-posts.js +128 -0
  68. package/package.json +26 -26
  69. package/src/ai-promise.ts +359 -3
  70. package/src/ai.ts +155 -110
  71. package/src/batch/anthropic.js +256 -0
  72. package/src/batch/bedrock.js +584 -0
  73. package/src/batch/cloudflare.js +287 -0
  74. package/src/batch/google.js +359 -0
  75. package/src/batch/index.js +30 -0
  76. package/src/batch/memory.js +187 -0
  77. package/src/batch/openai.js +402 -0
  78. package/src/batch-map.ts +46 -4
  79. package/src/batch-queue.ts +116 -12
  80. package/src/budget.ts +727 -0
  81. package/src/cache.ts +653 -0
  82. package/src/context.ts +33 -1
  83. package/src/eval/index.js +7 -0
  84. package/src/eval/models.js +119 -0
  85. package/src/eval/runner.js +147 -0
  86. package/src/eval/runner.ts +3 -2
  87. package/src/generate.ts +7 -12
  88. package/src/index.ts +231 -53
  89. package/src/primitives.ts +19 -1
  90. package/src/retry.ts +776 -0
  91. package/src/schema.ts +1 -10
  92. package/src/tool-orchestration.ts +1008 -0
  93. package/src/types.ts +59 -41
  94. package/test/ai-proxy.test.js +157 -0
  95. package/test/async-iterators.test.js +261 -0
  96. package/test/backward-compat.test.ts +147 -0
  97. package/test/batch-autosubmit-errors.test.ts +598 -0
  98. package/test/batch-background.test.js +352 -0
  99. package/test/batch-blog-posts.test.js +293 -0
  100. package/test/blog-generation.test.js +390 -0
  101. package/test/browse-read.test.js +480 -0
  102. package/test/budget-tracking.test.ts +800 -0
  103. package/test/cache.test.ts +712 -0
  104. package/test/context-isolation.test.ts +687 -0
  105. package/test/core-functions.test.js +490 -0
  106. package/test/decide.test.js +260 -0
  107. package/test/define.test.js +232 -0
  108. package/test/e2e-bedrock-manual.js +136 -0
  109. package/test/e2e-bedrock.test.js +164 -0
  110. package/test/e2e-flex-gateway.js +131 -0
  111. package/test/e2e-flex-manual.js +156 -0
  112. package/test/e2e-flex.test.js +174 -0
  113. package/test/e2e-google-manual.js +150 -0
  114. package/test/e2e-google.test.js +181 -0
  115. package/test/embeddings.test.js +220 -0
  116. package/test/evals/define-function.eval.test.js +309 -0
  117. package/test/evals/deterministic.eval.test.ts +376 -0
  118. package/test/evals/primitives.eval.test.js +360 -0
  119. package/test/function-types.test.js +407 -0
  120. package/test/generate-core.test.js +213 -0
  121. package/test/generate.test.js +143 -0
  122. package/test/generic-order.test.ts +342 -0
  123. package/test/implicit-batch.test.js +326 -0
  124. package/test/json-parse-error-handling.test.ts +463 -0
  125. package/test/retry.test.ts +1016 -0
  126. package/test/schema.test.js +96 -0
  127. package/test/streaming.test.ts +316 -0
  128. package/test/tagged-templates.test.js +240 -0
  129. package/test/tool-orchestration.test.ts +770 -0
  130. package/vitest.config.js +39 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Anthropic Message Batches API Adapter
3
+ *
4
+ * Implements batch processing using Anthropic's Message Batches API:
5
+ * - 50% cost discount
6
+ * - 24-hour turnaround
7
+ * - Up to 10,000 requests per batch
8
+ *
9
+ * @see https://docs.anthropic.com/en/docs/build-with-claude/message-batches
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { registerBatchAdapter, } from '../batch-queue.js';
14
+ import { schema as convertSchema } from '../schema.js';
15
+ // ============================================================================
16
+ // Anthropic Client
17
+ // ============================================================================
18
+ let anthropicApiKey;
19
+ let anthropicBaseUrl = 'https://api.anthropic.com/v1';
20
+ const ANTHROPIC_VERSION = '2023-06-01';
21
+ const ANTHROPIC_BETA = 'message-batches-2024-09-24';
22
+ /**
23
+ * Configure the Anthropic client
24
+ */
25
+ export function configureAnthropic(options) {
26
+ if (options.apiKey)
27
+ anthropicApiKey = options.apiKey;
28
+ if (options.baseUrl)
29
+ anthropicBaseUrl = options.baseUrl;
30
+ }
31
+ function getApiKey() {
32
+ const key = anthropicApiKey || process.env.ANTHROPIC_API_KEY;
33
+ if (!key) {
34
+ throw new Error('Anthropic API key not configured. Set ANTHROPIC_API_KEY or call configureAnthropic()');
35
+ }
36
+ return key;
37
+ }
38
+ async function anthropicRequest(method, path, body) {
39
+ const url = `${anthropicBaseUrl}${path}`;
40
+ const response = await fetch(url, {
41
+ method,
42
+ headers: {
43
+ 'x-api-key': getApiKey(),
44
+ 'anthropic-version': ANTHROPIC_VERSION,
45
+ 'anthropic-beta': ANTHROPIC_BETA,
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ body: body ? JSON.stringify(body) : undefined,
49
+ });
50
+ if (!response.ok) {
51
+ const error = await response.text();
52
+ throw new Error(`Anthropic API error: ${response.status} ${error}`);
53
+ }
54
+ return response.json();
55
+ }
56
+ // ============================================================================
57
+ // Status Mapping
58
+ // ============================================================================
59
+ function mapStatus(batch) {
60
+ if (batch.cancel_initiated_at) {
61
+ return batch.processing_status === 'ended' ? 'cancelled' : 'cancelling';
62
+ }
63
+ if (batch.processing_status === 'ended') {
64
+ return 'completed';
65
+ }
66
+ return 'in_progress';
67
+ }
68
+ // ============================================================================
69
+ // Anthropic Batch Adapter
70
+ // ============================================================================
71
+ const anthropicAdapter = {
72
+ async submit(items, options) {
73
+ const model = options.model || 'claude-sonnet-4-20250514';
74
+ const maxTokens = 4096;
75
+ // Build batch requests
76
+ const requests = items.map((item) => {
77
+ const request = {
78
+ custom_id: item.id,
79
+ params: {
80
+ model,
81
+ max_tokens: item.options?.maxTokens || maxTokens,
82
+ messages: [{ role: 'user', content: item.prompt }],
83
+ system: item.options?.system,
84
+ temperature: item.options?.temperature,
85
+ },
86
+ };
87
+ // Add JSON schema as a tool if provided
88
+ if (item.schema) {
89
+ const zodSchema = convertSchema(item.schema);
90
+ const jsonSchema = zodToJsonSchema(zodSchema);
91
+ request.params.tools = [
92
+ {
93
+ name: 'structured_response',
94
+ description: 'Generate a structured response matching the schema',
95
+ input_schema: jsonSchema,
96
+ },
97
+ ];
98
+ request.params.tool_choice = { type: 'tool', name: 'structured_response' };
99
+ }
100
+ return request;
101
+ });
102
+ // Create the batch
103
+ const batch = await anthropicRequest('POST', '/messages/batches', {
104
+ requests,
105
+ });
106
+ const job = {
107
+ id: batch.id,
108
+ provider: 'anthropic',
109
+ status: mapStatus(batch),
110
+ totalItems: items.length,
111
+ completedItems: batch.request_counts.succeeded,
112
+ failedItems: batch.request_counts.errored + batch.request_counts.expired + batch.request_counts.canceled,
113
+ createdAt: new Date(batch.created_at),
114
+ expiresAt: new Date(batch.expires_at),
115
+ webhookUrl: options.webhookUrl,
116
+ };
117
+ // Create completion promise
118
+ const completion = this.waitForCompletion(batch.id);
119
+ return { job, completion };
120
+ },
121
+ async getStatus(batchId) {
122
+ const batch = await anthropicRequest('GET', `/messages/batches/${batchId}`);
123
+ return {
124
+ id: batch.id,
125
+ provider: 'anthropic',
126
+ status: mapStatus(batch),
127
+ totalItems: batch.request_counts.processing +
128
+ batch.request_counts.succeeded +
129
+ batch.request_counts.errored +
130
+ batch.request_counts.canceled +
131
+ batch.request_counts.expired,
132
+ completedItems: batch.request_counts.succeeded,
133
+ failedItems: batch.request_counts.errored + batch.request_counts.expired + batch.request_counts.canceled,
134
+ createdAt: new Date(batch.created_at),
135
+ completedAt: batch.ended_at ? new Date(batch.ended_at) : undefined,
136
+ expiresAt: new Date(batch.expires_at),
137
+ };
138
+ },
139
+ async cancel(batchId) {
140
+ await anthropicRequest('POST', `/messages/batches/${batchId}/cancel`);
141
+ },
142
+ async getResults(batchId) {
143
+ const status = await this.getStatus(batchId);
144
+ if (status.status !== 'completed' && status.status !== 'cancelled') {
145
+ throw new Error(`Batch not complete. Status: ${status.status}`);
146
+ }
147
+ // Fetch results (Anthropic returns them directly via the API)
148
+ const batch = await anthropicRequest('GET', `/messages/batches/${batchId}`);
149
+ if (!batch.results_url) {
150
+ throw new Error('No results URL available');
151
+ }
152
+ // Download results from the URL
153
+ const response = await fetch(batch.results_url, {
154
+ headers: {
155
+ 'x-api-key': getApiKey(),
156
+ 'anthropic-version': ANTHROPIC_VERSION,
157
+ 'anthropic-beta': ANTHROPIC_BETA,
158
+ },
159
+ });
160
+ if (!response.ok) {
161
+ throw new Error(`Failed to fetch results: ${response.status}`);
162
+ }
163
+ const content = await response.text();
164
+ const lines = content.trim().split('\n');
165
+ const results = [];
166
+ for (const line of lines) {
167
+ const result = JSON.parse(line);
168
+ if (result.result.type === 'succeeded' && result.result.message) {
169
+ const message = result.result.message;
170
+ let extractedResult;
171
+ // Extract from tool use or text
172
+ const toolUse = message.content.find((c) => c.type === 'tool_use');
173
+ const textContent = message.content.find((c) => c.type === 'text');
174
+ if (toolUse?.input) {
175
+ extractedResult = toolUse.input;
176
+ }
177
+ else if (textContent?.text) {
178
+ // Try to parse as JSON
179
+ try {
180
+ extractedResult = JSON.parse(textContent.text);
181
+ }
182
+ catch {
183
+ extractedResult = textContent.text;
184
+ }
185
+ }
186
+ results.push({
187
+ id: result.custom_id,
188
+ customId: result.custom_id,
189
+ status: 'completed',
190
+ result: extractedResult,
191
+ usage: {
192
+ promptTokens: message.usage.input_tokens,
193
+ completionTokens: message.usage.output_tokens,
194
+ totalTokens: message.usage.input_tokens + message.usage.output_tokens,
195
+ },
196
+ });
197
+ }
198
+ else {
199
+ results.push({
200
+ id: result.custom_id,
201
+ customId: result.custom_id,
202
+ status: 'failed',
203
+ error: result.result.error?.message || `Request ${result.result.type}`,
204
+ });
205
+ }
206
+ }
207
+ return results;
208
+ },
209
+ async waitForCompletion(batchId, pollInterval = 5000) {
210
+ while (true) {
211
+ const status = await this.getStatus(batchId);
212
+ if (status.status === 'completed' || status.status === 'cancelled') {
213
+ return this.getResults(batchId);
214
+ }
215
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
216
+ }
217
+ },
218
+ };
219
+ // ============================================================================
220
+ // Helpers
221
+ // ============================================================================
222
+ /**
223
+ * Simple Zod to JSON Schema converter
224
+ */
225
+ function zodToJsonSchema(zodSchema) {
226
+ const schema = zodSchema;
227
+ if (!schema._def) {
228
+ return { type: 'object' };
229
+ }
230
+ const typeName = schema._def.typeName;
231
+ switch (typeName) {
232
+ case 'ZodString':
233
+ return { type: 'string' };
234
+ case 'ZodNumber':
235
+ return { type: 'number' };
236
+ case 'ZodBoolean':
237
+ return { type: 'boolean' };
238
+ case 'ZodArray':
239
+ return { type: 'array', items: zodToJsonSchema(schema._def.type) };
240
+ case 'ZodObject': {
241
+ const shape = schema._def.shape();
242
+ const properties = {};
243
+ for (const [key, value] of Object.entries(shape)) {
244
+ properties[key] = zodToJsonSchema(value);
245
+ }
246
+ return { type: 'object', properties, required: Object.keys(properties) };
247
+ }
248
+ default:
249
+ return { type: 'object' };
250
+ }
251
+ }
252
+ // ============================================================================
253
+ // Register Adapter
254
+ // ============================================================================
255
+ registerBatchAdapter('anthropic', anthropicAdapter);
256
+ export { anthropicAdapter };