@inngest/agent-kit
Version:
AgentKit is a framework for creating and orchestrating AI agents and AI workflows
1 lines • 75.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/server.ts","../src/agent.ts","../src/model.ts","../src/adapters/index.ts","../src/adapters/anthropic.ts","../src/types.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<any>[]; // eslint-disable-line @typescript-eslint/no-explicit-any\n agents?: Agent<any>[]; // eslint-disable-line @typescript-eslint/no-explicit-any\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, optimizeParallelism: true },\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, optimizeParallelism: true },\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 {\n JSONSchemaToZod,\n type JSONSchema,\n} from \"@dmitryrechkin/json-schema-to-zod\";\nimport { 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 { createAgenticModelFromAiAdapter, type AgenticModel } from \"./model\";\nimport { createNetwork, NetworkRun } from \"./network\";\nimport { State, type StateData } from \"./state\";\nimport { type MCP, type Tool } from \"./tool\";\nimport { AgentResult, type Message, type ToolResultMessage } from \"./types\";\nimport {\n getInngestFnInput,\n getStepTools,\n isInngestFn,\n type MaybePromise,\n} from \"./util\";\n\n/**\n * Agent represents a single agent, responsible for a set of tasks.\n */\nexport const createAgent = <T extends StateData>(opts: Agent.Constructor<T>) =>\n new Agent(opts);\n\nexport const createRoutingAgent = <T extends StateData>(\n opts: Agent.RoutingConstructor<T>\n) => new RoutingAgent(opts);\n\n/**\n * Agent represents a single agent, responsible for a set of tasks.\n */\nexport class Agent<T extends StateData> {\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<T> }) => 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<T> | Agent.RoutingLifecycle<T> | 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<T> | Agent.RoutingConstructor<T>) {\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<T>[\"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<T> {\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<T> | undefined = {}\n ): Promise<AgentResult> {\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 = new NetworkRun(\n network || createNetwork<T>({ name: \"default\", agents: [] }),\n s\n );\n\n let history = s ? s.formatHistory() : [];\n let prompt = await this.agentPrompt(input, run);\n let result = new AgentResult(\n this.name,\n [],\n [],\n new Date(),\n prompt,\n history,\n \"\"\n );\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(p, prompt, history, run);\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 p: AgenticModel.Any,\n prompt: Message[],\n history: Message[],\n network: NetworkRun<T>\n ): Promise<AgentResult> {\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 AgentResult for\n // lifecycles and history.\n let result = new AgentResult(\n this.name,\n output,\n [],\n new Date(),\n prompt,\n history,\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, 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 network: NetworkRun<T>\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<T>\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<T extends StateData> extends Agent<T> {\n type = \"routing\";\n override lifecycles: Agent.RoutingLifecycle<T>;\n constructor(opts: Agent.RoutingConstructor<T>) {\n super(opts);\n this.lifecycles = opts.lifecycle;\n }\n\n override withModel(model: AiAdapter.Any): RoutingAgent<T> {\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<T extends StateData> {\n name: string;\n description?: string;\n system:\n | string\n | ((ctx: { network?: NetworkRun<T> }) => MaybePromise<string>);\n assistant?: string;\n tools?: (Tool.Any | InngestFunction.Any)[];\n tool_choice?: Tool.Choice;\n lifecycle?: Lifecycle<T>;\n model?: AiAdapter.Any;\n mcpServers?: MCP.Server[];\n }\n\n export interface RoutingConstructor<T extends StateData>\n extends Omit<Constructor<T>, \"lifecycle\"> {\n lifecycle: RoutingLifecycle<T>;\n }\n\n export interface RoutingConstructor<T extends StateData>\n extends Omit<Constructor<T>, \"lifecycle\"> {\n lifecycle: RoutingLifecycle<T>;\n }\n\n export interface RoutingConstructor<T extends StateData>\n extends Omit<Constructor<T>, \"lifecycle\"> {\n lifecycle: RoutingLifecycle<T>;\n }\n\n export interface RunOptions<T extends StateData> {\n model?: AiAdapter.Any;\n network?: NetworkRun<T>;\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<T>;\n maxIter?: number;\n }\n\n export interface Lifecycle<T extends StateData> {\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<T>) => 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<T>) => 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<T>\n ) => MaybePromise<AgentResult>;\n\n /**\n * onFinish is called with a finalized AgentResult, including any tool\n * call results. The returned AgentResult will be saved to network\n * history, if the agent is part of the network.\n *\n */\n onFinish?: (\n args: Agent.LifecycleArgs.Result<T>\n ) => MaybePromise<AgentResult>;\n }\n\n export namespace LifecycleArgs {\n export interface Base<T extends StateData> {\n // Agent is the agent that made the call.\n agent: Agent<T>;\n // Network represents the network that this agent or lifecycle belongs to.\n network?: NetworkRun<T>;\n }\n\n export interface Result<T extends StateData> extends Base<T> {\n result: AgentResult;\n }\n\n export interface Before<T extends StateData> extends Base<T> {\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<T extends StateData> extends Lifecycle<T> {\n onRoute: RouterFn<T>;\n }\n\n export type RouterFn<T extends StateData> = (\n args: Agent.RouterArgs<T>\n ) => string[] | undefined;\n\n /**\n * Router args are the arguments passed to the onRoute lifecycle hook.\n */\n export type RouterArgs<T extends StateData> = Agent.LifecycleArgs.Result<T>;\n}\n","import { type AiAdapter } from \"@inngest/ai\";\nimport { adapters } from \"./adapters\";\nimport { type Message } from \"./types\";\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 \"../types\";\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: Message) => 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: Message) => m.role !== \"system\")\n .reduce(\n (\n acc: AiAdapter.Input<Anthropic.AiModel>[\"messages\"],\n m: Message\n ): AiAdapter.Input<Anthropic.AiModel>[\"messages\"] => {\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: Tool.Any) => {\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 xxh from \"xxhashjs\";\n\nexport type Message = TextMessage | ToolCallMessage | ToolResultMessage;\n\n/**\n * TextMessage represents plain text messages in the chat history, eg. the user's prompt or\n * an assistant's reply.\n */\nexport interface TextMessage {\n type: \"text\";\n role: \"system\" | \"user\" | \"assistant\";\n content: string | Array<TextContent>;\n // Anthropic:\n // stop_reason: \"end_turn\" | \"max_tokens\" | \"stop_sequence\" | \"tool_use\" | null;\n // OpenAI:\n // finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call' | null;\n stop_reason?: \"tool\" | \"stop\";\n}\n\n/**\n * ToolCallMessage represents a message for a tool call.\n */\nexport interface ToolCallMessage {\n type: \"tool_call\";\n role: \"user\" | \"assistant\";\n tools: ToolMessage[];\n stop_reason: \"tool\";\n}\n\n/**\n * ToolResultMessage represents the output of a tool call.\n */\nexport interface ToolResultMessage {\n type: \"tool_result\";\n role: \"tool_result\";\n // tool contains the tool call request for this result.\n tool: ToolMessage;\n content: unknown;\n stop_reason: \"tool\";\n}\n\n// Message content.\n\nexport interface TextContent {\n type: \"text\";\n text: string;\n}\n\nexport interface ToolMessage {\n type: \"tool\";\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\n/**\n * AgentResult represents a single iteration of an agent call in the router\n * loop. This includes the input prompt, the resulting messages, and any\n * tool call results.\n *\n * This is used in several ways:\n *\n * 1. To track the results of a given agent, including output and tool results.\n * 2. To construct chat history for each agent call in a network loop.\n * 3. To track what was sent to a given agent at any time.\n *\n *\n * ## Chat history and agent inputs in Networks.\n *\n * Networks call agents in a loop. Each iteration of the loop adds to conversation\n * history.\n *\n * We construct the agent input by:\n *\n * 1. Taking the system prompt from an agent\n * 2. Adding the user request as a message\n * 3. If provided, adding the agent's assistant message.\n *\n * These two or three messages are ALWAYS the start of an agent's request:\n * [system, input, ?assistant].\n *\n * We then iterate through the state's AgentResult objects, adding the output\n * and tool calls from each result to chat history.\n *\n */\nexport class AgentResult {\n constructor(\n /**\n * agentName represents the name of the agent which created this result.\n */\n public agentName: string,\n\n /**\n * output represents the parsed output from the inference call. This may be blank\n * if the agent responds with tool calls only.\n */\n public output: Message[],\n\n /**\n * toolCalls represents output from any tools called by the agent.\n */\n public toolCalls: ToolResultMessage[],\n\n /**\n * createdAt represents when this message was created.\n */\n public createdAt: Date,\n\n /**\n * prompt represents the input instructions - without any additional history\n * - as created by the agent. This includes the system prompt, the user input,\n * and any initial agent assistant message.\n *\n * This is ONLY used for tracking and debugging purposes, and is entirely optional.\n * It is not used to construct messages for future calls, and only serves to see\n * what was sent to the agent in this specific request.\n */\n public prompt?: Message[],\n\n /**\n * history represents the history sent to the inference call, appended to the\n * prompt to form a complete conversation log.\n *\n * This is ONLY used for tracking and debugging purposes, and is entirely optional.\n * It is not used to construct messages for future calls, and only serves to see\n * what was sent to the agent in this specific request.\n */\n public history?: Message[],\n\n /**\n * raw represents the raw API response from the call. This is a JSON\n * string, and the format depends on the agent's model.\n */\n public raw?: string\n ) {}\n\n // checksum memoizes a checksum so that it doe snot have to be calculated many times.\n #checksum?: string;\n\n /**\n * export returns all fields necessary to store the AgentResult for future use.\n */\n export() {\n return {\n agentName: this.agentName,\n output: this.output,\n toolCalls: this.toolCalls,\n createdAt: this.createdAt,\n checksum: this.checksum,\n };\n }\n\n /**\n * checksum is a unique ID for this result.\n *\n * It is generated by taking a checksum of the message output and the created at date.\n * This allows you to dedupe items when saving conversation history.\n */\n get checksum(): string {\n if (this.#checksum === undefined) {\n const input =\n JSON.stringify(this.output.concat(this.toolCalls)) +\n this.createdAt.toString();\n this.#checksum = xxh.h64(input, 0).toString();\n }\n return this.#checksum;\n }\n}\n","import { type GetStepTools, type Inngest } from \"inngest\";\nimport { type output as ZodOutput } from \"zod\";\nimport { type Agent } from \"./agent\";\nimport { type StateData } from \"./state\";\nimport { type NetworkRun } from \"./network\";\nimport { type AnyZodType, type MaybePromise } 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 function createTool<\n TInput extends Tool.Input,\n TState extends StateData,\n>({\n name,\n description,\n parameters,\n handler,\n}: {\n name: string;\n description?: string;\n parameters?: TInput;\n handler: (\n input: ZodOutput<TInput>,\n opts: Tool.Options<TState>\n ) => MaybePromise<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n}): Tool<TInput> {\n return {\n name,\n description,\n parameters,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: handler as any as <TState extends StateData>(\n input: ZodOutput<TInput>,\n opts: Tool.Options<TState>\n ) => MaybePromise<any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n };\n}\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 handler: <TState extends StateData>(\n input: ZodOutput<TInput>,\n opts: Tool.Options<TState>\n ) => MaybePromise<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n};\n\nexport namespace Tool {\n export type Any = Tool<Tool.Input>;\n\n export type Options<T extends StateData> = {\n agent: Agent<T>;\n network: NetworkRun<T>;\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 { createRoutingAgent, type Agent, RoutingAgent } from \"./agent\";\nimport { createState, State, type StateData } from \"./state\";\nimport { createTool } from \"./tool\";\nimport type { AgentResult } from \"./types\";\nimport { type MaybePromise } from \"./util\";\n\n/**\n * Network represents a network of agents.\n */\nexport const createNetwork = <T extends StateData>(\n opts: Network.Constructor<T>\n) => new Network(opts);\n\n/**\n * Network represents a network of agents.\n */\nexport class Network<T extends StateData> {\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<T>>;\n\n /**\n * state is the entire agent's state.\n */\n state: State<T>;\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 router?: Network.Router<T>;\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<T>>;\n\n constructor({\n name,\n description,\n agents,\n defaultModel,\n maxIter,\n defaultState,\n router,\n defaultRouter,\n }: Network.Constructor<T>) {\n this.name = name;\n this.description = description;\n this.agents = new Map();\n this._agents = new Map();\n this.defaultModel = defaultModel;\n this.router = defaultRouter ?? router;\n this.maxIter = maxIter || 0;\n this._stack = [];\n\n if (defaultState) {\n this.state = defaultState;\n } else {\n this.state = createState<T>();\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<T> = new NetworkRun(this, new State())\n ): Promise<Agent<T>[]> {\n const available: Agent<T>[] = [];\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<T>) {\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(\n ...[input, overrides]: Network.RunArgs<T>\n ): Promise<NetworkRun<T>> {\n let state: State<T>;\n if (overrides?.state) {\n if (overrides.state instanceof State) {\n state = overrides.state;\n } else {\n state = new State(overrides.state as T);\n }\n } else {\n state = this.state?.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<any> | undefined; // eslint-disable-line @typescript-eslint/no-explicit-any\n\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 (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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .map((a: Agent<any>) => {\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<T extends StateData> = {\n name: string;\n description?: string;\n agents: Agent<T>[];\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<T>;\n router?: Router<T>;\n defaultRouter?: Router<T>;\n };\n\n export type RunArgs<T extends StateData> = [\n input: string,\n overrides?: {\n router?: Router<T>;\n defaultRouter?: Router<T>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n state?: State<T> | Record<string, any>;\n },\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<T extends StateData> =\n | RoutingAgent<T>\n | Router.FnRouter<T>;\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<T extends StateData> = (\n args: Args<T>\n ) => MaybePromise<RoutingAgent<T> | Agent<T> | Agent<T>[] | undefined>;\n\n export interface Args<T extends StateData> {\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 coordinatin