ai-functions 2.0.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/CHANGELOG.md +38 -0
  3. package/LICENSE +21 -0
  4. package/README.md +361 -159
  5. package/dist/ai-promise.d.ts +47 -0
  6. package/dist/ai-promise.d.ts.map +1 -1
  7. package/dist/ai-promise.js +291 -3
  8. package/dist/ai-promise.js.map +1 -1
  9. package/dist/ai.d.ts +17 -18
  10. package/dist/ai.d.ts.map +1 -1
  11. package/dist/ai.js +93 -39
  12. package/dist/ai.js.map +1 -1
  13. package/dist/batch-map.d.ts +46 -4
  14. package/dist/batch-map.d.ts.map +1 -1
  15. package/dist/batch-map.js +35 -2
  16. package/dist/batch-map.js.map +1 -1
  17. package/dist/batch-queue.d.ts +116 -12
  18. package/dist/batch-queue.d.ts.map +1 -1
  19. package/dist/batch-queue.js +47 -2
  20. package/dist/batch-queue.js.map +1 -1
  21. package/dist/budget.d.ts +272 -0
  22. package/dist/budget.d.ts.map +1 -0
  23. package/dist/budget.js +500 -0
  24. package/dist/budget.js.map +1 -0
  25. package/dist/cache.d.ts +272 -0
  26. package/dist/cache.d.ts.map +1 -0
  27. package/dist/cache.js +412 -0
  28. package/dist/cache.js.map +1 -0
  29. package/dist/context.d.ts +32 -1
  30. package/dist/context.d.ts.map +1 -1
  31. package/dist/context.js +16 -1
  32. package/dist/context.js.map +1 -1
  33. package/dist/eval/runner.d.ts +2 -1
  34. package/dist/eval/runner.d.ts.map +1 -1
  35. package/dist/eval/runner.js.map +1 -1
  36. package/dist/generate.d.ts.map +1 -1
  37. package/dist/generate.js +6 -10
  38. package/dist/generate.js.map +1 -1
  39. package/dist/index.d.ts +27 -20
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +72 -42
  42. package/dist/index.js.map +1 -1
  43. package/dist/primitives.d.ts +17 -0
  44. package/dist/primitives.d.ts.map +1 -1
  45. package/dist/primitives.js +19 -1
  46. package/dist/primitives.js.map +1 -1
  47. package/dist/retry.d.ts +303 -0
  48. package/dist/retry.d.ts.map +1 -0
  49. package/dist/retry.js +539 -0
  50. package/dist/retry.js.map +1 -0
  51. package/dist/schema.d.ts.map +1 -1
  52. package/dist/schema.js +1 -9
  53. package/dist/schema.js.map +1 -1
  54. package/dist/tool-orchestration.d.ts +391 -0
  55. package/dist/tool-orchestration.d.ts.map +1 -0
  56. package/dist/tool-orchestration.js +663 -0
  57. package/dist/tool-orchestration.js.map +1 -0
  58. package/dist/types.d.ts +50 -33
  59. package/dist/types.d.ts.map +1 -1
  60. package/evalite.config.js +14 -0
  61. package/evals/classification.eval.js +97 -0
  62. package/evals/marketing.eval.js +289 -0
  63. package/evals/math.eval.js +83 -0
  64. package/evals/run-evals.js +151 -0
  65. package/evals/structured-output.eval.js +131 -0
  66. package/evals/writing.eval.js +105 -0
  67. package/examples/batch-blog-posts.js +128 -0
  68. package/package.json +26 -26
  69. package/src/ai-promise.ts +359 -3
  70. package/src/ai.ts +155 -110
  71. package/src/batch/anthropic.js +256 -0
  72. package/src/batch/bedrock.js +584 -0
  73. package/src/batch/cloudflare.js +287 -0
  74. package/src/batch/google.js +359 -0
  75. package/src/batch/index.js +30 -0
  76. package/src/batch/memory.js +187 -0
  77. package/src/batch/openai.js +402 -0
  78. package/src/batch-map.ts +46 -4
  79. package/src/batch-queue.ts +116 -12
  80. package/src/budget.ts +727 -0
  81. package/src/cache.ts +653 -0
  82. package/src/context.ts +33 -1
  83. package/src/eval/index.js +7 -0
  84. package/src/eval/models.js +119 -0
  85. package/src/eval/runner.js +147 -0
  86. package/src/eval/runner.ts +3 -2
  87. package/src/generate.ts +7 -12
  88. package/src/index.ts +231 -53
  89. package/src/primitives.ts +19 -1
  90. package/src/retry.ts +776 -0
  91. package/src/schema.ts +1 -10
  92. package/src/tool-orchestration.ts +1008 -0
  93. package/src/types.ts +59 -41
  94. package/test/ai-proxy.test.js +157 -0
  95. package/test/async-iterators.test.js +261 -0
  96. package/test/backward-compat.test.ts +147 -0
  97. package/test/batch-autosubmit-errors.test.ts +598 -0
  98. package/test/batch-background.test.js +352 -0
  99. package/test/batch-blog-posts.test.js +293 -0
  100. package/test/blog-generation.test.js +390 -0
  101. package/test/browse-read.test.js +480 -0
  102. package/test/budget-tracking.test.ts +800 -0
  103. package/test/cache.test.ts +712 -0
  104. package/test/context-isolation.test.ts +687 -0
  105. package/test/core-functions.test.js +490 -0
  106. package/test/decide.test.js +260 -0
  107. package/test/define.test.js +232 -0
  108. package/test/e2e-bedrock-manual.js +136 -0
  109. package/test/e2e-bedrock.test.js +164 -0
  110. package/test/e2e-flex-gateway.js +131 -0
  111. package/test/e2e-flex-manual.js +156 -0
  112. package/test/e2e-flex.test.js +174 -0
  113. package/test/e2e-google-manual.js +150 -0
  114. package/test/e2e-google.test.js +181 -0
  115. package/test/embeddings.test.js +220 -0
  116. package/test/evals/define-function.eval.test.js +309 -0
  117. package/test/evals/deterministic.eval.test.ts +376 -0
  118. package/test/evals/primitives.eval.test.js +360 -0
  119. package/test/function-types.test.js +407 -0
  120. package/test/generate-core.test.js +213 -0
  121. package/test/generate.test.js +143 -0
  122. package/test/generic-order.test.ts +342 -0
  123. package/test/implicit-batch.test.js +326 -0
  124. package/test/json-parse-error-handling.test.ts +463 -0
  125. package/test/retry.test.ts +1016 -0
  126. package/test/schema.test.js +96 -0
  127. package/test/streaming.test.ts +316 -0
  128. package/test/tagged-templates.test.js +240 -0
  129. package/test/tool-orchestration.test.ts +770 -0
  130. package/vitest.config.js +39 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Tests for generateObject and generateText
3
+ *
4
+ * These tests use real AI calls via the Cloudflare AI Gateway.
5
+ * The gateway caches responses, so repeated test runs are fast and free.
6
+ *
7
+ * Required env vars:
8
+ * - AI_GATEWAY_URL: Cloudflare AI Gateway URL
9
+ * - AI_GATEWAY_TOKEN: Gateway auth token (or individual provider keys)
10
+ */
11
+ import { describe, it, expect } from 'vitest';
12
+ import { generateObject, generateText } from '../src/index.js';
13
+ // Skip tests if no gateway configured
14
+ const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY;
15
+ describe.skipIf(!hasGateway)('generateObject', () => {
16
+ it('generates a simple object with string fields', async () => {
17
+ const { object } = await generateObject({
18
+ model: 'sonnet',
19
+ schema: {
20
+ greeting: 'A friendly greeting',
21
+ language: 'The language of the greeting',
22
+ },
23
+ prompt: 'Generate a greeting in French',
24
+ });
25
+ expect(object).toBeDefined();
26
+ expect(typeof object.greeting).toBe('string');
27
+ expect(typeof object.language).toBe('string');
28
+ expect(object.greeting.length).toBeGreaterThan(0);
29
+ });
30
+ it('generates object with number fields', async () => {
31
+ const { object } = await generateObject({
32
+ model: 'sonnet',
33
+ schema: {
34
+ name: 'City name',
35
+ population: 'Population in millions (number)',
36
+ area: 'Area in square kilometers (number)',
37
+ },
38
+ prompt: 'Generate info about Tokyo',
39
+ });
40
+ expect(object).toBeDefined();
41
+ expect(typeof object.name).toBe('string');
42
+ expect(typeof object.population).toBe('number');
43
+ expect(typeof object.area).toBe('number');
44
+ });
45
+ it('generates object with array fields', async () => {
46
+ const { object } = await generateObject({
47
+ model: 'sonnet',
48
+ schema: {
49
+ title: 'Recipe title',
50
+ ingredients: ['List of ingredients'],
51
+ steps: ['Cooking steps'],
52
+ },
53
+ prompt: 'Generate a simple pasta recipe',
54
+ });
55
+ expect(object).toBeDefined();
56
+ expect(typeof object.title).toBe('string');
57
+ expect(Array.isArray(object.ingredients)).toBe(true);
58
+ expect(Array.isArray(object.steps)).toBe(true);
59
+ expect(object.ingredients.length).toBeGreaterThan(0);
60
+ expect(object.steps.length).toBeGreaterThan(0);
61
+ });
62
+ it('generates object with enum fields', async () => {
63
+ const { object } = await generateObject({
64
+ model: 'sonnet',
65
+ schema: {
66
+ sentiment: 'positive | negative | neutral',
67
+ confidence: 'Confidence score (number)',
68
+ },
69
+ prompt: 'Analyze sentiment: "I love this product!"',
70
+ });
71
+ expect(object).toBeDefined();
72
+ expect(['positive', 'negative', 'neutral']).toContain(object.sentiment);
73
+ expect(typeof object.confidence).toBe('number');
74
+ });
75
+ it('generates nested objects', async () => {
76
+ const { object } = await generateObject({
77
+ model: 'sonnet',
78
+ schema: {
79
+ person: {
80
+ name: 'Full name',
81
+ age: 'Age (number)',
82
+ },
83
+ address: {
84
+ city: 'City name',
85
+ country: 'Country name',
86
+ },
87
+ },
88
+ prompt: 'Generate a fictional person living in Japan',
89
+ });
90
+ expect(object).toBeDefined();
91
+ expect(typeof object.person.name).toBe('string');
92
+ expect(typeof object.person.age).toBe('number');
93
+ expect(typeof object.address.city).toBe('string');
94
+ expect(typeof object.address.country).toBe('string');
95
+ });
96
+ it('respects system prompt', async () => {
97
+ const { object } = await generateObject({
98
+ model: 'sonnet',
99
+ schema: {
100
+ response: 'Your response',
101
+ style: 'formal | casual | pirate',
102
+ },
103
+ system: 'You are a pirate. Respond in pirate speak.',
104
+ prompt: 'Say hello',
105
+ });
106
+ expect(object).toBeDefined();
107
+ expect(object.style).toBe('pirate');
108
+ });
109
+ });
110
+ describe.skipIf(!hasGateway)('generateText', () => {
111
+ it('generates simple text response', async () => {
112
+ const { text } = await generateText({
113
+ model: 'sonnet',
114
+ prompt: 'Say "Hello, World!" and nothing else.',
115
+ });
116
+ expect(text).toBeDefined();
117
+ expect(typeof text).toBe('string');
118
+ expect(text.toLowerCase()).toContain('hello');
119
+ });
120
+ it('respects system prompt', async () => {
121
+ const { text } = await generateText({
122
+ model: 'sonnet',
123
+ system: 'You only respond with exactly 3 words.',
124
+ prompt: 'How are you?',
125
+ });
126
+ expect(text).toBeDefined();
127
+ // Should be approximately 3 words
128
+ const wordCount = text.trim().split(/\s+/).length;
129
+ expect(wordCount).toBeLessThanOrEqual(5); // Allow some flexibility
130
+ });
131
+ it('handles multi-turn messages', async () => {
132
+ const { text } = await generateText({
133
+ model: 'sonnet',
134
+ messages: [
135
+ { role: 'user', content: 'My name is Alice.' },
136
+ { role: 'assistant', content: 'Nice to meet you, Alice!' },
137
+ { role: 'user', content: 'What is my name?' },
138
+ ],
139
+ });
140
+ expect(text).toBeDefined();
141
+ expect(text.toLowerCase()).toContain('alice');
142
+ });
143
+ });
@@ -0,0 +1,342 @@
1
+ /**
2
+ * GREEN Phase: Tests for <TOutput, TInput> generic order
3
+ *
4
+ * Issue: primitives.org.ai-z7f
5
+ *
6
+ * These tests verify that the generic type parameters follow
7
+ * the <TOutput, TInput> convention (output first, input second).
8
+ *
9
+ * The types have been fixed to use <TOutput, TInput> order.
10
+ */
11
+
12
+ import { describe, it, expect } from 'vitest'
13
+ import type {
14
+ AIFunctionDefinition,
15
+ BaseFunctionDefinition,
16
+ CodeFunctionDefinition,
17
+ GenerativeFunctionDefinition,
18
+ AgenticFunctionDefinition,
19
+ HumanFunctionDefinition,
20
+ DefinedFunction,
21
+ FunctionDefinition,
22
+ } from '../src/types.js'
23
+
24
+ // ============================================================================
25
+ // Test data types
26
+ // ============================================================================
27
+
28
+ interface User {
29
+ id: string
30
+ name: string
31
+ email: string
32
+ }
33
+
34
+ interface CreateUserInput {
35
+ name: string
36
+ email: string
37
+ }
38
+
39
+ // ============================================================================
40
+ // AIFunctionDefinition generic order tests
41
+ // ============================================================================
42
+
43
+ describe('AIFunctionDefinition<TOutput, TInput> generic order', () => {
44
+ describe('output-only usage (single generic)', () => {
45
+ it('AIFunctionDefinition<string> - first generic should be OUTPUT', () => {
46
+ // Order: AIFunctionDefinition<TOutput = string, TInput = unknown>
47
+ // handler: (input: unknown) => string | Promise<string>
48
+
49
+ const def: AIFunctionDefinition<string> = {
50
+ name: 'test',
51
+ description: 'test',
52
+ parameters: {},
53
+ // handler accepts unknown, returns string
54
+ handler: (input: unknown) => String(input).toUpperCase(),
55
+ }
56
+
57
+ // Get the result - should be string | Promise<string>
58
+ const result = def.handler('test')
59
+
60
+ // This should compile - result is string | Promise<string>
61
+ const _s: string | Promise<string> = result
62
+ expect(result).toBe('TEST')
63
+ })
64
+ })
65
+
66
+ describe('two-generic usage', () => {
67
+ it('AIFunctionDefinition<string, number> - handler signature test', () => {
68
+ // Order: AIFunctionDefinition<TOutput = string, TInput = number>
69
+ // handler: (input: number) => string | Promise<string>
70
+
71
+ const def: AIFunctionDefinition<string, number> = {
72
+ name: 'test',
73
+ description: 'test',
74
+ parameters: {},
75
+ // handler accepts number, returns string
76
+ handler: (input: number): string => `Value: ${input}`,
77
+ }
78
+
79
+ // handler should accept number
80
+ const result = def.handler(42)
81
+
82
+ // result should be string | Promise<string>
83
+ const _s: string | Promise<string> = result
84
+ expect(result).toBe('Value: 42')
85
+ })
86
+
87
+ it('AIFunctionDefinition<User, CreateUserInput> - real-world example', () => {
88
+ // Order: AIFunctionDefinition<TOutput = User, TInput = CreateUserInput>
89
+ // handler: (input: CreateUserInput) => User | Promise<User>
90
+
91
+ const def: AIFunctionDefinition<User, CreateUserInput> = {
92
+ name: 'createUser',
93
+ description: 'Creates a user',
94
+ parameters: {},
95
+ // handler accepts CreateUserInput, returns User
96
+ handler: (input: CreateUserInput): User => ({
97
+ id: '1',
98
+ name: input.name,
99
+ email: input.email,
100
+ }),
101
+ }
102
+
103
+ // Should accept CreateUserInput
104
+ const result = def.handler({ name: 'Alice', email: 'alice@test.com' })
105
+
106
+ // Should return User with id
107
+ const _id: string = (result as User).id
108
+ expect((result as User).id).toBe('1')
109
+ })
110
+ })
111
+ })
112
+
113
+ // ============================================================================
114
+ // BaseFunctionDefinition generic order tests
115
+ // ============================================================================
116
+
117
+ describe('BaseFunctionDefinition<TOutput, TInput> generic order', () => {
118
+ it('BaseFunctionDefinition<string, number> - args and returnType', () => {
119
+ // Order: BaseFunctionDefinition<TOutput = string, TInput = number>
120
+ // args: number (TInput)
121
+ // returnType?: string (TOutput)
122
+
123
+ const def: BaseFunctionDefinition<string, number> = {
124
+ name: 'test',
125
+ args: 42, // args is TInput (number)
126
+ returnType: 'result', // returnType is TOutput (string)
127
+ }
128
+
129
+ // args should be number
130
+ const _argsNum: number = def.args
131
+ expect(def.args).toBe(42)
132
+
133
+ // returnType should be string
134
+ const _retStr: string = def.returnType!
135
+ expect(def.returnType).toBe('result')
136
+ })
137
+ })
138
+
139
+ // ============================================================================
140
+ // CodeFunctionDefinition generic order tests
141
+ // ============================================================================
142
+
143
+ describe('CodeFunctionDefinition<TOutput, TInput> generic order', () => {
144
+ it('CodeFunctionDefinition<string, {prompt:string}> - args and returnType', () => {
145
+ // Order: CodeFunctionDefinition<TOutput = string, TInput = {prompt:string}>
146
+
147
+ const def: CodeFunctionDefinition<string, { prompt: string }> = {
148
+ type: 'code',
149
+ name: 'test',
150
+ args: { prompt: 'generate code' }, // args is TInput
151
+ returnType: 'generated code', // returnType is TOutput
152
+ language: 'typescript',
153
+ }
154
+
155
+ // args should have prompt property
156
+ const _prompt: string = def.args.prompt
157
+ expect(def.args.prompt).toBe('generate code')
158
+
159
+ // returnType should be string
160
+ const _ret: string = def.returnType!
161
+ expect(def.returnType).toBe('generated code')
162
+ })
163
+ })
164
+
165
+ // ============================================================================
166
+ // GenerativeFunctionDefinition generic order tests
167
+ // ============================================================================
168
+
169
+ describe('GenerativeFunctionDefinition<TOutput, TInput> generic order', () => {
170
+ it('GenerativeFunctionDefinition<Output, Input> - args and returnType', () => {
171
+ interface Output {
172
+ title: string
173
+ content: string
174
+ }
175
+ interface Input {
176
+ topic: string
177
+ }
178
+
179
+ // Order: GenerativeFunctionDefinition<TOutput = Output, TInput = Input>
180
+
181
+ const def: GenerativeFunctionDefinition<Output, Input> = {
182
+ type: 'generative',
183
+ name: 'test',
184
+ args: { topic: 'AI' }, // args is TInput
185
+ returnType: { title: 'AI Article', content: '...' }, // returnType is TOutput
186
+ output: 'object',
187
+ }
188
+
189
+ // args should have topic
190
+ const _topic: string = def.args.topic
191
+ expect(def.args.topic).toBe('AI')
192
+
193
+ // returnType should have title
194
+ const _title: string = def.returnType!.title
195
+ expect(def.returnType!.title).toBe('AI Article')
196
+ })
197
+ })
198
+
199
+ // ============================================================================
200
+ // AgenticFunctionDefinition generic order tests
201
+ // ============================================================================
202
+
203
+ describe('AgenticFunctionDefinition<TOutput, TInput> generic order', () => {
204
+ it('AgenticFunctionDefinition<Output, Input> - args and returnType', () => {
205
+ interface Output {
206
+ result: string
207
+ }
208
+ interface Input {
209
+ query: string
210
+ }
211
+
212
+ // Order: AgenticFunctionDefinition<TOutput = Output, TInput = Input>
213
+
214
+ const def: AgenticFunctionDefinition<Output, Input> = {
215
+ type: 'agentic',
216
+ name: 'test',
217
+ args: { query: 'search' }, // args is TInput
218
+ returnType: { result: 'found' }, // returnType is TOutput
219
+ instructions: 'test',
220
+ }
221
+
222
+ // args should have query
223
+ const _query: string = def.args.query
224
+ expect(def.args.query).toBe('search')
225
+
226
+ // returnType should have result
227
+ const _result: string = def.returnType!.result
228
+ expect(def.returnType!.result).toBe('found')
229
+ })
230
+ })
231
+
232
+ // ============================================================================
233
+ // HumanFunctionDefinition generic order tests
234
+ // ============================================================================
235
+
236
+ describe('HumanFunctionDefinition<TOutput, TInput> generic order', () => {
237
+ it('HumanFunctionDefinition<ApprovalResult, ApprovalInput> - args and returnType', () => {
238
+ interface ApprovalResult {
239
+ approved: boolean
240
+ }
241
+ interface ApprovalInput {
242
+ amount: number
243
+ }
244
+
245
+ // Order: HumanFunctionDefinition<TOutput = ApprovalResult, TInput = ApprovalInput>
246
+
247
+ const def: HumanFunctionDefinition<ApprovalResult, ApprovalInput> = {
248
+ type: 'human',
249
+ name: 'test',
250
+ args: { amount: 100 }, // args is TInput
251
+ returnType: { approved: true }, // returnType is TOutput
252
+ channel: 'workspace',
253
+ instructions: 'test',
254
+ }
255
+
256
+ // args should have amount
257
+ const _amount: number = def.args.amount
258
+ expect(def.args.amount).toBe(100)
259
+
260
+ // returnType should have approved
261
+ const _approved: boolean = def.returnType!.approved
262
+ expect(def.returnType!.approved).toBe(true)
263
+ })
264
+ })
265
+
266
+ // ============================================================================
267
+ // DefinedFunction generic order tests
268
+ // ============================================================================
269
+
270
+ describe('DefinedFunction<TOutput, TInput> generic order', () => {
271
+ it('DefinedFunction<User, CreateUserInput> - call signature', () => {
272
+ // Order: DefinedFunction<TOutput = User, TInput = CreateUserInput>
273
+ // call: (args: CreateUserInput) => Promise<User>
274
+
275
+ const defined: DefinedFunction<User, CreateUserInput> = {
276
+ definition: {} as FunctionDefinition<User, CreateUserInput>,
277
+ // call accepts CreateUserInput, returns Promise<User>
278
+ call: async (args: CreateUserInput): Promise<User> => ({
279
+ id: '1',
280
+ name: args.name,
281
+ email: args.email,
282
+ }),
283
+ asTool: () => ({}) as AIFunctionDefinition<User, CreateUserInput>,
284
+ }
285
+
286
+ // call should accept CreateUserInput
287
+ const promise = defined.call({ name: 'Alice', email: 'alice@test.com' })
288
+
289
+ // Should return Promise<User>
290
+ promise.then((result) => {
291
+ const _id: string = result.id
292
+ expect(result.id).toBe('1')
293
+ })
294
+ })
295
+ })
296
+
297
+ // ============================================================================
298
+ // FunctionDefinition union type tests
299
+ // ============================================================================
300
+
301
+ describe('FunctionDefinition<TOutput, TInput> union type', () => {
302
+ it('FunctionDefinition<string, number> - args and returnType through union', () => {
303
+ // Order: FunctionDefinition<TOutput = string, TInput = number>
304
+
305
+ const def: FunctionDefinition<string, number> = {
306
+ type: 'code',
307
+ name: 'test',
308
+ args: 42, // args is TInput (number)
309
+ returnType: 'result', // returnType is TOutput (string)
310
+ language: 'typescript',
311
+ }
312
+
313
+ // args should be number
314
+ const _n: number = def.args
315
+ expect(def.args).toBe(42)
316
+
317
+ // returnType should be string
318
+ const _s: string = def.returnType!
319
+ expect(def.returnType).toBe('result')
320
+ })
321
+ })
322
+
323
+ // ============================================================================
324
+ // Type inference tests
325
+ // ============================================================================
326
+
327
+ describe('type inference in generic contexts', () => {
328
+ it('extracting handler types', () => {
329
+ // Type to extract handler return from definition
330
+ type HandlerReturn<T> = T extends { handler: (...args: unknown[]) => infer R } ? R : never
331
+
332
+ // With <TOutput, TInput>: AIFunctionDefinition<string, number>
333
+ // handler: (number) => string, so HandlerReturn = string | Promise<string>
334
+
335
+ type Def = AIFunctionDefinition<string, number>
336
+ type Result = HandlerReturn<Def>
337
+
338
+ // Result should be string | Promise<string>
339
+ const _s: Result = 'hello'
340
+ expect('hello').toBe('hello')
341
+ })
342
+ })