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
@@ -1,17 +1,19 @@
1
1
  /**
2
2
  * AWS Bedrock Batch Inference Adapter
3
3
  *
4
- * Implements batch processing using AWS Bedrock's batch inference API.
5
- * Bedrock batch inference provides cost-effective processing for large workloads.
4
+ * Bedrock has a true batch inference API (S3-driven) and a runtime invoke API.
5
+ * The "batch" adapter here uses concurrent runtime invocations as a fallback
6
+ * (no S3 setup required); `createBedrockBatchJob` is exported separately for
7
+ * callers who want to drive the real S3-based batch flow directly.
6
8
  *
7
9
  * @see https://docs.aws.amazon.com/bedrock/latest/userguide/batch-inference.html
8
10
  *
9
11
  * @packageDocumentation
10
12
  */
11
- import { registerBatchAdapter, registerFlexAdapter, } from '../batch-queue.js';
12
- import { schema as convertSchema } from '../schema.js';
13
+ import { getLogger } from '../logger.js';
14
+ import { LocalJobStore, processConcurrently, registerBatchAdapter, registerFlexAdapter, tryParseJson, } from './provider.js';
13
15
  // ============================================================================
14
- // AWS Configuration
16
+ // AWS configuration
15
17
  // ============================================================================
16
18
  let awsRegion;
17
19
  let awsAccessKeyId;
@@ -19,12 +21,9 @@ let awsSecretAccessKey;
19
21
  let awsSessionToken;
20
22
  let s3Bucket;
21
23
  let roleArn;
22
- // AI Gateway configuration (optional - for routing through Cloudflare AI Gateway)
23
24
  let gatewayUrl;
24
25
  let gatewayToken;
25
- /**
26
- * Configure AWS credentials and settings
27
- */
26
+ /** Configure AWS credentials and settings. */
28
27
  export function configureAWSBedrock(options) {
29
28
  if (options.region)
30
29
  awsRegion = options.region;
@@ -44,16 +43,14 @@ export function configureAWSBedrock(options) {
44
43
  gatewayToken = options.gatewayToken;
45
44
  }
46
45
  function getConfig() {
47
- const region = awsRegion || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
48
- const accessKeyId = awsAccessKeyId || process.env.AWS_ACCESS_KEY_ID;
49
- const secretAccessKey = awsSecretAccessKey || process.env.AWS_SECRET_ACCESS_KEY;
50
- const sessionToken = awsSessionToken || process.env.AWS_SESSION_TOKEN;
51
- const bucket = s3Bucket || process.env.BEDROCK_BATCH_S3_BUCKET;
52
- const role = roleArn || process.env.BEDROCK_BATCH_ROLE_ARN;
53
- // Check for AI Gateway configuration
54
- const gwUrl = gatewayUrl || process.env.AI_GATEWAY_URL;
55
- const gwToken = gatewayToken || process.env.AI_GATEWAY_TOKEN;
56
- // If using gateway, we don't need AWS credentials
46
+ const region = awsRegion || process.env['AWS_REGION'] || process.env['AWS_DEFAULT_REGION'] || 'us-east-1';
47
+ const accessKeyId = awsAccessKeyId || process.env['AWS_ACCESS_KEY_ID'];
48
+ const secretAccessKey = awsSecretAccessKey || process.env['AWS_SECRET_ACCESS_KEY'];
49
+ const sessionToken = awsSessionToken || process.env['AWS_SESSION_TOKEN'];
50
+ const bucket = s3Bucket || process.env['BEDROCK_BATCH_S3_BUCKET'];
51
+ const role = roleArn || process.env['BEDROCK_BATCH_ROLE_ARN'];
52
+ const gwUrl = gatewayUrl || process.env['AI_GATEWAY_URL'];
53
+ const gwToken = gatewayToken || process.env['AI_GATEWAY_TOKEN'];
57
54
  if (gwUrl && gwToken) {
58
55
  return {
59
56
  region,
@@ -72,14 +69,21 @@ function getConfig() {
72
69
  if (!bucket) {
73
70
  throw new Error('S3 bucket for Bedrock batch not configured. Set BEDROCK_BATCH_S3_BUCKET');
74
71
  }
75
- return { region, accessKeyId, secretAccessKey, sessionToken, bucket, role, gatewayUrl: undefined, gatewayToken: undefined };
72
+ return {
73
+ region,
74
+ accessKeyId,
75
+ secretAccessKey,
76
+ sessionToken,
77
+ bucket,
78
+ role,
79
+ gatewayUrl: undefined,
80
+ gatewayToken: undefined,
81
+ };
76
82
  }
77
83
  // ============================================================================
78
- // AWS Signature V4 (Simplified)
84
+ // AWS SigV4 (delegated to optional @smithy/signature-v4 if available)
79
85
  // ============================================================================
80
86
  async function signRequest(method, url, body, config, service) {
81
- // In production, use @aws-sdk/signature-v4 or similar
82
- // This is a simplified implementation for demonstration
83
87
  const headers = new Headers({
84
88
  'Content-Type': 'application/json',
85
89
  'X-Amz-Date': new Date().toISOString().replace(/[:-]|\.\d{3}/g, ''),
@@ -87,18 +91,13 @@ async function signRequest(method, url, body, config, service) {
87
91
  if (config.sessionToken) {
88
92
  headers.set('X-Amz-Security-Token', config.sessionToken);
89
93
  }
90
- // For actual implementation, compute proper AWS Signature V4
91
- // This requires crypto operations that vary by environment
92
- // Fallback: Use AWS SDK if available
93
94
  try {
94
- // Dynamic import to avoid build-time dependency
95
+ // Optional dependency present in production, absent in dev/test.
95
96
  // @ts-expect-error - Optional dependency
96
97
  const signatureV4Module = await import('@smithy/signature-v4');
97
98
  // @ts-expect-error - Optional dependency
98
99
  const sha256Module = await import('@aws-crypto/sha256-js');
99
- const SignatureV4 = signatureV4Module.SignatureV4;
100
- const Sha256 = sha256Module.Sha256;
101
- const signer = new SignatureV4({
100
+ const signer = new signatureV4Module.SignatureV4({
102
101
  service,
103
102
  region: config.region,
104
103
  credentials: {
@@ -106,7 +105,7 @@ async function signRequest(method, url, body, config, service) {
106
105
  secretAccessKey: config.secretAccessKey,
107
106
  sessionToken: config.sessionToken,
108
107
  },
109
- sha256: Sha256,
108
+ sha256: sha256Module.Sha256,
110
109
  });
111
110
  const signedRequest = await signer.sign({
112
111
  method,
@@ -118,185 +117,140 @@ async function signRequest(method, url, body, config, service) {
118
117
  return new Headers(signedRequest.headers);
119
118
  }
120
119
  catch {
121
- // AWS SDK not available - return basic headers
122
- // In production, the SDK should always be available
123
- console.warn('AWS SDK not available for request signing. Install @smithy/signature-v4 and @aws-crypto/sha256-js');
120
+ getLogger().warn('AWS SDK not available for request signing. Install @smithy/signature-v4 and @aws-crypto/sha256-js');
124
121
  return headers;
125
122
  }
126
123
  }
127
124
  // ============================================================================
128
- // In-memory job tracking
125
+ // Local job tracking
129
126
  // ============================================================================
130
- const pendingJobs = new Map();
131
- let jobCounter = 0;
127
+ const jobs = new LocalJobStore('bedrock_batch');
132
128
  // ============================================================================
133
- // Bedrock Batch Adapter
129
+ // Bedrock batch adapter (BatchProvider port)
134
130
  // ============================================================================
135
- /**
136
- * AWS Bedrock batch adapter
137
- *
138
- * Bedrock batch inference:
139
- * 1. Uploads input JSONL to S3
140
- * 2. Creates a batch inference job
141
- * 3. Results are written to S3
142
- * 4. Download and parse results
143
- *
144
- * Note: This requires S3 bucket access and proper IAM roles.
145
- */
146
131
  const bedrockAdapter = {
147
132
  async submit(items, options) {
148
133
  const config = getConfig();
149
- const jobId = `bedrock_batch_${++jobCounter}_${Date.now()}`;
150
- // Default to Claude on Bedrock
151
134
  const model = options.model || 'anthropic.claude-3-sonnet-20240229-v1:0';
152
- // Store job state
153
- pendingJobs.set(jobId, {
154
- items,
155
- options,
156
- results: [],
157
- status: 'pending',
158
- createdAt: new Date(),
159
- });
160
- // For true Bedrock batch processing:
161
- // 1. Create JSONL file with requests
162
- // 2. Upload to S3
163
- // 3. Create batch inference job via Bedrock API
164
- // 4. Poll for completion
165
- // 5. Download and parse results from S3
166
- // For now, we implement a concurrent processing approach
167
- // (similar to Cloudflare) that works without S3 setup
168
- const completion = processBedrockRequestsConcurrently(jobId, items, config, model, options);
135
+ const { id, state } = jobs.create(items, options);
136
+ // Drive the job state machine in the background.
137
+ const completion = (async () => {
138
+ state.status = 'in_progress';
139
+ const results = await processConcurrently(items, (item) => processBedrockItem(item, config, model), {
140
+ concurrency: 5, // Bedrock has stricter rate limits.
141
+ delayBetweenWaves: 1000,
142
+ onWaveComplete: (partial) => {
143
+ state.results = partial;
144
+ },
145
+ });
146
+ state.results = results;
147
+ state.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed';
148
+ state.completedAt = new Date();
149
+ return results;
150
+ })();
169
151
  const job = {
170
- id: jobId,
152
+ id,
171
153
  provider: 'bedrock',
172
154
  status: 'pending',
173
155
  totalItems: items.length,
174
156
  completedItems: 0,
175
157
  failedItems: 0,
176
- createdAt: new Date(),
177
- webhookUrl: options.webhookUrl,
158
+ createdAt: state.createdAt,
159
+ ...(options.webhookUrl !== undefined && { webhookUrl: options.webhookUrl }),
178
160
  };
179
161
  return { job, completion };
180
162
  },
181
163
  async getStatus(batchId) {
182
- const job = pendingJobs.get(batchId);
183
- if (!job) {
184
- throw new Error(`Batch not found: ${batchId}`);
185
- }
186
- const completedItems = job.results.filter((r) => r.status === 'completed').length;
187
- const failedItems = job.results.filter((r) => r.status === 'failed').length;
188
- return {
189
- id: batchId,
190
- provider: 'bedrock',
191
- status: job.status,
192
- totalItems: job.items.length,
193
- completedItems,
194
- failedItems,
195
- createdAt: job.createdAt,
196
- completedAt: job.completedAt,
197
- };
164
+ return jobs.snapshot(batchId, 'bedrock');
198
165
  },
199
166
  async cancel(batchId) {
200
- const job = pendingJobs.get(batchId);
201
- if (job) {
202
- job.status = 'cancelled';
203
- // If we have a Bedrock job ARN, cancel it
204
- if (job.jobArn) {
205
- const config = getConfig();
206
- const url = `https://bedrock.${config.region}.amazonaws.com/model-invocation-job/${encodeURIComponent(job.jobArn)}/stop`;
207
- try {
208
- await fetch(url, {
209
- method: 'POST',
210
- headers: await signRequest('POST', url, '', config, 'bedrock'),
211
- });
212
- }
213
- catch (error) {
214
- console.warn('Failed to cancel Bedrock job:', error);
215
- }
167
+ if (!jobs.has(batchId))
168
+ return;
169
+ const state = jobs.get(batchId);
170
+ state.status = 'cancelled';
171
+ const jobArn = state.meta?.['jobArn'];
172
+ if (jobArn) {
173
+ const config = getConfig();
174
+ const url = `https://bedrock.${config.region}.amazonaws.com/model-invocation-job/${encodeURIComponent(jobArn)}/stop`;
175
+ try {
176
+ await fetch(url, {
177
+ method: 'POST',
178
+ headers: await signRequest('POST', url, '', config, 'bedrock'),
179
+ });
180
+ }
181
+ catch (error) {
182
+ getLogger().warn('Failed to cancel Bedrock job:', error);
216
183
  }
217
184
  }
218
185
  },
219
186
  async getResults(batchId) {
220
- const job = pendingJobs.get(batchId);
221
- if (!job) {
222
- throw new Error(`Batch not found: ${batchId}`);
223
- }
224
- return job.results;
187
+ return jobs.get(batchId).results;
225
188
  },
226
189
  async waitForCompletion(batchId, pollInterval = 5000) {
227
- const job = pendingJobs.get(batchId);
228
- if (!job) {
229
- throw new Error(`Batch not found: ${batchId}`);
230
- }
231
- while (job.status !== 'completed' && job.status !== 'failed' && job.status !== 'cancelled') {
232
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
233
- }
234
- return job.results;
190
+ return jobs.waitForCompletion(batchId, pollInterval);
235
191
  },
236
192
  };
237
193
  // ============================================================================
238
- // Processing (Concurrent Mode)
194
+ // Per-item processing
239
195
  // ============================================================================
240
- /**
241
- * Process Bedrock requests concurrently
242
- * This is a fallback when true batch inference isn't configured
243
- */
244
- async function processBedrockRequestsConcurrently(jobId, items, config, model, options) {
245
- const job = pendingJobs.get(jobId);
246
- if (!job) {
247
- throw new Error(`Job not found: ${jobId}`);
248
- }
249
- job.status = 'in_progress';
250
- // Process with concurrency limit
251
- const CONCURRENCY = 5; // Bedrock has stricter rate limits
252
- const results = [];
253
- for (let i = 0; i < items.length; i += CONCURRENCY) {
254
- const batch = items.slice(i, i + CONCURRENCY);
255
- const batchResults = await Promise.all(batch.map(async (item) => {
256
- try {
257
- return await processBedrockItem(item, config, model);
258
- }
259
- catch (error) {
260
- return {
261
- id: item.id,
262
- customId: item.id,
263
- status: 'failed',
264
- error: error instanceof Error ? error.message : 'Unknown error',
265
- };
266
- }
267
- }));
268
- results.push(...batchResults);
269
- job.results = results;
270
- // Respect rate limits
271
- if (i + CONCURRENCY < items.length) {
272
- await new Promise((resolve) => setTimeout(resolve, 1000));
273
- }
274
- }
275
- job.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed';
276
- job.completedAt = new Date();
277
- return results;
278
- }
279
196
  async function processBedrockItem(item, config, model) {
280
- // Check if using AI Gateway
281
197
  if (config.gatewayUrl && config.gatewayToken) {
282
198
  return processBedrockItemViaGateway(item, config, model);
283
199
  }
284
200
  const url = `https://bedrock-runtime.${config.region}.amazonaws.com/model/${encodeURIComponent(model)}/invoke`;
285
- // Build the request body based on the model type
286
- let body;
201
+ const body = buildBedrockRequestBody(item, model);
202
+ const bodyStr = JSON.stringify(body);
203
+ const headers = await signRequest('POST', url, bodyStr, config, 'bedrock');
204
+ const response = await fetch(url, { method: 'POST', headers, body: bodyStr });
205
+ if (!response.ok) {
206
+ const error = await response.text();
207
+ throw new Error(`Bedrock API error: ${response.status} ${error}`);
208
+ }
209
+ return parseBedrockResponse(item, await response.json());
210
+ }
211
+ /**
212
+ * Process a Bedrock item via Cloudflare AI Gateway.
213
+ *
214
+ * Note: AI Gateway routes the request but doesn't handle authentication —
215
+ * Bedrock still requires AWS SigV4 signing.
216
+ * @see https://developers.cloudflare.com/ai-gateway/usage/providers/bedrock/
217
+ */
218
+ async function processBedrockItemViaGateway(item, config, model) {
219
+ const url = `${config.gatewayUrl}/aws-bedrock/bedrock-runtime/${config.region}/model/${encodeURIComponent(model)}/invoke`;
220
+ const body = {
221
+ anthropic_version: 'bedrock-2023-05-31',
222
+ max_tokens: item.options?.maxTokens || 4096,
223
+ messages: [{ role: 'user', content: item.prompt }],
224
+ ...(item.options?.system !== undefined && { system: item.options.system }),
225
+ ...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
226
+ };
227
+ const bodyStr = JSON.stringify(body);
228
+ if (!config.accessKeyId || !config.secretAccessKey) {
229
+ throw new Error('Bedrock via AI Gateway still requires AWS credentials for SigV4 signing. ' +
230
+ 'Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.');
231
+ }
232
+ const headers = await signRequest('POST', url, bodyStr, config, 'bedrock');
233
+ headers.set('cf-aig-authorization', `Bearer ${config.gatewayToken}`);
234
+ const response = await fetch(url, { method: 'POST', headers, body: bodyStr });
235
+ if (!response.ok) {
236
+ const error = await response.text();
237
+ throw new Error(`Bedrock via Gateway error: ${response.status} ${error}`);
238
+ }
239
+ return parseBedrockResponse(item, await response.json());
240
+ }
241
+ /** Build the Bedrock invoke body for the model family. */
242
+ function buildBedrockRequestBody(item, model) {
287
243
  if (model.includes('anthropic')) {
288
- // Anthropic models on Bedrock
289
- body = {
244
+ return {
290
245
  anthropic_version: 'bedrock-2023-05-31',
291
246
  max_tokens: item.options?.maxTokens || 4096,
292
247
  messages: [{ role: 'user', content: item.prompt }],
293
- system: item.options?.system,
294
- temperature: item.options?.temperature,
248
+ ...(item.options?.system !== undefined && { system: item.options.system }),
249
+ ...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
295
250
  };
296
251
  }
297
- else if (model.includes('amazon')) {
298
- // Amazon Titan models
299
- body = {
252
+ if (model.includes('amazon')) {
253
+ return {
300
254
  inputText: item.prompt,
301
255
  textGenerationConfig: {
302
256
  maxTokenCount: item.options?.maxTokens || 4096,
@@ -304,50 +258,35 @@ async function processBedrockItem(item, config, model) {
304
258
  },
305
259
  };
306
260
  }
307
- else if (model.includes('meta')) {
308
- // Meta Llama models
309
- body = {
261
+ if (model.includes('meta')) {
262
+ return {
310
263
  prompt: item.prompt,
311
264
  max_gen_len: item.options?.maxTokens || 4096,
312
265
  temperature: item.options?.temperature || 0.7,
313
266
  };
314
267
  }
315
- else if (model.includes('mistral')) {
316
- // Mistral models
317
- body = {
268
+ if (model.includes('mistral')) {
269
+ return {
318
270
  prompt: `<s>[INST] ${item.prompt} [/INST]`,
319
271
  max_tokens: item.options?.maxTokens || 4096,
320
272
  temperature: item.options?.temperature || 0.7,
321
273
  };
322
274
  }
323
- else {
324
- // Generic format (Claude-style)
325
- body = {
326
- anthropic_version: 'bedrock-2023-05-31',
327
- max_tokens: item.options?.maxTokens || 4096,
328
- messages: [{ role: 'user', content: item.prompt }],
329
- temperature: item.options?.temperature,
330
- };
331
- }
332
- const bodyStr = JSON.stringify(body);
333
- const headers = await signRequest('POST', url, bodyStr, config, 'bedrock');
334
- const response = await fetch(url, {
335
- method: 'POST',
336
- headers,
337
- body: bodyStr,
338
- });
339
- if (!response.ok) {
340
- const error = await response.text();
341
- throw new Error(`Bedrock API error: ${response.status} ${error}`);
342
- }
343
- const data = await response.json();
344
- // Extract content based on model response format
275
+ // Default: Claude-style.
276
+ return {
277
+ anthropic_version: 'bedrock-2023-05-31',
278
+ max_tokens: item.options?.maxTokens || 4096,
279
+ messages: [{ role: 'user', content: item.prompt }],
280
+ ...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
281
+ };
282
+ }
283
+ /** Parse a Bedrock invoke response across model families. */
284
+ function parseBedrockResponse(item, raw) {
285
+ const data = raw;
345
286
  let content;
346
287
  let usage;
347
288
  if (data.content) {
348
- // Anthropic format
349
- const textContent = data.content.find((c) => c.type === 'text');
350
- content = textContent?.text;
289
+ content = data.content.find((c) => c.type === 'text')?.text;
351
290
  if (data.usage) {
352
291
  usage = {
353
292
  promptTokens: data.usage.input_tokens,
@@ -357,16 +296,14 @@ async function processBedrockItem(item, config, model) {
357
296
  }
358
297
  }
359
298
  else if (data.results?.[0]) {
360
- // Titan format
361
299
  content = data.results[0].outputText;
362
300
  usage = {
363
- promptTokens: 0, // Titan doesn't return this
301
+ promptTokens: 0,
364
302
  completionTokens: data.results[0].tokenCount || 0,
365
303
  totalTokens: data.results[0].tokenCount || 0,
366
304
  };
367
305
  }
368
306
  else if (data.generation) {
369
- // Llama/Mistral format
370
307
  content = data.generation;
371
308
  if (data.generation_token_count !== undefined) {
372
309
  usage = {
@@ -376,103 +313,23 @@ async function processBedrockItem(item, config, model) {
376
313
  };
377
314
  }
378
315
  }
379
- let result = content;
380
- // Try to parse JSON if schema was provided
381
- if (item.schema && content) {
382
- try {
383
- result = JSON.parse(content);
384
- }
385
- catch {
386
- // Keep as string
387
- }
388
- }
389
316
  return {
390
317
  id: item.id,
391
318
  customId: item.id,
392
319
  status: 'completed',
393
- result,
394
- usage,
395
- };
396
- }
397
- /**
398
- * Process a Bedrock item via Cloudflare AI Gateway
399
- *
400
- * NOTE: Unlike OpenAI and Google, Bedrock via AI Gateway still requires AWS Signature V4 signing.
401
- * The gateway routes the request but doesn't handle authentication.
402
- * @see https://developers.cloudflare.com/ai-gateway/usage/providers/bedrock/
403
- *
404
- * Gateway URL format: {gateway_url}/aws-bedrock/bedrock-runtime/{region}/model/{model}/invoke
405
- */
406
- async function processBedrockItemViaGateway(item, config, model) {
407
- // AI Gateway URL for Bedrock - requires full path including region
408
- // Format: {gateway_url}/aws-bedrock/bedrock-runtime/{region}/model/{model}/invoke
409
- const url = `${config.gatewayUrl}/aws-bedrock/bedrock-runtime/${config.region}/model/${encodeURIComponent(model)}/invoke`;
410
- // Build the request body (Anthropic format for Claude models)
411
- const body = {
412
- anthropic_version: 'bedrock-2023-05-31',
413
- max_tokens: item.options?.maxTokens || 4096,
414
- messages: [{ role: 'user', content: item.prompt }],
415
- system: item.options?.system,
416
- temperature: item.options?.temperature,
417
- };
418
- const bodyStr = JSON.stringify(body);
419
- // NOTE: Bedrock via Gateway still requires AWS SigV4 signing
420
- // We need both the gateway token AND AWS credentials
421
- if (!config.accessKeyId || !config.secretAccessKey) {
422
- throw new Error('Bedrock via AI Gateway still requires AWS credentials for SigV4 signing. ' +
423
- 'Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.');
424
- }
425
- const headers = await signRequest('POST', url, bodyStr, config, 'bedrock');
426
- headers.set('cf-aig-authorization', `Bearer ${config.gatewayToken}`);
427
- const response = await fetch(url, {
428
- method: 'POST',
429
- headers,
430
- body: bodyStr,
431
- });
432
- if (!response.ok) {
433
- const error = await response.text();
434
- throw new Error(`Bedrock via Gateway error: ${response.status} ${error}`);
435
- }
436
- const data = await response.json();
437
- // Extract content (Anthropic format)
438
- const textContent = data.content?.find((c) => c.type === 'text');
439
- let content = textContent?.text;
440
- let usage;
441
- if (data.usage) {
442
- usage = {
443
- promptTokens: data.usage.input_tokens,
444
- completionTokens: data.usage.output_tokens,
445
- totalTokens: data.usage.input_tokens + data.usage.output_tokens,
446
- };
447
- }
448
- let result = content;
449
- // Try to parse JSON if schema was provided
450
- if (item.schema && content) {
451
- try {
452
- result = JSON.parse(content);
453
- }
454
- catch {
455
- // Keep as string
456
- }
457
- }
458
- return {
459
- id: item.id,
460
- customId: item.id,
461
- status: 'completed',
462
- result,
463
- usage,
320
+ result: tryParseJson(content, !!item.schema),
321
+ ...(usage && { usage }),
464
322
  };
465
323
  }
466
324
  // ============================================================================
467
- // True Batch Inference (S3-based)
325
+ // True S3-based batch inference (separate from the BatchProvider adapter)
468
326
  // ============================================================================
469
327
  /**
470
- * Create and submit a true Bedrock batch inference job
471
- * This requires S3 bucket access and proper IAM setup
328
+ * Create and submit a true Bedrock batch inference job.
329
+ * Requires S3 bucket access and proper IAM setup.
472
330
  */
473
331
  export async function createBedrockBatchJob(items, model, options) {
474
332
  const config = getConfig();
475
- // Build JSONL content
476
333
  const jsonlLines = items.map((item) => {
477
334
  const request = {
478
335
  recordId: item.id,
@@ -480,16 +337,16 @@ export async function createBedrockBatchJob(items, model, options) {
480
337
  anthropic_version: 'bedrock-2023-05-31',
481
338
  max_tokens: item.options?.maxTokens || 4096,
482
339
  messages: [{ role: 'user', content: item.prompt }],
483
- system: item.options?.system,
484
- temperature: item.options?.temperature,
340
+ ...(item.options?.system !== undefined && { system: item.options.system }),
341
+ ...(item.options?.temperature !== undefined && {
342
+ temperature: item.options.temperature,
343
+ }),
485
344
  },
486
345
  };
487
346
  return JSON.stringify(request);
488
347
  });
489
348
  const inputKey = `${options.s3InputPrefix || 'bedrock-batch/input'}/${options.jobName}.jsonl`;
490
349
  const outputPrefix = `${options.s3OutputPrefix || 'bedrock-batch/output'}/${options.jobName}/`;
491
- // Upload to S3
492
- // In production, use @aws-sdk/client-s3
493
350
  const s3Url = `https://${config.bucket}.s3.${config.region}.amazonaws.com/${inputKey}`;
494
351
  const content = jsonlLines.join('\n');
495
352
  const s3Response = await fetch(s3Url, {
@@ -500,21 +357,16 @@ export async function createBedrockBatchJob(items, model, options) {
500
357
  if (!s3Response.ok) {
501
358
  throw new Error(`Failed to upload to S3: ${s3Response.status}`);
502
359
  }
503
- // Create batch inference job
504
360
  const jobUrl = `https://bedrock.${config.region}.amazonaws.com/model-invocation-job`;
505
361
  const jobBody = JSON.stringify({
506
362
  jobName: options.jobName,
507
363
  modelId: model,
508
364
  roleArn: options.roleArn,
509
365
  inputDataConfig: {
510
- s3InputDataConfig: {
511
- s3Uri: `s3://${config.bucket}/${inputKey}`,
512
- },
366
+ s3InputDataConfig: { s3Uri: `s3://${config.bucket}/${inputKey}` },
513
367
  },
514
368
  outputDataConfig: {
515
- s3OutputDataConfig: {
516
- s3Uri: `s3://${config.bucket}/${outputPrefix}`,
517
- },
369
+ s3OutputDataConfig: { s3Uri: `s3://${config.bucket}/${outputPrefix}` },
518
370
  },
519
371
  });
520
372
  const jobResponse = await fetch(jobUrl, {
@@ -526,59 +378,24 @@ export async function createBedrockBatchJob(items, model, options) {
526
378
  const error = await jobResponse.text();
527
379
  throw new Error(`Failed to create Bedrock batch job: ${jobResponse.status} ${error}`);
528
380
  }
529
- const jobData = await jobResponse.json();
381
+ const jobData = (await jobResponse.json());
530
382
  return jobData;
531
383
  }
532
384
  // ============================================================================
533
- // Register Adapter
534
- // ============================================================================
385
+ // Bedrock flex adapter (FlexAdapter port)
535
386
  // ============================================================================
536
- // Bedrock Flex Adapter
537
- // ============================================================================
538
- /**
539
- * AWS Bedrock Flex Adapter
540
- *
541
- * Flex processing uses concurrent requests for medium-sized batches (5-500 items).
542
- * This provides a balance between:
543
- * - Immediate execution (fast but full price, <5 items)
544
- * - Full batch inference (50% discount but 24hr turnaround, 500+ items)
545
- *
546
- * Flex tier uses concurrent API calls with rate limiting, providing results
547
- * in minutes rather than hours while still benefiting from efficient processing.
548
- */
549
387
  const bedrockFlexAdapter = {
550
388
  async submitFlex(items, options) {
551
389
  const config = getConfig();
552
390
  const model = options.model || 'anthropic.claude-3-sonnet-20240229-v1:0';
553
- const CONCURRENCY = 8; // Bedrock has stricter rate limits than OpenAI
554
- const results = [];
555
- // Process items concurrently with rate limiting
556
- for (let i = 0; i < items.length; i += CONCURRENCY) {
557
- const batch = items.slice(i, i + CONCURRENCY);
558
- const batchResults = await Promise.all(batch.map(async (item) => {
559
- try {
560
- return await processBedrockItem(item, config, model);
561
- }
562
- catch (error) {
563
- return {
564
- id: item.id,
565
- customId: item.id,
566
- status: 'failed',
567
- error: error instanceof Error ? error.message : 'Unknown error',
568
- };
569
- }
570
- }));
571
- results.push(...batchResults);
572
- // Add delay between batches to respect rate limits
573
- if (i + CONCURRENCY < items.length) {
574
- await new Promise((resolve) => setTimeout(resolve, 500));
575
- }
576
- }
577
- return results;
391
+ return processConcurrently(items, (item) => processBedrockItem(item, config, model), {
392
+ concurrency: 8,
393
+ delayBetweenWaves: 500,
394
+ });
578
395
  },
579
396
  };
580
397
  // ============================================================================
581
- // Register Adapters
398
+ // Register adapters
582
399
  // ============================================================================
583
400
  registerBatchAdapter('bedrock', bedrockAdapter);
584
401
  registerFlexAdapter('bedrock', bedrockFlexAdapter);