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.
- package/.turbo/turbo-build.log +4 -5
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +361 -159
- package/dist/ai-promise.d.ts +47 -0
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +291 -3
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai.d.ts +17 -18
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +93 -39
- package/dist/ai.js.map +1 -1
- package/dist/batch-map.d.ts +46 -4
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +35 -2
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +116 -12
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +47 -2
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts +272 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +500 -0
- package/dist/budget.js.map +1 -0
- package/dist/cache.d.ts +272 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +412 -0
- package/dist/cache.js.map +1 -0
- package/dist/context.d.ts +32 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +16 -1
- package/dist/context.js.map +1 -1
- package/dist/eval/runner.d.ts +2 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js.map +1 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +6 -10
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +27 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -42
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +17 -0
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +19 -1
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +303 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +539 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -9
- package/dist/schema.js.map +1 -1
- package/dist/tool-orchestration.d.ts +391 -0
- package/dist/tool-orchestration.d.ts.map +1 -0
- package/dist/tool-orchestration.js +663 -0
- package/dist/tool-orchestration.js.map +1 -0
- package/dist/types.d.ts +50 -33
- package/dist/types.d.ts.map +1 -1
- package/evalite.config.js +14 -0
- package/evals/classification.eval.js +97 -0
- package/evals/marketing.eval.js +289 -0
- package/evals/math.eval.js +83 -0
- package/evals/run-evals.js +151 -0
- package/evals/structured-output.eval.js +131 -0
- package/evals/writing.eval.js +105 -0
- package/examples/batch-blog-posts.js +128 -0
- package/package.json +26 -26
- package/src/ai-promise.ts +359 -3
- package/src/ai.ts +155 -110
- package/src/batch/anthropic.js +256 -0
- package/src/batch/bedrock.js +584 -0
- package/src/batch/cloudflare.js +287 -0
- package/src/batch/google.js +359 -0
- package/src/batch/index.js +30 -0
- package/src/batch/memory.js +187 -0
- package/src/batch/openai.js +402 -0
- package/src/batch-map.ts +46 -4
- package/src/batch-queue.ts +116 -12
- package/src/budget.ts +727 -0
- package/src/cache.ts +653 -0
- package/src/context.ts +33 -1
- package/src/eval/index.js +7 -0
- package/src/eval/models.js +119 -0
- package/src/eval/runner.js +147 -0
- package/src/eval/runner.ts +3 -2
- package/src/generate.ts +7 -12
- package/src/index.ts +231 -53
- package/src/primitives.ts +19 -1
- package/src/retry.ts +776 -0
- package/src/schema.ts +1 -10
- package/src/tool-orchestration.ts +1008 -0
- package/src/types.ts +59 -41
- package/test/ai-proxy.test.js +157 -0
- package/test/async-iterators.test.js +261 -0
- package/test/backward-compat.test.ts +147 -0
- package/test/batch-autosubmit-errors.test.ts +598 -0
- package/test/batch-background.test.js +352 -0
- package/test/batch-blog-posts.test.js +293 -0
- package/test/blog-generation.test.js +390 -0
- package/test/browse-read.test.js +480 -0
- package/test/budget-tracking.test.ts +800 -0
- package/test/cache.test.ts +712 -0
- package/test/context-isolation.test.ts +687 -0
- package/test/core-functions.test.js +490 -0
- package/test/decide.test.js +260 -0
- package/test/define.test.js +232 -0
- package/test/e2e-bedrock-manual.js +136 -0
- package/test/e2e-bedrock.test.js +164 -0
- package/test/e2e-flex-gateway.js +131 -0
- package/test/e2e-flex-manual.js +156 -0
- package/test/e2e-flex.test.js +174 -0
- package/test/e2e-google-manual.js +150 -0
- package/test/e2e-google.test.js +181 -0
- package/test/embeddings.test.js +220 -0
- package/test/evals/define-function.eval.test.js +309 -0
- package/test/evals/deterministic.eval.test.ts +376 -0
- package/test/evals/primitives.eval.test.js +360 -0
- package/test/function-types.test.js +407 -0
- package/test/generate-core.test.js +213 -0
- package/test/generate.test.js +143 -0
- package/test/generic-order.test.ts +342 -0
- package/test/implicit-batch.test.js +326 -0
- package/test/json-parse-error-handling.test.ts +463 -0
- package/test/retry.test.ts +1016 -0
- package/test/schema.test.js +96 -0
- package/test/streaming.test.ts +316 -0
- package/test/tagged-templates.test.js +240 -0
- package/test/tool-orchestration.test.ts +770 -0
- 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
|
+
})
|