@copilotkit/sdk-js 1.75.2 → 1.77.0

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.
@@ -10,20 +10,42 @@ const a2uiToolsByThread = /* @__PURE__ */ new Map();
10
10
  const A2UI_DEFAULT_THREAD_KEY = "__copilotkit_a2ui_default__";
11
11
  const a2uiThreadKey = (state) => state?.thread_id || A2UI_DEFAULT_THREAD_KEY;
12
12
  /**
13
+ * Merge AG-UI defaults with CopilotKit overrides. Dictionaries recurse; every
14
+ * conflicting leaf uses CopilotKit, including false, null and empty values.
15
+ * Arrays and serialized strings are atomic: no positional pairing or decoding.
16
+ */
17
+ const isPropertyBag = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
18
+ const mergeProperties = (base, overrides) => Object.fromEntries(Array.from(new Set([...Object.keys(base), ...Object.keys(overrides)])).map((key) => {
19
+ return [key, Object.prototype.hasOwnProperty.call(overrides, key) ? isPropertyBag(base[key]) && isPropertyBag(overrides[key]) ? mergeProperties(base[key], overrides[key]) : overrides[key] : base[key]];
20
+ }));
21
+ const effectiveProperties = (state) => {
22
+ const source = isPropertyBag(state) ? state : {};
23
+ return mergeProperties(isPropertyBag(source["ag-ui"]) ? source["ag-ui"] : {}, isPropertyBag(source.copilotkit) ? source.copilotkit : {});
24
+ };
25
+ const decodeCatalogValue = (value) => {
26
+ if (typeof value !== "string") return value;
27
+ try {
28
+ return JSON.parse(value);
29
+ } catch {
30
+ return value;
31
+ }
32
+ };
33
+ /**
13
34
  * Find the frontend-registered A2UI catalog wherever it was passed. Returns
14
35
  * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`
15
- * (so the tool is never advertised when the client can't render A2UI). Two
36
+ * independently of the explicit tool-injection decision. Two
16
37
  * delivery paths, depending on how the agent is served:
17
38
  * - AG-UI native endpoint → `state["ag-ui"].a2ui_schema` (JSON
18
39
  * `{ catalogId, components }`); the toolkit reads it from state itself.
19
- * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing
40
+ * - Runtime proxy → an effective `context` entry describing
20
41
  * the A2UI catalog (catalog id + component schemas as text), passed to the
21
42
  * subagent via `compositionGuide`.
22
43
  * `catalogId` binds generated surfaces to the frontend's catalog so BYOC
23
44
  * custom catalogs render their own components (not the basic one).
24
45
  */
25
46
  const resolveA2uiCatalog = (state) => {
26
- const a2uiSchema = state?.["ag-ui"]?.a2ui_schema;
47
+ const properties = effectiveProperties(state);
48
+ const a2uiSchema = properties.a2ui_schema;
27
49
  if (a2uiSchema) {
28
50
  let catalogId;
29
51
  try {
@@ -31,11 +53,11 @@ const resolveA2uiCatalog = (state) => {
31
53
  } catch {}
32
54
  return { catalogId };
33
55
  }
34
- const context = state?.copilotkit?.context;
56
+ const context = decodeCatalogValue(properties.context);
35
57
  for (const entry of Array.isArray(context) ? context : []) {
36
- const description = entry?.description ?? "";
37
- const value = entry?.value ?? "";
38
- if (!description.includes("A2UI catalog") || !value) continue;
58
+ const description = entry?.description;
59
+ const value = decodeCatalogValue(entry?.value);
60
+ if (typeof description !== "string" || typeof value !== "string" || !description.includes("A2UI catalog") || !value) continue;
39
61
  return {
40
62
  compositionGuide: value,
41
63
  catalogId: /^\s*-\s+(\S+)/m.exec(value)?.[1]
@@ -50,7 +72,7 @@ const resolveA2uiCatalog = (state) => {
50
72
  * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means
51
73
  * no signal (off, or no A2UI middleware in the pipeline) → no auto-injection.
52
74
  */
53
- const a2uiInjectDecision = (state) => state?.["ag-ui"]?.inject_a2ui_tool;
75
+ const a2uiInjectDecision = (state) => effectiveProperties(state).inject_a2ui_tool;
54
76
  /**
55
77
  * Augment a Standard-Schema–compatible schema (e.g. Zod) with a
56
78
  * `~standard.jsonSchema.input` hook so LangGraph's
@@ -150,7 +172,8 @@ const applyStateNote = (request, expose) => {
150
172
  const createAppContextBeforeAgent = (state, runtime) => {
151
173
  const messages = state.messages;
152
174
  if (!messages || messages.length === 0) return;
153
- const appContext = state["copilotkit"]?.context ?? runtime?.context;
175
+ const properties = effectiveProperties(state);
176
+ const appContext = Object.prototype.hasOwnProperty.call(properties, "context") ? properties.context : runtime?.context;
154
177
  if (!appContext || typeof appContext === "string" && appContext.trim() === "" || typeof appContext === "object" && Object.keys(appContext).length === 0) return;
155
178
  const contextMessageContent = `App Context:\n${typeof appContext === "string" ? appContext : JSON.stringify(appContext, null, 2)}`;
156
179
  const contextMessagePrefix = "App Context:\n";
@@ -219,12 +242,15 @@ const createAppContextBeforeAgent = (state, runtime) => {
219
242
  * });
220
243
  * ```
221
244
  */
222
- const copilotKitStateSchema = zod.object({ copilotkit: zodState(zod.object({
223
- actions: zod.array(zod.any()),
224
- context: zod.any().optional(),
225
- interceptedToolCalls: zod.array(zod.any()).optional(),
226
- originalAIMessageId: zod.string().optional()
227
- }).optional()) }).passthrough();
245
+ const copilotKitStateSchema = zod.object({
246
+ "ag-ui": zodState(zod.record(zod.string(), zod.unknown()).optional()),
247
+ copilotkit: zodState(zod.object({
248
+ actions: zod.union([zod.array(zod.any()), zod.literal(false)]).nullable().optional(),
249
+ context: zod.any().optional(),
250
+ interceptedToolCalls: zod.array(zod.any()).optional(),
251
+ originalAIMessageId: zod.string().optional()
252
+ }).passthrough().optional())
253
+ }).passthrough();
228
254
  const isToolCallContentBlock = (block) => typeof block === "object" && block !== null && "type" in block && (block.type === "tool_call" || block.type === "tool_call_chunk");
229
255
  const usesV1ContentBlocks = (responseMetadata) => typeof responseMetadata === "object" && responseMetadata !== null && "output_version" in responseMetadata && responseMetadata.output_version === "v1";
230
256
  /**
@@ -283,11 +309,30 @@ const buildMiddlewareInput = (exposeState, a2uiParams) => ({
283
309
  }
284
310
  const candidate = (0, _ag_ui_langgraph.getA2UITools)(params);
285
311
  if (!new Set((request.tools || []).map((t) => t?.name)).has(candidate.name)) {
312
+ const invoke = candidate.invoke.bind(candidate);
313
+ candidate.invoke = (input, config) => {
314
+ const state = config && "state" in config && isPropertyBag(config.state) ? config.state : request.state;
315
+ const properties = effectiveProperties(state);
316
+ const schema = properties.a2ui_schema;
317
+ const context = decodeCatalogValue(properties.context);
318
+ return invoke(input, {
319
+ ...config,
320
+ state: {
321
+ ...state,
322
+ "ag-ui": {
323
+ ...properties,
324
+ context: Array.isArray(context) ? context : [],
325
+ a2ui_schema: isPropertyBag(schema) ? JSON.stringify(schema) : schema
326
+ }
327
+ }
328
+ });
329
+ };
286
330
  a2uiTool = candidate;
287
331
  a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);
288
332
  }
289
333
  }
290
- let frontendTools = request.state["copilotkit"]?.actions ?? [];
334
+ const actions = effectiveProperties(request.state).actions;
335
+ let frontendTools = Array.isArray(actions) ? actions : [];
291
336
  if (a2uiTool) {
292
337
  const drop = typeof decision === "string" ? decision : "render_a2ui";
293
338
  frontendTools = frontendTools.filter((t) => (t?.function?.name ?? t?.name) !== drop);
@@ -339,7 +384,8 @@ const buildMiddlewareInput = (exposeState, a2uiParams) => ({
339
384
  };
340
385
  },
341
386
  afterModel: (state) => {
342
- const frontendTools = state["copilotkit"]?.actions ?? [];
387
+ const actions = effectiveProperties(state).actions;
388
+ const frontendTools = Array.isArray(actions) ? actions : [];
343
389
  if (frontendTools.length === 0) return;
344
390
  const frontendToolNames = new Set(frontendTools.map((t) => t.function?.name || t.name));
345
391
  const lastMessage = state.messages[state.messages.length - 1];
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":["z","SystemMessage","AIMessage","getForwardedHeaders","getA2UITools"],"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 type { A2UIToolParams } 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 existingMessage = request.systemMessage;\n if (existingMessage != null) {\n const separator = existingMessage.text === \"\" ? \"\" : \"\\n\\n\";\n return {\n ...request,\n systemMessage: existingMessage.concat(`${separator}${note}`),\n };\n }\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: 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: `${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\n .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 .passthrough();\n\nconst isToolCallContentBlock = (block: unknown) =>\n typeof block === \"object\" &&\n block !== null &&\n \"type\" in block &&\n (block.type === \"tool_call\" || block.type === \"tool_call_chunk\");\n\nconst usesV1ContentBlocks = (responseMetadata: unknown) =>\n typeof responseMetadata === \"object\" &&\n responseMetadata !== null &&\n \"output_version\" in responseMetadata &&\n responseMetadata.output_version === \"v1\";\n\n/**\n * Rebuilds an AIMessage with `toolCalls` as the source of truth while\n * preserving its non-tool content and metadata. For v1 content blocks, old\n * tool blocks must be removed before construction so they cannot duplicate or\n * override the supplied tool calls when AIMessage synchronizes both fields.\n */\nconst rebuildAIMessageWithToolCalls = (\n message: AIMessage,\n toolCalls: AIMessage[\"tool_calls\"],\n) => {\n let content = message.content;\n if (\n usesV1ContentBlocks(message.response_metadata) &&\n Array.isArray(content)\n ) {\n content = content.filter((block) => !isToolCallContentBlock(block));\n }\n\n return new AIMessage({\n content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n tool_calls: toolCalls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: message.usage_metadata,\n id: message.id,\n name: message.name,\n });\n};\n\nconst buildMiddlewareInput = (\n exposeState: ExposeStateOption,\n a2uiParams?: Omit<A2UIToolParams, \"model\">,\n) => ({\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 // Shared A2UIToolParams: a single params object owned by the toolkit.\n // Start from the host overrides (guidelines / catalog id / tool name /\n // recovery) so a host can steer the subagent, then layer in only what the\n // host cannot know — the bound model, and the registered catalog id +\n // compositionGuide — without clobbering any host-set value.\n const params: A2UIToolParams = {\n ...a2uiParams,\n model: request.model,\n };\n if (catalog?.catalogId && params.defaultCatalogId == null)\n params.defaultCatalogId = catalog.catalogId;\n // Merge the registered catalog schema into any host `guidelines` bag; a\n // host-set compositionGuide wins, host generation/design overrides stay.\n if (catalog?.compositionGuide) {\n const guidelines = { ...params.guidelines };\n if (guidelines.compositionGuide == null)\n guidelines.compositionGuide = catalog.compositionGuide;\n params.guidelines = guidelines;\n }\n const candidate = getA2UITools(params);\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 rebuildAIMessageWithToolCalls(msg, [\n ...existingToolCalls,\n ...interceptedToolCalls,\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 = rebuildAIMessageWithToolCalls(\n lastMessage,\n backendToolCalls,\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), or\n * to steer the auto-injected `generate_a2ui` subagent via `a2uiParams`.\n *\n * `a2uiParams` is an `A2UIToolParams` without `model` (the middleware always\n * injects the bound model). Use it to override the subagent guidelines\n * (`generationGuidelines` / `designGuidelines` / `compositionGuide`),\n * `defaultCatalogId`, `toolName`, `recovery`, etc. on the auto-inject path —\n * which otherwise only ever uses the toolkit defaults. The registered catalog\n * is still folded in, but host-set values win.\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * a2uiParams: { guidelines: { designGuidelines: \"...repeating-card layout...\" } },\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: {\n exposeState?: ExposeStateOption;\n a2uiParams?: Omit<A2UIToolParams, \"model\">;\n } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(\n buildMiddlewareInput(exposeState, options.a2uiParams) as any,\n );\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","AIMessage","getForwardedHeaders","getA2UITools"],"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 type { A2UIToolParams } 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 * Merge AG-UI defaults with CopilotKit overrides. Dictionaries recurse; every\n * conflicting leaf uses CopilotKit, including false, null and empty values.\n * Arrays and serialized strings are atomic: no positional pairing or decoding.\n */\nconst isPropertyBag = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\nconst mergeProperties = (\n base: Record<string, unknown>,\n overrides: Record<string, unknown>,\n): Record<string, unknown> =>\n Object.fromEntries(\n Array.from(new Set([...Object.keys(base), ...Object.keys(overrides)])).map(\n (key) => {\n const value = Object.prototype.hasOwnProperty.call(overrides, key)\n ? isPropertyBag(base[key]) && isPropertyBag(overrides[key])\n ? mergeProperties(base[key], overrides[key])\n : overrides[key]\n : base[key];\n return [key, value];\n },\n ),\n );\n\nconst effectiveProperties = (state: unknown): Record<string, unknown> => {\n const source = isPropertyBag(state) ? state : {};\n return mergeProperties(\n isPropertyBag(source[\"ag-ui\"]) ? source[\"ag-ui\"] : {},\n isPropertyBag(source.copilotkit) ? source.copilotkit : {},\n );\n};\n\nconst decodeCatalogValue = (value: unknown): unknown => {\n if (typeof value !== \"string\") return value;\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n};\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 * independently of the explicit tool-injection decision. 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 * - Runtime proxy → an effective `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 properties = effectiveProperties(state);\n const a2uiSchema = properties.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 = decodeCatalogValue(properties.context);\n for (const entry of Array.isArray(context) ? context : []) {\n const description = entry?.description;\n const value = decodeCatalogValue(entry?.value);\n if (\n typeof description !== \"string\" ||\n typeof value !== \"string\" ||\n !description.includes(\"A2UI catalog\") ||\n !value\n )\n 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 effectiveProperties(state).inject_a2ui_tool as boolean | string | undefined;\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 existingMessage = request.systemMessage;\n if (existingMessage != null) {\n const separator = existingMessage.text === \"\" ? \"\" : \"\\n\\n\";\n return {\n ...request,\n systemMessage: existingMessage.concat(`${separator}${note}`),\n };\n }\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: 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: `${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 properties = effectiveProperties(state);\n const appContext = Object.prototype.hasOwnProperty.call(properties, \"context\")\n ? properties.context\n : 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\n .object({\n \"ag-ui\": zodState(z.record(z.string(), z.unknown()).optional()),\n copilotkit: zodState(\n z\n .object({\n actions: z\n .union([z.array(z.any()), z.literal(false)])\n .nullable()\n .optional(),\n context: z.any().optional(),\n interceptedToolCalls: z.array(z.any()).optional(),\n originalAIMessageId: z.string().optional(),\n })\n .passthrough()\n .optional(),\n ),\n })\n .passthrough();\n\nconst isToolCallContentBlock = (block: unknown) =>\n typeof block === \"object\" &&\n block !== null &&\n \"type\" in block &&\n (block.type === \"tool_call\" || block.type === \"tool_call_chunk\");\n\nconst usesV1ContentBlocks = (responseMetadata: unknown) =>\n typeof responseMetadata === \"object\" &&\n responseMetadata !== null &&\n \"output_version\" in responseMetadata &&\n responseMetadata.output_version === \"v1\";\n\n/**\n * Rebuilds an AIMessage with `toolCalls` as the source of truth while\n * preserving its non-tool content and metadata. For v1 content blocks, old\n * tool blocks must be removed before construction so they cannot duplicate or\n * override the supplied tool calls when AIMessage synchronizes both fields.\n */\nconst rebuildAIMessageWithToolCalls = (\n message: AIMessage,\n toolCalls: AIMessage[\"tool_calls\"],\n) => {\n let content = message.content;\n if (\n usesV1ContentBlocks(message.response_metadata) &&\n Array.isArray(content)\n ) {\n content = content.filter((block) => !isToolCallContentBlock(block));\n }\n\n return new AIMessage({\n content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n tool_calls: toolCalls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: message.usage_metadata,\n id: message.id,\n name: message.name,\n });\n};\n\nconst buildMiddlewareInput = (\n exposeState: ExposeStateOption,\n a2uiParams?: Omit<A2UIToolParams, \"model\">,\n) => ({\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 // Shared A2UIToolParams: a single params object owned by the toolkit.\n // Start from the host overrides (guidelines / catalog id / tool name /\n // recovery) so a host can steer the subagent, then layer in only what the\n // host cannot know — the bound model, and the registered catalog id +\n // compositionGuide — without clobbering any host-set value.\n const params: A2UIToolParams = {\n ...a2uiParams,\n model: request.model,\n };\n if (catalog?.catalogId && params.defaultCatalogId == null)\n params.defaultCatalogId = catalog.catalogId;\n // Merge the registered catalog schema into any host `guidelines` bag; a\n // host-set compositionGuide wins, host generation/design overrides stay.\n if (catalog?.compositionGuide) {\n const guidelines = { ...params.guidelines };\n if (guidelines.compositionGuide == null)\n guidelines.compositionGuide = catalog.compositionGuide;\n params.guidelines = guidelines;\n }\n const candidate = getA2UITools(params);\n const existingNames = new Set(\n (request.tools || []).map((t: any) => t?.name),\n );\n if (!existingNames.has(candidate.name)) {\n // ToolNode derives tool runtime state from its config, ignoring an\n // overridden middleware request.state. Adapt the tool's invocation\n // config so the toolkit sees the same effective properties.\n const invoke = candidate.invoke.bind(candidate);\n candidate.invoke = (input, config) => {\n const state =\n config && \"state\" in config && isPropertyBag(config.state)\n ? config.state\n : request.state;\n const properties = effectiveProperties(state);\n const schema = properties.a2ui_schema;\n const context = decodeCatalogValue(properties.context);\n return invoke(input, {\n ...config,\n state: {\n ...state,\n \"ag-ui\": {\n ...properties,\n context: Array.isArray(context) ? context : [],\n // The toolkit interpolates the native schema into its prompt.\n a2ui_schema: isPropertyBag(schema)\n ? JSON.stringify(schema)\n : schema,\n },\n },\n });\n };\n a2uiTool = candidate;\n a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);\n }\n }\n\n const actions = effectiveProperties(request.state).actions;\n let frontendTools = Array.isArray(actions) ? 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 rebuildAIMessageWithToolCalls(msg, [\n ...existingToolCalls,\n ...interceptedToolCalls,\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 actions = effectiveProperties(state).actions;\n const frontendTools = Array.isArray(actions) ? 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 = rebuildAIMessageWithToolCalls(\n lastMessage,\n backendToolCalls,\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), or\n * to steer the auto-injected `generate_a2ui` subagent via `a2uiParams`.\n *\n * `a2uiParams` is an `A2UIToolParams` without `model` (the middleware always\n * injects the bound model). Use it to override the subagent guidelines\n * (`generationGuidelines` / `designGuidelines` / `compositionGuide`),\n * `defaultCatalogId`, `toolName`, `recovery`, etc. on the auto-inject path —\n * which otherwise only ever uses the toolkit defaults. The registered catalog\n * is still folded in, but host-set values win.\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * a2uiParams: { guidelines: { designGuidelines: \"...repeating-card layout...\" } },\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: {\n exposeState?: ExposeStateOption;\n a2uiParams?: Omit<A2UIToolParams, \"model\">;\n } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(\n buildMiddlewareInput(exposeState, options.a2uiParams) as any,\n );\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":";;;;;;KAuEK,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;;;;;AAudA;;;;;;;cAAa,0BAAA,GACX,OAAA;EACE,WAAA,GAAc,iBAAA;EACd,UAAA,GAAa,IAAA,CAAK,cAAA;AAAA,MACd,SAAA,CAAA,eAAA,0CADa,sBAAA,CACb,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAaK,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":";;;;;;KAwHK,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;;;;;AA4fA;;;;;;;cAAa,0BAAA,GACX,OAAA;EACE,WAAA,GAAc,iBAAA;EACd,UAAA,GAAa,IAAA,CAAK,cAAA;AAAA,MACd,SAAA,CAAA,eAAA,0CADa,sBAAA,CACb,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAaK,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":";;;;;;KAuEK,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;;;;;AAudA;;;;;;;cAAa,0BAAA,GACX,OAAA;EACE,WAAA,GAAc,iBAAA;EACd,UAAA,GAAa,IAAA,CAAK,cAAA;AAAA,MACd,SAAA,CAAA,eAAA,0CADa,sBAAA,CACb,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAaK,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":";;;;;;KAwHK,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;;;;;AA4fA;;;;;;;cAAa,0BAAA,GACX,OAAA;EACE,WAAA,GAAc,iBAAA;EACd,UAAA,GAAa,IAAA,CAAK,cAAA;AAAA,MACd,SAAA,CAAA,eAAA,0CADa,sBAAA,CACb,UAAA,GAAA,sBAAA,CAAA,UAAA;;;;;;cAaK,oBAAA,EAAoB,SAAA,CAAA,eAAA,0CAA+B,sBAAA,CAA/B,UAAA,GAAA,sBAAA,CAAA,UAAA"}
@@ -8,20 +8,42 @@ const a2uiToolsByThread = /* @__PURE__ */ new Map();
8
8
  const A2UI_DEFAULT_THREAD_KEY = "__copilotkit_a2ui_default__";
9
9
  const a2uiThreadKey = (state) => state?.thread_id || A2UI_DEFAULT_THREAD_KEY;
10
10
  /**
11
+ * Merge AG-UI defaults with CopilotKit overrides. Dictionaries recurse; every
12
+ * conflicting leaf uses CopilotKit, including false, null and empty values.
13
+ * Arrays and serialized strings are atomic: no positional pairing or decoding.
14
+ */
15
+ const isPropertyBag = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16
+ const mergeProperties = (base, overrides) => Object.fromEntries(Array.from(new Set([...Object.keys(base), ...Object.keys(overrides)])).map((key) => {
17
+ return [key, Object.prototype.hasOwnProperty.call(overrides, key) ? isPropertyBag(base[key]) && isPropertyBag(overrides[key]) ? mergeProperties(base[key], overrides[key]) : overrides[key] : base[key]];
18
+ }));
19
+ const effectiveProperties = (state) => {
20
+ const source = isPropertyBag(state) ? state : {};
21
+ return mergeProperties(isPropertyBag(source["ag-ui"]) ? source["ag-ui"] : {}, isPropertyBag(source.copilotkit) ? source.copilotkit : {});
22
+ };
23
+ const decodeCatalogValue = (value) => {
24
+ if (typeof value !== "string") return value;
25
+ try {
26
+ return JSON.parse(value);
27
+ } catch {
28
+ return value;
29
+ }
30
+ };
31
+ /**
11
32
  * Find the frontend-registered A2UI catalog wherever it was passed. Returns
12
33
  * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`
13
- * (so the tool is never advertised when the client can't render A2UI). Two
34
+ * independently of the explicit tool-injection decision. Two
14
35
  * delivery paths, depending on how the agent is served:
15
36
  * - AG-UI native endpoint → `state["ag-ui"].a2ui_schema` (JSON
16
37
  * `{ catalogId, components }`); the toolkit reads it from state itself.
17
- * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing
38
+ * - Runtime proxy → an effective `context` entry describing
18
39
  * the A2UI catalog (catalog id + component schemas as text), passed to the
19
40
  * subagent via `compositionGuide`.
20
41
  * `catalogId` binds generated surfaces to the frontend's catalog so BYOC
21
42
  * custom catalogs render their own components (not the basic one).
22
43
  */
23
44
  const resolveA2uiCatalog = (state) => {
24
- const a2uiSchema = state?.["ag-ui"]?.a2ui_schema;
45
+ const properties = effectiveProperties(state);
46
+ const a2uiSchema = properties.a2ui_schema;
25
47
  if (a2uiSchema) {
26
48
  let catalogId;
27
49
  try {
@@ -29,11 +51,11 @@ const resolveA2uiCatalog = (state) => {
29
51
  } catch {}
30
52
  return { catalogId };
31
53
  }
32
- const context = state?.copilotkit?.context;
54
+ const context = decodeCatalogValue(properties.context);
33
55
  for (const entry of Array.isArray(context) ? context : []) {
34
- const description = entry?.description ?? "";
35
- const value = entry?.value ?? "";
36
- if (!description.includes("A2UI catalog") || !value) continue;
56
+ const description = entry?.description;
57
+ const value = decodeCatalogValue(entry?.value);
58
+ if (typeof description !== "string" || typeof value !== "string" || !description.includes("A2UI catalog") || !value) continue;
37
59
  return {
38
60
  compositionGuide: value,
39
61
  catalogId: /^\s*-\s+(\S+)/m.exec(value)?.[1]
@@ -48,7 +70,7 @@ const resolveA2uiCatalog = (state) => {
48
70
  * runtime A2UI tool on (truthy or a custom tool-name string). `undefined` means
49
71
  * no signal (off, or no A2UI middleware in the pipeline) → no auto-injection.
50
72
  */
51
- const a2uiInjectDecision = (state) => state?.["ag-ui"]?.inject_a2ui_tool;
73
+ const a2uiInjectDecision = (state) => effectiveProperties(state).inject_a2ui_tool;
52
74
  /**
53
75
  * Augment a Standard-Schema–compatible schema (e.g. Zod) with a
54
76
  * `~standard.jsonSchema.input` hook so LangGraph's
@@ -148,7 +170,8 @@ const applyStateNote = (request, expose) => {
148
170
  const createAppContextBeforeAgent = (state, runtime) => {
149
171
  const messages = state.messages;
150
172
  if (!messages || messages.length === 0) return;
151
- const appContext = state["copilotkit"]?.context ?? runtime?.context;
173
+ const properties = effectiveProperties(state);
174
+ const appContext = Object.prototype.hasOwnProperty.call(properties, "context") ? properties.context : runtime?.context;
152
175
  if (!appContext || typeof appContext === "string" && appContext.trim() === "" || typeof appContext === "object" && Object.keys(appContext).length === 0) return;
153
176
  const contextMessageContent = `App Context:\n${typeof appContext === "string" ? appContext : JSON.stringify(appContext, null, 2)}`;
154
177
  const contextMessagePrefix = "App Context:\n";
@@ -217,12 +240,15 @@ const createAppContextBeforeAgent = (state, runtime) => {
217
240
  * });
218
241
  * ```
219
242
  */
220
- const copilotKitStateSchema = z.object({ copilotkit: zodState(z.object({
221
- actions: z.array(z.any()),
222
- context: z.any().optional(),
223
- interceptedToolCalls: z.array(z.any()).optional(),
224
- originalAIMessageId: z.string().optional()
225
- }).optional()) }).passthrough();
243
+ const copilotKitStateSchema = z.object({
244
+ "ag-ui": zodState(z.record(z.string(), z.unknown()).optional()),
245
+ copilotkit: zodState(z.object({
246
+ actions: z.union([z.array(z.any()), z.literal(false)]).nullable().optional(),
247
+ context: z.any().optional(),
248
+ interceptedToolCalls: z.array(z.any()).optional(),
249
+ originalAIMessageId: z.string().optional()
250
+ }).passthrough().optional())
251
+ }).passthrough();
226
252
  const isToolCallContentBlock = (block) => typeof block === "object" && block !== null && "type" in block && (block.type === "tool_call" || block.type === "tool_call_chunk");
227
253
  const usesV1ContentBlocks = (responseMetadata) => typeof responseMetadata === "object" && responseMetadata !== null && "output_version" in responseMetadata && responseMetadata.output_version === "v1";
228
254
  /**
@@ -281,11 +307,30 @@ const buildMiddlewareInput = (exposeState, a2uiParams) => ({
281
307
  }
282
308
  const candidate = getA2UITools(params);
283
309
  if (!new Set((request.tools || []).map((t) => t?.name)).has(candidate.name)) {
310
+ const invoke = candidate.invoke.bind(candidate);
311
+ candidate.invoke = (input, config) => {
312
+ const state = config && "state" in config && isPropertyBag(config.state) ? config.state : request.state;
313
+ const properties = effectiveProperties(state);
314
+ const schema = properties.a2ui_schema;
315
+ const context = decodeCatalogValue(properties.context);
316
+ return invoke(input, {
317
+ ...config,
318
+ state: {
319
+ ...state,
320
+ "ag-ui": {
321
+ ...properties,
322
+ context: Array.isArray(context) ? context : [],
323
+ a2ui_schema: isPropertyBag(schema) ? JSON.stringify(schema) : schema
324
+ }
325
+ }
326
+ });
327
+ };
284
328
  a2uiTool = candidate;
285
329
  a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);
286
330
  }
287
331
  }
288
- let frontendTools = request.state["copilotkit"]?.actions ?? [];
332
+ const actions = effectiveProperties(request.state).actions;
333
+ let frontendTools = Array.isArray(actions) ? actions : [];
289
334
  if (a2uiTool) {
290
335
  const drop = typeof decision === "string" ? decision : "render_a2ui";
291
336
  frontendTools = frontendTools.filter((t) => (t?.function?.name ?? t?.name) !== drop);
@@ -337,7 +382,8 @@ const buildMiddlewareInput = (exposeState, a2uiParams) => ({
337
382
  };
338
383
  },
339
384
  afterModel: (state) => {
340
- const frontendTools = state["copilotkit"]?.actions ?? [];
385
+ const actions = effectiveProperties(state).actions;
386
+ const frontendTools = Array.isArray(actions) ? actions : [];
341
387
  if (frontendTools.length === 0) return;
342
388
  const frontendToolNames = new Set(frontendTools.map((t) => t.function?.name || t.name));
343
389
  const lastMessage = state.messages[state.messages.length - 1];
@@ -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 type { A2UIToolParams } 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 existingMessage = request.systemMessage;\n if (existingMessage != null) {\n const separator = existingMessage.text === \"\" ? \"\" : \"\\n\\n\";\n return {\n ...request,\n systemMessage: existingMessage.concat(`${separator}${note}`),\n };\n }\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: 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: `${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\n .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 .passthrough();\n\nconst isToolCallContentBlock = (block: unknown) =>\n typeof block === \"object\" &&\n block !== null &&\n \"type\" in block &&\n (block.type === \"tool_call\" || block.type === \"tool_call_chunk\");\n\nconst usesV1ContentBlocks = (responseMetadata: unknown) =>\n typeof responseMetadata === \"object\" &&\n responseMetadata !== null &&\n \"output_version\" in responseMetadata &&\n responseMetadata.output_version === \"v1\";\n\n/**\n * Rebuilds an AIMessage with `toolCalls` as the source of truth while\n * preserving its non-tool content and metadata. For v1 content blocks, old\n * tool blocks must be removed before construction so they cannot duplicate or\n * override the supplied tool calls when AIMessage synchronizes both fields.\n */\nconst rebuildAIMessageWithToolCalls = (\n message: AIMessage,\n toolCalls: AIMessage[\"tool_calls\"],\n) => {\n let content = message.content;\n if (\n usesV1ContentBlocks(message.response_metadata) &&\n Array.isArray(content)\n ) {\n content = content.filter((block) => !isToolCallContentBlock(block));\n }\n\n return new AIMessage({\n content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n tool_calls: toolCalls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: message.usage_metadata,\n id: message.id,\n name: message.name,\n });\n};\n\nconst buildMiddlewareInput = (\n exposeState: ExposeStateOption,\n a2uiParams?: Omit<A2UIToolParams, \"model\">,\n) => ({\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 // Shared A2UIToolParams: a single params object owned by the toolkit.\n // Start from the host overrides (guidelines / catalog id / tool name /\n // recovery) so a host can steer the subagent, then layer in only what the\n // host cannot know — the bound model, and the registered catalog id +\n // compositionGuide — without clobbering any host-set value.\n const params: A2UIToolParams = {\n ...a2uiParams,\n model: request.model,\n };\n if (catalog?.catalogId && params.defaultCatalogId == null)\n params.defaultCatalogId = catalog.catalogId;\n // Merge the registered catalog schema into any host `guidelines` bag; a\n // host-set compositionGuide wins, host generation/design overrides stay.\n if (catalog?.compositionGuide) {\n const guidelines = { ...params.guidelines };\n if (guidelines.compositionGuide == null)\n guidelines.compositionGuide = catalog.compositionGuide;\n params.guidelines = guidelines;\n }\n const candidate = getA2UITools(params);\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 rebuildAIMessageWithToolCalls(msg, [\n ...existingToolCalls,\n ...interceptedToolCalls,\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 = rebuildAIMessageWithToolCalls(\n lastMessage,\n backendToolCalls,\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), or\n * to steer the auto-injected `generate_a2ui` subagent via `a2uiParams`.\n *\n * `a2uiParams` is an `A2UIToolParams` without `model` (the middleware always\n * injects the bound model). Use it to override the subagent guidelines\n * (`generationGuidelines` / `designGuidelines` / `compositionGuide`),\n * `defaultCatalogId`, `toolName`, `recovery`, etc. on the auto-inject path —\n * which otherwise only ever uses the toolkit defaults. The registered catalog\n * is still folded in, but host-set values win.\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * a2uiParams: { guidelines: { designGuidelines: \"...repeating-card layout...\" } },\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: {\n exposeState?: ExposeStateOption;\n a2uiParams?: Omit<A2UIToolParams, \"model\">;\n } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(\n buildMiddlewareInput(exposeState, options.a2uiParams) as any,\n );\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":";;;;;;AAiBA,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;;;;;;;;;AAUT,MAAM,sBAAsB,UAC1B,QAAQ,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;AAoCpB,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,kBAAkB,QAAQ;AAChC,KAAI,mBAAmB,MAAM;EAC3B,MAAM,YAAY,gBAAgB,SAAS,KAAK,KAAK;AACrD,SAAO;GACL,GAAG;GACH,eAAe,gBAAgB,OAAO,GAAG,YAAY,OAAO;GAC7D;;CAGH,MAAM,WAAW,QAAQ;AACzB,KAAI,YAAY,KACd,QAAO;EAAE,GAAG;EAAS,cAAc;EAAM;CAG3C,MAAM,WACJ,OAAO,aAAa,WAChB,WACA,OAAO,SAAS,YAAY,WAC1B,SAAS,UACT,OAAO,SAAS,QAAQ;AAChC,QAAO;EACL,GAAG;EACH,cAAc,GAAG,SAAS,MAAM;EACjC;;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,EAC3B,OAAO,EACN,YAAY,SACV,EACG,OAAO;CACN,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;CACzB,SAAS,EAAE,KAAK,CAAC,UAAU;CAC3B,sBAAsB,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACjD,qBAAqB,EAAE,QAAQ,CAAC,UAAU;CAC3C,CAAC,CACD,UAAU,CACd,EACF,CAAC,CACD,aAAa;AAEhB,MAAM,0BAA0B,UAC9B,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,UACT,MAAM,SAAS,eAAe,MAAM,SAAS;AAEhD,MAAM,uBAAuB,qBAC3B,OAAO,qBAAqB,YAC5B,qBAAqB,QACrB,oBAAoB,oBACpB,iBAAiB,mBAAmB;;;;;;;AAQtC,MAAM,iCACJ,SACA,cACG;CACH,IAAI,UAAU,QAAQ;AACtB,KACE,oBAAoB,QAAQ,kBAAkB,IAC9C,MAAM,QAAQ,QAAQ,CAEtB,WAAU,QAAQ,QAAQ,UAAU,CAAC,uBAAuB,MAAM,CAAC;AAGrE,QAAO,IAAI,UAAU;EACnB;EACA,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,YAAY;EACZ,oBAAoB,QAAQ;EAC5B,gBAAgB,QAAQ;EACxB,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACf,CAAC;;AAGJ,MAAM,wBACJ,aACA,gBACI;CACJ,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;;EAUH,IAAI,WAAgB;EACpB,MAAM,WAAW,mBAAmB,QAAQ,MAAM;AAClD,MAAI,OAAO,iBAAiB,cAAc,UAAU;GAClD,MAAM,UAAU,mBAAmB,QAAQ,MAAM;GAMjD,MAAM,SAAyB;IAC7B,GAAG;IACH,OAAO,QAAQ;IAChB;AACD,OAAI,SAAS,aAAa,OAAO,oBAAoB,KACnD,QAAO,mBAAmB,QAAQ;AAGpC,OAAI,SAAS,kBAAkB;IAC7B,MAAM,aAAa,EAAE,GAAG,OAAO,YAAY;AAC3C,QAAI,WAAW,oBAAoB,KACjC,YAAW,mBAAmB,QAAQ;AACxC,WAAO,aAAa;;GAEtB,MAAM,YAAY,aAAa,OAAO;AAItC,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;AAEf,WAAO,8BAA8B,KAAK,CACxC,GAFwB,IAAI,cAAc,EAAE,EAG5C,GAAG,qBACJ,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,8BACvB,aACA,iBACD;AAED,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;;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,MAAa,8BACX,UAGI,EAAE,KACH;AAEH,QAAO,iBACL,qBAFkB,QAAQ,eAAe,OAEP,QAAQ,WAAW,CACtD;;;;;;;AAQH,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 type { A2UIToolParams } 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 * Merge AG-UI defaults with CopilotKit overrides. Dictionaries recurse; every\n * conflicting leaf uses CopilotKit, including false, null and empty values.\n * Arrays and serialized strings are atomic: no positional pairing or decoding.\n */\nconst isPropertyBag = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\nconst mergeProperties = (\n base: Record<string, unknown>,\n overrides: Record<string, unknown>,\n): Record<string, unknown> =>\n Object.fromEntries(\n Array.from(new Set([...Object.keys(base), ...Object.keys(overrides)])).map(\n (key) => {\n const value = Object.prototype.hasOwnProperty.call(overrides, key)\n ? isPropertyBag(base[key]) && isPropertyBag(overrides[key])\n ? mergeProperties(base[key], overrides[key])\n : overrides[key]\n : base[key];\n return [key, value];\n },\n ),\n );\n\nconst effectiveProperties = (state: unknown): Record<string, unknown> => {\n const source = isPropertyBag(state) ? state : {};\n return mergeProperties(\n isPropertyBag(source[\"ag-ui\"]) ? source[\"ag-ui\"] : {},\n isPropertyBag(source.copilotkit) ? source.copilotkit : {},\n );\n};\n\nconst decodeCatalogValue = (value: unknown): unknown => {\n if (typeof value !== \"string\") return value;\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n};\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 * independently of the explicit tool-injection decision. 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 * - Runtime proxy → an effective `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 properties = effectiveProperties(state);\n const a2uiSchema = properties.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 = decodeCatalogValue(properties.context);\n for (const entry of Array.isArray(context) ? context : []) {\n const description = entry?.description;\n const value = decodeCatalogValue(entry?.value);\n if (\n typeof description !== \"string\" ||\n typeof value !== \"string\" ||\n !description.includes(\"A2UI catalog\") ||\n !value\n )\n 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 effectiveProperties(state).inject_a2ui_tool as boolean | string | undefined;\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 existingMessage = request.systemMessage;\n if (existingMessage != null) {\n const separator = existingMessage.text === \"\" ? \"\" : \"\\n\\n\";\n return {\n ...request,\n systemMessage: existingMessage.concat(`${separator}${note}`),\n };\n }\n\n const existing = request.systemPrompt;\n if (existing == null) {\n return { ...request, systemPrompt: 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: `${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 properties = effectiveProperties(state);\n const appContext = Object.prototype.hasOwnProperty.call(properties, \"context\")\n ? properties.context\n : 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\n .object({\n \"ag-ui\": zodState(z.record(z.string(), z.unknown()).optional()),\n copilotkit: zodState(\n z\n .object({\n actions: z\n .union([z.array(z.any()), z.literal(false)])\n .nullable()\n .optional(),\n context: z.any().optional(),\n interceptedToolCalls: z.array(z.any()).optional(),\n originalAIMessageId: z.string().optional(),\n })\n .passthrough()\n .optional(),\n ),\n })\n .passthrough();\n\nconst isToolCallContentBlock = (block: unknown) =>\n typeof block === \"object\" &&\n block !== null &&\n \"type\" in block &&\n (block.type === \"tool_call\" || block.type === \"tool_call_chunk\");\n\nconst usesV1ContentBlocks = (responseMetadata: unknown) =>\n typeof responseMetadata === \"object\" &&\n responseMetadata !== null &&\n \"output_version\" in responseMetadata &&\n responseMetadata.output_version === \"v1\";\n\n/**\n * Rebuilds an AIMessage with `toolCalls` as the source of truth while\n * preserving its non-tool content and metadata. For v1 content blocks, old\n * tool blocks must be removed before construction so they cannot duplicate or\n * override the supplied tool calls when AIMessage synchronizes both fields.\n */\nconst rebuildAIMessageWithToolCalls = (\n message: AIMessage,\n toolCalls: AIMessage[\"tool_calls\"],\n) => {\n let content = message.content;\n if (\n usesV1ContentBlocks(message.response_metadata) &&\n Array.isArray(content)\n ) {\n content = content.filter((block) => !isToolCallContentBlock(block));\n }\n\n return new AIMessage({\n content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n tool_calls: toolCalls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: message.usage_metadata,\n id: message.id,\n name: message.name,\n });\n};\n\nconst buildMiddlewareInput = (\n exposeState: ExposeStateOption,\n a2uiParams?: Omit<A2UIToolParams, \"model\">,\n) => ({\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 // Shared A2UIToolParams: a single params object owned by the toolkit.\n // Start from the host overrides (guidelines / catalog id / tool name /\n // recovery) so a host can steer the subagent, then layer in only what the\n // host cannot know — the bound model, and the registered catalog id +\n // compositionGuide — without clobbering any host-set value.\n const params: A2UIToolParams = {\n ...a2uiParams,\n model: request.model,\n };\n if (catalog?.catalogId && params.defaultCatalogId == null)\n params.defaultCatalogId = catalog.catalogId;\n // Merge the registered catalog schema into any host `guidelines` bag; a\n // host-set compositionGuide wins, host generation/design overrides stay.\n if (catalog?.compositionGuide) {\n const guidelines = { ...params.guidelines };\n if (guidelines.compositionGuide == null)\n guidelines.compositionGuide = catalog.compositionGuide;\n params.guidelines = guidelines;\n }\n const candidate = getA2UITools(params);\n const existingNames = new Set(\n (request.tools || []).map((t: any) => t?.name),\n );\n if (!existingNames.has(candidate.name)) {\n // ToolNode derives tool runtime state from its config, ignoring an\n // overridden middleware request.state. Adapt the tool's invocation\n // config so the toolkit sees the same effective properties.\n const invoke = candidate.invoke.bind(candidate);\n candidate.invoke = (input, config) => {\n const state =\n config && \"state\" in config && isPropertyBag(config.state)\n ? config.state\n : request.state;\n const properties = effectiveProperties(state);\n const schema = properties.a2ui_schema;\n const context = decodeCatalogValue(properties.context);\n return invoke(input, {\n ...config,\n state: {\n ...state,\n \"ag-ui\": {\n ...properties,\n context: Array.isArray(context) ? context : [],\n // The toolkit interpolates the native schema into its prompt.\n a2ui_schema: isPropertyBag(schema)\n ? JSON.stringify(schema)\n : schema,\n },\n },\n });\n };\n a2uiTool = candidate;\n a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);\n }\n }\n\n const actions = effectiveProperties(request.state).actions;\n let frontendTools = Array.isArray(actions) ? 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 rebuildAIMessageWithToolCalls(msg, [\n ...existingToolCalls,\n ...interceptedToolCalls,\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 actions = effectiveProperties(state).actions;\n const frontendTools = Array.isArray(actions) ? 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 = rebuildAIMessageWithToolCalls(\n lastMessage,\n backendToolCalls,\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), or\n * to steer the auto-injected `generate_a2ui` subagent via `a2uiParams`.\n *\n * `a2uiParams` is an `A2UIToolParams` without `model` (the middleware always\n * injects the bound model). Use it to override the subagent guidelines\n * (`generationGuidelines` / `designGuidelines` / `compositionGuide`),\n * `defaultCatalogId`, `toolName`, `recovery`, etc. on the auto-inject path —\n * which otherwise only ever uses the toolkit defaults. The registered catalog\n * is still folded in, but host-set values win.\n *\n * @example\n * ```typescript\n * import { createCopilotkitMiddleware } from \"@copilotkit/sdk-js/langgraph\";\n *\n * const middleware = createCopilotkitMiddleware({\n * exposeState: [\"liked\", \"todos\"],\n * a2uiParams: { guidelines: { designGuidelines: \"...repeating-card layout...\" } },\n * });\n * ```\n */\nexport const createCopilotkitMiddleware = (\n options: {\n exposeState?: ExposeStateOption;\n a2uiParams?: Omit<A2UIToolParams, \"model\">;\n } = {},\n) => {\n const exposeState = options.exposeState ?? false;\n return createMiddleware(\n buildMiddlewareInput(exposeState, options.a2uiParams) as any,\n );\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.75.2",
3
+ "version": "1.77.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "ai",
@@ -48,9 +48,9 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@ag-ui/langgraph": "0.0.42",
51
+ "@ag-ui/langgraph": "0.0.44",
52
52
  "@standard-schema/spec": "^1.0.0",
53
- "@copilotkit/shared": "1.75.2"
53
+ "@copilotkit/shared": "1.77.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@langchain/core": "^1.1.41",
@@ -17,12 +17,17 @@
17
17
 
18
18
  import { describe, it, expect, beforeEach, vi } from "vitest";
19
19
 
20
- const { captured } = vi.hoisted(() => ({ captured: [] as any[] }));
20
+ import type { A2UIToolParams } from "@ag-ui/langgraph";
21
+
22
+ const { captured } = vi.hoisted(() => {
23
+ const params: A2UIToolParams[] = [];
24
+ return { captured: params };
25
+ });
21
26
 
22
27
  vi.mock("@ag-ui/langgraph", () => ({
23
- getA2UITools: (params: any) => {
28
+ getA2UITools: (params: A2UIToolParams) => {
24
29
  captured.push(params);
25
- return { name: "generate_a2ui" };
30
+ return { name: "generate_a2ui", invoke: async () => "ok" };
26
31
  },
27
32
  }));
28
33
 
@@ -0,0 +1,118 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ AIMessage,
4
+ AIMessageChunk,
5
+ HumanMessage,
6
+ } from "@langchain/core/messages";
7
+ import type { BaseMessage } from "@langchain/core/messages";
8
+ import type { getA2UITools } from "@ag-ui/langgraph";
9
+ import { createCopilotkitMiddleware } from "../middleware";
10
+
11
+ const components = [{ id: "root", component: "Text", text: "Sales Dashboard" }];
12
+
13
+ // A deterministic in-process model drives the actual toolkit; no provider
14
+ // requests or SDK/factory mocks are involved in this adapter contract test.
15
+ async function generate(properties: object) {
16
+ const prompts: string[] = [];
17
+ const model = {
18
+ bindTools: () => ({
19
+ async *stream(messages: BaseMessage[]) {
20
+ prompts.push(String(messages[0].content));
21
+ yield new AIMessageChunk({
22
+ content: "",
23
+ tool_calls: [
24
+ {
25
+ id: "render",
26
+ name: "render_a2ui",
27
+ args: { surfaceId: "dashboard", components },
28
+ },
29
+ ],
30
+ });
31
+ },
32
+ }),
33
+ };
34
+ const state = {
35
+ messages: [
36
+ new HumanMessage("Dashboard"),
37
+ new AIMessage({
38
+ content: "",
39
+ tool_calls: [{ id: "generate", name: "generate_a2ui", args: {} }],
40
+ }),
41
+ ],
42
+ thread_id: "native-tool-contract",
43
+ ...properties,
44
+ };
45
+ const before = JSON.stringify(state);
46
+ const middleware = createCopilotkitMiddleware();
47
+ let tool: ReturnType<typeof getA2UITools> | undefined;
48
+ await middleware.wrapModelCall(
49
+ { model, state, tools: [], messages: [], runtime: {} },
50
+ async (request) => {
51
+ tool = request.tools.find(
52
+ (candidate: { name: string }) => candidate.name === "generate_a2ui",
53
+ );
54
+ return new AIMessage("ok");
55
+ },
56
+ );
57
+ if (!tool) throw new Error("Expected the native A2UI tool");
58
+ const result = await tool.invoke({ intent: "create" }, { state });
59
+ if (typeof result !== "string")
60
+ throw new Error("Expected a native operations envelope");
61
+ expect(JSON.stringify(state)).toBe(before);
62
+ expect(JSON.parse(result)).toEqual({
63
+ a2ui_operations: [
64
+ {
65
+ version: "v0.9",
66
+ createSurface: {
67
+ surfaceId: "dashboard",
68
+ catalogId: "copilotkit://app-dashboard-catalog",
69
+ },
70
+ },
71
+ {
72
+ version: "v0.9",
73
+ updateComponents: { surfaceId: "dashboard", components },
74
+ },
75
+ ],
76
+ });
77
+ middleware.afterAgent(state);
78
+ return prompts[0];
79
+ }
80
+
81
+ describe("native A2UI tool namespace contract", () => {
82
+ it("uses the merged schema in the real toolkit prompt and CopilotKit catalog in native operations", async () => {
83
+ const prompt = await generate({
84
+ "ag-ui": {
85
+ inject_a2ui_tool: true,
86
+ a2ui_schema: {
87
+ catalogId: "agui://base",
88
+ components: { Base: { description: "RETAINED_NATIVE_SCHEMA" } },
89
+ },
90
+ },
91
+ copilotkit: {
92
+ a2ui_schema: {
93
+ catalogId: "copilotkit://app-dashboard-catalog",
94
+ components: { Text: { description: "COPILOTKIT_SCHEMA" } },
95
+ },
96
+ },
97
+ });
98
+ expect(prompt).toContain("RETAINED_NATIVE_SCHEMA");
99
+ expect(prompt).toContain("COPILOTKIT_SCHEMA");
100
+ });
101
+
102
+ it("uses an AG-UI-only serialized context catalog in native operations", async () => {
103
+ const prompt = await generate({
104
+ "ag-ui": {
105
+ inject_a2ui_tool: true,
106
+ context: JSON.stringify([
107
+ {
108
+ description: "A2UI catalog capabilities",
109
+ value: JSON.stringify(
110
+ "Available A2UI catalog:\n- copilotkit://app-dashboard-catalog\n - Text: {...}",
111
+ ),
112
+ },
113
+ ]),
114
+ },
115
+ });
116
+ expect(prompt).toContain("copilotkit://app-dashboard-catalog");
117
+ });
118
+ });
@@ -0,0 +1,288 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { AIMessage, HumanMessage } from "@langchain/core/messages";
3
+ import { StateGraph, START, END } from "@langchain/langgraph";
4
+ import type { A2UIToolParams } from "@ag-ui/langgraph";
5
+
6
+ // Capture the adapter boundary; no model or LLM responses are mocked.
7
+ const { captured, invoked } = vi.hoisted(() => {
8
+ const params: A2UIToolParams[] = [];
9
+ return {
10
+ captured: params,
11
+ invoked: vi.fn(
12
+ async (_input: unknown, config: { state?: unknown }) => config.state,
13
+ ),
14
+ };
15
+ });
16
+ vi.mock("@ag-ui/langgraph", () => ({
17
+ getA2UITools: (params: A2UIToolParams) => {
18
+ captured.push(params);
19
+ return { name: "generate_a2ui", invoke: invoked };
20
+ },
21
+ }));
22
+ import { createCopilotkitMiddleware } from "../middleware";
23
+
24
+ const catalog = (id: string) => [
25
+ {
26
+ description: "A2UI catalog capabilities",
27
+ value: "Available A2UI catalog:\n- " + id + "\n - Card: {}",
28
+ },
29
+ ];
30
+ const middleware = createCopilotkitMiddleware();
31
+ async function wrap(state: object) {
32
+ const tools: Array<{
33
+ name: string;
34
+ invoke: (input: unknown, config: { state: object }) => Promise<unknown>;
35
+ }> = [];
36
+ const request = { state, model: {}, tools, messages: [], runtime: {} };
37
+ let received = request;
38
+ await middleware.wrapModelCall(request, async (req) => {
39
+ received = req;
40
+ return new AIMessage("ok");
41
+ });
42
+ return received;
43
+ }
44
+ function context(state: object, runtime = {}) {
45
+ return middleware.beforeAgent(
46
+ { messages: [new HumanMessage("hello")], ...state },
47
+ runtime,
48
+ );
49
+ }
50
+
51
+ beforeEach(() => {
52
+ captured.length = 0;
53
+ invoked.mockClear();
54
+ });
55
+
56
+ describe("effective AG-UI and CopilotKit properties", () => {
57
+ it.each([null, false, []])(
58
+ "treats explicit %j actions as empty while injection is enabled",
59
+ async (actions) => {
60
+ const result = await wrap({
61
+ "ag-ui": { inject_a2ui_tool: true, actions: [{ name: "base" }] },
62
+ copilotkit: { actions },
63
+ });
64
+ expect(result.tools.map((tool) => tool.name)).toEqual(["generate_a2ui"]);
65
+ },
66
+ );
67
+
68
+ it("replaces serialized schemas atomically and adapts object context only for the toolkit", async () => {
69
+ const schema = JSON.stringify({ catalogId: "override" });
70
+ const state = {
71
+ "ag-ui": {
72
+ inject_a2ui_tool: true,
73
+ a2ui_schema: JSON.stringify({
74
+ catalogId: "base",
75
+ components: { Card: {} },
76
+ }),
77
+ },
78
+ copilotkit: { a2ui_schema: schema, context: { user: "alice" } },
79
+ };
80
+ const result = await wrap(state);
81
+ await result.tools[0].invoke({}, { state });
82
+ expect(captured[0].defaultCatalogId).toBe("override");
83
+ expect(invoked.mock.calls[0][1].state).toEqual(
84
+ expect.objectContaining({
85
+ "ag-ui": expect.objectContaining({ a2ui_schema: schema, context: [] }),
86
+ }),
87
+ );
88
+ expect(state.copilotkit.context).toEqual({ user: "alice" });
89
+ });
90
+
91
+ it("retains both namespace fields through the graph state schema", async () => {
92
+ const graph = new StateGraph(middleware.stateSchema)
93
+ .addNode("echo", () => ({}))
94
+ .addEdge(START, "echo")
95
+ .addEdge("echo", END)
96
+ .compile();
97
+ const state = {
98
+ "ag-ui": { context: catalog("native") },
99
+ copilotkit: { a2ui_schema: "schema", inject_a2ui_tool: false },
100
+ };
101
+ expect(await graph.invoke(state)).toEqual(state);
102
+ });
103
+
104
+ it("decodes serialized catalog carriers only at the catalog consumer", async () => {
105
+ const entries = catalog("serialized");
106
+ entries[0].value = JSON.stringify(entries[0].value);
107
+ const state = {
108
+ "ag-ui": { inject_a2ui_tool: true, context: JSON.stringify(entries) },
109
+ };
110
+ await wrap(state);
111
+ expect(captured[0].defaultCatalogId).toBe("serialized");
112
+ expect(context(state).messages[0].content).toBe(
113
+ "App Context:\n" + state["ag-ui"].context,
114
+ );
115
+ });
116
+
117
+ it("ignores malformed catalog entries", async () => {
118
+ await wrap({
119
+ "ag-ui": {
120
+ inject_a2ui_tool: true,
121
+ context: [
122
+ null,
123
+ 1,
124
+ { description: 4, value: true },
125
+ { description: "A2UI catalog", value: {} },
126
+ ],
127
+ },
128
+ });
129
+ expect(captured[0].defaultCatalogId).toBeUndefined();
130
+ });
131
+
132
+ it("merges nested context while preserving conflicting scalar and array leaves", () => {
133
+ const state = {
134
+ "ag-ui": {
135
+ context: {
136
+ nested: { retained: 1, conflict: "old", items: [{ base: 1 }] },
137
+ enabled: true,
138
+ },
139
+ },
140
+ copilotkit: {
141
+ context: {
142
+ nested: { added: 2, conflict: null, items: [{ override: 2 }] },
143
+ enabled: false,
144
+ },
145
+ },
146
+ };
147
+ const before = structuredClone(state);
148
+ const result = context(state);
149
+ expect(result.messages[0].content).toBe(
150
+ "App Context:\n" +
151
+ JSON.stringify(
152
+ {
153
+ nested: {
154
+ retained: 1,
155
+ conflict: null,
156
+ items: [{ override: 2 }],
157
+ added: 2,
158
+ },
159
+ enabled: false,
160
+ },
161
+ null,
162
+ 2,
163
+ ),
164
+ );
165
+ expect(state).toEqual(before);
166
+ });
167
+
168
+ it.each([null, false, "", []])(
169
+ "does not resurrect AG-UI or runtime context after an explicit %j override",
170
+ (value) => {
171
+ expect(
172
+ context(
173
+ { "ag-ui": { context: "base" }, copilotkit: { context: value } },
174
+ { context: "runtime" },
175
+ ),
176
+ ).toBeUndefined();
177
+ },
178
+ );
179
+
180
+ it("keeps serialized context atomic and preserves the runtime fallback", () => {
181
+ expect(
182
+ context({
183
+ "ag-ui": { context: { base: 1 } },
184
+ copilotkit: { context: '{"override":2}' },
185
+ }).messages[0].content,
186
+ ).toBe('App Context:\n{"override":2}');
187
+ expect(context({}, { context: "runtime" }).messages[0].content).toBe(
188
+ "App Context:\nruntime",
189
+ );
190
+ });
191
+
192
+ it.each(["ag-ui", "copilotkit"])(
193
+ "uses a catalog and frontend action supplied only through %s",
194
+ async (namespace) => {
195
+ const state = {
196
+ [namespace]: {
197
+ context: catalog("catalog-one"),
198
+ actions: [{ name: "frontend" }],
199
+ inject_a2ui_tool: true,
200
+ },
201
+ };
202
+ const result = await wrap(state);
203
+ expect(result.tools.map((tool) => tool.name)).toEqual([
204
+ "generate_a2ui",
205
+ "frontend",
206
+ ]);
207
+ expect(captured[0].defaultCatalogId).toBe("catalog-one");
208
+ expect(captured[0].guidelines.compositionGuide).toContain("catalog-one");
209
+ },
210
+ );
211
+
212
+ it("replaces context and action lists as wholes, including empty lists", async () => {
213
+ const state = {
214
+ "ag-ui": {
215
+ context: catalog("base"),
216
+ actions: [{ name: "base" }],
217
+ inject_a2ui_tool: true,
218
+ },
219
+ copilotkit: { context: [], actions: [] },
220
+ };
221
+ const result = await wrap(state);
222
+ expect(result.tools.map((tool) => tool.name)).toEqual(["generate_a2ui"]);
223
+ expect(captured[0].defaultCatalogId).toBeUndefined();
224
+ expect(captured[0].guidelines).toBeUndefined();
225
+ });
226
+
227
+ it.each([false, null, ""])(
228
+ "honors an explicit %j injection override",
229
+ async (value) => {
230
+ const result = await wrap({
231
+ "ag-ui": { inject_a2ui_tool: true },
232
+ copilotkit: { inject_a2ui_tool: value },
233
+ });
234
+ expect(result.tools).toEqual([]);
235
+ expect(captured).toEqual([]);
236
+ },
237
+ );
238
+
239
+ it("intercepts and restores AG-UI frontend calls without losing the namespace", () => {
240
+ const message = new AIMessage({
241
+ id: "assistant",
242
+ content: "",
243
+ tool_calls: [{ id: "call", name: "frontend", args: {} }],
244
+ });
245
+ const state = {
246
+ "ag-ui": { actions: [{ name: "frontend" }] },
247
+ messages: [message],
248
+ };
249
+ const intercepted = middleware.afterModel(state);
250
+ expect(intercepted.messages[0].tool_calls).toEqual([]);
251
+ const restored = middleware.afterAgent({ ...state, ...intercepted });
252
+ expect(restored.messages[0].tool_calls).toEqual(message.tool_calls);
253
+ expect(state.messages[0]).toBe(message);
254
+ });
255
+
256
+ it("delivers the merged native schema to the tool invocation without mutating state", async () => {
257
+ const state = {
258
+ "ag-ui": {
259
+ inject_a2ui_tool: true,
260
+ a2ui_schema: {
261
+ catalogId: "base",
262
+ components: { Card: { base: true, shared: "old" } },
263
+ },
264
+ },
265
+ copilotkit: {
266
+ a2ui_schema: {
267
+ catalogId: "override",
268
+ components: { Card: { shared: "new" }, Button: {} },
269
+ },
270
+ },
271
+ };
272
+ const before = structuredClone(state);
273
+ const result = await wrap(state);
274
+ await result.tools[0].invoke({}, { state });
275
+ expect(captured[0].defaultCatalogId).toBe("override");
276
+ expect(invoked.mock.calls[0][1].state).toEqual(
277
+ expect.objectContaining({
278
+ "ag-ui": expect.objectContaining({
279
+ a2ui_schema: JSON.stringify({
280
+ catalogId: "override",
281
+ components: { Card: { base: true, shared: "new" }, Button: {} },
282
+ }),
283
+ }),
284
+ }),
285
+ );
286
+ expect(state).toEqual(before);
287
+ });
288
+ });
@@ -20,14 +20,56 @@ const A2UI_DEFAULT_THREAD_KEY = "__copilotkit_a2ui_default__";
20
20
  const a2uiThreadKey = (state: any): string =>
21
21
  (state?.thread_id as string) || A2UI_DEFAULT_THREAD_KEY;
22
22
 
23
+ /**
24
+ * Merge AG-UI defaults with CopilotKit overrides. Dictionaries recurse; every
25
+ * conflicting leaf uses CopilotKit, including false, null and empty values.
26
+ * Arrays and serialized strings are atomic: no positional pairing or decoding.
27
+ */
28
+ const isPropertyBag = (value: unknown): value is Record<string, unknown> =>
29
+ value !== null && typeof value === "object" && !Array.isArray(value);
30
+
31
+ const mergeProperties = (
32
+ base: Record<string, unknown>,
33
+ overrides: Record<string, unknown>,
34
+ ): Record<string, unknown> =>
35
+ Object.fromEntries(
36
+ Array.from(new Set([...Object.keys(base), ...Object.keys(overrides)])).map(
37
+ (key) => {
38
+ const value = Object.prototype.hasOwnProperty.call(overrides, key)
39
+ ? isPropertyBag(base[key]) && isPropertyBag(overrides[key])
40
+ ? mergeProperties(base[key], overrides[key])
41
+ : overrides[key]
42
+ : base[key];
43
+ return [key, value];
44
+ },
45
+ ),
46
+ );
47
+
48
+ const effectiveProperties = (state: unknown): Record<string, unknown> => {
49
+ const source = isPropertyBag(state) ? state : {};
50
+ return mergeProperties(
51
+ isPropertyBag(source["ag-ui"]) ? source["ag-ui"] : {},
52
+ isPropertyBag(source.copilotkit) ? source.copilotkit : {},
53
+ );
54
+ };
55
+
56
+ const decodeCatalogValue = (value: unknown): unknown => {
57
+ if (typeof value !== "string") return value;
58
+ try {
59
+ return JSON.parse(value);
60
+ } catch {
61
+ return value;
62
+ }
63
+ };
64
+
23
65
  /**
24
66
  * Find the frontend-registered A2UI catalog wherever it was passed. Returns
25
67
  * `{ compositionGuide?, catalogId? }` when a catalog is present, else `null`
26
- * (so the tool is never advertised when the client can't render A2UI). Two
68
+ * independently of the explicit tool-injection decision. Two
27
69
  * delivery paths, depending on how the agent is served:
28
70
  * - AG-UI native endpoint → `state["ag-ui"].a2ui_schema` (JSON
29
71
  * `{ catalogId, components }`); the toolkit reads it from state itself.
30
- * - CopilotKit runtime proxy → a `state.copilotkit.context` entry describing
72
+ * - Runtime proxy → an effective `context` entry describing
31
73
  * the A2UI catalog (catalog id + component schemas as text), passed to the
32
74
  * subagent via `compositionGuide`.
33
75
  * `catalogId` binds generated surfaces to the frontend's catalog so BYOC
@@ -36,7 +78,8 @@ const a2uiThreadKey = (state: any): string =>
36
78
  const resolveA2uiCatalog = (
37
79
  state: any,
38
80
  ): { compositionGuide?: string; catalogId?: string } | null => {
39
- const a2uiSchema = state?.["ag-ui"]?.a2ui_schema;
81
+ const properties = effectiveProperties(state);
82
+ const a2uiSchema = properties.a2ui_schema;
40
83
  if (a2uiSchema) {
41
84
  let catalogId: string | undefined;
42
85
  try {
@@ -48,11 +91,17 @@ const resolveA2uiCatalog = (
48
91
  }
49
92
  return { catalogId };
50
93
  }
51
- const context = state?.copilotkit?.context;
94
+ const context = decodeCatalogValue(properties.context);
52
95
  for (const entry of Array.isArray(context) ? context : []) {
53
- const description = entry?.description ?? "";
54
- const value = entry?.value ?? "";
55
- if (!description.includes("A2UI catalog") || !value) continue;
96
+ const description = entry?.description;
97
+ const value = decodeCatalogValue(entry?.value);
98
+ if (
99
+ typeof description !== "string" ||
100
+ typeof value !== "string" ||
101
+ !description.includes("A2UI catalog") ||
102
+ !value
103
+ )
104
+ continue;
56
105
  const match = /^\s*-\s+(\S+)/m.exec(value);
57
106
  return { compositionGuide: value, catalogId: match?.[1] };
58
107
  }
@@ -67,7 +116,7 @@ const resolveA2uiCatalog = (
67
116
  * no signal (off, or no A2UI middleware in the pipeline) → no auto-injection.
68
117
  */
69
118
  const a2uiInjectDecision = (state: any): boolean | string | undefined =>
70
- state?.["ag-ui"]?.inject_a2ui_tool;
119
+ effectiveProperties(state).inject_a2ui_tool as boolean | string | undefined;
71
120
 
72
121
  type WithJsonSchema<T> = T extends { "~standard": infer S }
73
122
  ? Omit<T, "~standard"> & {
@@ -250,7 +299,10 @@ const createAppContextBeforeAgent = (state, runtime) => {
250
299
  }
251
300
 
252
301
  // Get app context from runtime
253
- const appContext = state["copilotkit"]?.context ?? runtime?.context;
302
+ const properties = effectiveProperties(state);
303
+ const appContext = Object.prototype.hasOwnProperty.call(properties, "context")
304
+ ? properties.context
305
+ : runtime?.context;
254
306
 
255
307
  // Check if appContext is missing or empty
256
308
  const isEmptyContext =
@@ -358,14 +410,19 @@ const createAppContextBeforeAgent = (state, runtime) => {
358
410
  */
359
411
  const copilotKitStateSchema = z
360
412
  .object({
413
+ "ag-ui": zodState(z.record(z.string(), z.unknown()).optional()),
361
414
  copilotkit: zodState(
362
415
  z
363
416
  .object({
364
- actions: z.array(z.any()),
417
+ actions: z
418
+ .union([z.array(z.any()), z.literal(false)])
419
+ .nullable()
420
+ .optional(),
365
421
  context: z.any().optional(),
366
422
  interceptedToolCalls: z.array(z.any()).optional(),
367
423
  originalAIMessageId: z.string().optional(),
368
424
  })
425
+ .passthrough()
369
426
  .optional(),
370
427
  ),
371
428
  })
@@ -475,12 +532,40 @@ const buildMiddlewareInput = (
475
532
  (request.tools || []).map((t: any) => t?.name),
476
533
  );
477
534
  if (!existingNames.has(candidate.name)) {
535
+ // ToolNode derives tool runtime state from its config, ignoring an
536
+ // overridden middleware request.state. Adapt the tool's invocation
537
+ // config so the toolkit sees the same effective properties.
538
+ const invoke = candidate.invoke.bind(candidate);
539
+ candidate.invoke = (input, config) => {
540
+ const state =
541
+ config && "state" in config && isPropertyBag(config.state)
542
+ ? config.state
543
+ : request.state;
544
+ const properties = effectiveProperties(state);
545
+ const schema = properties.a2ui_schema;
546
+ const context = decodeCatalogValue(properties.context);
547
+ return invoke(input, {
548
+ ...config,
549
+ state: {
550
+ ...state,
551
+ "ag-ui": {
552
+ ...properties,
553
+ context: Array.isArray(context) ? context : [],
554
+ // The toolkit interpolates the native schema into its prompt.
555
+ a2ui_schema: isPropertyBag(schema)
556
+ ? JSON.stringify(schema)
557
+ : schema,
558
+ },
559
+ },
560
+ });
561
+ };
478
562
  a2uiTool = candidate;
479
563
  a2uiToolsByThread.set(a2uiThreadKey(request.state), a2uiTool);
480
564
  }
481
565
  }
482
566
 
483
- let frontendTools = request.state["copilotkit"]?.actions ?? [];
567
+ const actions = effectiveProperties(request.state).actions;
568
+ let frontendTools = Array.isArray(actions) ? actions : [];
484
569
  if (a2uiTool) {
485
570
  // Our generate_a2ui replaces the runtime's render tool — don't advertise
486
571
  // both. Drop the render tool the A2UI middleware injected.
@@ -568,7 +653,8 @@ const buildMiddlewareInput = (
568
653
 
569
654
  // Intercept frontend tool calls after model returns, before ToolNode executes
570
655
  afterModel: (state) => {
571
- const frontendTools = state["copilotkit"]?.actions ?? [];
656
+ const actions = effectiveProperties(state).actions;
657
+ const frontendTools = Array.isArray(actions) ? actions : [];
572
658
  if (frontendTools.length === 0) return;
573
659
 
574
660
  const frontendToolNames = new Set(