@copilotkit/sdk-js 1.59.3-alpha.2 → 1.59.3-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,17 +44,13 @@ const resolveA2uiCatalog = (state) => {
44
44
  return null;
45
45
  };
46
46
  /**
47
- * The runtime's `injectA2UITool` decision, forwarded as
48
- * `state.copilotkit.a2ui = { injectTool: boolean | string }` whenever
49
- * CopilotRuntime is configured with an `a2ui` option. Returns `undefined` when
50
- * there is no runtime signal (AG-UI native path / no A2UI config), in which
51
- * case the middleware falls back to its catalog-gated default. A falsy value
52
- * is the host explicitly opting out.
47
+ * The A2UI `injectA2UITool` decision. The `@ag-ui/a2ui-middleware` forwards it on
48
+ * `forwardedProps`, which `ag-ui-langgraph` surfaces into agent state at
49
+ * `state["ag-ui"].inject_a2ui_tool` present only when the host turned the
50
+ * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means
51
+ * no signal (off, or no A2UI middleware in the pipeline) no auto-injection.
53
52
  */
54
- const a2uiInjectDecision = (state) => {
55
- const a2ui = state?.copilotkit?.a2ui;
56
- if (a2ui && typeof a2ui === "object" && "injectTool" in a2ui) return a2ui.injectTool;
57
- };
53
+ const a2uiInjectDecision = (state) => state?.["ag-ui"]?.inject_a2ui_tool;
58
54
  /**
59
55
  * Augment a Standard-Schema–compatible schema (e.g. Zod) with a
60
56
  * `~standard.jsonSchema.input` hook so LangGraph's
@@ -218,7 +214,6 @@ const createAppContextBeforeAgent = (state, runtime) => {
218
214
  const copilotKitStateSchema = zod.object({ copilotkit: zodState(zod.object({
219
215
  actions: zod.array(zod.any()),
220
216
  context: zod.any().optional(),
221
- a2ui: zod.any().optional(),
222
217
  interceptedToolCalls: zod.array(zod.any()).optional(),
223
218
  originalAIMessageId: zod.string().optional()
224
219
  }).optional()) });
@@ -244,11 +239,11 @@ const buildMiddlewareInput = (exposeState) => ({
244
239
  }
245
240
  let a2uiTool = null;
246
241
  const decision = a2uiInjectDecision(request.state);
247
- const a2uiCatalog = typeof _ag_ui_langgraph.getA2UITools === "function" && !(decision !== void 0 && !decision) ? resolveA2uiCatalog(request.state) : null;
248
- if (a2uiCatalog) {
242
+ if (typeof _ag_ui_langgraph.getA2UITools === "function" && decision) {
243
+ const catalog = resolveA2uiCatalog(request.state);
249
244
  const opts = {};
250
- if (a2uiCatalog.catalogId) opts.defaultCatalogId = a2uiCatalog.catalogId;
251
- if (a2uiCatalog.compositionGuide) opts.compositionGuide = a2uiCatalog.compositionGuide;
245
+ if (catalog?.catalogId) opts.defaultCatalogId = catalog.catalogId;
246
+ if (catalog?.compositionGuide) opts.compositionGuide = catalog.compositionGuide;
252
247
  const candidate = (0, _ag_ui_langgraph.getA2UITools)(request.model, opts);
253
248
  if (!new Set((request.tools || []).map((t) => t?.name)).has(candidate.name)) {
254
249
  a2uiTool = candidate;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":["z","SystemMessage","getForwardedHeaders","getA2UITools","AIMessage"],"sources":["../../src/langgraph/middleware.ts"],"sourcesContent":["import { createMiddleware, AIMessage, SystemMessage } from \"langchain\";\nimport type { InteropZodObject } from \"@langchain/core/utils/types\";\nimport type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from \"@standard-schema/spec\";\nimport * as z from \"zod\";\nimport { getA2UITools } from \"@ag-ui/langgraph\";\nimport { getForwardedHeaders } from \"../header-propagation\";\n\n// ---------------------------------------------------------------------------\n// Auto-A2UI: bridge the inferred model's generate_a2ui tool from wrapModelCall\n// (the only hook that exposes the bound model) to wrapToolCall (where the tool\n// actually executes but the model is absent). Keyed by the run's thread id so\n// concurrent runs don't clobber each other.\n// ---------------------------------------------------------------------------\nconst a2uiToolsByThread = new Map<string, any>();\nconst A2UI_DEFAULT_THREAD_KEY = \"__copilotkit_a2ui_default__\";\nconst a2uiThreadKey = (state: any): string =>\n (state?.thread_id as string) || A2UI_DEFAULT_THREAD_KEY;\n\n/**\n * Find the frontend-registered A2UI catalog wherever it was passed. Returns\n * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`\n * (so the tool is never advertised when the client can't render A2UI). Two\n * delivery paths, depending on how the agent is served:\n * - AG-UI native endpoint → `state[\"ag-ui\"].a2ui_schema` (JSON\n * `{ catalogId, components }`); the toolkit reads it from state itself.\n * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing\n * the A2UI catalog (catalog id + component schemas as text), passed to the\n * subagent via `compositionGuide`.\n * `catalogId` binds generated surfaces to the frontend's catalog so BYOC\n * custom catalogs render their own components (not the basic one).\n */\nconst resolveA2uiCatalog = (\n state: any,\n): { compositionGuide?: string; catalogId?: string } | null => {\n const a2uiSchema = state?.[\"ag-ui\"]?.a2ui_schema;\n if (a2uiSchema) {\n let catalogId: string | undefined;\n try {\n const parsed =\n typeof a2uiSchema === \"string\" ? JSON.parse(a2uiSchema) : a2uiSchema;\n catalogId = parsed?.catalogId;\n } catch {\n // non-JSON schema — fall back to the toolkit's basic catalog\n }\n return { catalogId };\n }\n const context = state?.copilotkit?.context;\n for (const entry of Array.isArray(context) ? context : []) {\n const description = entry?.description ?? \"\";\n const value = entry?.value ?? \"\";\n if (!description.includes(\"A2UI catalog\") || !value) continue;\n const match = /^\\s*-\\s+(\\S+)/m.exec(value);\n return { compositionGuide: value, catalogId: match?.[1] };\n }\n return null;\n};\n\n/**\n * The runtime's `injectA2UITool` decision, forwarded as\n * `state.copilotkit.a2ui = { injectTool: boolean | string }` whenever\n * CopilotRuntime is configured with an `a2ui` option. Returns `undefined` when\n * there is no runtime signal (AG-UI native path / no A2UI config), in which\n * case the middleware falls back to its catalog-gated default. A falsy value\n * is the host explicitly opting out.\n */\nconst a2uiInjectDecision = (state: any): boolean | string | undefined => {\n const a2ui = state?.copilotkit?.a2ui;\n if (a2ui && typeof a2ui === \"object\" && \"injectTool\" in a2ui) {\n return a2ui.injectTool;\n }\n return undefined;\n};\n\ntype WithJsonSchema<T> = T extends { \"~standard\": infer S }\n ? Omit<T, \"~standard\"> & {\n \"~standard\": S &\n StandardJSONSchemaV1.Props<\n S extends StandardSchemaV1.Props<infer I, any> ? I : unknown,\n S extends StandardSchemaV1.Props<any, infer O> ? O : unknown\n >;\n }\n : T;\n\n/**\n * Augment a Standard-Schema–compatible schema (e.g. Zod) with a\n * `~standard.jsonSchema.input` hook so LangGraph's\n * `getJsonSchemaFromSchema` (called from `StateSchema.getJsonSchema`)\n * can serialize the field.\n *\n * Without this, Zod v4 fields carry `~standard.validate` + `vendor` only,\n * and `isStandardJSONSchema()` returns false, so the field is silently\n * dropped from the graph's `output_schema`. That makes AG-UI\n * `STATE_SNAPSHOT` events filter the field out of the payload sent to\n * the frontend even though the underlying thread state has the value.\n *\n * Use this on any custom state field you want visible to the frontend\n * via `useAgent().state.*`.\n *\n * @example\n * ```ts\n * import { zodState } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const stateSchema = z.object({\n * todos: zodState(z.array(TodoSchema).default(() => [])),\n * });\n * ```\n */\nexport function zodState<T extends object>(schema: T): WithJsonSchema<T> {\n const std = (schema as { \"~standard\"?: { jsonSchema?: unknown } })[\n \"~standard\"\n ];\n if (std && typeof std === \"object\" && !(\"jsonSchema\" in std)) {\n let cached: Record<string, unknown> | undefined;\n std.jsonSchema = {\n input: () => {\n if (cached) return cached;\n // Prefer zod-v4's native `toJSONSchema` when available. Falls back to\n // an empty object, which is sufficient for the field to appear in the\n // graph's output_schema (langgraph-api treats it as an opaque field).\n try {\n const maybeV4ToJsonSchema = (\n z as unknown as {\n toJSONSchema?: (s: unknown) => Record<string, unknown>;\n }\n ).toJSONSchema;\n cached =\n typeof maybeV4ToJsonSchema === \"function\"\n ? maybeV4ToJsonSchema(schema)\n : {};\n } catch {\n cached = {};\n }\n return cached;\n },\n };\n }\n return schema as WithJsonSchema<T>;\n}\n\n/**\n * Internal/framework state keys that should never be auto-surfaced to the\n * LLM as user-facing state. These are reducer-managed message buckets,\n * CopilotKit/AG-UI plumbing, or graph-internal scaffolding.\n */\nconst RESERVED_STATE_KEYS: ReadonlySet<string> = new Set([\n \"messages\",\n \"copilotkit\",\n \"ag-ui\",\n \"tools\",\n \"structured_response\",\n \"thread_id\",\n \"remaining_steps\",\n]);\n\n/**\n * Controls how user-defined state keys are surfaced into the LLM prompt\n * on every model call. Off by default to avoid leaking arbitrary state\n * into prompts; opt in explicitly.\n *\n * - `false` (default) — never surface state.\n * - `true` — every state key not in the reserved internal set and not\n * prefixed with `_` is JSON-serialized into a \"Current agent state:\"\n * note appended to the system prompt.\n * - `string[]` — only surface the named keys (use this when you want\n * explicit control over what the LLM sees, e.g. `[\"liked\", \"todos\"]`).\n */\nexport type ExposeStateOption = boolean | readonly string[];\n\nconst buildStateNote = (\n state: Record<string, unknown>,\n expose: ExposeStateOption,\n): string | null => {\n if (expose === false) return null;\n\n const allow: ReadonlySet<string> | null = Array.isArray(expose)\n ? new Set(expose)\n : null;\n\n const snapshot: Record<string, unknown> = {};\n for (const key of Object.keys(state)) {\n if (\n allow\n ? !allow.has(key)\n : RESERVED_STATE_KEYS.has(key) || key.startsWith(\"_\")\n ) {\n continue;\n }\n const value = state[key];\n if (\n value === undefined ||\n value === null ||\n value === \"\" ||\n (Array.isArray(value) && value.length === 0) ||\n (typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0)\n ) {\n continue;\n }\n snapshot[key] = value;\n }\n\n if (Object.keys(snapshot).length === 0) return null;\n\n let body: string;\n try {\n body = JSON.stringify(snapshot, null, 2);\n } catch {\n body = String(snapshot);\n }\n return `Current agent state:\\n${body}`;\n};\n\nconst applyStateNote = (request: any, expose: ExposeStateOption): any => {\n const note = buildStateNote(\n (request.state ?? {}) as Record<string, unknown>,\n expose,\n );\n if (!note) return request;\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: new SystemMessage({ content: note }) };\n }\n // existing may be a string OR a SystemMessage\n const baseText =\n typeof existing === \"string\"\n ? existing\n : typeof existing.content === \"string\"\n ? existing.content\n : String(existing.content);\n return {\n ...request,\n systemPrompt: new SystemMessage({ content: `${baseText}\\n\\n${note}` }),\n };\n};\n\nconst createAppContextBeforeAgent = (state, runtime) => {\n const messages = state.messages;\n\n if (!messages || messages.length === 0) {\n return;\n }\n\n // Get app context from runtime\n const appContext = state[\"copilotkit\"]?.context ?? runtime?.context;\n\n // Check if appContext is missing or empty\n const isEmptyContext =\n !appContext ||\n (typeof appContext === \"string\" && appContext.trim() === \"\") ||\n (typeof appContext === \"object\" && Object.keys(appContext).length === 0);\n\n if (isEmptyContext) {\n return;\n }\n\n // Create the context content\n const contextContent =\n typeof appContext === \"string\"\n ? appContext\n : JSON.stringify(appContext, null, 2);\n const contextMessageContent = `App Context:\\n${contextContent}`;\n const contextMessagePrefix = \"App Context:\\n\";\n\n // Helper to get message content as string\n const getContentString = (msg: any): string | null => {\n if (typeof msg.content === \"string\") return msg.content;\n if (Array.isArray(msg.content) && msg.content[0]?.text)\n return msg.content[0].text;\n return null;\n };\n\n // Find the first system/developer message (not our context message) to determine\n // where to insert our context message (right after it)\n let firstSystemIndex = -1;\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n // Skip if this is our own context message\n if (content?.startsWith(contextMessagePrefix)) {\n continue;\n }\n firstSystemIndex = i;\n break;\n }\n }\n\n // Check if our context message already exists\n let existingContextIndex = -1;\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n if (content?.startsWith(contextMessagePrefix)) {\n existingContextIndex = i;\n break;\n }\n }\n }\n\n // Create the context message\n const contextMessage = new SystemMessage({ content: contextMessageContent });\n\n let updatedMessages;\n\n if (existingContextIndex !== -1) {\n // Replace existing context message\n updatedMessages = [...messages];\n updatedMessages[existingContextIndex] = contextMessage;\n } else {\n // Insert after the first system message, or at position 0 if no system message\n const insertIndex = firstSystemIndex !== -1 ? firstSystemIndex + 1 : 0;\n updatedMessages = [\n ...messages.slice(0, insertIndex),\n contextMessage,\n ...messages.slice(insertIndex),\n ];\n }\n\n return {\n ...state,\n messages: updatedMessages,\n };\n};\n\n/**\n * CopilotKit Middleware for LangGraph agents.\n *\n * Enables:\n * - Dynamic frontend tools from state.tools\n * - Context provided from CopilotKit useCopilotReadable\n *\n * Works with any agent (prebuilt or custom).\n *\n * @example\n * ```typescript\n * import { createAgent } from \"langchain\";\n * import { copilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const agent = createAgent({\n * model: \"gpt-4o\",\n * tools: [backendTool],\n * middleware: [copilotkitMiddleware],\n * });\n * ```\n */\nconst copilotKitStateSchema = z.object({\n copilotkit: zodState(\n z\n .object({\n actions: z.array(z.any()),\n context: z.any().optional(),\n // A2UI decision forwarded from the runtime ({ injectTool: bool | str }).\n // Declared so the state schema preserves it instead of stripping it as\n // an unknown key. Present only when CopilotRuntime has an `a2ui` config.\n a2ui: z.any().optional(),\n interceptedToolCalls: z.array(z.any()).optional(),\n originalAIMessageId: z.string().optional(),\n })\n .optional(),\n ),\n});\n\nconst buildMiddlewareInput = (exposeState: ExposeStateOption) => ({\n name: \"CopilotKitMiddleware\",\n\n stateSchema: copilotKitStateSchema as unknown as InteropZodObject,\n\n // Inject frontend tools, surface user state, and forward x-aimock-* headers\n wrapModelCall: async (request: any, handler: (req: any) => Promise<any>) => {\n request = applyStateNote(request, exposeState);\n\n // Forward x-aimock-* headers from the incoming AG-UI request\n const forwardedHeaders = getForwardedHeaders();\n if (Object.keys(forwardedHeaders).length > 0) {\n const existingSettings = request.modelSettings ?? {};\n const existingHeaders =\n (existingSettings.headers as Record<string, string>) ?? {};\n request = {\n ...request,\n modelSettings: {\n ...existingSettings,\n headers: { ...existingHeaders, ...forwardedHeaders },\n },\n };\n }\n\n // Auto-inject generate_a2ui when the frontend has registered an A2UI\n // catalog — sourced wherever the FE passed it (CopilotKit runtime proxy via\n // copilotkit.context, or AG-UI native via ag-ui.a2ui_schema). The catalog's\n // presence is the signal that the client can render A2UI surfaces, so this\n // never advertises a tool that would render nowhere while staying fully\n // zero-config. The model is inferred from request.model; the catalog id\n // binds surfaces to the FE's catalog; the built tool is stashed for\n // wrapToolCall to execute.\n // Gate auto-injection of generate_a2ui, in order:\n // (1) honor an explicit runtime opt-out (injectA2UITool: false);\n // (2) require a frontend-registered catalog (the client can render A2UI);\n // (3) don't double-inject if the agent already defines this tool.\n // When no runtime signal is present (AG-UI native path), only (2)–(3)\n // apply, keeping that path zero-config.\n let a2uiTool: any = null;\n const decision = a2uiInjectDecision(request.state);\n const optedOut = decision !== undefined && !decision;\n const a2uiCatalog =\n typeof getA2UITools === \"function\" && !optedOut\n ? resolveA2uiCatalog(request.state)\n : null;\n if (a2uiCatalog) {\n const opts: { defaultCatalogId?: string; compositionGuide?: string } = {};\n if (a2uiCatalog.catalogId) opts.defaultCatalogId = a2uiCatalog.catalogId;\n if (a2uiCatalog.compositionGuide)\n opts.compositionGuide = a2uiCatalog.compositionGuide;\n const candidate = getA2UITools(request.model, opts);\n const existingNames = new Set(\n (request.tools || []).map((t: any) => t?.name),\n );\n if (!existingNames.has(candidate.name)) {\n a2uiTool = candidate;\n a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);\n }\n }\n\n let frontendTools = request.state[\"copilotkit\"]?.actions ?? [];\n if (a2uiTool) {\n // Our generate_a2ui replaces the runtime's render tool — don't advertise\n // both. Drop the render tool the A2UI middleware injected.\n const drop = typeof decision === \"string\" ? decision : \"render_a2ui\";\n frontendTools = frontendTools.filter(\n (t: any) => (t?.function?.name ?? t?.name) !== drop,\n );\n }\n\n if (frontendTools.length === 0 && !a2uiTool) {\n return handler(request);\n }\n\n const existingTools = request.tools || [];\n const mergedTools = [\n ...existingTools,\n ...(a2uiTool ? [a2uiTool] : []),\n ...frontendTools,\n ];\n\n return handler({\n ...request,\n tools: mergedTools,\n });\n },\n\n // Execute the dynamically-advertised generate_a2ui tool. It is not in the\n // agent's static tool registry, so the tool node cannot run it on its own;\n // we supply the implementation (built with the inferred model) for that one\n // tool. This hook's presence also disables createAgent's \"unknown tool\"\n // guard for dynamically-advertised tools.\n wrapToolCall: async (request: any, handler: (req: any) => Promise<any>) => {\n const tool = a2uiToolsByThread.get(a2uiThreadKey(request.state));\n if (tool && !request.tool && request.toolCall?.name === tool.name) {\n return handler({ ...request, tool });\n }\n return handler(request);\n },\n\n beforeAgent: createAppContextBeforeAgent,\n\n // Restore frontend tool calls to AIMessage before agent exits\n afterAgent: (state) => {\n // Drop the bridged A2UI tool for this run — all tool calls for the turn\n // have executed by now; the next model call re-stashes if needed.\n a2uiToolsByThread.delete(a2uiThreadKey(state));\n\n const interceptedToolCalls = state[\"copilotkit\"]?.interceptedToolCalls;\n const originalMessageId = state[\"copilotkit\"]?.originalAIMessageId;\n\n if (!interceptedToolCalls?.length || !originalMessageId) {\n return;\n }\n\n let messageFound = false;\n const updatedMessages = state.messages.map((msg: any) => {\n if (AIMessage.isInstance(msg) && msg.id === originalMessageId) {\n messageFound = true;\n const existingToolCalls = msg.tool_calls || [];\n return new AIMessage({\n content: msg.content,\n tool_calls: [...existingToolCalls, ...interceptedToolCalls],\n id: msg.id,\n });\n }\n return msg;\n });\n\n // Only clear intercepted state if we successfully restored the tool calls\n if (!messageFound) {\n console.warn(\n `CopilotKit: Could not find message with id ${originalMessageId} to restore tool calls`,\n );\n return;\n }\n\n return {\n messages: updatedMessages,\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: undefined,\n originalAIMessageId: undefined,\n },\n };\n },\n\n // Intercept frontend tool calls after model returns, before ToolNode executes\n afterModel: (state) => {\n const frontendTools = state[\"copilotkit\"]?.actions ?? [];\n if (frontendTools.length === 0) return;\n\n const frontendToolNames = new Set(\n frontendTools.map((t: any) => t.function?.name || t.name),\n );\n\n const lastMessage = state.messages[state.messages.length - 1];\n if (!AIMessage.isInstance(lastMessage) || !lastMessage.tool_calls?.length) {\n return;\n }\n\n const backendToolCalls: any[] = [];\n const frontendToolCalls: any[] = [];\n\n for (const call of lastMessage.tool_calls) {\n if (frontendToolNames.has(call.name)) {\n frontendToolCalls.push(call);\n } else {\n backendToolCalls.push(call);\n }\n }\n\n if (frontendToolCalls.length === 0) return;\n\n const updatedAIMessage = new AIMessage({\n content: lastMessage.content,\n tool_calls: backendToolCalls,\n id: lastMessage.id,\n });\n\n return {\n messages: [...state.messages.slice(0, -1), updatedAIMessage],\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: frontendToolCalls,\n originalAIMessageId: lastMessage.id,\n },\n };\n },\n});\n\n/**\n * Build a CopilotKit middleware instance with custom options.\n *\n * Use this when you want to override the default state-exposure behavior\n * (for example to hide a sensitive key, or to use an explicit allowlist).\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: { exposeState?: ExposeStateOption } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(buildMiddlewareInput(exposeState) as any);\n};\n\n/**\n * Default CopilotKit middleware singleton — does NOT surface user state\n * to the LLM. Pass `exposeState: true` (or an allowlist) to\n * {@link createCopilotkitMiddleware} to opt in.\n */\nexport const copilotkitMiddleware = createCopilotkitMiddleware();\n"],"mappings":"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"}
1
+ {"version":3,"file":"middleware.cjs","names":["z","SystemMessage","getForwardedHeaders","getA2UITools","AIMessage"],"sources":["../../src/langgraph/middleware.ts"],"sourcesContent":["import { createMiddleware, AIMessage, SystemMessage } from \"langchain\";\nimport type { InteropZodObject } from \"@langchain/core/utils/types\";\nimport type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from \"@standard-schema/spec\";\nimport * as z from \"zod\";\nimport { getA2UITools } from \"@ag-ui/langgraph\";\nimport { getForwardedHeaders } from \"../header-propagation\";\n\n// ---------------------------------------------------------------------------\n// Auto-A2UI: bridge the inferred model's generate_a2ui tool from wrapModelCall\n// (the only hook that exposes the bound model) to wrapToolCall (where the tool\n// actually executes but the model is absent). Keyed by the run's thread id so\n// concurrent runs don't clobber each other.\n// ---------------------------------------------------------------------------\nconst a2uiToolsByThread = new Map<string, any>();\nconst A2UI_DEFAULT_THREAD_KEY = \"__copilotkit_a2ui_default__\";\nconst a2uiThreadKey = (state: any): string =>\n (state?.thread_id as string) || A2UI_DEFAULT_THREAD_KEY;\n\n/**\n * Find the frontend-registered A2UI catalog wherever it was passed. Returns\n * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`\n * (so the tool is never advertised when the client can't render A2UI). Two\n * delivery paths, depending on how the agent is served:\n * - AG-UI native endpoint → `state[\"ag-ui\"].a2ui_schema` (JSON\n * `{ catalogId, components }`); the toolkit reads it from state itself.\n * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing\n * the A2UI catalog (catalog id + component schemas as text), passed to the\n * subagent via `compositionGuide`.\n * `catalogId` binds generated surfaces to the frontend's catalog so BYOC\n * custom catalogs render their own components (not the basic one).\n */\nconst resolveA2uiCatalog = (\n state: any,\n): { compositionGuide?: string; catalogId?: string } | null => {\n const a2uiSchema = state?.[\"ag-ui\"]?.a2ui_schema;\n if (a2uiSchema) {\n let catalogId: string | undefined;\n try {\n const parsed =\n typeof a2uiSchema === \"string\" ? JSON.parse(a2uiSchema) : a2uiSchema;\n catalogId = parsed?.catalogId;\n } catch {\n // non-JSON schema — fall back to the toolkit's basic catalog\n }\n return { catalogId };\n }\n const context = state?.copilotkit?.context;\n for (const entry of Array.isArray(context) ? context : []) {\n const description = entry?.description ?? \"\";\n const value = entry?.value ?? \"\";\n if (!description.includes(\"A2UI catalog\") || !value) continue;\n const match = /^\\s*-\\s+(\\S+)/m.exec(value);\n return { compositionGuide: value, catalogId: match?.[1] };\n }\n return null;\n};\n\n/**\n * The A2UI `injectA2UITool` decision. The `@ag-ui/a2ui-middleware` forwards it on\n * `forwardedProps`, which `ag-ui-langgraph` surfaces into agent state at\n * `state[\"ag-ui\"].inject_a2ui_tool` — present only when the host turned the\n * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means\n * no signal (off, or no A2UI middleware in the pipeline) → no auto-injection.\n */\nconst a2uiInjectDecision = (state: any): boolean | string | undefined =>\n state?.[\"ag-ui\"]?.inject_a2ui_tool;\n\ntype WithJsonSchema<T> = T extends { \"~standard\": infer S }\n ? Omit<T, \"~standard\"> & {\n \"~standard\": S &\n StandardJSONSchemaV1.Props<\n S extends StandardSchemaV1.Props<infer I, any> ? I : unknown,\n S extends StandardSchemaV1.Props<any, infer O> ? O : unknown\n >;\n }\n : T;\n\n/**\n * Augment a Standard-Schema–compatible schema (e.g. Zod) with a\n * `~standard.jsonSchema.input` hook so LangGraph's\n * `getJsonSchemaFromSchema` (called from `StateSchema.getJsonSchema`)\n * can serialize the field.\n *\n * Without this, Zod v4 fields carry `~standard.validate` + `vendor` only,\n * and `isStandardJSONSchema()` returns false, so the field is silently\n * dropped from the graph's `output_schema`. That makes AG-UI\n * `STATE_SNAPSHOT` events filter the field out of the payload sent to\n * the frontend even though the underlying thread state has the value.\n *\n * Use this on any custom state field you want visible to the frontend\n * via `useAgent().state.*`.\n *\n * @example\n * ```ts\n * import { zodState } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const stateSchema = z.object({\n * todos: zodState(z.array(TodoSchema).default(() => [])),\n * });\n * ```\n */\nexport function zodState<T extends object>(schema: T): WithJsonSchema<T> {\n const std = (schema as { \"~standard\"?: { jsonSchema?: unknown } })[\n \"~standard\"\n ];\n if (std && typeof std === \"object\" && !(\"jsonSchema\" in std)) {\n let cached: Record<string, unknown> | undefined;\n std.jsonSchema = {\n input: () => {\n if (cached) return cached;\n // Prefer zod-v4's native `toJSONSchema` when available. Falls back to\n // an empty object, which is sufficient for the field to appear in the\n // graph's output_schema (langgraph-api treats it as an opaque field).\n try {\n const maybeV4ToJsonSchema = (\n z as unknown as {\n toJSONSchema?: (s: unknown) => Record<string, unknown>;\n }\n ).toJSONSchema;\n cached =\n typeof maybeV4ToJsonSchema === \"function\"\n ? maybeV4ToJsonSchema(schema)\n : {};\n } catch {\n cached = {};\n }\n return cached;\n },\n };\n }\n return schema as WithJsonSchema<T>;\n}\n\n/**\n * Internal/framework state keys that should never be auto-surfaced to the\n * LLM as user-facing state. These are reducer-managed message buckets,\n * CopilotKit/AG-UI plumbing, or graph-internal scaffolding.\n */\nconst RESERVED_STATE_KEYS: ReadonlySet<string> = new Set([\n \"messages\",\n \"copilotkit\",\n \"ag-ui\",\n \"tools\",\n \"structured_response\",\n \"thread_id\",\n \"remaining_steps\",\n]);\n\n/**\n * Controls how user-defined state keys are surfaced into the LLM prompt\n * on every model call. Off by default to avoid leaking arbitrary state\n * into prompts; opt in explicitly.\n *\n * - `false` (default) — never surface state.\n * - `true` — every state key not in the reserved internal set and not\n * prefixed with `_` is JSON-serialized into a \"Current agent state:\"\n * note appended to the system prompt.\n * - `string[]` — only surface the named keys (use this when you want\n * explicit control over what the LLM sees, e.g. `[\"liked\", \"todos\"]`).\n */\nexport type ExposeStateOption = boolean | readonly string[];\n\nconst buildStateNote = (\n state: Record<string, unknown>,\n expose: ExposeStateOption,\n): string | null => {\n if (expose === false) return null;\n\n const allow: ReadonlySet<string> | null = Array.isArray(expose)\n ? new Set(expose)\n : null;\n\n const snapshot: Record<string, unknown> = {};\n for (const key of Object.keys(state)) {\n if (\n allow\n ? !allow.has(key)\n : RESERVED_STATE_KEYS.has(key) || key.startsWith(\"_\")\n ) {\n continue;\n }\n const value = state[key];\n if (\n value === undefined ||\n value === null ||\n value === \"\" ||\n (Array.isArray(value) && value.length === 0) ||\n (typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0)\n ) {\n continue;\n }\n snapshot[key] = value;\n }\n\n if (Object.keys(snapshot).length === 0) return null;\n\n let body: string;\n try {\n body = JSON.stringify(snapshot, null, 2);\n } catch {\n body = String(snapshot);\n }\n return `Current agent state:\\n${body}`;\n};\n\nconst applyStateNote = (request: any, expose: ExposeStateOption): any => {\n const note = buildStateNote(\n (request.state ?? {}) as Record<string, unknown>,\n expose,\n );\n if (!note) return request;\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: new SystemMessage({ content: note }) };\n }\n // existing may be a string OR a SystemMessage\n const baseText =\n typeof existing === \"string\"\n ? existing\n : typeof existing.content === \"string\"\n ? existing.content\n : String(existing.content);\n return {\n ...request,\n systemPrompt: new SystemMessage({ content: `${baseText}\\n\\n${note}` }),\n };\n};\n\nconst createAppContextBeforeAgent = (state, runtime) => {\n const messages = state.messages;\n\n if (!messages || messages.length === 0) {\n return;\n }\n\n // Get app context from runtime\n const appContext = state[\"copilotkit\"]?.context ?? runtime?.context;\n\n // Check if appContext is missing or empty\n const isEmptyContext =\n !appContext ||\n (typeof appContext === \"string\" && appContext.trim() === \"\") ||\n (typeof appContext === \"object\" && Object.keys(appContext).length === 0);\n\n if (isEmptyContext) {\n return;\n }\n\n // Create the context content\n const contextContent =\n typeof appContext === \"string\"\n ? appContext\n : JSON.stringify(appContext, null, 2);\n const contextMessageContent = `App Context:\\n${contextContent}`;\n const contextMessagePrefix = \"App Context:\\n\";\n\n // Helper to get message content as string\n const getContentString = (msg: any): string | null => {\n if (typeof msg.content === \"string\") return msg.content;\n if (Array.isArray(msg.content) && msg.content[0]?.text)\n return msg.content[0].text;\n return null;\n };\n\n // Find the first system/developer message (not our context message) to determine\n // where to insert our context message (right after it)\n let firstSystemIndex = -1;\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n // Skip if this is our own context message\n if (content?.startsWith(contextMessagePrefix)) {\n continue;\n }\n firstSystemIndex = i;\n break;\n }\n }\n\n // Check if our context message already exists\n let existingContextIndex = -1;\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n if (content?.startsWith(contextMessagePrefix)) {\n existingContextIndex = i;\n break;\n }\n }\n }\n\n // Create the context message\n const contextMessage = new SystemMessage({ content: contextMessageContent });\n\n let updatedMessages;\n\n if (existingContextIndex !== -1) {\n // Replace existing context message\n updatedMessages = [...messages];\n updatedMessages[existingContextIndex] = contextMessage;\n } else {\n // Insert after the first system message, or at position 0 if no system message\n const insertIndex = firstSystemIndex !== -1 ? firstSystemIndex + 1 : 0;\n updatedMessages = [\n ...messages.slice(0, insertIndex),\n contextMessage,\n ...messages.slice(insertIndex),\n ];\n }\n\n return {\n ...state,\n messages: updatedMessages,\n };\n};\n\n/**\n * CopilotKit Middleware for LangGraph agents.\n *\n * Enables:\n * - Dynamic frontend tools from state.tools\n * - Context provided from CopilotKit useCopilotReadable\n *\n * Works with any agent (prebuilt or custom).\n *\n * @example\n * ```typescript\n * import { createAgent } from \"langchain\";\n * import { copilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const agent = createAgent({\n * model: \"gpt-4o\",\n * tools: [backendTool],\n * middleware: [copilotkitMiddleware],\n * });\n * ```\n */\nconst copilotKitStateSchema = z.object({\n copilotkit: zodState(\n z\n .object({\n actions: z.array(z.any()),\n context: z.any().optional(),\n interceptedToolCalls: z.array(z.any()).optional(),\n originalAIMessageId: z.string().optional(),\n })\n .optional(),\n ),\n});\n\nconst buildMiddlewareInput = (exposeState: ExposeStateOption) => ({\n name: \"CopilotKitMiddleware\",\n\n stateSchema: copilotKitStateSchema as unknown as InteropZodObject,\n\n // Inject frontend tools, surface user state, and forward x-aimock-* headers\n wrapModelCall: async (request: any, handler: (req: any) => Promise<any>) => {\n request = applyStateNote(request, exposeState);\n\n // Forward x-aimock-* headers from the incoming AG-UI request\n const forwardedHeaders = getForwardedHeaders();\n if (Object.keys(forwardedHeaders).length > 0) {\n const existingSettings = request.modelSettings ?? {};\n const existingHeaders =\n (existingSettings.headers as Record<string, string>) ?? {};\n request = {\n ...request,\n modelSettings: {\n ...existingSettings,\n headers: { ...existingHeaders, ...forwardedHeaders },\n },\n };\n }\n\n // Opt-in auto-injection of generate_a2ui:\n // (1) only inject when the A2UI injectA2UITool flag is truthy (forwarded by\n // @ag-ui/a2ui-middleware and surfaced at state[\"ag-ui\"].inject_a2ui_tool);\n // (2) don't double-inject if the agent already defines this tool.\n // The catalog (when present) only binds surfaces to the FE's catalog; it is\n // not the gate. The model is inferred from request.model; the built tool is\n // stashed for wrapToolCall to execute.\n let a2uiTool: any = null;\n const decision = a2uiInjectDecision(request.state);\n if (typeof getA2UITools === \"function\" && decision) {\n const catalog = resolveA2uiCatalog(request.state);\n const opts: { defaultCatalogId?: string; compositionGuide?: string } = {};\n if (catalog?.catalogId) opts.defaultCatalogId = catalog.catalogId;\n if (catalog?.compositionGuide)\n opts.compositionGuide = catalog.compositionGuide;\n const candidate = getA2UITools(request.model, opts);\n const existingNames = new Set(\n (request.tools || []).map((t: any) => t?.name),\n );\n if (!existingNames.has(candidate.name)) {\n a2uiTool = candidate;\n a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);\n }\n }\n\n let frontendTools = request.state[\"copilotkit\"]?.actions ?? [];\n if (a2uiTool) {\n // Our generate_a2ui replaces the runtime's render tool — don't advertise\n // both. Drop the render tool the A2UI middleware injected.\n const drop = typeof decision === \"string\" ? decision : \"render_a2ui\";\n frontendTools = frontendTools.filter(\n (t: any) => (t?.function?.name ?? t?.name) !== drop,\n );\n }\n\n if (frontendTools.length === 0 && !a2uiTool) {\n return handler(request);\n }\n\n const existingTools = request.tools || [];\n const mergedTools = [\n ...existingTools,\n ...(a2uiTool ? [a2uiTool] : []),\n ...frontendTools,\n ];\n\n return handler({\n ...request,\n tools: mergedTools,\n });\n },\n\n // Execute the dynamically-advertised generate_a2ui tool. It is not in the\n // agent's static tool registry, so the tool node cannot run it on its own;\n // we supply the implementation (built with the inferred model) for that one\n // tool. This hook's presence also disables createAgent's \"unknown tool\"\n // guard for dynamically-advertised tools.\n wrapToolCall: async (request: any, handler: (req: any) => Promise<any>) => {\n const tool = a2uiToolsByThread.get(a2uiThreadKey(request.state));\n if (tool && !request.tool && request.toolCall?.name === tool.name) {\n return handler({ ...request, tool });\n }\n return handler(request);\n },\n\n beforeAgent: createAppContextBeforeAgent,\n\n // Restore frontend tool calls to AIMessage before agent exits\n afterAgent: (state) => {\n // Drop the bridged A2UI tool for this run — all tool calls for the turn\n // have executed by now; the next model call re-stashes if needed.\n a2uiToolsByThread.delete(a2uiThreadKey(state));\n\n const interceptedToolCalls = state[\"copilotkit\"]?.interceptedToolCalls;\n const originalMessageId = state[\"copilotkit\"]?.originalAIMessageId;\n\n if (!interceptedToolCalls?.length || !originalMessageId) {\n return;\n }\n\n let messageFound = false;\n const updatedMessages = state.messages.map((msg: any) => {\n if (AIMessage.isInstance(msg) && msg.id === originalMessageId) {\n messageFound = true;\n const existingToolCalls = msg.tool_calls || [];\n return new AIMessage({\n content: msg.content,\n tool_calls: [...existingToolCalls, ...interceptedToolCalls],\n id: msg.id,\n });\n }\n return msg;\n });\n\n // Only clear intercepted state if we successfully restored the tool calls\n if (!messageFound) {\n console.warn(\n `CopilotKit: Could not find message with id ${originalMessageId} to restore tool calls`,\n );\n return;\n }\n\n return {\n messages: updatedMessages,\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: undefined,\n originalAIMessageId: undefined,\n },\n };\n },\n\n // Intercept frontend tool calls after model returns, before ToolNode executes\n afterModel: (state) => {\n const frontendTools = state[\"copilotkit\"]?.actions ?? [];\n if (frontendTools.length === 0) return;\n\n const frontendToolNames = new Set(\n frontendTools.map((t: any) => t.function?.name || t.name),\n );\n\n const lastMessage = state.messages[state.messages.length - 1];\n if (!AIMessage.isInstance(lastMessage) || !lastMessage.tool_calls?.length) {\n return;\n }\n\n const backendToolCalls: any[] = [];\n const frontendToolCalls: any[] = [];\n\n for (const call of lastMessage.tool_calls) {\n if (frontendToolNames.has(call.name)) {\n frontendToolCalls.push(call);\n } else {\n backendToolCalls.push(call);\n }\n }\n\n if (frontendToolCalls.length === 0) return;\n\n const updatedAIMessage = new AIMessage({\n content: lastMessage.content,\n tool_calls: backendToolCalls,\n id: lastMessage.id,\n });\n\n return {\n messages: [...state.messages.slice(0, -1), updatedAIMessage],\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: frontendToolCalls,\n originalAIMessageId: lastMessage.id,\n },\n };\n },\n});\n\n/**\n * Build a CopilotKit middleware instance with custom options.\n *\n * Use this when you want to override the default state-exposure behavior\n * (for example to hide a sensitive key, or to use an explicit allowlist).\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: { exposeState?: ExposeStateOption } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(buildMiddlewareInput(exposeState) as any);\n};\n\n/**\n * Default CopilotKit middleware singleton — does NOT surface user state\n * to the LLM. Pass `exposeState: true` (or an allowlist) to\n * {@link createCopilotkitMiddleware} to opt in.\n */\nexport const copilotkitMiddleware = createCopilotkitMiddleware();\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.cts","names":[],"sources":["../../src/langgraph/middleware.ts"],"mappings":";;;;;KA4EK,cAAA,MAAoB,CAAA;EAAY,WAAA;AAAA,IACjC,IAAA,CAAK,CAAA;EACH,WAAA,EAAa,CAAA,GACX,oBAAA,CAAqB,KAAA,CACnB,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA,YACjD,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA;AAAA,IAGvD,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BY,QAAA,kBAAA,CAA2B,MAAA,EAAQ,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;;;;;;;AAAtE;;KA2DY,iBAAA;;;;;;;;;;;;;AAAZ;;;cAwZa,0BAAA,GACX,OAAA;EAAW,WAAA,GAAc,iBAAA;AAAA,MAAwB,SAAA,CAAA,eAAA,0CAAP,sBAAA,CAAO,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAWtC,oBAAA,EAAoB,SAAA,CAAA,eAAA,0CAA+B,sBAAA,CAA/B,UAAA,GAAA,sBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"middleware.d.cts","names":[],"sources":["../../src/langgraph/middleware.ts"],"mappings":";;;;;KAsEK,cAAA,MAAoB,CAAA;EAAY,WAAA;AAAA,IACjC,IAAA,CAAK,CAAA;EACH,WAAA,EAAa,CAAA,GACX,oBAAA,CAAqB,KAAA,CACnB,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA,YACjD,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA;AAAA,IAGvD,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BY,QAAA,kBAAA,CAA2B,MAAA,EAAQ,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;;;;;;;AAAtE;;KA2DY,iBAAA;;;;;;;;;;;;;AAAZ;;;cAyYa,0BAAA,GACX,OAAA;EAAW,WAAA,GAAc,iBAAA;AAAA,MAAwB,SAAA,CAAA,eAAA,0CAAP,sBAAA,CAAO,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAWtC,oBAAA,EAAoB,SAAA,CAAA,eAAA,0CAA+B,sBAAA,CAA/B,UAAA,GAAA,sBAAA,CAAA,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/langgraph/middleware.ts"],"mappings":";;;;;KA4EK,cAAA,MAAoB,CAAA;EAAY,WAAA;AAAA,IACjC,IAAA,CAAK,CAAA;EACH,WAAA,EAAa,CAAA,GACX,oBAAA,CAAqB,KAAA,CACnB,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA,YACjD,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA;AAAA,IAGvD,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BY,QAAA,kBAAA,CAA2B,MAAA,EAAQ,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;;;;;;;AAAtE;;KA2DY,iBAAA;;;;;;;;;;;;;AAAZ;;;cAwZa,0BAAA,GACX,OAAA;EAAW,WAAA,GAAc,iBAAA;AAAA,MAAwB,SAAA,CAAA,eAAA,0CAAP,sBAAA,CAAO,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAWtC,oBAAA,EAAoB,SAAA,CAAA,eAAA,0CAA+B,sBAAA,CAA/B,UAAA,GAAA,sBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/langgraph/middleware.ts"],"mappings":";;;;;KAsEK,cAAA,MAAoB,CAAA;EAAY,WAAA;AAAA,IACjC,IAAA,CAAK,CAAA;EACH,WAAA,EAAa,CAAA,GACX,oBAAA,CAAqB,KAAA,CACnB,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA,YACjD,CAAA,SAAU,gBAAA,CAAiB,KAAA,iBAAsB,CAAA;AAAA,IAGvD,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BY,QAAA,kBAAA,CAA2B,MAAA,EAAQ,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;;;;;;;AAAtE;;KA2DY,iBAAA;;;;;;;;;;;;;AAAZ;;;cAyYa,0BAAA,GACX,OAAA;EAAW,WAAA,GAAc,iBAAA;AAAA,MAAwB,SAAA,CAAA,eAAA,0CAAP,sBAAA,CAAO,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAWtC,oBAAA,EAAoB,SAAA,CAAA,eAAA,0CAA+B,sBAAA,CAA/B,UAAA,GAAA,sBAAA,CAAA,UAAA"}
@@ -42,17 +42,13 @@ const resolveA2uiCatalog = (state) => {
42
42
  return null;
43
43
  };
44
44
  /**
45
- * The runtime's `injectA2UITool` decision, forwarded as
46
- * `state.copilotkit.a2ui = { injectTool: boolean | string }` whenever
47
- * CopilotRuntime is configured with an `a2ui` option. Returns `undefined` when
48
- * there is no runtime signal (AG-UI native path / no A2UI config), in which
49
- * case the middleware falls back to its catalog-gated default. A falsy value
50
- * is the host explicitly opting out.
45
+ * The A2UI `injectA2UITool` decision. The `@ag-ui/a2ui-middleware` forwards it on
46
+ * `forwardedProps`, which `ag-ui-langgraph` surfaces into agent state at
47
+ * `state["ag-ui"].inject_a2ui_tool` present only when the host turned the
48
+ * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means
49
+ * no signal (off, or no A2UI middleware in the pipeline) no auto-injection.
51
50
  */
52
- const a2uiInjectDecision = (state) => {
53
- const a2ui = state?.copilotkit?.a2ui;
54
- if (a2ui && typeof a2ui === "object" && "injectTool" in a2ui) return a2ui.injectTool;
55
- };
51
+ const a2uiInjectDecision = (state) => state?.["ag-ui"]?.inject_a2ui_tool;
56
52
  /**
57
53
  * Augment a Standard-Schema–compatible schema (e.g. Zod) with a
58
54
  * `~standard.jsonSchema.input` hook so LangGraph's
@@ -216,7 +212,6 @@ const createAppContextBeforeAgent = (state, runtime) => {
216
212
  const copilotKitStateSchema = z.object({ copilotkit: zodState(z.object({
217
213
  actions: z.array(z.any()),
218
214
  context: z.any().optional(),
219
- a2ui: z.any().optional(),
220
215
  interceptedToolCalls: z.array(z.any()).optional(),
221
216
  originalAIMessageId: z.string().optional()
222
217
  }).optional()) });
@@ -242,11 +237,11 @@ const buildMiddlewareInput = (exposeState) => ({
242
237
  }
243
238
  let a2uiTool = null;
244
239
  const decision = a2uiInjectDecision(request.state);
245
- const a2uiCatalog = typeof getA2UITools === "function" && !(decision !== void 0 && !decision) ? resolveA2uiCatalog(request.state) : null;
246
- if (a2uiCatalog) {
240
+ if (typeof getA2UITools === "function" && decision) {
241
+ const catalog = resolveA2uiCatalog(request.state);
247
242
  const opts = {};
248
- if (a2uiCatalog.catalogId) opts.defaultCatalogId = a2uiCatalog.catalogId;
249
- if (a2uiCatalog.compositionGuide) opts.compositionGuide = a2uiCatalog.compositionGuide;
243
+ if (catalog?.catalogId) opts.defaultCatalogId = catalog.catalogId;
244
+ if (catalog?.compositionGuide) opts.compositionGuide = catalog.compositionGuide;
250
245
  const candidate = getA2UITools(request.model, opts);
251
246
  if (!new Set((request.tools || []).map((t) => t?.name)).has(candidate.name)) {
252
247
  a2uiTool = candidate;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/langgraph/middleware.ts"],"sourcesContent":["import { createMiddleware, AIMessage, SystemMessage } from \"langchain\";\nimport type { InteropZodObject } from \"@langchain/core/utils/types\";\nimport type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from \"@standard-schema/spec\";\nimport * as z from \"zod\";\nimport { getA2UITools } from \"@ag-ui/langgraph\";\nimport { getForwardedHeaders } from \"../header-propagation\";\n\n// ---------------------------------------------------------------------------\n// Auto-A2UI: bridge the inferred model's generate_a2ui tool from wrapModelCall\n// (the only hook that exposes the bound model) to wrapToolCall (where the tool\n// actually executes but the model is absent). Keyed by the run's thread id so\n// concurrent runs don't clobber each other.\n// ---------------------------------------------------------------------------\nconst a2uiToolsByThread = new Map<string, any>();\nconst A2UI_DEFAULT_THREAD_KEY = \"__copilotkit_a2ui_default__\";\nconst a2uiThreadKey = (state: any): string =>\n (state?.thread_id as string) || A2UI_DEFAULT_THREAD_KEY;\n\n/**\n * Find the frontend-registered A2UI catalog wherever it was passed. Returns\n * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`\n * (so the tool is never advertised when the client can't render A2UI). Two\n * delivery paths, depending on how the agent is served:\n * - AG-UI native endpoint → `state[\"ag-ui\"].a2ui_schema` (JSON\n * `{ catalogId, components }`); the toolkit reads it from state itself.\n * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing\n * the A2UI catalog (catalog id + component schemas as text), passed to the\n * subagent via `compositionGuide`.\n * `catalogId` binds generated surfaces to the frontend's catalog so BYOC\n * custom catalogs render their own components (not the basic one).\n */\nconst resolveA2uiCatalog = (\n state: any,\n): { compositionGuide?: string; catalogId?: string } | null => {\n const a2uiSchema = state?.[\"ag-ui\"]?.a2ui_schema;\n if (a2uiSchema) {\n let catalogId: string | undefined;\n try {\n const parsed =\n typeof a2uiSchema === \"string\" ? JSON.parse(a2uiSchema) : a2uiSchema;\n catalogId = parsed?.catalogId;\n } catch {\n // non-JSON schema — fall back to the toolkit's basic catalog\n }\n return { catalogId };\n }\n const context = state?.copilotkit?.context;\n for (const entry of Array.isArray(context) ? context : []) {\n const description = entry?.description ?? \"\";\n const value = entry?.value ?? \"\";\n if (!description.includes(\"A2UI catalog\") || !value) continue;\n const match = /^\\s*-\\s+(\\S+)/m.exec(value);\n return { compositionGuide: value, catalogId: match?.[1] };\n }\n return null;\n};\n\n/**\n * The runtime's `injectA2UITool` decision, forwarded as\n * `state.copilotkit.a2ui = { injectTool: boolean | string }` whenever\n * CopilotRuntime is configured with an `a2ui` option. Returns `undefined` when\n * there is no runtime signal (AG-UI native path / no A2UI config), in which\n * case the middleware falls back to its catalog-gated default. A falsy value\n * is the host explicitly opting out.\n */\nconst a2uiInjectDecision = (state: any): boolean | string | undefined => {\n const a2ui = state?.copilotkit?.a2ui;\n if (a2ui && typeof a2ui === \"object\" && \"injectTool\" in a2ui) {\n return a2ui.injectTool;\n }\n return undefined;\n};\n\ntype WithJsonSchema<T> = T extends { \"~standard\": infer S }\n ? Omit<T, \"~standard\"> & {\n \"~standard\": S &\n StandardJSONSchemaV1.Props<\n S extends StandardSchemaV1.Props<infer I, any> ? I : unknown,\n S extends StandardSchemaV1.Props<any, infer O> ? O : unknown\n >;\n }\n : T;\n\n/**\n * Augment a Standard-Schema–compatible schema (e.g. Zod) with a\n * `~standard.jsonSchema.input` hook so LangGraph's\n * `getJsonSchemaFromSchema` (called from `StateSchema.getJsonSchema`)\n * can serialize the field.\n *\n * Without this, Zod v4 fields carry `~standard.validate` + `vendor` only,\n * and `isStandardJSONSchema()` returns false, so the field is silently\n * dropped from the graph's `output_schema`. That makes AG-UI\n * `STATE_SNAPSHOT` events filter the field out of the payload sent to\n * the frontend even though the underlying thread state has the value.\n *\n * Use this on any custom state field you want visible to the frontend\n * via `useAgent().state.*`.\n *\n * @example\n * ```ts\n * import { zodState } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const stateSchema = z.object({\n * todos: zodState(z.array(TodoSchema).default(() => [])),\n * });\n * ```\n */\nexport function zodState<T extends object>(schema: T): WithJsonSchema<T> {\n const std = (schema as { \"~standard\"?: { jsonSchema?: unknown } })[\n \"~standard\"\n ];\n if (std && typeof std === \"object\" && !(\"jsonSchema\" in std)) {\n let cached: Record<string, unknown> | undefined;\n std.jsonSchema = {\n input: () => {\n if (cached) return cached;\n // Prefer zod-v4's native `toJSONSchema` when available. Falls back to\n // an empty object, which is sufficient for the field to appear in the\n // graph's output_schema (langgraph-api treats it as an opaque field).\n try {\n const maybeV4ToJsonSchema = (\n z as unknown as {\n toJSONSchema?: (s: unknown) => Record<string, unknown>;\n }\n ).toJSONSchema;\n cached =\n typeof maybeV4ToJsonSchema === \"function\"\n ? maybeV4ToJsonSchema(schema)\n : {};\n } catch {\n cached = {};\n }\n return cached;\n },\n };\n }\n return schema as WithJsonSchema<T>;\n}\n\n/**\n * Internal/framework state keys that should never be auto-surfaced to the\n * LLM as user-facing state. These are reducer-managed message buckets,\n * CopilotKit/AG-UI plumbing, or graph-internal scaffolding.\n */\nconst RESERVED_STATE_KEYS: ReadonlySet<string> = new Set([\n \"messages\",\n \"copilotkit\",\n \"ag-ui\",\n \"tools\",\n \"structured_response\",\n \"thread_id\",\n \"remaining_steps\",\n]);\n\n/**\n * Controls how user-defined state keys are surfaced into the LLM prompt\n * on every model call. Off by default to avoid leaking arbitrary state\n * into prompts; opt in explicitly.\n *\n * - `false` (default) — never surface state.\n * - `true` — every state key not in the reserved internal set and not\n * prefixed with `_` is JSON-serialized into a \"Current agent state:\"\n * note appended to the system prompt.\n * - `string[]` — only surface the named keys (use this when you want\n * explicit control over what the LLM sees, e.g. `[\"liked\", \"todos\"]`).\n */\nexport type ExposeStateOption = boolean | readonly string[];\n\nconst buildStateNote = (\n state: Record<string, unknown>,\n expose: ExposeStateOption,\n): string | null => {\n if (expose === false) return null;\n\n const allow: ReadonlySet<string> | null = Array.isArray(expose)\n ? new Set(expose)\n : null;\n\n const snapshot: Record<string, unknown> = {};\n for (const key of Object.keys(state)) {\n if (\n allow\n ? !allow.has(key)\n : RESERVED_STATE_KEYS.has(key) || key.startsWith(\"_\")\n ) {\n continue;\n }\n const value = state[key];\n if (\n value === undefined ||\n value === null ||\n value === \"\" ||\n (Array.isArray(value) && value.length === 0) ||\n (typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0)\n ) {\n continue;\n }\n snapshot[key] = value;\n }\n\n if (Object.keys(snapshot).length === 0) return null;\n\n let body: string;\n try {\n body = JSON.stringify(snapshot, null, 2);\n } catch {\n body = String(snapshot);\n }\n return `Current agent state:\\n${body}`;\n};\n\nconst applyStateNote = (request: any, expose: ExposeStateOption): any => {\n const note = buildStateNote(\n (request.state ?? {}) as Record<string, unknown>,\n expose,\n );\n if (!note) return request;\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: new SystemMessage({ content: note }) };\n }\n // existing may be a string OR a SystemMessage\n const baseText =\n typeof existing === \"string\"\n ? existing\n : typeof existing.content === \"string\"\n ? existing.content\n : String(existing.content);\n return {\n ...request,\n systemPrompt: new SystemMessage({ content: `${baseText}\\n\\n${note}` }),\n };\n};\n\nconst createAppContextBeforeAgent = (state, runtime) => {\n const messages = state.messages;\n\n if (!messages || messages.length === 0) {\n return;\n }\n\n // Get app context from runtime\n const appContext = state[\"copilotkit\"]?.context ?? runtime?.context;\n\n // Check if appContext is missing or empty\n const isEmptyContext =\n !appContext ||\n (typeof appContext === \"string\" && appContext.trim() === \"\") ||\n (typeof appContext === \"object\" && Object.keys(appContext).length === 0);\n\n if (isEmptyContext) {\n return;\n }\n\n // Create the context content\n const contextContent =\n typeof appContext === \"string\"\n ? appContext\n : JSON.stringify(appContext, null, 2);\n const contextMessageContent = `App Context:\\n${contextContent}`;\n const contextMessagePrefix = \"App Context:\\n\";\n\n // Helper to get message content as string\n const getContentString = (msg: any): string | null => {\n if (typeof msg.content === \"string\") return msg.content;\n if (Array.isArray(msg.content) && msg.content[0]?.text)\n return msg.content[0].text;\n return null;\n };\n\n // Find the first system/developer message (not our context message) to determine\n // where to insert our context message (right after it)\n let firstSystemIndex = -1;\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n // Skip if this is our own context message\n if (content?.startsWith(contextMessagePrefix)) {\n continue;\n }\n firstSystemIndex = i;\n break;\n }\n }\n\n // Check if our context message already exists\n let existingContextIndex = -1;\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n if (content?.startsWith(contextMessagePrefix)) {\n existingContextIndex = i;\n break;\n }\n }\n }\n\n // Create the context message\n const contextMessage = new SystemMessage({ content: contextMessageContent });\n\n let updatedMessages;\n\n if (existingContextIndex !== -1) {\n // Replace existing context message\n updatedMessages = [...messages];\n updatedMessages[existingContextIndex] = contextMessage;\n } else {\n // Insert after the first system message, or at position 0 if no system message\n const insertIndex = firstSystemIndex !== -1 ? firstSystemIndex + 1 : 0;\n updatedMessages = [\n ...messages.slice(0, insertIndex),\n contextMessage,\n ...messages.slice(insertIndex),\n ];\n }\n\n return {\n ...state,\n messages: updatedMessages,\n };\n};\n\n/**\n * CopilotKit Middleware for LangGraph agents.\n *\n * Enables:\n * - Dynamic frontend tools from state.tools\n * - Context provided from CopilotKit useCopilotReadable\n *\n * Works with any agent (prebuilt or custom).\n *\n * @example\n * ```typescript\n * import { createAgent } from \"langchain\";\n * import { copilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const agent = createAgent({\n * model: \"gpt-4o\",\n * tools: [backendTool],\n * middleware: [copilotkitMiddleware],\n * });\n * ```\n */\nconst copilotKitStateSchema = z.object({\n copilotkit: zodState(\n z\n .object({\n actions: z.array(z.any()),\n context: z.any().optional(),\n // A2UI decision forwarded from the runtime ({ injectTool: bool | str }).\n // Declared so the state schema preserves it instead of stripping it as\n // an unknown key. Present only when CopilotRuntime has an `a2ui` config.\n a2ui: z.any().optional(),\n interceptedToolCalls: z.array(z.any()).optional(),\n originalAIMessageId: z.string().optional(),\n })\n .optional(),\n ),\n});\n\nconst buildMiddlewareInput = (exposeState: ExposeStateOption) => ({\n name: \"CopilotKitMiddleware\",\n\n stateSchema: copilotKitStateSchema as unknown as InteropZodObject,\n\n // Inject frontend tools, surface user state, and forward x-aimock-* headers\n wrapModelCall: async (request: any, handler: (req: any) => Promise<any>) => {\n request = applyStateNote(request, exposeState);\n\n // Forward x-aimock-* headers from the incoming AG-UI request\n const forwardedHeaders = getForwardedHeaders();\n if (Object.keys(forwardedHeaders).length > 0) {\n const existingSettings = request.modelSettings ?? {};\n const existingHeaders =\n (existingSettings.headers as Record<string, string>) ?? {};\n request = {\n ...request,\n modelSettings: {\n ...existingSettings,\n headers: { ...existingHeaders, ...forwardedHeaders },\n },\n };\n }\n\n // Auto-inject generate_a2ui when the frontend has registered an A2UI\n // catalog — sourced wherever the FE passed it (CopilotKit runtime proxy via\n // copilotkit.context, or AG-UI native via ag-ui.a2ui_schema). The catalog's\n // presence is the signal that the client can render A2UI surfaces, so this\n // never advertises a tool that would render nowhere while staying fully\n // zero-config. The model is inferred from request.model; the catalog id\n // binds surfaces to the FE's catalog; the built tool is stashed for\n // wrapToolCall to execute.\n // Gate auto-injection of generate_a2ui, in order:\n // (1) honor an explicit runtime opt-out (injectA2UITool: false);\n // (2) require a frontend-registered catalog (the client can render A2UI);\n // (3) don't double-inject if the agent already defines this tool.\n // When no runtime signal is present (AG-UI native path), only (2)–(3)\n // apply, keeping that path zero-config.\n let a2uiTool: any = null;\n const decision = a2uiInjectDecision(request.state);\n const optedOut = decision !== undefined && !decision;\n const a2uiCatalog =\n typeof getA2UITools === \"function\" && !optedOut\n ? resolveA2uiCatalog(request.state)\n : null;\n if (a2uiCatalog) {\n const opts: { defaultCatalogId?: string; compositionGuide?: string } = {};\n if (a2uiCatalog.catalogId) opts.defaultCatalogId = a2uiCatalog.catalogId;\n if (a2uiCatalog.compositionGuide)\n opts.compositionGuide = a2uiCatalog.compositionGuide;\n const candidate = getA2UITools(request.model, opts);\n const existingNames = new Set(\n (request.tools || []).map((t: any) => t?.name),\n );\n if (!existingNames.has(candidate.name)) {\n a2uiTool = candidate;\n a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);\n }\n }\n\n let frontendTools = request.state[\"copilotkit\"]?.actions ?? [];\n if (a2uiTool) {\n // Our generate_a2ui replaces the runtime's render tool — don't advertise\n // both. Drop the render tool the A2UI middleware injected.\n const drop = typeof decision === \"string\" ? decision : \"render_a2ui\";\n frontendTools = frontendTools.filter(\n (t: any) => (t?.function?.name ?? t?.name) !== drop,\n );\n }\n\n if (frontendTools.length === 0 && !a2uiTool) {\n return handler(request);\n }\n\n const existingTools = request.tools || [];\n const mergedTools = [\n ...existingTools,\n ...(a2uiTool ? [a2uiTool] : []),\n ...frontendTools,\n ];\n\n return handler({\n ...request,\n tools: mergedTools,\n });\n },\n\n // Execute the dynamically-advertised generate_a2ui tool. It is not in the\n // agent's static tool registry, so the tool node cannot run it on its own;\n // we supply the implementation (built with the inferred model) for that one\n // tool. This hook's presence also disables createAgent's \"unknown tool\"\n // guard for dynamically-advertised tools.\n wrapToolCall: async (request: any, handler: (req: any) => Promise<any>) => {\n const tool = a2uiToolsByThread.get(a2uiThreadKey(request.state));\n if (tool && !request.tool && request.toolCall?.name === tool.name) {\n return handler({ ...request, tool });\n }\n return handler(request);\n },\n\n beforeAgent: createAppContextBeforeAgent,\n\n // Restore frontend tool calls to AIMessage before agent exits\n afterAgent: (state) => {\n // Drop the bridged A2UI tool for this run — all tool calls for the turn\n // have executed by now; the next model call re-stashes if needed.\n a2uiToolsByThread.delete(a2uiThreadKey(state));\n\n const interceptedToolCalls = state[\"copilotkit\"]?.interceptedToolCalls;\n const originalMessageId = state[\"copilotkit\"]?.originalAIMessageId;\n\n if (!interceptedToolCalls?.length || !originalMessageId) {\n return;\n }\n\n let messageFound = false;\n const updatedMessages = state.messages.map((msg: any) => {\n if (AIMessage.isInstance(msg) && msg.id === originalMessageId) {\n messageFound = true;\n const existingToolCalls = msg.tool_calls || [];\n return new AIMessage({\n content: msg.content,\n tool_calls: [...existingToolCalls, ...interceptedToolCalls],\n id: msg.id,\n });\n }\n return msg;\n });\n\n // Only clear intercepted state if we successfully restored the tool calls\n if (!messageFound) {\n console.warn(\n `CopilotKit: Could not find message with id ${originalMessageId} to restore tool calls`,\n );\n return;\n }\n\n return {\n messages: updatedMessages,\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: undefined,\n originalAIMessageId: undefined,\n },\n };\n },\n\n // Intercept frontend tool calls after model returns, before ToolNode executes\n afterModel: (state) => {\n const frontendTools = state[\"copilotkit\"]?.actions ?? [];\n if (frontendTools.length === 0) return;\n\n const frontendToolNames = new Set(\n frontendTools.map((t: any) => t.function?.name || t.name),\n );\n\n const lastMessage = state.messages[state.messages.length - 1];\n if (!AIMessage.isInstance(lastMessage) || !lastMessage.tool_calls?.length) {\n return;\n }\n\n const backendToolCalls: any[] = [];\n const frontendToolCalls: any[] = [];\n\n for (const call of lastMessage.tool_calls) {\n if (frontendToolNames.has(call.name)) {\n frontendToolCalls.push(call);\n } else {\n backendToolCalls.push(call);\n }\n }\n\n if (frontendToolCalls.length === 0) return;\n\n const updatedAIMessage = new AIMessage({\n content: lastMessage.content,\n tool_calls: backendToolCalls,\n id: lastMessage.id,\n });\n\n return {\n messages: [...state.messages.slice(0, -1), updatedAIMessage],\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: frontendToolCalls,\n originalAIMessageId: lastMessage.id,\n },\n };\n },\n});\n\n/**\n * Build a CopilotKit middleware instance with custom options.\n *\n * Use this when you want to override the default state-exposure behavior\n * (for example to hide a sensitive key, or to use an explicit allowlist).\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: { exposeState?: ExposeStateOption } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(buildMiddlewareInput(exposeState) as any);\n};\n\n/**\n * Default CopilotKit middleware singleton — does NOT surface user state\n * to the LLM. Pass `exposeState: true` (or an allowlist) to\n * {@link createCopilotkitMiddleware} to opt in.\n */\nexport const copilotkitMiddleware = createCopilotkitMiddleware();\n"],"mappings":";;;;;;AAgBA,MAAM,oCAAoB,IAAI,KAAkB;AAChD,MAAM,0BAA0B;AAChC,MAAM,iBAAiB,UACpB,OAAO,aAAwB;;;;;;;;;;;;;;AAelC,MAAM,sBACJ,UAC6D;CAC7D,MAAM,aAAa,QAAQ,UAAU;AACrC,KAAI,YAAY;EACd,IAAI;AACJ,MAAI;AAGF,gBADE,OAAO,eAAe,WAAW,KAAK,MAAM,WAAW,GAAG,aACxC;UACd;AAGR,SAAO,EAAE,WAAW;;CAEtB,MAAM,UAAU,OAAO,YAAY;AACnC,MAAK,MAAM,SAAS,MAAM,QAAQ,QAAQ,GAAG,UAAU,EAAE,EAAE;EACzD,MAAM,cAAc,OAAO,eAAe;EAC1C,MAAM,QAAQ,OAAO,SAAS;AAC9B,MAAI,CAAC,YAAY,SAAS,eAAe,IAAI,CAAC,MAAO;AAErD,SAAO;GAAE,kBAAkB;GAAO,WADpB,iBAAiB,KAAK,MAAM,GACW;GAAI;;AAE3D,QAAO;;;;;;;;;;AAWT,MAAM,sBAAsB,UAA6C;CACvE,MAAM,OAAO,OAAO,YAAY;AAChC,KAAI,QAAQ,OAAO,SAAS,YAAY,gBAAgB,KACtD,QAAO,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;AAuChB,SAAgB,SAA2B,QAA8B;CACvE,MAAM,MAAO,OACX;AAEF,KAAI,OAAO,OAAO,QAAQ,YAAY,EAAE,gBAAgB,MAAM;EAC5D,IAAI;AACJ,MAAI,aAAa,EACf,aAAa;AACX,OAAI,OAAQ,QAAO;AAInB,OAAI;IACF,MAAM,sBACJ,EAGA;AACF,aACE,OAAO,wBAAwB,aAC3B,oBAAoB,OAAO,GAC3B,EAAE;WACF;AACN,aAAS,EAAE;;AAEb,UAAO;KAEV;;AAEH,QAAO;;;;;;;AAQT,MAAM,sBAA2C,IAAI,IAAI;CACvD;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAgBF,MAAM,kBACJ,OACA,WACkB;AAClB,KAAI,WAAW,MAAO,QAAO;CAE7B,MAAM,QAAoC,MAAM,QAAQ,OAAO,GAC3D,IAAI,IAAI,OAAO,GACf;CAEJ,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE;AACpC,MACE,QACI,CAAC,MAAM,IAAI,IAAI,GACf,oBAAoB,IAAI,IAAI,IAAI,IAAI,WAAW,IAAI,CAEvD;EAEF,MAAM,QAAQ,MAAM;AACpB,MACE,UAAU,UACV,UAAU,QACV,UAAU,MACT,MAAM,QAAQ,MAAM,IAAI,MAAM,WAAW,KACzC,OAAO,UAAU,YAChB,CAAC,MAAM,QAAQ,MAAM,IACrB,OAAO,KAAK,MAAiC,CAAC,WAAW,EAE3D;AAEF,WAAS,OAAO;;AAGlB,KAAI,OAAO,KAAK,SAAS,CAAC,WAAW,EAAG,QAAO;CAE/C,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,UAAU,UAAU,MAAM,EAAE;SAClC;AACN,SAAO,OAAO,SAAS;;AAEzB,QAAO,yBAAyB;;AAGlC,MAAM,kBAAkB,SAAc,WAAmC;CACvE,MAAM,OAAO,eACV,QAAQ,SAAS,EAAE,EACpB,OACD;AACD,KAAI,CAAC,KAAM,QAAO;CAElB,MAAM,WAAW,QAAQ;AACzB,KAAI,YAAY,KACd,QAAO;EAAE,GAAG;EAAS,cAAc,IAAI,cAAc,EAAE,SAAS,MAAM,CAAC;EAAE;CAG3E,MAAM,WACJ,OAAO,aAAa,WAChB,WACA,OAAO,SAAS,YAAY,WAC1B,SAAS,UACT,OAAO,SAAS,QAAQ;AAChC,QAAO;EACL,GAAG;EACH,cAAc,IAAI,cAAc,EAAE,SAAS,GAAG,SAAS,MAAM,QAAQ,CAAC;EACvE;;AAGH,MAAM,+BAA+B,OAAO,YAAY;CACtD,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,YAAY,SAAS,WAAW,EACnC;CAIF,MAAM,aAAa,MAAM,eAAe,WAAW,SAAS;AAQ5D,KAJE,CAAC,cACA,OAAO,eAAe,YAAY,WAAW,MAAM,KAAK,MACxD,OAAO,eAAe,YAAY,OAAO,KAAK,WAAW,CAAC,WAAW,EAGtE;CAQF,MAAM,wBAAwB,iBAH5B,OAAO,eAAe,WAClB,aACA,KAAK,UAAU,YAAY,MAAM,EAAE;CAEzC,MAAM,uBAAuB;CAG7B,MAAM,oBAAoB,QAA4B;AACpD,MAAI,OAAO,IAAI,YAAY,SAAU,QAAO,IAAI;AAChD,MAAI,MAAM,QAAQ,IAAI,QAAQ,IAAI,IAAI,QAAQ,IAAI,KAChD,QAAO,IAAI,QAAQ,GAAG;AACxB,SAAO;;CAKT,IAAI,mBAAmB;AAEvB,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,IAAI,YAAY;AAC7B,MAAI,SAAS,YAAY,SAAS,aAAa;AAG7C,OAFgB,iBAAiB,IAAI,EAExB,WAAW,qBAAqB,CAC3C;AAEF,sBAAmB;AACnB;;;CAKJ,IAAI,uBAAuB;AAC3B,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,IAAI,YAAY;AAC7B,MAAI,SAAS,YAAY,SAAS,aAEhC;OADgB,iBAAiB,IAAI,EACxB,WAAW,qBAAqB,EAAE;AAC7C,2BAAuB;AACvB;;;;CAMN,MAAM,iBAAiB,IAAI,cAAc,EAAE,SAAS,uBAAuB,CAAC;CAE5E,IAAI;AAEJ,KAAI,yBAAyB,IAAI;AAE/B,oBAAkB,CAAC,GAAG,SAAS;AAC/B,kBAAgB,wBAAwB;QACnC;EAEL,MAAM,cAAc,qBAAqB,KAAK,mBAAmB,IAAI;AACrE,oBAAkB;GAChB,GAAG,SAAS,MAAM,GAAG,YAAY;GACjC;GACA,GAAG,SAAS,MAAM,YAAY;GAC/B;;AAGH,QAAO;EACL,GAAG;EACH,UAAU;EACX;;;;;;;;;;;;;;;;;;;;;;;AAwBH,MAAM,wBAAwB,EAAE,OAAO,EACrC,YAAY,SACV,EACG,OAAO;CACN,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;CACzB,SAAS,EAAE,KAAK,CAAC,UAAU;CAI3B,MAAM,EAAE,KAAK,CAAC,UAAU;CACxB,sBAAsB,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACjD,qBAAqB,EAAE,QAAQ,CAAC,UAAU;CAC3C,CAAC,CACD,UAAU,CACd,EACF,CAAC;AAEF,MAAM,wBAAwB,iBAAoC;CAChE,MAAM;CAEN,aAAa;CAGb,eAAe,OAAO,SAAc,YAAwC;AAC1E,YAAU,eAAe,SAAS,YAAY;EAG9C,MAAM,mBAAmB,qBAAqB;AAC9C,MAAI,OAAO,KAAK,iBAAiB,CAAC,SAAS,GAAG;GAC5C,MAAM,mBAAmB,QAAQ,iBAAiB,EAAE;GACpD,MAAM,kBACH,iBAAiB,WAAsC,EAAE;AAC5D,aAAU;IACR,GAAG;IACH,eAAe;KACb,GAAG;KACH,SAAS;MAAE,GAAG;MAAiB,GAAG;MAAkB;KACrD;IACF;;EAiBH,IAAI,WAAgB;EACpB,MAAM,WAAW,mBAAmB,QAAQ,MAAM;EAElD,MAAM,cACJ,OAAO,iBAAiB,cAAc,EAFvB,aAAa,UAAa,CAAC,YAGtC,mBAAmB,QAAQ,MAAM,GACjC;AACN,MAAI,aAAa;GACf,MAAM,OAAiE,EAAE;AACzE,OAAI,YAAY,UAAW,MAAK,mBAAmB,YAAY;AAC/D,OAAI,YAAY,iBACd,MAAK,mBAAmB,YAAY;GACtC,MAAM,YAAY,aAAa,QAAQ,OAAO,KAAK;AAInD,OAAI,CAHkB,IAAI,KACvB,QAAQ,SAAS,EAAE,EAAE,KAAK,MAAW,GAAG,KAAK,CAC/C,CACkB,IAAI,UAAU,KAAK,EAAE;AACtC,eAAW;AACX,sBAAkB,IAAI,cAAc,QAAQ,MAAM,EAAE,SAAS;;;EAIjE,IAAI,gBAAgB,QAAQ,MAAM,eAAe,WAAW,EAAE;AAC9D,MAAI,UAAU;GAGZ,MAAM,OAAO,OAAO,aAAa,WAAW,WAAW;AACvD,mBAAgB,cAAc,QAC3B,OAAY,GAAG,UAAU,QAAQ,GAAG,UAAU,KAChD;;AAGH,MAAI,cAAc,WAAW,KAAK,CAAC,SACjC,QAAO,QAAQ,QAAQ;EAIzB,MAAM,cAAc;GAClB,GAFoB,QAAQ,SAAS,EAAE;GAGvC,GAAI,WAAW,CAAC,SAAS,GAAG,EAAE;GAC9B,GAAG;GACJ;AAED,SAAO,QAAQ;GACb,GAAG;GACH,OAAO;GACR,CAAC;;CAQJ,cAAc,OAAO,SAAc,YAAwC;EACzE,MAAM,OAAO,kBAAkB,IAAI,cAAc,QAAQ,MAAM,CAAC;AAChE,MAAI,QAAQ,CAAC,QAAQ,QAAQ,QAAQ,UAAU,SAAS,KAAK,KAC3D,QAAO,QAAQ;GAAE,GAAG;GAAS;GAAM,CAAC;AAEtC,SAAO,QAAQ,QAAQ;;CAGzB,aAAa;CAGb,aAAa,UAAU;AAGrB,oBAAkB,OAAO,cAAc,MAAM,CAAC;EAE9C,MAAM,uBAAuB,MAAM,eAAe;EAClD,MAAM,oBAAoB,MAAM,eAAe;AAE/C,MAAI,CAAC,sBAAsB,UAAU,CAAC,kBACpC;EAGF,IAAI,eAAe;EACnB,MAAM,kBAAkB,MAAM,SAAS,KAAK,QAAa;AACvD,OAAI,UAAU,WAAW,IAAI,IAAI,IAAI,OAAO,mBAAmB;AAC7D,mBAAe;IACf,MAAM,oBAAoB,IAAI,cAAc,EAAE;AAC9C,WAAO,IAAI,UAAU;KACnB,SAAS,IAAI;KACb,YAAY,CAAC,GAAG,mBAAmB,GAAG,qBAAqB;KAC3D,IAAI,IAAI;KACT,CAAC;;AAEJ,UAAO;IACP;AAGF,MAAI,CAAC,cAAc;AACjB,WAAQ,KACN,8CAA8C,kBAAkB,wBACjE;AACD;;AAGF,SAAO;GACL,UAAU;GACV,YAAY;IACV,GAAG,MAAM;IACT,sBAAsB;IACtB,qBAAqB;IACtB;GACF;;CAIH,aAAa,UAAU;EACrB,MAAM,gBAAgB,MAAM,eAAe,WAAW,EAAE;AACxD,MAAI,cAAc,WAAW,EAAG;EAEhC,MAAM,oBAAoB,IAAI,IAC5B,cAAc,KAAK,MAAW,EAAE,UAAU,QAAQ,EAAE,KAAK,CAC1D;EAED,MAAM,cAAc,MAAM,SAAS,MAAM,SAAS,SAAS;AAC3D,MAAI,CAAC,UAAU,WAAW,YAAY,IAAI,CAAC,YAAY,YAAY,OACjE;EAGF,MAAM,mBAA0B,EAAE;EAClC,MAAM,oBAA2B,EAAE;AAEnC,OAAK,MAAM,QAAQ,YAAY,WAC7B,KAAI,kBAAkB,IAAI,KAAK,KAAK,CAClC,mBAAkB,KAAK,KAAK;MAE5B,kBAAiB,KAAK,KAAK;AAI/B,MAAI,kBAAkB,WAAW,EAAG;EAEpC,MAAM,mBAAmB,IAAI,UAAU;GACrC,SAAS,YAAY;GACrB,YAAY;GACZ,IAAI,YAAY;GACjB,CAAC;AAEF,SAAO;GACL,UAAU,CAAC,GAAG,MAAM,SAAS,MAAM,GAAG,GAAG,EAAE,iBAAiB;GAC5D,YAAY;IACV,GAAG,MAAM;IACT,sBAAsB;IACtB,qBAAqB,YAAY;IAClC;GACF;;CAEJ;;;;;;;;;;;;;;;;AAiBD,MAAa,8BACX,UAA+C,EAAE,KAC9C;AAEH,QAAO,iBAAiB,qBADJ,QAAQ,eAAe,MACc,CAAQ;;;;;;;AAQnE,MAAa,uBAAuB,4BAA4B"}
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/langgraph/middleware.ts"],"sourcesContent":["import { createMiddleware, AIMessage, SystemMessage } from \"langchain\";\nimport type { InteropZodObject } from \"@langchain/core/utils/types\";\nimport type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from \"@standard-schema/spec\";\nimport * as z from \"zod\";\nimport { getA2UITools } from \"@ag-ui/langgraph\";\nimport { getForwardedHeaders } from \"../header-propagation\";\n\n// ---------------------------------------------------------------------------\n// Auto-A2UI: bridge the inferred model's generate_a2ui tool from wrapModelCall\n// (the only hook that exposes the bound model) to wrapToolCall (where the tool\n// actually executes but the model is absent). Keyed by the run's thread id so\n// concurrent runs don't clobber each other.\n// ---------------------------------------------------------------------------\nconst a2uiToolsByThread = new Map<string, any>();\nconst A2UI_DEFAULT_THREAD_KEY = \"__copilotkit_a2ui_default__\";\nconst a2uiThreadKey = (state: any): string =>\n (state?.thread_id as string) || A2UI_DEFAULT_THREAD_KEY;\n\n/**\n * Find the frontend-registered A2UI catalog wherever it was passed. Returns\n * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`\n * (so the tool is never advertised when the client can't render A2UI). Two\n * delivery paths, depending on how the agent is served:\n * - AG-UI native endpoint → `state[\"ag-ui\"].a2ui_schema` (JSON\n * `{ catalogId, components }`); the toolkit reads it from state itself.\n * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing\n * the A2UI catalog (catalog id + component schemas as text), passed to the\n * subagent via `compositionGuide`.\n * `catalogId` binds generated surfaces to the frontend's catalog so BYOC\n * custom catalogs render their own components (not the basic one).\n */\nconst resolveA2uiCatalog = (\n state: any,\n): { compositionGuide?: string; catalogId?: string } | null => {\n const a2uiSchema = state?.[\"ag-ui\"]?.a2ui_schema;\n if (a2uiSchema) {\n let catalogId: string | undefined;\n try {\n const parsed =\n typeof a2uiSchema === \"string\" ? JSON.parse(a2uiSchema) : a2uiSchema;\n catalogId = parsed?.catalogId;\n } catch {\n // non-JSON schema — fall back to the toolkit's basic catalog\n }\n return { catalogId };\n }\n const context = state?.copilotkit?.context;\n for (const entry of Array.isArray(context) ? context : []) {\n const description = entry?.description ?? \"\";\n const value = entry?.value ?? \"\";\n if (!description.includes(\"A2UI catalog\") || !value) continue;\n const match = /^\\s*-\\s+(\\S+)/m.exec(value);\n return { compositionGuide: value, catalogId: match?.[1] };\n }\n return null;\n};\n\n/**\n * The A2UI `injectA2UITool` decision. The `@ag-ui/a2ui-middleware` forwards it on\n * `forwardedProps`, which `ag-ui-langgraph` surfaces into agent state at\n * `state[\"ag-ui\"].inject_a2ui_tool` — present only when the host turned the\n * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means\n * no signal (off, or no A2UI middleware in the pipeline) → no auto-injection.\n */\nconst a2uiInjectDecision = (state: any): boolean | string | undefined =>\n state?.[\"ag-ui\"]?.inject_a2ui_tool;\n\ntype WithJsonSchema<T> = T extends { \"~standard\": infer S }\n ? Omit<T, \"~standard\"> & {\n \"~standard\": S &\n StandardJSONSchemaV1.Props<\n S extends StandardSchemaV1.Props<infer I, any> ? I : unknown,\n S extends StandardSchemaV1.Props<any, infer O> ? O : unknown\n >;\n }\n : T;\n\n/**\n * Augment a Standard-Schema–compatible schema (e.g. Zod) with a\n * `~standard.jsonSchema.input` hook so LangGraph's\n * `getJsonSchemaFromSchema` (called from `StateSchema.getJsonSchema`)\n * can serialize the field.\n *\n * Without this, Zod v4 fields carry `~standard.validate` + `vendor` only,\n * and `isStandardJSONSchema()` returns false, so the field is silently\n * dropped from the graph's `output_schema`. That makes AG-UI\n * `STATE_SNAPSHOT` events filter the field out of the payload sent to\n * the frontend even though the underlying thread state has the value.\n *\n * Use this on any custom state field you want visible to the frontend\n * via `useAgent().state.*`.\n *\n * @example\n * ```ts\n * import { zodState } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const stateSchema = z.object({\n * todos: zodState(z.array(TodoSchema).default(() => [])),\n * });\n * ```\n */\nexport function zodState<T extends object>(schema: T): WithJsonSchema<T> {\n const std = (schema as { \"~standard\"?: { jsonSchema?: unknown } })[\n \"~standard\"\n ];\n if (std && typeof std === \"object\" && !(\"jsonSchema\" in std)) {\n let cached: Record<string, unknown> | undefined;\n std.jsonSchema = {\n input: () => {\n if (cached) return cached;\n // Prefer zod-v4's native `toJSONSchema` when available. Falls back to\n // an empty object, which is sufficient for the field to appear in the\n // graph's output_schema (langgraph-api treats it as an opaque field).\n try {\n const maybeV4ToJsonSchema = (\n z as unknown as {\n toJSONSchema?: (s: unknown) => Record<string, unknown>;\n }\n ).toJSONSchema;\n cached =\n typeof maybeV4ToJsonSchema === \"function\"\n ? maybeV4ToJsonSchema(schema)\n : {};\n } catch {\n cached = {};\n }\n return cached;\n },\n };\n }\n return schema as WithJsonSchema<T>;\n}\n\n/**\n * Internal/framework state keys that should never be auto-surfaced to the\n * LLM as user-facing state. These are reducer-managed message buckets,\n * CopilotKit/AG-UI plumbing, or graph-internal scaffolding.\n */\nconst RESERVED_STATE_KEYS: ReadonlySet<string> = new Set([\n \"messages\",\n \"copilotkit\",\n \"ag-ui\",\n \"tools\",\n \"structured_response\",\n \"thread_id\",\n \"remaining_steps\",\n]);\n\n/**\n * Controls how user-defined state keys are surfaced into the LLM prompt\n * on every model call. Off by default to avoid leaking arbitrary state\n * into prompts; opt in explicitly.\n *\n * - `false` (default) — never surface state.\n * - `true` — every state key not in the reserved internal set and not\n * prefixed with `_` is JSON-serialized into a \"Current agent state:\"\n * note appended to the system prompt.\n * - `string[]` — only surface the named keys (use this when you want\n * explicit control over what the LLM sees, e.g. `[\"liked\", \"todos\"]`).\n */\nexport type ExposeStateOption = boolean | readonly string[];\n\nconst buildStateNote = (\n state: Record<string, unknown>,\n expose: ExposeStateOption,\n): string | null => {\n if (expose === false) return null;\n\n const allow: ReadonlySet<string> | null = Array.isArray(expose)\n ? new Set(expose)\n : null;\n\n const snapshot: Record<string, unknown> = {};\n for (const key of Object.keys(state)) {\n if (\n allow\n ? !allow.has(key)\n : RESERVED_STATE_KEYS.has(key) || key.startsWith(\"_\")\n ) {\n continue;\n }\n const value = state[key];\n if (\n value === undefined ||\n value === null ||\n value === \"\" ||\n (Array.isArray(value) && value.length === 0) ||\n (typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0)\n ) {\n continue;\n }\n snapshot[key] = value;\n }\n\n if (Object.keys(snapshot).length === 0) return null;\n\n let body: string;\n try {\n body = JSON.stringify(snapshot, null, 2);\n } catch {\n body = String(snapshot);\n }\n return `Current agent state:\\n${body}`;\n};\n\nconst applyStateNote = (request: any, expose: ExposeStateOption): any => {\n const note = buildStateNote(\n (request.state ?? {}) as Record<string, unknown>,\n expose,\n );\n if (!note) return request;\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: new SystemMessage({ content: note }) };\n }\n // existing may be a string OR a SystemMessage\n const baseText =\n typeof existing === \"string\"\n ? existing\n : typeof existing.content === \"string\"\n ? existing.content\n : String(existing.content);\n return {\n ...request,\n systemPrompt: new SystemMessage({ content: `${baseText}\\n\\n${note}` }),\n };\n};\n\nconst createAppContextBeforeAgent = (state, runtime) => {\n const messages = state.messages;\n\n if (!messages || messages.length === 0) {\n return;\n }\n\n // Get app context from runtime\n const appContext = state[\"copilotkit\"]?.context ?? runtime?.context;\n\n // Check if appContext is missing or empty\n const isEmptyContext =\n !appContext ||\n (typeof appContext === \"string\" && appContext.trim() === \"\") ||\n (typeof appContext === \"object\" && Object.keys(appContext).length === 0);\n\n if (isEmptyContext) {\n return;\n }\n\n // Create the context content\n const contextContent =\n typeof appContext === \"string\"\n ? appContext\n : JSON.stringify(appContext, null, 2);\n const contextMessageContent = `App Context:\\n${contextContent}`;\n const contextMessagePrefix = \"App Context:\\n\";\n\n // Helper to get message content as string\n const getContentString = (msg: any): string | null => {\n if (typeof msg.content === \"string\") return msg.content;\n if (Array.isArray(msg.content) && msg.content[0]?.text)\n return msg.content[0].text;\n return null;\n };\n\n // Find the first system/developer message (not our context message) to determine\n // where to insert our context message (right after it)\n let firstSystemIndex = -1;\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n // Skip if this is our own context message\n if (content?.startsWith(contextMessagePrefix)) {\n continue;\n }\n firstSystemIndex = i;\n break;\n }\n }\n\n // Check if our context message already exists\n let existingContextIndex = -1;\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n const type = msg._getType?.();\n if (type === \"system\" || type === \"developer\") {\n const content = getContentString(msg);\n if (content?.startsWith(contextMessagePrefix)) {\n existingContextIndex = i;\n break;\n }\n }\n }\n\n // Create the context message\n const contextMessage = new SystemMessage({ content: contextMessageContent });\n\n let updatedMessages;\n\n if (existingContextIndex !== -1) {\n // Replace existing context message\n updatedMessages = [...messages];\n updatedMessages[existingContextIndex] = contextMessage;\n } else {\n // Insert after the first system message, or at position 0 if no system message\n const insertIndex = firstSystemIndex !== -1 ? firstSystemIndex + 1 : 0;\n updatedMessages = [\n ...messages.slice(0, insertIndex),\n contextMessage,\n ...messages.slice(insertIndex),\n ];\n }\n\n return {\n ...state,\n messages: updatedMessages,\n };\n};\n\n/**\n * CopilotKit Middleware for LangGraph agents.\n *\n * Enables:\n * - Dynamic frontend tools from state.tools\n * - Context provided from CopilotKit useCopilotReadable\n *\n * Works with any agent (prebuilt or custom).\n *\n * @example\n * ```typescript\n * import { createAgent } from \"langchain\";\n * import { copilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const agent = createAgent({\n * model: \"gpt-4o\",\n * tools: [backendTool],\n * middleware: [copilotkitMiddleware],\n * });\n * ```\n */\nconst copilotKitStateSchema = z.object({\n copilotkit: zodState(\n z\n .object({\n actions: z.array(z.any()),\n context: z.any().optional(),\n interceptedToolCalls: z.array(z.any()).optional(),\n originalAIMessageId: z.string().optional(),\n })\n .optional(),\n ),\n});\n\nconst buildMiddlewareInput = (exposeState: ExposeStateOption) => ({\n name: \"CopilotKitMiddleware\",\n\n stateSchema: copilotKitStateSchema as unknown as InteropZodObject,\n\n // Inject frontend tools, surface user state, and forward x-aimock-* headers\n wrapModelCall: async (request: any, handler: (req: any) => Promise<any>) => {\n request = applyStateNote(request, exposeState);\n\n // Forward x-aimock-* headers from the incoming AG-UI request\n const forwardedHeaders = getForwardedHeaders();\n if (Object.keys(forwardedHeaders).length > 0) {\n const existingSettings = request.modelSettings ?? {};\n const existingHeaders =\n (existingSettings.headers as Record<string, string>) ?? {};\n request = {\n ...request,\n modelSettings: {\n ...existingSettings,\n headers: { ...existingHeaders, ...forwardedHeaders },\n },\n };\n }\n\n // Opt-in auto-injection of generate_a2ui:\n // (1) only inject when the A2UI injectA2UITool flag is truthy (forwarded by\n // @ag-ui/a2ui-middleware and surfaced at state[\"ag-ui\"].inject_a2ui_tool);\n // (2) don't double-inject if the agent already defines this tool.\n // The catalog (when present) only binds surfaces to the FE's catalog; it is\n // not the gate. The model is inferred from request.model; the built tool is\n // stashed for wrapToolCall to execute.\n let a2uiTool: any = null;\n const decision = a2uiInjectDecision(request.state);\n if (typeof getA2UITools === \"function\" && decision) {\n const catalog = resolveA2uiCatalog(request.state);\n const opts: { defaultCatalogId?: string; compositionGuide?: string } = {};\n if (catalog?.catalogId) opts.defaultCatalogId = catalog.catalogId;\n if (catalog?.compositionGuide)\n opts.compositionGuide = catalog.compositionGuide;\n const candidate = getA2UITools(request.model, opts);\n const existingNames = new Set(\n (request.tools || []).map((t: any) => t?.name),\n );\n if (!existingNames.has(candidate.name)) {\n a2uiTool = candidate;\n a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);\n }\n }\n\n let frontendTools = request.state[\"copilotkit\"]?.actions ?? [];\n if (a2uiTool) {\n // Our generate_a2ui replaces the runtime's render tool — don't advertise\n // both. Drop the render tool the A2UI middleware injected.\n const drop = typeof decision === \"string\" ? decision : \"render_a2ui\";\n frontendTools = frontendTools.filter(\n (t: any) => (t?.function?.name ?? t?.name) !== drop,\n );\n }\n\n if (frontendTools.length === 0 && !a2uiTool) {\n return handler(request);\n }\n\n const existingTools = request.tools || [];\n const mergedTools = [\n ...existingTools,\n ...(a2uiTool ? [a2uiTool] : []),\n ...frontendTools,\n ];\n\n return handler({\n ...request,\n tools: mergedTools,\n });\n },\n\n // Execute the dynamically-advertised generate_a2ui tool. It is not in the\n // agent's static tool registry, so the tool node cannot run it on its own;\n // we supply the implementation (built with the inferred model) for that one\n // tool. This hook's presence also disables createAgent's \"unknown tool\"\n // guard for dynamically-advertised tools.\n wrapToolCall: async (request: any, handler: (req: any) => Promise<any>) => {\n const tool = a2uiToolsByThread.get(a2uiThreadKey(request.state));\n if (tool && !request.tool && request.toolCall?.name === tool.name) {\n return handler({ ...request, tool });\n }\n return handler(request);\n },\n\n beforeAgent: createAppContextBeforeAgent,\n\n // Restore frontend tool calls to AIMessage before agent exits\n afterAgent: (state) => {\n // Drop the bridged A2UI tool for this run — all tool calls for the turn\n // have executed by now; the next model call re-stashes if needed.\n a2uiToolsByThread.delete(a2uiThreadKey(state));\n\n const interceptedToolCalls = state[\"copilotkit\"]?.interceptedToolCalls;\n const originalMessageId = state[\"copilotkit\"]?.originalAIMessageId;\n\n if (!interceptedToolCalls?.length || !originalMessageId) {\n return;\n }\n\n let messageFound = false;\n const updatedMessages = state.messages.map((msg: any) => {\n if (AIMessage.isInstance(msg) && msg.id === originalMessageId) {\n messageFound = true;\n const existingToolCalls = msg.tool_calls || [];\n return new AIMessage({\n content: msg.content,\n tool_calls: [...existingToolCalls, ...interceptedToolCalls],\n id: msg.id,\n });\n }\n return msg;\n });\n\n // Only clear intercepted state if we successfully restored the tool calls\n if (!messageFound) {\n console.warn(\n `CopilotKit: Could not find message with id ${originalMessageId} to restore tool calls`,\n );\n return;\n }\n\n return {\n messages: updatedMessages,\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: undefined,\n originalAIMessageId: undefined,\n },\n };\n },\n\n // Intercept frontend tool calls after model returns, before ToolNode executes\n afterModel: (state) => {\n const frontendTools = state[\"copilotkit\"]?.actions ?? [];\n if (frontendTools.length === 0) return;\n\n const frontendToolNames = new Set(\n frontendTools.map((t: any) => t.function?.name || t.name),\n );\n\n const lastMessage = state.messages[state.messages.length - 1];\n if (!AIMessage.isInstance(lastMessage) || !lastMessage.tool_calls?.length) {\n return;\n }\n\n const backendToolCalls: any[] = [];\n const frontendToolCalls: any[] = [];\n\n for (const call of lastMessage.tool_calls) {\n if (frontendToolNames.has(call.name)) {\n frontendToolCalls.push(call);\n } else {\n backendToolCalls.push(call);\n }\n }\n\n if (frontendToolCalls.length === 0) return;\n\n const updatedAIMessage = new AIMessage({\n content: lastMessage.content,\n tool_calls: backendToolCalls,\n id: lastMessage.id,\n });\n\n return {\n messages: [...state.messages.slice(0, -1), updatedAIMessage],\n copilotkit: {\n ...state[\"copilotkit\"],\n interceptedToolCalls: frontendToolCalls,\n originalAIMessageId: lastMessage.id,\n },\n };\n },\n});\n\n/**\n * Build a CopilotKit middleware instance with custom options.\n *\n * Use this when you want to override the default state-exposure behavior\n * (for example to hide a sensitive key, or to use an explicit allowlist).\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: { exposeState?: ExposeStateOption } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(buildMiddlewareInput(exposeState) as any);\n};\n\n/**\n * Default CopilotKit middleware singleton — does NOT surface user state\n * to the LLM. Pass `exposeState: true` (or an allowlist) to\n * {@link createCopilotkitMiddleware} to opt in.\n */\nexport const copilotkitMiddleware = createCopilotkitMiddleware();\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/sdk-js",
3
- "version": "1.59.3-alpha.2",
3
+ "version": "1.59.3-alpha.3",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "ai",
@@ -48,7 +48,7 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@ag-ui/langgraph": "0.0.35",
51
+ "@ag-ui/langgraph": "0.0.37",
52
52
  "@copilotkit/shared": "1.59.2"
53
53
  },
54
54
  "devDependencies": {
@@ -519,7 +519,7 @@ async function runWrapTool(middleware: any, request: any) {
519
519
  }
520
520
 
521
521
  describe("auto-A2UI injection", () => {
522
- it("does NOT advertise generate_a2ui when there is no A2UI catalog", async () => {
522
+ it("does NOT advertise generate_a2ui when the inject flag is absent", async () => {
523
523
  const request = makeRequest({
524
524
  state: { messages: [], thread_id: "a2ui-off" },
525
525
  tools: [{ name: "backend" }],
@@ -530,11 +530,11 @@ describe("auto-A2UI injection", () => {
530
530
  expect(received.tools.map((t: any) => t.name)).toEqual(["backend"]);
531
531
  });
532
532
 
533
- it("advertises generate_a2ui (alongside existing tools) when a catalog is present", async () => {
533
+ it("does NOT advertise generate_a2ui when a catalog is present but the flag is absent (opt-in)", async () => {
534
534
  const request = makeRequest({
535
535
  state: {
536
536
  messages: [],
537
- thread_id: "a2ui-on",
537
+ thread_id: "a2ui-noflag",
538
538
  "ag-ui": { a2ui_schema: "<components/>" },
539
539
  },
540
540
  tools: [{ name: "backend" }],
@@ -542,16 +542,34 @@ describe("auto-A2UI injection", () => {
542
542
 
543
543
  const { received } = await runWrap(copilotkitMiddleware, request);
544
544
 
545
+ const names = received.tools.map((t: any) => t.name);
546
+ expect(names).not.toContain("generate_a2ui");
547
+ expect(names).toContain("backend");
548
+ });
549
+
550
+ it("advertises generate_a2ui (alongside existing tools) when the flag is on", async () => {
551
+ const request = makeRequest({
552
+ state: {
553
+ messages: [],
554
+ thread_id: "a2ui-on",
555
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
556
+ },
557
+ tools: [{ name: "backend" }],
558
+ });
559
+
560
+ const { received } = await runWrap(copilotkitMiddleware, request);
561
+
545
562
  const names = received.tools.map((t: any) => t.name);
546
563
  expect(names).toContain("backend");
547
564
  expect(names).toContain("generate_a2ui");
548
565
  });
549
566
 
550
- it("advertises generate_a2ui when the catalog arrives via copilotkit.context (runtime-proxy path)", async () => {
567
+ it("binds to the catalog from copilotkit.context when the flag is on (runtime-proxy path)", async () => {
551
568
  const request = makeRequest({
552
569
  state: {
553
570
  messages: [],
554
571
  thread_id: "a2ui-ctx",
572
+ "ag-ui": { inject_a2ui_tool: true },
555
573
  copilotkit: {
556
574
  context: [
557
575
  {
@@ -577,7 +595,7 @@ describe("auto-A2UI injection", () => {
577
595
  const state = {
578
596
  messages: [],
579
597
  thread_id: "a2ui-exec",
580
- "ag-ui": { a2ui_schema: "<components/>" },
598
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
581
599
  };
582
600
  // First the model call infers the model + stashes the built tool.
583
601
  await runWrap(copilotkitMiddleware, makeRequest({ state, tools: [] }));
@@ -597,7 +615,7 @@ describe("auto-A2UI injection", () => {
597
615
  const state = {
598
616
  messages: [],
599
617
  thread_id: "a2ui-other",
600
- "ag-ui": { a2ui_schema: "<components/>" },
618
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
601
619
  };
602
620
  await runWrap(copilotkitMiddleware, makeRequest({ state, tools: [] }));
603
621
 
@@ -616,7 +634,7 @@ describe("auto-A2UI injection", () => {
616
634
  const state = {
617
635
  messages: [],
618
636
  thread_id: "a2ui-clean",
619
- "ag-ui": { a2ui_schema: "<components/>" },
637
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
620
638
  };
621
639
  await runWrap(copilotkitMiddleware, makeRequest({ state, tools: [] }));
622
640
  copilotkitMiddleware.afterAgent(state, {} as any);
@@ -631,15 +649,14 @@ describe("auto-A2UI injection", () => {
631
649
  expect(received.tool).toBeUndefined();
632
650
  });
633
651
 
634
- // --- runtime injectA2UITool flag (state.copilotkit.a2ui) -----------------
652
+ // --- A2UI injectA2UITool flag (forwarded → ag-ui state) ------------------
635
653
 
636
- it("does NOT advertise generate_a2ui when the runtime set injectA2UITool=false", async () => {
654
+ it("does NOT advertise generate_a2ui when inject_a2ui_tool is false", async () => {
637
655
  const request = makeRequest({
638
656
  state: {
639
657
  messages: [],
640
658
  thread_id: "a2ui-optout",
641
- "ag-ui": { a2ui_schema: "<components/>" },
642
- copilotkit: { a2ui: { injectTool: false } },
659
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: false },
643
660
  },
644
661
  tools: [{ name: "backend" }],
645
662
  });
@@ -651,13 +668,12 @@ describe("auto-A2UI injection", () => {
651
668
  expect(names).toContain("backend");
652
669
  });
653
670
 
654
- it("advertises generate_a2ui when the runtime set injectA2UITool=true", async () => {
671
+ it("advertises generate_a2ui when inject_a2ui_tool is true", async () => {
655
672
  const request = makeRequest({
656
673
  state: {
657
674
  messages: [],
658
675
  thread_id: "a2ui-optin",
659
- "ag-ui": { a2ui_schema: "<components/>" },
660
- copilotkit: { a2ui: { injectTool: true } },
676
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
661
677
  },
662
678
  tools: [{ name: "backend" }],
663
679
  });
@@ -672,9 +688,8 @@ describe("auto-A2UI injection", () => {
672
688
  state: {
673
689
  messages: [],
674
690
  thread_id: "a2ui-drop",
675
- "ag-ui": { a2ui_schema: "<components/>" },
691
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
676
692
  copilotkit: {
677
- a2ui: { injectTool: true },
678
693
  actions: [{ name: "render_a2ui" }, { name: "fe_tool" }],
679
694
  },
680
695
  },
@@ -694,7 +709,7 @@ describe("auto-A2UI injection", () => {
694
709
  state: {
695
710
  messages: [],
696
711
  thread_id: "a2ui-dup",
697
- "ag-ui": { a2ui_schema: "<components/>" },
712
+ "ag-ui": { a2ui_schema: "<components/>", inject_a2ui_tool: true },
698
713
  },
699
714
  tools: [{ name: "generate_a2ui" }],
700
715
  });
@@ -59,20 +59,14 @@ const resolveA2uiCatalog = (
59
59
  };
60
60
 
61
61
  /**
62
- * The runtime's `injectA2UITool` decision, forwarded as
63
- * `state.copilotkit.a2ui = { injectTool: boolean | string }` whenever
64
- * CopilotRuntime is configured with an `a2ui` option. Returns `undefined` when
65
- * there is no runtime signal (AG-UI native path / no A2UI config), in which
66
- * case the middleware falls back to its catalog-gated default. A falsy value
67
- * is the host explicitly opting out.
62
+ * The A2UI `injectA2UITool` decision. The `@ag-ui/a2ui-middleware` forwards it on
63
+ * `forwardedProps`, which `ag-ui-langgraph` surfaces into agent state at
64
+ * `state["ag-ui"].inject_a2ui_tool` present only when the host turned the
65
+ * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means
66
+ * no signal (off, or no A2UI middleware in the pipeline) no auto-injection.
68
67
  */
69
- const a2uiInjectDecision = (state: any): boolean | string | undefined => {
70
- const a2ui = state?.copilotkit?.a2ui;
71
- if (a2ui && typeof a2ui === "object" && "injectTool" in a2ui) {
72
- return a2ui.injectTool;
73
- }
74
- return undefined;
75
- };
68
+ const a2uiInjectDecision = (state: any): boolean | string | undefined =>
69
+ state?.["ag-ui"]?.inject_a2ui_tool;
76
70
 
77
71
  type WithJsonSchema<T> = T extends { "~standard": infer S }
78
72
  ? Omit<T, "~standard"> & {
@@ -358,10 +352,6 @@ const copilotKitStateSchema = z.object({
358
352
  .object({
359
353
  actions: z.array(z.any()),
360
354
  context: z.any().optional(),
361
- // A2UI decision forwarded from the runtime ({ injectTool: bool | str }).
362
- // Declared so the state schema preserves it instead of stripping it as
363
- // an unknown key. Present only when CopilotRuntime has an `a2ui` config.
364
- a2ui: z.any().optional(),
365
355
  interceptedToolCalls: z.array(z.any()).optional(),
366
356
  originalAIMessageId: z.string().optional(),
367
357
  })
@@ -393,32 +383,21 @@ const buildMiddlewareInput = (exposeState: ExposeStateOption) => ({
393
383
  };
394
384
  }
395
385
 
396
- // Auto-inject generate_a2ui when the frontend has registered an A2UI
397
- // catalog sourced wherever the FE passed it (CopilotKit runtime proxy via
398
- // copilotkit.context, or AG-UI native via ag-ui.a2ui_schema). The catalog's
399
- // presence is the signal that the client can render A2UI surfaces, so this
400
- // never advertises a tool that would render nowhere while staying fully
401
- // zero-config. The model is inferred from request.model; the catalog id
402
- // binds surfaces to the FE's catalog; the built tool is stashed for
403
- // wrapToolCall to execute.
404
- // Gate auto-injection of generate_a2ui, in order:
405
- // (1) honor an explicit runtime opt-out (injectA2UITool: false);
406
- // (2) require a frontend-registered catalog (the client can render A2UI);
407
- // (3) don't double-inject if the agent already defines this tool.
408
- // When no runtime signal is present (AG-UI native path), only (2)–(3)
409
- // apply, keeping that path zero-config.
386
+ // Opt-in auto-injection of generate_a2ui:
387
+ // (1) only inject when the A2UI injectA2UITool flag is truthy (forwarded by
388
+ // @ag-ui/a2ui-middleware and surfaced at state["ag-ui"].inject_a2ui_tool);
389
+ // (2) don't double-inject if the agent already defines this tool.
390
+ // The catalog (when present) only binds surfaces to the FE's catalog; it is
391
+ // not the gate. The model is inferred from request.model; the built tool is
392
+ // stashed for wrapToolCall to execute.
410
393
  let a2uiTool: any = null;
411
394
  const decision = a2uiInjectDecision(request.state);
412
- const optedOut = decision !== undefined && !decision;
413
- const a2uiCatalog =
414
- typeof getA2UITools === "function" && !optedOut
415
- ? resolveA2uiCatalog(request.state)
416
- : null;
417
- if (a2uiCatalog) {
395
+ if (typeof getA2UITools === "function" && decision) {
396
+ const catalog = resolveA2uiCatalog(request.state);
418
397
  const opts: { defaultCatalogId?: string; compositionGuide?: string } = {};
419
- if (a2uiCatalog.catalogId) opts.defaultCatalogId = a2uiCatalog.catalogId;
420
- if (a2uiCatalog.compositionGuide)
421
- opts.compositionGuide = a2uiCatalog.compositionGuide;
398
+ if (catalog?.catalogId) opts.defaultCatalogId = catalog.catalogId;
399
+ if (catalog?.compositionGuide)
400
+ opts.compositionGuide = catalog.compositionGuide;
422
401
  const candidate = getA2UITools(request.model, opts);
423
402
  const existingNames = new Set(
424
403
  (request.tools || []).map((t: any) => t?.name),