diff --git a/.changeset/fair-memes-open.md b/.changeset/fair-memes-open.md new file mode 100644 index 000000000..3d1403e68 --- /dev/null +++ b/.changeset/fair-memes-open.md @@ -0,0 +1,5 @@ +--- +"@voltagent/core": minor +--- + +feat(core): support valibot and other schema libraries via xsschema, not just zod diff --git a/packages/anthropic-ai/package.json b/packages/anthropic-ai/package.json index c0c832fdc..a88e05e47 100644 --- a/packages/anthropic-ai/package.json +++ b/packages/anthropic-ai/package.json @@ -25,6 +25,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.40.0", "@voltagent/core": "^0.1.22", + "xsschema": "0.3.0-beta.2", "zod": "3.24.2" }, "devDependencies": { diff --git a/packages/anthropic-ai/src/index.ts b/packages/anthropic-ai/src/index.ts index 7c83cbe8c..f3e0ca63e 100644 --- a/packages/anthropic-ai/src/index.ts +++ b/packages/anthropic-ai/src/index.ts @@ -13,7 +13,7 @@ import type { StreamTextOptions, VoltAgentError, } from "@voltagent/core"; -import type { z } from "zod"; +import * as xsschema from "xsschema"; import type { AnthropicMessage, AnthropicProviderOptions, @@ -29,7 +29,6 @@ import { handleStepFinish, processContent, processResponseContent, - zodToJsonSchema, } from "./utils"; export class AnthropicProvider implements LLMProvider { @@ -80,10 +79,20 @@ export class AnthropicProvider implements LLMProvider { } toTool(tool: BaseTool): AnthropicTool { + const jsonSchema = xsschema.toJsonSchemaSync(tool.parameters); + if (jsonSchema.type !== "object") { + throw new Error("Tool parameters must be an object"); + } + return { name: tool.name, description: tool.description, - input_schema: zodToJsonSchema(tool.parameters), + input_schema: { + ...jsonSchema, + // Already checked that the type is object above + type: "object", + properties: jsonSchema.properties ?? {}, + }, }; } @@ -243,11 +252,11 @@ export class AnthropicProvider implements LLMProvider { } } - async generateObject( + async generateObject( options: GenerateObjectOptions, - ): Promise>> { + ): Promise>> { const { temperature = 0.2, maxTokens = 1024, topP, stopSequences } = options.provider || {}; - const JsonSchema = zodToJsonSchema(options.schema); + const JsonSchema = await xsschema.toJsonSchema(options.schema); const systemPrompt = `${getSystemMessage(options.messages)}. Response Schema: ${JSON.stringify(JsonSchema)}. You must return the response in valid JSON Format with proper schema, nothing else `; const anthropicMessages = this.getAnthropicMessages(options.messages); @@ -283,7 +292,7 @@ export class AnthropicProvider implements LLMProvider { throw new Error(`The JSON returned by Anthropic API is not valid \n ${err}`); } - const parsedResult = options.schema.safeParse(parsedObject); + const parsedResult = await xsschema.validate(parsedObject, options.schema); if (!parsedResult.success) { throw new Error( `the response doesn't match the specified schema: ${parsedResult.error.message}`, @@ -320,12 +329,12 @@ export class AnthropicProvider implements LLMProvider { } } - async streamObject( + async streamObject( options: StreamObjectOptions, - ): Promise>> { + ): Promise>> { try { const anthropicMessages = this.getAnthropicMessages(options.messages); - const JsonSchema = zodToJsonSchema(options.schema); + const JsonSchema = await xsschema.toJsonSchema(options.schema); const systemPrompt = `${getSystemMessage(options.messages)}. Response Schema: ${JSON.stringify(JsonSchema)}. You must return the response in valid JSON Format with proper schema, nothing else `; const { temperature = 0.2, maxTokens = 1024, topP, stopSequences } = options.provider || {}; @@ -353,7 +362,7 @@ export class AnthropicProvider implements LLMProvider { // Try to parse partial JSON as it comes in try { const partialObject = JSON.parse(accumulatedText); - const parseResult = options.schema.safeParse(partialObject); + const parseResult = await xsschema.validate(partialObject, options.schema); if (parseResult.success) { controller.enqueue(parseResult.data); @@ -366,7 +375,7 @@ export class AnthropicProvider implements LLMProvider { if (chunk.type === "message_stop") { try { const parsedObject = JSON.parse(accumulatedText); - const parsedResult = options.schema.safeParse(parsedObject); + const parsedResult = await xsschema.validate(parsedObject, options.schema); if (parsedResult.success) { controller.enqueue(parsedResult.data); diff --git a/packages/anthropic-ai/src/utils/index.ts b/packages/anthropic-ai/src/utils/index.ts index f05dd4eee..10f5bbee7 100644 --- a/packages/anthropic-ai/src/utils/index.ts +++ b/packages/anthropic-ai/src/utils/index.ts @@ -10,7 +10,6 @@ import type { StepWithContent, VoltAgentError, } from "@voltagent/core"; -import { z } from "zod"; /** * Processes text content into a text content block @@ -171,111 +170,6 @@ export function processContentPart(part: any): ContentBlockParam | null { return null; } -/** - * Converts a Zod schema to JSON Schema format that Anthropic expects - * @param {z.ZodType} schema - The Zod schema to convert - * @returns {Object} A JSON Schema object with type, properties, and required fields - * @throws {Error} If the schema is not a Zod object - */ -export function zodToJsonSchema(schema: z.ZodType): { - type: "object"; - properties: Record; - required?: string[]; -} { - // Check if it's a ZodObject by checking for the typeName property - if ( - schema && - typeof schema === "object" && - "_def" in schema && - schema._def && - typeof schema._def === "object" && - "typeName" in schema._def && - schema._def.typeName === "ZodObject" - ) { - // Use a safer type assertion approach - const def = schema._def as unknown as { shape: () => Record }; - const shape = def.shape(); - const properties: Record = {}; - const required: string[] = []; - - for (const [key, value] of Object.entries(shape)) { - const fieldSchema = convertZodField(value as z.ZodTypeAny); - properties[key] = fieldSchema; - - // Check if the field is required - if (!(value instanceof z.ZodOptional)) { - required.push(key); - } - } - - return { - type: "object" as const, - properties, - ...(required.length > 0 ? { required } : {}), - }; - } - - throw new Error("Root schema must be a Zod object"); -} - -/** - * Helper function to create a base schema with type and optional description - * @param {z.ZodType} field - The Zod field to extract description from - * @param {string} type - The type string to use - * @returns {Object} Schema object with type and optional description - */ -function getBaseSchema(field: z.ZodType, type: string) { - return { - type, - ...(field.description ? { description: field.description } : {}), - }; -} - -/** - * Helper function to handle primitive type fields - * @param {z.ZodTypeAny} field - The Zod field to process - * @param {string} type - The type string to use - * @returns {Object} Schema object with type and optional description - */ -function handlePrimitiveType(field: z.ZodTypeAny, type: string) { - return getBaseSchema(field, type); -} - -/** - * Converts a Zod field to a JSON Schema field - * @param {z.ZodTypeAny} zodField - The Zod field to convert - * @returns {any} The JSON Schema representation of the field - */ -export function convertZodField(zodField: z.ZodTypeAny): any { - if (zodField instanceof z.ZodString) { - return handlePrimitiveType(zodField, "string"); - } - if (zodField instanceof z.ZodNumber) { - return handlePrimitiveType(zodField, "number"); - } - if (zodField instanceof z.ZodBoolean) { - return handlePrimitiveType(zodField, "boolean"); - } - if (zodField instanceof z.ZodArray) { - return { - type: "array", - items: convertZodField(zodField.element), - ...(zodField.description ? { description: zodField.description } : {}), - }; - } - if (zodField instanceof z.ZodEnum) { - return { - type: "string", - enum: zodField._def.values, - ...(zodField.description ? { description: zodField.description } : {}), - }; - } - if (zodField instanceof z.ZodOptional) { - return convertZodField(zodField.unwrap()); - } - return { type: "string" }; -} - /** * Creates a response object from Anthropic's response * @param {Message} response - The response from Anthropic diff --git a/packages/core/package.json b/packages/core/package.json index 4c0fb4f29..43c8d262a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,6 +38,7 @@ "uuid": "^9.0.1", "ws": "^8.18.1", "zod": "3.24.2", + "xsschema": "0.3.0-beta.2", "zod-from-json-schema": "^0.0.5" }, "devDependencies": { diff --git a/packages/core/src/agent/index.spec.ts b/packages/core/src/agent/index.spec.ts index 3005b2902..f11945587 100644 --- a/packages/core/src/agent/index.spec.ts +++ b/packages/core/src/agent/index.spec.ts @@ -1,5 +1,6 @@ // @ts-ignore - To prevent errors when loading Jest mocks import { z } from "zod"; +import type * as xsschema from "xsschema"; import { AgentEventEmitter } from "../events"; import type { MemoryMessage, Memory } from "../memory/types"; import { AgentRegistry } from "../server/registry"; @@ -245,11 +246,13 @@ class MockProvider implements LLMProvider { }; } - async generateObject(options: { + async generateObject(options: { messages: BaseMessage[]; model: MockModelType; schema: T; - }): Promise>, z.infer>> { + }): Promise< + ProviderObjectResponse>, xsschema.Infer> + > { this.generateObjectCalls++; this.lastMessages = options.messages; @@ -258,7 +261,7 @@ class MockProvider implements LLMProvider { name: "John Doe", age: 30, hobbies: ["reading", "gaming"], - } as z.infer, + } as xsschema.Infer, }; return { @@ -273,11 +276,13 @@ class MockProvider implements LLMProvider { }; } - async streamObject(options: { + async streamObject(options: { messages: BaseMessage[]; model: MockModelType; schema: T; - }): Promise>, z.infer>> { + }): Promise< + ProviderObjectStreamResponse>, xsschema.Infer> + > { this.streamObjectCalls++; this.lastMessages = options.messages; @@ -291,9 +296,9 @@ class MockProvider implements LLMProvider { }, }); - const partialObjectStream = new ReadableStream>>({ + const partialObjectStream = new ReadableStream>>({ start(controller) { - controller.enqueue({ name: "John" } as Partial>); + controller.enqueue({ name: "John" } as Partial>); controller.close(); }, }); diff --git a/packages/core/src/agent/index.ts b/packages/core/src/agent/index.ts index 3778f977f..d54b9502f 100644 --- a/packages/core/src/agent/index.ts +++ b/packages/core/src/agent/index.ts @@ -1,4 +1,4 @@ -import type { z } from "zod"; +import type * as xsschema from "xsschema"; import { AgentEventEmitter } from "../events"; import type { EventStatus } from "../events"; import type { StandardEventData } from "../events/types"; @@ -155,7 +155,7 @@ export class Agent }> { voice?: Voice; markdown?: boolean; telemetryExporter?: VoltAgentExporter; - }, + } ) { this.id = options.id || options.name; this.name = options.name; @@ -191,7 +191,7 @@ export class Agent }> { this.id, this.memoryManager, options.maxHistoryEntries || 0, - chosenExporter, + chosenExporter ); } @@ -362,7 +362,7 @@ export class Agent }> { // Generate the supervisor message with the agents memory inserted finalInstructions = this.subAgentManager.generateSupervisorSystemMessage( finalInstructions, - agentsMemory, + agentsMemory ); return { @@ -408,7 +408,7 @@ export class Agent }> { */ private async formatInputMessages( messages: BaseMessage[], - input: string | BaseMessage[], + input: string | BaseMessage[] ): Promise { if (typeof input === "string") { // Add user message to the messages array @@ -444,7 +444,7 @@ export class Agent }> { // Ensure operationContext exists before proceeding if (!operationContext) { devLogger.warn( - `[Agent ${this.id}] Missing operationContext in prepareTextOptions. Tool execution context might be incomplete.`, + `[Agent ${this.id}] Missing operationContext in prepareTextOptions. Tool execution context might be incomplete.` ); // Potentially handle this case more gracefully, e.g., throw an error or create a default context } @@ -479,7 +479,7 @@ export class Agent }> { if (!reasoningOptions.historyEntryId || reasoningOptions.historyEntryId === "unknown") { devLogger.warn( - `Executing reasoning tool '${tool.name}' without a known historyEntryId within the operation context.`, + `Executing reasoning tool '${tool.name}' without a known historyEntryId within the operation context.` ); } // Pass the correctly typed options @@ -541,7 +541,7 @@ export class Agent }> { conversationId?: string; } = { operationName: "unknown", - }, + } ): Promise { const otelSpan = startOperationSpan({ agentId: this.id, @@ -642,7 +642,7 @@ export class Agent }> { */ private async updateHistoryEntry( context: OperationContext, - updates: Partial, + updates: Partial ): Promise { await this.historyManager.updateEntry(context.historyEntry.id, updates); } @@ -654,7 +654,7 @@ export class Agent }> { context: OperationContext, toolName: string, status: EventStatus, - data: Partial & Record = {}, + data: Partial & Record = {} ): void => { // Ensure the toolSpans map exists on the context if (!context.toolSpans) { @@ -688,7 +688,7 @@ export class Agent }> { context: OperationContext, eventName: string, status: AgentStatus, - data: Partial & Record = {}, + data: Partial & Record = {} ): void => { // Retrieve the OpenTelemetry span from the context const otelSpan = context.otelSpan; @@ -701,7 +701,7 @@ export class Agent }> { }); } else { devLogger.warn( - `OpenTelemetry span not found in OperationContext for agent event ${eventName} (Operation ID: ${context.operationId})`, + `OpenTelemetry span not found in OperationContext for agent event ${eventName} (Operation ID: ${context.operationId})` ); } }; @@ -713,7 +713,7 @@ export class Agent }> { context: OperationContext, toolCallId: string, toolName: string, - resultData: { result?: any; content?: any; error?: any }, + resultData: { result?: any; content?: any; error?: any } ): void { const toolSpan = context.toolSpans?.get(toolCallId); @@ -722,7 +722,7 @@ export class Agent }> { context.toolSpans?.delete(toolCallId); // Remove from map after ending } else { devLogger.warn( - `OTEL tool span not found for toolCallId: ${toolCallId} in _endOtelToolSpan (Tool: ${toolName})`, + `OTEL tool span not found for toolCallId: ${toolCallId} in _endOtelToolSpan (Tool: ${toolName})` ); } } @@ -732,7 +732,7 @@ export class Agent }> { */ async generateText( input: string | BaseMessage[], - options: PublicGenerateOptions = {}, + options: PublicGenerateOptions = {} ): Promise> { const internalOptions: InternalGenerateOptions = options as InternalGenerateOptions; const { @@ -759,7 +759,7 @@ export class Agent }> { input, userId, initialConversationId, - contextLimit, + contextLimit ); if (operationContext.otelSpan) { @@ -827,7 +827,7 @@ export class Agent }> { const onStepFinish = this.memoryManager.createStepFinishHandler( operationContext, userId, - finalConversationId, + finalConversationId ); const { tools, maxSteps } = this.prepareTextOptions({ ...internalOptions, @@ -1153,7 +1153,7 @@ export class Agent }> { */ async streamText( input: string | BaseMessage[], - options: PublicGenerateOptions = {}, + options: PublicGenerateOptions = {} ): Promise> { const internalOptions: InternalGenerateOptions = options as InternalGenerateOptions; const { @@ -1180,7 +1180,7 @@ export class Agent }> { input, userId, initialConversationId, - contextLimit, + contextLimit ); if (operationContext.otelSpan) { @@ -1245,7 +1245,7 @@ export class Agent }> { const onStepFinish = this.memoryManager.createStepFinishHandler( operationContext, userId, - finalConversationId, + finalConversationId ); const { tools, maxSteps } = this.prepareTextOptions({ ...internalOptions, @@ -1407,7 +1407,7 @@ export class Agent }> { await onStepFinish(step); if (internalOptions.provider?.onStepFinish) { await (internalOptions.provider.onStepFinish as (step: StepWithContent) => Promise)( - step, + step ); } this.addStepToHistory(step, operationContext); @@ -1536,7 +1536,7 @@ export class Agent }> { } catch (updateError) { devLogger.error( `[Agent ${this.id}] Failed to update tool event to error status for ${toolName} (${toolCallId}):`, - updateError, + updateError ); } const tool = this.toolManager.getToolByName(toolName); @@ -1633,10 +1633,10 @@ export class Agent }> { /** * Generate a structured object response */ - async generateObject( + async generateObject( input: string | BaseMessage[], schema: T, - options: PublicGenerateOptions = {}, + options: PublicGenerateOptions = {} ): Promise> { const internalOptions: InternalGenerateOptions = options as InternalGenerateOptions; const { @@ -1663,7 +1663,7 @@ export class Agent }> { input, userId, initialConversationId, - contextLimit, + contextLimit ); if (operationContext.otelSpan) { @@ -1730,7 +1730,7 @@ export class Agent }> { const onStepFinish = this.memoryManager.createStepFinishHandler( operationContext, userId, - finalConversationId, + finalConversationId ); const response = await this.llm.generateObject({ @@ -1817,7 +1817,7 @@ export class Agent }> { status: "completed" as any, }); - const standardizedOutput: StandardizedObjectResult> = { + const standardizedOutput: StandardizedObjectResult> = { object: response.object, usage: response.usage, finishReason: response.finishReason, @@ -1912,10 +1912,10 @@ export class Agent }> { /** * Stream a structured object response */ - async streamObject( + async streamObject( input: string | BaseMessage[], schema: T, - options: PublicGenerateOptions = {}, + options: PublicGenerateOptions = {} ): Promise> { const internalOptions: InternalGenerateOptions = options as InternalGenerateOptions; const { @@ -1943,7 +1943,7 @@ export class Agent }> { input, userId, initialConversationId, - contextLimit, + contextLimit ); if (operationContext.otelSpan) { @@ -2008,7 +2008,7 @@ export class Agent }> { const onStepFinish = this.memoryManager.createStepFinishHandler( operationContext, userId, - finalConversationId, + finalConversationId ); try { @@ -2030,7 +2030,7 @@ export class Agent }> { await (provider.onStepFinish as (step: StepWithContent) => Promise)(step); } }, - onFinish: async (result: StreamObjectFinishResult>) => { + onFinish: async (result: StreamObjectFinishResult>) => { if (!operationContext.isActive) { return; } @@ -2108,7 +2108,7 @@ export class Agent }> { context: operationContext, }); if (provider?.onFinish) { - await (provider.onFinish as StreamObjectOnFinishCallback>)(result); + await (provider.onFinish as StreamObjectOnFinishCallback>)(result); } }, onError: async (error: VoltAgentError) => { diff --git a/packages/core/src/agent/providers/base/types.ts b/packages/core/src/agent/providers/base/types.ts index b291c6c0e..eccac602b 100644 --- a/packages/core/src/agent/providers/base/types.ts +++ b/packages/core/src/agent/providers/base/types.ts @@ -1,4 +1,4 @@ -import type { z } from "zod"; +import type * as xsschema from "xsschema"; import type { ProviderOptions, ToolExecutionContext, @@ -191,7 +191,7 @@ export type BaseMessage = { }; // Schema types -export type ToolSchema = z.ZodType; +export type ToolSchema = xsschema.JsonSchema; // Base tool types export type ToolExecuteOptions = { @@ -274,7 +274,7 @@ export interface StreamTextOptions { toolExecutionContext?: ToolExecutionContext; } -export interface GenerateObjectOptions { +export interface GenerateObjectOptions { messages: BaseMessage[]; model: TModel; schema: TSchema; @@ -284,13 +284,13 @@ export interface GenerateObjectOptions { toolExecutionContext?: ToolExecutionContext; } -export interface StreamObjectOptions { +export interface StreamObjectOptions { messages: BaseMessage[]; model: TModel; schema: TSchema; provider?: ProviderOptions; onStepFinish?: StepFinishCallback; - onFinish?: StreamObjectOnFinishCallback>; + onFinish?: StreamObjectOnFinishCallback>; onError?: StreamOnErrorCallback; signal?: AbortSignal; toolExecutionContext?: ToolExecutionContext; @@ -368,13 +368,15 @@ export type LLMProvider = { * Implementers should catch underlying SDK/API errors and throw a VoltAgentError. * @throws {VoltAgentError} If an error occurs during generation. */ - generateObject( + generateObject( options: GenerateObjectOptions, TSchema>, - ): Promise, z.infer>>; + ): Promise< + ProviderObjectResponse, xsschema.Infer> + >; - streamObject( + streamObject( options: StreamObjectOptions, TSchema>, - ): Promise, z.infer>>; + ): Promise, xsschema.Infer>>; // Message conversion methods toMessage(message: BaseMessage): InferMessage; diff --git a/packages/core/src/mcp/client/index.ts b/packages/core/src/mcp/client/index.ts index d60d3df89..c3f24cd78 100644 --- a/packages/core/src/mcp/client/index.ts +++ b/packages/core/src/mcp/client/index.ts @@ -11,8 +11,7 @@ import { CallToolResultSchema, ListResourcesResultSchema, } from "@modelcontextprotocol/sdk/types.js"; -import type * as z from "zod"; -import { convertJsonSchemaToZod } from "zod-from-json-schema"; +import * as xsschema from "xsschema"; import devLogger from "../../utils/internal/dev-logger"; import { type Tool, createTool } from "../../tool"; import type { @@ -192,16 +191,14 @@ export class MCPClient extends EventEmitter { inputSchema: unknown; }[]) { try { - const zodSchema = convertJsonSchemaToZod( - toolDef.inputSchema as Record, - ) as unknown as z.ZodType; + const jsonSchema = await xsschema.toJsonSchema(toolDef.inputSchema as xsschema.Schema); const namespacedToolName = `${this.clientInfo.name}_${toolDef.name}`; // Use original separator const agentTool = createTool({ name: namespacedToolName, description: toolDef.description || `Executes the remote tool: ${toolDef.name}`, - parameters: zodSchema, - execute: async (args: Record): Promise => { + parameters: jsonSchema, + execute: async (args: xsschema.JsonSchema): Promise => { try { const result = await this.callTool({ // Use original method name diff --git a/packages/core/src/mcp/types.ts b/packages/core/src/mcp/types.ts index fa6321481..0708d0459 100644 --- a/packages/core/src/mcp/types.ts +++ b/packages/core/src/mcp/types.ts @@ -1,5 +1,6 @@ import type { ClientCapabilities } from "@modelcontextprotocol/sdk/types.js"; import type { Tool } from "../tool"; +import type * as xsschema from "xsschema"; /** * Client information for MCP @@ -170,7 +171,7 @@ export type MCPToolCall = { /** * Arguments to pass to the tool */ - arguments: Record; + arguments: xsschema.JsonSchema; }; /** diff --git a/packages/core/src/server/api.ts b/packages/core/src/server/api.ts index 61d3b5692..2a53a5afa 100644 --- a/packages/core/src/server/api.ts +++ b/packages/core/src/server/api.ts @@ -1,7 +1,7 @@ import { cors } from "hono/cors"; import { WebSocketServer } from "ws"; import type { WebSocket } from "ws"; -import type { z } from "zod"; +import type * as xsschema from "xsschema"; import { OpenAPIHono } from "@hono/zod-openapi"; import { swaggerUI } from "@hono/swagger-ui"; import type { AgentHistoryEntry } from "../agent/history"; @@ -132,7 +132,7 @@ app.use( exposeHeaders: ["Content-Length", "X-Kuma-Revision"], maxAge: 600, credentials: true, - }), + }) ); // Get all agents @@ -169,7 +169,7 @@ app.openapi(getAgentsRoute, (c) => { }); // Define the exact success response type based on the route schema - type SuccessResponse = z.infer< + type SuccessResponse = xsschema.Infer< (typeof getAgentsRoute.responses)[200]["content"]["application/json"]["schema"] >; @@ -182,8 +182,10 @@ app.openapi(getAgentsRoute, (c) => { } catch (error) { console.error("Failed to get agents:", error); return c.json( - { success: false, error: "Failed to retrieve agents" } satisfies z.infer, - 500, + { success: false, error: "Failed to retrieve agents" } satisfies xsschema.Infer< + typeof ErrorSchema + >, + 500 ); } }); @@ -264,26 +266,26 @@ app.openapi(textRoute, async (c) => { if (!agent) { return c.json( - { success: false, error: "Agent not found" } satisfies z.infer, - 404, + { success: false, error: "Agent not found" } satisfies xsschema.Infer, + 404 ); } try { - const { input, options = {} } = c.req.valid("json") as z.infer; + const { input, options = {} } = c.req.valid("json") as xsschema.Infer; const response = await agent.generateText(input, options); return c.json( - { success: true, data: response } satisfies z.infer, - 200, + { success: true, data: response } satisfies xsschema.Infer, + 200 ); } catch (error) { return c.json( { success: false, error: error instanceof Error ? error.message : "Failed to generate text", - } satisfies z.infer, - 500, + } satisfies xsschema.Infer, + 500 ); } }); @@ -296,8 +298,8 @@ app.openapi(streamRoute, async (c) => { if (!agent) { return c.json( - { success: false, error: "Agent not found" } satisfies z.infer, - 404, + { success: false, error: "Agent not found" } satisfies xsschema.Infer, + 404 ); } @@ -308,7 +310,7 @@ app.openapi(streamRoute, async (c) => { maxTokens: 4000, temperature: 0.7, }, - } = c.req.valid("json") as z.infer; + } = c.req.valid("json") as xsschema.Infer; const stream = new ReadableStream({ async start(controller) { @@ -437,8 +439,8 @@ app.openapi(streamRoute, async (c) => { { success: false, error: error instanceof Error ? error.message : "Failed to initiate text stream", - } satisfies z.infer, - 500, + } satisfies xsschema.Infer, + 500 ); } }); @@ -451,8 +453,8 @@ app.openapi(objectRoute, async (c) => { if (!agent) { return c.json( - { success: false, error: "Agent not found" } satisfies z.infer, - 404, + { success: false, error: "Agent not found" } satisfies xsschema.Infer, + 404 ); } @@ -461,22 +463,22 @@ app.openapi(objectRoute, async (c) => { input, schema, options = {}, - } = c.req.valid("json") as z.infer; + } = c.req.valid("json") as xsschema.Infer; - const schemaInZodObject = convertJsonSchemaToZod(schema) as unknown as z.ZodType; + const schemaInZodObject = convertJsonSchemaToZod(schema); const response = await agent.generateObject(input, schemaInZodObject, options); return c.json( - { success: true, data: response } satisfies z.infer, - 200, + { success: true, data: response } satisfies xsschema.Infer, + 200 ); } catch (error) { return c.json( { success: false, error: error instanceof Error ? error.message : "Failed to generate object", - } satisfies z.infer, - 500, + } satisfies xsschema.Infer, + 500 ); } }); @@ -489,8 +491,8 @@ app.openapi(streamObjectRoute, async (c) => { if (!agent) { return c.json( - { success: false, error: "Agent not found" } satisfies z.infer, - 404, + { success: false, error: "Agent not found" } satisfies xsschema.Infer, + 404 ); } @@ -499,9 +501,9 @@ app.openapi(streamObjectRoute, async (c) => { input, schema, options = {}, - } = c.req.valid("json") as z.infer; + } = c.req.valid("json") as xsschema.Infer; - const schemaInZodObject = convertJsonSchemaToZod(schema) as unknown as z.ZodType; + const schemaInZodObject = convertJsonSchemaToZod(schema); const sseStream = new ReadableStream({ async start(controller) { @@ -638,8 +640,8 @@ app.openapi(streamObjectRoute, async (c) => { { success: false, error: error instanceof Error ? error.message : "Failed to initiate object stream", - } satisfies z.infer, - 500, + } satisfies xsschema.Infer, + 500 ); } }); @@ -671,7 +673,7 @@ app.get("/updates", async (c: ApiContext) => { success: false, error: error instanceof Error ? error.message : "Failed to check for updates", }, - 500, + 500 ); } }); @@ -696,7 +698,7 @@ app.post("/updates", async (c: ApiContext) => { success: false, error: error instanceof Error ? error.message : "Failed to perform update", }, - 500, + 500 ); } }); @@ -723,7 +725,7 @@ app.post("/updates/:packageName", async (c: ApiContext) => { success: false, error: error instanceof Error ? error.message : "Failed to update package", }, - 500, + 500 ); } }); @@ -789,7 +791,7 @@ export function registerCustomEndpoint(endpoint: CustomEndpointDefinition): void throw error; } throw new CustomEndpointError( - `Failed to register custom endpoint: ${error instanceof Error ? error.message : String(error)}`, + `Failed to register custom endpoint: ${error instanceof Error ? error.message : String(error)}` ); } } @@ -847,7 +849,7 @@ export function registerCustomEndpoints(endpoints: CustomEndpointDefinition[]): throw error; } throw new CustomEndpointError( - `Failed to register custom endpoints: ${error instanceof Error ? error.message : String(error)}`, + `Failed to register custom endpoints: ${error instanceof Error ? error.message : String(error)}` ); } } @@ -915,7 +917,7 @@ export const createWebSocketServer = () => { message: "WebSocket test connection successful", timestamp: new Date().toISOString(), }, - }), + }) ); ws.on("message", (message) => { @@ -927,7 +929,7 @@ export const createWebSocketServer = () => { type: "ECHO", success: true, data, - }), + }) ); } catch (error) { console.error("[WebSocket] Failed to parse message:", error); @@ -965,12 +967,12 @@ export const createWebSocketServer = () => { type: "HISTORY_LIST", success: true, data: history, - }), + }) ); // Also check if there's an active history entry and send it individually const activeHistory = history.find( - (entry: AgentHistoryEntry) => entry.status !== "completed" && entry.status !== "error", + (entry: AgentHistoryEntry) => entry.status !== "completed" && entry.status !== "error" ); if (activeHistory) { @@ -979,7 +981,7 @@ export const createWebSocketServer = () => { type: "HISTORY_UPDATE", success: true, data: activeHistory, - }), + }) ); } } diff --git a/packages/core/src/tool/index.ts b/packages/core/src/tool/index.ts index a6d98ee29..b15502722 100644 --- a/packages/core/src/tool/index.ts +++ b/packages/core/src/tool/index.ts @@ -1,5 +1,4 @@ import { v4 as uuidv4 } from "uuid"; -import type { z } from "zod"; import type { BaseTool, ToolExecuteOptions, ToolSchema } from "../agent/providers/base/types"; import devLogger from "../utils/internal/dev-logger"; @@ -40,7 +39,7 @@ export type ToolOptions = { /** * Function to execute when the tool is called */ - execute: (args: z.infer, options?: ToolExecuteOptions) => Promise; + execute: (args: T, options?: ToolExecuteOptions) => Promise; }; /** @@ -70,7 +69,7 @@ export class Tool /* implements BaseTool, options?: ToolExecuteOptions) => Promise; + readonly execute: (args: T, options?: ToolExecuteOptions) => Promise; /** * Create a new tool diff --git a/packages/core/src/tool/reasoning/types.ts b/packages/core/src/tool/reasoning/types.ts index ed8ac6ce7..661ebfe0c 100644 --- a/packages/core/src/tool/reasoning/types.ts +++ b/packages/core/src/tool/reasoning/types.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import type * as xsschema from "xsschema"; import type { ToolExecuteOptions } from "../../agent/providers/base/types"; /** @@ -30,7 +31,7 @@ export const ReasoningStepSchema = z.object({ /** * TypeScript type inferred from the ReasoningStepSchema. */ -export type ReasoningStep = z.infer; +export type ReasoningStep = xsschema.Infer; /** * Options specific to reasoning tool execution, extending base ToolExecuteOptions. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6a328c7b..2cd6b4717 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1041,6 +1041,9 @@ importers: '@voltagent/core': specifier: ^0.1.22 version: link:../core + xsschema: + specifier: 0.3.0-beta.2 + version: 0.3.0-beta.2(effect@3.12.7)(zod@3.24.2) zod: specifier: 3.24.2 version: 3.24.2 @@ -1184,6 +1187,9 @@ importers: ws: specifier: ^8.18.1 version: 8.18.1 + xsschema: + specifier: 0.3.0-beta.2 + version: 0.3.0-beta.2(effect@3.12.7)(zod@3.24.2) zod: specifier: 3.24.2 version: 3.24.2 @@ -6944,12 +6950,12 @@ packages: resolution: {integrity: sha512-mXezX18gebJsFObBR4D2undF4PqA+GdraiffejIOL7SRWfJEyBCBYXMtL8CqbA/ESU2DuPQUFChusJA4dF1+ig==} dependencies: '@xsai/generate-text': 0.2.0 - xsschema: 0.2.0(effect@3.12.7)(zod@3.24.2) + xsschema: 0.3.0-beta.3(effect@3.12.7)(zod@3.24.2) transitivePeerDependencies: - '@valibot/to-json-schema' - - '@zod/mini' - arktype - effect + - sury - zod - zod-to-json-schema dev: false @@ -6993,12 +6999,12 @@ packages: resolution: {integrity: sha512-Uanx1CVeYEL8Au/T1VK9viNJWj11pIHuK1bPte3P0XcJKyztI+h07LFWqXeanPMxRe57XSUvV1gOc3ynPYTO/g==} dependencies: '@xsai/stream-text': 0.2.0 - xsschema: 0.2.0(effect@3.12.7)(zod@3.24.2) + xsschema: 0.3.0-beta.3(effect@3.12.7)(zod@3.24.2) transitivePeerDependencies: - '@valibot/to-json-schema' - - '@zod/mini' - arktype - effect + - sury - zod - zod-to-json-schema dev: false @@ -7014,12 +7020,12 @@ packages: dependencies: '@xsai/shared': 0.2.0 '@xsai/shared-chat': 0.2.0 - xsschema: 0.2.0(effect@3.12.7)(zod@3.24.2) + xsschema: 0.3.0-beta.3(effect@3.12.7)(zod@3.24.2) transitivePeerDependencies: - '@valibot/to-json-schema' - - '@zod/mini' - arktype - effect + - sury - zod - zod-to-json-schema dev: false @@ -16519,31 +16525,58 @@ packages: '@xsai/utils-stream': 0.2.0 transitivePeerDependencies: - '@valibot/to-json-schema' - - '@zod/mini' - arktype - effect + - sury - zod - zod-to-json-schema dev: false - /xsschema@0.2.0(effect@3.12.7)(zod@3.24.2): - resolution: {integrity: sha512-UPPI+jP6pn/49q8w7Faz4CYRn33VvG7ngfNQ79nwqTauFP4/m3VusB095a4d5uZ4/TNip5GDxASpzKDoEouN5Q==} + /xsschema@0.3.0-beta.2(effect@3.12.7)(zod@3.24.2): + resolution: {integrity: sha512-J3DZQuitaGtYqikylm+cJvlI/aatbWvPj2GIzjeUO03opEG/+nIQMLVdZhUjFFE6eEWYDjgtrBLevkqewVcjqQ==} peerDependencies: '@valibot/to-json-schema': ^1.0.0 - '@zod/mini': ^4.0.0-beta arktype: ^2.1.16 effect: ^3.14.5 - zod: ^3.24.3 || ^4.0.0-beta + sury: ^10.0.0-rc + zod: ^3.25.0 zod-to-json-schema: ^3.24.5 peerDependenciesMeta: '@valibot/to-json-schema': optional: true - '@zod/mini': + arktype: + optional: true + effect: + optional: true + sury: + optional: true + zod: + optional: true + zod-to-json-schema: + optional: true + dependencies: + effect: 3.12.7 + zod: 3.24.2 + dev: false + + /xsschema@0.3.0-beta.3(effect@3.12.7)(zod@3.24.2): + resolution: {integrity: sha512-8fKI0Kqxs7npz3ElebNCeGdS0HDuS2qL3IqHK5O53yCdh419hcr3GQillwN39TNFasHjbMLQ+DjSwpY0NONdnQ==} + peerDependencies: + '@valibot/to-json-schema': ^1.0.0 + arktype: ^2.1.16 + effect: ^3.14.5 + sury: ^10.0.0-rc + zod: ^3.25.0 + zod-to-json-schema: ^3.24.5 + peerDependenciesMeta: + '@valibot/to-json-schema': optional: true arktype: optional: true effect: optional: true + sury: + optional: true zod: optional: true zod-to-json-schema: