ai-functions 0.3.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +190 -86
  4. package/TODO.md +138 -0
  5. package/dist/ai-promise.d.ts +219 -0
  6. package/dist/ai-promise.d.ts.map +1 -0
  7. package/dist/ai-promise.js +610 -0
  8. package/dist/ai-promise.js.map +1 -0
  9. package/dist/ai.d.ts +285 -0
  10. package/dist/ai.d.ts.map +1 -0
  11. package/dist/ai.js +842 -0
  12. package/dist/ai.js.map +1 -0
  13. package/dist/batch/anthropic.d.ts +23 -0
  14. package/dist/batch/anthropic.d.ts.map +1 -0
  15. package/dist/batch/anthropic.js +257 -0
  16. package/dist/batch/anthropic.js.map +1 -0
  17. package/dist/batch/bedrock.d.ts +64 -0
  18. package/dist/batch/bedrock.d.ts.map +1 -0
  19. package/dist/batch/bedrock.js +586 -0
  20. package/dist/batch/bedrock.js.map +1 -0
  21. package/dist/batch/cloudflare.d.ts +37 -0
  22. package/dist/batch/cloudflare.d.ts.map +1 -0
  23. package/dist/batch/cloudflare.js +289 -0
  24. package/dist/batch/cloudflare.js.map +1 -0
  25. package/dist/batch/google.d.ts +41 -0
  26. package/dist/batch/google.d.ts.map +1 -0
  27. package/dist/batch/google.js +360 -0
  28. package/dist/batch/google.js.map +1 -0
  29. package/dist/batch/index.d.ts +31 -0
  30. package/dist/batch/index.d.ts.map +1 -0
  31. package/dist/batch/index.js +31 -0
  32. package/dist/batch/index.js.map +1 -0
  33. package/dist/batch/memory.d.ts +44 -0
  34. package/dist/batch/memory.d.ts.map +1 -0
  35. package/dist/batch/memory.js +188 -0
  36. package/dist/batch/memory.js.map +1 -0
  37. package/dist/batch/openai.d.ts +37 -0
  38. package/dist/batch/openai.d.ts.map +1 -0
  39. package/dist/batch/openai.js +403 -0
  40. package/dist/batch/openai.js.map +1 -0
  41. package/dist/batch-map.d.ts +125 -0
  42. package/dist/batch-map.d.ts.map +1 -0
  43. package/dist/batch-map.js +406 -0
  44. package/dist/batch-map.js.map +1 -0
  45. package/dist/batch-queue.d.ts +273 -0
  46. package/dist/batch-queue.d.ts.map +1 -0
  47. package/dist/batch-queue.js +271 -0
  48. package/dist/batch-queue.js.map +1 -0
  49. package/dist/context.d.ts +133 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +267 -0
  52. package/dist/context.js.map +1 -0
  53. package/dist/embeddings.d.ts +123 -0
  54. package/dist/embeddings.d.ts.map +1 -0
  55. package/dist/embeddings.js +170 -0
  56. package/dist/embeddings.js.map +1 -0
  57. package/dist/eval/index.d.ts +8 -0
  58. package/dist/eval/index.d.ts.map +1 -0
  59. package/dist/eval/index.js +8 -0
  60. package/dist/eval/index.js.map +1 -0
  61. package/dist/eval/models.d.ts +66 -0
  62. package/dist/eval/models.d.ts.map +1 -0
  63. package/dist/eval/models.js +120 -0
  64. package/dist/eval/models.js.map +1 -0
  65. package/dist/eval/runner.d.ts +64 -0
  66. package/dist/eval/runner.d.ts.map +1 -0
  67. package/dist/eval/runner.js +148 -0
  68. package/dist/eval/runner.js.map +1 -0
  69. package/dist/generate.d.ts +168 -0
  70. package/dist/generate.d.ts.map +1 -0
  71. package/dist/generate.js +174 -0
  72. package/dist/generate.js.map +1 -0
  73. package/dist/index.d.ts +29 -4
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +53 -52
  76. package/dist/index.js.map +1 -1
  77. package/dist/primitives.d.ts +292 -0
  78. package/dist/primitives.d.ts.map +1 -0
  79. package/dist/primitives.js +471 -0
  80. package/dist/primitives.js.map +1 -0
  81. package/dist/providers/cloudflare.d.ts +9 -0
  82. package/dist/providers/cloudflare.d.ts.map +1 -0
  83. package/dist/providers/cloudflare.js +9 -0
  84. package/dist/providers/cloudflare.js.map +1 -0
  85. package/dist/providers/index.d.ts +9 -0
  86. package/dist/providers/index.d.ts.map +1 -0
  87. package/dist/providers/index.js +9 -0
  88. package/dist/providers/index.js.map +1 -0
  89. package/dist/rpc/auth.d.ts +69 -0
  90. package/dist/rpc/auth.d.ts.map +1 -0
  91. package/dist/rpc/auth.js +136 -0
  92. package/dist/rpc/auth.js.map +1 -0
  93. package/dist/rpc/client.d.ts +62 -0
  94. package/dist/rpc/client.d.ts.map +1 -0
  95. package/dist/rpc/client.js +103 -0
  96. package/dist/rpc/client.js.map +1 -0
  97. package/dist/rpc/deferred.d.ts +60 -0
  98. package/dist/rpc/deferred.d.ts.map +1 -0
  99. package/dist/rpc/deferred.js +96 -0
  100. package/dist/rpc/deferred.js.map +1 -0
  101. package/dist/rpc/index.d.ts +22 -0
  102. package/dist/rpc/index.d.ts.map +1 -0
  103. package/dist/rpc/index.js +38 -0
  104. package/dist/rpc/index.js.map +1 -0
  105. package/dist/rpc/local.d.ts +42 -0
  106. package/dist/rpc/local.d.ts.map +1 -0
  107. package/dist/rpc/local.js +50 -0
  108. package/dist/rpc/local.js.map +1 -0
  109. package/dist/rpc/server.d.ts +165 -0
  110. package/dist/rpc/server.d.ts.map +1 -0
  111. package/dist/rpc/server.js +405 -0
  112. package/dist/rpc/server.js.map +1 -0
  113. package/dist/rpc/session.d.ts +32 -0
  114. package/dist/rpc/session.d.ts.map +1 -0
  115. package/dist/rpc/session.js +43 -0
  116. package/dist/rpc/session.js.map +1 -0
  117. package/dist/rpc/transport.d.ts +306 -0
  118. package/dist/rpc/transport.d.ts.map +1 -0
  119. package/dist/rpc/transport.js +731 -0
  120. package/dist/rpc/transport.js.map +1 -0
  121. package/dist/schema.d.ts +54 -0
  122. package/dist/schema.d.ts.map +1 -0
  123. package/dist/schema.js +109 -0
  124. package/dist/schema.js.map +1 -0
  125. package/dist/template.d.ts +73 -0
  126. package/dist/template.d.ts.map +1 -0
  127. package/dist/template.js +129 -0
  128. package/dist/template.js.map +1 -0
  129. package/dist/types.d.ts +474 -106
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +4 -8
  132. package/dist/types.js.map +1 -1
  133. package/evalite.config.ts +19 -0
  134. package/evals/README.md +212 -0
  135. package/evals/classification.eval.ts +108 -0
  136. package/evals/marketing.eval.ts +370 -0
  137. package/evals/math.eval.ts +94 -0
  138. package/evals/run-evals.ts +166 -0
  139. package/evals/structured-output.eval.ts +143 -0
  140. package/evals/writing.eval.ts +117 -0
  141. package/examples/batch-blog-posts.ts +160 -0
  142. package/package.json +57 -57
  143. package/src/ai-promise.ts +784 -0
  144. package/src/ai.ts +1183 -0
  145. package/src/batch/anthropic.ts +375 -0
  146. package/src/batch/bedrock.ts +801 -0
  147. package/src/batch/cloudflare.ts +421 -0
  148. package/src/batch/google.ts +491 -0
  149. package/src/batch/index.ts +31 -0
  150. package/src/batch/memory.ts +253 -0
  151. package/src/batch/openai.ts +557 -0
  152. package/src/batch-map.ts +534 -0
  153. package/src/batch-queue.ts +493 -0
  154. package/src/context.ts +332 -0
  155. package/src/embeddings.ts +244 -0
  156. package/src/eval/index.ts +8 -0
  157. package/src/eval/models.ts +158 -0
  158. package/src/eval/runner.ts +217 -0
  159. package/src/generate.ts +245 -0
  160. package/src/index.ts +154 -0
  161. package/src/primitives.ts +612 -0
  162. package/src/providers/cloudflare.ts +15 -0
  163. package/src/providers/index.ts +14 -0
  164. package/src/schema.ts +147 -0
  165. package/src/template.ts +209 -0
  166. package/src/types.ts +540 -0
  167. package/test/README.md +105 -0
  168. package/test/ai-proxy.test.ts +192 -0
  169. package/test/async-iterators.test.ts +327 -0
  170. package/test/batch-background.test.ts +482 -0
  171. package/test/batch-blog-posts.test.ts +387 -0
  172. package/test/blog-generation.test.ts +510 -0
  173. package/test/browse-read.test.ts +611 -0
  174. package/test/core-functions.test.ts +694 -0
  175. package/test/decide.test.ts +393 -0
  176. package/test/define.test.ts +274 -0
  177. package/test/e2e-bedrock-manual.ts +163 -0
  178. package/test/e2e-bedrock.test.ts +191 -0
  179. package/test/e2e-flex-gateway.ts +157 -0
  180. package/test/e2e-flex-manual.ts +183 -0
  181. package/test/e2e-flex.test.ts +209 -0
  182. package/test/e2e-google-manual.ts +178 -0
  183. package/test/e2e-google.test.ts +216 -0
  184. package/test/embeddings.test.ts +284 -0
  185. package/test/evals/define-function.eval.test.ts +379 -0
  186. package/test/evals/primitives.eval.test.ts +384 -0
  187. package/test/function-types.test.ts +492 -0
  188. package/test/generate-core.test.ts +319 -0
  189. package/test/generate.test.ts +163 -0
  190. package/test/implicit-batch.test.ts +422 -0
  191. package/test/schema.test.ts +109 -0
  192. package/test/tagged-templates.test.ts +302 -0
  193. package/tsconfig.json +10 -0
  194. package/vitest.config.ts +42 -0
  195. package/LICENSE +0 -21
  196. package/bin/cli.js +0 -5
  197. package/dist/cli/index.d.ts +0 -10
  198. package/dist/cli/index.d.ts.map +0 -1
  199. package/dist/cli/index.js +0 -38
  200. package/dist/cli/index.js.map +0 -1
  201. package/dist/cli/index.test.d.ts +0 -2
  202. package/dist/cli/index.test.d.ts.map +0 -1
  203. package/dist/cli/index.test.js +0 -35
  204. package/dist/cli/index.test.js.map +0 -1
  205. package/dist/constants/models.d.ts +0 -10
  206. package/dist/constants/models.d.ts.map +0 -1
  207. package/dist/constants/models.js +0 -12
  208. package/dist/constants/models.js.map +0 -1
  209. package/dist/converters/index.d.ts +0 -3
  210. package/dist/converters/index.d.ts.map +0 -1
  211. package/dist/converters/index.js +0 -3
  212. package/dist/converters/index.js.map +0 -1
  213. package/dist/converters/model.d.ts +0 -4
  214. package/dist/converters/model.d.ts.map +0 -1
  215. package/dist/converters/model.js +0 -19
  216. package/dist/converters/model.js.map +0 -1
  217. package/dist/converters/schema.d.ts +0 -4
  218. package/dist/converters/schema.d.ts.map +0 -1
  219. package/dist/converters/schema.js +0 -25
  220. package/dist/converters/schema.js.map +0 -1
  221. package/dist/core/responses.d.ts +0 -5
  222. package/dist/core/responses.d.ts.map +0 -1
  223. package/dist/core/responses.js +0 -16
  224. package/dist/core/responses.js.map +0 -1
  225. package/dist/core/responses.test.d.ts +0 -2
  226. package/dist/core/responses.test.d.ts.map +0 -1
  227. package/dist/core/responses.test.js +0 -31
  228. package/dist/core/responses.test.js.map +0 -1
  229. package/dist/errors.d.ts +0 -6
  230. package/dist/errors.d.ts.map +0 -1
  231. package/dist/errors.js +0 -9
  232. package/dist/errors.js.map +0 -1
  233. package/dist/examples/streaming.test.d.ts +0 -2
  234. package/dist/examples/streaming.test.d.ts.map +0 -1
  235. package/dist/examples/streaming.test.js +0 -176
  236. package/dist/examples/streaming.test.js.map +0 -1
  237. package/dist/factory/__tests__/index.test.d.ts +0 -2
  238. package/dist/factory/__tests__/index.test.d.ts.map +0 -1
  239. package/dist/factory/__tests__/index.test.js +0 -430
  240. package/dist/factory/__tests__/index.test.js.map +0 -1
  241. package/dist/factory/__tests__/list.test.d.ts +0 -2
  242. package/dist/factory/__tests__/list.test.d.ts.map +0 -1
  243. package/dist/factory/__tests__/list.test.js +0 -92
  244. package/dist/factory/__tests__/list.test.js.map +0 -1
  245. package/dist/factory/index.d.ts +0 -20
  246. package/dist/factory/index.d.ts.map +0 -1
  247. package/dist/factory/index.js +0 -287
  248. package/dist/factory/index.js.map +0 -1
  249. package/dist/factory/index.test.d.ts +0 -2
  250. package/dist/factory/index.test.d.ts.map +0 -1
  251. package/dist/factory/index.test.js +0 -287
  252. package/dist/factory/index.test.js.map +0 -1
  253. package/dist/factory/list.d.ts +0 -3
  254. package/dist/factory/list.d.ts.map +0 -1
  255. package/dist/factory/list.js +0 -221
  256. package/dist/factory/list.js.map +0 -1
  257. package/dist/factory/list.test.d.ts +0 -2
  258. package/dist/factory/list.test.d.ts.map +0 -1
  259. package/dist/factory/list.test.js +0 -84
  260. package/dist/factory/list.test.js.map +0 -1
  261. package/dist/generate/index.d.ts +0 -5
  262. package/dist/generate/index.d.ts.map +0 -1
  263. package/dist/generate/index.js +0 -17
  264. package/dist/generate/index.js.map +0 -1
  265. package/dist/index.test.d.ts +0 -2
  266. package/dist/index.test.d.ts.map +0 -1
  267. package/dist/index.test.js +0 -59
  268. package/dist/index.test.js.map +0 -1
  269. package/dist/list/await.d.ts +0 -3
  270. package/dist/list/await.d.ts.map +0 -1
  271. package/dist/list/await.js +0 -28
  272. package/dist/list/await.js.map +0 -1
  273. package/dist/list/constants.d.ts +0 -4
  274. package/dist/list/constants.d.ts.map +0 -1
  275. package/dist/list/constants.js +0 -5
  276. package/dist/list/constants.js.map +0 -1
  277. package/dist/list/create-function.d.ts +0 -3
  278. package/dist/list/create-function.d.ts.map +0 -1
  279. package/dist/list/create-function.js +0 -11
  280. package/dist/list/create-function.js.map +0 -1
  281. package/dist/list/index.d.ts +0 -4
  282. package/dist/list/index.d.ts.map +0 -1
  283. package/dist/list/index.js +0 -5
  284. package/dist/list/index.js.map +0 -1
  285. package/dist/list/prompt.d.ts +0 -3
  286. package/dist/list/prompt.d.ts.map +0 -1
  287. package/dist/list/prompt.js +0 -6
  288. package/dist/list/prompt.js.map +0 -1
  289. package/dist/list/schemas.d.ts +0 -4
  290. package/dist/list/schemas.d.ts.map +0 -1
  291. package/dist/list/schemas.js +0 -8
  292. package/dist/list/schemas.js.map +0 -1
  293. package/dist/list/stream.d.ts +0 -3
  294. package/dist/list/stream.d.ts.map +0 -1
  295. package/dist/list/stream.js +0 -33
  296. package/dist/list/stream.js.map +0 -1
  297. package/dist/list/types.d.ts +0 -11
  298. package/dist/list/types.d.ts.map +0 -1
  299. package/dist/list/types.js +0 -2
  300. package/dist/list/types.js.map +0 -1
  301. package/dist/list/validation.d.ts +0 -3
  302. package/dist/list/validation.d.ts.map +0 -1
  303. package/dist/list/validation.js +0 -12
  304. package/dist/list/validation.js.map +0 -1
  305. package/dist/providers/config.d.ts +0 -4
  306. package/dist/providers/config.d.ts.map +0 -1
  307. package/dist/providers/config.js +0 -21
  308. package/dist/providers/config.js.map +0 -1
  309. package/dist/providers/config.test.d.ts +0 -2
  310. package/dist/providers/config.test.d.ts.map +0 -1
  311. package/dist/providers/config.test.js +0 -37
  312. package/dist/providers/config.test.js.map +0 -1
  313. package/dist/proxy/constants.d.ts +0 -4
  314. package/dist/proxy/constants.d.ts.map +0 -1
  315. package/dist/proxy/constants.js +0 -5
  316. package/dist/proxy/constants.js.map +0 -1
  317. package/dist/proxy/create-function.d.ts +0 -4
  318. package/dist/proxy/create-function.d.ts.map +0 -1
  319. package/dist/proxy/create-function.js +0 -24
  320. package/dist/proxy/create-function.js.map +0 -1
  321. package/dist/proxy/create-proxy.d.ts +0 -2
  322. package/dist/proxy/create-proxy.d.ts.map +0 -1
  323. package/dist/proxy/create-proxy.js +0 -11
  324. package/dist/proxy/create-proxy.js.map +0 -1
  325. package/dist/proxy/function-generator.d.ts +0 -9
  326. package/dist/proxy/function-generator.d.ts.map +0 -1
  327. package/dist/proxy/function-generator.js +0 -29
  328. package/dist/proxy/function-generator.js.map +0 -1
  329. package/dist/proxy/index.d.ts +0 -4
  330. package/dist/proxy/index.d.ts.map +0 -1
  331. package/dist/proxy/index.js +0 -4
  332. package/dist/proxy/index.js.map +0 -1
  333. package/dist/proxy/prompt.d.ts +0 -2
  334. package/dist/proxy/prompt.d.ts.map +0 -1
  335. package/dist/proxy/prompt.js +0 -6
  336. package/dist/proxy/prompt.js.map +0 -1
  337. package/dist/proxy/types.d.ts +0 -7
  338. package/dist/proxy/types.d.ts.map +0 -1
  339. package/dist/proxy/types.js +0 -2
  340. package/dist/proxy/types.js.map +0 -1
  341. package/dist/queue/manager.d.ts +0 -5
  342. package/dist/queue/manager.d.ts.map +0 -1
  343. package/dist/queue/manager.js +0 -37
  344. package/dist/queue/manager.js.map +0 -1
  345. package/dist/queue/manager.test.d.ts +0 -2
  346. package/dist/queue/manager.test.d.ts.map +0 -1
  347. package/dist/queue/manager.test.js +0 -52
  348. package/dist/queue/manager.test.js.map +0 -1
  349. package/dist/schema-converter.d.ts +0 -4
  350. package/dist/schema-converter.d.ts.map +0 -1
  351. package/dist/schema-converter.js +0 -30
  352. package/dist/schema-converter.js.map +0 -1
  353. package/dist/stream/index.d.ts +0 -7
  354. package/dist/stream/index.d.ts.map +0 -1
  355. package/dist/stream/index.js +0 -23
  356. package/dist/stream/index.js.map +0 -1
  357. package/dist/streaming/utils.d.ts +0 -4
  358. package/dist/streaming/utils.d.ts.map +0 -1
  359. package/dist/streaming/utils.js +0 -131
  360. package/dist/streaming/utils.js.map +0 -1
  361. package/dist/streaming/utils.test.d.ts +0 -2
  362. package/dist/streaming/utils.test.d.ts.map +0 -1
  363. package/dist/streaming/utils.test.js +0 -84
  364. package/dist/streaming/utils.test.js.map +0 -1
  365. package/dist/templates/result.d.ts +0 -7
  366. package/dist/templates/result.d.ts.map +0 -1
  367. package/dist/templates/result.js +0 -40
  368. package/dist/templates/result.js.map +0 -1
  369. package/dist/templates/result.test.d.ts +0 -2
  370. package/dist/templates/result.test.d.ts.map +0 -1
  371. package/dist/templates/result.test.js +0 -75
  372. package/dist/templates/result.test.js.map +0 -1
  373. package/dist/test/setup.d.ts +0 -2
  374. package/dist/test/setup.d.ts.map +0 -1
  375. package/dist/test/setup.js +0 -21
  376. package/dist/test/setup.js.map +0 -1
  377. package/dist/test-types.d.ts +0 -13
  378. package/dist/test-types.d.ts.map +0 -1
  379. package/dist/test-types.js +0 -55
  380. package/dist/test-types.js.map +0 -1
  381. package/dist/types/index.d.ts +0 -4
  382. package/dist/types/index.d.ts.map +0 -1
  383. package/dist/types/index.js +0 -4
  384. package/dist/types/index.js.map +0 -1
  385. package/dist/types/list.d.ts +0 -10
  386. package/dist/types/list.d.ts.map +0 -1
  387. package/dist/types/list.js +0 -2
  388. package/dist/types/list.js.map +0 -1
  389. package/dist/types/model.d.ts +0 -7
  390. package/dist/types/model.d.ts.map +0 -1
  391. package/dist/types/model.js +0 -2
  392. package/dist/types/model.js.map +0 -1
  393. package/dist/types/options.d.ts +0 -25
  394. package/dist/types/options.d.ts.map +0 -1
  395. package/dist/types/options.js +0 -2
  396. package/dist/types/options.js.map +0 -1
  397. package/dist/types/schema.d.ts +0 -5
  398. package/dist/types/schema.d.ts.map +0 -1
  399. package/dist/types/schema.js +0 -2
  400. package/dist/types/schema.js.map +0 -1
  401. package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
  402. package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
  403. package/dist/utils/__tests__/request-handler.test.js +0 -134
  404. package/dist/utils/__tests__/request-handler.test.js.map +0 -1
  405. package/dist/utils/__tests__/schema.test.d.ts +0 -2
  406. package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
  407. package/dist/utils/__tests__/schema.test.js +0 -49
  408. package/dist/utils/__tests__/schema.test.js.map +0 -1
  409. package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
  410. package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
  411. package/dist/utils/__tests__/stream-progress.test.js +0 -85
  412. package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
  413. package/dist/utils/index.d.ts +0 -2
  414. package/dist/utils/index.d.ts.map +0 -1
  415. package/dist/utils/index.js +0 -2
  416. package/dist/utils/index.js.map +0 -1
  417. package/dist/utils/request-handler.d.ts +0 -17
  418. package/dist/utils/request-handler.d.ts.map +0 -1
  419. package/dist/utils/request-handler.js +0 -105
  420. package/dist/utils/request-handler.js.map +0 -1
  421. package/dist/utils/schema.d.ts +0 -11
  422. package/dist/utils/schema.d.ts.map +0 -1
  423. package/dist/utils/schema.js +0 -51
  424. package/dist/utils/schema.js.map +0 -1
  425. package/dist/utils/stream-progress.d.ts +0 -17
  426. package/dist/utils/stream-progress.d.ts.map +0 -1
  427. package/dist/utils/stream-progress.js +0 -86
  428. package/dist/utils/stream-progress.js.map +0 -1
  429. package/dist/utils/validation.d.ts +0 -3
  430. package/dist/utils/validation.d.ts.map +0 -1
  431. package/dist/utils/validation.js +0 -30
  432. package/dist/utils/validation.js.map +0 -1
@@ -0,0 +1,209 @@
1
+ /**
2
+ * E2E Tests for Flex Tier Processing
3
+ *
4
+ * These tests hit real APIs to verify the flex tier actually works.
5
+ * Requires OPENAI_API_KEY environment variable.
6
+ *
7
+ * Run with: npx vitest run test/e2e-flex.test.ts
8
+ */
9
+
10
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
11
+ import { configure, resetContext, getExecutionTier, isFlexAvailable } from '../src/context.js'
12
+ import { getFlexAdapter, type BatchItem, type BatchResult } from '../src/batch-queue.js'
13
+
14
+ // Import the OpenAI adapter to register it
15
+ import { configureOpenAI } from '../src/batch/openai.js'
16
+ import '../src/batch/openai.js'
17
+
18
+ // Skip tests if no API key (either direct or via gateway)
19
+ const hasApiKey = !!(process.env.OPENAI_API_KEY || (process.env.AI_GATEWAY_URL && process.env.AI_GATEWAY_TOKEN))
20
+
21
+ describe.skipIf(!hasApiKey)('E2E Flex Tier Processing', () => {
22
+ beforeAll(() => {
23
+ // If using gateway, configure the base URL
24
+ if (process.env.AI_GATEWAY_URL && process.env.AI_GATEWAY_TOKEN && !process.env.OPENAI_API_KEY) {
25
+ configureOpenAI({
26
+ apiKey: process.env.AI_GATEWAY_TOKEN,
27
+ baseUrl: `${process.env.AI_GATEWAY_URL}/openai`,
28
+ })
29
+ }
30
+
31
+ configure({
32
+ provider: 'openai',
33
+ model: 'gpt-4o-mini', // Use mini for faster/cheaper tests
34
+ batchMode: 'auto',
35
+ flexThreshold: 3,
36
+ batchThreshold: 500,
37
+ })
38
+ })
39
+
40
+ afterAll(() => {
41
+ resetContext()
42
+ })
43
+
44
+ describe('Flex Adapter Direct', () => {
45
+ it('processes a small batch via flex adapter', async () => {
46
+ const adapter = getFlexAdapter('openai')
47
+
48
+ const items: BatchItem[] = [
49
+ {
50
+ id: 'item_1',
51
+ prompt: 'What is 2 + 2? Reply with just the number.',
52
+ status: 'pending',
53
+ },
54
+ {
55
+ id: 'item_2',
56
+ prompt: 'What is 3 + 3? Reply with just the number.',
57
+ status: 'pending',
58
+ },
59
+ {
60
+ id: 'item_3',
61
+ prompt: 'What is 4 + 4? Reply with just the number.',
62
+ status: 'pending',
63
+ },
64
+ ]
65
+
66
+ const results = await adapter.submitFlex(items, { model: 'gpt-4o-mini' })
67
+
68
+ expect(results).toHaveLength(3)
69
+ results.forEach((result) => {
70
+ expect(result.status).toBe('completed')
71
+ expect(result.result).toBeDefined()
72
+ })
73
+
74
+ // Check the actual responses contain numbers
75
+ const result1 = results.find((r) => r.id === 'item_1')
76
+ const result2 = results.find((r) => r.id === 'item_2')
77
+ const result3 = results.find((r) => r.id === 'item_3')
78
+
79
+ expect(result1?.result).toContain('4')
80
+ expect(result2?.result).toContain('6')
81
+ expect(result3?.result).toContain('8')
82
+ }, 30000) // 30 second timeout
83
+
84
+ it('processes items with structured output (JSON schema)', async () => {
85
+ const adapter = getFlexAdapter('openai')
86
+
87
+ const items: BatchItem[] = [
88
+ {
89
+ id: 'structured_1',
90
+ prompt: 'Generate a person with name "Alice" and age 30',
91
+ schema: { name: 'string', age: 'number' },
92
+ status: 'pending',
93
+ },
94
+ {
95
+ id: 'structured_2',
96
+ prompt: 'Generate a person with name "Bob" and age 25',
97
+ schema: { name: 'string', age: 'number' },
98
+ status: 'pending',
99
+ },
100
+ ]
101
+
102
+ const results = await adapter.submitFlex(items, { model: 'gpt-4o-mini' })
103
+
104
+ expect(results).toHaveLength(2)
105
+ results.forEach((result) => {
106
+ expect(result.status).toBe('completed')
107
+ expect(result.result).toBeDefined()
108
+ expect(typeof result.result).toBe('object')
109
+ })
110
+
111
+ const result1 = results.find((r) => r.id === 'structured_1')
112
+ const result2 = results.find((r) => r.id === 'structured_2')
113
+
114
+ // Results should be parsed JSON objects
115
+ expect((result1?.result as any)?.name).toBeDefined()
116
+ expect((result2?.result as any)?.name).toBeDefined()
117
+ }, 30000)
118
+
119
+ it('handles errors gracefully', async () => {
120
+ const adapter = getFlexAdapter('openai')
121
+
122
+ const items: BatchItem[] = [
123
+ {
124
+ id: 'valid',
125
+ prompt: 'Say hello',
126
+ status: 'pending',
127
+ },
128
+ ]
129
+
130
+ // This should work even with a minimal prompt
131
+ const results = await adapter.submitFlex(items, { model: 'gpt-4o-mini' })
132
+
133
+ expect(results).toHaveLength(1)
134
+ expect(results[0].status).toBe('completed')
135
+ }, 30000)
136
+
137
+ it('reports token usage', async () => {
138
+ const adapter = getFlexAdapter('openai')
139
+
140
+ const items: BatchItem[] = [
141
+ {
142
+ id: 'usage_test',
143
+ prompt: 'Count from 1 to 5',
144
+ status: 'pending',
145
+ },
146
+ ]
147
+
148
+ const results = await adapter.submitFlex(items, { model: 'gpt-4o-mini' })
149
+
150
+ expect(results[0].usage).toBeDefined()
151
+ expect(results[0].usage?.promptTokens).toBeGreaterThan(0)
152
+ expect(results[0].usage?.completionTokens).toBeGreaterThan(0)
153
+ expect(results[0].usage?.totalTokens).toBeGreaterThan(0)
154
+ }, 30000)
155
+ })
156
+
157
+ describe('Execution Tier Selection', () => {
158
+ it('selects immediate tier for < flexThreshold items', () => {
159
+ configure({ batchMode: 'auto', flexThreshold: 5, batchThreshold: 500 })
160
+ expect(getExecutionTier(1)).toBe('immediate')
161
+ expect(getExecutionTier(4)).toBe('immediate')
162
+ })
163
+
164
+ it('selects flex tier for flexThreshold to < batchThreshold items', () => {
165
+ configure({ batchMode: 'auto', flexThreshold: 5, batchThreshold: 500 })
166
+ expect(getExecutionTier(5)).toBe('flex')
167
+ expect(getExecutionTier(100)).toBe('flex')
168
+ expect(getExecutionTier(499)).toBe('flex')
169
+ })
170
+
171
+ it('selects batch tier for >= batchThreshold items', () => {
172
+ configure({ batchMode: 'auto', flexThreshold: 5, batchThreshold: 500 })
173
+ expect(getExecutionTier(500)).toBe('batch')
174
+ expect(getExecutionTier(1000)).toBe('batch')
175
+ })
176
+
177
+ it('reports flex as available for openai', () => {
178
+ configure({ provider: 'openai' })
179
+ expect(isFlexAvailable()).toBe(true)
180
+ })
181
+ })
182
+
183
+ describe('Concurrent Processing', () => {
184
+ it('processes multiple items concurrently', async () => {
185
+ const adapter = getFlexAdapter('openai')
186
+
187
+ // Create 10 items to test concurrent processing
188
+ const items: BatchItem[] = Array.from({ length: 10 }, (_, i) => ({
189
+ id: `concurrent_${i}`,
190
+ prompt: `What is ${i} + 1? Reply with just the number.`,
191
+ status: 'pending' as const,
192
+ }))
193
+
194
+ const startTime = Date.now()
195
+ const results = await adapter.submitFlex(items, { model: 'gpt-4o-mini' })
196
+ const duration = Date.now() - startTime
197
+
198
+ expect(results).toHaveLength(10)
199
+ results.forEach((result, i) => {
200
+ expect(result.status).toBe('completed')
201
+ })
202
+
203
+ // With concurrency of 10, this should complete much faster than sequential
204
+ // Sequential would be ~10-20 seconds, concurrent should be ~2-5 seconds
205
+ console.log(`Processed 10 items in ${duration}ms`)
206
+ expect(duration).toBeLessThan(20000) // Should be well under 20 seconds
207
+ }, 60000)
208
+ })
209
+ })
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Manual E2E Test for Google GenAI (Gemini) Flex Tier Processing
4
+ *
5
+ * Run with your Google API key:
6
+ *
7
+ * GOOGLE_API_KEY=... npx tsx test/e2e-google-manual.ts
8
+ *
9
+ * Or:
10
+ *
11
+ * GEMINI_API_KEY=... npx tsx test/e2e-google-manual.ts
12
+ */
13
+
14
+ import { configure, resetContext, getExecutionTier, isFlexAvailable, getProvider } from '../src/context.js'
15
+ import { type BatchItem } from '../src/batch-queue.js'
16
+ import { configureGoogleGenAI, googleFlexAdapter } from '../src/batch/google.js'
17
+
18
+ // Gemini models
19
+ const GEMINI_FLASH = 'gemini-2.0-flash'
20
+ const GEMINI_PRO = 'gemini-1.5-pro'
21
+
22
+ async function main() {
23
+ console.log('\n🧪 E2E Google GenAI (Gemini) Flex Tier Test\n')
24
+
25
+ // Check for API key
26
+ const apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY
27
+ if (!apiKey) {
28
+ console.error('❌ Google API key required')
29
+ console.error(' Set GOOGLE_API_KEY or GEMINI_API_KEY')
30
+ console.error(' Get one at: https://aistudio.google.com/app/apikey')
31
+ process.exit(1)
32
+ }
33
+
34
+ console.log('✅ Google API key found')
35
+
36
+ // Configure
37
+ configure({
38
+ provider: 'google',
39
+ model: GEMINI_FLASH,
40
+ batchMode: 'auto',
41
+ flexThreshold: 3,
42
+ batchThreshold: 500,
43
+ })
44
+
45
+ console.log(`✅ Configured: provider=${getProvider()}, flexAvailable=${isFlexAvailable()}`)
46
+
47
+ // Test 1: Basic flex processing with Gemini 2.0 Flash
48
+ console.log('\n📝 Test 1: Basic Flex Processing (Gemini 2.0 Flash)')
49
+ console.log(' Creating 3 simple prompts...')
50
+
51
+ const items: BatchItem[] = [
52
+ { id: 'test_1', prompt: 'What is 2 + 2? Reply with just the number.', status: 'pending' },
53
+ { id: 'test_2', prompt: 'What is 3 + 3? Reply with just the number.', status: 'pending' },
54
+ { id: 'test_3', prompt: 'What is 4 + 4? Reply with just the number.', status: 'pending' },
55
+ ]
56
+
57
+ console.log(' Submitting via flex adapter...')
58
+ const startTime = Date.now()
59
+
60
+ try {
61
+ const results = await googleFlexAdapter.submitFlex(items, { model: GEMINI_FLASH })
62
+ const duration = Date.now() - startTime
63
+
64
+ console.log(` ✅ Completed in ${duration}ms`)
65
+ console.log(' Results:')
66
+
67
+ results.forEach((result) => {
68
+ const status = result.status === 'completed' ? '✅' : '❌'
69
+ console.log(` ${status} ${result.id}: "${result.result}"`)
70
+ if (result.error) {
71
+ console.log(` Error: ${result.error}`)
72
+ }
73
+ })
74
+ } catch (error) {
75
+ console.error(` ❌ Error: ${error}`)
76
+ process.exit(1)
77
+ }
78
+
79
+ // Test 2: Token usage
80
+ console.log('\n📝 Test 2: Token Usage Tracking')
81
+
82
+ const usageItems: BatchItem[] = [
83
+ { id: 'usage', prompt: 'Count from 1 to 5', status: 'pending' },
84
+ ]
85
+
86
+ try {
87
+ const results = await googleFlexAdapter.submitFlex(usageItems, { model: GEMINI_FLASH })
88
+ const usage = results[0].usage
89
+
90
+ if (usage) {
91
+ console.log(` Prompt tokens: ${usage.promptTokens}`)
92
+ console.log(` Completion tokens: ${usage.completionTokens}`)
93
+ console.log(` Total tokens: ${usage.totalTokens}`)
94
+ console.log(' ✅ Token usage tracking works')
95
+ } else {
96
+ console.log(' ⚠️ No usage data returned')
97
+ }
98
+ } catch (error) {
99
+ console.error(` ❌ Error: ${error}`)
100
+ }
101
+
102
+ // Test 3: Structured output (JSON)
103
+ console.log('\n📝 Test 3: Structured Output (JSON)')
104
+
105
+ const jsonItems: BatchItem[] = [
106
+ {
107
+ id: 'json_test',
108
+ prompt: 'Generate a JSON object with name "Alice" and age 30. Return only valid JSON, no markdown.',
109
+ schema: { name: 'string', age: 'number' },
110
+ status: 'pending',
111
+ },
112
+ ]
113
+
114
+ try {
115
+ const results = await googleFlexAdapter.submitFlex(jsonItems, { model: GEMINI_FLASH })
116
+ console.log(` Result: ${JSON.stringify(results[0].result)}`)
117
+ console.log(' ✅ Structured output works')
118
+ } catch (error) {
119
+ console.error(` ❌ Error: ${error}`)
120
+ }
121
+
122
+ // Test 4: Concurrent processing
123
+ console.log('\n📝 Test 4: Concurrent Processing (10 items)')
124
+
125
+ const concurrentItems: BatchItem[] = Array.from({ length: 10 }, (_, i) => ({
126
+ id: `concurrent_${i}`,
127
+ prompt: `What is ${i + 1} * 2? Reply with just the number.`,
128
+ status: 'pending' as const,
129
+ }))
130
+
131
+ try {
132
+ const startTime = Date.now()
133
+ const results = await googleFlexAdapter.submitFlex(concurrentItems, { model: GEMINI_FLASH })
134
+ const duration = Date.now() - startTime
135
+
136
+ const successCount = results.filter((r) => r.status === 'completed').length
137
+ console.log(` ✅ Processed ${successCount}/10 items in ${duration}ms`)
138
+ console.log(` Average: ${Math.round(duration / 10)}ms per item`)
139
+
140
+ results.forEach((result) => {
141
+ console.log(` ${result.id}: "${result.result}"`)
142
+ })
143
+ } catch (error) {
144
+ console.error(` ❌ Error: ${error}`)
145
+ }
146
+
147
+ // Test 5: Gemini 1.5 Pro
148
+ console.log('\n📝 Test 5: Gemini 1.5 Pro')
149
+
150
+ const proItems: BatchItem[] = [
151
+ { id: 'pro_test', prompt: 'Write a haiku about AI.', status: 'pending' },
152
+ ]
153
+
154
+ try {
155
+ const results = await googleFlexAdapter.submitFlex(proItems, { model: GEMINI_PRO })
156
+ console.log(` ✅ Gemini 1.5 Pro response:`)
157
+ console.log(` ${results[0].result}`)
158
+ } catch (error) {
159
+ console.error(` ❌ Error: ${error}`)
160
+ }
161
+
162
+ // Test 6: Tier selection
163
+ console.log('\n📝 Test 6: Execution Tier Selection')
164
+
165
+ console.log(` 1 item → ${getExecutionTier(1)} (expected: immediate)`)
166
+ console.log(` 3 items → ${getExecutionTier(3)} (expected: flex)`)
167
+ console.log(` 10 items → ${getExecutionTier(10)} (expected: flex)`)
168
+ console.log(` 500 items → ${getExecutionTier(500)} (expected: batch)`)
169
+
170
+ console.log('\n✨ E2E Tests Complete!\n')
171
+
172
+ resetContext()
173
+ }
174
+
175
+ main().catch((error) => {
176
+ console.error('Fatal error:', error)
177
+ process.exit(1)
178
+ })
@@ -0,0 +1,216 @@
1
+ /**
2
+ * E2E Tests for Google GenAI (Gemini) Flex Tier Processing
3
+ *
4
+ * These tests hit real Google GenAI APIs to verify the flex tier actually works.
5
+ * Requires Google API key:
6
+ * - GOOGLE_API_KEY or GEMINI_API_KEY
7
+ *
8
+ * Run with: npx vitest run test/e2e-google.test.ts
9
+ */
10
+
11
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
12
+ import { configure, resetContext, getExecutionTier, isFlexAvailable } from '../src/context.js'
13
+ import { getFlexAdapter, type BatchItem } from '../src/batch-queue.js'
14
+
15
+ // Import the Google adapter to register it
16
+ import { configureGoogleGenAI, googleFlexAdapter } from '../src/batch/google.js'
17
+ import '../src/batch/google.js'
18
+
19
+ // Skip tests if no Google API key or gateway
20
+ const hasApiKey = !!(
21
+ process.env.GOOGLE_API_KEY ||
22
+ process.env.GEMINI_API_KEY ||
23
+ (process.env.AI_GATEWAY_URL && process.env.AI_GATEWAY_TOKEN)
24
+ )
25
+
26
+ // Gemini 2.0 Flash model
27
+ const GEMINI_MODEL = 'gemini-2.0-flash'
28
+
29
+ describe.skipIf(!hasApiKey)('E2E Google GenAI Flex Tier Processing', () => {
30
+ beforeAll(() => {
31
+ // Configure Google GenAI - can use either direct API key or AI Gateway
32
+ configureGoogleGenAI({
33
+ gatewayUrl: process.env.AI_GATEWAY_URL,
34
+ gatewayToken: process.env.AI_GATEWAY_TOKEN,
35
+ })
36
+
37
+ configure({
38
+ provider: 'google',
39
+ model: GEMINI_MODEL,
40
+ batchMode: 'auto',
41
+ flexThreshold: 3,
42
+ batchThreshold: 500,
43
+ })
44
+ })
45
+
46
+ afterAll(() => {
47
+ resetContext()
48
+ })
49
+
50
+ describe('Flex Adapter Direct', () => {
51
+ it('processes a small batch via flex adapter', async () => {
52
+ const items: BatchItem[] = [
53
+ {
54
+ id: 'item_1',
55
+ prompt: 'What is 2 + 2? Reply with just the number.',
56
+ status: 'pending',
57
+ },
58
+ {
59
+ id: 'item_2',
60
+ prompt: 'What is 3 + 3? Reply with just the number.',
61
+ status: 'pending',
62
+ },
63
+ {
64
+ id: 'item_3',
65
+ prompt: 'What is 4 + 4? Reply with just the number.',
66
+ status: 'pending',
67
+ },
68
+ ]
69
+
70
+ const results = await googleFlexAdapter.submitFlex(items, { model: GEMINI_MODEL })
71
+
72
+ expect(results).toHaveLength(3)
73
+ results.forEach((result) => {
74
+ expect(result.status).toBe('completed')
75
+ expect(result.result).toBeDefined()
76
+ })
77
+
78
+ // Check the actual responses contain numbers
79
+ const result1 = results.find((r) => r.id === 'item_1')
80
+ const result2 = results.find((r) => r.id === 'item_2')
81
+ const result3 = results.find((r) => r.id === 'item_3')
82
+
83
+ expect(String(result1?.result)).toContain('4')
84
+ expect(String(result2?.result)).toContain('6')
85
+ expect(String(result3?.result)).toContain('8')
86
+ }, 30000)
87
+
88
+ it('reports token usage', async () => {
89
+ const items: BatchItem[] = [
90
+ {
91
+ id: 'usage_test',
92
+ prompt: 'Count from 1 to 5',
93
+ status: 'pending',
94
+ },
95
+ ]
96
+
97
+ const results = await googleFlexAdapter.submitFlex(items, { model: GEMINI_MODEL })
98
+
99
+ expect(results[0].usage).toBeDefined()
100
+ expect(results[0].usage?.promptTokens).toBeGreaterThan(0)
101
+ expect(results[0].usage?.completionTokens).toBeGreaterThan(0)
102
+ expect(results[0].usage?.totalTokens).toBeGreaterThan(0)
103
+ }, 30000)
104
+
105
+ it('handles system prompts', async () => {
106
+ const items: BatchItem[] = [
107
+ {
108
+ id: 'system_test',
109
+ prompt: 'What should I say?',
110
+ options: {
111
+ system: 'You are a helpful assistant that always responds with exactly "Hello World"',
112
+ },
113
+ status: 'pending',
114
+ },
115
+ ]
116
+
117
+ const results = await googleFlexAdapter.submitFlex(items, { model: GEMINI_MODEL })
118
+
119
+ expect(results[0].status).toBe('completed')
120
+ expect(String(results[0].result).toLowerCase()).toContain('hello')
121
+ }, 30000)
122
+
123
+ it('handles structured output (JSON)', async () => {
124
+ const items: BatchItem[] = [
125
+ {
126
+ id: 'json_test',
127
+ prompt: 'Generate a JSON object with name "Alice" and age 30. Return only valid JSON.',
128
+ schema: { name: 'string', age: 'number' },
129
+ status: 'pending',
130
+ },
131
+ ]
132
+
133
+ const results = await googleFlexAdapter.submitFlex(items, { model: GEMINI_MODEL })
134
+
135
+ expect(results[0].status).toBe('completed')
136
+ const result = results[0].result as { name?: string; age?: number }
137
+ expect(result).toBeDefined()
138
+ // The result should be parsed JSON
139
+ expect(typeof result === 'object').toBe(true)
140
+ }, 30000)
141
+ })
142
+
143
+ describe('Execution Tier Selection', () => {
144
+ it('selects immediate tier for < flexThreshold items', () => {
145
+ configure({ batchMode: 'auto', flexThreshold: 5, batchThreshold: 500 })
146
+ expect(getExecutionTier(1)).toBe('immediate')
147
+ expect(getExecutionTier(4)).toBe('immediate')
148
+ })
149
+
150
+ it('selects flex tier for flexThreshold to < batchThreshold items', () => {
151
+ configure({ batchMode: 'auto', flexThreshold: 5, batchThreshold: 500 })
152
+ expect(getExecutionTier(5)).toBe('flex')
153
+ expect(getExecutionTier(100)).toBe('flex')
154
+ })
155
+
156
+ it('reports flex as available for google', () => {
157
+ configure({ provider: 'google' })
158
+ expect(isFlexAvailable()).toBe(true)
159
+ })
160
+ })
161
+
162
+ describe('Concurrent Processing', () => {
163
+ it('processes multiple items concurrently', async () => {
164
+ // Create 10 items to test concurrent processing
165
+ const items: BatchItem[] = Array.from({ length: 10 }, (_, i) => ({
166
+ id: `concurrent_${i}`,
167
+ prompt: `What is ${i} + 1? Reply with just the number.`,
168
+ status: 'pending' as const,
169
+ }))
170
+
171
+ const startTime = Date.now()
172
+ const results = await googleFlexAdapter.submitFlex(items, { model: GEMINI_MODEL })
173
+ const duration = Date.now() - startTime
174
+
175
+ expect(results).toHaveLength(10)
176
+ results.forEach((result) => {
177
+ expect(result.status).toBe('completed')
178
+ })
179
+
180
+ console.log(`Processed 10 items in ${duration}ms`)
181
+ // With concurrency, should be much faster than sequential
182
+ expect(duration).toBeLessThan(30000) // Should complete in under 30 seconds
183
+ }, 60000)
184
+ })
185
+ })
186
+
187
+ // Test with different Gemini models (optional - may not be available via gateway)
188
+ describe.skipIf(!hasApiKey)('E2E Google GenAI with Different Models', () => {
189
+ it.skip('works with gemini-1.5-flash (may not be in gateway)', async () => {
190
+ const items: BatchItem[] = [
191
+ {
192
+ id: 'flash_test',
193
+ prompt: 'Say "hello" and nothing else.',
194
+ status: 'pending',
195
+ },
196
+ ]
197
+
198
+ const results = await googleFlexAdapter.submitFlex(items, { model: 'gemini-1.5-flash' })
199
+ expect(results[0].status).toBe('completed')
200
+ expect(String(results[0].result).toLowerCase()).toContain('hello')
201
+ }, 30000)
202
+
203
+ it.skip('works with gemini-1.5-pro (may not be in gateway)', async () => {
204
+ const items: BatchItem[] = [
205
+ {
206
+ id: 'pro_test',
207
+ prompt: 'Say "hello" and nothing else.',
208
+ status: 'pending',
209
+ },
210
+ ]
211
+
212
+ const results = await googleFlexAdapter.submitFlex(items, { model: 'gemini-1.5-pro' })
213
+ expect(results[0].status).toBe('completed')
214
+ expect(String(results[0].result).toLowerCase()).toContain('hello')
215
+ }, 30000)
216
+ })