@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.
Files changed (169) hide show
  1. package/dist/agent/converters/tanstack.cjs.map +1 -1
  2. package/dist/agent/converters/tanstack.d.cts +6 -19
  3. package/dist/agent/converters/tanstack.d.cts.map +1 -1
  4. package/dist/agent/converters/tanstack.d.mts +6 -19
  5. package/dist/agent/converters/tanstack.d.mts.map +1 -1
  6. package/dist/agent/converters/tanstack.mjs.map +1 -1
  7. package/dist/agent/index.cjs +16 -2
  8. package/dist/agent/index.cjs.map +1 -1
  9. package/dist/agent/index.d.cts +12 -1
  10. package/dist/agent/index.d.cts.map +1 -1
  11. package/dist/agent/index.d.mts +12 -1
  12. package/dist/agent/index.d.mts.map +1 -1
  13. package/dist/agent/index.mjs +16 -2
  14. package/dist/agent/index.mjs.map +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.cts +3 -2
  17. package/dist/index.d.mts +3 -2
  18. package/dist/index.mjs +1 -1
  19. package/dist/lib/index.cjs +1 -1
  20. package/dist/lib/index.d.cts +2 -1
  21. package/dist/lib/index.d.cts.map +1 -1
  22. package/dist/lib/index.d.mts +2 -1
  23. package/dist/lib/index.d.mts.map +1 -1
  24. package/dist/lib/index.mjs +1 -1
  25. package/dist/lib/integrations/node-http/index.cjs +4 -1
  26. package/dist/lib/integrations/node-http/index.cjs.map +1 -1
  27. package/dist/lib/integrations/node-http/index.d.cts.map +1 -1
  28. package/dist/lib/integrations/node-http/index.d.mts.map +1 -1
  29. package/dist/lib/integrations/node-http/index.mjs +4 -1
  30. package/dist/lib/integrations/node-http/index.mjs.map +1 -1
  31. package/dist/lib/integrations/shared.cjs +1 -1
  32. package/dist/lib/integrations/shared.d.cts +1 -1
  33. package/dist/lib/integrations/shared.d.mts +1 -1
  34. package/dist/lib/integrations/shared.mjs +1 -1
  35. package/dist/lib/runtime/copilot-runtime.cjs +25 -5
  36. package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
  37. package/dist/lib/runtime/copilot-runtime.d.cts +15 -3
  38. package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
  39. package/dist/lib/runtime/copilot-runtime.d.mts +15 -3
  40. package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
  41. package/dist/lib/runtime/copilot-runtime.mjs +25 -5
  42. package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
  43. package/dist/lib/runtime/mcp-tools-utils.cjs +21 -4
  44. package/dist/lib/runtime/mcp-tools-utils.cjs.map +1 -1
  45. package/dist/lib/runtime/mcp-tools-utils.d.cts.map +1 -1
  46. package/dist/lib/runtime/mcp-tools-utils.d.mts.map +1 -1
  47. package/dist/lib/runtime/mcp-tools-utils.mjs +21 -4
  48. package/dist/lib/runtime/mcp-tools-utils.mjs.map +1 -1
  49. package/dist/package.cjs +6 -5
  50. package/dist/package.mjs +6 -5
  51. package/dist/service-adapters/anthropic/anthropic-adapter.cjs +11 -3
  52. package/dist/service-adapters/anthropic/anthropic-adapter.cjs.map +1 -1
  53. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts +6 -0
  54. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts.map +1 -1
  55. package/dist/service-adapters/anthropic/anthropic-adapter.d.mts +6 -0
  56. package/dist/service-adapters/anthropic/anthropic-adapter.d.mts.map +1 -1
  57. package/dist/service-adapters/anthropic/anthropic-adapter.mjs +11 -3
  58. package/dist/service-adapters/anthropic/anthropic-adapter.mjs.map +1 -1
  59. package/dist/service-adapters/anthropic/utils.cjs +27 -1
  60. package/dist/service-adapters/anthropic/utils.cjs.map +1 -1
  61. package/dist/service-adapters/anthropic/utils.mjs +27 -1
  62. package/dist/service-adapters/anthropic/utils.mjs.map +1 -1
  63. package/dist/service-adapters/langchain/utils.cjs +1 -1
  64. package/dist/service-adapters/langchain/utils.cjs.map +1 -1
  65. package/dist/service-adapters/langchain/utils.mjs +1 -1
  66. package/dist/service-adapters/langchain/utils.mjs.map +1 -1
  67. package/dist/service-adapters/openai/openai-adapter.cjs +3 -2
  68. package/dist/service-adapters/openai/openai-adapter.cjs.map +1 -1
  69. package/dist/service-adapters/openai/openai-adapter.d.cts +6 -0
  70. package/dist/service-adapters/openai/openai-adapter.d.cts.map +1 -1
  71. package/dist/service-adapters/openai/openai-adapter.d.mts +6 -0
  72. package/dist/service-adapters/openai/openai-adapter.d.mts.map +1 -1
  73. package/dist/service-adapters/openai/openai-adapter.mjs +4 -3
  74. package/dist/service-adapters/openai/openai-adapter.mjs.map +1 -1
  75. package/dist/service-adapters/openai/openai-assistant-adapter.cjs +8 -9
  76. package/dist/service-adapters/openai/openai-assistant-adapter.cjs.map +1 -1
  77. package/dist/service-adapters/openai/openai-assistant-adapter.d.cts.map +1 -1
  78. package/dist/service-adapters/openai/openai-assistant-adapter.d.mts.map +1 -1
  79. package/dist/service-adapters/openai/openai-assistant-adapter.mjs +9 -10
  80. package/dist/service-adapters/openai/openai-assistant-adapter.mjs.map +1 -1
  81. package/dist/service-adapters/openai/utils.cjs +53 -0
  82. package/dist/service-adapters/openai/utils.cjs.map +1 -1
  83. package/dist/service-adapters/openai/utils.mjs +51 -1
  84. package/dist/service-adapters/openai/utils.mjs.map +1 -1
  85. package/dist/v2/index.cjs +1 -0
  86. package/dist/v2/index.d.cts +3 -3
  87. package/dist/v2/index.d.mts +3 -3
  88. package/dist/v2/index.mjs +2 -2
  89. package/dist/v2/runtime/core/middleware-sse-parser.cjs +5 -2
  90. package/dist/v2/runtime/core/middleware-sse-parser.cjs.map +1 -1
  91. package/dist/v2/runtime/core/middleware-sse-parser.mjs +5 -2
  92. package/dist/v2/runtime/core/middleware-sse-parser.mjs.map +1 -1
  93. package/dist/v2/runtime/core/runtime.cjs +25 -0
  94. package/dist/v2/runtime/core/runtime.cjs.map +1 -1
  95. package/dist/v2/runtime/core/runtime.d.cts +53 -4
  96. package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
  97. package/dist/v2/runtime/core/runtime.d.mts +53 -4
  98. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  99. package/dist/v2/runtime/core/runtime.mjs +26 -2
  100. package/dist/v2/runtime/core/runtime.mjs.map +1 -1
  101. package/dist/v2/runtime/handlers/get-runtime-info.cjs +18 -10
  102. package/dist/v2/runtime/handlers/get-runtime-info.cjs.map +1 -1
  103. package/dist/v2/runtime/handlers/get-runtime-info.mjs +19 -11
  104. package/dist/v2/runtime/handlers/get-runtime-info.mjs.map +1 -1
  105. package/dist/v2/runtime/handlers/handle-connect.cjs +1 -1
  106. package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
  107. package/dist/v2/runtime/handlers/handle-connect.mjs +1 -1
  108. package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
  109. package/dist/v2/runtime/handlers/handle-run.cjs +8 -2
  110. package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
  111. package/dist/v2/runtime/handlers/handle-run.mjs +8 -2
  112. package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
  113. package/dist/v2/runtime/handlers/handle-stop.cjs +2 -1
  114. package/dist/v2/runtime/handlers/handle-stop.cjs.map +1 -1
  115. package/dist/v2/runtime/handlers/handle-stop.mjs +2 -1
  116. package/dist/v2/runtime/handlers/handle-stop.mjs.map +1 -1
  117. package/dist/v2/runtime/handlers/intelligence/thread-names.cjs +1 -1
  118. package/dist/v2/runtime/handlers/intelligence/thread-names.cjs.map +1 -1
  119. package/dist/v2/runtime/handlers/intelligence/thread-names.mjs +1 -1
  120. package/dist/v2/runtime/handlers/intelligence/thread-names.mjs.map +1 -1
  121. package/dist/v2/runtime/handlers/shared/agent-utils.cjs +3 -2
  122. package/dist/v2/runtime/handlers/shared/agent-utils.cjs.map +1 -1
  123. package/dist/v2/runtime/handlers/shared/agent-utils.mjs +3 -2
  124. package/dist/v2/runtime/handlers/shared/agent-utils.mjs.map +1 -1
  125. package/dist/v2/runtime/handlers/shared/sse-response.cjs +40 -1
  126. package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
  127. package/dist/v2/runtime/handlers/shared/sse-response.mjs +40 -1
  128. package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
  129. package/dist/v2/runtime/handlers/sse/run.cjs +3 -1
  130. package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
  131. package/dist/v2/runtime/handlers/sse/run.mjs +3 -1
  132. package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
  133. package/dist/v2/runtime/index.d.cts +1 -1
  134. package/dist/v2/runtime/index.d.mts +1 -1
  135. package/package.json +7 -6
  136. package/src/agent/__tests__/capabilities.test.ts +81 -0
  137. package/src/agent/__tests__/provider-id-collision.test.ts +195 -0
  138. package/src/agent/converters/tanstack.ts +15 -7
  139. package/src/agent/index.ts +52 -11
  140. package/src/lib/integrations/node-http/__tests__/request-duck-type.test.ts +66 -0
  141. package/src/lib/integrations/node-http/index.ts +15 -1
  142. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +30 -1
  143. package/src/lib/runtime/__tests__/on-after-request.test.ts +122 -0
  144. package/src/lib/runtime/__tests__/v1-agent-factory.test.ts +109 -0
  145. package/src/lib/runtime/copilot-runtime.ts +54 -5
  146. package/src/lib/runtime/mcp-tools-utils.ts +41 -6
  147. package/src/service-adapters/anthropic/anthropic-adapter.ts +22 -2
  148. package/src/service-adapters/anthropic/utils.ts +60 -1
  149. package/src/service-adapters/langchain/utils.ts +1 -1
  150. package/src/service-adapters/openai/__tests__/openai-v5-compat.test.ts +177 -0
  151. package/src/service-adapters/openai/openai-adapter.ts +17 -2
  152. package/src/service-adapters/openai/openai-assistant-adapter.ts +7 -9
  153. package/src/service-adapters/openai/utils.ts +100 -0
  154. package/src/v2/runtime/__tests__/agents-factory.test.ts +136 -0
  155. package/src/v2/runtime/__tests__/debug-sse-response.test.ts +302 -0
  156. package/src/v2/runtime/__tests__/get-runtime-info.test.ts +134 -1
  157. package/src/v2/runtime/__tests__/middleware-sse-parser.test.ts +50 -0
  158. package/src/v2/runtime/core/middleware-sse-parser.ts +12 -2
  159. package/src/v2/runtime/core/runtime.ts +90 -2
  160. package/src/v2/runtime/handlers/get-runtime-info.ts +33 -8
  161. package/src/v2/runtime/handlers/handle-connect.ts +1 -1
  162. package/src/v2/runtime/handlers/handle-run.ts +16 -2
  163. package/src/v2/runtime/handlers/handle-stop.ts +2 -1
  164. package/src/v2/runtime/handlers/intelligence/thread-names.ts +1 -1
  165. package/src/v2/runtime/handlers/shared/agent-utils.ts +3 -2
  166. package/src/v2/runtime/handlers/shared/sse-response.ts +69 -0
  167. package/src/v2/runtime/handlers/sse/run.ts +9 -0
  168. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +268 -0
  169. 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: "array",
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?: MaybePromise<NonEmptyRecord<Record<string, AbstractAgent>>>;
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: { ...endpointAgents, ...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
- // TODO: provide old expected params here when available
594
- // @ts-expect-error -- missing arguments.
595
- params.middleware.onAfterRequest({});
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
- const enumValues = paramDef.enum.join(" | ");
91
+ enumValues = paramDef.enum.map(String);
92
+ const enumDisplay = enumValues.join(" | ");
91
93
  description =
92
94
  description +
93
95
  (description ? " " : "") +
94
- `Allowed values: ${enumValues}`;
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
- parameters.push({
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 || 1024,
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
- return result;
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 = value.lc_kwargs?.id || randomId();
272
+ currentMessageId = randomId();
273
273
  eventStream$.sendTextMessageStart({ messageId: currentMessageId });
274
274
  }
275
275
  }