@copilotkit/sdk-js 1.56.2 → 1.56.4-canary.1777529757
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/langchain.cjs +3 -0
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.d.cts +3 -2
- package/dist/langchain.d.mts +3 -2
- package/dist/langchain.mjs +3 -2
- package/dist/langchain.mjs.map +1 -1
- 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.cjs +37 -0
- package/dist/langgraph/state-schema.cjs.map +1 -0
- package/dist/langgraph/state-schema.d.cts +118 -0
- package/dist/langgraph/state-schema.d.cts.map +1 -0
- package/dist/langgraph/state-schema.d.mts +118 -0
- package/dist/langgraph/state-schema.d.mts.map +1 -0
- package/dist/langgraph/state-schema.mjs +36 -0
- package/dist/langgraph/state-schema.mjs.map +1 -0
- package/dist/langgraph/types.cjs +41 -0
- package/dist/langgraph/types.cjs.map +1 -1
- package/dist/langgraph/types.d.cts +140 -51
- package/dist/langgraph/types.d.cts.map +1 -1
- package/dist/langgraph/types.d.mts +140 -51
- package/dist/langgraph/types.d.mts.map +1 -1
- package/dist/langgraph/types.mjs +41 -1
- package/dist/langgraph/types.mjs.map +1 -1
- package/dist/langgraph.cjs +6 -1
- package/dist/langgraph.d.cts +4 -3
- package/dist/langgraph.d.mts +4 -3
- package/dist/langgraph.mjs +4 -3
- package/package.json +6 -6
- package/src/langchain.ts +4 -0
- package/src/langgraph/__tests__/middleware.test.ts +611 -0
- package/src/langgraph/__tests__/state-schema.test.ts +85 -0
- package/src/langgraph/__tests__/utils.test.ts +254 -0
- package/src/langgraph/index.ts +1 -0
- package/src/langgraph/middleware.ts +210 -15
- package/src/langgraph/state-schema.ts +34 -0
- package/src/langgraph/types.ts +57 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copilotkitCustomizeConfig,
|
|
3
|
+
convertActionsToDynamicStructuredTools,
|
|
4
|
+
convertActionToDynamicStructuredTool,
|
|
5
|
+
} from "../utils";
|
|
6
|
+
|
|
7
|
+
describe("copilotkitCustomizeConfig", () => {
|
|
8
|
+
it("returns config unchanged when no options provided", () => {
|
|
9
|
+
const baseConfig = { metadata: { existing: true } };
|
|
10
|
+
const result = copilotkitCustomizeConfig(baseConfig);
|
|
11
|
+
expect(result.metadata).toEqual({ existing: true });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns config unchanged when options is empty object", () => {
|
|
15
|
+
const baseConfig = { metadata: {} };
|
|
16
|
+
const result = copilotkitCustomizeConfig(baseConfig, {});
|
|
17
|
+
expect(result.metadata).toEqual({});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("sets emit-messages metadata flag to false", () => {
|
|
21
|
+
const result = copilotkitCustomizeConfig({}, { emitMessages: false });
|
|
22
|
+
expect(result.metadata!["copilotkit:emit-messages"]).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("sets emit-messages metadata flag to true", () => {
|
|
26
|
+
const result = copilotkitCustomizeConfig({}, { emitMessages: true });
|
|
27
|
+
expect(result.metadata!["copilotkit:emit-messages"]).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("sets emit-tool-calls metadata flag to false", () => {
|
|
31
|
+
const result = copilotkitCustomizeConfig({}, { emitToolCalls: false });
|
|
32
|
+
expect(result.metadata!["copilotkit:emit-tool-calls"]).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("sets emit-tool-calls to a specific tool name string", () => {
|
|
36
|
+
const result = copilotkitCustomizeConfig(
|
|
37
|
+
{},
|
|
38
|
+
{ emitToolCalls: "SearchTool" },
|
|
39
|
+
);
|
|
40
|
+
expect(result.metadata!["copilotkit:emit-tool-calls"]).toBe("SearchTool");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("sets emit-tool-calls to an array of tool names", () => {
|
|
44
|
+
const result = copilotkitCustomizeConfig(
|
|
45
|
+
{},
|
|
46
|
+
{ emitToolCalls: ["SearchTool", "FetchTool"] },
|
|
47
|
+
);
|
|
48
|
+
expect(result.metadata!["copilotkit:emit-tool-calls"]).toEqual([
|
|
49
|
+
"SearchTool",
|
|
50
|
+
"FetchTool",
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("sets both emit flags together", () => {
|
|
55
|
+
const result = copilotkitCustomizeConfig(
|
|
56
|
+
{},
|
|
57
|
+
{
|
|
58
|
+
emitMessages: false,
|
|
59
|
+
emitToolCalls: false,
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
expect(result.metadata!["copilotkit:emit-messages"]).toBe(false);
|
|
63
|
+
expect(result.metadata!["copilotkit:emit-tool-calls"]).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("emitAll sets both messages and tool-calls to true", () => {
|
|
67
|
+
const result = copilotkitCustomizeConfig({}, { emitAll: true });
|
|
68
|
+
expect(result.metadata!["copilotkit:emit-messages"]).toBe(true);
|
|
69
|
+
expect(result.metadata!["copilotkit:emit-tool-calls"]).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("converts emitIntermediateState to snake_case in metadata", () => {
|
|
73
|
+
const result = copilotkitCustomizeConfig(
|
|
74
|
+
{},
|
|
75
|
+
{
|
|
76
|
+
emitIntermediateState: [
|
|
77
|
+
{ stateKey: "steps", tool: "SearchTool", toolArgument: "steps" },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
const intermediateState =
|
|
82
|
+
result.metadata!["copilotkit:emit-intermediate-state"];
|
|
83
|
+
expect(intermediateState).toHaveLength(1);
|
|
84
|
+
expect(intermediateState[0]).toEqual({
|
|
85
|
+
state_key: "steps",
|
|
86
|
+
tool: "SearchTool",
|
|
87
|
+
tool_argument: "steps",
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("handles emitIntermediateState without toolArgument", () => {
|
|
92
|
+
const result = copilotkitCustomizeConfig(
|
|
93
|
+
{},
|
|
94
|
+
{
|
|
95
|
+
emitIntermediateState: [{ stateKey: "output", tool: "WriteTool" }],
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
const intermediateState =
|
|
99
|
+
result.metadata!["copilotkit:emit-intermediate-state"];
|
|
100
|
+
expect(intermediateState[0]).toEqual({
|
|
101
|
+
state_key: "output",
|
|
102
|
+
tool: "WriteTool",
|
|
103
|
+
tool_argument: undefined,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("throws when emitIntermediateState item is missing stateKey", () => {
|
|
108
|
+
expect(() => {
|
|
109
|
+
copilotkitCustomizeConfig(
|
|
110
|
+
{},
|
|
111
|
+
{
|
|
112
|
+
emitIntermediateState: [{ tool: "SearchTool" } as any],
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
}).toThrow("stateKey");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("throws when emitIntermediateState item is missing tool", () => {
|
|
119
|
+
expect(() => {
|
|
120
|
+
copilotkitCustomizeConfig(
|
|
121
|
+
{},
|
|
122
|
+
{
|
|
123
|
+
emitIntermediateState: [{ stateKey: "steps" } as any],
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
}).toThrow("tool");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("preserves existing metadata from baseConfig", () => {
|
|
130
|
+
const result = copilotkitCustomizeConfig(
|
|
131
|
+
{ metadata: { "custom-key": "custom-value" } },
|
|
132
|
+
{ emitMessages: false },
|
|
133
|
+
);
|
|
134
|
+
expect(result.metadata!["custom-key"]).toBe("custom-value");
|
|
135
|
+
expect(result.metadata!["copilotkit:emit-messages"]).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("handles null/undefined baseConfig gracefully", () => {
|
|
139
|
+
const result = copilotkitCustomizeConfig(null as any, {
|
|
140
|
+
emitMessages: false,
|
|
141
|
+
});
|
|
142
|
+
expect(result.metadata!["copilotkit:emit-messages"]).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("convertActionToDynamicStructuredTool", () => {
|
|
147
|
+
it("converts a valid action to DynamicStructuredTool", () => {
|
|
148
|
+
const action = {
|
|
149
|
+
name: "myTool",
|
|
150
|
+
description: "A test tool",
|
|
151
|
+
parameters: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
query: { type: "string" },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const tool = convertActionToDynamicStructuredTool(action);
|
|
159
|
+
expect(tool.name).toBe("myTool");
|
|
160
|
+
expect(tool.description).toBe("A test tool");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("throws when actionInput is null", () => {
|
|
164
|
+
expect(() => convertActionToDynamicStructuredTool(null)).toThrow(
|
|
165
|
+
"Action input is required",
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("throws when name is missing", () => {
|
|
170
|
+
expect(() =>
|
|
171
|
+
convertActionToDynamicStructuredTool({
|
|
172
|
+
description: "test",
|
|
173
|
+
parameters: {},
|
|
174
|
+
}),
|
|
175
|
+
).toThrow("name");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("throws when description is missing", () => {
|
|
179
|
+
expect(() =>
|
|
180
|
+
convertActionToDynamicStructuredTool({ name: "test", parameters: {} }),
|
|
181
|
+
).toThrow("description");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("throws when parameters is missing", () => {
|
|
185
|
+
expect(() =>
|
|
186
|
+
convertActionToDynamicStructuredTool({
|
|
187
|
+
name: "test",
|
|
188
|
+
description: "test",
|
|
189
|
+
}),
|
|
190
|
+
).toThrow("parameters");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("convertActionsToDynamicStructuredTools", () => {
|
|
195
|
+
it("converts multiple actions", () => {
|
|
196
|
+
const actions = [
|
|
197
|
+
{
|
|
198
|
+
name: "tool1",
|
|
199
|
+
description: "First tool",
|
|
200
|
+
parameters: { type: "object", properties: {} },
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "tool2",
|
|
204
|
+
description: "Second tool",
|
|
205
|
+
parameters: { type: "object", properties: {} },
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
const tools = convertActionsToDynamicStructuredTools(actions);
|
|
209
|
+
expect(tools).toHaveLength(2);
|
|
210
|
+
expect(tools[0].name).toBe("tool1");
|
|
211
|
+
expect(tools[1].name).toBe("tool2");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("returns empty array for empty input", () => {
|
|
215
|
+
const tools = convertActionsToDynamicStructuredTools([]);
|
|
216
|
+
expect(tools).toEqual([]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("handles { type: 'function', function: {...} } format", () => {
|
|
220
|
+
const actions = [
|
|
221
|
+
{
|
|
222
|
+
type: "function",
|
|
223
|
+
function: {
|
|
224
|
+
name: "wrappedTool",
|
|
225
|
+
description: "A wrapped tool",
|
|
226
|
+
parameters: { type: "object", properties: {} },
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
const tools = convertActionsToDynamicStructuredTools(actions);
|
|
231
|
+
expect(tools).toHaveLength(1);
|
|
232
|
+
expect(tools[0].name).toBe("wrappedTool");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("throws when input is not an array", () => {
|
|
236
|
+
expect(() =>
|
|
237
|
+
convertActionsToDynamicStructuredTools("not-array" as any),
|
|
238
|
+
).toThrow("Actions must be an array");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("wraps individual action errors with index info", () => {
|
|
242
|
+
const actions = [
|
|
243
|
+
{
|
|
244
|
+
name: "goodTool",
|
|
245
|
+
description: "works",
|
|
246
|
+
parameters: { type: "object", properties: {} },
|
|
247
|
+
},
|
|
248
|
+
{ name: "badTool" }, // missing description and parameters
|
|
249
|
+
];
|
|
250
|
+
expect(() => convertActionsToDynamicStructuredTools(actions)).toThrow(
|
|
251
|
+
"index 1",
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
});
|
package/src/langgraph/index.ts
CHANGED
|
@@ -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();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { MessagesValue, StateSchema } from "@langchain/langgraph";
|
|
2
|
+
import { CopilotKitPropertiesSchema } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CopilotKit agent state defined with LangGraph's modern
|
|
6
|
+
* [`StateSchema`](https://docs.langchain.com/oss/javascript/langgraph/graph-api)
|
|
7
|
+
* API.
|
|
8
|
+
*
|
|
9
|
+
* Prefer this over `CopilotKitStateAnnotation` when starting a new
|
|
10
|
+
* TypeScript agent. `Annotation.Root` is still supported by LangGraph but
|
|
11
|
+
* `StateSchema` is the recommended API going forward.
|
|
12
|
+
*
|
|
13
|
+
* ### Example
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { StateSchema } from "@langchain/langgraph";
|
|
17
|
+
* import { CopilotKitStateSchema } from "@copilotkit/sdk-js/langgraph";
|
|
18
|
+
* import { z } from "zod";
|
|
19
|
+
*
|
|
20
|
+
* export const AgentStateSchema = new StateSchema({
|
|
21
|
+
* language: z.enum(["english", "spanish"]),
|
|
22
|
+
* ...CopilotKitStateSchema.fields,
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* export type AgentState = typeof AgentStateSchema.State;
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const CopilotKitStateSchema = new StateSchema({
|
|
29
|
+
copilotkit: CopilotKitPropertiesSchema,
|
|
30
|
+
messages: MessagesValue,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type CopilotKitSchemaState = typeof CopilotKitStateSchema.State;
|
|
34
|
+
export type CopilotKitSchemaUpdate = typeof CopilotKitStateSchema.Update;
|
package/src/langgraph/types.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { Annotation, MessagesAnnotation } from "@langchain/langgraph";
|
|
2
2
|
|
|
3
|
+
export interface StandardSerializableSchema<Input, Output = Input> {
|
|
4
|
+
readonly "~standard": {
|
|
5
|
+
readonly version: 1;
|
|
6
|
+
readonly vendor: string;
|
|
7
|
+
readonly validate: (
|
|
8
|
+
value: unknown,
|
|
9
|
+
) => { value: Output } | { issues: ReadonlyArray<{ message: string }> };
|
|
10
|
+
readonly types?: { readonly input: Input; readonly output: Output };
|
|
11
|
+
readonly jsonSchema: {
|
|
12
|
+
readonly input: (options: { target: string }) => Record<string, unknown>;
|
|
13
|
+
readonly output: (options: { target: string }) => Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
export const CopilotKitPropertiesAnnotation = Annotation.Root({
|
|
4
19
|
actions: Annotation<any[]>,
|
|
5
20
|
context: Annotation<{ description: string; value: string }[]>,
|
|
@@ -12,6 +27,48 @@ export const CopilotKitStateAnnotation = Annotation.Root({
|
|
|
12
27
|
...MessagesAnnotation.spec,
|
|
13
28
|
});
|
|
14
29
|
|
|
30
|
+
const COPILOTKIT_PROPERTIES_JSON_SCHEMA = {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
actions: { type: "array", items: {} },
|
|
34
|
+
context: {
|
|
35
|
+
type: "array",
|
|
36
|
+
items: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
description: { type: "string" },
|
|
40
|
+
value: { type: "string" },
|
|
41
|
+
},
|
|
42
|
+
required: ["description", "value"],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
interceptedToolCalls: { type: "array", items: {} },
|
|
46
|
+
originalAIMessageId: { type: "string" },
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Standard Schema describing the `copilotkit` field on agent state.
|
|
52
|
+
*
|
|
53
|
+
* CopilotKit populates these fields at runtime, so the schema accepts any
|
|
54
|
+
* input shape. Use it with `new StateSchema({ ...CopilotKitStateSchema.fields })`.
|
|
55
|
+
*/
|
|
56
|
+
export const CopilotKitPropertiesSchema: StandardSerializableSchema<
|
|
57
|
+
typeof CopilotKitPropertiesAnnotation.State
|
|
58
|
+
> = {
|
|
59
|
+
"~standard": {
|
|
60
|
+
version: 1,
|
|
61
|
+
vendor: "@copilotkit/sdk-js",
|
|
62
|
+
validate: (value) => ({
|
|
63
|
+
value: value as typeof CopilotKitPropertiesAnnotation.State,
|
|
64
|
+
}),
|
|
65
|
+
jsonSchema: {
|
|
66
|
+
input: () => COPILOTKIT_PROPERTIES_JSON_SCHEMA,
|
|
67
|
+
output: () => COPILOTKIT_PROPERTIES_JSON_SCHEMA,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
15
72
|
export interface IntermediateStateConfig {
|
|
16
73
|
stateKey: string;
|
|
17
74
|
tool: string;
|