@copilotkit/runtime 1.55.3 → 1.56.1
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/agent/converters/tanstack.cjs.map +1 -1
- package/dist/agent/converters/tanstack.d.cts +6 -19
- package/dist/agent/converters/tanstack.d.cts.map +1 -1
- package/dist/agent/converters/tanstack.d.mts +6 -19
- package/dist/agent/converters/tanstack.d.mts.map +1 -1
- package/dist/agent/converters/tanstack.mjs.map +1 -1
- package/dist/agent/index.cjs +16 -2
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +12 -1
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts +12 -1
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +16 -2
- package/dist/agent/index.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +1 -1
- package/dist/lib/index.cjs +1 -1
- package/dist/lib/index.d.cts +2 -1
- package/dist/lib/index.d.cts.map +1 -1
- package/dist/lib/index.d.mts +2 -1
- package/dist/lib/index.d.mts.map +1 -1
- package/dist/lib/index.mjs +1 -1
- package/dist/lib/integrations/node-http/index.cjs +4 -1
- package/dist/lib/integrations/node-http/index.cjs.map +1 -1
- package/dist/lib/integrations/node-http/index.d.cts.map +1 -1
- package/dist/lib/integrations/node-http/index.d.mts.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +4 -1
- package/dist/lib/integrations/node-http/index.mjs.map +1 -1
- package/dist/lib/integrations/shared.cjs +1 -1
- package/dist/lib/integrations/shared.d.cts +1 -1
- package/dist/lib/integrations/shared.d.mts +1 -1
- package/dist/lib/integrations/shared.mjs +1 -1
- package/dist/lib/runtime/copilot-runtime.cjs +25 -5
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts +15 -3
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts +15 -3
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +25 -5
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.cjs +21 -4
- package/dist/lib/runtime/mcp-tools-utils.cjs.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.d.cts.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.d.mts.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.mjs +21 -4
- package/dist/lib/runtime/mcp-tools-utils.mjs.map +1 -1
- package/dist/package.cjs +6 -5
- package/dist/package.mjs +6 -5
- package/dist/service-adapters/anthropic/anthropic-adapter.cjs +11 -3
- package/dist/service-adapters/anthropic/anthropic-adapter.cjs.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.d.cts +6 -0
- package/dist/service-adapters/anthropic/anthropic-adapter.d.cts.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.d.mts +6 -0
- package/dist/service-adapters/anthropic/anthropic-adapter.d.mts.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.mjs +11 -3
- package/dist/service-adapters/anthropic/anthropic-adapter.mjs.map +1 -1
- package/dist/service-adapters/anthropic/utils.cjs +27 -1
- package/dist/service-adapters/anthropic/utils.cjs.map +1 -1
- package/dist/service-adapters/anthropic/utils.mjs +27 -1
- package/dist/service-adapters/anthropic/utils.mjs.map +1 -1
- package/dist/service-adapters/langchain/utils.cjs +1 -1
- package/dist/service-adapters/langchain/utils.cjs.map +1 -1
- package/dist/service-adapters/langchain/utils.mjs +1 -1
- package/dist/service-adapters/langchain/utils.mjs.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.cjs +3 -2
- package/dist/service-adapters/openai/openai-adapter.cjs.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.d.cts +6 -0
- package/dist/service-adapters/openai/openai-adapter.d.cts.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.d.mts +6 -0
- package/dist/service-adapters/openai/openai-adapter.d.mts.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.mjs +4 -3
- package/dist/service-adapters/openai/openai-adapter.mjs.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.cjs +8 -9
- package/dist/service-adapters/openai/openai-assistant-adapter.cjs.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.d.cts.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.d.mts.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.mjs +9 -10
- package/dist/service-adapters/openai/openai-assistant-adapter.mjs.map +1 -1
- package/dist/service-adapters/openai/utils.cjs +53 -0
- package/dist/service-adapters/openai/utils.cjs.map +1 -1
- package/dist/service-adapters/openai/utils.mjs +51 -1
- package/dist/service-adapters/openai/utils.mjs.map +1 -1
- package/dist/v2/index.cjs +1 -0
- package/dist/v2/index.d.cts +3 -3
- package/dist/v2/index.d.mts +3 -3
- package/dist/v2/index.mjs +2 -2
- package/dist/v2/runtime/core/middleware-sse-parser.cjs +5 -2
- package/dist/v2/runtime/core/middleware-sse-parser.cjs.map +1 -1
- package/dist/v2/runtime/core/middleware-sse-parser.mjs +5 -2
- package/dist/v2/runtime/core/middleware-sse-parser.mjs.map +1 -1
- package/dist/v2/runtime/core/runtime.cjs +25 -0
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts +53 -4
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts +53 -4
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs +26 -2
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.cjs +18 -10
- package/dist/v2/runtime/handlers/get-runtime-info.cjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.mjs +19 -11
- package/dist/v2/runtime/handlers/get-runtime-info.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.cjs +8 -2
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +8 -2
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-stop.cjs +2 -1
- package/dist/v2/runtime/handlers/handle-stop.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-stop.mjs +2 -1
- package/dist/v2/runtime/handlers/handle-stop.mjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.cjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.mjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs +3 -2
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs +3 -2
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs +40 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs +40 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.cjs +3 -1
- package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.mjs +3 -1
- package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
- package/dist/v2/runtime/index.d.cts +1 -1
- package/dist/v2/runtime/index.d.mts +1 -1
- package/package.json +7 -6
- package/src/agent/__tests__/capabilities.test.ts +81 -0
- package/src/agent/__tests__/provider-id-collision.test.ts +195 -0
- package/src/agent/converters/tanstack.ts +15 -7
- package/src/agent/index.ts +52 -11
- package/src/lib/integrations/node-http/__tests__/request-duck-type.test.ts +66 -0
- package/src/lib/integrations/node-http/index.ts +15 -1
- package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +30 -1
- package/src/lib/runtime/__tests__/on-after-request.test.ts +122 -0
- package/src/lib/runtime/__tests__/v1-agent-factory.test.ts +109 -0
- package/src/lib/runtime/copilot-runtime.ts +54 -5
- package/src/lib/runtime/mcp-tools-utils.ts +41 -6
- package/src/service-adapters/anthropic/anthropic-adapter.ts +22 -2
- package/src/service-adapters/anthropic/utils.ts +60 -1
- package/src/service-adapters/langchain/utils.ts +1 -1
- package/src/service-adapters/openai/__tests__/openai-v5-compat.test.ts +177 -0
- package/src/service-adapters/openai/openai-adapter.ts +17 -2
- package/src/service-adapters/openai/openai-assistant-adapter.ts +7 -9
- package/src/service-adapters/openai/utils.ts +100 -0
- package/src/v2/runtime/__tests__/agents-factory.test.ts +136 -0
- package/src/v2/runtime/__tests__/debug-sse-response.test.ts +302 -0
- package/src/v2/runtime/__tests__/get-runtime-info.test.ts +134 -1
- package/src/v2/runtime/__tests__/middleware-sse-parser.test.ts +50 -0
- package/src/v2/runtime/core/middleware-sse-parser.ts +12 -2
- package/src/v2/runtime/core/runtime.ts +90 -2
- package/src/v2/runtime/handlers/get-runtime-info.ts +33 -8
- package/src/v2/runtime/handlers/handle-connect.ts +1 -1
- package/src/v2/runtime/handlers/handle-run.ts +16 -2
- package/src/v2/runtime/handlers/handle-stop.ts +2 -1
- package/src/v2/runtime/handlers/intelligence/thread-names.ts +1 -1
- package/src/v2/runtime/handlers/shared/agent-utils.ts +3 -2
- package/src/v2/runtime/handlers/shared/sse-response.ts +69 -0
- package/src/v2/runtime/handlers/sse/run.ts +9 -0
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +268 -0
- package/tests/service-adapters/anthropic/utils-token-trimming.test.ts +301 -0
|
@@ -110,10 +110,14 @@ describe("MCP Tools Utils", () => {
|
|
|
110
110
|
});
|
|
111
111
|
expect(result[1]).toEqual({
|
|
112
112
|
name: "objectArray",
|
|
113
|
-
type: "
|
|
113
|
+
type: "object[]",
|
|
114
114
|
description:
|
|
115
115
|
"Array of objects Array of objects with properties: name, value",
|
|
116
116
|
required: false,
|
|
117
|
+
attributes: [
|
|
118
|
+
{ name: "name", type: "string", description: "", required: false },
|
|
119
|
+
{ name: "value", type: "number", description: "", required: false },
|
|
120
|
+
],
|
|
117
121
|
});
|
|
118
122
|
});
|
|
119
123
|
|
|
@@ -147,6 +151,7 @@ describe("MCP Tools Utils", () => {
|
|
|
147
151
|
type: "string",
|
|
148
152
|
description: "Status value Allowed values: active | inactive | pending",
|
|
149
153
|
required: true,
|
|
154
|
+
enum: ["active", "inactive", "pending"],
|
|
150
155
|
});
|
|
151
156
|
expect(result[1]).toEqual({
|
|
152
157
|
name: "priority",
|
|
@@ -192,6 +197,30 @@ describe("MCP Tools Utils", () => {
|
|
|
192
197
|
description:
|
|
193
198
|
"User object Object with properties: name, email, preferences",
|
|
194
199
|
required: true,
|
|
200
|
+
attributes: [
|
|
201
|
+
{ name: "name", type: "string", description: "", required: false },
|
|
202
|
+
{ name: "email", type: "string", description: "", required: false },
|
|
203
|
+
{
|
|
204
|
+
name: "preferences",
|
|
205
|
+
type: "object",
|
|
206
|
+
description: "Object with properties: theme, notifications",
|
|
207
|
+
required: false,
|
|
208
|
+
attributes: [
|
|
209
|
+
{
|
|
210
|
+
name: "theme",
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "",
|
|
213
|
+
required: false,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "notifications",
|
|
217
|
+
type: "boolean",
|
|
218
|
+
description: "",
|
|
219
|
+
required: false,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
],
|
|
195
224
|
});
|
|
196
225
|
});
|
|
197
226
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { CopilotRuntime } from "../copilot-runtime";
|
|
3
|
+
|
|
4
|
+
describe("onAfterRequest middleware (#2124)", () => {
|
|
5
|
+
it("should pass hookParams to onAfterRequest, not an empty object", async () => {
|
|
6
|
+
const onAfterRequest = vi.fn();
|
|
7
|
+
|
|
8
|
+
const runtime = new CopilotRuntime({
|
|
9
|
+
middleware: {
|
|
10
|
+
onAfterRequest,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Access the internal afterRequestMiddleware function
|
|
15
|
+
const afterRequestMw = runtime.instance.afterRequestMiddleware;
|
|
16
|
+
expect(afterRequestMw).toBeDefined();
|
|
17
|
+
|
|
18
|
+
// Simulate calling the middleware with hookParams (as the v2 runtime would)
|
|
19
|
+
const fakeHookParams = {
|
|
20
|
+
runtime: {} as any,
|
|
21
|
+
response: new Response("test"),
|
|
22
|
+
path: "/api/copilotkit",
|
|
23
|
+
messages: [
|
|
24
|
+
{ id: "msg-1", role: "user", content: "Hi there" },
|
|
25
|
+
{ id: "msg-2", role: "assistant", content: "Hello" },
|
|
26
|
+
{ id: "msg-3", role: "tool", content: "result", toolCallId: "tc-1" },
|
|
27
|
+
],
|
|
28
|
+
threadId: "thread-123",
|
|
29
|
+
runId: "run-456",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
await (afterRequestMw as Function)(fakeHookParams);
|
|
33
|
+
|
|
34
|
+
expect(onAfterRequest).toHaveBeenCalledTimes(1);
|
|
35
|
+
|
|
36
|
+
const callArg = onAfterRequest.mock.calls[0][0];
|
|
37
|
+
|
|
38
|
+
// Should NOT be called with an empty object
|
|
39
|
+
expect(callArg).not.toEqual({});
|
|
40
|
+
|
|
41
|
+
// Verify all OnAfterRequestOptions fields are present
|
|
42
|
+
expect(callArg).toHaveProperty("threadId", "thread-123");
|
|
43
|
+
expect(callArg).toHaveProperty("runId", "run-456");
|
|
44
|
+
expect(callArg).toHaveProperty("url", "/api/copilotkit");
|
|
45
|
+
expect(callArg).toHaveProperty("properties");
|
|
46
|
+
expect(callArg.properties).toEqual({});
|
|
47
|
+
|
|
48
|
+
// Verify message splitting: user messages → inputMessages, others → outputMessages
|
|
49
|
+
expect(callArg.inputMessages).toHaveLength(1);
|
|
50
|
+
expect(callArg.inputMessages[0]).toMatchObject({
|
|
51
|
+
id: "msg-1",
|
|
52
|
+
role: "user",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(callArg.outputMessages).toHaveLength(2);
|
|
56
|
+
expect(callArg.outputMessages[0]).toMatchObject({
|
|
57
|
+
id: "msg-2",
|
|
58
|
+
role: "assistant",
|
|
59
|
+
});
|
|
60
|
+
expect(callArg.outputMessages[1]).toMatchObject({
|
|
61
|
+
id: "msg-3",
|
|
62
|
+
role: "tool",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should handle undefined messages gracefully", async () => {
|
|
67
|
+
const onAfterRequest = vi.fn();
|
|
68
|
+
|
|
69
|
+
const runtime = new CopilotRuntime({
|
|
70
|
+
middleware: {
|
|
71
|
+
onAfterRequest,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const afterRequestMw = runtime.instance.afterRequestMiddleware;
|
|
76
|
+
|
|
77
|
+
const fakeHookParams = {
|
|
78
|
+
runtime: {} as any,
|
|
79
|
+
response: new Response("test"),
|
|
80
|
+
path: "/api/copilotkit",
|
|
81
|
+
// messages intentionally omitted (undefined)
|
|
82
|
+
threadId: "thread-789",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await (afterRequestMw as Function)(fakeHookParams);
|
|
86
|
+
|
|
87
|
+
expect(onAfterRequest).toHaveBeenCalledTimes(1);
|
|
88
|
+
|
|
89
|
+
const callArg = onAfterRequest.mock.calls[0][0];
|
|
90
|
+
expect(callArg.threadId).toBe("thread-789");
|
|
91
|
+
expect(callArg.inputMessages).toEqual([]);
|
|
92
|
+
expect(callArg.outputMessages).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should default threadId to empty string when undefined", async () => {
|
|
96
|
+
const onAfterRequest = vi.fn();
|
|
97
|
+
|
|
98
|
+
const runtime = new CopilotRuntime({
|
|
99
|
+
middleware: {
|
|
100
|
+
onAfterRequest,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const afterRequestMw = runtime.instance.afterRequestMiddleware;
|
|
105
|
+
|
|
106
|
+
const fakeHookParams = {
|
|
107
|
+
runtime: {} as any,
|
|
108
|
+
response: new Response("test"),
|
|
109
|
+
path: "/api/copilotkit",
|
|
110
|
+
messages: [],
|
|
111
|
+
// threadId intentionally omitted
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
await (afterRequestMw as Function)(fakeHookParams);
|
|
115
|
+
|
|
116
|
+
expect(onAfterRequest).toHaveBeenCalledTimes(1);
|
|
117
|
+
|
|
118
|
+
const callArg = onAfterRequest.mock.calls[0][0];
|
|
119
|
+
expect(callArg.threadId).toBe("");
|
|
120
|
+
expect(callArg.runId).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { HttpAgent } from "@ag-ui/client";
|
|
3
|
+
import { CopilotRuntime } from "../copilot-runtime";
|
|
4
|
+
import {
|
|
5
|
+
resolveAgents,
|
|
6
|
+
type AgentsConfig,
|
|
7
|
+
} from "../../../v2/runtime/core/runtime";
|
|
8
|
+
|
|
9
|
+
function createMockAgent(name = "test") {
|
|
10
|
+
return new HttpAgent({ url: `https://example.com/${name}` });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createMockRequest(headers?: Record<string, string>) {
|
|
14
|
+
return new Request("https://example.com/agent/test/run", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
17
|
+
body: JSON.stringify({ threadId: "thread-1", messages: [], state: {} }),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("V1 CopilotRuntime with agent factory function", () => {
|
|
22
|
+
it("preserves factory function through constructor (does not spread to {})", () => {
|
|
23
|
+
const factory = vi.fn().mockReturnValue({
|
|
24
|
+
default: createMockAgent("default"),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const runtime = new CopilotRuntime({ agents: factory });
|
|
28
|
+
|
|
29
|
+
// The V2 instance should receive a function (factory), not an empty object.
|
|
30
|
+
// Before the fix, spreading a function produced {}, losing all agents.
|
|
31
|
+
const v2Agents = runtime.instance.agents;
|
|
32
|
+
expect(typeof v2Agents).toBe("function");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("factory function resolves agents on each request", async () => {
|
|
36
|
+
const agentA = createMockAgent("tenant-a");
|
|
37
|
+
const agentB = createMockAgent("tenant-b");
|
|
38
|
+
|
|
39
|
+
const factory: AgentsConfig = ({ request }) => {
|
|
40
|
+
const tenantId = request.headers.get("x-tenant-id");
|
|
41
|
+
if (tenantId === "a") return { default: agentA };
|
|
42
|
+
return { default: agentB };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const runtime = new CopilotRuntime({ agents: factory });
|
|
46
|
+
const v2Agents = runtime.instance.agents;
|
|
47
|
+
|
|
48
|
+
const requestA = createMockRequest({ "x-tenant-id": "a" });
|
|
49
|
+
const resolvedA = await resolveAgents(v2Agents, requestA);
|
|
50
|
+
expect(resolvedA.default).toBe(agentA);
|
|
51
|
+
|
|
52
|
+
const requestB = createMockRequest({ "x-tenant-id": "b" });
|
|
53
|
+
const resolvedB = await resolveAgents(v2Agents, requestB);
|
|
54
|
+
expect(resolvedB.default).toBe(agentB);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("merges endpoint agents with factory-resolved agents", async () => {
|
|
58
|
+
const factoryAgent = createMockAgent("factory-agent");
|
|
59
|
+
const factory = vi.fn().mockReturnValue({
|
|
60
|
+
dynamic: factoryAgent,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Use remoteEndpoints to generate endpoint agents that should be merged
|
|
64
|
+
const runtime = new CopilotRuntime({
|
|
65
|
+
agents: factory,
|
|
66
|
+
remoteEndpoints: [
|
|
67
|
+
{
|
|
68
|
+
url: "https://example.com/endpoint",
|
|
69
|
+
onBeforeRequest: undefined,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const v2Agents = runtime.instance.agents;
|
|
75
|
+
expect(typeof v2Agents).toBe("function");
|
|
76
|
+
|
|
77
|
+
const request = createMockRequest();
|
|
78
|
+
const resolved = await resolveAgents(v2Agents, request);
|
|
79
|
+
|
|
80
|
+
// Factory agent should be present
|
|
81
|
+
expect(resolved.dynamic).toBe(factoryAgent);
|
|
82
|
+
// Factory should have been called with request context
|
|
83
|
+
expect(factory).toHaveBeenCalledWith({ request });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("static agents record still works after fix", async () => {
|
|
87
|
+
const agent = createMockAgent("static");
|
|
88
|
+
|
|
89
|
+
const runtime = new CopilotRuntime({
|
|
90
|
+
agents: { myAgent: agent },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const v2Agents = runtime.instance.agents;
|
|
94
|
+
const resolved = await resolveAgents(v2Agents);
|
|
95
|
+
expect(resolved.myAgent).toBe(agent);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("promised agents record still works after fix", async () => {
|
|
99
|
+
const agent = createMockAgent("promised");
|
|
100
|
+
|
|
101
|
+
const runtime = new CopilotRuntime({
|
|
102
|
+
agents: Promise.resolve({ myAgent: agent }),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const v2Agents = runtime.instance.agents;
|
|
106
|
+
const resolved = await resolveAgents(v2Agents);
|
|
107
|
+
expect(resolved.myAgent).toBe(agent);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getZodParameters,
|
|
24
24
|
type PartialBy,
|
|
25
25
|
isTelemetryDisabled,
|
|
26
|
+
type DebugConfig,
|
|
26
27
|
} from "@copilotkit/shared";
|
|
27
28
|
import type { RunAgentInput } from "@ag-ui/core";
|
|
28
29
|
import { aguiToGQL } from "../../graphql/message-conversion/agui-to-gql";
|
|
@@ -35,8 +36,13 @@ import {
|
|
|
35
36
|
type CopilotRuntimeOptions,
|
|
36
37
|
type CopilotRuntimeOptions as CopilotRuntimeOptionsVNext,
|
|
37
38
|
type AgentRunner,
|
|
39
|
+
type AgentsConfig,
|
|
40
|
+
type AgentsFactory,
|
|
41
|
+
type AgentFactoryContext,
|
|
38
42
|
InMemoryAgentRunner,
|
|
39
43
|
} from "../../v2/runtime";
|
|
44
|
+
|
|
45
|
+
export type { AgentsConfig, AgentsFactory, AgentFactoryContext };
|
|
40
46
|
import { TelemetryAgentRunner } from "./telemetry-agent-runner";
|
|
41
47
|
import telemetry from "../telemetry-client";
|
|
42
48
|
|
|
@@ -287,6 +293,19 @@ export interface CopilotRuntimeConstructorParams_BASE<
|
|
|
287
293
|
|
|
288
294
|
onStopGeneration?: OnStopGenerationHandler;
|
|
289
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Enable debug logging for the runtime event pipeline.
|
|
298
|
+
* Pass `true` for full output, or an object for granular control:
|
|
299
|
+
*
|
|
300
|
+
* ```ts
|
|
301
|
+
* const runtime = new CopilotRuntime({
|
|
302
|
+
* debug: true,
|
|
303
|
+
* // or: debug: { events: true, lifecycle: true, verbose: false }
|
|
304
|
+
* });
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
debug?: DebugConfig;
|
|
308
|
+
|
|
290
309
|
// /** Optional transcription service for audio processing. */
|
|
291
310
|
// transcriptionService?: CopilotRuntimeOptionsVNext["transcriptionService"];
|
|
292
311
|
// /** Optional *before* middleware – callback function or webhook URL. */
|
|
@@ -318,7 +337,7 @@ interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []>
|
|
|
318
337
|
* – the `MaybePromise<NonEmptyRecord<T>>` constraint in `CopilotRuntimeOptionsVNext`
|
|
319
338
|
* – the `Record<string, AbstractAgent>` constraint in `both
|
|
320
339
|
*/
|
|
321
|
-
agents?:
|
|
340
|
+
agents?: AgentsConfig;
|
|
322
341
|
}
|
|
323
342
|
|
|
324
343
|
/**
|
|
@@ -342,6 +361,22 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
342
361
|
params?.remoteEndpoints ?? [],
|
|
343
362
|
);
|
|
344
363
|
|
|
364
|
+
// Merge endpoint agents with user-provided agents.
|
|
365
|
+
// When agents is a factory function, wrap it so endpoint agents are merged
|
|
366
|
+
// at resolution time (spreading a function produces {} — silent data loss).
|
|
367
|
+
let mergedAgents: AgentsConfig;
|
|
368
|
+
if (typeof agents === "function") {
|
|
369
|
+
mergedAgents = async (ctx) => {
|
|
370
|
+
const resolved = await agents(ctx);
|
|
371
|
+
return { ...endpointAgents, ...resolved };
|
|
372
|
+
};
|
|
373
|
+
} else {
|
|
374
|
+
mergedAgents = Promise.resolve(agents).then((resolved) => ({
|
|
375
|
+
...endpointAgents,
|
|
376
|
+
...resolved,
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
|
|
345
380
|
// Determine the base runner (user-provided or default)
|
|
346
381
|
const baseRunner = params?.runner ?? new InMemoryAgentRunner();
|
|
347
382
|
|
|
@@ -353,9 +388,10 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
353
388
|
: new TelemetryAgentRunner({ runner: baseRunner });
|
|
354
389
|
|
|
355
390
|
this.runtimeArgs = {
|
|
356
|
-
agents:
|
|
391
|
+
agents: mergedAgents,
|
|
357
392
|
runner,
|
|
358
393
|
licenseToken: params?.licenseToken,
|
|
394
|
+
debug: params?.debug,
|
|
359
395
|
// TODO: add support for transcriptionService from CopilotRuntimeOptionsVNext once it is ready
|
|
360
396
|
// transcriptionService: params?.transcriptionService,
|
|
361
397
|
|
|
@@ -590,9 +626,22 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
590
626
|
params?.afterRequestMiddleware?.(hookParams);
|
|
591
627
|
|
|
592
628
|
if (params?.middleware?.onAfterRequest) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
629
|
+
const messages = hookParams.messages ?? [];
|
|
630
|
+
params.middleware.onAfterRequest({
|
|
631
|
+
threadId: hookParams.threadId ?? "",
|
|
632
|
+
runId: hookParams.runId,
|
|
633
|
+
inputMessages: messages.filter(
|
|
634
|
+
(m): m is typeof m & { role: string } =>
|
|
635
|
+
"role" in m && m.role === "user",
|
|
636
|
+
) as unknown as Message[],
|
|
637
|
+
outputMessages: messages.filter(
|
|
638
|
+
(m): m is typeof m & { role: string } =>
|
|
639
|
+
"role" in m && m.role !== "user",
|
|
640
|
+
) as unknown as Message[],
|
|
641
|
+
// TODO: forward actual properties once the after-request hook has access to the request body
|
|
642
|
+
properties: {},
|
|
643
|
+
url: hookParams.path,
|
|
644
|
+
} satisfies OnAfterRequestOptions);
|
|
596
645
|
}
|
|
597
646
|
};
|
|
598
647
|
}
|
|
@@ -85,30 +85,65 @@ export function extractParametersFromSchema(
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// Handle enums
|
|
88
|
+
// Handle enums — preserve as structured data for Zod conversion
|
|
89
|
+
let enumValues: string[] | undefined;
|
|
89
90
|
if (paramDef.enum && Array.isArray(paramDef.enum)) {
|
|
90
|
-
|
|
91
|
+
enumValues = paramDef.enum.map(String);
|
|
92
|
+
const enumDisplay = enumValues.join(" | ");
|
|
91
93
|
description =
|
|
92
94
|
description +
|
|
93
95
|
(description ? " " : "") +
|
|
94
|
-
`Allowed values: ${
|
|
96
|
+
`Allowed values: ${enumDisplay}`;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
// Handle objects with properties
|
|
99
|
+
// Handle objects with properties — recurse to preserve nested structure
|
|
100
|
+
let attributes: Parameter[] | undefined;
|
|
98
101
|
if (type === "object" && paramDef.properties) {
|
|
99
102
|
const objectProperties = Object.keys(paramDef.properties).join(", ");
|
|
100
103
|
description =
|
|
101
104
|
description +
|
|
102
105
|
(description ? " " : "") +
|
|
103
106
|
`Object with properties: ${objectProperties}`;
|
|
107
|
+
// Recursively extract nested parameters
|
|
108
|
+
attributes = extractParametersFromSchema({
|
|
109
|
+
parameters: {
|
|
110
|
+
properties: paramDef.properties,
|
|
111
|
+
required: paramDef.required || [],
|
|
112
|
+
},
|
|
113
|
+
});
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
// Handle object arrays — recurse into item schema
|
|
117
|
+
if (type === "array" && paramDef.items?.type === "object" && paramDef.items?.properties) {
|
|
118
|
+
attributes = extractParametersFromSchema({
|
|
119
|
+
parameters: {
|
|
120
|
+
properties: paramDef.items.properties,
|
|
121
|
+
required: paramDef.items.required || [],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const param: any = {
|
|
107
127
|
name: paramName,
|
|
108
128
|
type: type,
|
|
109
129
|
description: description,
|
|
110
130
|
required: requiredParams.has(paramName),
|
|
111
|
-
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Preserve enum values for string parameters
|
|
134
|
+
if (type === "string" && enumValues) {
|
|
135
|
+
param.enum = enumValues;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Preserve nested attributes for object and object[] types
|
|
139
|
+
if (attributes && attributes.length > 0) {
|
|
140
|
+
param.attributes = attributes;
|
|
141
|
+
if (type === "array") {
|
|
142
|
+
param.type = "object[]";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
parameters.push(param);
|
|
112
147
|
}
|
|
113
148
|
}
|
|
114
149
|
|
|
@@ -70,12 +70,19 @@ export interface AnthropicAdapterParams {
|
|
|
70
70
|
* See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
|
71
71
|
*/
|
|
72
72
|
promptCaching?: AnthropicPromptCachingConfig;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Optional maximum input token limit. Overrides the default limit
|
|
76
|
+
* used when trimming messages to fit the context window.
|
|
77
|
+
*/
|
|
78
|
+
maxInputTokens?: number;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
76
82
|
public model: string = DEFAULT_MODEL;
|
|
77
83
|
public provider = "anthropic";
|
|
78
84
|
private promptCaching: AnthropicPromptCachingConfig;
|
|
85
|
+
private maxInputTokens?: number;
|
|
79
86
|
|
|
80
87
|
private _anthropic: Anthropic;
|
|
81
88
|
public get anthropic(): Anthropic {
|
|
@@ -94,6 +101,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
94
101
|
this.model = params.model;
|
|
95
102
|
}
|
|
96
103
|
this.promptCaching = params?.promptCaching || { enabled: false };
|
|
104
|
+
this.maxInputTokens = params?.maxInputTokens;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
getLanguageModel(): LanguageModel {
|
|
@@ -244,6 +252,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
244
252
|
forwardedParameters,
|
|
245
253
|
} = request;
|
|
246
254
|
const tools = actions.map(convertActionInputToAnthropicTool);
|
|
255
|
+
const knownActionNames = new Set(actions.map((a) => a.name));
|
|
247
256
|
|
|
248
257
|
const messages = [...rawMessages];
|
|
249
258
|
|
|
@@ -322,6 +331,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
322
331
|
anthropicMessages,
|
|
323
332
|
tools,
|
|
324
333
|
model,
|
|
334
|
+
this.maxInputTokens,
|
|
325
335
|
);
|
|
326
336
|
|
|
327
337
|
// Apply prompt caching if enabled
|
|
@@ -350,7 +360,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
350
360
|
system: cachedSystemPrompt,
|
|
351
361
|
model: this.model,
|
|
352
362
|
messages: cachedMessages,
|
|
353
|
-
max_tokens: forwardedParameters?.maxTokens ||
|
|
363
|
+
max_tokens: forwardedParameters?.maxTokens || 4096,
|
|
354
364
|
...(forwardedParameters?.temperature
|
|
355
365
|
? { temperature: forwardedParameters.temperature }
|
|
356
366
|
: {}),
|
|
@@ -375,12 +385,18 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
375
385
|
if (chunk.type === "message_start") {
|
|
376
386
|
currentMessageId = chunk.message.id;
|
|
377
387
|
} else if (chunk.type === "content_block_start") {
|
|
378
|
-
hasReceivedContent = true;
|
|
379
388
|
if (chunk.content_block.type === "text") {
|
|
389
|
+
hasReceivedContent = true;
|
|
380
390
|
didOutputText = false;
|
|
381
391
|
filterThinkingTextBuffer.reset();
|
|
382
392
|
mode = "message";
|
|
383
393
|
} else if (chunk.content_block.type === "tool_use") {
|
|
394
|
+
if (!knownActionNames.has(chunk.content_block.name)) {
|
|
395
|
+
// Unknown tool - skip execution to prevent crashes
|
|
396
|
+
mode = null;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
hasReceivedContent = true;
|
|
384
400
|
currentToolCallId = chunk.content_block.id;
|
|
385
401
|
eventStream$.sendActionExecutionStart({
|
|
386
402
|
actionExecutionId: currentToolCallId,
|
|
@@ -390,6 +406,10 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
390
406
|
mode = "function";
|
|
391
407
|
}
|
|
392
408
|
} else if (chunk.type === "content_block_delta") {
|
|
409
|
+
if (mode === null) {
|
|
410
|
+
// Skip deltas for unknown/skipped content blocks
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
393
413
|
if (chunk.delta.type === "text_delta") {
|
|
394
414
|
const text = filterThinkingTextBuffer.onTextChunk(
|
|
395
415
|
chunk.delta.text,
|
|
@@ -49,7 +49,66 @@ export function limitMessagesToTokenCount(
|
|
|
49
49
|
maxTokens -= numTokens;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
// Post-process: remove orphaned tool_result and tool_use blocks.
|
|
53
|
+
// Token trimming may have removed the assistant message containing tool_use
|
|
54
|
+
// while keeping the user message with tool_result (or vice versa),
|
|
55
|
+
// which Anthropic rejects.
|
|
56
|
+
|
|
57
|
+
// Collect all tool_use IDs from assistant messages
|
|
58
|
+
const toolUseIds = new Set<string>();
|
|
59
|
+
for (const msg of result) {
|
|
60
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
61
|
+
for (const block of msg.content) {
|
|
62
|
+
if (block.type === "tool_use") {
|
|
63
|
+
toolUseIds.add(block.id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Collect all tool_result IDs from user messages
|
|
70
|
+
const toolResultIds = new Set<string>();
|
|
71
|
+
for (const msg of result) {
|
|
72
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
73
|
+
for (const block of msg.content) {
|
|
74
|
+
if (block.type === "tool_result") {
|
|
75
|
+
toolResultIds.add(block.tool_use_id);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Filter orphaned blocks without mutating the original messages
|
|
82
|
+
const filtered: any[] = [];
|
|
83
|
+
for (const msg of result) {
|
|
84
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
85
|
+
const remaining = msg.content.filter(
|
|
86
|
+
(block: any) =>
|
|
87
|
+
block.type !== "tool_result" || toolUseIds.has(block.tool_use_id),
|
|
88
|
+
);
|
|
89
|
+
if (remaining.length === 0) continue;
|
|
90
|
+
if (remaining.length !== msg.content.length) {
|
|
91
|
+
filtered.push({ ...msg, content: remaining });
|
|
92
|
+
} else {
|
|
93
|
+
filtered.push(msg);
|
|
94
|
+
}
|
|
95
|
+
} else if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
96
|
+
const remaining = msg.content.filter(
|
|
97
|
+
(block: any) =>
|
|
98
|
+
block.type !== "tool_use" || toolResultIds.has(block.id),
|
|
99
|
+
);
|
|
100
|
+
if (remaining.length === 0) continue;
|
|
101
|
+
if (remaining.length !== msg.content.length) {
|
|
102
|
+
filtered.push({ ...msg, content: remaining });
|
|
103
|
+
} else {
|
|
104
|
+
filtered.push(msg);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
filtered.push(msg);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return filtered;
|
|
53
112
|
}
|
|
54
113
|
|
|
55
114
|
const MAX_TOKENS = 128000;
|
|
@@ -269,7 +269,7 @@ export async function streamLangChainResponse({
|
|
|
269
269
|
});
|
|
270
270
|
} else if (content) {
|
|
271
271
|
mode = "message";
|
|
272
|
-
currentMessageId =
|
|
272
|
+
currentMessageId = randomId();
|
|
273
273
|
eventStream$.sendTextMessageStart({ messageId: currentMessageId });
|
|
274
274
|
}
|
|
275
275
|
}
|