@copilotkit/runtime 0.0.0-0.0.0-max-changeset-10101010101010-20260109191632
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/.eslintrc.js +7 -0
- package/CHANGELOG.md +2905 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/__snapshots__/schema/schema.graphql +371 -0
- package/dist/index.d.ts +1495 -0
- package/dist/index.js +5644 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5601 -0
- package/dist/index.mjs.map +1 -0
- package/dist/langgraph.d.ts +284 -0
- package/dist/langgraph.js +211 -0
- package/dist/langgraph.js.map +1 -0
- package/dist/langgraph.mjs +206 -0
- package/dist/langgraph.mjs.map +1 -0
- package/dist/v2/index.d.ts +2 -0
- package/dist/v2/index.js +22 -0
- package/dist/v2/index.js.map +1 -0
- package/dist/v2/index.mjs +5 -0
- package/dist/v2/index.mjs.map +1 -0
- package/jest.config.js +10 -0
- package/package.json +143 -0
- package/scripts/generate-gql-schema.ts +13 -0
- package/src/agents/langgraph/event-source.ts +329 -0
- package/src/agents/langgraph/events.ts +377 -0
- package/src/graphql/inputs/action.input.ts +16 -0
- package/src/graphql/inputs/agent-session.input.ts +13 -0
- package/src/graphql/inputs/agent-state.input.ts +13 -0
- package/src/graphql/inputs/cloud-guardrails.input.ts +16 -0
- package/src/graphql/inputs/cloud.input.ts +8 -0
- package/src/graphql/inputs/context-property.input.ts +10 -0
- package/src/graphql/inputs/copilot-context.input.ts +10 -0
- package/src/graphql/inputs/custom-property.input.ts +15 -0
- package/src/graphql/inputs/extensions.input.ts +21 -0
- package/src/graphql/inputs/forwarded-parameters.input.ts +22 -0
- package/src/graphql/inputs/frontend.input.ts +14 -0
- package/src/graphql/inputs/generate-copilot-response.input.ts +59 -0
- package/src/graphql/inputs/load-agent-state.input.ts +10 -0
- package/src/graphql/inputs/message.input.ts +110 -0
- package/src/graphql/inputs/meta-event.input.ts +18 -0
- package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
- package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
- package/src/graphql/message-conversion/gql-to-agui.test.ts +1580 -0
- package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
- package/src/graphql/message-conversion/index.ts +2 -0
- package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
- package/src/graphql/resolvers/copilot.resolver.ts +708 -0
- package/src/graphql/resolvers/state.resolver.ts +27 -0
- package/src/graphql/types/agents-response.type.ts +19 -0
- package/src/graphql/types/base/index.ts +10 -0
- package/src/graphql/types/converted/index.ts +176 -0
- package/src/graphql/types/copilot-response.type.ts +138 -0
- package/src/graphql/types/enums.ts +38 -0
- package/src/graphql/types/extensions-response.type.ts +23 -0
- package/src/graphql/types/guardrails-result.type.ts +20 -0
- package/src/graphql/types/load-agent-state-response.type.ts +17 -0
- package/src/graphql/types/message-status.type.ts +42 -0
- package/src/graphql/types/meta-events.type.ts +71 -0
- package/src/graphql/types/response-status.type.ts +66 -0
- package/src/index.ts +4 -0
- package/src/langgraph.ts +1 -0
- package/src/lib/cloud/index.ts +4 -0
- package/src/lib/error-messages.ts +200 -0
- package/src/lib/index.ts +52 -0
- package/src/lib/integrations/index.ts +6 -0
- package/src/lib/integrations/nest/index.ts +14 -0
- package/src/lib/integrations/nextjs/app-router.ts +38 -0
- package/src/lib/integrations/nextjs/pages-router.ts +39 -0
- package/src/lib/integrations/node-express/index.ts +14 -0
- package/src/lib/integrations/node-http/index.ts +143 -0
- package/src/lib/integrations/node-http/request-handler.ts +111 -0
- package/src/lib/integrations/shared.ts +161 -0
- package/src/lib/logger.ts +28 -0
- package/src/lib/observability.ts +160 -0
- package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
- package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
- package/src/lib/runtime/agent-integrations/langgraph/agent.ts +209 -0
- package/src/lib/runtime/agent-integrations/langgraph/consts.ts +34 -0
- package/src/lib/runtime/agent-integrations/langgraph/index.ts +2 -0
- package/src/lib/runtime/copilot-runtime.ts +710 -0
- package/src/lib/runtime/mcp-tools-utils.ts +254 -0
- package/src/lib/runtime/retry-utils.ts +96 -0
- package/src/lib/runtime/telemetry-agent-runner.ts +139 -0
- package/src/lib/runtime/types.ts +49 -0
- package/src/lib/runtime/utils.ts +87 -0
- package/src/lib/streaming.ts +202 -0
- package/src/lib/telemetry-client.ts +64 -0
- package/src/service-adapters/anthropic/anthropic-adapter.ts +452 -0
- package/src/service-adapters/anthropic/utils.ts +152 -0
- package/src/service-adapters/bedrock/bedrock-adapter.ts +73 -0
- package/src/service-adapters/conversion.ts +67 -0
- package/src/service-adapters/empty/empty-adapter.ts +38 -0
- package/src/service-adapters/events.ts +294 -0
- package/src/service-adapters/experimental/ollama/ollama-adapter.ts +84 -0
- package/src/service-adapters/google/google-genai-adapter.test.ts +104 -0
- package/src/service-adapters/google/google-genai-adapter.ts +88 -0
- package/src/service-adapters/groq/groq-adapter.ts +203 -0
- package/src/service-adapters/index.ts +18 -0
- package/src/service-adapters/langchain/langchain-adapter.ts +111 -0
- package/src/service-adapters/langchain/langserve.ts +88 -0
- package/src/service-adapters/langchain/types.ts +14 -0
- package/src/service-adapters/langchain/utils.ts +313 -0
- package/src/service-adapters/openai/openai-adapter.ts +283 -0
- package/src/service-adapters/openai/openai-assistant-adapter.ts +344 -0
- package/src/service-adapters/openai/utils.ts +199 -0
- package/src/service-adapters/service-adapter.ts +41 -0
- package/src/service-adapters/shared/error-utils.ts +61 -0
- package/src/service-adapters/shared/index.ts +1 -0
- package/src/service-adapters/unify/unify-adapter.ts +151 -0
- package/src/utils/failed-response-status-reasons.ts +70 -0
- package/src/utils/index.ts +1 -0
- package/src/v2/index.ts +3 -0
- package/tests/global.d.ts +13 -0
- package/tests/service-adapters/anthropic/allowlist-approach.test.ts +226 -0
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +389 -0
- package/tests/service-adapters/openai/allowlist-approach.test.ts +238 -0
- package/tests/service-adapters/openai/openai-adapter.test.ts +301 -0
- package/tests/setup.jest.ts +21 -0
- package/tests/tsconfig.json +10 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +4 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractParametersFromSchema,
|
|
3
|
+
convertMCPToolsToActions,
|
|
4
|
+
generateMcpToolInstructions,
|
|
5
|
+
MCPTool,
|
|
6
|
+
} from "../mcp-tools-utils";
|
|
7
|
+
|
|
8
|
+
describe("MCP Tools Utils", () => {
|
|
9
|
+
describe("extractParametersFromSchema", () => {
|
|
10
|
+
it("should extract parameters from schema.parameters.properties", () => {
|
|
11
|
+
const tool: MCPTool = {
|
|
12
|
+
description: "Test tool",
|
|
13
|
+
schema: {
|
|
14
|
+
parameters: {
|
|
15
|
+
properties: {
|
|
16
|
+
name: { type: "string", description: "A name parameter" },
|
|
17
|
+
age: { type: "number", description: "An age parameter" },
|
|
18
|
+
},
|
|
19
|
+
required: ["name"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
execute: async () => ({}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const result = extractParametersFromSchema(tool);
|
|
26
|
+
expect(result).toHaveLength(2);
|
|
27
|
+
expect(result[0]).toEqual({
|
|
28
|
+
name: "name",
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "A name parameter",
|
|
31
|
+
required: true,
|
|
32
|
+
});
|
|
33
|
+
expect(result[1]).toEqual({
|
|
34
|
+
name: "age",
|
|
35
|
+
type: "number",
|
|
36
|
+
description: "An age parameter",
|
|
37
|
+
required: false,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should extract parameters from schema.parameters.jsonSchema", () => {
|
|
42
|
+
const tool: MCPTool = {
|
|
43
|
+
description: "Test tool with jsonSchema",
|
|
44
|
+
schema: {
|
|
45
|
+
parameters: {
|
|
46
|
+
jsonSchema: {
|
|
47
|
+
properties: {
|
|
48
|
+
title: { type: "string", description: "A title parameter" },
|
|
49
|
+
count: { type: "number", description: "A count parameter" },
|
|
50
|
+
},
|
|
51
|
+
required: ["title"],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
execute: async () => ({}),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const result = extractParametersFromSchema(tool);
|
|
59
|
+
expect(result).toHaveLength(2);
|
|
60
|
+
expect(result[0]).toEqual({
|
|
61
|
+
name: "title",
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "A title parameter",
|
|
64
|
+
required: true,
|
|
65
|
+
});
|
|
66
|
+
expect(result[1]).toEqual({
|
|
67
|
+
name: "count",
|
|
68
|
+
type: "number",
|
|
69
|
+
description: "A count parameter",
|
|
70
|
+
required: false,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should handle arrays with items", () => {
|
|
75
|
+
const tool: MCPTool = {
|
|
76
|
+
description: "Test tool with array parameters",
|
|
77
|
+
schema: {
|
|
78
|
+
parameters: {
|
|
79
|
+
properties: {
|
|
80
|
+
simpleArray: {
|
|
81
|
+
type: "array",
|
|
82
|
+
items: { type: "string" },
|
|
83
|
+
description: "Array of strings",
|
|
84
|
+
},
|
|
85
|
+
objectArray: {
|
|
86
|
+
type: "array",
|
|
87
|
+
items: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
name: { type: "string" },
|
|
91
|
+
value: { type: "number" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
description: "Array of objects",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["simpleArray"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
execute: async () => ({}),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = extractParametersFromSchema(tool);
|
|
104
|
+
expect(result).toHaveLength(2);
|
|
105
|
+
expect(result[0]).toEqual({
|
|
106
|
+
name: "simpleArray",
|
|
107
|
+
type: "array<string>",
|
|
108
|
+
description: "Array of strings",
|
|
109
|
+
required: true,
|
|
110
|
+
});
|
|
111
|
+
expect(result[1]).toEqual({
|
|
112
|
+
name: "objectArray",
|
|
113
|
+
type: "array",
|
|
114
|
+
description: "Array of objects Array of objects with properties: name, value",
|
|
115
|
+
required: false,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle enums", () => {
|
|
120
|
+
const tool: MCPTool = {
|
|
121
|
+
description: "Test tool with enum parameters",
|
|
122
|
+
schema: {
|
|
123
|
+
parameters: {
|
|
124
|
+
properties: {
|
|
125
|
+
status: {
|
|
126
|
+
type: "string",
|
|
127
|
+
enum: ["active", "inactive", "pending"],
|
|
128
|
+
description: "Status value",
|
|
129
|
+
},
|
|
130
|
+
priority: {
|
|
131
|
+
type: "number",
|
|
132
|
+
enum: [1, 2, 3],
|
|
133
|
+
description: "Priority level",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
required: ["status"],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
execute: async () => ({}),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = extractParametersFromSchema(tool);
|
|
143
|
+
expect(result).toHaveLength(2);
|
|
144
|
+
expect(result[0]).toEqual({
|
|
145
|
+
name: "status",
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "Status value Allowed values: active | inactive | pending",
|
|
148
|
+
required: true,
|
|
149
|
+
});
|
|
150
|
+
expect(result[1]).toEqual({
|
|
151
|
+
name: "priority",
|
|
152
|
+
type: "number",
|
|
153
|
+
description: "Priority level Allowed values: 1 | 2 | 3",
|
|
154
|
+
required: false,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should handle nested objects", () => {
|
|
159
|
+
const tool: MCPTool = {
|
|
160
|
+
description: "Test tool with nested object parameters",
|
|
161
|
+
schema: {
|
|
162
|
+
parameters: {
|
|
163
|
+
properties: {
|
|
164
|
+
user: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
name: { type: "string" },
|
|
168
|
+
email: { type: "string" },
|
|
169
|
+
preferences: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
theme: { type: "string" },
|
|
173
|
+
notifications: { type: "boolean" },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
description: "User object",
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
required: ["user"],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
execute: async () => ({}),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = extractParametersFromSchema(tool);
|
|
187
|
+
expect(result).toHaveLength(1);
|
|
188
|
+
expect(result[0]).toEqual({
|
|
189
|
+
name: "user",
|
|
190
|
+
type: "object",
|
|
191
|
+
description: "User object Object with properties: name, email, preferences",
|
|
192
|
+
required: true,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should return empty array when no properties", () => {
|
|
197
|
+
const tool: MCPTool = {
|
|
198
|
+
description: "Test tool without properties",
|
|
199
|
+
schema: {
|
|
200
|
+
parameters: {},
|
|
201
|
+
},
|
|
202
|
+
execute: async () => ({}),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const result = extractParametersFromSchema(tool);
|
|
206
|
+
expect(result).toHaveLength(0);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("generateMcpToolInstructions", () => {
|
|
211
|
+
it("should generate instructions with correct parameter schema from schema.parameters.properties", () => {
|
|
212
|
+
const toolsMap: Record<string, MCPTool> = {
|
|
213
|
+
testTool: {
|
|
214
|
+
description: "A test tool",
|
|
215
|
+
schema: {
|
|
216
|
+
parameters: {
|
|
217
|
+
properties: {
|
|
218
|
+
name: { type: "string", description: "The name parameter" },
|
|
219
|
+
age: { type: "number", description: "The age parameter" },
|
|
220
|
+
},
|
|
221
|
+
required: ["name"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
execute: async () => ({}),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const result = generateMcpToolInstructions(toolsMap);
|
|
229
|
+
expect(result).toContain("testTool: A test tool");
|
|
230
|
+
expect(result).toContain("- name* (string) - The name parameter");
|
|
231
|
+
expect(result).toContain("- age (number) - The age parameter");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should generate instructions with correct parameter schema from schema.parameters.jsonSchema", () => {
|
|
235
|
+
const toolsMap: Record<string, MCPTool> = {
|
|
236
|
+
testTool: {
|
|
237
|
+
description: "A test tool with jsonSchema",
|
|
238
|
+
schema: {
|
|
239
|
+
parameters: {
|
|
240
|
+
jsonSchema: {
|
|
241
|
+
properties: {
|
|
242
|
+
title: { type: "string", description: "The title parameter" },
|
|
243
|
+
count: { type: "number", description: "The count parameter" },
|
|
244
|
+
},
|
|
245
|
+
required: ["title"],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
execute: async () => ({}),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const result = generateMcpToolInstructions(toolsMap);
|
|
254
|
+
expect(result).toContain("testTool: A test tool with jsonSchema");
|
|
255
|
+
expect(result).toContain("- title* (string) - The title parameter");
|
|
256
|
+
expect(result).toContain("- count (number) - The count parameter");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should handle complex schemas with arrays and enums", () => {
|
|
260
|
+
const toolsMap: Record<string, MCPTool> = {
|
|
261
|
+
complexTool: {
|
|
262
|
+
description: "A complex tool",
|
|
263
|
+
schema: {
|
|
264
|
+
parameters: {
|
|
265
|
+
properties: {
|
|
266
|
+
items: {
|
|
267
|
+
type: "array",
|
|
268
|
+
items: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
name: { type: "string" },
|
|
272
|
+
value: { type: "number" },
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
description: "Array of items",
|
|
276
|
+
},
|
|
277
|
+
status: {
|
|
278
|
+
type: "string",
|
|
279
|
+
enum: ["active", "inactive"],
|
|
280
|
+
description: "Status",
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
required: ["items"],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
execute: async () => ({}),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const result = generateMcpToolInstructions(toolsMap);
|
|
291
|
+
expect(result).toContain("complexTool: A complex tool");
|
|
292
|
+
expect(result).toContain(
|
|
293
|
+
"- items* (array<object>) - Array of items Array of objects with properties: name, value",
|
|
294
|
+
);
|
|
295
|
+
expect(result).toContain("- status (string) - Status Allowed values: active | inactive");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should fallback to schema.properties for backward compatibility", () => {
|
|
299
|
+
const toolsMap: Record<string, MCPTool> = {
|
|
300
|
+
backwardCompatTool: {
|
|
301
|
+
description: "A backward compatible tool",
|
|
302
|
+
schema: {
|
|
303
|
+
// Direct properties without nested parameters
|
|
304
|
+
properties: {
|
|
305
|
+
name: { type: "string", description: "The name parameter" },
|
|
306
|
+
},
|
|
307
|
+
required: ["name"],
|
|
308
|
+
} as any,
|
|
309
|
+
execute: async () => ({}),
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const result = generateMcpToolInstructions(toolsMap);
|
|
314
|
+
expect(result).toContain("backwardCompatTool: A backward compatible tool");
|
|
315
|
+
expect(result).toContain("- name* (string) - The name parameter");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should show 'No parameters required' when no schema properties", () => {
|
|
319
|
+
const toolsMap: Record<string, MCPTool> = {
|
|
320
|
+
noParamsTool: {
|
|
321
|
+
description: "A tool with no parameters",
|
|
322
|
+
schema: {
|
|
323
|
+
parameters: {},
|
|
324
|
+
},
|
|
325
|
+
execute: async () => ({}),
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const result = generateMcpToolInstructions(toolsMap);
|
|
330
|
+
expect(result).toContain("noParamsTool: A tool with no parameters");
|
|
331
|
+
expect(result).toContain("No parameters required");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should handle tools with no schema", () => {
|
|
335
|
+
const toolsMap: Record<string, MCPTool> = {
|
|
336
|
+
noSchemaTool: {
|
|
337
|
+
description: "A tool with no schema",
|
|
338
|
+
execute: async () => ({}),
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const result = generateMcpToolInstructions(toolsMap);
|
|
343
|
+
expect(result).toContain("noSchemaTool: A tool with no schema");
|
|
344
|
+
expect(result).toContain("No parameters required");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("should return empty string for empty tools map", () => {
|
|
348
|
+
const result = generateMcpToolInstructions({});
|
|
349
|
+
expect(result).toBe("");
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe("convertMCPToolsToActions", () => {
|
|
354
|
+
it("should convert MCP tools to CopilotKit actions", () => {
|
|
355
|
+
const mcpTools: Record<string, MCPTool> = {
|
|
356
|
+
testTool: {
|
|
357
|
+
description: "A test tool",
|
|
358
|
+
schema: {
|
|
359
|
+
parameters: {
|
|
360
|
+
properties: {
|
|
361
|
+
name: { type: "string", description: "The name parameter" },
|
|
362
|
+
age: { type: "number", description: "The age parameter" },
|
|
363
|
+
},
|
|
364
|
+
required: ["name"],
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
execute: async () => "test result",
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const result = convertMCPToolsToActions(mcpTools, "http://test-endpoint");
|
|
372
|
+
expect(result).toHaveLength(1);
|
|
373
|
+
expect(result[0].name).toBe("testTool");
|
|
374
|
+
expect(result[0].description).toBe("A test tool");
|
|
375
|
+
expect(result[0].parameters).toHaveLength(2);
|
|
376
|
+
expect(result[0].parameters[0]).toEqual({
|
|
377
|
+
name: "name",
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "The name parameter",
|
|
380
|
+
required: true,
|
|
381
|
+
});
|
|
382
|
+
expect(result[0].parameters[1]).toEqual({
|
|
383
|
+
name: "age",
|
|
384
|
+
type: "number",
|
|
385
|
+
description: "The age parameter",
|
|
386
|
+
required: false,
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("should handle tool execution correctly", async () => {
|
|
391
|
+
const mockExecute = jest.fn().mockResolvedValue("mock result");
|
|
392
|
+
const mcpTools: Record<string, MCPTool> = {
|
|
393
|
+
testTool: {
|
|
394
|
+
description: "A test tool",
|
|
395
|
+
schema: {
|
|
396
|
+
parameters: {
|
|
397
|
+
properties: {
|
|
398
|
+
name: { type: "string", description: "The name parameter" },
|
|
399
|
+
},
|
|
400
|
+
required: ["name"],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
execute: mockExecute,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const result = convertMCPToolsToActions(mcpTools, "http://test-endpoint");
|
|
408
|
+
const action = result[0];
|
|
409
|
+
|
|
410
|
+
const executeResult = await action.handler({ name: "test" });
|
|
411
|
+
expect(executeResult).toBe("mock result");
|
|
412
|
+
expect(mockExecute).toHaveBeenCalledWith({ name: "test" });
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("should stringify non-string results", async () => {
|
|
416
|
+
const mcpTools: Record<string, MCPTool> = {
|
|
417
|
+
testTool: {
|
|
418
|
+
description: "A test tool",
|
|
419
|
+
schema: {
|
|
420
|
+
parameters: {
|
|
421
|
+
properties: {
|
|
422
|
+
name: { type: "string", description: "The name parameter" },
|
|
423
|
+
},
|
|
424
|
+
required: ["name"],
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
execute: async () => ({ result: "complex object" }),
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const result = convertMCPToolsToActions(mcpTools, "http://test-endpoint");
|
|
432
|
+
const action = result[0];
|
|
433
|
+
|
|
434
|
+
const executeResult = await action.handler({ name: "test" });
|
|
435
|
+
expect(executeResult).toBe('{"result":"complex object"}');
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("should handle execution errors", async () => {
|
|
439
|
+
const mcpTools: Record<string, MCPTool> = {
|
|
440
|
+
testTool: {
|
|
441
|
+
description: "A test tool",
|
|
442
|
+
schema: {
|
|
443
|
+
parameters: {
|
|
444
|
+
properties: {
|
|
445
|
+
name: { type: "string", description: "The name parameter" },
|
|
446
|
+
},
|
|
447
|
+
required: ["name"],
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
execute: async () => {
|
|
451
|
+
throw new Error("Test error");
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const result = convertMCPToolsToActions(mcpTools, "http://test-endpoint");
|
|
457
|
+
const action = result[0];
|
|
458
|
+
|
|
459
|
+
await expect(action.handler({ name: "test" })).rejects.toThrow(
|
|
460
|
+
"Execution failed for MCP tool 'testTool': Test error",
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { map } from "rxjs";
|
|
2
|
+
import { LangGraphEventTypes } from "../../../../agents/langgraph/events";
|
|
3
|
+
import { RawEvent } from "@ag-ui/core";
|
|
4
|
+
import {
|
|
5
|
+
LangGraphAgent as AGUILangGraphAgent,
|
|
6
|
+
LangGraphHttpAgent,
|
|
7
|
+
type LangGraphAgentConfig,
|
|
8
|
+
ProcessedEvents,
|
|
9
|
+
SchemaKeys,
|
|
10
|
+
type State,
|
|
11
|
+
StateEnrichment,
|
|
12
|
+
} from "@ag-ui/langgraph";
|
|
13
|
+
import { Message as LangGraphMessage } from "@langchain/langgraph-sdk/dist/types.messages";
|
|
14
|
+
import { ThreadState } from "@langchain/langgraph-sdk";
|
|
15
|
+
|
|
16
|
+
interface CopilotKitStateEnrichment {
|
|
17
|
+
copilotkit: {
|
|
18
|
+
actions: StateEnrichment["ag-ui"]["tools"];
|
|
19
|
+
context: StateEnrichment["ag-ui"]["context"];
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
import { RunAgentInput, EventType, CustomEvent } from "@ag-ui/client";
|
|
24
|
+
|
|
25
|
+
// Import and re-export from separate file to maintain API compatibility
|
|
26
|
+
import { CustomEventNames, TextMessageEvents, ToolCallEvents, PredictStateTool } from "./consts";
|
|
27
|
+
export { CustomEventNames };
|
|
28
|
+
|
|
29
|
+
export class LangGraphAgent extends AGUILangGraphAgent {
|
|
30
|
+
constructor(config: LangGraphAgentConfig) {
|
|
31
|
+
super(config);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
public clone() {
|
|
36
|
+
return new LangGraphAgent(this.config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dispatchEvent(event: ProcessedEvents) {
|
|
40
|
+
if (event.type === EventType.CUSTOM) {
|
|
41
|
+
// const event = processedEvent as unknown as CustomEvent;
|
|
42
|
+
const customEvent = event as unknown as CustomEvent;
|
|
43
|
+
|
|
44
|
+
if (customEvent.name === CustomEventNames.CopilotKitManuallyEmitMessage) {
|
|
45
|
+
this.subscriber.next({
|
|
46
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
47
|
+
role: "assistant",
|
|
48
|
+
messageId: customEvent.value.message_id,
|
|
49
|
+
rawEvent: event,
|
|
50
|
+
});
|
|
51
|
+
this.subscriber.next({
|
|
52
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
53
|
+
messageId: customEvent.value.message_id,
|
|
54
|
+
delta: customEvent.value.message,
|
|
55
|
+
rawEvent: event,
|
|
56
|
+
});
|
|
57
|
+
this.subscriber.next({
|
|
58
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
59
|
+
messageId: customEvent.value.message_id,
|
|
60
|
+
rawEvent: event,
|
|
61
|
+
});
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (customEvent.name === CustomEventNames.CopilotKitManuallyEmitToolCall) {
|
|
66
|
+
this.subscriber.next({
|
|
67
|
+
type: EventType.TOOL_CALL_START,
|
|
68
|
+
toolCallId: customEvent.value.id,
|
|
69
|
+
toolCallName: customEvent.value.name,
|
|
70
|
+
parentMessageId: customEvent.value.id,
|
|
71
|
+
rawEvent: event,
|
|
72
|
+
});
|
|
73
|
+
this.subscriber.next({
|
|
74
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
75
|
+
toolCallId: customEvent.value.id,
|
|
76
|
+
delta: customEvent.value.args,
|
|
77
|
+
rawEvent: event,
|
|
78
|
+
});
|
|
79
|
+
this.subscriber.next({
|
|
80
|
+
type: EventType.TOOL_CALL_END,
|
|
81
|
+
toolCallId: customEvent.value.id,
|
|
82
|
+
rawEvent: event,
|
|
83
|
+
});
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (customEvent.name === CustomEventNames.CopilotKitManuallyEmitIntermediateState) {
|
|
88
|
+
this.activeRun.manuallyEmittedState = customEvent.value;
|
|
89
|
+
this.dispatchEvent({
|
|
90
|
+
type: EventType.STATE_SNAPSHOT,
|
|
91
|
+
snapshot: this.getStateSnapshot({
|
|
92
|
+
values: this.activeRun.manuallyEmittedState,
|
|
93
|
+
} as ThreadState<State>),
|
|
94
|
+
rawEvent: event,
|
|
95
|
+
});
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (customEvent.name === CustomEventNames.CopilotKitExit) {
|
|
100
|
+
this.subscriber.next({
|
|
101
|
+
type: EventType.CUSTOM,
|
|
102
|
+
name: "Exit",
|
|
103
|
+
value: true,
|
|
104
|
+
});
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Intercept all text message and tool call events and check if should disable
|
|
110
|
+
const rawEvent = (event as ToolCallEvents | TextMessageEvents).rawEvent;
|
|
111
|
+
if (!rawEvent) {
|
|
112
|
+
this.subscriber.next(event);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const isMessageEvent =
|
|
117
|
+
event.type === EventType.TEXT_MESSAGE_START ||
|
|
118
|
+
event.type === EventType.TEXT_MESSAGE_CONTENT ||
|
|
119
|
+
event.type === EventType.TEXT_MESSAGE_END;
|
|
120
|
+
const isToolEvent =
|
|
121
|
+
event.type === EventType.TOOL_CALL_START ||
|
|
122
|
+
event.type === EventType.TOOL_CALL_ARGS ||
|
|
123
|
+
event.type === EventType.TOOL_CALL_END;
|
|
124
|
+
if ("copilotkit:emit-tool-calls" in (rawEvent.metadata || {})) {
|
|
125
|
+
if (rawEvent.metadata["copilotkit:emit-tool-calls"] === false && isToolEvent) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if ("copilotkit:emit-messages" in (rawEvent.metadata || {})) {
|
|
130
|
+
if (rawEvent.metadata["copilotkit:emit-messages"] === false && isMessageEvent) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.subscriber.next(event);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// @ts-ignore
|
|
140
|
+
run(input: RunAgentInput) {
|
|
141
|
+
return super.run(input).pipe(
|
|
142
|
+
map((processedEvent) => {
|
|
143
|
+
// Turn raw event into emit state snapshot from tool call event
|
|
144
|
+
if (processedEvent.type === EventType.RAW) {
|
|
145
|
+
// Get the LangGraph event from the AGUI event.
|
|
146
|
+
const event = (processedEvent as RawEvent).event ?? (processedEvent as RawEvent).rawEvent;
|
|
147
|
+
|
|
148
|
+
const eventType = event.event;
|
|
149
|
+
const toolCallData = event.data?.chunk?.tool_call_chunks?.[0];
|
|
150
|
+
const toolCallUsedToPredictState = event.metadata?.[
|
|
151
|
+
"copilotkit:emit-intermediate-state"
|
|
152
|
+
]?.some(
|
|
153
|
+
(predictStateTool: PredictStateTool) => predictStateTool.tool === toolCallData?.name,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (eventType === LangGraphEventTypes.OnChatModelStream && toolCallUsedToPredictState) {
|
|
157
|
+
return {
|
|
158
|
+
type: EventType.CUSTOM,
|
|
159
|
+
name: "PredictState",
|
|
160
|
+
value: event.metadata["copilotkit:emit-intermediate-state"],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return processedEvent;
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
langGraphDefaultMergeState(
|
|
171
|
+
state: State,
|
|
172
|
+
messages: LangGraphMessage[],
|
|
173
|
+
input: RunAgentInput,
|
|
174
|
+
): State<StateEnrichment & CopilotKitStateEnrichment> {
|
|
175
|
+
const aguiMergedState = super.langGraphDefaultMergeState(state, messages, input);
|
|
176
|
+
const { tools: returnedTools, "ag-ui": agui } = aguiMergedState;
|
|
177
|
+
// tolerate undefined and de-duplicate by stable key (id | name | key)
|
|
178
|
+
const rawCombinedTools = [
|
|
179
|
+
...((returnedTools as any[]) ?? []),
|
|
180
|
+
...((agui?.tools as any[]) ?? []),
|
|
181
|
+
];
|
|
182
|
+
const combinedTools = Array.from(
|
|
183
|
+
new Map(
|
|
184
|
+
rawCombinedTools.map((t: any) => [t?.id ?? t?.name ?? t?.key ?? JSON.stringify(t), t]),
|
|
185
|
+
).values(),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
...aguiMergedState,
|
|
190
|
+
copilotkit: {
|
|
191
|
+
actions: combinedTools,
|
|
192
|
+
context: agui?.context ?? [],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async getSchemaKeys(): Promise<SchemaKeys> {
|
|
198
|
+
const CONSTANT_KEYS = ["copilotkit"];
|
|
199
|
+
const schemaKeys = await super.getSchemaKeys();
|
|
200
|
+
return {
|
|
201
|
+
config: schemaKeys.config,
|
|
202
|
+
input: schemaKeys.input ? [...schemaKeys.input, ...CONSTANT_KEYS] : null,
|
|
203
|
+
output: schemaKeys.output ? [...schemaKeys.output, ...CONSTANT_KEYS] : null,
|
|
204
|
+
context: schemaKeys.context ? [...schemaKeys.context, ...CONSTANT_KEYS] : null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { LangGraphHttpAgent };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for LangGraph integration.
|
|
3
|
+
* This file is separate from langgraph.agent.ts to avoid pulling in @ag-ui/langgraph
|
|
4
|
+
* when only these constants are needed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
TextMessageStartEvent,
|
|
9
|
+
TextMessageContentEvent,
|
|
10
|
+
TextMessageEndEvent,
|
|
11
|
+
ToolCallStartEvent,
|
|
12
|
+
ToolCallArgsEvent,
|
|
13
|
+
ToolCallEndEvent,
|
|
14
|
+
} from "@ag-ui/client";
|
|
15
|
+
|
|
16
|
+
export type TextMessageEvents =
|
|
17
|
+
| TextMessageStartEvent
|
|
18
|
+
| TextMessageContentEvent
|
|
19
|
+
| TextMessageEndEvent;
|
|
20
|
+
|
|
21
|
+
export type ToolCallEvents = ToolCallStartEvent | ToolCallArgsEvent | ToolCallEndEvent;
|
|
22
|
+
|
|
23
|
+
export enum CustomEventNames {
|
|
24
|
+
CopilotKitManuallyEmitMessage = "copilotkit_manually_emit_message",
|
|
25
|
+
CopilotKitManuallyEmitToolCall = "copilotkit_manually_emit_tool_call",
|
|
26
|
+
CopilotKitManuallyEmitIntermediateState = "copilotkit_manually_emit_intermediate_state",
|
|
27
|
+
CopilotKitExit = "copilotkit_exit",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PredictStateTool {
|
|
31
|
+
tool: string;
|
|
32
|
+
state_key: string;
|
|
33
|
+
tool_argument: string;
|
|
34
|
+
}
|