ai-functions 0.3.0 → 0.4.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 (400) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-test.log +105 -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/schema.d.ts +54 -0
  90. package/dist/schema.d.ts.map +1 -0
  91. package/dist/schema.js +109 -0
  92. package/dist/schema.js.map +1 -0
  93. package/dist/template.d.ts +73 -0
  94. package/dist/template.d.ts.map +1 -0
  95. package/dist/template.js +129 -0
  96. package/dist/template.js.map +1 -0
  97. package/dist/types.d.ts +474 -106
  98. package/dist/types.d.ts.map +1 -1
  99. package/dist/types.js +4 -8
  100. package/dist/types.js.map +1 -1
  101. package/evalite.config.ts +19 -0
  102. package/evals/README.md +212 -0
  103. package/evals/classification.eval.ts +108 -0
  104. package/evals/marketing.eval.ts +370 -0
  105. package/evals/math.eval.ts +94 -0
  106. package/evals/run-evals.ts +166 -0
  107. package/evals/structured-output.eval.ts +143 -0
  108. package/evals/writing.eval.ts +117 -0
  109. package/examples/batch-blog-posts.ts +160 -0
  110. package/package.json +57 -57
  111. package/src/ai-promise.ts +784 -0
  112. package/src/ai.ts +1183 -0
  113. package/src/batch/anthropic.ts +375 -0
  114. package/src/batch/bedrock.ts +801 -0
  115. package/src/batch/cloudflare.ts +421 -0
  116. package/src/batch/google.ts +491 -0
  117. package/src/batch/index.ts +31 -0
  118. package/src/batch/memory.ts +253 -0
  119. package/src/batch/openai.ts +557 -0
  120. package/src/batch-map.ts +534 -0
  121. package/src/batch-queue.ts +493 -0
  122. package/src/context.ts +332 -0
  123. package/src/embeddings.ts +244 -0
  124. package/src/eval/index.ts +8 -0
  125. package/src/eval/models.ts +158 -0
  126. package/src/eval/runner.ts +217 -0
  127. package/src/generate.ts +245 -0
  128. package/src/index.ts +154 -0
  129. package/src/primitives.ts +612 -0
  130. package/src/providers/cloudflare.ts +15 -0
  131. package/src/providers/index.ts +14 -0
  132. package/src/schema.ts +147 -0
  133. package/src/template.ts +209 -0
  134. package/src/types.ts +540 -0
  135. package/test/README.md +105 -0
  136. package/test/ai-proxy.test.ts +192 -0
  137. package/test/async-iterators.test.ts +327 -0
  138. package/test/batch-background.test.ts +482 -0
  139. package/test/batch-blog-posts.test.ts +387 -0
  140. package/test/blog-generation.test.ts +510 -0
  141. package/test/browse-read.test.ts +611 -0
  142. package/test/core-functions.test.ts +694 -0
  143. package/test/decide.test.ts +393 -0
  144. package/test/define.test.ts +274 -0
  145. package/test/e2e-bedrock-manual.ts +163 -0
  146. package/test/e2e-bedrock.test.ts +191 -0
  147. package/test/e2e-flex-gateway.ts +157 -0
  148. package/test/e2e-flex-manual.ts +183 -0
  149. package/test/e2e-flex.test.ts +209 -0
  150. package/test/e2e-google-manual.ts +178 -0
  151. package/test/e2e-google.test.ts +216 -0
  152. package/test/embeddings.test.ts +284 -0
  153. package/test/evals/define-function.eval.test.ts +379 -0
  154. package/test/evals/primitives.eval.test.ts +384 -0
  155. package/test/function-types.test.ts +492 -0
  156. package/test/generate-core.test.ts +319 -0
  157. package/test/generate.test.ts +163 -0
  158. package/test/implicit-batch.test.ts +422 -0
  159. package/test/schema.test.ts +109 -0
  160. package/test/tagged-templates.test.ts +302 -0
  161. package/tsconfig.json +10 -0
  162. package/vitest.config.ts +42 -0
  163. package/LICENSE +0 -21
  164. package/bin/cli.js +0 -5
  165. package/dist/cli/index.d.ts +0 -10
  166. package/dist/cli/index.d.ts.map +0 -1
  167. package/dist/cli/index.js +0 -38
  168. package/dist/cli/index.js.map +0 -1
  169. package/dist/cli/index.test.d.ts +0 -2
  170. package/dist/cli/index.test.d.ts.map +0 -1
  171. package/dist/cli/index.test.js +0 -35
  172. package/dist/cli/index.test.js.map +0 -1
  173. package/dist/constants/models.d.ts +0 -10
  174. package/dist/constants/models.d.ts.map +0 -1
  175. package/dist/constants/models.js +0 -12
  176. package/dist/constants/models.js.map +0 -1
  177. package/dist/converters/index.d.ts +0 -3
  178. package/dist/converters/index.d.ts.map +0 -1
  179. package/dist/converters/index.js +0 -3
  180. package/dist/converters/index.js.map +0 -1
  181. package/dist/converters/model.d.ts +0 -4
  182. package/dist/converters/model.d.ts.map +0 -1
  183. package/dist/converters/model.js +0 -19
  184. package/dist/converters/model.js.map +0 -1
  185. package/dist/converters/schema.d.ts +0 -4
  186. package/dist/converters/schema.d.ts.map +0 -1
  187. package/dist/converters/schema.js +0 -25
  188. package/dist/converters/schema.js.map +0 -1
  189. package/dist/core/responses.d.ts +0 -5
  190. package/dist/core/responses.d.ts.map +0 -1
  191. package/dist/core/responses.js +0 -16
  192. package/dist/core/responses.js.map +0 -1
  193. package/dist/core/responses.test.d.ts +0 -2
  194. package/dist/core/responses.test.d.ts.map +0 -1
  195. package/dist/core/responses.test.js +0 -31
  196. package/dist/core/responses.test.js.map +0 -1
  197. package/dist/errors.d.ts +0 -6
  198. package/dist/errors.d.ts.map +0 -1
  199. package/dist/errors.js +0 -9
  200. package/dist/errors.js.map +0 -1
  201. package/dist/examples/streaming.test.d.ts +0 -2
  202. package/dist/examples/streaming.test.d.ts.map +0 -1
  203. package/dist/examples/streaming.test.js +0 -176
  204. package/dist/examples/streaming.test.js.map +0 -1
  205. package/dist/factory/__tests__/index.test.d.ts +0 -2
  206. package/dist/factory/__tests__/index.test.d.ts.map +0 -1
  207. package/dist/factory/__tests__/index.test.js +0 -430
  208. package/dist/factory/__tests__/index.test.js.map +0 -1
  209. package/dist/factory/__tests__/list.test.d.ts +0 -2
  210. package/dist/factory/__tests__/list.test.d.ts.map +0 -1
  211. package/dist/factory/__tests__/list.test.js +0 -92
  212. package/dist/factory/__tests__/list.test.js.map +0 -1
  213. package/dist/factory/index.d.ts +0 -20
  214. package/dist/factory/index.d.ts.map +0 -1
  215. package/dist/factory/index.js +0 -287
  216. package/dist/factory/index.js.map +0 -1
  217. package/dist/factory/index.test.d.ts +0 -2
  218. package/dist/factory/index.test.d.ts.map +0 -1
  219. package/dist/factory/index.test.js +0 -287
  220. package/dist/factory/index.test.js.map +0 -1
  221. package/dist/factory/list.d.ts +0 -3
  222. package/dist/factory/list.d.ts.map +0 -1
  223. package/dist/factory/list.js +0 -221
  224. package/dist/factory/list.js.map +0 -1
  225. package/dist/factory/list.test.d.ts +0 -2
  226. package/dist/factory/list.test.d.ts.map +0 -1
  227. package/dist/factory/list.test.js +0 -84
  228. package/dist/factory/list.test.js.map +0 -1
  229. package/dist/generate/index.d.ts +0 -5
  230. package/dist/generate/index.d.ts.map +0 -1
  231. package/dist/generate/index.js +0 -17
  232. package/dist/generate/index.js.map +0 -1
  233. package/dist/index.test.d.ts +0 -2
  234. package/dist/index.test.d.ts.map +0 -1
  235. package/dist/index.test.js +0 -59
  236. package/dist/index.test.js.map +0 -1
  237. package/dist/list/await.d.ts +0 -3
  238. package/dist/list/await.d.ts.map +0 -1
  239. package/dist/list/await.js +0 -28
  240. package/dist/list/await.js.map +0 -1
  241. package/dist/list/constants.d.ts +0 -4
  242. package/dist/list/constants.d.ts.map +0 -1
  243. package/dist/list/constants.js +0 -5
  244. package/dist/list/constants.js.map +0 -1
  245. package/dist/list/create-function.d.ts +0 -3
  246. package/dist/list/create-function.d.ts.map +0 -1
  247. package/dist/list/create-function.js +0 -11
  248. package/dist/list/create-function.js.map +0 -1
  249. package/dist/list/index.d.ts +0 -4
  250. package/dist/list/index.d.ts.map +0 -1
  251. package/dist/list/index.js +0 -5
  252. package/dist/list/index.js.map +0 -1
  253. package/dist/list/prompt.d.ts +0 -3
  254. package/dist/list/prompt.d.ts.map +0 -1
  255. package/dist/list/prompt.js +0 -6
  256. package/dist/list/prompt.js.map +0 -1
  257. package/dist/list/schemas.d.ts +0 -4
  258. package/dist/list/schemas.d.ts.map +0 -1
  259. package/dist/list/schemas.js +0 -8
  260. package/dist/list/schemas.js.map +0 -1
  261. package/dist/list/stream.d.ts +0 -3
  262. package/dist/list/stream.d.ts.map +0 -1
  263. package/dist/list/stream.js +0 -33
  264. package/dist/list/stream.js.map +0 -1
  265. package/dist/list/types.d.ts +0 -11
  266. package/dist/list/types.d.ts.map +0 -1
  267. package/dist/list/types.js +0 -2
  268. package/dist/list/types.js.map +0 -1
  269. package/dist/list/validation.d.ts +0 -3
  270. package/dist/list/validation.d.ts.map +0 -1
  271. package/dist/list/validation.js +0 -12
  272. package/dist/list/validation.js.map +0 -1
  273. package/dist/providers/config.d.ts +0 -4
  274. package/dist/providers/config.d.ts.map +0 -1
  275. package/dist/providers/config.js +0 -21
  276. package/dist/providers/config.js.map +0 -1
  277. package/dist/providers/config.test.d.ts +0 -2
  278. package/dist/providers/config.test.d.ts.map +0 -1
  279. package/dist/providers/config.test.js +0 -37
  280. package/dist/providers/config.test.js.map +0 -1
  281. package/dist/proxy/constants.d.ts +0 -4
  282. package/dist/proxy/constants.d.ts.map +0 -1
  283. package/dist/proxy/constants.js +0 -5
  284. package/dist/proxy/constants.js.map +0 -1
  285. package/dist/proxy/create-function.d.ts +0 -4
  286. package/dist/proxy/create-function.d.ts.map +0 -1
  287. package/dist/proxy/create-function.js +0 -24
  288. package/dist/proxy/create-function.js.map +0 -1
  289. package/dist/proxy/create-proxy.d.ts +0 -2
  290. package/dist/proxy/create-proxy.d.ts.map +0 -1
  291. package/dist/proxy/create-proxy.js +0 -11
  292. package/dist/proxy/create-proxy.js.map +0 -1
  293. package/dist/proxy/function-generator.d.ts +0 -9
  294. package/dist/proxy/function-generator.d.ts.map +0 -1
  295. package/dist/proxy/function-generator.js +0 -29
  296. package/dist/proxy/function-generator.js.map +0 -1
  297. package/dist/proxy/index.d.ts +0 -4
  298. package/dist/proxy/index.d.ts.map +0 -1
  299. package/dist/proxy/index.js +0 -4
  300. package/dist/proxy/index.js.map +0 -1
  301. package/dist/proxy/prompt.d.ts +0 -2
  302. package/dist/proxy/prompt.d.ts.map +0 -1
  303. package/dist/proxy/prompt.js +0 -6
  304. package/dist/proxy/prompt.js.map +0 -1
  305. package/dist/proxy/types.d.ts +0 -7
  306. package/dist/proxy/types.d.ts.map +0 -1
  307. package/dist/proxy/types.js +0 -2
  308. package/dist/proxy/types.js.map +0 -1
  309. package/dist/queue/manager.d.ts +0 -5
  310. package/dist/queue/manager.d.ts.map +0 -1
  311. package/dist/queue/manager.js +0 -37
  312. package/dist/queue/manager.js.map +0 -1
  313. package/dist/queue/manager.test.d.ts +0 -2
  314. package/dist/queue/manager.test.d.ts.map +0 -1
  315. package/dist/queue/manager.test.js +0 -52
  316. package/dist/queue/manager.test.js.map +0 -1
  317. package/dist/schema-converter.d.ts +0 -4
  318. package/dist/schema-converter.d.ts.map +0 -1
  319. package/dist/schema-converter.js +0 -30
  320. package/dist/schema-converter.js.map +0 -1
  321. package/dist/stream/index.d.ts +0 -7
  322. package/dist/stream/index.d.ts.map +0 -1
  323. package/dist/stream/index.js +0 -23
  324. package/dist/stream/index.js.map +0 -1
  325. package/dist/streaming/utils.d.ts +0 -4
  326. package/dist/streaming/utils.d.ts.map +0 -1
  327. package/dist/streaming/utils.js +0 -131
  328. package/dist/streaming/utils.js.map +0 -1
  329. package/dist/streaming/utils.test.d.ts +0 -2
  330. package/dist/streaming/utils.test.d.ts.map +0 -1
  331. package/dist/streaming/utils.test.js +0 -84
  332. package/dist/streaming/utils.test.js.map +0 -1
  333. package/dist/templates/result.d.ts +0 -7
  334. package/dist/templates/result.d.ts.map +0 -1
  335. package/dist/templates/result.js +0 -40
  336. package/dist/templates/result.js.map +0 -1
  337. package/dist/templates/result.test.d.ts +0 -2
  338. package/dist/templates/result.test.d.ts.map +0 -1
  339. package/dist/templates/result.test.js +0 -75
  340. package/dist/templates/result.test.js.map +0 -1
  341. package/dist/test/setup.d.ts +0 -2
  342. package/dist/test/setup.d.ts.map +0 -1
  343. package/dist/test/setup.js +0 -21
  344. package/dist/test/setup.js.map +0 -1
  345. package/dist/test-types.d.ts +0 -13
  346. package/dist/test-types.d.ts.map +0 -1
  347. package/dist/test-types.js +0 -55
  348. package/dist/test-types.js.map +0 -1
  349. package/dist/types/index.d.ts +0 -4
  350. package/dist/types/index.d.ts.map +0 -1
  351. package/dist/types/index.js +0 -4
  352. package/dist/types/index.js.map +0 -1
  353. package/dist/types/list.d.ts +0 -10
  354. package/dist/types/list.d.ts.map +0 -1
  355. package/dist/types/list.js +0 -2
  356. package/dist/types/list.js.map +0 -1
  357. package/dist/types/model.d.ts +0 -7
  358. package/dist/types/model.d.ts.map +0 -1
  359. package/dist/types/model.js +0 -2
  360. package/dist/types/model.js.map +0 -1
  361. package/dist/types/options.d.ts +0 -25
  362. package/dist/types/options.d.ts.map +0 -1
  363. package/dist/types/options.js +0 -2
  364. package/dist/types/options.js.map +0 -1
  365. package/dist/types/schema.d.ts +0 -5
  366. package/dist/types/schema.d.ts.map +0 -1
  367. package/dist/types/schema.js +0 -2
  368. package/dist/types/schema.js.map +0 -1
  369. package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
  370. package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
  371. package/dist/utils/__tests__/request-handler.test.js +0 -134
  372. package/dist/utils/__tests__/request-handler.test.js.map +0 -1
  373. package/dist/utils/__tests__/schema.test.d.ts +0 -2
  374. package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
  375. package/dist/utils/__tests__/schema.test.js +0 -49
  376. package/dist/utils/__tests__/schema.test.js.map +0 -1
  377. package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
  378. package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
  379. package/dist/utils/__tests__/stream-progress.test.js +0 -85
  380. package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
  381. package/dist/utils/index.d.ts +0 -2
  382. package/dist/utils/index.d.ts.map +0 -1
  383. package/dist/utils/index.js +0 -2
  384. package/dist/utils/index.js.map +0 -1
  385. package/dist/utils/request-handler.d.ts +0 -17
  386. package/dist/utils/request-handler.d.ts.map +0 -1
  387. package/dist/utils/request-handler.js +0 -105
  388. package/dist/utils/request-handler.js.map +0 -1
  389. package/dist/utils/schema.d.ts +0 -11
  390. package/dist/utils/schema.d.ts.map +0 -1
  391. package/dist/utils/schema.js +0 -51
  392. package/dist/utils/schema.js.map +0 -1
  393. package/dist/utils/stream-progress.d.ts +0 -17
  394. package/dist/utils/stream-progress.d.ts.map +0 -1
  395. package/dist/utils/stream-progress.js +0 -86
  396. package/dist/utils/stream-progress.js.map +0 -1
  397. package/dist/utils/validation.d.ts +0 -3
  398. package/dist/utils/validation.d.ts.map +0 -1
  399. package/dist/utils/validation.js +0 -30
  400. package/dist/utils/validation.js.map +0 -1
@@ -0,0 +1,492 @@
1
+ /**
2
+ * Tests for function type inference from name + args
3
+ *
4
+ * The ai.* magic proxy infers function type based on:
5
+ * 1. Function name (e.g., fizzBuzz → Code, storyBrand → Generative)
6
+ * 2. Argument structure (e.g., { max: 100 } → Code parameters)
7
+ * 3. Argument values (e.g., { amount: 50000 } → Human approval needed)
8
+ *
9
+ * Function Types:
10
+ * - Generative: Generate content (text, json, lists, images)
11
+ * - Code: Generate and execute code
12
+ * - Agentic: Multi-step tasks with tools
13
+ * - Human: Requires human input or approval
14
+ */
15
+
16
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
17
+
18
+ // ============================================================================
19
+ // Type definitions
20
+ // ============================================================================
21
+
22
+ type FunctionType = 'generative' | 'code' | 'agentic' | 'human'
23
+
24
+ interface InferredFunction {
25
+ type: FunctionType
26
+ name: string
27
+ args: unknown
28
+ execute: () => Promise<unknown>
29
+ }
30
+
31
+ // ============================================================================
32
+ // Mock type inference logic
33
+ // ============================================================================
34
+
35
+ /**
36
+ * Infer function type from name patterns
37
+ */
38
+ function inferTypeFromName(name: string): FunctionType | null {
39
+ // Code patterns - computing, algorithms, transformations
40
+ const codePatterns = [
41
+ /^(fizz|buzz|calculate|compute|sort|filter|map|reduce|transform)/i,
42
+ /^(validate|parse|format|convert|encode|decode|hash|compress)/i,
43
+ /^(find|search|match|replace|split|join|merge)/i,
44
+ /(algorithm|function|method|handler|processor)$/i,
45
+ ]
46
+
47
+ // Agentic patterns - tasks, workflows, automation
48
+ const agenticPatterns = [
49
+ /^(launch|deploy|publish|schedule|automate)/i,
50
+ /^(research|investigate|analyze|audit|monitor)/i,
51
+ /^(create|build|setup|configure|integrate)/i,
52
+ /(workflow|pipeline|process|task|job)$/i,
53
+ ]
54
+
55
+ // Human patterns - approval, review, decisions
56
+ const humanPatterns = [
57
+ /^(approve|reject|review|verify|confirm)/i,
58
+ /^(decide|choose|select|pick)/i,
59
+ /(approval|review|confirmation)$/i,
60
+ ]
61
+
62
+ // Check patterns in order of specificity
63
+ for (const pattern of humanPatterns) {
64
+ if (pattern.test(name)) return 'human'
65
+ }
66
+ for (const pattern of agenticPatterns) {
67
+ if (pattern.test(name)) return 'agentic'
68
+ }
69
+ for (const pattern of codePatterns) {
70
+ if (pattern.test(name)) return 'code'
71
+ }
72
+
73
+ // Default to generative
74
+ return 'generative'
75
+ }
76
+
77
+ /**
78
+ * Infer function type from argument structure
79
+ */
80
+ function inferTypeFromArgs(args: unknown): FunctionType | null {
81
+ if (typeof args !== 'object' || args === null) return null
82
+
83
+ const argObj = args as Record<string, unknown>
84
+ const keys = Object.keys(argObj)
85
+
86
+ // Code indicators - numeric parameters, input/output pairs
87
+ const codeIndicators = ['max', 'min', 'count', 'input', 'output', 'format', 'precision']
88
+ if (codeIndicators.some(k => keys.includes(k))) {
89
+ return 'code'
90
+ }
91
+
92
+ // Agentic indicators - URLs, targets, configurations
93
+ const agenticIndicators = ['url', 'target', 'destination', 'webhook', 'schedule']
94
+ if (agenticIndicators.some(k => keys.includes(k))) {
95
+ return 'agentic'
96
+ }
97
+
98
+ // Human indicators - requires explicit approval flags
99
+ const humanIndicators = ['requiresApproval', 'needsReview', 'manualStep']
100
+ if (humanIndicators.some(k => keys.includes(k))) {
101
+ return 'human'
102
+ }
103
+
104
+ return null
105
+ }
106
+
107
+ /**
108
+ * Infer if human approval needed based on values (subjective judgment)
109
+ */
110
+ function requiresHumanApproval(name: string, args: unknown): boolean {
111
+ if (typeof args !== 'object' || args === null) return false
112
+
113
+ const argObj = args as Record<string, unknown>
114
+
115
+ // High-value financial transactions
116
+ if ('amount' in argObj && typeof argObj.amount === 'number') {
117
+ if (argObj.amount > 10000) return true
118
+ }
119
+
120
+ // Sensitive operations
121
+ if ('action' in argObj) {
122
+ const sensitiveActions = ['delete', 'terminate', 'cancel', 'refund', 'transfer']
123
+ if (sensitiveActions.includes(String(argObj.action).toLowerCase())) {
124
+ return true
125
+ }
126
+ }
127
+
128
+ // Large scale operations
129
+ if ('count' in argObj && typeof argObj.count === 'number') {
130
+ if (argObj.count > 1000) return true
131
+ }
132
+
133
+ // External communications
134
+ if (name.toLowerCase().includes('email') || name.toLowerCase().includes('notify')) {
135
+ if ('recipients' in argObj && Array.isArray(argObj.recipients)) {
136
+ if (argObj.recipients.length > 100) return true
137
+ }
138
+ }
139
+
140
+ return false
141
+ }
142
+
143
+ /**
144
+ * Full type inference combining name, args, and values
145
+ */
146
+ function inferFunctionType(name: string, args: unknown): FunctionType {
147
+ // First check if human approval is needed based on values
148
+ if (requiresHumanApproval(name, args)) {
149
+ return 'human'
150
+ }
151
+
152
+ // Then check args structure
153
+ const argsType = inferTypeFromArgs(args)
154
+ if (argsType) return argsType
155
+
156
+ // Finally fall back to name inference
157
+ return inferTypeFromName(name) || 'generative'
158
+ }
159
+
160
+ // ============================================================================
161
+ // Type inference from name tests
162
+ // ============================================================================
163
+
164
+ describe('type inference from function name', () => {
165
+ describe('Code function patterns', () => {
166
+ it('infers code type from algorithm names', () => {
167
+ expect(inferTypeFromName('fizzBuzz')).toBe('code')
168
+ expect(inferTypeFromName('calculateSum')).toBe('code')
169
+ expect(inferTypeFromName('computeHash')).toBe('code')
170
+ expect(inferTypeFromName('sortArray')).toBe('code')
171
+ })
172
+
173
+ it('infers code type from transformation names', () => {
174
+ expect(inferTypeFromName('validateEmail')).toBe('code')
175
+ expect(inferTypeFromName('parseJSON')).toBe('code')
176
+ expect(inferTypeFromName('formatDate')).toBe('code')
177
+ expect(inferTypeFromName('convertCurrency')).toBe('code')
178
+ })
179
+
180
+ it('infers code type from data operation names', () => {
181
+ expect(inferTypeFromName('findUser')).toBe('code')
182
+ expect(inferTypeFromName('searchRecords')).toBe('code')
183
+ expect(inferTypeFromName('filterProducts')).toBe('code')
184
+ expect(inferTypeFromName('mergeArrays')).toBe('code')
185
+ })
186
+ })
187
+
188
+ describe('Generative function patterns', () => {
189
+ it('infers generative type from content names', () => {
190
+ expect(inferTypeFromName('storyBrand')).toBe('generative')
191
+ expect(inferTypeFromName('blogPost')).toBe('generative')
192
+ expect(inferTypeFromName('productDescription')).toBe('generative')
193
+ expect(inferTypeFromName('marketingCopy')).toBe('generative')
194
+ })
195
+
196
+ it('defaults unknown names to generative', () => {
197
+ expect(inferTypeFromName('foo')).toBe('generative')
198
+ expect(inferTypeFromName('customThing')).toBe('generative')
199
+ expect(inferTypeFromName('randomName')).toBe('generative')
200
+ })
201
+ })
202
+
203
+ describe('Agentic function patterns', () => {
204
+ it('infers agentic type from automation names', () => {
205
+ expect(inferTypeFromName('launchProduct')).toBe('agentic')
206
+ expect(inferTypeFromName('deployApplication')).toBe('agentic')
207
+ expect(inferTypeFromName('publishArticle')).toBe('agentic')
208
+ expect(inferTypeFromName('scheduleTask')).toBe('agentic')
209
+ })
210
+
211
+ it('infers agentic type from research names', () => {
212
+ expect(inferTypeFromName('researchMarket')).toBe('agentic')
213
+ expect(inferTypeFromName('investigateIssue')).toBe('agentic')
214
+ expect(inferTypeFromName('analyzeCompetitors')).toBe('agentic')
215
+ })
216
+
217
+ it('infers agentic type from workflow suffixes', () => {
218
+ expect(inferTypeFromName('onboardingWorkflow')).toBe('agentic')
219
+ expect(inferTypeFromName('deploymentPipeline')).toBe('agentic')
220
+ expect(inferTypeFromName('dataProcess')).toBe('agentic')
221
+ })
222
+ })
223
+
224
+ describe('Human function patterns', () => {
225
+ it('infers human type from approval names', () => {
226
+ expect(inferTypeFromName('approveRefund')).toBe('human')
227
+ expect(inferTypeFromName('rejectApplication')).toBe('human')
228
+ expect(inferTypeFromName('reviewCode')).toBe('human')
229
+ })
230
+
231
+ it('infers human type from decision names', () => {
232
+ expect(inferTypeFromName('decideOutcome')).toBe('human')
233
+ expect(inferTypeFromName('chooseOption')).toBe('human')
234
+ expect(inferTypeFromName('selectWinner')).toBe('human')
235
+ })
236
+ })
237
+ })
238
+
239
+ // ============================================================================
240
+ // Type inference from args tests
241
+ // ============================================================================
242
+
243
+ describe('type inference from argument structure', () => {
244
+ describe('Code argument patterns', () => {
245
+ it('infers code from numeric parameters', () => {
246
+ expect(inferTypeFromArgs({ max: 100 })).toBe('code')
247
+ expect(inferTypeFromArgs({ min: 0, max: 100 })).toBe('code')
248
+ expect(inferTypeFromArgs({ count: 10 })).toBe('code')
249
+ })
250
+
251
+ it('infers code from input/output pairs', () => {
252
+ expect(inferTypeFromArgs({ input: 'data', format: 'json' })).toBe('code')
253
+ expect(inferTypeFromArgs({ output: 'file.txt' })).toBe('code')
254
+ })
255
+ })
256
+
257
+ describe('Agentic argument patterns', () => {
258
+ it('infers agentic from URL parameters', () => {
259
+ expect(inferTypeFromArgs({ url: 'https://example.com' })).toBe('agentic')
260
+ expect(inferTypeFromArgs({ target: 'https://api.example.com' })).toBe('agentic')
261
+ })
262
+
263
+ it('infers agentic from webhook/schedule parameters', () => {
264
+ expect(inferTypeFromArgs({ webhook: 'https://hooks.example.com' })).toBe('agentic')
265
+ expect(inferTypeFromArgs({ schedule: '0 9 * * *' })).toBe('agentic')
266
+ })
267
+ })
268
+
269
+ describe('Human argument patterns', () => {
270
+ it('infers human from explicit approval flags', () => {
271
+ expect(inferTypeFromArgs({ requiresApproval: true })).toBe('human')
272
+ expect(inferTypeFromArgs({ needsReview: true })).toBe('human')
273
+ expect(inferTypeFromArgs({ manualStep: true })).toBe('human')
274
+ })
275
+ })
276
+
277
+ describe('No inference from args', () => {
278
+ it('returns null for unrecognized arg patterns', () => {
279
+ expect(inferTypeFromArgs({ name: 'John' })).toBeNull()
280
+ expect(inferTypeFromArgs({ text: 'Hello' })).toBeNull()
281
+ expect(inferTypeFromArgs({})).toBeNull()
282
+ })
283
+
284
+ it('returns null for non-object args', () => {
285
+ expect(inferTypeFromArgs('string')).toBeNull()
286
+ expect(inferTypeFromArgs(123)).toBeNull()
287
+ expect(inferTypeFromArgs(null)).toBeNull()
288
+ })
289
+ })
290
+ })
291
+
292
+ // ============================================================================
293
+ // Human approval from values (subjective judgment)
294
+ // ============================================================================
295
+
296
+ describe('human approval from values (subjective judgment)', () => {
297
+ describe('high-value transactions', () => {
298
+ it('requires human approval for large amounts', () => {
299
+ expect(requiresHumanApproval('processRefund', { amount: 50000 })).toBe(true)
300
+ expect(requiresHumanApproval('processRefund', { amount: 12.99 })).toBe(false)
301
+ })
302
+
303
+ it('threshold is $10,000', () => {
304
+ expect(requiresHumanApproval('transfer', { amount: 9999 })).toBe(false)
305
+ expect(requiresHumanApproval('transfer', { amount: 10001 })).toBe(true)
306
+ })
307
+ })
308
+
309
+ describe('sensitive actions', () => {
310
+ it('requires approval for destructive actions', () => {
311
+ expect(requiresHumanApproval('performAction', { action: 'delete' })).toBe(true)
312
+ expect(requiresHumanApproval('performAction', { action: 'terminate' })).toBe(true)
313
+ expect(requiresHumanApproval('performAction', { action: 'cancel' })).toBe(true)
314
+ })
315
+
316
+ it('does not require approval for safe actions', () => {
317
+ expect(requiresHumanApproval('performAction', { action: 'read' })).toBe(false)
318
+ expect(requiresHumanApproval('performAction', { action: 'list' })).toBe(false)
319
+ })
320
+ })
321
+
322
+ describe('large scale operations', () => {
323
+ it('requires approval for operations affecting many items', () => {
324
+ expect(requiresHumanApproval('bulkOperation', { count: 5000 })).toBe(true)
325
+ expect(requiresHumanApproval('bulkOperation', { count: 100 })).toBe(false)
326
+ })
327
+ })
328
+
329
+ describe('mass communications', () => {
330
+ it('requires approval for mass emails', () => {
331
+ const manyRecipients = Array(500).fill('user@example.com')
332
+ expect(requiresHumanApproval('sendEmail', { recipients: manyRecipients })).toBe(true)
333
+
334
+ const fewRecipients = ['user1@example.com', 'user2@example.com']
335
+ expect(requiresHumanApproval('sendEmail', { recipients: fewRecipients })).toBe(false)
336
+ })
337
+ })
338
+ })
339
+
340
+ // ============================================================================
341
+ // Combined inference tests
342
+ // ============================================================================
343
+
344
+ describe('combined function type inference', () => {
345
+ it('prioritizes human approval based on values', () => {
346
+ // Even though name suggests code, high amount triggers human
347
+ expect(inferFunctionType('processRefund', { amount: 50000 })).toBe('human')
348
+ })
349
+
350
+ it('uses args when name is ambiguous', () => {
351
+ expect(inferFunctionType('handleData', { max: 100 })).toBe('code')
352
+ expect(inferFunctionType('handleData', { url: 'https://api.com' })).toBe('agentic')
353
+ })
354
+
355
+ it('falls back to name when args are generic', () => {
356
+ expect(inferFunctionType('fizzBuzz', { n: 100 })).toBe('code')
357
+ expect(inferFunctionType('storyBrand', { hero: 'developers' })).toBe('generative')
358
+ })
359
+
360
+ describe('README examples', () => {
361
+ it('ai.fizzBuzz({ max: 100 }) → code', () => {
362
+ expect(inferFunctionType('fizzBuzz', { max: 100 })).toBe('code')
363
+ })
364
+
365
+ it('ai.storyBrand({ hero: "developers" }) → generative', () => {
366
+ expect(inferFunctionType('storyBrand', { hero: 'developers' })).toBe('generative')
367
+ })
368
+
369
+ it('ai.launchProductHunt({ product }) → agentic', () => {
370
+ expect(inferFunctionType('launchProductHunt', { product: {} })).toBe('agentic')
371
+ })
372
+
373
+ it('ai.processRefund({ amount: 12.99 }) → generative (small amount, automated)', () => {
374
+ // Small refund can be automated - falls through to generative
375
+ // The key insight is it's NOT human (which would be triggered by high amounts)
376
+ expect(inferFunctionType('processRefund', { amount: 12.99 })).toBe('generative')
377
+ })
378
+
379
+ it('ai.processRefund({ amount: 50000 }) → human (large amount)', () => {
380
+ // Large refund needs human approval
381
+ expect(inferFunctionType('processRefund', { amount: 50000 })).toBe('human')
382
+ })
383
+ })
384
+ })
385
+
386
+ // ============================================================================
387
+ // Magic proxy behavior tests
388
+ // ============================================================================
389
+
390
+ describe('magic proxy ai.* behavior', () => {
391
+ // Mock the magic proxy
392
+ const mockDefine = vi.fn()
393
+ const mockExecute = vi.fn()
394
+
395
+ function createMockAiProxy() {
396
+ return new Proxy(
397
+ {},
398
+ {
399
+ get(_target, prop) {
400
+ if (typeof prop !== 'string') return undefined
401
+
402
+ // Return a function that infers type and executes
403
+ return (args: unknown) => {
404
+ const type = inferFunctionType(prop, args)
405
+ mockDefine(prop, type, args)
406
+ return mockExecute(prop, type, args)
407
+ }
408
+ },
409
+ }
410
+ )
411
+ }
412
+
413
+ beforeEach(() => {
414
+ mockDefine.mockReset()
415
+ mockExecute.mockReset()
416
+ })
417
+
418
+ it('infers type when accessing property', async () => {
419
+ const ai = createMockAiProxy() as Record<string, (args: unknown) => Promise<unknown>>
420
+
421
+ mockExecute.mockResolvedValue('1, 2, Fizz, 4, Buzz...')
422
+
423
+ await ai.fizzBuzz({ max: 100 })
424
+
425
+ expect(mockDefine).toHaveBeenCalledWith('fizzBuzz', 'code', { max: 100 })
426
+ })
427
+
428
+ it('creates new function for each call', async () => {
429
+ const ai = createMockAiProxy() as Record<string, (args: unknown) => Promise<unknown>>
430
+
431
+ mockExecute.mockResolvedValue('result')
432
+
433
+ await ai.functionA({ a: 1 })
434
+ await ai.functionB({ b: 2 })
435
+
436
+ expect(mockDefine).toHaveBeenCalledTimes(2)
437
+ expect(mockDefine).toHaveBeenNthCalledWith(1, 'functionA', expect.any(String), { a: 1 })
438
+ expect(mockDefine).toHaveBeenNthCalledWith(2, 'functionB', expect.any(String), { b: 2 })
439
+ })
440
+
441
+ it('same function name can have different types based on args', async () => {
442
+ const ai = createMockAiProxy() as Record<string, (args: unknown) => Promise<unknown>>
443
+
444
+ mockExecute.mockResolvedValue('result')
445
+
446
+ // Same name, different args → different types
447
+ await ai.process({ max: 100 }) // code (due to max)
448
+ await ai.process({ url: 'https://api.com' }) // agentic (due to url)
449
+ await ai.process({ amount: 50000 }) // human (due to high amount)
450
+
451
+ expect(mockDefine).toHaveBeenNthCalledWith(1, 'process', 'code', { max: 100 })
452
+ expect(mockDefine).toHaveBeenNthCalledWith(2, 'process', 'agentic', { url: 'https://api.com' })
453
+ expect(mockDefine).toHaveBeenNthCalledWith(3, 'process', 'human', { amount: 50000 })
454
+ })
455
+ })
456
+
457
+ // ============================================================================
458
+ // Function type execution behavior
459
+ // ============================================================================
460
+
461
+ describe('function type execution behavior', () => {
462
+ it('documents expected behavior per type', () => {
463
+ const typeBehaviors = {
464
+ generative: {
465
+ description: 'Generate content',
466
+ examples: ['ai.storyBrand()', 'ai.blogPost()', 'ai.productDescription()'],
467
+ returns: 'Generated content (text, JSON, etc.)',
468
+ },
469
+ code: {
470
+ description: 'Generate and execute code',
471
+ examples: ['ai.fizzBuzz()', 'ai.validateEmail()', 'ai.sortArray()'],
472
+ returns: 'Execution result',
473
+ integration: 'Uses ai-sandbox evaluate()',
474
+ },
475
+ agentic: {
476
+ description: 'Multi-step tasks with tools',
477
+ examples: ['ai.launchProductHunt()', 'ai.researchCompetitors()'],
478
+ returns: 'Task completion result',
479
+ features: ['Tool use', 'Multi-step execution', 'External integrations'],
480
+ },
481
+ human: {
482
+ description: 'Requires human input or approval',
483
+ examples: ['ai.approveRefund()', 'ai.processRefund({ amount: 50000 })'],
484
+ returns: 'Result after human approval',
485
+ flow: ['Generate proposal', 'Wait for approval', 'Execute if approved'],
486
+ },
487
+ }
488
+
489
+ expect(Object.keys(typeBehaviors)).toHaveLength(4)
490
+ expect(typeBehaviors.code.integration).toBe('Uses ai-sandbox evaluate()')
491
+ })
492
+ })