@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.
Files changed (194) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.d.cts.map +1 -1
  4. package/dist/agent/index.d.mts.map +1 -1
  5. package/dist/agent/index.mjs +2 -2
  6. package/dist/agent/index.mjs.map +1 -1
  7. package/dist/graphql/resolvers/copilot.resolver.cjs +2 -1
  8. package/dist/graphql/resolvers/copilot.resolver.cjs.map +1 -1
  9. package/dist/graphql/resolvers/copilot.resolver.mjs +2 -1
  10. package/dist/graphql/resolvers/copilot.resolver.mjs.map +1 -1
  11. package/dist/graphql/resolvers/resolve-message-id.cjs +19 -0
  12. package/dist/graphql/resolvers/resolve-message-id.cjs.map +1 -0
  13. package/dist/graphql/resolvers/resolve-message-id.mjs +18 -0
  14. package/dist/graphql/resolvers/resolve-message-id.mjs.map +1 -0
  15. package/dist/lib/integrations/node-http/index.cjs +4 -1
  16. package/dist/lib/integrations/node-http/index.cjs.map +1 -1
  17. package/dist/lib/integrations/node-http/index.d.cts.map +1 -1
  18. package/dist/lib/integrations/node-http/index.d.mts.map +1 -1
  19. package/dist/lib/integrations/node-http/index.mjs +4 -1
  20. package/dist/lib/integrations/node-http/index.mjs.map +1 -1
  21. package/dist/lib/runtime/copilot-runtime.cjs +15 -3
  22. package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
  23. package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
  24. package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
  25. package/dist/lib/runtime/copilot-runtime.mjs +15 -3
  26. package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
  27. package/dist/lib/runtime/mcp-tools-utils.cjs +21 -4
  28. package/dist/lib/runtime/mcp-tools-utils.cjs.map +1 -1
  29. package/dist/lib/runtime/mcp-tools-utils.d.cts.map +1 -1
  30. package/dist/lib/runtime/mcp-tools-utils.d.mts.map +1 -1
  31. package/dist/lib/runtime/mcp-tools-utils.mjs +21 -4
  32. package/dist/lib/runtime/mcp-tools-utils.mjs.map +1 -1
  33. package/dist/package.cjs +2 -2
  34. package/dist/package.mjs +2 -2
  35. package/dist/service-adapters/anthropic/anthropic-adapter.cjs +11 -3
  36. package/dist/service-adapters/anthropic/anthropic-adapter.cjs.map +1 -1
  37. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts +6 -0
  38. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts.map +1 -1
  39. package/dist/service-adapters/anthropic/anthropic-adapter.d.mts +6 -0
  40. package/dist/service-adapters/anthropic/anthropic-adapter.d.mts.map +1 -1
  41. package/dist/service-adapters/anthropic/anthropic-adapter.mjs +11 -3
  42. package/dist/service-adapters/anthropic/anthropic-adapter.mjs.map +1 -1
  43. package/dist/service-adapters/anthropic/utils.cjs +27 -1
  44. package/dist/service-adapters/anthropic/utils.cjs.map +1 -1
  45. package/dist/service-adapters/anthropic/utils.mjs +27 -1
  46. package/dist/service-adapters/anthropic/utils.mjs.map +1 -1
  47. package/dist/service-adapters/langchain/utils.cjs +1 -1
  48. package/dist/service-adapters/langchain/utils.cjs.map +1 -1
  49. package/dist/service-adapters/langchain/utils.mjs +1 -1
  50. package/dist/service-adapters/langchain/utils.mjs.map +1 -1
  51. package/dist/service-adapters/openai/openai-adapter.cjs +2 -1
  52. package/dist/service-adapters/openai/openai-adapter.cjs.map +1 -1
  53. package/dist/service-adapters/openai/openai-adapter.d.cts +6 -0
  54. package/dist/service-adapters/openai/openai-adapter.d.cts.map +1 -1
  55. package/dist/service-adapters/openai/openai-adapter.d.mts +6 -0
  56. package/dist/service-adapters/openai/openai-adapter.d.mts.map +1 -1
  57. package/dist/service-adapters/openai/openai-adapter.mjs +2 -1
  58. package/dist/service-adapters/openai/openai-adapter.mjs.map +1 -1
  59. package/dist/v2/runtime/core/debug-event-bus.cjs +36 -0
  60. package/dist/v2/runtime/core/debug-event-bus.cjs.map +1 -0
  61. package/dist/v2/runtime/core/debug-event-bus.d.cts +19 -0
  62. package/dist/v2/runtime/core/debug-event-bus.d.cts.map +1 -0
  63. package/dist/v2/runtime/core/debug-event-bus.d.mts +19 -0
  64. package/dist/v2/runtime/core/debug-event-bus.d.mts.map +1 -0
  65. package/dist/v2/runtime/core/debug-event-bus.mjs +35 -0
  66. package/dist/v2/runtime/core/debug-event-bus.mjs.map +1 -0
  67. package/dist/v2/runtime/core/fetch-handler.cjs +6 -0
  68. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  69. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  70. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  71. package/dist/v2/runtime/core/fetch-handler.mjs +6 -0
  72. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  73. package/dist/v2/runtime/core/fetch-router.cjs +1 -0
  74. package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
  75. package/dist/v2/runtime/core/fetch-router.mjs +1 -0
  76. package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
  77. package/dist/v2/runtime/core/hooks.cjs.map +1 -1
  78. package/dist/v2/runtime/core/hooks.d.cts +2 -0
  79. package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
  80. package/dist/v2/runtime/core/hooks.d.mts +2 -0
  81. package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
  82. package/dist/v2/runtime/core/hooks.mjs.map +1 -1
  83. package/dist/v2/runtime/core/middleware-sse-parser.cjs +5 -2
  84. package/dist/v2/runtime/core/middleware-sse-parser.cjs.map +1 -1
  85. package/dist/v2/runtime/core/middleware-sse-parser.mjs +5 -2
  86. package/dist/v2/runtime/core/middleware-sse-parser.mjs.map +1 -1
  87. package/dist/v2/runtime/core/runtime.cjs +5 -0
  88. package/dist/v2/runtime/core/runtime.cjs.map +1 -1
  89. package/dist/v2/runtime/core/runtime.d.cts +5 -0
  90. package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
  91. package/dist/v2/runtime/core/runtime.d.mts +5 -0
  92. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  93. package/dist/v2/runtime/core/runtime.mjs +5 -0
  94. package/dist/v2/runtime/core/runtime.mjs.map +1 -1
  95. package/dist/v2/runtime/handlers/handle-connect.cjs +2 -0
  96. package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
  97. package/dist/v2/runtime/handlers/handle-connect.mjs +2 -0
  98. package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
  99. package/dist/v2/runtime/handlers/handle-debug-events.cjs +33 -0
  100. package/dist/v2/runtime/handlers/handle-debug-events.cjs.map +1 -0
  101. package/dist/v2/runtime/handlers/handle-debug-events.mjs +32 -0
  102. package/dist/v2/runtime/handlers/handle-debug-events.mjs.map +1 -0
  103. package/dist/v2/runtime/handlers/handle-run.cjs +1 -0
  104. package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
  105. package/dist/v2/runtime/handlers/handle-run.mjs +1 -0
  106. package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
  107. package/dist/v2/runtime/handlers/intelligence/connect.cjs +32 -2
  108. package/dist/v2/runtime/handlers/intelligence/connect.cjs.map +1 -1
  109. package/dist/v2/runtime/handlers/intelligence/connect.mjs +31 -2
  110. package/dist/v2/runtime/handlers/intelligence/connect.mjs.map +1 -1
  111. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs +5 -1
  112. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs.map +1 -1
  113. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs +5 -1
  114. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs.map +1 -1
  115. package/dist/v2/runtime/handlers/shared/sse-response.cjs +21 -1
  116. package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
  117. package/dist/v2/runtime/handlers/shared/sse-response.mjs +21 -1
  118. package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
  119. package/dist/v2/runtime/handlers/sse/connect.cjs +3 -1
  120. package/dist/v2/runtime/handlers/sse/connect.cjs.map +1 -1
  121. package/dist/v2/runtime/handlers/sse/connect.mjs +3 -1
  122. package/dist/v2/runtime/handlers/sse/connect.mjs.map +1 -1
  123. package/dist/v2/runtime/handlers/sse/run.cjs +3 -1
  124. package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
  125. package/dist/v2/runtime/handlers/sse/run.mjs +3 -1
  126. package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
  127. package/dist/v2/runtime/intelligence-platform/client.cjs +2 -7
  128. package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
  129. package/dist/v2/runtime/intelligence-platform/client.d.cts +1 -4
  130. package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
  131. package/dist/v2/runtime/intelligence-platform/client.d.mts +1 -4
  132. package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
  133. package/dist/v2/runtime/intelligence-platform/client.mjs +2 -7
  134. package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
  135. package/dist/v2/runtime/runner/intelligence.cjs +17 -5
  136. package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
  137. package/dist/v2/runtime/runner/intelligence.d.cts +1 -0
  138. package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
  139. package/dist/v2/runtime/runner/intelligence.d.mts +1 -0
  140. package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
  141. package/dist/v2/runtime/runner/intelligence.mjs +17 -5
  142. package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
  143. package/package.json +3 -3
  144. package/src/agent/__tests__/provider-id-collision.test.ts +195 -0
  145. package/src/agent/index.ts +19 -11
  146. package/src/agents/langgraph/__tests__/event-source.test.ts +256 -0
  147. package/src/graphql/resolvers/__tests__/resolve-message-id.test.ts +25 -0
  148. package/src/graphql/resolvers/copilot.resolver.ts +2 -1
  149. package/src/graphql/resolvers/resolve-message-id.ts +14 -0
  150. package/src/lib/integrations/node-http/__tests__/request-duck-type.test.ts +66 -0
  151. package/src/lib/integrations/node-http/index.ts +15 -1
  152. package/src/lib/runtime/__tests__/handle-service-adapter.test.ts +108 -0
  153. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +30 -1
  154. package/src/lib/runtime/__tests__/on-after-request.test.ts +122 -0
  155. package/src/lib/runtime/__tests__/retry-utils.test.ts +137 -0
  156. package/src/lib/runtime/agent-integrations/langgraph/__tests__/dispatch-event-filtering.test.ts +190 -0
  157. package/src/lib/runtime/copilot-runtime.ts +36 -7
  158. package/src/lib/runtime/mcp-tools-utils.ts +41 -6
  159. package/src/lib/runtime/retry-utils.ts +41 -1
  160. package/src/service-adapters/anthropic/anthropic-adapter.ts +22 -2
  161. package/src/service-adapters/anthropic/utils.ts +60 -1
  162. package/src/service-adapters/langchain/utils.ts +1 -1
  163. package/src/service-adapters/openai/openai-adapter.ts +14 -1
  164. package/src/v2/runtime/__tests__/fetch-router.test.ts +22 -0
  165. package/src/v2/runtime/__tests__/handle-connect.test.ts +58 -5
  166. package/src/v2/runtime/__tests__/handle-run.test.ts +31 -4
  167. package/src/v2/runtime/__tests__/handle-threads.test.ts +66 -4
  168. package/src/v2/runtime/__tests__/integration/node-servers.integration.test.ts +19 -0
  169. package/src/v2/runtime/__tests__/integration/suites/debug-events.suite.ts +253 -0
  170. package/src/v2/runtime/__tests__/middleware-sse-parser.test.ts +50 -0
  171. package/src/v2/runtime/__tests__/runtime.test.ts +3 -1
  172. package/src/v2/runtime/core/__tests__/debug-event-bus.test.ts +156 -0
  173. package/src/v2/runtime/core/debug-event-bus.ts +45 -0
  174. package/src/v2/runtime/core/fetch-handler.ts +4 -0
  175. package/src/v2/runtime/core/fetch-router.ts +11 -0
  176. package/src/v2/runtime/core/hooks.ts +2 -1
  177. package/src/v2/runtime/core/middleware-sse-parser.ts +12 -2
  178. package/src/v2/runtime/core/runtime.ts +12 -0
  179. package/src/v2/runtime/handlers/__tests__/handle-debug-events.test.ts +176 -0
  180. package/src/v2/runtime/handlers/handle-connect.ts +2 -0
  181. package/src/v2/runtime/handlers/handle-debug-events.ts +52 -0
  182. package/src/v2/runtime/handlers/handle-run.ts +1 -0
  183. package/src/v2/runtime/handlers/intelligence/connect.ts +58 -1
  184. package/src/v2/runtime/handlers/shared/resolve-intelligence-user.ts +4 -1
  185. package/src/v2/runtime/handlers/shared/sse-response.ts +46 -0
  186. package/src/v2/runtime/handlers/sse/__tests__/sse-connect-agent-id.test.ts +71 -0
  187. package/src/v2/runtime/handlers/sse/connect.ts +6 -0
  188. package/src/v2/runtime/handlers/sse/run.ts +4 -0
  189. package/src/v2/runtime/intelligence-platform/__tests__/client.test.ts +13 -11
  190. package/src/v2/runtime/intelligence-platform/client.ts +3 -11
  191. package/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts +51 -1
  192. package/src/v2/runtime/runner/intelligence.ts +27 -9
  193. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +268 -0
  194. package/tests/service-adapters/anthropic/utils-token-trimming.test.ts +301 -0
@@ -0,0 +1,195 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { BuiltInAgent } from "../index";
3
+ import { EventType, type RunAgentInput } from "@ag-ui/client";
4
+ import { streamText } from "ai";
5
+ import {
6
+ mockStreamTextResponse,
7
+ textDelta,
8
+ finish,
9
+ collectEvents,
10
+ } from "./test-helpers";
11
+
12
+ // Mock the ai module
13
+ vi.mock("ai", () => ({
14
+ streamText: vi.fn(),
15
+ tool: vi.fn((config) => config),
16
+ stepCountIs: vi.fn((count: number) => ({ type: "stepCount", count })),
17
+ }));
18
+
19
+ vi.mock("@ai-sdk/openai", () => ({
20
+ createOpenAI: vi.fn(() => (modelId: string) => ({
21
+ specificationVersion: "v3",
22
+ modelId,
23
+ provider: "openai",
24
+ })),
25
+ }));
26
+
27
+ vi.mock("@ai-sdk/anthropic", () => ({
28
+ createAnthropic: vi.fn(() => (modelId: string) => ({
29
+ specificationVersion: "v3",
30
+ modelId,
31
+ provider: "anthropic",
32
+ })),
33
+ }));
34
+
35
+ vi.mock("@ai-sdk/google", () => ({
36
+ createGoogleGenerativeAI: vi.fn(() => (modelId: string) => ({
37
+ specificationVersion: "v3",
38
+ modelId,
39
+ provider: "google",
40
+ })),
41
+ }));
42
+
43
+ describe("Provider ID collision (#3410, #3623)", () => {
44
+ const originalEnv = process.env;
45
+
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ process.env = { ...originalEnv };
49
+ process.env.OPENAI_API_KEY = "test-key";
50
+ });
51
+
52
+ afterEach(() => {
53
+ process.env = originalEnv;
54
+ });
55
+
56
+ it('should replace text-start providedId "txt-0" with a UUID', async () => {
57
+ const agent = new BuiltInAgent({ model: "openai:gpt-4o-mini" });
58
+
59
+ vi.mocked(streamText).mockReturnValue(
60
+ mockStreamTextResponse([
61
+ { type: "text-start", id: "txt-0" },
62
+ textDelta("Hello"),
63
+ finish(),
64
+ ]) as any,
65
+ );
66
+
67
+ const input: RunAgentInput = {
68
+ threadId: "thread-1",
69
+ runId: "run-1",
70
+ messages: [{ id: "1", role: "user", content: "Hi" }],
71
+ tools: [],
72
+ context: [],
73
+ state: {},
74
+ };
75
+
76
+ const events = await collectEvents(agent["run"](input));
77
+
78
+ // Find the TEXT_MESSAGE_CHUNK event and check its messageId
79
+ const textChunks = events.filter(
80
+ (e) => e.type === EventType.TEXT_MESSAGE_CHUNK,
81
+ );
82
+ expect(textChunks.length).toBeGreaterThan(0);
83
+ const messageId = (textChunks[0] as any).messageId;
84
+
85
+ // The messageId should NOT be "txt-0" — it should be a UUID
86
+ expect(messageId).not.toBe("txt-0");
87
+ // UUID v4 pattern
88
+ expect(messageId).toMatch(
89
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
90
+ );
91
+ });
92
+
93
+ it('should replace reasoning-start providedId "reasoning-0" with a UUID', async () => {
94
+ const agent = new BuiltInAgent({ model: "openai:gpt-4o-mini" });
95
+
96
+ vi.mocked(streamText).mockReturnValue(
97
+ mockStreamTextResponse([
98
+ { type: "reasoning-start", id: "reasoning-0" },
99
+ { type: "reasoning-delta", text: "Thinking..." },
100
+ { type: "reasoning-end" },
101
+ { type: "text-start", id: "txt-0" },
102
+ textDelta("Answer"),
103
+ finish(),
104
+ ]) as any,
105
+ );
106
+
107
+ const input: RunAgentInput = {
108
+ threadId: "thread-2",
109
+ runId: "run-2",
110
+ messages: [{ id: "1", role: "user", content: "Hi" }],
111
+ tools: [],
112
+ context: [],
113
+ state: {},
114
+ };
115
+
116
+ const events = await collectEvents(agent["run"](input));
117
+
118
+ // Find REASONING_START event
119
+ const reasoningStarts = events.filter(
120
+ (e) => e.type === EventType.REASONING_START,
121
+ );
122
+ expect(reasoningStarts.length).toBeGreaterThan(0);
123
+ const reasoningId = (reasoningStarts[0] as any).messageId;
124
+
125
+ // Should NOT be "reasoning-0"
126
+ expect(reasoningId).not.toBe("reasoning-0");
127
+ expect(reasoningId).toMatch(
128
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
129
+ );
130
+ });
131
+
132
+ it('should replace providedId "msg-0" with a UUID', async () => {
133
+ const agent = new BuiltInAgent({ model: "openai:gpt-4o-mini" });
134
+
135
+ vi.mocked(streamText).mockReturnValue(
136
+ mockStreamTextResponse([
137
+ { type: "text-start", id: "msg-0" },
138
+ textDelta("Hello"),
139
+ finish(),
140
+ ]) as any,
141
+ );
142
+
143
+ const input: RunAgentInput = {
144
+ threadId: "thread-3",
145
+ runId: "run-3",
146
+ messages: [{ id: "1", role: "user", content: "Hi" }],
147
+ tools: [],
148
+ context: [],
149
+ state: {},
150
+ };
151
+
152
+ const events = await collectEvents(agent["run"](input));
153
+
154
+ const textChunks = events.filter(
155
+ (e) => e.type === EventType.TEXT_MESSAGE_CHUNK,
156
+ );
157
+ expect(textChunks.length).toBeGreaterThan(0);
158
+ const messageId = (textChunks[0] as any).messageId;
159
+
160
+ expect(messageId).not.toBe("msg-0");
161
+ expect(messageId).toMatch(
162
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
163
+ );
164
+ });
165
+
166
+ it("should preserve legitimate provider IDs", async () => {
167
+ const agent = new BuiltInAgent({ model: "openai:gpt-4o-mini" });
168
+
169
+ vi.mocked(streamText).mockReturnValue(
170
+ mockStreamTextResponse([
171
+ { type: "text-start", id: "custom-msg-id-123" },
172
+ textDelta("Hello"),
173
+ finish(),
174
+ ]) as any,
175
+ );
176
+
177
+ const input: RunAgentInput = {
178
+ threadId: "thread-4",
179
+ runId: "run-4",
180
+ messages: [{ id: "1", role: "user", content: "Hi" }],
181
+ tools: [],
182
+ context: [],
183
+ state: {},
184
+ };
185
+
186
+ const events = await collectEvents(agent["run"](input));
187
+
188
+ const textChunks = events.filter(
189
+ (e) => e.type === EventType.TEXT_MESSAGE_CHUNK,
190
+ );
191
+ expect(textChunks.length).toBeGreaterThan(0);
192
+ // Legitimate IDs should be preserved
193
+ expect((textChunks[0] as any).messageId).toBe("custom-msg-id-123");
194
+ });
195
+ });
@@ -1267,13 +1267,17 @@ export class BuiltInAgent extends AbstractAgent {
1267
1267
  break;
1268
1268
  }
1269
1269
  case "reasoning-start": {
1270
- // Use SDK-provided id, or generate a fresh UUID if id is falsy/"0"
1271
- // to prevent consecutive reasoning blocks from sharing a messageId
1270
+ // Use SDK-provided id, or generate a fresh UUID if the id is falsy,
1271
+ // "0", or matches the non-unique pattern emitted by @ai-sdk/openai-compatible
1272
+ // (e.g. "txt-0", "reasoning-0", "msg-0").
1272
1273
  const providedId = "id" in part ? part.id : undefined;
1273
- reasoningMessageId =
1274
- providedId && providedId !== "0"
1275
- ? (providedId as typeof reasoningMessageId)
1276
- : randomUUID();
1274
+ const isNonUniqueId =
1275
+ !providedId ||
1276
+ providedId === "0" ||
1277
+ /^(txt|reasoning|msg)-0$/.test(providedId);
1278
+ reasoningMessageId = isNonUniqueId
1279
+ ? randomUUID()
1280
+ : (providedId as typeof reasoningMessageId);
1277
1281
  const reasoningStartEvent: ReasoningStartEvent = {
1278
1282
  type: EventType.REASONING_START,
1279
1283
  messageId: reasoningMessageId,
@@ -1341,12 +1345,16 @@ export class BuiltInAgent extends AbstractAgent {
1341
1345
 
1342
1346
  case "text-start": {
1343
1347
  // New text message starting - use the SDK-provided id
1344
- // Use randomUUID() if part.id is falsy or "0" to prevent message merging issues
1348
+ // Use randomUUID() if part.id is falsy, "0", or matches the non-unique
1349
+ // pattern emitted by @ai-sdk/openai-compatible (e.g. "txt-0", "msg-0").
1345
1350
  const providedId = "id" in part ? part.id : undefined;
1346
- messageId =
1347
- providedId && providedId !== "0"
1348
- ? (providedId as typeof messageId)
1349
- : randomUUID();
1351
+ const isNonUniqueTextId =
1352
+ !providedId ||
1353
+ providedId === "0" ||
1354
+ /^(txt|reasoning|msg)-0$/.test(providedId);
1355
+ messageId = isNonUniqueTextId
1356
+ ? randomUUID()
1357
+ : (providedId as typeof messageId);
1350
1358
  break;
1351
1359
  }
1352
1360
 
@@ -0,0 +1,256 @@
1
+ import { RemoteLangGraphEventSource } from "../event-source";
2
+ import { LangGraphEventTypes } from "../events";
3
+
4
+ /**
5
+ * Access private methods for testing via a helper.
6
+ * These are pure functions that extract data from LangGraph events.
7
+ */
8
+ function getSource() {
9
+ return new RemoteLangGraphEventSource();
10
+ }
11
+
12
+ // Helper to call private methods. Returns `any` — type safety is traded for
13
+ // access to private implementation details that have no public test surface.
14
+ function callPrivate(
15
+ source: RemoteLangGraphEventSource,
16
+ method: string,
17
+ ...args: any[]
18
+ ) {
19
+ return (source as any)[method](...args);
20
+ }
21
+
22
+ describe("shouldEmitToolCall", () => {
23
+ const source = getSource();
24
+
25
+ it("returns true when shouldEmitToolCalls is true (boolean)", () => {
26
+ expect(callPrivate(source, "shouldEmitToolCall", true, "anyTool")).toBe(
27
+ true,
28
+ );
29
+ });
30
+
31
+ it("returns false when shouldEmitToolCalls is false (boolean)", () => {
32
+ expect(callPrivate(source, "shouldEmitToolCall", false, "anyTool")).toBe(
33
+ false,
34
+ );
35
+ });
36
+
37
+ it("returns true when tool name matches string", () => {
38
+ expect(
39
+ callPrivate(source, "shouldEmitToolCall", "SearchTool", "SearchTool"),
40
+ ).toBe(true);
41
+ });
42
+
43
+ it("returns false when tool name does not match string", () => {
44
+ expect(
45
+ callPrivate(source, "shouldEmitToolCall", "SearchTool", "OtherTool"),
46
+ ).toBe(false);
47
+ });
48
+
49
+ it("returns true when tool name is in array", () => {
50
+ expect(
51
+ callPrivate(
52
+ source,
53
+ "shouldEmitToolCall",
54
+ ["SearchTool", "FetchTool"],
55
+ "FetchTool",
56
+ ),
57
+ ).toBe(true);
58
+ });
59
+
60
+ it("returns false when tool name is not in array", () => {
61
+ expect(
62
+ callPrivate(
63
+ source,
64
+ "shouldEmitToolCall",
65
+ ["SearchTool", "FetchTool"],
66
+ "OtherTool",
67
+ ),
68
+ ).toBe(false);
69
+ });
70
+ });
71
+
72
+ describe("getCurrentMessageId", () => {
73
+ const source = getSource();
74
+
75
+ it("extracts id from standard kwargs layout", () => {
76
+ const event = {
77
+ event: LangGraphEventTypes.OnChatModelStream,
78
+ data: { chunk: { kwargs: { id: "msg-std-123" } } },
79
+ };
80
+ expect(callPrivate(source, "getCurrentMessageId", event)).toBe(
81
+ "msg-std-123",
82
+ );
83
+ });
84
+
85
+ it("extracts id from LangGraph Platform layout (no kwargs)", () => {
86
+ const event = {
87
+ event: LangGraphEventTypes.OnChatModelStream,
88
+ data: { chunk: { id: "msg-plat-456" } },
89
+ };
90
+ expect(callPrivate(source, "getCurrentMessageId", event)).toBe(
91
+ "msg-plat-456",
92
+ );
93
+ });
94
+
95
+ it("prefers kwargs layout when both present", () => {
96
+ const event = {
97
+ event: LangGraphEventTypes.OnChatModelStream,
98
+ data: { chunk: { kwargs: { id: "kwargs-id" }, id: "direct-id" } },
99
+ };
100
+ expect(callPrivate(source, "getCurrentMessageId", event)).toBe("kwargs-id");
101
+ });
102
+
103
+ it("returns undefined when neither layout is present", () => {
104
+ const event = {
105
+ event: LangGraphEventTypes.OnChatModelStream,
106
+ data: { chunk: {} },
107
+ };
108
+ expect(callPrivate(source, "getCurrentMessageId", event)).toBeUndefined();
109
+ });
110
+
111
+ it("handles missing data gracefully", () => {
112
+ const event = { event: LangGraphEventTypes.OnChatModelStream };
113
+ expect(callPrivate(source, "getCurrentMessageId", event)).toBeUndefined();
114
+ });
115
+ });
116
+
117
+ describe("getCurrentToolCallChunks", () => {
118
+ const source = getSource();
119
+
120
+ it("extracts chunks from standard kwargs layout", () => {
121
+ const chunks = [{ name: "tool1", args: "{}", id: "tc-1", index: 0 }];
122
+ const event = {
123
+ event: LangGraphEventTypes.OnChatModelStream,
124
+ data: { chunk: { kwargs: { tool_call_chunks: chunks } } },
125
+ };
126
+ expect(callPrivate(source, "getCurrentToolCallChunks", event)).toEqual(
127
+ chunks,
128
+ );
129
+ });
130
+
131
+ it("extracts chunks from LangGraph Platform layout", () => {
132
+ const chunks = [{ name: "tool2", args: "{}", id: "tc-2", index: 0 }];
133
+ const event = {
134
+ event: LangGraphEventTypes.OnChatModelStream,
135
+ data: { chunk: { tool_call_chunks: chunks } },
136
+ };
137
+ expect(callPrivate(source, "getCurrentToolCallChunks", event)).toEqual(
138
+ chunks,
139
+ );
140
+ });
141
+
142
+ it("returns undefined when no chunks present", () => {
143
+ const event = {
144
+ event: LangGraphEventTypes.OnChatModelStream,
145
+ data: { chunk: {} },
146
+ };
147
+ expect(
148
+ callPrivate(source, "getCurrentToolCallChunks", event),
149
+ ).toBeUndefined();
150
+ });
151
+ });
152
+
153
+ describe("getResponseMetadata", () => {
154
+ const source = getSource();
155
+
156
+ it("extracts metadata from standard kwargs layout", () => {
157
+ const meta = { finish_reason: "stop" };
158
+ const event = {
159
+ event: LangGraphEventTypes.OnChatModelStream,
160
+ data: { chunk: { kwargs: { response_metadata: meta } } },
161
+ };
162
+ expect(callPrivate(source, "getResponseMetadata", event)).toEqual(meta);
163
+ });
164
+
165
+ it("extracts metadata from LangGraph Platform layout", () => {
166
+ const meta = { finish_reason: "tool_calls" };
167
+ const event = {
168
+ event: LangGraphEventTypes.OnChatModelStream,
169
+ data: { chunk: { response_metadata: meta } },
170
+ };
171
+ expect(callPrivate(source, "getResponseMetadata", event)).toEqual(meta);
172
+ });
173
+
174
+ it("returns undefined when no metadata present", () => {
175
+ const event = {
176
+ event: LangGraphEventTypes.OnChatModelStream,
177
+ data: { chunk: {} },
178
+ };
179
+ expect(callPrivate(source, "getResponseMetadata", event)).toBeUndefined();
180
+ });
181
+ });
182
+
183
+ describe("getCurrentContent", () => {
184
+ const source = getSource();
185
+
186
+ it("extracts string content from kwargs layout", () => {
187
+ const event = {
188
+ event: LangGraphEventTypes.OnChatModelStream,
189
+ data: { chunk: { kwargs: { content: "hello world" } } },
190
+ };
191
+ expect(callPrivate(source, "getCurrentContent", event)).toBe("hello world");
192
+ });
193
+
194
+ it("extracts string content from Platform layout", () => {
195
+ const event = {
196
+ event: LangGraphEventTypes.OnChatModelStream,
197
+ data: { chunk: { content: "platform content" } },
198
+ };
199
+ expect(callPrivate(source, "getCurrentContent", event)).toBe(
200
+ "platform content",
201
+ );
202
+ });
203
+
204
+ it("extracts text from array content (Anthropic format)", () => {
205
+ const event = {
206
+ event: LangGraphEventTypes.OnChatModelStream,
207
+ data: {
208
+ chunk: {
209
+ kwargs: {
210
+ content: [{ text: "array text", type: "text", index: 0 }],
211
+ },
212
+ },
213
+ },
214
+ };
215
+ expect(callPrivate(source, "getCurrentContent", event)).toBe("array text");
216
+ });
217
+
218
+ it("returns null when no content and no tool call chunks", () => {
219
+ const event = {
220
+ event: LangGraphEventTypes.OnChatModelStream,
221
+ data: { chunk: { kwargs: {} } },
222
+ };
223
+ expect(callPrivate(source, "getCurrentContent", event)).toBeNull();
224
+ });
225
+
226
+ it("falls back to tool_call_chunks args when no content", () => {
227
+ const event = {
228
+ event: LangGraphEventTypes.OnChatModelStream,
229
+ data: {
230
+ chunk: {
231
+ kwargs: {
232
+ content: "",
233
+ tool_call_chunks: [{ args: '{"key":"val"}' }],
234
+ },
235
+ },
236
+ },
237
+ };
238
+ expect(callPrivate(source, "getCurrentContent", event)).toBe(
239
+ '{"key":"val"}',
240
+ );
241
+ });
242
+
243
+ it("handles missing data gracefully", () => {
244
+ const event = { event: LangGraphEventTypes.OnChatModelStream };
245
+ expect(callPrivate(source, "getCurrentContent", event)).toBeNull();
246
+ });
247
+
248
+ it("returns empty string when content is empty string", () => {
249
+ const event = {
250
+ event: LangGraphEventTypes.OnChatModelStream,
251
+ data: { chunk: { kwargs: { content: "" } } },
252
+ };
253
+ // Empty string is a valid string, so typeof === "string" returns it as-is
254
+ expect(callPrivate(source, "getCurrentContent", event)).toBe("");
255
+ });
256
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveMessageId } from "../resolve-message-id";
3
+
4
+ describe("resolveMessageId (#2118)", () => {
5
+ it("preserves a provided non-empty id verbatim", () => {
6
+ expect(resolveMessageId("msg-123")).toBe("msg-123");
7
+ });
8
+
9
+ it.each(["", null, undefined] as const)(
10
+ "falls back to a generated id when the event id is %p",
11
+ (input) => {
12
+ const id = resolveMessageId(input);
13
+ // randomId() always produces the "ck-<uuid>" shape; the important
14
+ // contract for #2118 is that the returned value is a non-empty string,
15
+ // never null/undefined.
16
+ expect(id).toMatch(/^ck-[0-9a-f-]{36}$/);
17
+ },
18
+ );
19
+
20
+ it("generates a fresh id on each fallback call", () => {
21
+ const a = resolveMessageId(undefined);
22
+ const b = resolveMessageId(undefined);
23
+ expect(a).not.toBe(b);
24
+ });
25
+ });
@@ -58,6 +58,7 @@ import {
58
58
  } from "../types/converted";
59
59
  import telemetry from "../../lib/telemetry-client";
60
60
  import { randomId } from "@copilotkit/shared";
61
+ import { resolveMessageId } from "./resolve-message-id";
61
62
  import { AgentsResponse } from "../types/agents-response.type";
62
63
  import { LangGraphEventTypes } from "../../agents/langgraph/events";
63
64
  import {
@@ -488,7 +489,7 @@ export class CopilotResolver {
488
489
  typeof MessageStatusUnion
489
490
  >();
490
491
 
491
- const messageId = event.messageId;
492
+ const messageId = resolveMessageId(event.messageId);
492
493
  // push the new message
493
494
  pushMessage({
494
495
  id: messageId,
@@ -0,0 +1,14 @@
1
+ import { randomId } from "@copilotkit/shared";
2
+
3
+ /**
4
+ * Resolve the id to use for a streamed TextMessageOutput.
5
+ *
6
+ * Upstream events occasionally arrive with `messageId` missing, null, or an
7
+ * empty string. We fall back to a freshly generated id in those cases so the
8
+ * resulting output never surfaces a null id to the GraphQL client (#2118).
9
+ */
10
+ export function resolveMessageId(
11
+ eventMessageId: string | null | undefined,
12
+ ): string {
13
+ return eventMessageId || randomId();
14
+ }
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ /**
4
+ * Test for #2986: instanceof Request fails with @hono/node-server polyfill
5
+ *
6
+ * When Hono polyfills the Request class, `instanceof Request` fails because
7
+ * the polyfilled Request has a different prototype. We need duck-type checking.
8
+ */
9
+
10
+ // Simulates a polyfilled Request object that does NOT pass instanceof Request
11
+ function createPolyfillRequest(url: string, method: string = "GET"): object {
12
+ return {
13
+ url,
14
+ method,
15
+ headers: new Headers({ "content-type": "application/json" }),
16
+ body: null,
17
+ clone: () => createPolyfillRequest(url, method),
18
+ };
19
+ }
20
+
21
+ // This is the duck-type check that should replace instanceof
22
+ function isRequestLike(obj: unknown): obj is Request {
23
+ return (
24
+ typeof obj === "object" &&
25
+ obj !== null &&
26
+ "url" in obj &&
27
+ "method" in obj &&
28
+ "headers" in obj &&
29
+ typeof (obj as any).url === "string" &&
30
+ typeof (obj as any).method === "string"
31
+ );
32
+ }
33
+
34
+ describe("Request duck-type detection (#2986)", () => {
35
+ it("should detect a native Request object", () => {
36
+ const req = new Request("http://localhost:3000/api/copilotkit", {
37
+ method: "POST",
38
+ });
39
+ expect(isRequestLike(req)).toBe(true);
40
+ expect(req instanceof Request).toBe(true);
41
+ });
42
+
43
+ it("should detect a polyfilled Request object that fails instanceof", () => {
44
+ const polyfilled = createPolyfillRequest(
45
+ "http://localhost:3000/api/copilotkit",
46
+ "POST",
47
+ );
48
+
49
+ // instanceof fails for polyfilled objects
50
+ expect(polyfilled instanceof Request).toBe(false);
51
+
52
+ // But duck-type check succeeds
53
+ expect(isRequestLike(polyfilled)).toBe(true);
54
+ });
55
+
56
+ it("should NOT match null or undefined", () => {
57
+ expect(isRequestLike(null)).toBe(false);
58
+ expect(isRequestLike(undefined)).toBe(false);
59
+ });
60
+
61
+ it("should NOT match an object missing required properties", () => {
62
+ expect(isRequestLike({ url: "http://test.com" })).toBe(false);
63
+ expect(isRequestLike({ method: "GET" })).toBe(false);
64
+ expect(isRequestLike({})).toBe(false);
65
+ });
66
+ });
@@ -158,11 +158,25 @@ export function copilotRuntimeNodeHttpEndpoint(
158
158
  }
159
159
  };
160
160
 
161
+ // Duck-type check for Request-like objects (handles polyfilled Request from @hono/node-server)
162
+ function isRequestLike(obj: unknown): obj is Request {
163
+ return (
164
+ obj instanceof Request ||
165
+ (typeof obj === "object" &&
166
+ obj !== null &&
167
+ "url" in obj &&
168
+ "method" in obj &&
169
+ "headers" in obj &&
170
+ typeof (obj as any).url === "string" &&
171
+ typeof (obj as any).method === "string")
172
+ );
173
+ }
174
+
161
175
  return function (
162
176
  reqOrRequest: IncomingMessage | Request,
163
177
  res?: ServerResponse,
164
178
  ): Promise<void> | Promise<Response> | Response {
165
- if (reqOrRequest instanceof Request) {
179
+ if (isRequestLike(reqOrRequest) && !res) {
166
180
  return honoApp.fetch(reqOrRequest as Request);
167
181
  }
168
182
  if (!res) {