@inngest/agent-kit
Version:
AgentKit is a framework for creating and orchestrating AI agents and AI workflows
1 lines • 105 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/agent.ts","../src/model.ts","../src/adapters/index.ts","../src/adapters/anthropic.ts","../src/types.ts","../src/tool.ts","../src/state.ts","../src/network.ts","../src/util.ts","../src/adapters/openai.ts","../src/adapters/gemini.ts","../src/adapters/grok.ts","../src/models.ts"],"sourcesContent":["// Base\nexport * from \"./agent\";\nexport * from \"./model\";\nexport * from \"./models\";\nexport * from \"./network\";\nexport * from \"./state\";\nexport * from \"./tool\";\nexport * from \"./util\";\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 AgentResult, type Message } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type StateData = Record<string, any>;\n\n/**\n * createState creates new state for a given network. You can add any\n * initial state data for routing, plus provide an object of previous\n * AgentResult objects or conversation history within Message.\n *\n * To store chat history, we strongly recommend serializing and storing\n * the list of AgentResult items from state after each network run.\n *\n * You can then load and pass those messages into this constructor to\n * create conversational memory.\n *\n * You can optionally pass a list of Message types in this constructor.\n * Any messages in this State will always be added after the system and\n * user prompt.\n */\nexport const createState = <T extends StateData>(\n initialState?: T,\n opts?: Omit<State.Constructor<T>, \"data\">\n): State<T> => {\n return new State({ ...opts, data: initialState });\n};\n\n/**\n * State stores state (history) for a given network of agents. The state\n * includes a stack of all AgentResult items and strongly-typed data\n * modified via tool calls.\n *\n * From this, the chat history can be reconstructed (and manipulated) for each\n * subsequent agentic call.\n */\nexport class State<T extends StateData> {\n public data: T;\n\n private _data: T;\n\n /**\n * _results stores all agent results. This is internal and is used to\n * track each call made in the network loop.\n */\n private _results: AgentResult[];\n\n /**\n * _messages stores a linear history of ALL messages from the current\n * network. You can seed this with initial messages to create conversation\n * history.\n */\n private _messages: Message[];\n\n constructor({ data, messages }: State.Constructor<T> = {}) {\n this._results = [];\n this._messages = messages || [];\n this._data = data ? { ...data } : ({} as T);\n\n // Create a new proxy that allows us to intercept the setting of state.\n //\n // This will be used to add middleware hooks to record state\n // before and after setting.\n this.data = new Proxy(this._data, {\n set: (target, prop: string | symbol, value) => {\n if (typeof prop === \"string\" && prop in target) {\n // Update the property\n Reflect.set(target, prop, value);\n return true;\n }\n return Reflect.set(target, prop, value);\n },\n });\n\n // NOTE: KV is deprecated and should be fully typed.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.#_kv = new Map<string, any>(Object.entries(this._data));\n this.kv = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n set: (key: string, value: any) => {\n this.#_kv.set(key, value);\n },\n get: (key: string) => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return this.#_kv.get(key);\n },\n delete: (key: string) => {\n return this.#_kv.delete(key);\n },\n has: (key: string) => {\n return this.#_kv.has(key);\n },\n all: () => {\n return Object.fromEntries(this.#_kv);\n },\n };\n }\n\n /**\n * Results returns a new array containing all past inference results in the\n * network. This array is safe to modify.\n */\n get results(): AgentResult[] {\n return this._results.slice();\n }\n\n /**\n * formatHistory returns the memory used for agentic calls based off of prior\n * agentic calls.\n *\n * This is used to format the current State as a conversation log when\n * calling an individual agent.\n *\n */\n formatHistory(formatter?: (r: AgentResult) => Message[]): Message[] {\n if (!formatter) {\n formatter = defaultResultFormatter;\n }\n\n // Always add any messages before any AgentResult items. This allows\n // you to preload any\n return this._messages.concat(\n this._results.map((result) => formatter(result)).flat()\n );\n }\n\n /**\n * appendResult appends a given result to the current state. This\n * is called by the network after each iteration.\n */\n appendResult(call: AgentResult) {\n this._results.push(call);\n }\n\n /**\n * clone allows you to safely clone the state.\n */\n clone() {\n const state = new State<T>(this.data);\n state._results = this._results.slice();\n state._messages = this._messages.slice();\n return state;\n }\n\n /**\n * @deprecated Fully type state instead of using the KV.\n */\n public kv: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n set: <T = any>(key: string, value: T) => void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get: <T = any>(key: string) => T | undefined;\n delete: (key: string) => boolean;\n has: (key: string) => boolean;\n all: () => Record<string, unknown>;\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n #_kv: Map<string, any>;\n}\n\nexport namespace State {\n export type Constructor<T extends StateData> = {\n /**\n * Data represents initial typed data\n */\n data?: T;\n\n /**\n * Results represents any previous AgentResult entries for\n * conversation history and memory.\n */\n results?: AgentResult[];\n\n /**\n * Messages allows you to pas custom messages which will be appended\n * after the system and user message to each agent.\n */\n messages?: Message[];\n };\n}\n\nconst defaultResultFormatter = (r: AgentResult): Message[] => {\n return ([] as Message[]).concat(r.output).concat(r.toolCalls);\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