@inngest/agent-kit
Version:
AgentKit is a framework for creating and orchestrating AI agents and AI workflows
1 lines • 94.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/server.ts","../src/agent.ts","../src/jsonSchema.ts","../src/model.ts","../src/adapters/index.ts","../src/adapters/anthropic.ts","../src/tool.ts","../src/network.ts","../src/util.ts","../src/adapters/openai.ts","../src/adapters/gemini.ts"],"sourcesContent":["import { Inngest, slugify, type InngestFunction } from \"inngest\";\nimport { createServer as createInngestServer } from \"inngest/node\";\nimport { type Agent } from \"./agent\";\nimport { type Network } from \"./network\";\n\n/**\n * Create a server to serve Agents and Networks as Inngest functions\n *\n * @example\n * ```ts\n * import { createServer, createAgent, createNetwork } from \"@inngest/agent-kit\";\n *\n * const myAgent = createAgent(...);\n * const myNetwork = createNetwork(...);\n * const server = createServer({\n * agents: [myAgent],\n * networks: [myNetworks],\n * });\n * server.listen(3000)\n * ```\n *\n * @public\n */\nexport const createServer = ({\n appId = \"agent-kit\",\n networks = [],\n agents = [],\n client,\n functions: manualFns = [],\n}: {\n appId?: string;\n networks?: Network[];\n agents?: Agent[];\n functions?: InngestFunction.Any[];\n client?: Inngest.Any;\n}) => {\n const inngest = client ?? new Inngest({ id: appId });\n\n const functions = manualFns.reduce<Record<string, InngestFunction.Any>>(\n (acc, fn) => {\n return {\n ...acc,\n [fn.id()]: fn,\n };\n },\n {}\n );\n\n for (const agent of agents) {\n const slug = slugify(agent.name);\n const id = `agent-${slug}`;\n\n functions[id] = inngest.createFunction(\n { id, name: agent.name },\n { event: `${inngest.id}/${id}` },\n async ({ event }) => {\n // eslint-disable-next-line\n return agent.run(event.data.input);\n }\n );\n }\n\n for (const network of networks) {\n const slug = slugify(network.name);\n const id = `network-${slug}`;\n\n functions[id] = inngest.createFunction(\n { id, name: network.name },\n { event: `${inngest.id}/${id}` },\n async ({ event }) => {\n // eslint-disable-next-line\n return network.run(event.data.input);\n }\n );\n }\n\n return createInngestServer({\n client: inngest,\n functions: Object.values(functions),\n });\n};\n","import { type AiAdapter } from \"@inngest/ai\";\nimport { Client as MCPClient } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { WebSocketClientTransport } from \"@modelcontextprotocol/sdk/client/websocket.js\";\nimport { type Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { ListToolsResultSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { EventSource } from \"eventsource\";\nimport { referenceFunction, type Inngest } from \"inngest\";\nimport { type InngestFunction } from \"inngest/components/InngestFunction\";\nimport { serializeError } from \"inngest/helpers/errors\";\nimport { type MinimalEventPayload } from \"inngest/types\";\nimport type { ZodType } from \"zod\";\nimport { JSONSchemaToZod, type JSONSchema } from \"./jsonSchema\";\nimport { createAgenticModelFromAiAdapter, type AgenticModel } from \"./model\";\nimport { NetworkRun } from \"./networkRun\";\nimport {\n InferenceResult,\n State,\n type Message,\n type ToolResultMessage,\n} from \"./state\";\nimport { type MCP, type Tool } from \"./tool\";\nimport {\n getInngestFnInput,\n getStepTools,\n isInngestFn,\n type AnyZodType,\n type MaybePromise,\n} from \"./util\";\n\n/**\n * createTool is a helper that properly types the input argument for a handler\n * based off of the Zod parameter types.\n */\nexport const createTool = <T extends AnyZodType>(t: Tool<T>): Tool<T> => t;\n\n/**\n * Agent represents a single agent, responsible for a set of tasks.\n */\nexport const createAgent = (opts: Agent.Constructor) => new Agent(opts);\n\nexport const createRoutingAgent = (opts: Agent.RoutingConstructor) =>\n new RoutingAgent(opts);\n\n/**\n * Agent represents a single agent, responsible for a set of tasks.\n */\nexport class Agent {\n /**\n * name is the name of the agent.\n */\n name: string;\n\n /**\n * description is the description of the agent.\n */\n description: string;\n\n /**\n * system is the system prompt for the agent.\n */\n system: string | ((ctx: { network?: NetworkRun }) => MaybePromise<string>);\n\n /**\n * Assistant is the assistent message used for completion, if any.\n */\n assistant: string;\n\n /**\n * tools are a list of tools that this specific agent has access to.\n */\n tools: Map<string, Tool.Any>;\n\n /**\n * tool_choice allows you to specify whether tools are automatically. this defaults\n * to \"auto\", allowing the model to detect when to call tools automatically. Choices are:\n *\n * - \"auto\": allow the model to choose tools automatically\n * - \"any\": force the use of any tool in the tools map\n * - string: force the name of a particular tool\n */\n tool_choice?: Tool.Choice;\n\n /**\n * lifecycles are programmatic hooks used to manage the agent.\n */\n lifecycles: Agent.Lifecycle | Agent.RoutingLifecycle | undefined;\n\n /**\n * model is the step caller to use for this agent. This allows the agent\n * to use a specific model which may be different to other agents in the\n * system\n */\n model: AiAdapter.Any | undefined;\n\n /**\n * mcpServers is a list of MCP (model-context-protocol) servers which can\n * provide tools to the agent.\n */\n mcpServers?: MCP.Server[];\n\n // _mcpInit records whether the MCP tool list has been initialized.\n private _mcpClients: MCPClient[];\n\n constructor(opts: Agent.Constructor | Agent.RoutingConstructor) {\n this.name = opts.name;\n this.description = opts.description || \"\";\n this.system = opts.system;\n this.assistant = opts.assistant || \"\";\n this.tools = new Map();\n this.tool_choice = opts.tool_choice;\n this.lifecycles = opts.lifecycle;\n this.model = opts.model;\n this.setTools(opts.tools);\n this.mcpServers = opts.mcpServers;\n this._mcpClients = [];\n }\n\n private setTools(tools: Agent.Constructor[\"tools\"]): void {\n for (const tool of tools || []) {\n if (isInngestFn(tool)) {\n this.tools.set(tool[\"absoluteId\"], {\n name: tool[\"absoluteId\"],\n description: tool.description,\n // TODO Should we error here if we can't find an input schema?\n parameters: getInngestFnInput(tool),\n handler: async (input: MinimalEventPayload[\"data\"], opts) => {\n // Doing this late means a potential throw if we use the agent in a\n // non-Inngest environment. We could instead calculate the tool list\n // JIT and omit any Inngest tools if we're not in an Inngest\n // context.\n const step = await getStepTools();\n if (!step) {\n throw new Error(\"Inngest tool called outside of Inngest context\");\n }\n\n const stepId = `${opts.agent.name}/tools/${tool[\"absoluteId\"]}`;\n\n return step.invoke(stepId, {\n function: referenceFunction({\n appId: (tool[\"client\"] as Inngest.Any)[\"id\"],\n functionId: tool.id(),\n }),\n data: input,\n });\n },\n });\n } else {\n this.tools.set(tool.name, tool);\n }\n }\n }\n\n withModel(model: AiAdapter.Any): Agent {\n return new Agent({\n name: this.name,\n description: this.description,\n system: this.system,\n assistant: this.assistant,\n tools: Array.from(this.tools.values()),\n lifecycle: this.lifecycles,\n model,\n });\n }\n\n /**\n * Run runs an agent with the given user input, treated as a user message. If\n * the input is an empty string, only the system prompt will execute.\n */\n async run(\n input: string,\n { model, network, state, maxIter = 0 }: Agent.RunOptions | undefined = {}\n ): Promise<InferenceResult> {\n // Attempt to resolve the MCP tools, if we haven't yet done so.\n await this.initMCP();\n\n const rawModel = model || this.model || network?.defaultModel;\n if (!rawModel) {\n throw new Error(\"No model provided to agent\");\n }\n\n const p = createAgenticModelFromAiAdapter(rawModel);\n\n // input state always overrides the network state.\n const s = state || network?.state || new State();\n const run = network && new NetworkRun(network, s);\n\n let history = s ? s.format() : [];\n let prompt = await this.agentPrompt(input, run);\n let result = new InferenceResult(this, input, prompt, history, [], [], \"\");\n let hasMoreActions = true;\n let iter = 0;\n\n do {\n // Call lifecycles each time we perform inference.\n if (this.lifecycles?.onStart) {\n const modified = await this.lifecycles.onStart({\n agent: this,\n network: run,\n input,\n prompt,\n history,\n });\n\n if (modified.stop) {\n // We allow users to prevent calling the LLM directly here.\n return result;\n }\n\n prompt = modified.prompt;\n history = modified.history;\n }\n\n const inference = await this.performInference(\n input,\n p,\n prompt,\n history,\n run\n );\n\n hasMoreActions = Boolean(\n this.tools.size > 0 &&\n inference.output.length &&\n inference.output[inference.output.length - 1]!.stop_reason !== \"stop\"\n );\n\n result = inference;\n history = [...inference.output];\n iter++;\n } while (hasMoreActions && iter < maxIter);\n\n if (this.lifecycles?.onFinish) {\n result = await this.lifecycles.onFinish({\n agent: this,\n network: run,\n result,\n });\n }\n\n // Note that the routing lifecycles aren't called by the agent. They're called\n // by the network.\n\n return result;\n }\n\n private async performInference(\n input: string,\n p: AgenticModel.Any,\n prompt: Message[],\n history: Message[],\n network?: NetworkRun\n ): Promise<InferenceResult> {\n const { output, raw } = await p.infer(\n this.name,\n prompt.concat(history),\n Array.from(this.tools.values()),\n this.tool_choice || \"auto\"\n );\n\n // Now that we've made the call, we instantiate a new InferenceResult for\n // lifecycles and history.\n let result = new InferenceResult(\n this,\n input,\n prompt,\n history,\n output,\n [],\n typeof raw === \"string\" ? raw : JSON.stringify(raw)\n );\n if (this.lifecycles?.onResponse) {\n result = await this.lifecycles.onResponse({\n agent: this,\n network,\n result,\n });\n }\n\n // And ensure we invoke any call from the agent\n const toolCallOutput = await this.invokeTools(result.output, p, network);\n if (toolCallOutput.length > 0) {\n result.toolCalls = result.toolCalls.concat(toolCallOutput);\n }\n\n return result;\n }\n\n /**\n * invokeTools takes output messages from an inference call then invokes any tools\n * in the message responses.\n */\n private async invokeTools(\n msgs: Message[],\n p: AgenticModel.Any,\n network?: NetworkRun\n ): Promise<ToolResultMessage[]> {\n const output: ToolResultMessage[] = [];\n\n for (const msg of msgs) {\n if (msg.type !== \"tool_call\") {\n continue;\n }\n\n if (!Array.isArray(msg.tools)) {\n continue;\n }\n\n for (const tool of msg.tools) {\n const found = this.tools.get(tool.name);\n if (!found) {\n throw new Error(\n `Inference requested a non-existent tool: ${tool.name}`\n );\n }\n\n // Call this tool.\n //\n // XXX: You might expect this to be wrapped in a step, but each tool can\n // use multiple step tools, eg. `step.run`, then `step.waitForEvent` for\n // human in the loop tasks.\n //\n\n const result = await Promise.resolve(\n found.handler(tool.input, {\n agent: this,\n network,\n step: await getStepTools(),\n })\n )\n .then((r) => {\n return {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n data:\n typeof r === \"undefined\"\n ? `${tool.name} successfully executed`\n : r,\n };\n })\n .catch((err) => {\n return { error: serializeError(err) };\n });\n\n output.push({\n role: \"tool_result\",\n type: \"tool_result\",\n tool: {\n type: \"tool\",\n id: tool.id,\n name: tool.name,\n input: tool.input.arguments as Record<string, unknown>,\n },\n\n content: result,\n stop_reason: \"tool\",\n });\n }\n }\n\n return output;\n }\n\n private async agentPrompt(\n input: string,\n network?: NetworkRun\n ): Promise<Message[]> {\n // Prompt returns the full prompt for the current agent. This does NOT\n // include the existing network's state as part of the prompt.\n //\n // Note that the agent's system message always comes first.\n const messages: Message[] = [\n {\n type: \"text\",\n role: \"system\",\n content:\n typeof this.system === \"string\"\n ? this.system\n : await this.system({ network }),\n },\n ];\n\n if (input.length > 0) {\n messages.push({ type: \"text\", role: \"user\", content: input });\n }\n\n if (this.assistant.length > 0) {\n messages.push({\n type: \"text\",\n role: \"assistant\",\n content: this.assistant,\n });\n }\n\n return messages;\n }\n\n // initMCP fetches all tools from the agent's MCP servers, adding them to the tool list.\n // This is all that's necessary in order to enable MCP tool use within agents\n private async initMCP() {\n if (\n !this.mcpServers ||\n this._mcpClients.length === this.mcpServers.length\n ) {\n return;\n }\n\n const promises = [];\n for (const server of this.mcpServers) {\n await this.listMCPTools(server);\n promises.push(this.listMCPTools(server));\n }\n\n await Promise.all(promises);\n }\n\n /**\n * listMCPTools lists all available tools for a given MCP server\n */\n private async listMCPTools(server: MCP.Server) {\n const client = await this.mcpClient(server);\n try {\n const results = await client.request(\n { method: \"tools/list\" },\n ListToolsResultSchema\n );\n results.tools.forEach((t) => {\n const name = `${server.name}-${t.name}`;\n\n let zschema: undefined | ZodType;\n try {\n zschema = JSONSchemaToZod.convert(t.inputSchema as JSONSchema);\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (e) {\n // Do nothing here.\n zschema = undefined;\n }\n\n // Add the MCP tools directly to the tool set.\n this.tools.set(name, {\n name: name,\n description: t.description,\n parameters: zschema,\n mcp: {\n server,\n tool: t,\n },\n handler: async (input: { [x: string]: unknown } | undefined) => {\n const fn = () =>\n client.callTool({\n name: t.name,\n arguments: input,\n });\n\n const step = await getStepTools();\n const result = await (step?.run(name, fn) ?? fn());\n\n return result.content;\n },\n });\n });\n } catch (e) {\n console.warn(\"error listing mcp tools\", e);\n }\n }\n\n /**\n * mcpClient creates a new MCP client for the given server.\n */\n private async mcpClient(server: MCP.Server): Promise<MCPClient> {\n // Does this client already exist?\n const transport: Transport = (() => {\n switch (server.transport.type) {\n case \"sse\":\n // Check if EventSource is defined. If not, we use a polyfill.\n if (global.EventSource === undefined) {\n global.EventSource = EventSource;\n }\n return new SSEClientTransport(new URL(server.transport.url), {\n eventSourceInit: server.transport.eventSourceInit,\n requestInit: server.transport.requestInit,\n });\n case \"ws\":\n return new WebSocketClientTransport(new URL(server.transport.url));\n }\n })();\n\n const client = new MCPClient(\n {\n name: this.name,\n // XXX: This version should change.\n version: \"1.0.0\",\n },\n {\n capabilities: {},\n }\n );\n try {\n await client.connect(transport);\n } catch (e) {\n // The transport closed.\n console.warn(\"mcp server disconnected\", server, e);\n }\n this._mcpClients.push(client);\n return client;\n }\n}\n\nexport class RoutingAgent extends Agent {\n type = \"routing\";\n override lifecycles: Agent.RoutingLifecycle;\n constructor(opts: Agent.RoutingConstructor) {\n super(opts);\n this.lifecycles = opts.lifecycle;\n }\n\n override withModel(model: AiAdapter.Any): RoutingAgent {\n return new RoutingAgent({\n name: this.name,\n description: this.description,\n system: this.system,\n assistant: this.assistant,\n tools: Array.from(this.tools.values()),\n lifecycle: this.lifecycles,\n model,\n });\n }\n}\n\nexport namespace Agent {\n export interface Constructor {\n name: string;\n description?: string;\n system: string | ((ctx: { network?: NetworkRun }) => MaybePromise<string>);\n assistant?: string;\n tools?: (Tool.Any | InngestFunction.Any)[];\n tool_choice?: Tool.Choice;\n lifecycle?: Lifecycle;\n model?: AiAdapter.Any;\n mcpServers?: MCP.Server[];\n }\n\n export interface RoutingConstructor extends Omit<Constructor, \"lifecycle\"> {\n lifecycle: RoutingLifecycle;\n }\n\n export interface RoutingConstructor extends Omit<Constructor, \"lifecycle\"> {\n lifecycle: RoutingLifecycle;\n }\n\n export interface RoutingConstructor extends Omit<Constructor, \"lifecycle\"> {\n lifecycle: RoutingLifecycle;\n }\n\n export interface RunOptions {\n model?: AiAdapter.Any;\n network?: NetworkRun;\n /**\n * State allows you to pass custom state into a single agent run call. This should only\n * be provided if you are running agents outside of a network. Networks automatically\n * supply their own state.\n */\n state?: State;\n maxIter?: number;\n }\n\n export interface Lifecycle {\n /**\n * enabled selectively enables or disables this agent based off of network\n * state. If this function is not provided, the agent is always enabled.\n */\n enabled?: (args: Agent.LifecycleArgs.Base) => MaybePromise<boolean>;\n\n /**\n * onStart is called just before an agent starts an inference call.\n *\n * This receives the full agent prompt. If this is a networked agent, the\n * agent will also receive the network's history which will be concatenated\n * to the end of the prompt when making the inference request.\n *\n * The return values can be used to adjust the prompt, history, or to stop\n * the agent from making the call altogether.\n *\n */\n onStart?: (args: Agent.LifecycleArgs.Before) => MaybePromise<{\n prompt: Message[];\n history: Message[];\n // stop, if true, will prevent calling the agent\n stop: boolean;\n }>;\n\n /**\n * onResponse is called after the inference call finishes, before any tools\n * have been invoked. This allows you to moderate the response prior to\n * running tools.\n */\n onResponse?: (\n args: Agent.LifecycleArgs.Result\n ) => MaybePromise<InferenceResult>;\n\n /**\n * onFinish is called with a finalized InferenceResult, including any tool\n * call results. The returned InferenceResult will be saved to network\n * history, if the agent is part of the network.\n *\n */\n onFinish?: (\n args: Agent.LifecycleArgs.Result\n ) => MaybePromise<InferenceResult>;\n }\n\n export namespace LifecycleArgs {\n export interface Base {\n // Agent is the agent that made the call.\n agent: Agent;\n // Network represents the network that this agent or lifecycle belongs to.\n network?: NetworkRun;\n }\n\n export interface Result extends Base {\n result: InferenceResult;\n }\n\n export interface Before extends Base {\n // input is the user request for the entire agentic operation.\n input?: string;\n\n // prompt is the system, user, and any assistant prompt as generated\n // by the Agent. This does not include any past history.\n prompt: Message[];\n\n // history is the past history as generated via State. Ths will be added\n // after the prompt to form a single conversation log.\n history?: Message[];\n }\n }\n\n export interface RoutingLifecycle extends Lifecycle {\n onRoute: RouterFn;\n }\n\n export type RouterFn = (args: Agent.RouterArgs) => string[] | undefined;\n\n /**\n * Router args are the arguments passed to the onRoute lifecycle hook.\n */\n export type RouterArgs = Agent.LifecycleArgs.Result;\n}\n","import { z, type ZodSchema, type ZodTypeAny } from \"zod\";\n\n/**\n * Represents any valid JSON value.\n */\nexport type JSONValue =\n | string\n | number\n | boolean\n | null\n | JSONObject\n | JSONValue[];\n\n/**\n * Represents a JSON object.\n */\nexport type JSONObject = {\n [key: string]: JSONValue;\n};\n\nexport type JSONSchema = {\n type?: string | string[];\n properties?: Record<string, JSONSchema>;\n items?: JSONSchema | JSONSchema[];\n required?: string[];\n enum?: (string | number)[];\n format?: string;\n oneOf?: JSONSchema[];\n allOf?: JSONSchema[];\n anyOf?: JSONSchema[];\n additionalProperties?: boolean | JSONSchema;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any; // For any other additional properties\n};\n\nexport class JSONSchemaToZod {\n /**\n * Converts a JSON schema to a Zod schema.\n *\n * @param {JSONSchema} schema - The JSON schema.\n * @returns {ZodSchema} - The Zod schema.\n */\n public static convert(schema: JSONSchema): ZodSchema {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return this.parseSchema(schema);\n }\n\n /**\n * Checks if data matches a condition schema.\n *\n * @param {JSONValue} data - The data to check.\n * @param {JSONSchema} condition - The condition schema.\n * @returns {boolean} - Whether the data matches the condition.\n */\n private static matchesCondition(\n data: JSONValue,\n condition: JSONSchema\n ): boolean {\n // If no properties to check, condition is met\n if (!condition.properties) {\n return true;\n }\n\n // If data is not an object or is null, it can't match a schema with properties\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n return false;\n }\n\n // Now we know data is a JSONObject\n const objectData = data;\n\n // Check all property conditions\n for (const [key, propCondition] of Object.entries(condition.properties)) {\n // If property doesn't exist in data\n if (!(key in objectData)) {\n // If there's a const condition and property is missing, it doesn't match\n if (\"const\" in propCondition) {\n return false;\n }\n\n // For other conditions, skip this property\n continue;\n }\n\n const value = objectData[key];\n\n // Check for const condition\n if (\"const\" in propCondition && value !== propCondition[\"const\"]) {\n return false;\n }\n\n // Check for minimum condition\n if (\n \"minimum\" in propCondition &&\n typeof value === \"number\" &&\n value < propCondition[\"minimum\"]\n ) {\n return false;\n }\n\n // Check for maximum condition\n if (\n \"maximum\" in propCondition &&\n typeof value === \"number\" &&\n value > propCondition[\"maximum\"]\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Validates data against a conditional schema and adds issues to context if validation fails.\n *\n * @param {JSONValue} data - The data to validate.\n * @param {JSONSchema} schema - The conditional schema.\n * @param {z.RefinementCtx} ctx - The Zod refinement context.\n */\n private static validateConditionalSchema(\n data: JSONValue,\n schema: JSONSchema,\n ctx: z.RefinementCtx\n ): void {\n this.validateRequiredProperties(data, schema, ctx);\n this.validatePropertyPatterns(data, schema, ctx);\n this.validateNestedConditions(data, schema, ctx);\n }\n\n /**\n * Validates that all required properties are present in the data.\n *\n * @param {JSONValue} data - The data to validate.\n * @param {JSONSchema} schema - The schema containing required properties.\n * @param {z.RefinementCtx} ctx - The Zod refinement context.\n */\n private static validateRequiredProperties(\n data: JSONValue,\n schema: JSONSchema,\n ctx: z.RefinementCtx\n ): void {\n if (!schema.required) {\n return;\n }\n\n // If data is not an object or is null, all required properties are missing\n if (typeof data !== \"object\" || data === null) {\n for (const requiredProp of schema.required) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `Required property '${requiredProp}' is missing`,\n path: [requiredProp],\n });\n }\n return;\n }\n\n // Now we know data is an object (either a plain object or an array)\n for (const requiredProp of schema.required) {\n if (!(requiredProp in data)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `Required property '${requiredProp}' is missing`,\n path: [requiredProp],\n });\n }\n }\n }\n\n /**\n * Validates property patterns for string properties.\n *\n * @param {JSONValue} data - The data to validate.\n * @param {JSONSchema} schema - The schema containing property patterns.\n * @param {z.RefinementCtx} ctx - The Zod refinement context.\n */\n private static validatePropertyPatterns(\n data: JSONValue,\n schema: JSONSchema,\n ctx: z.RefinementCtx\n ): void {\n if (!schema.properties) {\n return;\n }\n\n // If data is not an object or is null, we can't validate property patterns\n if (typeof data !== \"object\" || data === null) {\n return;\n }\n\n // If data is an array, we can't validate property patterns\n if (Array.isArray(data)) {\n return;\n }\n\n // Now we know data is a JSONObject\n const objectData = data;\n\n // Process each property in the schema\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n // Skip if property doesn't exist in data\n if (!(key in objectData)) {\n continue;\n }\n\n const value = objectData[key];\n\n // Check pattern validation for strings\n if (propSchema[\"pattern\"] && typeof value === \"string\") {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const regex = new RegExp(propSchema[\"pattern\"]);\n if (!regex.test(value)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `String '${value}' does not match pattern '${propSchema[\"pattern\"]}'`,\n path: [key],\n });\n }\n }\n }\n }\n\n /**\n * Validates nested if-then-else conditions.\n *\n * @param {JSONValue} data - The data to validate.\n * @param {JSONSchema} schema - The schema containing if-then-else conditions.\n * @param {z.RefinementCtx} ctx - The Zod refinement context.\n */\n private static validateNestedConditions(\n data: JSONValue,\n schema: JSONSchema,\n ctx: z.RefinementCtx\n ): void {\n if (!schema[\"if\"] || !schema[\"then\"]) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const matchesIf = this.matchesCondition(data, schema[\"if\"]);\n if (matchesIf) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.validateConditionalSchema(data, schema[\"then\"], ctx);\n } else if (schema[\"else\"]) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.validateConditionalSchema(data, schema[\"else\"], ctx);\n }\n }\n\n /**\n * Parses a JSON schema and returns the corresponding Zod schema.\n * This is the main entry point for schema conversion.\n *\n * @param {JSONSchema} schema - The JSON schema.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseSchema(schema: JSONSchema): ZodTypeAny {\n // Handle array of types (e.g., ['string', 'null'] for nullable types)\n if (Array.isArray(schema.type)) {\n return this.handleTypeArray(schema);\n }\n\n // Handle combinators (oneOf, anyOf, allOf)\n if (schema.oneOf || schema.anyOf || schema.allOf) {\n return this.parseCombinator(schema);\n }\n\n // Handle if-then-else conditional validation\n if (schema[\"if\"] && schema[\"then\"]) {\n return this.parseObject(schema);\n }\n\n // Handle object schema without explicit type but with properties\n if (schema.properties && (!schema.type || schema.type === \"object\")) {\n return this.parseObject(schema);\n }\n\n // Handle all other types\n return this.handleSingleType(schema);\n }\n\n /**\n * Handles schemas with an array of types.\n *\n * @param {JSONSchema} schema - The JSON schema with type array.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static handleTypeArray(schema: JSONSchema): ZodTypeAny {\n if (!Array.isArray(schema.type)) {\n throw new Error(\"Expected schema.type to be an array\");\n }\n\n // Check if the type array includes 'null' to create a nullable type\n if (schema.type.includes(\"null\")) {\n return this.handleNullableType(schema);\n }\n\n // If no 'null' in the type array, handle as a union of types\n return this.createUnionFromTypes(schema.type, schema);\n }\n\n /**\n * Handles nullable types by creating a nullable schema.\n *\n * @param {JSONSchema} schema - The JSON schema with nullable type.\n * @returns {ZodTypeAny} - The nullable Zod schema.\n */\n private static handleNullableType(schema: JSONSchema): ZodTypeAny {\n if (!Array.isArray(schema.type)) {\n throw new Error(\"Expected schema.type to be an array\");\n }\n\n // Create a copy of the schema without the 'null' type\n const nonNullSchema = { ...schema };\n nonNullSchema.type = schema.type.filter((t) => t !== \"null\");\n\n // If there's only one type left, handle it as a single type and make it nullable\n if (nonNullSchema.type.length === 1) {\n const singleTypeSchema = this.handleSingleType({\n ...schema,\n type: nonNullSchema.type[0],\n });\n return singleTypeSchema.nullable();\n }\n\n // If multiple non-null types, create a union and make it nullable\n const unionSchema = this.parseSchema(nonNullSchema);\n return unionSchema.nullable();\n }\n\n /**\n * Creates a union type from an array of types.\n *\n * @param {string[]} types - Array of type strings.\n * @param {JSONSchema} baseSchema - The base schema to apply to each type.\n * @returns {ZodTypeAny} - The union Zod schema.\n */\n private static createUnionFromTypes(\n types: string[],\n baseSchema: JSONSchema\n ): ZodTypeAny {\n const schemas = types.map((type) => {\n const singleTypeSchema = { ...baseSchema, type };\n return this.parseSchema(singleTypeSchema);\n });\n\n return z.union(schemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n }\n\n /**\n * Handles schemas with a single type.\n *\n * @param {JSONSchema} schema - The JSON schema with single type.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static handleSingleType(schema: JSONSchema): ZodTypeAny {\n // Handle schemas without a type property\n if (schema.type === undefined) {\n // Check for combinators first\n if (schema.oneOf || schema.anyOf || schema.allOf) {\n return this.parseCombinator(schema);\n }\n\n // Check for object properties\n if (schema.properties) {\n return this.parseObject(schema);\n }\n\n // Default to any() for schemas with no type and no other indicators\n return z.any();\n }\n\n // Handle specific types\n switch (schema.type) {\n case \"string\":\n return this.parseString(schema);\n case \"number\":\n case \"integer\":\n return this.parseNumberSchema(schema);\n case \"boolean\":\n return z.boolean();\n case \"array\":\n return this.parseArray(schema);\n case \"object\":\n return this.parseObject(schema);\n default:\n throw new Error(\"Unsupported schema type\");\n }\n }\n\n /**\n * Parses a number schema.\n *\n * @param {JSONSchema} schema - The JSON schema for a number.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseNumberSchema(schema: JSONSchema): ZodTypeAny {\n const numberSchema = z.number();\n\n // Apply all number validations\n let result: z.ZodTypeAny = numberSchema;\n result = this.applyNumberBounds(numberSchema, schema);\n result = this.applyNumberMultipleOf(numberSchema, schema);\n result = this.applyNumberEnum(numberSchema, schema);\n result = this.applyIntegerConstraint(numberSchema, schema);\n\n return result;\n }\n\n /**\n * Applies bounds validation to a number schema.\n *\n * @param {z.ZodNumber} numberSchema - The base number schema.\n * @param {JSONSchema} schema - The JSON schema with bounds.\n * @returns {z.ZodNumber} - The updated schema with bounds validation.\n */\n private static applyNumberBounds(\n numberSchema: z.ZodNumber,\n schema: JSONSchema\n ): z.ZodTypeAny {\n let result = numberSchema;\n\n if (schema[\"minimum\"] !== undefined) {\n result = schema[\"exclusiveMinimum\"]\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n result.gt(schema[\"minimum\"])\n : // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n result.gte(schema[\"minimum\"]);\n }\n\n if (schema[\"maximum\"] !== undefined) {\n result = schema[\"exclusiveMaximum\"]\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n result.lt(schema[\"maximum\"])\n : // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n result.lte(schema[\"maximum\"]);\n }\n\n return result;\n }\n\n /**\n * Applies multipleOf validation to a number schema.\n *\n * @param {z.ZodNumber} numberSchema - The base number schema.\n * @param {JSONSchema} schema - The JSON schema with multipleOf.\n * @returns {z.ZodNumber} - The updated schema with multipleOf validation.\n */\n private static applyNumberMultipleOf(\n numberSchema: z.ZodNumber,\n schema: JSONSchema\n ): z.ZodTypeAny {\n if (schema[\"multipleOf\"] === undefined) {\n return numberSchema;\n }\n\n return numberSchema.refine((val) => val % schema[\"multipleOf\"]! === 0, {\n message: `Number must be a multiple of ${schema[\"multipleOf\"]}`,\n });\n }\n\n /**\n * Applies enum validation to a number schema.\n *\n * @param {z.ZodNumber} numberSchema - The base number schema.\n * @param {JSONSchema} schema - The JSON schema with enum.\n * @returns {z.ZodNumber} - The updated schema with enum validation.\n */\n private static applyNumberEnum(\n numberSchema: z.ZodNumber,\n schema: JSONSchema\n ): z.ZodTypeAny {\n if (!schema.enum) {\n return numberSchema;\n }\n\n // Filter out non-number values from enum\n const numberEnums = schema.enum.filter((val) => typeof val === \"number\");\n if (numberEnums.length === 0) {\n return numberSchema;\n }\n\n // Use refinement to validate against enum values\n return numberSchema.refine((val) => numberEnums.includes(val), {\n message: `Number must be one of: ${numberEnums.join(\", \")}`,\n });\n }\n\n /**\n * Applies integer constraint to a number schema if needed.\n *\n * @param {z.ZodNumber} numberSchema - The base number schema.\n * @param {JSONSchema} schema - The JSON schema.\n * @returns {z.ZodNumber} - The updated schema with integer validation if needed.\n */\n private static applyIntegerConstraint(\n numberSchema: z.ZodNumber,\n schema: JSONSchema\n ): z.ZodTypeAny {\n if (schema.type !== \"integer\") {\n return numberSchema;\n }\n\n return numberSchema.refine((val) => Number.isInteger(val), {\n message: \"Number must be an integer\",\n });\n }\n\n /**\n * Parses a string schema.\n *\n * @param {JSONSchema} schema - The JSON schema for a string.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseString(schema: JSONSchema): ZodTypeAny {\n const stringSchema = z.string();\n let result: z.ZodTypeAny = stringSchema;\n\n // Apply all string validations\n if (schema.format) {\n // Handle format-specific string validation\n return this.applyStringFormat(stringSchema, schema);\n } else {\n // Only apply other validations if format is not specified\n // or apply them to the formatted string\n result = this.applyStringPattern(stringSchema, schema);\n result = this.applyStringLength(stringSchema, schema);\n result = this.applyStringEnum(stringSchema, schema);\n }\n\n return result;\n }\n\n /**\n * Applies format validation to a string schema.\n *\n * @param {z.ZodString} stringSchema - The base string schema.\n * @param {JSONSchema} schema - The JSON schema with format.\n * @returns {ZodTypeAny} - The updated schema with format validation.\n */\n private static applyStringFormat(\n stringSchema: z.ZodString,\n schema: JSONSchema\n ): ZodTypeAny {\n if (!schema.format) {\n return stringSchema;\n }\n\n switch (schema.format) {\n case \"email\":\n return stringSchema.email();\n case \"date-time\":\n return stringSchema.datetime();\n case \"uri\":\n return stringSchema.url();\n case \"uuid\":\n return stringSchema.uuid();\n case \"date\":\n return stringSchema.date();\n default:\n return stringSchema;\n }\n }\n\n /**\n * Applies pattern validation to a string schema.\n *\n * @param {z.ZodString} stringSchema - The base string schema.\n * @param {JSONSchema} schema - The JSON schema with pattern.\n * @returns {z.ZodString} - The updated schema with pattern validation.\n */\n private static applyStringPattern(\n stringSchema: z.ZodString,\n schema: JSONSchema\n ): z.ZodTypeAny {\n if (!schema[\"pattern\"]) {\n return stringSchema;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const regex = new RegExp(schema[\"pattern\"]);\n return stringSchema.regex(regex, {\n message: `String must match pattern: ${schema[\"pattern\"]}`,\n });\n }\n\n /**\n * Applies length constraints to a string schema.\n *\n * @param {z.ZodString} stringSchema - The base string schema.\n * @param {JSONSchema} schema - The JSON schema with length constraints.\n * @returns {z.ZodString} - The updated schema with length validation.\n */\n private static applyStringLength(\n stringSchema: z.ZodString,\n schema: JSONSchema\n ): z.ZodTypeAny {\n const result = stringSchema;\n\n if (schema[\"minLength\"] !== undefined) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n stringSchema = stringSchema.min(schema[\"minLength\"]);\n }\n\n if (schema[\"maxLength\"] !== undefined) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n stringSchema = stringSchema.max(schema[\"maxLength\"]);\n }\n\n return result;\n }\n\n /**\n * Applies enum validation to a string schema.\n *\n * @param {z.ZodString} stringSchema - The base string schema.\n * @param {JSONSchema} schema - The JSON schema with enum.\n * @returns {ZodTypeAny} - The updated schema with enum validation.\n */\n private static applyStringEnum(\n stringSchema: z.ZodString,\n schema: JSONSchema\n ): ZodTypeAny {\n if (!schema.enum) {\n return stringSchema;\n }\n\n // Use refinement to validate against enum values\n return stringSchema.refine((val) => schema.enum?.includes(val), {\n message: `Value must be one of: ${schema.enum?.join(\", \")}`,\n });\n }\n\n /**\n * Parses a JSON schema of type array and returns the corresponding Zod schema.\n *\n * @param {JSONSchema} schema - The JSON schema.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseArray(schema: JSONSchema): ZodTypeAny {\n // Handle tuple validation (items is an array)\n if (Array.isArray(schema.items)) {\n const tupleSchemas = schema.items.map((item) => this.parseSchema(item));\n return z.union(tupleSchemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n }\n\n // Create regular array schema\n const itemSchema = schema.items ? this.parseSchema(schema.items) : z.any();\n const arraySchema = z.array(itemSchema);\n\n // Apply array constraints\n let result: z.ZodTypeAny = arraySchema;\n result = this.applyArrayConstraints(arraySchema, schema);\n\n return result;\n }\n\n /**\n * Applies constraints to an array schema.\n *\n * @param {z.ZodArray<any>} arraySchema - The base array schema.\n * @param {JSONSchema} schema - The JSON schema with array constraints.\n * @returns {z.ZodTypeAny} - The updated array schema with constraints.\n */\n private static applyArrayConstraints(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n arraySchema: z.ZodArray<any>,\n schema: JSONSchema\n ): z.ZodTypeAny {\n // Handle minItems\n if (schema[\"minItems\"] !== undefined) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n arraySchema = arraySchema.min(schema[\"minItems\"]);\n }\n\n // Handle maxItems\n if (schema[\"maxItems\"] !== undefined) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n arraySchema = arraySchema.max(schema[\"maxItems\"]);\n }\n\n // Handle uniqueItems\n if (schema[\"uniqueItems\"]) {\n return arraySchema.refine(\n (items) => new Set(items).size === items.length,\n { message: \"Array items must be unique\" }\n );\n }\n\n return arraySchema;\n }\n\n /**\n * Parses an object schema.\n *\n * @param {JSONSchema} schema - The JSON schema for an object.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseObject(schema: JSONSchema): ZodTypeAny {\n // Handle conditional validation (if-then-else) first\n if (schema[\"if\"] && schema[\"then\"]) {\n return this.parseConditional(schema);\n }\n\n // Create shape object for Zod\n const shape: Record<string, ZodTypeAny> = {};\n\n // Process properties\n this.processObjectProperties(schema, shape);\n\n // Create the object schema and handle additionalProperties\n return this.processAdditionalProperties(schema, z.object(shape));\n }\n\n /**\n * Processes object properties and builds the shape object.\n *\n * @param {JSONSchema} schema - The JSON schema for an object.\n * @param {Record<string, ZodTypeAny>} shape - The shape object to populate.\n */\n private static processObjectProperties(\n schema: JSONSchema,\n shape: Record<string, ZodTypeAny>\n ): void {\n const required = new Set(schema.required || []);\n\n if (!schema.properties) {\n return;\n }\n\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n const zodSchema = this.parseSchema(propSchema);\n shape[key] = required.has(key) ? zodSchema : zodSchema.optional();\n }\n }\n\n /**\n * Processes additionalProperties configuration.\n *\n * @param {JSONSchema} schema - The JSON schema for an object.\n * @param {z.ZodObject<any, any>} objectSchema - The Zod object schema.\n * @returns {z.ZodObject<any, any>} - The updated Zod object schema.\n */\n private static processAdditionalProperties(\n schema: JSONSchema,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n objectSchema: z.ZodObject<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): z.ZodObject<any, any> {\n if (schema.additionalProperties === true) {\n return objectSchema.passthrough();\n } else if (\n schema.additionalProperties &&\n typeof schema.additionalProperties === \"object\"\n ) {\n // Handle schema for additional properties\n const additionalPropSchema = this.parseSchema(\n schema.additionalProperties\n );\n return objectSchema.catchall(additionalPropSchema);\n } else {\n return objectSchema.strict();\n }\n }\n\n /**\n * Parses a conditional schema with if-then-else.\n *\n * @param {JSONSchema} schema - The JSON schema with conditional validation.\n * @returns {ZodTypeAny} - The conditional Zod schema.\n */\n private static parseConditional(schema: JSONSchema): ZodTypeAny {\n // Create base object schema\n const zodObject = this.createBaseObjectSchema(schema);\n\n // Extract conditional parts\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const ifCondition = schema[\"if\"];\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const thenSchema = schema[\"then\"];\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const elseSchema = schema[\"else\"];\n\n // Apply conditional validation using superRefine\n return zodObject.superRefine((data, ctx) => {\n // Apply default values to data for condition checking\n const dataWithDefaults = this.applyDefaultValues(data, schema);\n\n // Apply appropriate validation based on condition\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n if (this.matchesCondition(dataWithDefaults, ifCondition)) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.validateConditionalSchema(dataWithDefaults, thenSchema, ctx);\n } else if (elseSchema) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.validateConditionalSchema(dataWithDefaults, elseSchema, ctx);\n }\n });\n }\n\n /**\n * Creates a base object schema from the given JSON schema.\n *\n * @param {JSONSchema} schema - The JSON schema.\n * @returns {z.ZodObject<any, any>} - The base Zod object schema.\n */\n private static createBaseObjectSchema(\n schema: JSONSchema\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): z.ZodObject<any, any> {\n const shape: Record<string, ZodTypeAny> = {};\n const required = new Set(schema.required || []);\n\n for (const [key, value] of Object.entries(schema.properties || {})) {\n const zodSchema = this.parseSchema(value);\n shape[key] = required.has(key) ? zodSchema : zodSchema.optional();\n }\n\n const zodObject = z.object(shape);\n return this.processAdditionalProperties(schema, zodObject);\n }\n\n /**\n * Applies default values from schema properties to data object.\n *\n * @param {JSONValue} data - The original data object.\n * @param {JSONSchema} schema - The schema with default values.\n * @returns {JSONValue} - The data object with defaults applied.\n */\n private static applyDefaultValues(\n data: JSONValue,\n schema: JSONSchema\n ): JSONValue {\n // If data is not an object or is null, we can't apply defaults\n if (typeof data !== \"object\" || data === null) {\n return data;\n }\n\n // If data is an array, we can't apply defaults from schema properties\n if (Array.isArray(data)) {\n return data;\n }\n\n // Now we know data is a JSONObject\n const objectData = data;\n const dataWithDefaults = { ...objectData };\n\n if (!schema.properties) {\n return dataWithDefaults;\n }\n\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n if (!(key in dataWithDefaults) && \"default\" in propSchema) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n dataWithDefaults[key] = propSchema[\"default\"];\n }\n }\n\n return dataWithDefaults;\n }\n\n /**\n * Parses a schema with combinators (oneOf, anyOf, allOf).\n * Delegates to the appropriate combinator parser based on which combinator is present.\n *\n * @param {JSONSchema} schema - The JSON schema with combinators.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseCombinator(schema: JSONSchema): ZodTypeAny {\n if (schema.oneOf) {\n return this.parseOneOf(schema.oneOf);\n }\n\n if (schema.anyOf) {\n return this.parseAnyOf(schema.anyOf);\n }\n\n if (schema.allOf) {\n return this.parseAllOf(schema.allOf);\n }\n\n // Should not reach here if schema has combinators\n throw new Error(\"Unsupported schema type\");\n }\n\n /**\n * Parses a oneOf combinator schema.\n *\n * @param {JSONSchema[]} schemas - Array of JSON schemas in the oneOf.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseOneOf(schemas: JSONSchema[]): ZodTypeAny {\n return this.createUnionFromSchemas(schemas);\n }\n\n /**\n * Parses an anyOf combinator schema.\n *\n * @param {JSONSchema[]} schemas - Array of JSON schemas in the anyOf.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseAnyOf(schemas: JSONSchema[]): ZodTypeAny {\n return this.createUnionFromSchemas(schemas);\n }\n\n /**\n * Creates a union from an array of schemas, handling special cases.\n *\n * @param {JSONSchema[]} schemas - Array of JSON schemas to create a union from.\n * @returns {ZodTypeAny} - The union Zod schema.\n */\n private static createUnionFromSchemas(schemas: JSONSchema[]): ZodTypeAny {\n // Handle empty array case\n if (schemas.length === 0) {\n return z.any();\n }\n\n // Handle single schema case\n if (schemas.length === 1) {\n return this.parseSchema(schemas[0] as JSONSchema);\n }\n\n // Process each subschema individually\n const zodSchemas: ZodTypeAny[] = [];\n\n for (const subSchema of schemas) {\n // Handle null type specially\n if (subSchema.type === \"null\") {\n zodSchemas.push(z.null());\n } else {\n zodSchemas.push(this.parseSchema(subSchema));\n }\n }\n\n // Return appropriate schema based on number of valid schemas\n if (zodSchemas.length >= 2) {\n return z.union(zodSchemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n } else if (zodSchemas.length === 1) {\n return zodSchemas[0] as ZodTypeAny;\n }\n\n // Fallback if no valid schemas were created\n return z.any();\n }\n\n /**\n * Parses an allOf combinator schema by merging all schemas.\n *\n * @param {JSONSchema[]} schemas - Array of JSON schemas in the allOf.\n * @returns {ZodTypeAny} - The ZodTypeAny schema.\n */\n private static parseAllOf(schemas: JSONSchema[]): ZodTypeAny {\n // Handle empty array case\n if (schemas.length === 0) {\n return z.any();\n }\n\n // Handle single schema case\n if (schemas.length === 1) {\n return this.parseSchema(schemas[0] as JSONSchema);\n }\n\n // Merge all schemas together\n const mergedSchema = schemas.reduce((acc, currentSchema) =>\n this.mergeSchemas(acc, currentSchema)\n );\n\n return this.parseSchema(mergedSchema);\n }\n\n /**\n * Merges two JSON schemas together.\n *\n * @param {JSONSchema} baseSchema - The base JSON schema.\n * @param {JSONSchema} addSchema - The JSON schema to add.\n * @returns {JSONSchema} - The merged JSON schema\n */\n private static mergeSchemas(\n baseSchema: JSONSchema,\n addSchema: JSONSchema\n ): JSONSchema {\n const merged: JSONSchema = { ...baseSchema, ...addSchema };\n if (baseSchema.properties && addSchema.properties) {\n const mergedProperties = {\n ...baseSchema.properties,\n ...addSchema.properties,\n };\n merged.properties = mergedProperties;\n }\n if (baseSchema.required && addSchema.required) {\n const mergedRequired = [\n ...new Set([...baseSchema.required, ...addSchema.required]),\n ];\n merged.required = mergedRequired;\n }\n return merged;\n }\n}\n","import { type AiAdapter } from \"@inngest/ai\";\nimport { adapters } from \"./adapters\";\nimport { type Message } from \"./state\";\nimport { type Tool } from \"./tool\";\nimport { getStepTools } from \"./util\";\n\nexport const createAgenticModelFromAiAdapter = <\n TAiAdapter extends AiAdapter.Any,\n>(\n adapter: TAiAdapter\n): AgenticModel<TAiAdapter> => {\n const opts = adapters[adapter.format as AiAdapter.Format];\n\n return new AgenticModel({\n model: adapter,\n requestParser:\n opts.request as unknown as AgenticModel.RequestParser<TAiAdapter>,\n responseParser:\n opts.response as unknown as AgenticModel.ResponseParser<TAiAdapter>,\n });\n};\n\nexport class AgenticModel<TAiAdapter extends AiAdapter.Any> {\n #model: TAiAdapter;\n requestParser: AgenticModel.RequestParser<TAiAdapter>;\n responseParser: AgenticModel.ResponseParser<TAiAdapter>;\n\n constructor({\n model,\n requestParser,\n responseParser,\n }: AgenticModel.Constructor<TAiAdapter>) {\n this.#model = model;\n this.requestParser = requestParser;\n this.responseParser = responseParser;\n }\n\n async infer(\n stepID: string,\n input: Message[],\n tools: Tool.Any[],\n tool_choice: Tool.Choice\n ): Promise<AgenticModel.InferenceResponse> {\n const body = this.requestParser(this.#model, input, tools, tool_choice);\n let result: AiAdapter.Input<TAiAdapter>;\n\n const step = await getStepTools();\n\n if (step) {\n result = (await step.ai.infer(stepID, {\n model: this.#model,\n body,\n })) as AiAdapter.Input<TAiAdapter>;\n } else {\n // Allow the model to mutate options and body for this call\n const modelCopy = { ...this.#model };\n this.#model.onCall?.(modelCopy, body);\n\n const url = new URL(modelCopy.url || \"\");\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n // Make sure we handle every known format in `@inngest/ai`.\n const formatHandlers: Record<AiAdapter.Format, () => void> = {\n \"openai-chat\": () => {\n headers[\"Authorization\"] = `Bearer ${modelCopy.authKey}`;\n },\n anthropic: () => {\n headers[\"x-api-key\"] = modelCopy.authKey;\n headers[\"anthropic-version\"] = \"2023-06-01\";\n },\n gemini: () => {},\n grok: () => {},\n };\n\n formatHandlers[modelCopy.format as AiAdapter.Format]();\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n result = await (\n await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n })\n ).json();\n }\n\n return { output: this.responseParser(result), raw: result };\n }\n}\n\nexport namespace AgenticModel {\n export type Any = AgenticModel<AiAdapter.Any>;\n\n /**\n * InferenceResponse is the response from a model for an inference request.\n * This contains parsed messages and the raw result, with the type of the raw\n * result depending on the model's API repsonse.\n */\n export type InferenceResponse<T = unknown> = {\n output: Message[];\n raw: T;\n };\n\n export interface Constructor<TAiAdapter extends AiAdapter.Any> {\n model: TAiAdapter;\n requestParser: RequestParser<TAiAdapter>;\n responseParser: ResponseParser<TAiAdapter>;\n }\n\n export type RequestParser<TAiAdapter extends AiAdapter.Any> = (\n model: TAiAdapter,\n state: Message[],\n tools: Tool.Any[],\n tool_choice: Tool.Choice\n ) => AiAdapter.Input<TAiAdapter>;\n\n export type ResponseParser<TAiAdapter extends AiAdapter.Any> = (\n output: AiAdapter.Output<TAiAdapter>\n ) => Message[];\n}\n","import { type AiAdapter, type AiAdapters } from \"@inngest/ai\";\nimport { type AgenticModel } from \"../model\";\nimport * as anthropic from \"./anthropic\";\nimport * as openai from \"./openai\";\nimport * as gemini from \"./gemini\";\nimport * as grok from \"./grok\";\n\nexport type Adapters = {\n [Format in AiAdapter.Format]: {\n request: AgenticModel.RequestParser<AiAdapters[Format]>;\n response: AgenticModel.ResponseParser<AiAdapters[Format]>;\n };\n};\n\nexport const adapters: Adapters = {\n \"openai-chat\": {\n request: openai.requestParser,\n response: openai.responseParser,\n },\n anthropic: {\n request: anthropic.requestParser,\n response: anthropic.responseParser,\n },\n gemini: {\n request: gemini.requestParser,\n response: gemini.responseParser,\n },\n grok: {\n request: grok.requestParser,\n response: grok.responseParser,\n },\n};\n","/**\n * Adapters for Anthropic I/O to transform to/from internal network messages.\n *\n * @module\n */\nimport {\n type AiAdapter,\n type Anthropic,\n type AnthropicAiAdapter,\n} from \"@inngest/ai\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { z } from \"zod\";\nimport { type AgenticModel } from \"../model\";\nimport { type Message, type TextMessage } from \"../state\";\nimport { type Tool } from \"../tool\";\n\n/**\n * Parse a request from internal network messages to an Anthropic input.\n */\nexport const requestParser: AgenticModel.RequestParser<Anthropic.AiModel> = (\n model,\n messages,\n tools,\n tool_choice = \"auto\"\n) => {\n // Note that Anthropic has a top-level system prompt, then a series of prompts\n // for assistants and users.\n const systemMessage = messages.find(\n (m) => m.role === \"system\" && m.type === \"text\"\n ) as TextMessage;\n const system =\n typeof systemMessage?.content === \"string\" ? systemMessage.content : \"\";\n\n const anthropicMessages: AiAdapter.Input<Anthropic.AiModel>[\"messages\"] =\n messages\n .filter((m) => m.role !== \"system\")\n .reduce(\n (acc, m) => {\n switch (m.type) {\n case \"text\":\n return [\n ...acc,\n {\n role: m.role,\n content: Array.isArray(m.content)\n ? m.content.map((text) => ({ type: \"text\", text }))\n : m.content,\n },\n ] as AiAdapter.Input<Anthropic.AiModel>[\"messages\"];\n case \"tool_call\":\n return [\n ...acc,\n {\n role: m.role,\n content: m.tools.map((tool) => ({\n type: \"tool_use\",\n id: tool.id,\n input: tool.input,\n name: tool.name,\n })),\n },\n ];\n case \"tool_result\":\n return [\n ...acc,\n {\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: m.tool.id,\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n },\n ],\n },\n ];\n }\n },\n [] as AiAdapter.Input<Anthropic.AiModel>[\"messages\"]\n );\n\n // We need to patch the last message if it's an assistant message. This is a known limitation of Anthropic's API.\n // cf: https://github.com/langchain-ai/langgraph/discussions/952#discussioncomment-10012320\n const lastMessage = anthropicMessages[anthropicMessages.length - 1];\n if (lastMessage?.role === \"assistant\") {\n lastMessage.role = \"user\";\n }\n\n const request: AiAdapter.Input<Anthropic.AiModel> = {\n system,\n model: model.options.model,\n max_tokens: model.options.defaultParameters.max_tokens,\n messages: anthropicMessages,\n };\n\n if (tools?.length) {\n request.tools = tools.map((t) => {\n return {\n name: t.name,\n description: t.description,\n input_schema: (t.parameters\n ? zodToJsonSchema(t.parameters)\n : zodToJsonSchema(\n z.object({})\n )) as AnthropicAiAdapter.Tool.InputSchema,\n };\n });\n request.tool_choice = toolChoice(tool_choice);\n }\n\n return request;\n};\n\n/**\n * Parse a response from Anthropic output to internal network messages.\n */\nexport const responseParser: AgenticModel.ResponseParser<Anthropic.AiModel> = (\n input\n) => {\n if (input.type === \"error\") {\n throw new Error(\n input.error?.message ||\n `Anthropic request failed: ${JSON.stringify(input.error)}`\n );\n }\n\n return (input?.content ?? []).reduce<Message[]>((acc, item) => {\n if (!item.type) {\n return acc;\n }\n\n switch (item.type) {\n case \"text\":\n return [\n ...acc,\n {\n type: \"text\",\n role: input.role,\n content: item.text,\n // XXX: Better stop reason parsing\n stop_reason: \"stop\",\n },\n ];\n case \"tool_use\": {\n let args;\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n args =\n typeof item.input === \"string\"\n ? JSON.parse(item.input)\n : item.input;\n } catch {\n args = item.input;\n }\n\n return [\n ...acc,\n {\n type: \"tool_call\",\n role: input.role,\n stop_reason: \"tool\",\n tools: [\n {\n type: \"tool\",\n id: item.id,\n name: item.name,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n input: args,\n },\n ],\n },\n ];\n }\n }\n }, []);\n};\n\nconst toolChoice = (\n choice: Tool.Choice\n): AiAdapter.Input<Anthropic.AiModel>[\"tool_choice\"] => {\n switch (choice) {\n case \"auto\":\n return { type: \"auto\" };\n case \"any\":\n return { type: \"any\" };\n default:\n if (typeof choice === \"string\") {\n return {\n type: \"tool\",\n name: choice as string,\n };\n }\n }\n};\n","import { type GetStepTools, type Inngest } from \"inngest\";\nimport { type output as ZodOutput } from \"zod\";\nimport { type Agent } from \"./agent\";\nimport { type NetworkRun } from \"./networkRun\";\nimport { type AnyZodType, type MaybePromise } from \"./util\";\n\nexport type Tool<TInput extends Tool.Input> = {\n name: string;\n description?: string;\n parameters?: TInput;\n\n // mcp lists the MCP details for this tool, if this tool is provided by an\n // MCP server.\n mcp?: {\n server: MCP.Server;\n tool: MCP.Tool;\n };\n\n strict?: boolean;\n\n // TODO: Handler input types based off of JSON above.\n //\n // Handlers get their input arguments from inference calls, and can also\n // access the current agent and network. This allows tools to reference and\n // schedule future work via the network, if necessary.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (input: ZodOutput<TInput>, opts: Tool.Options) => MaybePromise<any>;\n};\n\nexport namespace Tool {\n export type Any = Tool<Tool.Input>;\n\n export type Options = {\n agent: Agent;\n network?: NetworkRun;\n step?: GetStepTools<Inngest.Any>;\n };\n\n export type Input = AnyZodType;\n\n export type Choice = \"auto\" | \"any\" | (string & {});\n}\n\nexport namespace MCP {\n export type Server = {\n // name is a short name for the MCP server, eg. \"github\". This allows\n // us to namespace tools for each MCP server.\n name: string;\n transport: TransportSSE | TransportWebsocket;\n };\n\n export type Transport = TransportSSE | TransportWebsocket;\n\n export type TransportSSE = {\n type: \"sse\";\n url: string;\n eventSourceInit?: EventSourceInit;\n requestInit?: RequestInit;\n };\n\n export type TransportWebsocket = {\n type: \"ws\";\n url: string;\n };\n\n export type Tool = {\n name: string;\n description?: string;\n inputSchema?: {\n type: \"object\";\n properties?: unknown;\n };\n };\n}\n","import { type AiAdapter } from \"@inngest/ai\";\nimport { z } from \"zod\";\nimport {\n createRoutingAgent,\n createTool,\n type Agent,\n type RoutingAgent,\n} from \"./agent\";\nimport { NetworkRun } from \"./networkRun\";\nimport { State, type InferenceResult } from \"./state\";\nimport { type MaybePromise } from \"./util\";\n\n/**\n * Network represents a network of agents.\n */\nexport const createNetwork = (opts: Network.Constructor) => new Network(opts);\n\n/**\n * Network represents a network of agents.\n */\nexport class Network {\n /**\n * The name for the system of agents\n */\n name: string;\n\n description?: string;\n\n /**\n * agents are all publicly available agents in the netwrok\n */\n agents: Map<string, Agent>;\n\n /**\n * state is the entire agent's state.\n */\n defaultState?: State;\n\n /**\n * defaultModel is the default model to use with the network. This will not\n * override an agent's specific model if the agent already has a model defined\n * (eg. via withModel or via its constructor).\n */\n defaultModel?: AiAdapter.Any;\n\n defaultRouter?: Network.Router;\n\n /**\n * maxIter is the maximum number of times the we can call agents before ending\n * the network's run loop.\n */\n maxIter: number;\n\n // _stack is an array of strings, each representing an agent name to call.\n protected _stack: string[];\n\n protected _counter = 0;\n\n // _agents atores all egents. note that you may not include eg. the\n // defaultRoutingAgent within the network constructor, and you may return an\n // agent in the router that's not included. This is okay; we store all\n // agents referenced in the router here.\n protected _agents: Map<string, Agent>;\n\n constructor({\n name,\n description,\n agents,\n defaultModel,\n maxIter,\n defaultState,\n defaultRouter,\n }: Network.Constructor) {\n this.name = name;\n this.description = description;\n this.agents = new Map();\n this._agents = new Map();\n this.defaultModel = defaultModel;\n this.defaultRouter = defaultRouter;\n this.maxIter = maxIter || 0;\n this._stack = [];\n\n if (defaultState) {\n this.defaultState = defaultState;\n }\n\n for (const agent of agents) {\n // Store all agents publicly visible.\n this.agents.set(agent.name, agent);\n // Store an internal map of all agents referenced.\n this._agents.set(agent.name, agent);\n }\n }\n\n async availableAgents(\n networkRun: NetworkRun = new NetworkRun(this, new State())\n ): Promise<Agent[]> {\n const available: Agent[] = [];\n const all = Array.from(this.agents.values());\n for (const a of all) {\n const enabled = a?.lifecycles?.enabled;\n if (!enabled || (await enabled({ agent: a, network: networkRun }))) {\n available.push(a);\n }\n }\n return available;\n }\n\n /**\n * addAgent adds a new agent to the network.\n */\n addAgent(agent: Agent) {\n this.agents.set(agent.name, agent);\n }\n\n /**\n * run handles a given request using the network of agents. It is not\n * concurrency-safe; you can only call run on a network once, as networks are\n * stateful.\n *\n */\n public run(...[input, overrides]: Network.RunArgs): Promise<NetworkRun> {\n let state: State;\n if (overrides?.state) {\n if (overrides.state instanceof State) {\n state = overrides.state;\n } else {\n state = new State(overrides.state);\n }\n } else {\n state = this.defaultState?.clone() || new State();\n }\n\n return new NetworkRun(this, state)[\"execute\"](input, overrides);\n }\n}\n\n/**\n * defaultRoutingAgent is an AI agent that selects the appropriate agent from\n * the network to handle the incoming request.\n *\n * It is no set model and so relies on the presence of a default model in the\n * network or being explicitly given one.\n */\nlet defaultRoutingAgent: RoutingAgent | undefined;\nexport const getDefaultRoutingAgent = () => {\n defaultRoutingAgent ??= createRoutingAgent({\n name: \"Default routing agent\",\n\n description:\n \"Selects which agents to work on based off of the current prompt and input.\",\n\n lifecycle: {\n onRoute: ({ result }) => {\n const tool = result.toolCalls[0];\n if (!tool) {\n return;\n }\n if (\n typeof tool.content === \"object\" &&\n tool.content !== null &&\n \"data\" in tool.content &&\n typeof tool.content.data === \"string\"\n ) {\n return [tool.content.data];\n }\n return;\n },\n },\n\n tools: [\n // This tool does nothing but ensure that the model responds with the\n // agent name as valid JSON.\n createTool({\n name: \"select_agent\",\n description:\n \"select an agent to handle the input, based off of the current conversation\",\n parameters: z\n .object({\n name: z\n .string()\n .describe(\"The name of the agent that should handle the request\"),\n })\n .strict(),\n handler: ({ name }, { network }) => {\n if (!network) {\n throw new Error(\n \"The routing agent can only be used within a network of agents\"\n );\n }\n\n if (typeof name !== \"string\") {\n throw new Error(\"The routing agent requested an invalid agent\");\n }\n\n const agent = network.agents.get(name);\n if (agent === undefined) {\n throw new Error(\n `The routing agent requested an agent that doesn't exist: ${name}`\n );\n }\n\n // This returns the agent name to call. The default routing functon\n // schedules this agent by inpsecting this name via the tool call output.\n return agent.name;\n },\n }),\n ],\n\n tool_choice: \"select_agent\",\n\n system: async ({ network }): Promise<string> => {\n if (!network) {\n throw new Error(\n \"The routing agent can only be used within a network of agents\"\n );\n }\n\n const agents = await network?.availableAgents();\n\n return `You are the orchestrator between a group of agents. Each agent is suited for a set of specific tasks, and has a name, instructions, and a set of tools.\n\nThe following agents are available:\n<agents>\n ${agents\n .map((a) => {\n return `\n <agent>\n <name>${a.name}</name>\n <description>${a.description}</description>\n <tools>${JSON.stringify(Array.from(a.tools.values()))}</tools>\n </agent>`;\n })\n .join(\"\\n\")}\n</agents>\n\nFollow the set of instructions:\n\n<instructions>\n Think about the current history and status. Determine which agent to use to handle the user's request, based off of the current agents and their tools.\n\n Your aim is to thoroughly complete the request, thinking step by step, choosing the right agent based off of the context.\n</instructions>\n `;\n },\n });\n\n return defaultRoutingAgent;\n};\n\nexport namespace Network {\n export type Constructor = {\n name: string;\n description?: string;\n agents: Agent[];\n defaultModel?: AiAdapter.Any;\n maxIter?: number;\n // state is any pre-existing network state to use in this Network instance. By\n // default, new state is created without any history for every Network.\n defaultState?: State;\n defaultRouter?: Router;\n };\n\n export type RunArgs = [\n input: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n overrides?: { router?: Router; state?: State | Record<string, any> },\n ];\n\n /**\n * Router defines how a network coordinates between many agents. A router is\n * either a RoutingAgent which uses inference calls to choose the next Agent,\n * or a function which chooses the next Agent to call.\n *\n * The function gets given the network, current state, future\n * agentic calls, and the last inference result from the network.\n *\n */\n export type Router = RoutingAgent | Router.FnRouter;\n\n export namespace Router {\n /**\n * FnRouter defines a function router which returns an Agent, an AgentRouter, or\n * undefined if the network should stop.\n *\n * If the FnRouter returns an AgentRouter (an agent with the .route function),\n * the agent will first be ran, then the `.route` function will be called.\n *\n */\n export type FnRouter = (\n args: Args\n ) => MaybePromise<RoutingAgent | Agent | Agent[] | undefined>;\n\n export interface Args {\n /**\n * input is the input called to the network\n */\n input: string;\n\n /**\n * Network is the network that this router is coordinating. Network state\n * is accessible via `network.state`.\n */\n network: NetworkRun;\n\n /**\n * stack is an ordered array of agents that will be called next.\n */\n stack: Agent[];\n\n /**\n * callCount is the number of current agent invocations that the network\n * has made. This is a shorthand for `network.state.results.length`.\n */\n callCount: number;\n\n /**\n * lastResult is the last inference result that the network made. This is\n * a shorthand for `network.state.results.pop()`.\n */\n lastResult?: InferenceResult;\n }\n }\n}\n","import { type Inngest } from \"inngest\";\nimport { InngestFunction } from \"inngest/components/InngestFunction\";\nimport { getAsyncCtx, type AsyncContext } from \"inngest/experimental\";\nimport { ZodType, type ZodObject, type ZodTypeAny } from \"zod\";\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/**\n * AnyZodType is a type alias for any Zod type.\n *\n * It specifically matches the typing used for the OpenAI JSON schema typings,\n * which do not use the standardized `z.ZodTypeAny` type.\n *\n * Not that using this type directly can break between any versions of Zod\n * (including minor and patch versions). It may be pertinent to maintain a\n * custom type which matches many versions in the future.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyZodType = ZodType<any> | ZodTypeAny;\n\n/**\n * Given an unknown value, return a string representation of the error if it is\n * an error, otherwise return the stringified value.\n */\nexport const stringifyError = (e: unknown): string => {\n if (e instanceof Error) {\n return e.message;\n }\n\n return String(e);\n};\n\n/**\n * Attempts to retrieve the step tools from the async context.\n */\nexport const getStepTools = async (): Promise<\n AsyncContext[\"ctx\"][\"step\"] | undefined\n> => {\n const asyncCtx = await getAsyncCtx();\n\n return asyncCtx?.ctx.step;\n};\n\nexport const isInngestFn = (fn: unknown): fn is InngestFunction.Any => {\n // Derivation of `InngestFunction` means it's definitely correct\n if (fn instanceof InngestFunction) {\n return true;\n }\n\n // If it's not derived from `InngestFunction`, it could still be a function\n // but from a different version of the library. Depending on your other deps\n // this could be likely and multiple versions of the `inngest` package are\n // installed at the same time. Thus, we check the generic shape here instead.\n if (\n typeof fn === \"object\" &&\n fn !== null &&\n \"createExecution\" in fn &&\n typeof fn.createExecution === \"function\"\n ) {\n return true;\n }\n\n return false;\n};\n\nexport const getInngestFnInput = (\n fn: InngestFunction.Any\n): AnyZodType | undefined => {\n const runtimeSchemas = (fn[\"client\"] as Inngest.Any)[\"schemas\"]?.[\n \"runtimeSchemas\"\n ];\n if (!runtimeSchemas) {\n return;\n }\n\n const schemasToAttempt = new Set<string>(\n (fn[\"opts\"] as InngestFunction.Options).triggers?.reduce((acc, trigger) => {\n if (trigger.event) {\n return [...acc, trigger.event];\n }\n\n return acc;\n }, [] as string[]) ?? []\n );\n\n if (!schemasToAttempt.size) {\n return;\n }\n\n let schema: AnyZodType | undefined;\n\n for (const eventSchema of schemasToAttempt) {\n const runtimeSchema = runtimeSchemas[eventSchema];\n\n // We only support Zod atm\n if (\n typeof runtimeSchema === \"object\" &&\n runtimeSchema !== null &&\n \"data\" in runtimeSchema &&\n helpers.isZodObject(runtimeSchema.data)\n ) {\n if (schema) {\n schema = schema.or(runtimeSchema.data);\n } else {\n schema = runtimeSchema.data;\n }\n continue;\n }\n\n // TODO It could also be a regular object with inidivudal fields, so\n // validate that too\n }\n\n return schema;\n};\n\nconst helpers = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n isZodObject: (value: unknown): value is ZodObject<any> => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return value instanceof ZodType && value._def.typeName === \"ZodObject\";\n },\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n isObject: (value: unknown): value is Record<string, any> => {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n },\n};\n","/**\n * Adapters for OpenAI I/O to transform to/from internal network messages.\n *\n * @module\n */\n\nimport { type AiAdapter, type OpenAi } from \"@inngest/ai\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { type AgenticModel } from \"../model\";\nimport {\n type Message,\n type TextMessage,\n type ToolCallMessage,\n type ToolMessage,\n} from \"../state\";\nimport { type Tool } from \"../tool\";\nimport { stringifyError } from \"../util\";\n\n/**\n * Parse a request from internal network messages to an OpenAI input.\n */\nexport const requestParser: AgenticModel.RequestParser<OpenAi.AiModel> = (\n model,\n messages,\n tools,\n tool_choice = \"auto\"\n) => {\n const request: AiAdapter.Input<OpenAi.AiModel> = {\n messages: messages.map((m) => {\n switch (m.type) {\n case \"text\":\n return {\n role: m.role,\n content: m.content,\n };\n case \"tool_call\":\n return {\n role: \"assistant\",\n content: null,\n tool_calls: m.tools\n ? m.tools?.map((tool) => ({\n id: tool.id,\n type: \"function\",\n function: {\n name: tool.name,\n arguments: JSON.stringify(tool.input),\n },\n }))\n : undefined,\n };\n case \"tool_result\":\n return {\n role: \"tool\",\n tool_call_id: m.tool.id,\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n };\n }\n }) as AiAdapter.Input<OpenAi.AiModel>[\"messages\"],\n };\n\n if (tools?.length) {\n request.tool_choice = toolChoice(tool_choice);\n // OpenAI o3 models have several issues with tool calling.\n // one of them is not supporting the `parallel_tool_calls` parameter\n // https://community.openai.com/t/o3-mini-api-with-tools-only-ever-returns-1-tool-no-matter-prompt/1112390/6\n if (\n !model.options.model?.includes(\"o3\") &&\n !model.options.model?.includes(\"o1\")\n ) {\n // it is recommended to disable parallel tool calls with structured output\n // https://platform.openai.com/docs/guides/function-calling#parallel-function-calling-and-structured-outputs\n request.parallel_tool_calls = false;\n }\n request.tools = tools.map((t) => {\n return {\n type: \"function\",\n function: {\n name: t.name,\n description: t.description,\n parameters:\n t.parameters && zodToJsonSchema(t.parameters, { target: \"openAi\" }),\n strict:\n typeof t.strict !== \"undefined\" ? t.strict : Boolean(t.parameters), // strict mode is only supported with parameters\n },\n };\n });\n }\n\n return request;\n};\n\n/**\n * Parse a response from OpenAI output to internal network messages.\n */\nexport const responseParser: AgenticModel.ResponseParser<OpenAi.AiModel> = (\n input\n) => {\n if (input.error) {\n throw new Error(\n input.error.message ||\n `OpenAI request failed: ${JSON.stringify(input.error)}`\n );\n }\n\n return (input?.choices ?? []).reduce<Message[]>((acc, choice) => {\n const { message, finish_reason } = choice;\n if (!message) {\n return acc;\n }\n\n const base = {\n role: choice.message.role,\n stop_reason:\n openAiStopReasonToStateStopReason[finish_reason ?? \"\"] || \"stop\",\n };\n\n if (message.content) {\n return [\n ...acc,\n {\n ...base,\n type: \"text\",\n content: message.content,\n } as TextMessage,\n ];\n }\n if (message.tool_calls.length > 0) {\n return [\n ...acc,\n {\n ...base,\n type: \"tool_call\",\n tools: message.tool_calls.map((tool) => {\n return {\n type: \"tool\",\n id: tool.id,\n name: tool.function.name,\n function: tool.function.name,\n input: safeParseOpenAIJson(tool.function.arguments || \"{}\"),\n } as ToolMessage;\n }),\n } as ToolCallMessage,\n ];\n }\n return acc;\n }, []);\n};\n\n/**\n * Parse the given `str` `string` as JSON, also handling backticks, a common\n * OpenAI quirk.\n *\n * @example Input\n * ```\n * \"{\\n \\\"files\\\": [\\n {\\n \\\"filename\\\": \\\"fibo.ts\\\",\\n \\\"content\\\": `\\nfunction fibonacci(n: number): number {\\n if (n < 2) {\\n return n;\\n } else {\\n return fibonacci(n - 1) + fibonacci(n - 2);\\n }\\n}\\n\\nexport default fibonacci;\\n`\\n }\\n ]\\n}\"\n * ```\n */\nconst safeParseOpenAIJson = (str: string): unknown => {\n // Remove any leading/trailing quotes if present\n const trimmed = str.replace(/^[\"']|[\"']$/g, \"\");\n\n try {\n // First try direct JSON parse\n return JSON.parse(trimmed);\n } catch {\n try {\n // Replace backtick strings with regular JSON strings\n // Match content between backticks, preserving newlines\n const withQuotes = trimmed.replace(/`([\\s\\S]*?)`/g, (_, content) =>\n JSON.stringify(content)\n );\n return JSON.parse(withQuotes);\n } catch (e) {\n throw new Error(\n `Failed to parse JSON with backticks: ${stringifyError(e)}`\n );\n }\n }\n};\n\nconst openAiStopReasonToStateStopReason: Record<string, string> = {\n tool_calls: \"tool\",\n stop: \"stop\",\n length: \"stop\",\n content_filter: \"stop\",\n function_call: \"tool\",\n};\n\nconst toolChoice = (choice: Tool.Choice) => {\n switch (choice) {\n case \"auto\":\n return \"auto\";\n case \"any\":\n return \"required\";\n default:\n return {\n type: \"function\" as const,\n function: { name: choice as string },\n };\n }\n};\n","/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/**\n * Adapters for Gemini I/O to transform to/from internal network messages.\n *\n * @module\n */\nimport { type AiAdapter, type Gemini } from \"@inngest/ai\";\nimport { z, type ZodSchema } from \"zod\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\n\nimport { type AgenticModel } from \"../model\";\nimport type { Tool } from \"../tool\";\nimport type { Message, TextContent } from \"../state\";\n\n/**\n * Parse a request from internal network messages to an Gemini input.\n */\nexport const requestParser: AgenticModel.RequestParser<Gemini.AiModel> = (\n _model,\n messages,\n tools,\n tool_choice = \"auto\"\n) => {\n const contents = messages.map((m) => messageToContent(m));\n\n const functionDeclarations = tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters\n ? geminiZodToJsonSchema(t.parameters)\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (geminiZodToJsonSchema(z.object({})) as any),\n }));\n\n return {\n contents,\n tools: [\n {\n functionDeclarations,\n },\n ],\n tool_config: toolChoice(tool_choice),\n };\n};\n\nconst messageContentToString = (content: string | TextContent[]): string => {\n if (typeof content === \"string\") {\n return content;\n }\n return content.map((c) => c.text).join(\"\");\n};\n\n/**\n * Parse a response from Gemini output to internal network messages.\n */\nexport const responseParser: AgenticModel.ResponseParser<Gemini.AiModel> = (\n input\n) => {\n if (input.error) {\n throw new Error(\n input.error?.message ||\n `Gemini request failed: ${JSON.stringify(input.error)}`\n );\n }\n\n const messages: Message[] = [];\n\n for (const candidate of input.candidates ?? []) {\n for (const content of candidate.content.parts) {\n // user text\n if (candidate.content.role === \"user\" && \"text\" in content) {\n messages.push({\n role: \"user\",\n type: \"text\",\n content: content.text,\n });\n }\n // assistant text\n else if (candidate.content.role === \"model\" && \"text\" in content) {\n messages.push({\n role: \"assistant\",\n type: \"text\",\n content: content.text,\n });\n }\n // tool call\n else if (\n candidate.content.role === \"model\" &&\n \"functionCall\" in content\n ) {\n messages.push({\n role: \"assistant\",\n type: \"tool_call\",\n stop_reason: \"tool\",\n tools: [\n {\n name: content.functionCall.name,\n input: content.functionCall.args,\n type: \"tool\",\n id: content.functionCall.name,\n },\n ],\n });\n }\n // tool result\n else if (\n candidate.content.role === \"user\" &&\n \"functionResponse\" in content\n ) {\n messages.push({\n role: \"tool_result\",\n type: \"tool_result\",\n stop_reason: \"tool\",\n tool: {\n name: content.functionResponse.name,\n input: content.functionResponse.response,\n type: \"tool\",\n id: content.functionResponse.name,\n },\n content: JSON.stringify(content.functionResponse.response),\n });\n } else {\n throw new Error(\"Unknown content type\");\n }\n }\n }\n\n return messages;\n};\n\nconst messageToContent = (\n m: Message\n): AiAdapter.Input<Gemini.AiModel>[\"contents\"][0] => {\n switch (m.role) {\n case \"system\":\n return {\n role: \"user\",\n parts: [{ text: messageContentToString(m.content) }],\n };\n case \"user\":\n switch (m.type) {\n case \"tool_call\":\n if (m.tools.length === 0) {\n throw new Error(\"Tool call message must have at least one tool\");\n }\n // Note: multiple tools is only supported over WS (Compositional function calling)\n return {\n role: \"model\",\n parts: [\n {\n functionCall: {\n name: m.tools[0]!.name,\n args: m.tools[0]!.input,\n },\n },\n ],\n };\n case \"text\":\n default:\n return {\n role: \"user\",\n parts: [{ text: messageContentToString(m.content) }],\n };\n }\n case \"assistant\":\n switch (m.type) {\n case \"tool_call\":\n if (m.tools.length === 0) {\n throw new Error(\"Tool call message must have at least one tool\");\n }\n // Note: multiple tools is only supported over WS (Compositional function calling)\n return {\n role: \"model\",\n parts: [\n {\n functionCall: {\n name: m.tools[0]!.name,\n args: m.tools[0]!.input,\n },\n },\n ],\n };\n case \"text\":\n default:\n return {\n role: \"model\",\n parts: [{ text: messageContentToString(m.content) }],\n };\n }\n case \"tool_result\":\n return {\n role: \"user\",\n parts: [\n {\n functionResponse: {\n name: m.tool.name,\n response: {\n name: m.tool.name,\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n },\n },\n },\n ],\n };\n default:\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n throw new Error(`Unknown message role: ${(m as any).role}`);\n }\n};\n\nconst toolChoice = (\n choice: Tool.Choice\n): AiAdapter.Input<Gemini.AiModel>[\"toolConfig\"] => {\n switch (choice) {\n case \"auto\":\n return {\n functionCallingConfig: {\n mode: \"AUTO\",\n },\n };\n case \"any\":\n return {\n functionCallingConfig: {\n mode: \"ANY\",\n },\n };\n default:\n if (typeof choice === \"string\") {\n return {\n functionCallingConfig: {\n mode: \"ANY\",\n allowedFunctionNames: [choice],\n },\n };\n }\n }\n};\n\nconst geminiZodToJsonSchema = (zod: ZodSchema) => {\n const schema = zodToJsonSchema(zod, { target: \"openApi3\" });\n // @ts-expect-error this prop does exists and Gemini don't like it\n delete schema[\"additionalProperties\"];\n return schema;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,kBAAuD;AACvD,kBAAoD;;;ACDpD,IAAAC,aAA+B;AAC/B,oBAAoC;AACpC,iBAAmC;AACnC,uBAAyC;AACzC,uBAA+B;AAC/B,mBAAsC;AACtC,yBAA4B;AAC5B,IAAAC,kBAAgD;AAChD,IAAAC,0BAAqC;AACrC,oBAA+B;AAC/B,IAAAC,gBAAyC;;;ACVzC,iBAAmD;;;ACAnD,IAAAC,aAA+B;;;ACA/B,IAAAC,aAAgD;;;ACKhD,IAAAC,aAIO;AACP,gCAAgC;AAChC,IAAAC,cAAkB;;;ACXlB,IAAAC,kBAAgD;AAChD,IAAAC,cAAyC;;;ACDzC,gBAA+B;AAC/B,IAAAC,cAAkB;;;ACDlB,qBAA6B;AAC7B,6BAAgC;AAChC,0BAA+C;AAC/C,IAAAC,cAAyD;;;ACGzD,IAAAC,aAA4C;AAC5C,IAAAC,6BAAgC;;;ACAhC,IAAAC,aAA4C;AAC5C,IAAAC,cAAkC;AAClC,IAAAC,6BAAgC;;;AVczB,IAAM,eAAe,CAAC;AAAA,EAC3B,QAAQ;AAAA,EACR,WAAW,CAAC;AAAA,EACZ,SAAS,CAAC;AAAA,EACV;AAAA,EACA,WAAW,YAAY,CAAC;AAC1B,MAMM;AACJ,QAAM,UAAU,0BAAU,IAAI,wBAAQ,EAAE,IAAI,MAAM,CAAC;AAEnD,QAAM,YAAY,UAAU;AAAA,IAC1B,CAAC,KAAK,OAAO;AACX,aAAO,iCACF,MADE;AAAA,QAEL,CAAC,GAAG,GAAG,CAAC,GAAG;AAAA,MACb;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,aAAW,SAAS,QAAQ;AAC1B,UAAM,WAAO,yBAAQ,MAAM,IAAI;AAC/B,UAAM,KAAK,SAAS,IAAI;AAExB,cAAU,EAAE,IAAI,QAAQ;AAAA,MACtB,EAAE,IAAI,MAAM,MAAM,KAAK;AAAA,MACvB,EAAE,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,GAAG;AAAA,MAC/B,OAAO,EAAE,MAAM,MAAM;AAEnB,eAAO,MAAM,IAAI,MAAM,KAAK,KAAK;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAEA,aAAW,WAAW,UAAU;AAC9B,UAAM,WAAO,yBAAQ,QAAQ,IAAI;AACjC,UAAM,KAAK,WAAW,IAAI;AAE1B,cAAU,EAAE,IAAI,QAAQ;AAAA,MACtB,EAAE,IAAI,MAAM,QAAQ,KAAK;AAAA,MACzB,EAAE,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,GAAG;AAAA,MAC/B,OAAO,EAAE,MAAM,MAAM;AAEnB,eAAO,QAAQ,IAAI,MAAM,KAAK,KAAK;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAEA,aAAO,YAAAC,cAAoB;AAAA,IACzB,QAAQ;AAAA,IACR,WAAW,OAAO,OAAO,SAAS;AAAA,EACpC,CAAC;AACH;","names":["import_inngest","import_ai","import_inngest","import_InngestFunction","import_types","import_ai","import_ai","import_ai","import_zod","import_inngest","import_zod","import_zod","import_zod","import_ai","import_zod_to_json_schema","import_ai","import_zod","import_zod_to_json_schema","createInngestServer"]}