@copilotkit/runtime 1.56.0 → 1.56.2-canary.pin-to-send
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/index.cjs +2 -2
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +2 -2
- package/dist/agent/index.mjs.map +1 -1
- package/dist/graphql/resolvers/copilot.resolver.cjs +2 -1
- package/dist/graphql/resolvers/copilot.resolver.cjs.map +1 -1
- package/dist/graphql/resolvers/copilot.resolver.mjs +2 -1
- package/dist/graphql/resolvers/copilot.resolver.mjs.map +1 -1
- package/dist/graphql/resolvers/resolve-message-id.cjs +19 -0
- package/dist/graphql/resolvers/resolve-message-id.cjs.map +1 -0
- package/dist/graphql/resolvers/resolve-message-id.mjs +18 -0
- package/dist/graphql/resolvers/resolve-message-id.mjs.map +1 -0
- 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/runtime/copilot-runtime.cjs +15 -3
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +15 -3
- 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 +2 -2
- package/dist/package.mjs +2 -2
- 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 +2 -1
- 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 +2 -1
- package/dist/service-adapters/openai/openai-adapter.mjs.map +1 -1
- package/dist/v2/runtime/core/debug-event-bus.cjs +36 -0
- package/dist/v2/runtime/core/debug-event-bus.cjs.map +1 -0
- package/dist/v2/runtime/core/debug-event-bus.d.cts +19 -0
- package/dist/v2/runtime/core/debug-event-bus.d.cts.map +1 -0
- package/dist/v2/runtime/core/debug-event-bus.d.mts +19 -0
- package/dist/v2/runtime/core/debug-event-bus.d.mts.map +1 -0
- package/dist/v2/runtime/core/debug-event-bus.mjs +35 -0
- package/dist/v2/runtime/core/debug-event-bus.mjs.map +1 -0
- package/dist/v2/runtime/core/fetch-handler.cjs +6 -0
- package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.mjs +6 -0
- package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
- package/dist/v2/runtime/core/fetch-router.cjs +1 -0
- package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-router.mjs +1 -0
- package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
- package/dist/v2/runtime/core/hooks.cjs.map +1 -1
- package/dist/v2/runtime/core/hooks.d.cts +2 -0
- package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
- package/dist/v2/runtime/core/hooks.d.mts +2 -0
- package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
- package/dist/v2/runtime/core/hooks.mjs.map +1 -1
- 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 +5 -0
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts +5 -0
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts +5 -0
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs +5 -0
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs +2 -0
- package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs +2 -0
- package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-debug-events.cjs +33 -0
- package/dist/v2/runtime/handlers/handle-debug-events.cjs.map +1 -0
- package/dist/v2/runtime/handlers/handle-debug-events.mjs +32 -0
- package/dist/v2/runtime/handlers/handle-debug-events.mjs.map +1 -0
- package/dist/v2/runtime/handlers/handle-run.cjs +1 -0
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +1 -0
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/connect.cjs +32 -2
- package/dist/v2/runtime/handlers/intelligence/connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/connect.mjs +31 -2
- package/dist/v2/runtime/handlers/intelligence/connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs +5 -1
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs +5 -1
- package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs +21 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs +21 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/connect.cjs +3 -1
- package/dist/v2/runtime/handlers/sse/connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/connect.mjs +3 -1
- package/dist/v2/runtime/handlers/sse/connect.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/intelligence-platform/client.cjs +2 -7
- package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.cts +1 -4
- package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +1 -4
- package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.mjs +2 -7
- package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.cjs +17 -5
- package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.d.cts +1 -0
- package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
- package/dist/v2/runtime/runner/intelligence.d.mts +1 -0
- package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
- package/dist/v2/runtime/runner/intelligence.mjs +17 -5
- package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
- package/package.json +3 -3
- package/src/agent/__tests__/provider-id-collision.test.ts +195 -0
- package/src/agent/index.ts +19 -11
- package/src/agents/langgraph/__tests__/event-source.test.ts +256 -0
- package/src/graphql/resolvers/__tests__/resolve-message-id.test.ts +25 -0
- package/src/graphql/resolvers/copilot.resolver.ts +2 -1
- package/src/graphql/resolvers/resolve-message-id.ts +14 -0
- 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__/handle-service-adapter.test.ts +108 -0
- 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__/retry-utils.test.ts +137 -0
- package/src/lib/runtime/agent-integrations/langgraph/__tests__/dispatch-event-filtering.test.ts +190 -0
- package/src/lib/runtime/copilot-runtime.ts +36 -7
- package/src/lib/runtime/mcp-tools-utils.ts +41 -6
- package/src/lib/runtime/retry-utils.ts +41 -1
- 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/openai-adapter.ts +14 -1
- package/src/v2/runtime/__tests__/fetch-router.test.ts +22 -0
- package/src/v2/runtime/__tests__/handle-connect.test.ts +58 -5
- package/src/v2/runtime/__tests__/handle-run.test.ts +31 -4
- package/src/v2/runtime/__tests__/handle-threads.test.ts +66 -4
- package/src/v2/runtime/__tests__/integration/node-servers.integration.test.ts +19 -0
- package/src/v2/runtime/__tests__/integration/suites/debug-events.suite.ts +253 -0
- package/src/v2/runtime/__tests__/middleware-sse-parser.test.ts +50 -0
- package/src/v2/runtime/__tests__/runtime.test.ts +3 -1
- package/src/v2/runtime/core/__tests__/debug-event-bus.test.ts +156 -0
- package/src/v2/runtime/core/debug-event-bus.ts +45 -0
- package/src/v2/runtime/core/fetch-handler.ts +4 -0
- package/src/v2/runtime/core/fetch-router.ts +11 -0
- package/src/v2/runtime/core/hooks.ts +2 -1
- package/src/v2/runtime/core/middleware-sse-parser.ts +12 -2
- package/src/v2/runtime/core/runtime.ts +12 -0
- package/src/v2/runtime/handlers/__tests__/handle-debug-events.test.ts +176 -0
- package/src/v2/runtime/handlers/handle-connect.ts +2 -0
- package/src/v2/runtime/handlers/handle-debug-events.ts +52 -0
- package/src/v2/runtime/handlers/handle-run.ts +1 -0
- package/src/v2/runtime/handlers/intelligence/connect.ts +58 -1
- package/src/v2/runtime/handlers/shared/resolve-intelligence-user.ts +4 -1
- package/src/v2/runtime/handlers/shared/sse-response.ts +46 -0
- package/src/v2/runtime/handlers/sse/__tests__/sse-connect-agent-id.test.ts +71 -0
- package/src/v2/runtime/handlers/sse/connect.ts +6 -0
- package/src/v2/runtime/handlers/sse/run.ts +4 -0
- package/src/v2/runtime/intelligence-platform/__tests__/client.test.ts +13 -11
- package/src/v2/runtime/intelligence-platform/client.ts +3 -11
- package/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts +51 -1
- package/src/v2/runtime/runner/intelligence.ts +27 -9
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +268 -0
- package/tests/service-adapters/anthropic/utils-token-trimming.test.ts +301 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { LanguageModel } from "ai";
|
|
2
|
+
import { CopilotKitMisuseError } from "@copilotkit/shared";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { BuiltInAgent } from "../../../agent";
|
|
5
|
+
import type { CopilotServiceAdapter } from "../../../service-adapters";
|
|
6
|
+
import { CopilotRuntime } from "../copilot-runtime";
|
|
7
|
+
|
|
8
|
+
function makeAdapter(
|
|
9
|
+
overrides?: Partial<CopilotServiceAdapter>,
|
|
10
|
+
): CopilotServiceAdapter {
|
|
11
|
+
return {
|
|
12
|
+
name: "TestAdapter",
|
|
13
|
+
async process() {
|
|
14
|
+
throw new Error("process() is not expected to be called in these tests");
|
|
15
|
+
},
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function getDefaultAgent(runtime: CopilotRuntime) {
|
|
21
|
+
const agents = await runtime.instance.agents;
|
|
22
|
+
return agents.default;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// `BuiltInAgent.config` is private; reading it is the only way to verify the
|
|
26
|
+
// correct model was passed through without running the entire agent pipeline.
|
|
27
|
+
// This narrow accessor is the Rule 2 exception, documented here once rather
|
|
28
|
+
// than inline at each call site.
|
|
29
|
+
function getBuiltInAgentModel(agent: BuiltInAgent): unknown {
|
|
30
|
+
return (agent as unknown as { config: { model: unknown } }).config.model;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("CopilotRuntime#handleServiceAdapter (#3217)", () => {
|
|
34
|
+
it("uses the adapter's pre-configured LanguageModel when getLanguageModel() returns one", async () => {
|
|
35
|
+
const fakeLanguageModel = {
|
|
36
|
+
specificationVersion: "v1",
|
|
37
|
+
} as unknown as LanguageModel;
|
|
38
|
+
const runtime = new CopilotRuntime();
|
|
39
|
+
|
|
40
|
+
runtime.handleServiceAdapter(
|
|
41
|
+
makeAdapter({
|
|
42
|
+
name: "OpenAIAdapter",
|
|
43
|
+
provider: "openai",
|
|
44
|
+
model: "gpt-4o",
|
|
45
|
+
getLanguageModel: () => fakeLanguageModel,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const agent = await getDefaultAgent(runtime);
|
|
50
|
+
expect(agent).toBeInstanceOf(BuiltInAgent);
|
|
51
|
+
expect(getBuiltInAgentModel(agent as BuiltInAgent)).toBe(fakeLanguageModel);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("builds a 'provider/model' string when only provider+model are exposed", async () => {
|
|
55
|
+
const runtime = new CopilotRuntime();
|
|
56
|
+
|
|
57
|
+
runtime.handleServiceAdapter(
|
|
58
|
+
makeAdapter({
|
|
59
|
+
name: "GroqAdapter",
|
|
60
|
+
provider: "groq",
|
|
61
|
+
model: "llama-3.3-70b-versatile",
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const agent = await getDefaultAgent(runtime);
|
|
66
|
+
expect(agent).toBeInstanceOf(BuiltInAgent);
|
|
67
|
+
expect(getBuiltInAgentModel(agent as BuiltInAgent)).toBe(
|
|
68
|
+
"groq/llama-3.3-70b-versatile",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("throws CopilotKitMisuseError when no model source is available (LangChainAdapter regression)", async () => {
|
|
73
|
+
const runtime = new CopilotRuntime();
|
|
74
|
+
|
|
75
|
+
runtime.handleServiceAdapter(makeAdapter({ name: "LangChainAdapter" }));
|
|
76
|
+
|
|
77
|
+
await expect(runtime.instance.agents).rejects.toBeInstanceOf(
|
|
78
|
+
CopilotKitMisuseError,
|
|
79
|
+
);
|
|
80
|
+
await expect(runtime.instance.agents).rejects.toThrow(
|
|
81
|
+
/Service adapter "LangChainAdapter" does not provide model information/,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("falls back to 'unknown' in the thrown error when the adapter has no name", async () => {
|
|
86
|
+
const runtime = new CopilotRuntime();
|
|
87
|
+
|
|
88
|
+
runtime.handleServiceAdapter(makeAdapter({ name: undefined }));
|
|
89
|
+
|
|
90
|
+
await expect(runtime.instance.agents).rejects.toThrow(
|
|
91
|
+
/Service adapter "unknown" does not provide model information/,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("does not throw when provider is set without a model — but must not emit 'undefined/undefined'", async () => {
|
|
96
|
+
// Guards the specific #3217 regression: when only one half of the pair is
|
|
97
|
+
// present, we must NOT synthesize a bogus "provider/undefined" string.
|
|
98
|
+
const runtime = new CopilotRuntime();
|
|
99
|
+
|
|
100
|
+
runtime.handleServiceAdapter(
|
|
101
|
+
makeAdapter({ name: "PartialAdapter", provider: "openai" }),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
await expect(runtime.instance.agents).rejects.toThrow(
|
|
105
|
+
CopilotKitMisuseError,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -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,137 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { fetchWithRetry, parseRetryAfter, RETRY_CONFIG } from "../retry-utils";
|
|
3
|
+
|
|
4
|
+
function responseWithRetryAfter(
|
|
5
|
+
headerValue: string | null,
|
|
6
|
+
status = 429,
|
|
7
|
+
): Response {
|
|
8
|
+
const headers = new Headers();
|
|
9
|
+
if (headerValue !== null) headers.set("Retry-After", headerValue);
|
|
10
|
+
return new Response(null, { status, headers });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("parseRetryAfter", () => {
|
|
14
|
+
it("returns undefined when the Retry-After header is absent", () => {
|
|
15
|
+
expect(parseRetryAfter(responseWithRetryAfter(null))).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("parses integer seconds into milliseconds", () => {
|
|
19
|
+
expect(parseRetryAfter(responseWithRetryAfter("5"))).toBe(5000);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("treats zero seconds as zero delay", () => {
|
|
23
|
+
expect(parseRetryAfter(responseWithRetryAfter("0"))).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("clamps a negative numeric value to zero", () => {
|
|
27
|
+
// `-1` fails the `seconds >= 0` guard and falls through to Date.parse,
|
|
28
|
+
// which interprets it as a year-in-the-past timestamp; the past-date
|
|
29
|
+
// branch then clamps to 0. The behavior is lenient rather than strict.
|
|
30
|
+
expect(parseRetryAfter(responseWithRetryAfter("-1"))).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("parses an HTTP-date in the future as the delta to now", () => {
|
|
34
|
+
const now = Date.parse("2026-04-22T12:00:00Z");
|
|
35
|
+
vi.useFakeTimers();
|
|
36
|
+
vi.setSystemTime(now);
|
|
37
|
+
try {
|
|
38
|
+
const future = new Date(now + 30_000).toUTCString();
|
|
39
|
+
expect(parseRetryAfter(responseWithRetryAfter(future))).toBe(30_000);
|
|
40
|
+
} finally {
|
|
41
|
+
vi.useRealTimers();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("clamps an HTTP-date in the past to zero", () => {
|
|
46
|
+
const now = Date.parse("2026-04-22T12:00:00Z");
|
|
47
|
+
vi.useFakeTimers();
|
|
48
|
+
vi.setSystemTime(now);
|
|
49
|
+
try {
|
|
50
|
+
const past = new Date(now - 60_000).toUTCString();
|
|
51
|
+
expect(parseRetryAfter(responseWithRetryAfter(past))).toBe(0);
|
|
52
|
+
} finally {
|
|
53
|
+
vi.useRealTimers();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns undefined for unparseable values", () => {
|
|
58
|
+
expect(parseRetryAfter(responseWithRetryAfter("soon"))).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("fetchWithRetry Retry-After handling (#3637)", () => {
|
|
63
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
vi.useFakeTimers();
|
|
67
|
+
fetchMock = vi.fn();
|
|
68
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
vi.useRealTimers();
|
|
73
|
+
vi.unstubAllGlobals();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("honors Retry-After within the allowed maximum on 429", async () => {
|
|
77
|
+
fetchMock
|
|
78
|
+
.mockResolvedValueOnce(responseWithRetryAfter("2"))
|
|
79
|
+
.mockResolvedValueOnce(new Response("ok", { status: 200 }));
|
|
80
|
+
|
|
81
|
+
const promise = fetchWithRetry("https://example.com", {});
|
|
82
|
+
await vi.advanceTimersByTimeAsync(1999);
|
|
83
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
84
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
85
|
+
const response = await promise;
|
|
86
|
+
|
|
87
|
+
expect(response.status).toBe(200);
|
|
88
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("throws when Retry-After exceeds maxRetryAfterSeconds", async () => {
|
|
92
|
+
const excessive = RETRY_CONFIG.maxRetryAfterSeconds + 1;
|
|
93
|
+
fetchMock.mockResolvedValue(responseWithRetryAfter(String(excessive)));
|
|
94
|
+
|
|
95
|
+
// The oversized-Retry-After branch throws before sleeping, and the
|
|
96
|
+
// resulting Error doesn't match any retryable pattern, so the loop
|
|
97
|
+
// breaks out without consuming the remaining attempts.
|
|
98
|
+
await expect(fetchWithRetry("https://example.com", {})).rejects.toThrow(
|
|
99
|
+
new RegExp(
|
|
100
|
+
`Retry-After of ${excessive}s.*exceeds the maximum of ${RETRY_CONFIG.maxRetryAfterSeconds}s`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("falls back to exponential backoff when Retry-After is missing on 429", async () => {
|
|
107
|
+
fetchMock
|
|
108
|
+
.mockResolvedValueOnce(new Response(null, { status: 429 }))
|
|
109
|
+
.mockResolvedValueOnce(new Response("ok", { status: 200 }));
|
|
110
|
+
|
|
111
|
+
const promise = fetchWithRetry("https://example.com", {});
|
|
112
|
+
// calculateDelay(0) === RETRY_CONFIG.baseDelayMs
|
|
113
|
+
await vi.advanceTimersByTimeAsync(RETRY_CONFIG.baseDelayMs - 1);
|
|
114
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
115
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
116
|
+
const response = await promise;
|
|
117
|
+
|
|
118
|
+
expect(response.status).toBe(200);
|
|
119
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("ignores Retry-After on non-429 retryable responses (e.g. 503)", async () => {
|
|
123
|
+
const longRetryAfter = String(RETRY_CONFIG.maxRetryAfterSeconds + 600);
|
|
124
|
+
fetchMock
|
|
125
|
+
.mockResolvedValueOnce(responseWithRetryAfter(longRetryAfter, 503))
|
|
126
|
+
.mockResolvedValueOnce(new Response("ok", { status: 200 }));
|
|
127
|
+
|
|
128
|
+
const promise = fetchWithRetry("https://example.com", {});
|
|
129
|
+
// Exponential backoff applies, not the header value — otherwise this
|
|
130
|
+
// would wait 10 minutes and the test would time out.
|
|
131
|
+
await vi.advanceTimersByTimeAsync(RETRY_CONFIG.baseDelayMs);
|
|
132
|
+
const response = await promise;
|
|
133
|
+
|
|
134
|
+
expect(response.status).toBe(200);
|
|
135
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
136
|
+
});
|
|
137
|
+
});
|
package/src/lib/runtime/agent-integrations/langgraph/__tests__/dispatch-event-filtering.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventType } from "@ag-ui/client";
|
|
2
2
|
import { LangGraphAgent } from "../agent";
|
|
3
|
+
import { CustomEventNames } from "../consts";
|
|
4
|
+
import { vi } from "vitest";
|
|
3
5
|
|
|
4
6
|
function createAgent() {
|
|
5
7
|
const agent = new LangGraphAgent({
|
|
@@ -33,6 +35,25 @@ function makeTextEvent(
|
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
function makeCustomEvent(name: string, value: any) {
|
|
39
|
+
return {
|
|
40
|
+
type: EventType.CUSTOM,
|
|
41
|
+
name,
|
|
42
|
+
value,
|
|
43
|
+
} as any;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Mock the parent class's langGraphDefaultMergeState for a single test,
|
|
48
|
+
* using vi.spyOn for automatic cleanup.
|
|
49
|
+
*/
|
|
50
|
+
function withMockedParentMerge(agent: LangGraphAgent, returnValue: any) {
|
|
51
|
+
const parentProto = Object.getPrototypeOf(Object.getPrototypeOf(agent));
|
|
52
|
+
return vi
|
|
53
|
+
.spyOn(parentProto, "langGraphDefaultMergeState")
|
|
54
|
+
.mockReturnValue(returnValue);
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
describe("dispatchEvent emit-messages filtering", () => {
|
|
37
58
|
it("suppresses message events when copilotkit:emit-messages is false", () => {
|
|
38
59
|
const { agent, events } = createAgent();
|
|
@@ -153,3 +174,172 @@ describe("dispatchEvent emit-tool-calls filtering", () => {
|
|
|
153
174
|
expect(events).toHaveLength(1);
|
|
154
175
|
});
|
|
155
176
|
});
|
|
177
|
+
|
|
178
|
+
// ---------- CopilotKit custom event dispatch ----------
|
|
179
|
+
|
|
180
|
+
describe("dispatchEvent custom CopilotKit events", () => {
|
|
181
|
+
it("manually_emit_message produces TextMessage event sequence", () => {
|
|
182
|
+
const { agent, events } = createAgent();
|
|
183
|
+
|
|
184
|
+
const result = agent.dispatchEvent(
|
|
185
|
+
makeCustomEvent(CustomEventNames.CopilotKitManuallyEmitMessage, {
|
|
186
|
+
message_id: "msg-manual-1",
|
|
187
|
+
message: "Hello from agent",
|
|
188
|
+
role: "assistant",
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(result).toBe(true);
|
|
193
|
+
expect(events).toHaveLength(3);
|
|
194
|
+
expect(events[0].type).toBe(EventType.TEXT_MESSAGE_START);
|
|
195
|
+
expect(events[0].messageId).toBe("msg-manual-1");
|
|
196
|
+
expect(events[0].role).toBe("assistant");
|
|
197
|
+
expect(events[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT);
|
|
198
|
+
expect(events[1].delta).toBe("Hello from agent");
|
|
199
|
+
expect(events[2].type).toBe(EventType.TEXT_MESSAGE_END);
|
|
200
|
+
expect(events[2].messageId).toBe("msg-manual-1");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("manually_emit_tool_call produces ToolCall event sequence", () => {
|
|
204
|
+
const { agent, events } = createAgent();
|
|
205
|
+
|
|
206
|
+
const result = agent.dispatchEvent(
|
|
207
|
+
makeCustomEvent(CustomEventNames.CopilotKitManuallyEmitToolCall, {
|
|
208
|
+
id: "tc-manual-1",
|
|
209
|
+
name: "SearchTool",
|
|
210
|
+
args: { query: "test" },
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(result).toBe(true);
|
|
215
|
+
expect(events).toHaveLength(3);
|
|
216
|
+
expect(events[0].type).toBe(EventType.TOOL_CALL_START);
|
|
217
|
+
expect(events[0].toolCallId).toBe("tc-manual-1");
|
|
218
|
+
expect(events[0].toolCallName).toBe("SearchTool");
|
|
219
|
+
expect(events[1].type).toBe(EventType.TOOL_CALL_ARGS);
|
|
220
|
+
expect(events[1].toolCallId).toBe("tc-manual-1");
|
|
221
|
+
expect(events[1].delta).toEqual({ query: "test" });
|
|
222
|
+
expect(events[2].type).toBe(EventType.TOOL_CALL_END);
|
|
223
|
+
expect(events[2].toolCallId).toBe("tc-manual-1");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("manually_emit_state produces StateSnapshot event", () => {
|
|
227
|
+
const { agent, events } = createAgent();
|
|
228
|
+
|
|
229
|
+
// Mock getStateSnapshot since it depends on thread state
|
|
230
|
+
(agent as any).getStateSnapshot = (state: any) => ({
|
|
231
|
+
values: state.values,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const result = agent.dispatchEvent(
|
|
235
|
+
makeCustomEvent(
|
|
236
|
+
CustomEventNames.CopilotKitManuallyEmitIntermediateState,
|
|
237
|
+
{
|
|
238
|
+
progress: 75,
|
|
239
|
+
},
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
expect(result).toBe(true);
|
|
244
|
+
expect((agent as any).activeRun.manuallyEmittedState).toEqual({
|
|
245
|
+
progress: 75,
|
|
246
|
+
});
|
|
247
|
+
const snapshotEvents = events.filter(
|
|
248
|
+
(e) => e.type === EventType.STATE_SNAPSHOT,
|
|
249
|
+
);
|
|
250
|
+
expect(snapshotEvents.length).toBeGreaterThanOrEqual(1);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("copilotkit_exit produces Exit custom event", () => {
|
|
254
|
+
const { agent, events } = createAgent();
|
|
255
|
+
|
|
256
|
+
const result = agent.dispatchEvent(
|
|
257
|
+
makeCustomEvent(CustomEventNames.CopilotKitExit, {}),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
expect(result).toBe(true);
|
|
261
|
+
expect(events).toHaveLength(1);
|
|
262
|
+
expect(events[0].type).toBe(EventType.CUSTOM);
|
|
263
|
+
// "Exit" is the hardcoded downstream name in agent.ts — not a constant,
|
|
264
|
+
// because it's the value the frontend listens for, not an internal enum.
|
|
265
|
+
expect(events[0].name).toBe("Exit");
|
|
266
|
+
expect(events[0].value).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("events without rawEvent pass through to subscriber", () => {
|
|
270
|
+
const { agent, events } = createAgent();
|
|
271
|
+
|
|
272
|
+
const result = agent.dispatchEvent({
|
|
273
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
274
|
+
messageId: "msg-no-raw",
|
|
275
|
+
role: "assistant",
|
|
276
|
+
} as any);
|
|
277
|
+
|
|
278
|
+
expect(result).toBe(true);
|
|
279
|
+
expect(events).toHaveLength(1);
|
|
280
|
+
expect(events[0].messageId).toBe("msg-no-raw");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// ---------- langGraphDefaultMergeState ----------
|
|
285
|
+
|
|
286
|
+
describe("langGraphDefaultMergeState", () => {
|
|
287
|
+
afterEach(() => {
|
|
288
|
+
vi.restoreAllMocks();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("merges copilotkit actions from ag-ui tools", () => {
|
|
292
|
+
const { agent } = createAgent();
|
|
293
|
+
const tools = [{ name: "tool1" }, { name: "tool2" }];
|
|
294
|
+
|
|
295
|
+
withMockedParentMerge(agent, {
|
|
296
|
+
"ag-ui": { tools, context: [] },
|
|
297
|
+
tools: [],
|
|
298
|
+
messages: [],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const result = agent.langGraphDefaultMergeState({} as any, [], {} as any);
|
|
302
|
+
expect(result.copilotkit).toBeDefined();
|
|
303
|
+
expect(result.copilotkit.actions).toEqual(expect.arrayContaining(tools));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("merges copilotkit context from ag-ui", () => {
|
|
307
|
+
const { agent } = createAgent();
|
|
308
|
+
const context = [{ description: "user info", value: "test" }];
|
|
309
|
+
|
|
310
|
+
withMockedParentMerge(agent, {
|
|
311
|
+
"ag-ui": { tools: [], context },
|
|
312
|
+
tools: [],
|
|
313
|
+
messages: [],
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const result = agent.langGraphDefaultMergeState({} as any, [], {} as any);
|
|
317
|
+
expect(result.copilotkit.context).toEqual(context);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("handles missing ag-ui key without crashing", () => {
|
|
321
|
+
const { agent } = createAgent();
|
|
322
|
+
|
|
323
|
+
withMockedParentMerge(agent, { messages: [] });
|
|
324
|
+
|
|
325
|
+
const result = agent.langGraphDefaultMergeState({} as any, [], {} as any);
|
|
326
|
+
expect(result.copilotkit).toBeDefined();
|
|
327
|
+
expect(result.copilotkit.actions).toEqual([]);
|
|
328
|
+
expect(result.copilotkit.context).toEqual([]);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("deduplicates tools from returnedTools and ag-ui tools", () => {
|
|
332
|
+
const { agent } = createAgent();
|
|
333
|
+
const tool = { name: "SharedTool", id: "shared-1" };
|
|
334
|
+
|
|
335
|
+
withMockedParentMerge(agent, {
|
|
336
|
+
"ag-ui": { tools: [tool], context: [] },
|
|
337
|
+
tools: [tool],
|
|
338
|
+
messages: [],
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = agent.langGraphDefaultMergeState({} as any, [], {} as any);
|
|
342
|
+
expect(result.copilotkit.actions).toHaveLength(1);
|
|
343
|
+
expect(result.copilotkit.actions[0].name).toBe("SharedTool");
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -463,10 +463,26 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
if (isAgentsListEmpty) {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
const languageModel = serviceAdapter.getLanguageModel?.();
|
|
467
|
+
if (languageModel) {
|
|
468
|
+
// Adapter exposes a pre-configured LanguageModel (e.g. OpenAI/Anthropic adapters)
|
|
469
|
+
agentsList.default = new BuiltInAgent({ model: languageModel });
|
|
470
|
+
} else if (serviceAdapter.provider && serviceAdapter.model) {
|
|
471
|
+
// Adapter exposes provider/model strings
|
|
472
|
+
agentsList.default = new BuiltInAgent({
|
|
473
|
+
model: `${serviceAdapter.provider}/${serviceAdapter.model}`,
|
|
474
|
+
});
|
|
475
|
+
} else {
|
|
476
|
+
throw new CopilotKitMisuseError({
|
|
477
|
+
message:
|
|
478
|
+
`Service adapter "${serviceAdapter.name ?? "unknown"}" does not provide model information. ` +
|
|
479
|
+
`When using adapters like LangChainAdapter without an explicit agents list, ` +
|
|
480
|
+
`please provide a default agent in the runtime config. Example:\n` +
|
|
481
|
+
` new CopilotRuntime({\n` +
|
|
482
|
+
` agents: { default: new BuiltInAgent({ model: "openai/gpt-4o" }) }\n` +
|
|
483
|
+
` })`,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
470
486
|
}
|
|
471
487
|
|
|
472
488
|
const actions = this.params?.actions;
|
|
@@ -626,9 +642,22 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
626
642
|
params?.afterRequestMiddleware?.(hookParams);
|
|
627
643
|
|
|
628
644
|
if (params?.middleware?.onAfterRequest) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
645
|
+
const messages = hookParams.messages ?? [];
|
|
646
|
+
params.middleware.onAfterRequest({
|
|
647
|
+
threadId: hookParams.threadId ?? "",
|
|
648
|
+
runId: hookParams.runId,
|
|
649
|
+
inputMessages: messages.filter(
|
|
650
|
+
(m): m is typeof m & { role: string } =>
|
|
651
|
+
"role" in m && m.role === "user",
|
|
652
|
+
) as unknown as Message[],
|
|
653
|
+
outputMessages: messages.filter(
|
|
654
|
+
(m): m is typeof m & { role: string } =>
|
|
655
|
+
"role" in m && m.role !== "user",
|
|
656
|
+
) as unknown as Message[],
|
|
657
|
+
// TODO: forward actual properties once the after-request hook has access to the request body
|
|
658
|
+
properties: {},
|
|
659
|
+
url: hookParams.path,
|
|
660
|
+
} satisfies OnAfterRequestOptions);
|
|
632
661
|
}
|
|
633
662
|
};
|
|
634
663
|
}
|