ai-functions 2.1.1 → 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 (286) hide show
  1. package/.turbo/turbo-build.log +1 -4
  2. package/CHANGELOG.md +68 -1
  3. package/README.md +397 -157
  4. package/dist/ai-promise.d.ts +50 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +410 -51
  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 +54 -837
  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 +272 -0
  56. package/dist/budget.d.ts.map +1 -0
  57. package/dist/budget.js +513 -0
  58. package/dist/budget.js.map +1 -0
  59. package/dist/cache.d.ts +295 -0
  60. package/dist/cache.d.ts.map +1 -0
  61. package/dist/cache.js +433 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/context.d.ts +42 -8
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +64 -62
  66. package/dist/context.js.map +1 -1
  67. package/dist/digital-objects-registry.d.ts +229 -0
  68. package/dist/digital-objects-registry.d.ts.map +1 -0
  69. package/dist/digital-objects-registry.js +617 -0
  70. package/dist/digital-objects-registry.js.map +1 -0
  71. package/dist/embeddings.d.ts +2 -2
  72. package/dist/embeddings.d.ts.map +1 -1
  73. package/dist/errors.d.ts +22 -0
  74. package/dist/errors.d.ts.map +1 -0
  75. package/dist/errors.js +35 -0
  76. package/dist/errors.js.map +1 -0
  77. package/dist/eval/runner.d.ts +10 -1
  78. package/dist/eval/runner.d.ts.map +1 -1
  79. package/dist/eval/runner.js +41 -35
  80. package/dist/eval/runner.js.map +1 -1
  81. package/dist/eval-log/in-memory.d.ts +34 -0
  82. package/dist/eval-log/in-memory.d.ts.map +1 -0
  83. package/dist/eval-log/in-memory.js +84 -0
  84. package/dist/eval-log/in-memory.js.map +1 -0
  85. package/dist/eval-log/index.d.ts +29 -0
  86. package/dist/eval-log/index.d.ts.map +1 -0
  87. package/dist/eval-log/index.js +39 -0
  88. package/dist/eval-log/index.js.map +1 -0
  89. package/dist/eval-log/types.d.ts +101 -0
  90. package/dist/eval-log/types.d.ts.map +1 -0
  91. package/dist/eval-log/types.js +16 -0
  92. package/dist/eval-log/types.js.map +1 -0
  93. package/dist/function-registry.d.ts +116 -0
  94. package/dist/function-registry.d.ts.map +1 -0
  95. package/dist/function-registry.js +546 -0
  96. package/dist/function-registry.js.map +1 -0
  97. package/dist/generate.d.ts +9 -3
  98. package/dist/generate.d.ts.map +1 -1
  99. package/dist/generate.js +18 -22
  100. package/dist/generate.js.map +1 -1
  101. package/dist/index.d.ts +35 -20
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +89 -42
  104. package/dist/index.js.map +1 -1
  105. package/dist/logger.d.ts +118 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +187 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/middleware/budget.d.ts +84 -0
  110. package/dist/middleware/budget.d.ts.map +1 -0
  111. package/dist/middleware/budget.js +110 -0
  112. package/dist/middleware/budget.js.map +1 -0
  113. package/dist/middleware/cache.d.ts +103 -0
  114. package/dist/middleware/cache.d.ts.map +1 -0
  115. package/dist/middleware/cache.js +228 -0
  116. package/dist/middleware/cache.js.map +1 -0
  117. package/dist/middleware/embed-cache.d.ts +99 -0
  118. package/dist/middleware/embed-cache.d.ts.map +1 -0
  119. package/dist/middleware/embed-cache.js +128 -0
  120. package/dist/middleware/embed-cache.js.map +1 -0
  121. package/dist/middleware/index.d.ts +11 -0
  122. package/dist/middleware/index.d.ts.map +1 -0
  123. package/dist/middleware/index.js +11 -0
  124. package/dist/middleware/index.js.map +1 -0
  125. package/dist/middleware/trace.d.ts +103 -0
  126. package/dist/middleware/trace.d.ts.map +1 -0
  127. package/dist/middleware/trace.js +176 -0
  128. package/dist/middleware/trace.js.map +1 -0
  129. package/dist/primitives.d.ts +120 -1
  130. package/dist/primitives.d.ts.map +1 -1
  131. package/dist/primitives.js +398 -26
  132. package/dist/primitives.js.map +1 -1
  133. package/dist/retry.d.ts +368 -0
  134. package/dist/retry.d.ts.map +1 -0
  135. package/dist/retry.js +646 -0
  136. package/dist/retry.js.map +1 -0
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +2 -10
  139. package/dist/schema.js.map +1 -1
  140. package/dist/telemetry.d.ts +128 -0
  141. package/dist/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry.js +285 -0
  143. package/dist/telemetry.js.map +1 -0
  144. package/dist/template.d.ts.map +1 -1
  145. package/dist/template.js +6 -1
  146. package/dist/template.js.map +1 -1
  147. package/dist/tool-orchestration.d.ts +453 -0
  148. package/dist/tool-orchestration.d.ts.map +1 -0
  149. package/dist/tool-orchestration.js +763 -0
  150. package/dist/tool-orchestration.js.map +1 -0
  151. package/dist/type-guards.d.ts +28 -0
  152. package/dist/type-guards.d.ts.map +1 -0
  153. package/dist/type-guards.js +29 -0
  154. package/dist/type-guards.js.map +1 -0
  155. package/dist/types.d.ts +135 -17
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +36 -1
  158. package/dist/types.js.map +1 -1
  159. package/dist/wrap-for-v3.d.ts +80 -0
  160. package/dist/wrap-for-v3.d.ts.map +1 -0
  161. package/dist/wrap-for-v3.js +89 -0
  162. package/dist/wrap-for-v3.js.map +1 -0
  163. package/examples/00-quickstart.ts +232 -0
  164. package/examples/01-rag-chatbot.ts +212 -0
  165. package/examples/02-multi-agent-research.ts +290 -0
  166. package/examples/03-email-classification.ts +379 -0
  167. package/examples/04-content-moderation.ts +400 -0
  168. package/examples/05-document-extraction.ts +455 -0
  169. package/examples/06-streaming-chat-nextjs.ts +437 -0
  170. package/examples/07-cloudflare-worker.ts +483 -0
  171. package/examples/08-batch-processing.ts +491 -0
  172. package/examples/09-budget-constrained.ts +527 -0
  173. package/examples/10-tool-orchestration.ts +565 -0
  174. package/examples/11-retry-resilience.ts +403 -0
  175. package/examples/12-caching-strategies.ts +422 -0
  176. package/examples/README.md +145 -0
  177. package/package.json +10 -6
  178. package/src/ai-promise.ts +528 -99
  179. package/src/ai-schemas.ts +122 -0
  180. package/src/ai.ts +69 -1153
  181. package/src/batch/anthropic.ts +96 -161
  182. package/src/batch/bedrock.ts +203 -454
  183. package/src/batch/cloudflare.ts +99 -282
  184. package/src/batch/google.ts +91 -297
  185. package/src/batch/index.ts +4 -1
  186. package/src/batch/memory.ts +15 -10
  187. package/src/batch/openai.ts +65 -193
  188. package/src/batch/provider.ts +336 -0
  189. package/src/batch-map.ts +29 -24
  190. package/src/batch-queue.ts +200 -11
  191. package/src/budget.ts +740 -0
  192. package/src/cache.ts +681 -0
  193. package/src/context.ts +122 -76
  194. package/src/digital-objects-registry.ts +750 -0
  195. package/src/errors.ts +37 -0
  196. package/src/eval/runner.ts +63 -38
  197. package/src/eval-log/in-memory.ts +90 -0
  198. package/src/eval-log/index.ts +46 -0
  199. package/src/eval-log/types.ts +110 -0
  200. package/src/function-registry.ts +671 -0
  201. package/src/generate.ts +33 -33
  202. package/src/index.ts +325 -49
  203. package/src/logger.ts +232 -0
  204. package/src/middleware/budget.ts +171 -0
  205. package/src/middleware/cache.ts +299 -0
  206. package/src/middleware/embed-cache.ts +195 -0
  207. package/src/middleware/index.ts +23 -0
  208. package/src/middleware/trace.ts +248 -0
  209. package/src/primitives.ts +589 -62
  210. package/src/retry.ts +902 -0
  211. package/src/schema.ts +8 -17
  212. package/src/telemetry.ts +403 -0
  213. package/src/template.ts +8 -4
  214. package/src/tool-orchestration.ts +1173 -0
  215. package/src/type-guards.ts +31 -0
  216. package/src/types.ts +164 -25
  217. package/src/wrap-for-v3.ts +105 -0
  218. package/test/ai-promise.test.ts +1080 -0
  219. package/test/ai-proxy.test.ts +1 -1
  220. package/test/backward-compat.test.ts +147 -0
  221. package/test/batch-autosubmit-errors.test.ts +610 -0
  222. package/test/batch-blog-posts.test.ts +87 -129
  223. package/test/budget-tracking.test.ts +800 -0
  224. package/test/cache.test.ts +712 -0
  225. package/test/context-isolation.test.ts +687 -0
  226. package/test/core-functions.test.ts +183 -579
  227. package/test/decide.test.ts +154 -322
  228. package/test/define.test.ts +211 -8
  229. package/test/digital-objects-registry.test.ts +760 -0
  230. package/test/embedding-cache-middleware.test.ts +140 -0
  231. package/test/evals/deterministic.eval.test.ts +376 -0
  232. package/test/generate-core.test.ts +140 -229
  233. package/test/implicit-batch.test.ts +22 -65
  234. package/test/json-parse-error-handling.test.ts +463 -0
  235. package/test/retry-policy-integration.test.ts +117 -0
  236. package/test/retry.test.ts +1016 -0
  237. package/test/schema.test.ts +55 -19
  238. package/test/streaming.test.ts +316 -0
  239. package/test/template.test.ts +1164 -0
  240. package/test/tool-orchestration.test.ts +1040 -0
  241. package/test/wrap-for-v3.test.ts +612 -0
  242. package/vitest.config.js +6 -0
  243. package/vitest.config.ts +20 -0
  244. package/dist/rpc/auth.d.ts +0 -69
  245. package/dist/rpc/auth.d.ts.map +0 -1
  246. package/dist/rpc/auth.js +0 -136
  247. package/dist/rpc/auth.js.map +0 -1
  248. package/dist/rpc/client.d.ts +0 -62
  249. package/dist/rpc/client.d.ts.map +0 -1
  250. package/dist/rpc/client.js +0 -103
  251. package/dist/rpc/client.js.map +0 -1
  252. package/dist/rpc/deferred.d.ts +0 -60
  253. package/dist/rpc/deferred.d.ts.map +0 -1
  254. package/dist/rpc/deferred.js +0 -96
  255. package/dist/rpc/deferred.js.map +0 -1
  256. package/dist/rpc/index.d.ts +0 -22
  257. package/dist/rpc/index.d.ts.map +0 -1
  258. package/dist/rpc/index.js +0 -38
  259. package/dist/rpc/index.js.map +0 -1
  260. package/dist/rpc/local.d.ts +0 -42
  261. package/dist/rpc/local.d.ts.map +0 -1
  262. package/dist/rpc/local.js +0 -50
  263. package/dist/rpc/local.js.map +0 -1
  264. package/dist/rpc/server.d.ts +0 -165
  265. package/dist/rpc/server.d.ts.map +0 -1
  266. package/dist/rpc/server.js +0 -405
  267. package/dist/rpc/server.js.map +0 -1
  268. package/dist/rpc/session.d.ts +0 -32
  269. package/dist/rpc/session.d.ts.map +0 -1
  270. package/dist/rpc/session.js +0 -43
  271. package/dist/rpc/session.js.map +0 -1
  272. package/dist/rpc/transport.d.ts +0 -306
  273. package/dist/rpc/transport.d.ts.map +0 -1
  274. package/dist/rpc/transport.js +0 -731
  275. package/dist/rpc/transport.js.map +0 -1
  276. package/src/batch/anthropic.js +0 -256
  277. package/src/batch/bedrock.js +0 -584
  278. package/src/batch/cloudflare.js +0 -287
  279. package/src/batch/google.js +0 -359
  280. package/src/batch/index.js +0 -30
  281. package/src/batch/memory.js +0 -187
  282. package/src/batch/openai.js +0 -402
  283. package/src/eval/index.js +0 -7
  284. package/src/eval/models.js +0 -119
  285. package/src/eval/runner.js +0 -147
  286. package/test/schema.test.js +0 -96
@@ -5,14 +5,14 @@
5
5
  */
6
6
 
7
7
  import { describe, it, expect, beforeEach } from 'vitest'
8
- import { define, defineFunction, functions } from '../src/index.js'
8
+ import { define, defineFunction, functions, createFunctionRegistry, resetGlobalRegistry } from '../src/index.js'
9
9
 
10
10
  // Skip tests if no gateway configured
11
11
  const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY
12
12
 
13
13
  describe('functions registry', () => {
14
14
  beforeEach(() => {
15
- functions.clear()
15
+ resetGlobalRegistry()
16
16
  })
17
17
 
18
18
  it('starts empty', () => {
@@ -75,14 +75,152 @@ describe('functions registry', () => {
75
75
  functions.set('func1', fn1)
76
76
  functions.set('func2', fn2)
77
77
 
78
- functions.clear()
78
+ resetGlobalRegistry()
79
+ expect(functions.list()).toEqual([])
80
+ })
81
+ })
82
+
83
+ describe('createFunctionRegistry', () => {
84
+ beforeEach(() => {
85
+ resetGlobalRegistry()
86
+ })
87
+
88
+ it('creates an isolated registry instance', () => {
89
+ const registry = createFunctionRegistry()
90
+ expect(registry.list()).toEqual([])
91
+ })
92
+
93
+ it('registry operations do not affect global registry', () => {
94
+ // Add function to global registry
95
+ const globalFn = defineFunction({
96
+ type: 'generative',
97
+ name: 'globalFunc',
98
+ args: { input: 'Test input' },
99
+ output: 'string',
100
+ })
101
+ functions.set('globalFunc', globalFn)
102
+
103
+ // Create isolated registry and add different function
104
+ const registry = createFunctionRegistry()
105
+ const isolatedFn = defineFunction({
106
+ type: 'generative',
107
+ name: 'isolatedFunc',
108
+ args: { data: 'Test data' },
109
+ output: 'string',
110
+ })
111
+ registry.set('isolatedFunc', isolatedFn)
112
+
113
+ // Verify isolation
114
+ expect(functions.has('globalFunc')).toBe(true)
115
+ expect(functions.has('isolatedFunc')).toBe(false)
116
+ expect(registry.has('isolatedFunc')).toBe(true)
117
+ expect(registry.has('globalFunc')).toBe(false)
118
+ })
119
+
120
+ it('multiple registries are independent of each other', () => {
121
+ const registry1 = createFunctionRegistry()
122
+ const registry2 = createFunctionRegistry()
123
+
124
+ const fn1 = defineFunction({
125
+ type: 'generative',
126
+ name: 'func1',
127
+ args: {},
128
+ output: 'string',
129
+ })
130
+ const fn2 = defineFunction({
131
+ type: 'generative',
132
+ name: 'func2',
133
+ args: {},
134
+ output: 'string',
135
+ })
136
+
137
+ registry1.set('func1', fn1)
138
+ registry2.set('func2', fn2)
139
+
140
+ expect(registry1.has('func1')).toBe(true)
141
+ expect(registry1.has('func2')).toBe(false)
142
+ expect(registry2.has('func1')).toBe(false)
143
+ expect(registry2.has('func2')).toBe(true)
144
+ })
145
+
146
+ it('isolated registry supports all operations', () => {
147
+ const registry = createFunctionRegistry()
148
+
149
+ const fn = defineFunction({
150
+ type: 'generative',
151
+ name: 'testFunc',
152
+ args: { x: 'number' },
153
+ output: 'string',
154
+ })
155
+
156
+ // set and get
157
+ registry.set('testFunc', fn)
158
+ expect(registry.get('testFunc')).toBe(fn)
159
+
160
+ // has
161
+ expect(registry.has('testFunc')).toBe(true)
162
+
163
+ // list
164
+ expect(registry.list()).toEqual(['testFunc'])
165
+
166
+ // delete
167
+ expect(registry.delete('testFunc')).toBe(true)
168
+ expect(registry.has('testFunc')).toBe(false)
169
+
170
+ // clear
171
+ registry.set('a', fn)
172
+ registry.set('b', fn)
173
+ registry.clear()
174
+ expect(registry.list()).toEqual([])
175
+ })
176
+ })
177
+
178
+ describe('resetGlobalRegistry', () => {
179
+ it('clears all functions from global registry', () => {
180
+ const fn = defineFunction({
181
+ type: 'generative',
182
+ name: 'testFunc',
183
+ args: {},
184
+ output: 'string',
185
+ })
186
+ functions.set('testFunc', fn)
187
+ expect(functions.has('testFunc')).toBe(true)
188
+
189
+ resetGlobalRegistry()
190
+ expect(functions.has('testFunc')).toBe(false)
191
+ expect(functions.list()).toEqual([])
192
+ })
193
+
194
+ it('does not affect isolated registries', () => {
195
+ const isolatedRegistry = createFunctionRegistry()
196
+ const fn = defineFunction({
197
+ type: 'generative',
198
+ name: 'isolatedFunc',
199
+ args: {},
200
+ output: 'string',
201
+ })
202
+ isolatedRegistry.set('isolatedFunc', fn)
203
+
204
+ // Add to global and reset
205
+ functions.set('globalFunc', fn)
206
+ resetGlobalRegistry()
207
+
208
+ // Global should be empty, isolated should still have function
209
+ expect(functions.list()).toEqual([])
210
+ expect(isolatedRegistry.has('isolatedFunc')).toBe(true)
211
+ })
212
+
213
+ it('can be called multiple times safely', () => {
214
+ resetGlobalRegistry()
215
+ resetGlobalRegistry()
216
+ resetGlobalRegistry()
79
217
  expect(functions.list()).toEqual([])
80
218
  })
81
219
  })
82
220
 
83
221
  describe('defineFunction', () => {
84
222
  beforeEach(() => {
85
- functions.clear()
223
+ resetGlobalRegistry()
86
224
  })
87
225
 
88
226
  it('creates a generative function definition', () => {
@@ -132,6 +270,7 @@ describe('defineFunction', () => {
132
270
  name: 'implement',
133
271
  args: { spec: 'Function specification' },
134
272
  language: 'typescript',
273
+ handler: () => 'ok',
135
274
  })
136
275
 
137
276
  expect(fn.definition.type).toBe('code')
@@ -163,7 +302,7 @@ describe('defineFunction', () => {
163
302
 
164
303
  describe('define helpers', () => {
165
304
  beforeEach(() => {
166
- functions.clear()
305
+ resetGlobalRegistry()
167
306
  })
168
307
 
169
308
  it('define.generative registers function', () => {
@@ -204,7 +343,8 @@ describe('define helpers', () => {
204
343
  const fn = define.code({
205
344
  name: 'generate',
206
345
  args: { prompt: 'Code generation prompt' },
207
- language: 'python',
346
+ language: 'typescript',
347
+ handler: () => 'ok',
208
348
  })
209
349
 
210
350
  expect(functions.has('generate')).toBe(true)
@@ -212,9 +352,72 @@ describe('define helpers', () => {
212
352
  })
213
353
  })
214
354
 
355
+ // Code is the DETERMINISTIC kind — no gateway required. These tests assert the
356
+ // post-split contract: `type: 'code'` runs a handler (or inline body), never a
357
+ // model, at call time. (Code-authoring moved to generateCode().)
358
+ describe('code function execution (deterministic)', () => {
359
+ beforeEach(() => {
360
+ resetGlobalRegistry()
361
+ })
362
+
363
+ it('runs the supplied handler with no LLM call', async () => {
364
+ const calculateTax = defineFunction<number, { amount: number; rate: number }>({
365
+ type: 'code',
366
+ name: 'calculateTax',
367
+ args: { amount: 'Amount (number)', rate: 'Rate (number)' },
368
+ handler: ({ amount, rate }) => amount * rate,
369
+ })
370
+
371
+ const result = await calculateTax.call({ amount: 100, rate: 0.2 })
372
+ expect(result).toBe(20)
373
+ // Determinism: same input → same output, every time
374
+ expect(await calculateTax.call({ amount: 100, rate: 0.2 })).toBe(20)
375
+ })
376
+
377
+ it('awaits an async handler', async () => {
378
+ const fn = defineFunction<string, { name: string }>({
379
+ type: 'code',
380
+ name: 'greetAsync',
381
+ args: { name: 'Name' },
382
+ handler: async ({ name }) => `hi ${name}`,
383
+ })
384
+ expect(await fn.call({ name: 'world' })).toBe('hi world')
385
+ })
386
+
387
+ it('evaluates an inline code body deterministically', async () => {
388
+ const fn = defineFunction<number, { items: number[] }>({
389
+ type: 'code',
390
+ name: 'sum',
391
+ args: { items: ['Numbers'] },
392
+ language: 'typescript',
393
+ code: 'return args.items.reduce((a, b) => a + b, 0)',
394
+ })
395
+ expect(await fn.call({ items: [1, 2, 3, 4] })).toBe(10)
396
+ })
397
+
398
+ it('throws (does not call a model) when no handler or code is provided', async () => {
399
+ const fn = defineFunction({
400
+ type: 'code',
401
+ name: 'noImpl',
402
+ args: { spec: 'spec' },
403
+ language: 'typescript',
404
+ })
405
+ await expect(fn.call({ spec: 'x' })).rejects.toThrow(/no handler or inline code/i)
406
+ })
407
+
408
+ it('define.code runs the handler', async () => {
409
+ const fn = define.code<number, { a: number; b: number }>({
410
+ name: 'add',
411
+ args: { a: 'a (number)', b: 'b (number)' },
412
+ handler: ({ a, b }) => a + b,
413
+ })
414
+ expect(await fn.call({ a: 2, b: 3 })).toBe(5)
415
+ })
416
+ })
417
+
215
418
  describe.skipIf(!hasGateway)('generative function execution', () => {
216
419
  beforeEach(() => {
217
- functions.clear()
420
+ resetGlobalRegistry()
218
421
  })
219
422
 
220
423
  it('executes a generative string function', async () => {
@@ -251,7 +454,7 @@ describe.skipIf(!hasGateway)('generative function execution', () => {
251
454
 
252
455
  describe.skipIf(!hasGateway)('auto-define', () => {
253
456
  beforeEach(() => {
254
- functions.clear()
457
+ resetGlobalRegistry()
255
458
  })
256
459
 
257
460
  it('auto-defines a function from name and args', async () => {