@copilotkit/sdk-js 1.56.4 → 1.56.5
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.
- package/dist/langgraph/middleware.cjs +144 -11
- package/dist/langgraph/middleware.cjs.map +1 -1
- package/dist/langgraph/middleware.d.cts +68 -1
- package/dist/langgraph/middleware.d.cts.map +1 -1
- package/dist/langgraph/middleware.d.mts +68 -1
- package/dist/langgraph/middleware.d.mts.map +1 -1
- package/dist/langgraph/middleware.mjs +143 -12
- package/dist/langgraph/middleware.mjs.map +1 -1
- package/dist/langgraph/state-schema.d.cts +22 -16
- package/dist/langgraph/state-schema.d.cts.map +1 -1
- package/dist/langgraph/state-schema.d.mts +22 -16
- package/dist/langgraph/state-schema.d.mts.map +1 -1
- package/dist/langgraph/types.d.cts +107 -50
- package/dist/langgraph/types.d.cts.map +1 -1
- package/dist/langgraph/types.d.mts +107 -50
- package/dist/langgraph/types.d.mts.map +1 -1
- package/dist/langgraph.cjs +3 -1
- package/dist/langgraph.d.cts +2 -2
- package/dist/langgraph.d.mts +2 -2
- package/dist/langgraph.mjs +2 -2
- package/package.json +6 -6
- package/src/langgraph/__tests__/middleware.test.ts +611 -0
- package/src/langgraph/middleware.ts +210 -15
|
@@ -1,7 +1,175 @@
|
|
|
1
1
|
import { createMiddleware, AIMessage, SystemMessage } from "langchain";
|
|
2
2
|
import type { InteropZodObject } from "@langchain/core/utils/types";
|
|
3
|
+
import type {
|
|
4
|
+
StandardJSONSchemaV1,
|
|
5
|
+
StandardSchemaV1,
|
|
6
|
+
} from "@standard-schema/spec";
|
|
3
7
|
import * as z from "zod";
|
|
4
8
|
|
|
9
|
+
type WithJsonSchema<T> = T extends { "~standard": infer S }
|
|
10
|
+
? Omit<T, "~standard"> & {
|
|
11
|
+
"~standard": S &
|
|
12
|
+
StandardJSONSchemaV1.Props<
|
|
13
|
+
S extends StandardSchemaV1.Props<infer I, any> ? I : unknown,
|
|
14
|
+
S extends StandardSchemaV1.Props<any, infer O> ? O : unknown
|
|
15
|
+
>;
|
|
16
|
+
}
|
|
17
|
+
: T;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Augment a Standard-Schema–compatible schema (e.g. Zod) with a
|
|
21
|
+
* `~standard.jsonSchema.input` hook so LangGraph's
|
|
22
|
+
* `getJsonSchemaFromSchema` (called from `StateSchema.getJsonSchema`)
|
|
23
|
+
* can serialize the field.
|
|
24
|
+
*
|
|
25
|
+
* Without this, Zod v4 fields carry `~standard.validate` + `vendor` only,
|
|
26
|
+
* and `isStandardJSONSchema()` returns false, so the field is silently
|
|
27
|
+
* dropped from the graph's `output_schema`. That makes AG-UI
|
|
28
|
+
* `STATE_SNAPSHOT` events filter the field out of the payload sent to
|
|
29
|
+
* the frontend even though the underlying thread state has the value.
|
|
30
|
+
*
|
|
31
|
+
* Use this on any custom state field you want visible to the frontend
|
|
32
|
+
* via `useAgent().state.*`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { zodState } from "@copilotkit/sdk-js/langgraph";
|
|
37
|
+
*
|
|
38
|
+
* const stateSchema = z.object({
|
|
39
|
+
* todos: zodState(z.array(TodoSchema).default(() => [])),
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function zodState<T extends object>(schema: T): WithJsonSchema<T> {
|
|
44
|
+
const std = (schema as { "~standard"?: { jsonSchema?: unknown } })[
|
|
45
|
+
"~standard"
|
|
46
|
+
];
|
|
47
|
+
if (std && typeof std === "object" && !("jsonSchema" in std)) {
|
|
48
|
+
let cached: Record<string, unknown> | undefined;
|
|
49
|
+
std.jsonSchema = {
|
|
50
|
+
input: () => {
|
|
51
|
+
if (cached) return cached;
|
|
52
|
+
// Prefer zod-v4's native `toJSONSchema` when available. Falls back to
|
|
53
|
+
// an empty object, which is sufficient for the field to appear in the
|
|
54
|
+
// graph's output_schema (langgraph-api treats it as an opaque field).
|
|
55
|
+
try {
|
|
56
|
+
const maybeV4ToJsonSchema = (
|
|
57
|
+
z as unknown as {
|
|
58
|
+
toJSONSchema?: (s: unknown) => Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
).toJSONSchema;
|
|
61
|
+
cached =
|
|
62
|
+
typeof maybeV4ToJsonSchema === "function"
|
|
63
|
+
? maybeV4ToJsonSchema(schema)
|
|
64
|
+
: {};
|
|
65
|
+
} catch {
|
|
66
|
+
cached = {};
|
|
67
|
+
}
|
|
68
|
+
return cached;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return schema as WithJsonSchema<T>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Internal/framework state keys that should never be auto-surfaced to the
|
|
77
|
+
* LLM as user-facing state. These are reducer-managed message buckets,
|
|
78
|
+
* CopilotKit/AG-UI plumbing, or graph-internal scaffolding.
|
|
79
|
+
*/
|
|
80
|
+
const RESERVED_STATE_KEYS: ReadonlySet<string> = new Set([
|
|
81
|
+
"messages",
|
|
82
|
+
"copilotkit",
|
|
83
|
+
"ag-ui",
|
|
84
|
+
"tools",
|
|
85
|
+
"structured_response",
|
|
86
|
+
"thread_id",
|
|
87
|
+
"remaining_steps",
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Controls how user-defined state keys are surfaced into the LLM prompt
|
|
92
|
+
* on every model call. Off by default to avoid leaking arbitrary state
|
|
93
|
+
* into prompts; opt in explicitly.
|
|
94
|
+
*
|
|
95
|
+
* - `false` (default) — never surface state.
|
|
96
|
+
* - `true` — every state key not in the reserved internal set and not
|
|
97
|
+
* prefixed with `_` is JSON-serialized into a "Current agent state:"
|
|
98
|
+
* note appended to the system prompt.
|
|
99
|
+
* - `string[]` — only surface the named keys (use this when you want
|
|
100
|
+
* explicit control over what the LLM sees, e.g. `["liked", "todos"]`).
|
|
101
|
+
*/
|
|
102
|
+
export type ExposeStateOption = boolean | readonly string[];
|
|
103
|
+
|
|
104
|
+
const buildStateNote = (
|
|
105
|
+
state: Record<string, unknown>,
|
|
106
|
+
expose: ExposeStateOption,
|
|
107
|
+
): string | null => {
|
|
108
|
+
if (expose === false) return null;
|
|
109
|
+
|
|
110
|
+
const allow: ReadonlySet<string> | null = Array.isArray(expose)
|
|
111
|
+
? new Set(expose)
|
|
112
|
+
: null;
|
|
113
|
+
|
|
114
|
+
const snapshot: Record<string, unknown> = {};
|
|
115
|
+
for (const key of Object.keys(state)) {
|
|
116
|
+
if (
|
|
117
|
+
allow
|
|
118
|
+
? !allow.has(key)
|
|
119
|
+
: RESERVED_STATE_KEYS.has(key) || key.startsWith("_")
|
|
120
|
+
) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const value = state[key];
|
|
124
|
+
if (
|
|
125
|
+
value === undefined ||
|
|
126
|
+
value === null ||
|
|
127
|
+
value === "" ||
|
|
128
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
129
|
+
(typeof value === "object" &&
|
|
130
|
+
!Array.isArray(value) &&
|
|
131
|
+
Object.keys(value as Record<string, unknown>).length === 0)
|
|
132
|
+
) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
snapshot[key] = value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Object.keys(snapshot).length === 0) return null;
|
|
139
|
+
|
|
140
|
+
let body: string;
|
|
141
|
+
try {
|
|
142
|
+
body = JSON.stringify(snapshot, null, 2);
|
|
143
|
+
} catch {
|
|
144
|
+
body = String(snapshot);
|
|
145
|
+
}
|
|
146
|
+
return `Current agent state:\n${body}`;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const applyStateNote = (request: any, expose: ExposeStateOption): any => {
|
|
150
|
+
const note = buildStateNote(
|
|
151
|
+
(request.state ?? {}) as Record<string, unknown>,
|
|
152
|
+
expose,
|
|
153
|
+
);
|
|
154
|
+
if (!note) return request;
|
|
155
|
+
|
|
156
|
+
const existing = request.systemPrompt;
|
|
157
|
+
if (existing == null) {
|
|
158
|
+
return { ...request, systemPrompt: new SystemMessage({ content: note }) };
|
|
159
|
+
}
|
|
160
|
+
// existing may be a string OR a SystemMessage
|
|
161
|
+
const baseText =
|
|
162
|
+
typeof existing === "string"
|
|
163
|
+
? existing
|
|
164
|
+
: typeof existing.content === "string"
|
|
165
|
+
? existing.content
|
|
166
|
+
: String(existing.content);
|
|
167
|
+
return {
|
|
168
|
+
...request,
|
|
169
|
+
systemPrompt: new SystemMessage({ content: `${baseText}\n\n${note}` }),
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
5
173
|
const createAppContextBeforeAgent = (state, runtime) => {
|
|
6
174
|
const messages = state.messages;
|
|
7
175
|
|
|
@@ -117,23 +285,26 @@ const createAppContextBeforeAgent = (state, runtime) => {
|
|
|
117
285
|
* ```
|
|
118
286
|
*/
|
|
119
287
|
const copilotKitStateSchema = z.object({
|
|
120
|
-
copilotkit:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
288
|
+
copilotkit: zodState(
|
|
289
|
+
z
|
|
290
|
+
.object({
|
|
291
|
+
actions: z.array(z.any()),
|
|
292
|
+
context: z.any().optional(),
|
|
293
|
+
interceptedToolCalls: z.array(z.any()).optional(),
|
|
294
|
+
originalAIMessageId: z.string().optional(),
|
|
295
|
+
})
|
|
296
|
+
.optional(),
|
|
297
|
+
),
|
|
128
298
|
});
|
|
129
299
|
|
|
130
|
-
const
|
|
300
|
+
const buildMiddlewareInput = (exposeState: ExposeStateOption) => ({
|
|
131
301
|
name: "CopilotKitMiddleware",
|
|
132
302
|
|
|
133
303
|
stateSchema: copilotKitStateSchema as unknown as InteropZodObject,
|
|
134
304
|
|
|
135
|
-
// Inject frontend tools before model call
|
|
136
|
-
wrapModelCall: async (request, handler) => {
|
|
305
|
+
// Inject frontend tools and surface user state before model call
|
|
306
|
+
wrapModelCall: async (request: any, handler: (req: any) => Promise<any>) => {
|
|
307
|
+
request = applyStateNote(request, exposeState);
|
|
137
308
|
const frontendTools = request.state["copilotkit"]?.actions ?? [];
|
|
138
309
|
|
|
139
310
|
if (frontendTools.length === 0) {
|
|
@@ -234,9 +405,33 @@ const middlewareInput = {
|
|
|
234
405
|
},
|
|
235
406
|
};
|
|
236
407
|
},
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Build a CopilotKit middleware instance with custom options.
|
|
412
|
+
*
|
|
413
|
+
* Use this when you want to override the default state-exposure behavior
|
|
414
|
+
* (for example to hide a sensitive key, or to use an explicit allowlist).
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* import { createCopilotkitMiddleware } from "@copilotkit/sdk-js/langgraph";
|
|
419
|
+
*
|
|
420
|
+
* const middleware = createCopilotkitMiddleware({
|
|
421
|
+
* exposeState: ["liked", "todos"],
|
|
422
|
+
* });
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
export const createCopilotkitMiddleware = (
|
|
426
|
+
options: { exposeState?: ExposeStateOption } = {},
|
|
427
|
+
) => {
|
|
428
|
+
const exposeState = options.exposeState ?? false;
|
|
429
|
+
return createMiddleware(buildMiddlewareInput(exposeState) as any);
|
|
240
430
|
};
|
|
241
431
|
|
|
242
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Default CopilotKit middleware singleton — does NOT surface user state
|
|
434
|
+
* to the LLM. Pass `exposeState: true` (or an allowlist) to
|
|
435
|
+
* {@link createCopilotkitMiddleware} to opt in.
|
|
436
|
+
*/
|
|
437
|
+
export const copilotkitMiddleware = createCopilotkitMiddleware();
|