@tanstack/ai 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -25
- package/dist/esm/activities/chat/index.d.ts +11 -10
- package/dist/esm/activities/chat/index.js +9 -9
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +116 -0
- package/dist/esm/activities/chat/tools/schema-converter.js +115 -0
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -0
- package/dist/esm/activities/chat/tools/tool-calls.js +23 -34
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +18 -14
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +5 -4
- package/dist/esm/stream-to-response.d.ts +28 -21
- package/dist/esm/stream-to-response.js +24 -17
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/types.d.ts +44 -25
- package/package.json +3 -6
- package/src/activities/chat/index.ts +36 -28
- package/src/activities/chat/tools/schema-converter.ts +332 -0
- package/src/activities/chat/tools/tool-calls.ts +47 -53
- package/src/activities/chat/tools/tool-definition.ts +33 -29
- package/src/index.ts +5 -2
- package/src/stream-to-response.ts +59 -44
- package/src/types.ts +47 -26
- package/dist/esm/activities/chat/tools/zod-converter.d.ts +0 -69
- package/dist/esm/activities/chat/tools/zod-converter.js +0 -99
- package/dist/esm/activities/chat/tools/zod-converter.js.map +0 -1
- package/src/activities/chat/tools/zod-converter.ts +0 -235
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
StandardJSONSchemaV1,
|
|
5
|
+
StandardSchemaV1,
|
|
6
|
+
} from '@standard-schema/spec'
|
|
7
|
+
import type { JSONSchema, SchemaInput } from '../../../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if a value is a Standard JSON Schema compliant schema.
|
|
11
|
+
* Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)
|
|
12
|
+
* implement the '~standard' property with jsonSchema converter methods.
|
|
13
|
+
*/
|
|
14
|
+
export function isStandardJSONSchema(
|
|
15
|
+
schema: unknown,
|
|
16
|
+
): schema is StandardJSONSchemaV1 {
|
|
17
|
+
return (
|
|
18
|
+
typeof schema === 'object' &&
|
|
19
|
+
schema !== null &&
|
|
20
|
+
'~standard' in schema &&
|
|
21
|
+
typeof (schema as StandardJSONSchemaV1)['~standard'] === 'object' &&
|
|
22
|
+
(schema as StandardJSONSchemaV1)['~standard'].version === 1 &&
|
|
23
|
+
typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema ===
|
|
24
|
+
'object' &&
|
|
25
|
+
typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema.input ===
|
|
26
|
+
'function'
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if a value is a Standard Schema compliant schema (for validation).
|
|
32
|
+
* Standard Schema compliant libraries implement the '~standard' property with a validate function.
|
|
33
|
+
*/
|
|
34
|
+
export function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {
|
|
35
|
+
return (
|
|
36
|
+
typeof schema === 'object' &&
|
|
37
|
+
schema !== null &&
|
|
38
|
+
'~standard' in schema &&
|
|
39
|
+
typeof schema['~standard'] === 'object' &&
|
|
40
|
+
schema !== null &&
|
|
41
|
+
schema['~standard'] !== null &&
|
|
42
|
+
'version' in schema['~standard'] &&
|
|
43
|
+
schema['~standard'].version === 1 &&
|
|
44
|
+
'validate' in schema['~standard'] &&
|
|
45
|
+
typeof schema['~standard'].validate === 'function'
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Transform a JSON schema to be compatible with OpenAI's structured output requirements.
|
|
51
|
+
* OpenAI requires:
|
|
52
|
+
* - All properties must be in the `required` array
|
|
53
|
+
* - Optional fields should have null added to their type union
|
|
54
|
+
* - additionalProperties must be false for objects
|
|
55
|
+
*
|
|
56
|
+
* @param schema - JSON schema to transform
|
|
57
|
+
* @param originalRequired - Original required array (to know which fields were optional)
|
|
58
|
+
* @returns Transformed schema compatible with OpenAI structured output
|
|
59
|
+
*/
|
|
60
|
+
function makeStructuredOutputCompatible(
|
|
61
|
+
schema: Record<string, any>,
|
|
62
|
+
originalRequired: Array<string> = [],
|
|
63
|
+
): Record<string, any> {
|
|
64
|
+
const result = { ...schema }
|
|
65
|
+
|
|
66
|
+
// Handle object types
|
|
67
|
+
if (result.type === 'object' && result.properties) {
|
|
68
|
+
const properties = { ...result.properties }
|
|
69
|
+
const allPropertyNames = Object.keys(properties)
|
|
70
|
+
|
|
71
|
+
// Transform each property
|
|
72
|
+
for (const propName of allPropertyNames) {
|
|
73
|
+
const prop = properties[propName]
|
|
74
|
+
const wasOptional = !originalRequired.includes(propName)
|
|
75
|
+
|
|
76
|
+
// Recursively transform nested objects/arrays
|
|
77
|
+
if (prop.type === 'object' && prop.properties) {
|
|
78
|
+
properties[propName] = makeStructuredOutputCompatible(
|
|
79
|
+
prop,
|
|
80
|
+
prop.required || [],
|
|
81
|
+
)
|
|
82
|
+
} else if (prop.type === 'array' && prop.items) {
|
|
83
|
+
properties[propName] = {
|
|
84
|
+
...prop,
|
|
85
|
+
items: makeStructuredOutputCompatible(
|
|
86
|
+
prop.items,
|
|
87
|
+
prop.items.required || [],
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
} else if (wasOptional) {
|
|
91
|
+
// Make optional fields nullable by adding null to the type
|
|
92
|
+
if (prop.type && !Array.isArray(prop.type)) {
|
|
93
|
+
properties[propName] = {
|
|
94
|
+
...prop,
|
|
95
|
+
type: [prop.type, 'null'],
|
|
96
|
+
}
|
|
97
|
+
} else if (Array.isArray(prop.type) && !prop.type.includes('null')) {
|
|
98
|
+
properties[propName] = {
|
|
99
|
+
...prop,
|
|
100
|
+
type: [...prop.type, 'null'],
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
result.properties = properties
|
|
107
|
+
// ALL properties must be required for OpenAI structured output
|
|
108
|
+
result.required = allPropertyNames
|
|
109
|
+
// additionalProperties must be false
|
|
110
|
+
result.additionalProperties = false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Handle array types with object items
|
|
114
|
+
if (result.type === 'array' && result.items) {
|
|
115
|
+
result.items = makeStructuredOutputCompatible(
|
|
116
|
+
result.items,
|
|
117
|
+
result.items.required || [],
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Options for schema conversion
|
|
126
|
+
*/
|
|
127
|
+
export interface ConvertSchemaOptions {
|
|
128
|
+
/**
|
|
129
|
+
* When true, transforms the schema to be compatible with OpenAI's structured output requirements:
|
|
130
|
+
* - All properties are added to the `required` array
|
|
131
|
+
* - Optional fields get null added to their type union
|
|
132
|
+
* - additionalProperties is set to false for all objects
|
|
133
|
+
*
|
|
134
|
+
* @default false
|
|
135
|
+
*/
|
|
136
|
+
forStructuredOutput?: boolean
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format
|
|
141
|
+
* compatible with LLM providers.
|
|
142
|
+
*
|
|
143
|
+
* Supports any schema library that implements the Standard JSON Schema spec (v1):
|
|
144
|
+
* - Zod v4+ (natively supports StandardJSONSchemaV1)
|
|
145
|
+
* - ArkType (natively supports StandardJSONSchemaV1)
|
|
146
|
+
* - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)
|
|
147
|
+
*
|
|
148
|
+
* If the input is already a plain JSONSchema object, it is returned as-is.
|
|
149
|
+
*
|
|
150
|
+
* @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert
|
|
151
|
+
* @param options - Conversion options
|
|
152
|
+
* @returns JSON Schema object that can be sent to LLM providers
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* // Using Zod v4+ (natively supports Standard JSON Schema)
|
|
157
|
+
* import * as z from 'zod';
|
|
158
|
+
*
|
|
159
|
+
* const zodSchema = z.object({
|
|
160
|
+
* location: z.string().describe('City name'),
|
|
161
|
+
* unit: z.enum(['celsius', 'fahrenheit']).optional()
|
|
162
|
+
* });
|
|
163
|
+
*
|
|
164
|
+
* const jsonSchema = convertSchemaToJsonSchema(zodSchema);
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* // Using ArkType (natively supports Standard JSON Schema)
|
|
168
|
+
* import { type } from 'arktype';
|
|
169
|
+
*
|
|
170
|
+
* const arkSchema = type({
|
|
171
|
+
* location: 'string',
|
|
172
|
+
* unit: "'celsius' | 'fahrenheit'"
|
|
173
|
+
* });
|
|
174
|
+
*
|
|
175
|
+
* const jsonSchema = convertSchemaToJsonSchema(arkSchema);
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* // Using Valibot (via toStandardJsonSchema)
|
|
179
|
+
* import * as v from 'valibot';
|
|
180
|
+
* import { toStandardJsonSchema } from '@valibot/to-json-schema';
|
|
181
|
+
*
|
|
182
|
+
* const valibotSchema = toStandardJsonSchema(v.object({
|
|
183
|
+
* location: v.string(),
|
|
184
|
+
* unit: v.optional(v.picklist(['celsius', 'fahrenheit']))
|
|
185
|
+
* }));
|
|
186
|
+
*
|
|
187
|
+
* const jsonSchema = convertSchemaToJsonSchema(valibotSchema);
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* // Using JSONSchema directly (passes through unchanged)
|
|
191
|
+
* const rawSchema = {
|
|
192
|
+
* type: 'object',
|
|
193
|
+
* properties: { location: { type: 'string' } },
|
|
194
|
+
* required: ['location']
|
|
195
|
+
* };
|
|
196
|
+
* const result = convertSchemaToJsonSchema(rawSchema);
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function convertSchemaToJsonSchema(
|
|
200
|
+
schema: SchemaInput | undefined,
|
|
201
|
+
options: ConvertSchemaOptions = {},
|
|
202
|
+
): JSONSchema | undefined {
|
|
203
|
+
if (!schema) return undefined
|
|
204
|
+
|
|
205
|
+
const { forStructuredOutput = false } = options
|
|
206
|
+
|
|
207
|
+
// If it's a Standard JSON Schema compliant schema, use the standard interface
|
|
208
|
+
if (isStandardJSONSchema(schema)) {
|
|
209
|
+
const jsonSchema = schema['~standard'].jsonSchema.input({
|
|
210
|
+
target: 'draft-07',
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
let result = jsonSchema
|
|
214
|
+
|
|
215
|
+
if (typeof result === 'object' && '$schema' in result) {
|
|
216
|
+
// Remove $schema property as it's not needed for LLM providers
|
|
217
|
+
const { $schema, ...rest } = result
|
|
218
|
+
result = rest
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Ensure object schemas always have type: "object"
|
|
222
|
+
|
|
223
|
+
if (typeof result === 'object') {
|
|
224
|
+
// If it has properties (even empty), it should be an object type
|
|
225
|
+
if ('properties' in result && !result.type) {
|
|
226
|
+
result.type = 'object'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Ensure properties exists for object types (even if empty)
|
|
230
|
+
if (result.type === 'object' && !('properties' in result)) {
|
|
231
|
+
result.properties = {}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Ensure required exists for object types (even if empty array)
|
|
235
|
+
if (result.type === 'object' && !('required' in result)) {
|
|
236
|
+
result.required = []
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Apply structured output transformation if requested
|
|
240
|
+
if (forStructuredOutput) {
|
|
241
|
+
result = makeStructuredOutputCompatible(
|
|
242
|
+
result,
|
|
243
|
+
(result.required as Array<string>) || [],
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result as JSONSchema
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// If it's not a Standard JSON Schema, assume it's already a JSONSchema and pass through
|
|
252
|
+
// Still apply structured output transformation if requested
|
|
253
|
+
|
|
254
|
+
if (forStructuredOutput && typeof schema === 'object') {
|
|
255
|
+
return makeStructuredOutputCompatible(
|
|
256
|
+
schema as Record<string, any>,
|
|
257
|
+
((schema as JSONSchema).required as Array<string>) || [],
|
|
258
|
+
) as JSONSchema
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return schema as JSONSchema
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Validates data against a Standard Schema compliant schema.
|
|
266
|
+
*
|
|
267
|
+
* @param schema - Standard Schema compliant schema
|
|
268
|
+
* @param data - Data to validate
|
|
269
|
+
* @returns Validation result with success status, data or issues
|
|
270
|
+
*/
|
|
271
|
+
export async function validateWithStandardSchema<T>(
|
|
272
|
+
schema: unknown,
|
|
273
|
+
data: unknown,
|
|
274
|
+
): Promise<
|
|
275
|
+
| { success: true; data: T }
|
|
276
|
+
| { success: false; issues: Array<{ message: string; path?: Array<string> }> }
|
|
277
|
+
> {
|
|
278
|
+
if (!isStandardSchema(schema)) {
|
|
279
|
+
// If it's not a Standard Schema, just return the data as-is
|
|
280
|
+
return { success: true, data: data as T }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = await schema['~standard'].validate(data)
|
|
284
|
+
|
|
285
|
+
if (!result.issues) {
|
|
286
|
+
return { success: true, data: result.value as T }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
issues: result.issues.map((issue) => ({
|
|
292
|
+
message: issue.message || 'Validation failed',
|
|
293
|
+
path: issue.path?.map(String),
|
|
294
|
+
})),
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Synchronously validates data against a Standard Schema compliant schema.
|
|
300
|
+
* Note: Some Standard Schema implementations may only support async validation.
|
|
301
|
+
* In those cases, this function will throw.
|
|
302
|
+
*
|
|
303
|
+
* @param schema - Standard Schema compliant schema
|
|
304
|
+
* @param data - Data to validate
|
|
305
|
+
* @returns Parsed/validated data
|
|
306
|
+
* @throws Error if validation fails or if the schema only supports async validation
|
|
307
|
+
*/
|
|
308
|
+
export function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {
|
|
309
|
+
if (!isStandardSchema(schema)) {
|
|
310
|
+
// If it's not a Standard Schema, just return the data as-is
|
|
311
|
+
return data as T
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const result = schema['~standard'].validate(data)
|
|
315
|
+
|
|
316
|
+
// Handle async result (Promise)
|
|
317
|
+
if (result instanceof Promise) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
// Standard Schema validation returns { value } for success or { issues } for failure
|
|
323
|
+
if (!result.issues) {
|
|
324
|
+
return result.value as T
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// invalid validation, throw error with all issues
|
|
328
|
+
const errorMessages = result.issues
|
|
329
|
+
.map((issue) => issue.message || 'Validation failed')
|
|
330
|
+
.join(', ')
|
|
331
|
+
throw new Error(`Validation failed: ${errorMessages}`)
|
|
332
|
+
}
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
+
import { isStandardSchema, parseWithStandardSchema } from './schema-converter'
|
|
1
2
|
import type {
|
|
2
3
|
DoneStreamChunk,
|
|
3
4
|
ModelMessage,
|
|
4
|
-
SchemaInput,
|
|
5
5
|
Tool,
|
|
6
6
|
ToolCall,
|
|
7
7
|
ToolResultStreamChunk,
|
|
8
8
|
} from '../../../types'
|
|
9
|
-
import type { z } from 'zod'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Check if a value is a Zod schema by looking for Zod-specific internals.
|
|
13
|
-
*/
|
|
14
|
-
function isZodSchema(schema: SchemaInput | undefined): schema is z.ZodType {
|
|
15
|
-
if (!schema) return false
|
|
16
|
-
return '_zod' in schema && typeof (schema as any)._zod === 'object'
|
|
17
|
-
}
|
|
18
9
|
|
|
19
10
|
/**
|
|
20
11
|
* Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.
|
|
@@ -131,7 +122,7 @@ export class ToolCallManager {
|
|
|
131
122
|
if (tool?.execute) {
|
|
132
123
|
try {
|
|
133
124
|
// Parse arguments
|
|
134
|
-
let args:
|
|
125
|
+
let args: unknown
|
|
135
126
|
try {
|
|
136
127
|
args = JSON.parse(toolCall.function.arguments)
|
|
137
128
|
} catch (parseError) {
|
|
@@ -140,13 +131,17 @@ export class ToolCallManager {
|
|
|
140
131
|
)
|
|
141
132
|
}
|
|
142
133
|
|
|
143
|
-
// Validate input against inputSchema (
|
|
144
|
-
if (tool.inputSchema &&
|
|
134
|
+
// Validate input against inputSchema (for Standard Schema compliant schemas)
|
|
135
|
+
if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {
|
|
145
136
|
try {
|
|
146
|
-
args = tool.inputSchema
|
|
147
|
-
} catch (validationError:
|
|
137
|
+
args = parseWithStandardSchema(tool.inputSchema, args)
|
|
138
|
+
} catch (validationError: unknown) {
|
|
139
|
+
const message =
|
|
140
|
+
validationError instanceof Error
|
|
141
|
+
? validationError.message
|
|
142
|
+
: 'Validation failed'
|
|
148
143
|
throw new Error(
|
|
149
|
-
`Input validation failed for tool ${tool.name}: ${
|
|
144
|
+
`Input validation failed for tool ${tool.name}: ${message}`,
|
|
150
145
|
)
|
|
151
146
|
}
|
|
152
147
|
}
|
|
@@ -154,27 +149,33 @@ export class ToolCallManager {
|
|
|
154
149
|
// Execute the tool
|
|
155
150
|
let result = await tool.execute(args)
|
|
156
151
|
|
|
157
|
-
// Validate output against outputSchema if provided (
|
|
152
|
+
// Validate output against outputSchema if provided (for Standard Schema compliant schemas)
|
|
158
153
|
if (
|
|
159
154
|
tool.outputSchema &&
|
|
160
|
-
|
|
155
|
+
isStandardSchema(tool.outputSchema) &&
|
|
161
156
|
result !== undefined &&
|
|
162
157
|
result !== null
|
|
163
158
|
) {
|
|
164
159
|
try {
|
|
165
|
-
result = tool.outputSchema
|
|
166
|
-
} catch (validationError:
|
|
160
|
+
result = parseWithStandardSchema(tool.outputSchema, result)
|
|
161
|
+
} catch (validationError: unknown) {
|
|
162
|
+
const message =
|
|
163
|
+
validationError instanceof Error
|
|
164
|
+
? validationError.message
|
|
165
|
+
: 'Validation failed'
|
|
167
166
|
throw new Error(
|
|
168
|
-
`Output validation failed for tool ${tool.name}: ${
|
|
167
|
+
`Output validation failed for tool ${tool.name}: ${message}`,
|
|
169
168
|
)
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
toolResultContent =
|
|
174
173
|
typeof result === 'string' ? result : JSON.stringify(result)
|
|
175
|
-
} catch (error:
|
|
174
|
+
} catch (error: unknown) {
|
|
176
175
|
// If tool execution fails, add error message
|
|
177
|
-
|
|
176
|
+
const message =
|
|
177
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
178
|
+
toolResultContent = `Error executing tool: ${message}`
|
|
178
179
|
}
|
|
179
180
|
} else {
|
|
180
181
|
// Tool doesn't have execute function, add placeholder
|
|
@@ -286,7 +287,7 @@ export async function executeToolCalls(
|
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
// Parse arguments, throwing error if invalid JSON
|
|
289
|
-
let input:
|
|
290
|
+
let input: unknown = {}
|
|
290
291
|
const argsStr = toolCall.function.arguments.trim() || '{}'
|
|
291
292
|
if (argsStr) {
|
|
292
293
|
try {
|
|
@@ -297,16 +298,20 @@ export async function executeToolCalls(
|
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
|
|
300
|
-
// Validate input against inputSchema (
|
|
301
|
-
if (tool.inputSchema &&
|
|
301
|
+
// Validate input against inputSchema (for Standard Schema compliant schemas)
|
|
302
|
+
if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {
|
|
302
303
|
try {
|
|
303
|
-
input = tool.inputSchema
|
|
304
|
-
} catch (validationError:
|
|
304
|
+
input = parseWithStandardSchema(tool.inputSchema, input)
|
|
305
|
+
} catch (validationError: unknown) {
|
|
306
|
+
const message =
|
|
307
|
+
validationError instanceof Error
|
|
308
|
+
? validationError.message
|
|
309
|
+
: 'Validation failed'
|
|
305
310
|
results.push({
|
|
306
311
|
toolCallId: toolCall.id,
|
|
307
312
|
toolName,
|
|
308
313
|
result: {
|
|
309
|
-
error: `Input validation failed for tool ${tool.name}: ${
|
|
314
|
+
error: `Input validation failed for tool ${tool.name}: ${message}`,
|
|
310
315
|
},
|
|
311
316
|
state: 'output-error',
|
|
312
317
|
})
|
|
@@ -393,21 +398,14 @@ export async function executeToolCalls(
|
|
|
393
398
|
let result = await tool.execute(input)
|
|
394
399
|
const duration = Date.now() - startTime
|
|
395
400
|
|
|
396
|
-
// Validate output against outputSchema if provided (
|
|
401
|
+
// Validate output against outputSchema if provided (for Standard Schema compliant schemas)
|
|
397
402
|
if (
|
|
398
403
|
tool.outputSchema &&
|
|
399
|
-
|
|
404
|
+
isStandardSchema(tool.outputSchema) &&
|
|
400
405
|
result !== undefined &&
|
|
401
406
|
result !== null
|
|
402
407
|
) {
|
|
403
|
-
|
|
404
|
-
if (parsed.success) {
|
|
405
|
-
result = parsed.data
|
|
406
|
-
} else {
|
|
407
|
-
throw new Error(
|
|
408
|
-
`Output validation failed for tool ${tool.name}: ${parsed.error.message}`,
|
|
409
|
-
)
|
|
410
|
-
}
|
|
408
|
+
result = parseWithStandardSchema(tool.outputSchema, result)
|
|
411
409
|
}
|
|
412
410
|
|
|
413
411
|
results.push({
|
|
@@ -419,12 +417,14 @@ export async function executeToolCalls(
|
|
|
419
417
|
: result || null,
|
|
420
418
|
duration,
|
|
421
419
|
})
|
|
422
|
-
} catch (error:
|
|
420
|
+
} catch (error: unknown) {
|
|
423
421
|
const duration = Date.now() - startTime
|
|
422
|
+
const message =
|
|
423
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
424
424
|
results.push({
|
|
425
425
|
toolCallId: toolCall.id,
|
|
426
426
|
toolName,
|
|
427
|
-
result: { error:
|
|
427
|
+
result: { error: message },
|
|
428
428
|
state: 'output-error',
|
|
429
429
|
duration,
|
|
430
430
|
})
|
|
@@ -456,21 +456,14 @@ export async function executeToolCalls(
|
|
|
456
456
|
let result = await tool.execute(input)
|
|
457
457
|
const duration = Date.now() - startTime
|
|
458
458
|
|
|
459
|
-
// Validate output against outputSchema if provided (
|
|
459
|
+
// Validate output against outputSchema if provided (for Standard Schema compliant schemas)
|
|
460
460
|
if (
|
|
461
461
|
tool.outputSchema &&
|
|
462
|
-
|
|
462
|
+
isStandardSchema(tool.outputSchema) &&
|
|
463
463
|
result !== undefined &&
|
|
464
464
|
result !== null
|
|
465
465
|
) {
|
|
466
|
-
|
|
467
|
-
if (parsed.success) {
|
|
468
|
-
result = parsed.data
|
|
469
|
-
} else {
|
|
470
|
-
throw new Error(
|
|
471
|
-
`Output validation failed for tool ${tool.name}: ${parsed.error.message}`,
|
|
472
|
-
)
|
|
473
|
-
}
|
|
466
|
+
result = parseWithStandardSchema(tool.outputSchema, result)
|
|
474
467
|
}
|
|
475
468
|
|
|
476
469
|
results.push({
|
|
@@ -480,12 +473,13 @@ export async function executeToolCalls(
|
|
|
480
473
|
typeof result === 'string' ? JSON.parse(result) : result || null,
|
|
481
474
|
duration,
|
|
482
475
|
})
|
|
483
|
-
} catch (error:
|
|
476
|
+
} catch (error: unknown) {
|
|
484
477
|
const duration = Date.now() - startTime
|
|
478
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
485
479
|
results.push({
|
|
486
480
|
toolCallId: toolCall.id,
|
|
487
481
|
toolName,
|
|
488
|
-
result: { error:
|
|
482
|
+
result: { error: message },
|
|
489
483
|
state: 'output-error',
|
|
490
484
|
duration,
|
|
491
485
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardJSONSchemaV1 } from '@standard-schema/spec'
|
|
2
2
|
import type {
|
|
3
3
|
InferSchemaType,
|
|
4
4
|
JSONSchema,
|
|
@@ -10,8 +10,8 @@ import type {
|
|
|
10
10
|
* Marker type for server-side tools
|
|
11
11
|
*/
|
|
12
12
|
export interface ServerTool<
|
|
13
|
-
TInput extends SchemaInput =
|
|
14
|
-
TOutput extends SchemaInput =
|
|
13
|
+
TInput extends SchemaInput = SchemaInput,
|
|
14
|
+
TOutput extends SchemaInput = SchemaInput,
|
|
15
15
|
TName extends string = string,
|
|
16
16
|
> extends Tool<TInput, TOutput, TName> {
|
|
17
17
|
__toolSide: 'server'
|
|
@@ -21,8 +21,8 @@ export interface ServerTool<
|
|
|
21
21
|
* Marker type for client-side tools
|
|
22
22
|
*/
|
|
23
23
|
export interface ClientTool<
|
|
24
|
-
TInput extends SchemaInput =
|
|
25
|
-
TOutput extends SchemaInput =
|
|
24
|
+
TInput extends SchemaInput = SchemaInput,
|
|
25
|
+
TOutput extends SchemaInput = SchemaInput,
|
|
26
26
|
TName extends string = string,
|
|
27
27
|
> {
|
|
28
28
|
__toolSide: 'client'
|
|
@@ -31,7 +31,7 @@ export interface ClientTool<
|
|
|
31
31
|
inputSchema?: TInput
|
|
32
32
|
outputSchema?: TOutput
|
|
33
33
|
needsApproval?: boolean
|
|
34
|
-
metadata?: Record<string,
|
|
34
|
+
metadata?: Record<string, unknown>
|
|
35
35
|
execute?: (
|
|
36
36
|
args: InferSchemaType<TInput>,
|
|
37
37
|
) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>
|
|
@@ -41,8 +41,8 @@ export interface ClientTool<
|
|
|
41
41
|
* Tool definition that can be used directly or instantiated for server/client
|
|
42
42
|
*/
|
|
43
43
|
export interface ToolDefinitionInstance<
|
|
44
|
-
TInput extends SchemaInput =
|
|
45
|
-
TOutput extends SchemaInput =
|
|
44
|
+
TInput extends SchemaInput = SchemaInput,
|
|
45
|
+
TOutput extends SchemaInput = SchemaInput,
|
|
46
46
|
TName extends string = string,
|
|
47
47
|
> extends Tool<TInput, TOutput, TName> {
|
|
48
48
|
__toolSide: 'definition'
|
|
@@ -52,8 +52,8 @@ export interface ToolDefinitionInstance<
|
|
|
52
52
|
* Union type for any kind of client-side tool (client tool or definition)
|
|
53
53
|
*/
|
|
54
54
|
export type AnyClientTool =
|
|
55
|
-
| ClientTool<
|
|
56
|
-
| ToolDefinitionInstance<
|
|
55
|
+
| ClientTool<SchemaInput, SchemaInput>
|
|
56
|
+
| ToolDefinitionInstance<SchemaInput, SchemaInput>
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Extract the tool name as a literal type
|
|
@@ -61,33 +61,33 @@ export type AnyClientTool =
|
|
|
61
61
|
export type InferToolName<T> = T extends { name: infer N } ? N : never
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Extract the input type from a tool (inferred from
|
|
64
|
+
* Extract the input type from a tool (inferred from Standard JSON Schema, or `unknown` for plain JSONSchema)
|
|
65
65
|
*/
|
|
66
66
|
export type InferToolInput<T> = T extends { inputSchema?: infer TInput }
|
|
67
|
-
? TInput extends
|
|
68
|
-
?
|
|
67
|
+
? TInput extends StandardJSONSchemaV1<infer TInferred, unknown>
|
|
68
|
+
? TInferred
|
|
69
69
|
: TInput extends JSONSchema
|
|
70
|
-
?
|
|
71
|
-
:
|
|
72
|
-
:
|
|
70
|
+
? unknown
|
|
71
|
+
: unknown
|
|
72
|
+
: unknown
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Extract the output type from a tool (inferred from
|
|
75
|
+
* Extract the output type from a tool (inferred from Standard JSON Schema, or `unknown` for plain JSONSchema)
|
|
76
76
|
*/
|
|
77
77
|
export type InferToolOutput<T> = T extends { outputSchema?: infer TOutput }
|
|
78
|
-
? TOutput extends
|
|
79
|
-
?
|
|
78
|
+
? TOutput extends StandardJSONSchemaV1<infer TInferred, unknown>
|
|
79
|
+
? TInferred
|
|
80
80
|
: TOutput extends JSONSchema
|
|
81
|
-
?
|
|
82
|
-
:
|
|
83
|
-
:
|
|
81
|
+
? unknown
|
|
82
|
+
: unknown
|
|
83
|
+
: unknown
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Tool definition configuration
|
|
87
87
|
*/
|
|
88
88
|
export interface ToolDefinitionConfig<
|
|
89
|
-
TInput extends SchemaInput =
|
|
90
|
-
TOutput extends SchemaInput =
|
|
89
|
+
TInput extends SchemaInput = SchemaInput,
|
|
90
|
+
TOutput extends SchemaInput = SchemaInput,
|
|
91
91
|
TName extends string = string,
|
|
92
92
|
> {
|
|
93
93
|
name: TName
|
|
@@ -95,15 +95,15 @@ export interface ToolDefinitionConfig<
|
|
|
95
95
|
inputSchema?: TInput
|
|
96
96
|
outputSchema?: TOutput
|
|
97
97
|
needsApproval?: boolean
|
|
98
|
-
metadata?: Record<string,
|
|
98
|
+
metadata?: Record<string, unknown>
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Tool definition builder that allows creating server or client tools from a shared definition
|
|
103
103
|
*/
|
|
104
104
|
export interface ToolDefinition<
|
|
105
|
-
TInput extends SchemaInput =
|
|
106
|
-
TOutput extends SchemaInput =
|
|
105
|
+
TInput extends SchemaInput = SchemaInput,
|
|
106
|
+
TOutput extends SchemaInput = SchemaInput,
|
|
107
107
|
TName extends string = string,
|
|
108
108
|
> extends ToolDefinitionInstance<TInput, TOutput, TName> {
|
|
109
109
|
/**
|
|
@@ -133,11 +133,15 @@ export interface ToolDefinition<
|
|
|
133
133
|
* 2. Instantiated as a server tool with .server()
|
|
134
134
|
* 3. Instantiated as a client tool with .client()
|
|
135
135
|
*
|
|
136
|
+
* Supports any Standard JSON Schema compliant library (Zod v4+, ArkType, Valibot, etc.)
|
|
137
|
+
* or plain JSON Schema objects.
|
|
138
|
+
*
|
|
136
139
|
* @example
|
|
137
140
|
* ```typescript
|
|
138
141
|
* import { toolDefinition } from '@tanstack/ai';
|
|
139
142
|
* import { z } from 'zod';
|
|
140
143
|
*
|
|
144
|
+
* // Using Zod (natively supports Standard JSON Schema)
|
|
141
145
|
* const addToCartTool = toolDefinition({
|
|
142
146
|
* name: 'addToCart',
|
|
143
147
|
* description: 'Add a guitar to the shopping cart (requires approval)',
|
|
@@ -177,8 +181,8 @@ export interface ToolDefinition<
|
|
|
177
181
|
* ```
|
|
178
182
|
*/
|
|
179
183
|
export function toolDefinition<
|
|
180
|
-
TInput extends SchemaInput =
|
|
181
|
-
TOutput extends SchemaInput =
|
|
184
|
+
TInput extends SchemaInput = SchemaInput,
|
|
185
|
+
TOutput extends SchemaInput = SchemaInput,
|
|
182
186
|
TName extends string = string,
|
|
183
187
|
>(
|
|
184
188
|
config: ToolDefinitionConfig<TInput, TOutput, TName>,
|