@copilotkit/runtime 1.56.2 → 1.56.4-canary.1777529757

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 (203) hide show
  1. package/dist/agent/converters/tanstack.cjs +121 -25
  2. package/dist/agent/converters/tanstack.cjs.map +1 -1
  3. package/dist/agent/converters/tanstack.d.cts.map +1 -1
  4. package/dist/agent/converters/tanstack.d.mts.map +1 -1
  5. package/dist/agent/converters/tanstack.mjs +121 -25
  6. package/dist/agent/converters/tanstack.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/runtime/agent-integrations/langgraph/agent.cjs +8 -1
  16. package/dist/lib/runtime/agent-integrations/langgraph/agent.cjs.map +1 -1
  17. package/dist/lib/runtime/agent-integrations/langgraph/agent.d.cts.map +1 -1
  18. package/dist/lib/runtime/agent-integrations/langgraph/agent.d.mts.map +1 -1
  19. package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs +8 -1
  20. package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs.map +1 -1
  21. package/dist/lib/runtime/copilot-runtime.cjs +4 -2
  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 +4 -2
  26. package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
  27. package/dist/package.cjs +7 -7
  28. package/dist/package.mjs +7 -7
  29. package/dist/v2/index.d.cts +2 -2
  30. package/dist/v2/index.d.mts +2 -2
  31. package/dist/v2/runtime/core/debug-event-bus.cjs +36 -0
  32. package/dist/v2/runtime/core/debug-event-bus.cjs.map +1 -0
  33. package/dist/v2/runtime/core/debug-event-bus.d.cts +19 -0
  34. package/dist/v2/runtime/core/debug-event-bus.d.cts.map +1 -0
  35. package/dist/v2/runtime/core/debug-event-bus.d.mts +19 -0
  36. package/dist/v2/runtime/core/debug-event-bus.d.mts.map +1 -0
  37. package/dist/v2/runtime/core/debug-event-bus.mjs +35 -0
  38. package/dist/v2/runtime/core/debug-event-bus.mjs.map +1 -0
  39. package/dist/v2/runtime/core/fetch-handler.cjs +8 -0
  40. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  41. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  42. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  43. package/dist/v2/runtime/core/fetch-handler.mjs +8 -0
  44. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  45. package/dist/v2/runtime/core/fetch-router.cjs +1 -0
  46. package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
  47. package/dist/v2/runtime/core/fetch-router.mjs +1 -0
  48. package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
  49. package/dist/v2/runtime/core/hooks.cjs.map +1 -1
  50. package/dist/v2/runtime/core/hooks.d.cts +2 -0
  51. package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
  52. package/dist/v2/runtime/core/hooks.d.mts +2 -0
  53. package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
  54. package/dist/v2/runtime/core/hooks.mjs.map +1 -1
  55. package/dist/v2/runtime/core/runtime.cjs +5 -0
  56. package/dist/v2/runtime/core/runtime.cjs.map +1 -1
  57. package/dist/v2/runtime/core/runtime.d.cts +5 -0
  58. package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
  59. package/dist/v2/runtime/core/runtime.d.mts +5 -1
  60. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  61. package/dist/v2/runtime/core/runtime.mjs +5 -0
  62. package/dist/v2/runtime/core/runtime.mjs.map +1 -1
  63. package/dist/v2/runtime/endpoints/express.cjs +5 -5
  64. package/dist/v2/runtime/endpoints/express.cjs.map +1 -1
  65. package/dist/v2/runtime/endpoints/express.mjs +5 -5
  66. package/dist/v2/runtime/endpoints/express.mjs.map +1 -1
  67. package/dist/v2/runtime/handlers/handle-connect.cjs +3 -2
  68. package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
  69. package/dist/v2/runtime/handlers/handle-connect.mjs +3 -2
  70. package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
  71. package/dist/v2/runtime/handlers/handle-debug-events.cjs +33 -0
  72. package/dist/v2/runtime/handlers/handle-debug-events.cjs.map +1 -0
  73. package/dist/v2/runtime/handlers/handle-debug-events.mjs +32 -0
  74. package/dist/v2/runtime/handlers/handle-debug-events.mjs.map +1 -0
  75. package/dist/v2/runtime/handlers/handle-run.cjs +1 -0
  76. package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
  77. package/dist/v2/runtime/handlers/handle-run.mjs +1 -0
  78. package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
  79. package/dist/v2/runtime/handlers/intelligence/connect.cjs +24 -4
  80. package/dist/v2/runtime/handlers/intelligence/connect.cjs.map +1 -1
  81. package/dist/v2/runtime/handlers/intelligence/connect.mjs +25 -5
  82. package/dist/v2/runtime/handlers/intelligence/connect.mjs.map +1 -1
  83. package/dist/v2/runtime/handlers/intelligence/run.cjs +111 -26
  84. package/dist/v2/runtime/handlers/intelligence/run.cjs.map +1 -1
  85. package/dist/v2/runtime/handlers/intelligence/run.mjs +111 -26
  86. package/dist/v2/runtime/handlers/intelligence/run.mjs.map +1 -1
  87. package/dist/v2/runtime/handlers/shared/intelligence-utils.cjs +7 -3
  88. package/dist/v2/runtime/handlers/shared/intelligence-utils.cjs.map +1 -1
  89. package/dist/v2/runtime/handlers/shared/intelligence-utils.mjs +7 -3
  90. package/dist/v2/runtime/handlers/shared/intelligence-utils.mjs.map +1 -1
  91. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs +5 -1
  92. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs.map +1 -1
  93. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs +5 -1
  94. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs.map +1 -1
  95. package/dist/v2/runtime/handlers/shared/sse-response.cjs +21 -1
  96. package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
  97. package/dist/v2/runtime/handlers/shared/sse-response.mjs +21 -1
  98. package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
  99. package/dist/v2/runtime/handlers/sse/connect.cjs +3 -1
  100. package/dist/v2/runtime/handlers/sse/connect.cjs.map +1 -1
  101. package/dist/v2/runtime/handlers/sse/connect.mjs +3 -1
  102. package/dist/v2/runtime/handlers/sse/connect.mjs.map +1 -1
  103. package/dist/v2/runtime/handlers/sse/run.cjs +3 -1
  104. package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
  105. package/dist/v2/runtime/handlers/sse/run.mjs +3 -1
  106. package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
  107. package/dist/v2/runtime/index.d.cts +1 -1
  108. package/dist/v2/runtime/index.d.mts +1 -2
  109. package/dist/v2/runtime/index.d.mts.map +1 -1
  110. package/dist/v2/runtime/intelligence-platform/client.cjs +6 -8
  111. package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
  112. package/dist/v2/runtime/intelligence-platform/client.d.cts +16 -21
  113. package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
  114. package/dist/v2/runtime/intelligence-platform/client.d.mts +16 -21
  115. package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
  116. package/dist/v2/runtime/intelligence-platform/client.mjs +6 -8
  117. package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
  118. package/dist/v2/runtime/runner/agent-runner.cjs.map +1 -1
  119. package/dist/v2/runtime/runner/agent-runner.d.cts +0 -1
  120. package/dist/v2/runtime/runner/agent-runner.d.cts.map +1 -1
  121. package/dist/v2/runtime/runner/agent-runner.d.mts +0 -1
  122. package/dist/v2/runtime/runner/agent-runner.d.mts.map +1 -1
  123. package/dist/v2/runtime/runner/agent-runner.mjs.map +1 -1
  124. package/dist/v2/runtime/runner/index.d.cts +1 -1
  125. package/dist/v2/runtime/runner/index.d.mts +1 -1
  126. package/dist/v2/runtime/runner/intelligence.cjs +47 -10
  127. package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
  128. package/dist/v2/runtime/runner/intelligence.d.cts +8 -1
  129. package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
  130. package/dist/v2/runtime/runner/intelligence.d.mts +8 -1
  131. package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
  132. package/dist/v2/runtime/runner/intelligence.mjs +47 -10
  133. package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
  134. package/dist/v2/runtime/telemetry/instance-created.cjs +33 -0
  135. package/dist/v2/runtime/telemetry/instance-created.cjs.map +1 -0
  136. package/dist/v2/runtime/telemetry/instance-created.mjs +33 -0
  137. package/dist/v2/runtime/telemetry/instance-created.mjs.map +1 -0
  138. package/dist/v2/runtime/telemetry/telemetry-client.cjs +1 -38
  139. package/dist/v2/runtime/telemetry/telemetry-client.cjs.map +1 -1
  140. package/dist/v2/runtime/telemetry/telemetry-client.mjs +1 -37
  141. package/dist/v2/runtime/telemetry/telemetry-client.mjs.map +1 -1
  142. package/package.json +8 -8
  143. package/src/agent/__tests__/agent-test-helpers.ts +31 -1
  144. package/src/agent/__tests__/converter-tanstack.test.ts +280 -0
  145. package/src/agent/converters/tanstack.ts +167 -10
  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/runtime/__tests__/handle-service-adapter.test.ts +108 -0
  151. package/src/lib/runtime/__tests__/retry-utils.test.ts +137 -0
  152. package/src/lib/runtime/agent-integrations/langgraph/__tests__/dispatch-event-filtering.test.ts +190 -0
  153. package/src/lib/runtime/agent-integrations/langgraph/agent.ts +8 -1
  154. package/src/lib/runtime/copilot-runtime.ts +20 -4
  155. package/src/lib/runtime/retry-utils.ts +41 -1
  156. package/src/v2/runtime/__tests__/express-fetch-bridge.test.ts +1 -1
  157. package/src/v2/runtime/__tests__/express-single-telemetry.integration.test.ts +65 -0
  158. package/src/v2/runtime/__tests__/express-telemetry.integration.test.ts +101 -0
  159. package/src/v2/runtime/__tests__/fetch-router.test.ts +22 -0
  160. package/src/v2/runtime/__tests__/handle-connect.test.ts +183 -23
  161. package/src/v2/runtime/__tests__/handle-run.test.ts +411 -33
  162. package/src/v2/runtime/__tests__/handle-threads.test.ts +66 -4
  163. package/src/v2/runtime/__tests__/hono-single-telemetry.integration.test.ts +46 -0
  164. package/src/v2/runtime/__tests__/hono-telemetry.integration.test.ts +99 -0
  165. package/src/v2/runtime/__tests__/integration/node-servers.integration.test.ts +19 -0
  166. package/src/v2/runtime/__tests__/integration/suites/debug-events.suite.ts +253 -0
  167. package/src/v2/runtime/__tests__/intelligence-run-telemetry.test.ts +194 -0
  168. package/src/v2/runtime/__tests__/runtime.test.ts +3 -1
  169. package/src/v2/runtime/__tests__/sse-response-telemetry.test.ts +108 -0
  170. package/src/v2/runtime/__tests__/telemetry.test.ts +0 -61
  171. package/src/v2/runtime/core/__tests__/debug-event-bus.test.ts +156 -0
  172. package/src/v2/runtime/core/debug-event-bus.ts +45 -0
  173. package/src/v2/runtime/core/fetch-handler.ts +7 -0
  174. package/src/v2/runtime/core/fetch-router.ts +11 -0
  175. package/src/v2/runtime/core/hooks.ts +2 -1
  176. package/src/v2/runtime/core/runtime.ts +12 -0
  177. package/src/v2/runtime/endpoints/express.ts +9 -3
  178. package/src/v2/runtime/handlers/__tests__/handle-debug-events.test.ts +176 -0
  179. package/src/v2/runtime/handlers/handle-connect.ts +2 -1
  180. package/src/v2/runtime/handlers/handle-debug-events.ts +52 -0
  181. package/src/v2/runtime/handlers/handle-run.ts +1 -0
  182. package/src/v2/runtime/handlers/intelligence/connect.ts +48 -11
  183. package/src/v2/runtime/handlers/intelligence/run.ts +162 -21
  184. package/src/v2/runtime/handlers/shared/intelligence-utils.ts +21 -1
  185. package/src/v2/runtime/handlers/shared/resolve-intelligence-user.ts +4 -1
  186. package/src/v2/runtime/handlers/shared/sse-response.ts +46 -0
  187. package/src/v2/runtime/handlers/sse/__tests__/sse-connect-agent-id.test.ts +71 -0
  188. package/src/v2/runtime/handlers/sse/connect.ts +6 -0
  189. package/src/v2/runtime/handlers/sse/run.ts +4 -0
  190. package/src/v2/runtime/intelligence-platform/__tests__/client.test.ts +33 -37
  191. package/src/v2/runtime/intelligence-platform/client.ts +37 -40
  192. package/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts +66 -8
  193. package/src/v2/runtime/runner/agent-runner.ts +0 -1
  194. package/src/v2/runtime/runner/intelligence.ts +74 -15
  195. package/src/v2/runtime/telemetry/__tests__/instance-created.test.ts +96 -0
  196. package/src/v2/runtime/telemetry/instance-created.ts +44 -0
  197. package/src/v2/runtime/telemetry/telemetry-client.ts +1 -57
  198. package/dist/v2/runtime/intelligence-platform/index.d.mts +0 -2
  199. package/dist/v2/runtime/telemetry/utils.cjs +0 -15
  200. package/dist/v2/runtime/telemetry/utils.cjs.map +0 -1
  201. package/dist/v2/runtime/telemetry/utils.mjs +0 -14
  202. package/dist/v2/runtime/telemetry/utils.mjs.map +0 -1
  203. package/src/v2/runtime/telemetry/utils.ts +0 -15
@@ -24,6 +24,7 @@ import type {
24
24
  } from "./middleware";
25
25
  import { createLogger, type CopilotRuntimeLogger } from "../../../lib/logger";
26
26
  import { TranscriptionService } from "../transcription-service/transcription-service";
27
+ import { DebugEventBus } from "./debug-event-bus";
27
28
  import { AgentRunner } from "../runner/agent-runner";
28
29
  import { InMemoryAgentRunner } from "../runner/in-memory";
29
30
  import { IntelligenceAgentRunner } from "../runner/intelligence";
@@ -140,6 +141,7 @@ interface BaseCopilotRuntimeOptions extends CopilotRuntimeMiddlewares {
140
141
 
141
142
  export interface CopilotRuntimeUser {
142
143
  id: string;
144
+ name: string;
143
145
  }
144
146
 
145
147
  export type IdentifyUserCallback = (
@@ -189,6 +191,7 @@ export interface CopilotRuntimeLike {
189
191
  identifyUser?: IdentifyUserCallback;
190
192
  mode: RuntimeMode;
191
193
  licenseChecker?: LicenseChecker;
194
+ debugEventBus?: DebugEventBus;
192
195
  debug: ResolvedDebugConfig;
193
196
  debugLogger?: CopilotRuntimeLogger;
194
197
  }
@@ -218,6 +221,7 @@ abstract class BaseCopilotRuntime implements CopilotRuntimeLike {
218
221
  public mcpApps: CopilotRuntimeOptions["mcpApps"];
219
222
  public openGenerativeUI: CopilotRuntimeOptions["openGenerativeUI"];
220
223
  public licenseChecker?: LicenseChecker;
224
+ public readonly debugEventBus?: DebugEventBus;
221
225
  public debug: ResolvedDebugConfig;
222
226
  public debugLogger?: CopilotRuntimeLogger;
223
227
 
@@ -243,6 +247,10 @@ abstract class BaseCopilotRuntime implements CopilotRuntimeLike {
243
247
  this.mcpApps = mcpApps;
244
248
  this.openGenerativeUI = openGenerativeUI;
245
249
  this.runner = runner;
250
+
251
+ if (process.env.NODE_ENV !== "production") {
252
+ this.debugEventBus = new DebugEventBus();
253
+ }
246
254
  this.debug = resolveDebugConfig(options.debug);
247
255
  if (this.debug.enabled) {
248
256
  this.debugLogger = createLogger({
@@ -407,6 +415,10 @@ export class CopilotRuntime implements CopilotRuntimeLike {
407
415
  return this.delegate.licenseChecker;
408
416
  }
409
417
 
418
+ get debugEventBus() {
419
+ return this.delegate.debugEventBus;
420
+ }
421
+
410
422
  get debug(): ResolvedDebugConfig {
411
423
  return this.delegate.debug;
412
424
  }
@@ -147,15 +147,21 @@ export function createCopilotExpressHandler({
147
147
  router.post(normalizedBase, expressHandler);
148
148
  router.options(normalizedBase, expressHandler);
149
149
  } else if (normalizedBase === "/") {
150
- router.all("*", expressHandler);
150
+ router.all(/.*/, expressHandler);
151
151
  } else {
152
- router.all(`${normalizedBase}/*`, expressHandler);
153
- router.all(normalizedBase, expressHandler);
152
+ router.all(
153
+ new RegExp(`^${escapeRegExp(normalizedBase)}(\\/.*)?$`),
154
+ expressHandler,
155
+ );
154
156
  }
155
157
 
156
158
  return router;
157
159
  }
158
160
 
161
+ function escapeRegExp(s: string): string {
162
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
163
+ }
164
+
159
165
  function normalizeBasePath(path: string): string {
160
166
  if (!path) {
161
167
  throw new Error("basePath must be provided for Express endpoint");
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { handleDebugEvents } from "../handle-debug-events";
3
+ import { DebugEventBus } from "../../core/debug-event-bus";
4
+ import type { CopilotRuntimeLike } from "../../core/runtime";
5
+ import type { BaseEvent } from "@ag-ui/client";
6
+
7
+ /* ------------------------------------------------------------------------------------------------
8
+ * Helpers
9
+ * --------------------------------------------------------------------------------------------- */
10
+
11
+ function createMockRuntime(
12
+ overrides: { debugEventBus?: DebugEventBus } = {},
13
+ ): Pick<CopilotRuntimeLike, "debugEventBus"> {
14
+ return {
15
+ debugEventBus: overrides.debugEventBus ?? new DebugEventBus(),
16
+ };
17
+ }
18
+
19
+ function createMockRequest(options: { signal?: AbortSignal } = {}): Request {
20
+ return new Request("http://localhost/cpk-debug-events", {
21
+ method: "GET",
22
+ signal: options.signal,
23
+ });
24
+ }
25
+
26
+ function createTestEvent(): BaseEvent {
27
+ return { type: "custom" } as BaseEvent;
28
+ }
29
+
30
+ async function readNextSSELine(
31
+ reader: ReadableStreamDefaultReader<Uint8Array>,
32
+ ): Promise<string> {
33
+ const decoder = new TextDecoder();
34
+ let accumulated = "";
35
+ while (true) {
36
+ const { value, done } = await reader.read();
37
+ if (done) break;
38
+ accumulated += decoder.decode(value, { stream: true });
39
+ if (accumulated.includes("\n\n")) return accumulated;
40
+ }
41
+ return accumulated;
42
+ }
43
+
44
+ /* ------------------------------------------------------------------------------------------------
45
+ * Tests
46
+ * --------------------------------------------------------------------------------------------- */
47
+
48
+ describe("handleDebugEvents", () => {
49
+ let savedNodeEnv: string | undefined;
50
+
51
+ beforeEach(() => {
52
+ savedNodeEnv = process.env.NODE_ENV;
53
+ });
54
+
55
+ afterEach(() => {
56
+ if (savedNodeEnv === undefined) {
57
+ delete process.env.NODE_ENV;
58
+ } else {
59
+ process.env.NODE_ENV = savedNodeEnv;
60
+ }
61
+ });
62
+
63
+ it("returns 404 when NODE_ENV is production", async () => {
64
+ process.env.NODE_ENV = "production";
65
+
66
+ const runtime = createMockRuntime();
67
+ const request = createMockRequest();
68
+
69
+ const response = handleDebugEvents({
70
+ runtime: runtime as CopilotRuntimeLike,
71
+ request,
72
+ });
73
+
74
+ expect(response.status).toBe(404);
75
+ expect(await response.text()).toBe("Not Found");
76
+ });
77
+
78
+ it("returns 503 when debugEventBus is undefined", async () => {
79
+ process.env.NODE_ENV = "test";
80
+
81
+ const runtime = createMockRuntime({ debugEventBus: undefined });
82
+ // Remove the property so it's truly undefined
83
+ delete (runtime as Record<string, unknown>).debugEventBus;
84
+
85
+ const request = createMockRequest();
86
+
87
+ const response = handleDebugEvents({
88
+ runtime: runtime as CopilotRuntimeLike,
89
+ request,
90
+ });
91
+
92
+ expect(response.status).toBe(503);
93
+ expect(await response.text()).toBe("Debug event bus not available");
94
+ });
95
+
96
+ it("returns correct SSE response headers", () => {
97
+ process.env.NODE_ENV = "test";
98
+
99
+ const runtime = createMockRuntime();
100
+ const request = createMockRequest();
101
+
102
+ const response = handleDebugEvents({
103
+ runtime: runtime as CopilotRuntimeLike,
104
+ request,
105
+ });
106
+
107
+ expect(response.status).toBe(200);
108
+ expect(response.headers.get("Content-Type")).toBe("text/event-stream");
109
+ expect(response.headers.get("Cache-Control")).toBe("no-cache");
110
+ expect(response.headers.get("Connection")).toBe("keep-alive");
111
+ });
112
+
113
+ it("streams events as SSE data lines when bus broadcasts", async () => {
114
+ process.env.NODE_ENV = "test";
115
+
116
+ const bus = new DebugEventBus();
117
+ const runtime = createMockRuntime({ debugEventBus: bus });
118
+ const request = createMockRequest();
119
+
120
+ const response = handleDebugEvents({
121
+ runtime: runtime as CopilotRuntimeLike,
122
+ request,
123
+ });
124
+
125
+ const reader = response.body!.getReader();
126
+
127
+ // Read and discard the initial ": connected" SSE comment
128
+ const comment = await readNextSSELine(reader);
129
+ expect(comment).toMatch(/^: connected/);
130
+
131
+ // Broadcast an event through the bus
132
+ bus.broadcast(createTestEvent(), {
133
+ agentId: "agent-1",
134
+ threadId: "thread-1",
135
+ runId: "run-1",
136
+ });
137
+
138
+ const line = await readNextSSELine(reader);
139
+
140
+ // The line should be "data: {json}\n\n"
141
+ expect(line).toMatch(/^data: \{.*\}\n\n$/);
142
+
143
+ const parsed = JSON.parse(line.replace("data: ", "").trim());
144
+ expect(parsed.agentId).toBe("agent-1");
145
+ expect(parsed.threadId).toBe("thread-1");
146
+ expect(parsed.runId).toBe("run-1");
147
+ expect(parsed.event).toEqual(createTestEvent());
148
+ expect(typeof parsed.timestamp).toBe("number");
149
+
150
+ reader.releaseLock();
151
+ });
152
+
153
+ it("unsubscribes from bus when request is aborted", async () => {
154
+ process.env.NODE_ENV = "test";
155
+
156
+ const bus = new DebugEventBus();
157
+ const runtime = createMockRuntime({ debugEventBus: bus });
158
+
159
+ const abortController = new AbortController();
160
+ const request = createMockRequest({ signal: abortController.signal });
161
+
162
+ handleDebugEvents({
163
+ runtime: runtime as CopilotRuntimeLike,
164
+ request,
165
+ });
166
+
167
+ // Before abort, the bus should have one listener
168
+ expect(bus.listenerCount).toBe(1);
169
+
170
+ // Abort the request
171
+ abortController.abort();
172
+
173
+ // After abort, the listener should be cleaned up
174
+ expect(bus.listenerCount).toBe(0);
175
+ });
176
+ });
@@ -43,14 +43,15 @@ export async function handleConnectAgent({
43
43
  return handleIntelligenceConnect({
44
44
  runtime,
45
45
  request,
46
+ agentId,
46
47
  threadId: connectRequest.input.threadId,
47
- lastSeenEventId: connectRequest.lastSeenEventId,
48
48
  });
49
49
  }
50
50
 
51
51
  return handleSseConnect({
52
52
  runtime,
53
53
  request,
54
+ agentId,
54
55
  threadId: connectRequest.input.threadId,
55
56
  });
56
57
  } catch (error) {
@@ -0,0 +1,52 @@
1
+ import { CopilotRuntimeLike } from "../core/runtime";
2
+ import { DebugEventEnvelope } from "@copilotkit/shared";
3
+
4
+ interface HandleDebugEventsParams {
5
+ runtime: CopilotRuntimeLike;
6
+ request: Request;
7
+ }
8
+
9
+ export function handleDebugEvents({
10
+ runtime,
11
+ request,
12
+ }: HandleDebugEventsParams): Response {
13
+ if (process.env.NODE_ENV === "production") {
14
+ return new Response("Not Found", { status: 404 });
15
+ }
16
+
17
+ if (!runtime.debugEventBus) {
18
+ return new Response("Debug event bus not available", { status: 503 });
19
+ }
20
+
21
+ const bus = runtime.debugEventBus;
22
+ const encoder = new TextEncoder();
23
+ const stream = new TransformStream();
24
+ const writer = stream.writable.getWriter();
25
+
26
+ // Send an SSE comment immediately to flush response headers to the client.
27
+ // Without this, some frameworks buffer the response until actual data is written,
28
+ // leaving the client stuck in "connecting" state.
29
+ writer.write(encoder.encode(": connected\n\n")).catch(() => {});
30
+
31
+ const unsubscribe = bus.subscribe((envelope: DebugEventEnvelope) => {
32
+ if (request.signal.aborted) return;
33
+ const line = `data: ${JSON.stringify(envelope)}\n\n`;
34
+ writer.write(encoder.encode(line)).catch(() => {
35
+ // Client disconnected, will be cleaned up by abort handler.
36
+ });
37
+ });
38
+
39
+ request.signal.addEventListener("abort", () => {
40
+ unsubscribe();
41
+ writer.close().catch(() => {});
42
+ });
43
+
44
+ return new Response(stream.readable, {
45
+ status: 200,
46
+ headers: {
47
+ "Content-Type": "text/event-stream",
48
+ "Cache-Control": "no-cache",
49
+ Connection: "keep-alive",
50
+ },
51
+ });
52
+ }
@@ -77,6 +77,7 @@ export async function handleRunAgent({
77
77
  request,
78
78
  agent,
79
79
  input,
80
+ agentId,
80
81
  debug: runtime.debug,
81
82
  logger: runtime.debugLogger,
82
83
  });
@@ -1,20 +1,33 @@
1
1
  import { CopilotIntelligenceRuntimeLike } from "../../core/runtime";
2
- import { isPlatformNotFoundError } from "../shared/intelligence-utils";
2
+ import { getPlatformErrorStatus } from "../shared/intelligence-utils";
3
3
  import { resolveIntelligenceUser } from "../shared/resolve-intelligence-user";
4
4
  import { isHandlerResponse } from "../shared/json-response";
5
5
 
6
+ /**
7
+ * Builds browser-facing realtime connection metadata owned by the runtime.
8
+ */
9
+ function buildRealtimeConnectionInfo(params: {
10
+ clientUrl: string;
11
+ threadId: string;
12
+ }): { clientUrl: string; topic: string } {
13
+ return {
14
+ clientUrl: params.clientUrl,
15
+ topic: `thread:${params.threadId}`,
16
+ };
17
+ }
18
+
6
19
  interface HandleIntelligenceConnectParams {
7
20
  runtime: CopilotIntelligenceRuntimeLike;
8
21
  request: Request;
22
+ agentId: string;
9
23
  threadId: string;
10
- lastSeenEventId: string | null;
11
24
  }
12
25
 
13
26
  export async function handleIntelligenceConnect({
14
27
  runtime,
15
28
  request,
29
+ agentId,
16
30
  threadId,
17
- lastSeenEventId,
18
31
  }: HandleIntelligenceConnectParams): Promise<Response> {
19
32
  if (!runtime.intelligence) {
20
33
  return Response.json(
@@ -35,7 +48,7 @@ export async function handleIntelligenceConnect({
35
48
  const result = await runtime.intelligence.ɵconnectThread({
36
49
  threadId,
37
50
  userId: user.id,
38
- lastSeenEventId,
51
+ agentId,
39
52
  });
40
53
 
41
54
  if (result === null) {
@@ -44,14 +57,38 @@ export async function handleIntelligenceConnect({
44
57
  });
45
58
  }
46
59
 
47
- return Response.json(result, {
48
- headers: { "Cache-Control": "no-cache", Connection: "keep-alive" },
49
- });
60
+ return Response.json(
61
+ {
62
+ threadId: result.threadId,
63
+ joinToken: result.joinToken,
64
+ realtime: buildRealtimeConnectionInfo({
65
+ clientUrl: runtime.intelligence.ɵgetClientWsUrl(),
66
+ threadId: result.threadId,
67
+ }),
68
+ },
69
+ {
70
+ headers: { "Cache-Control": "no-cache", Connection: "keep-alive" },
71
+ },
72
+ );
50
73
  } catch (error) {
51
- if (isPlatformNotFoundError(error)) {
52
- return new Response(null, {
53
- status: 204,
54
- });
74
+ const status = getPlatformErrorStatus(error);
75
+ if (
76
+ status === 400 ||
77
+ status === 401 ||
78
+ status === 403 ||
79
+ status === 404 ||
80
+ status === 409
81
+ ) {
82
+ return Response.json(
83
+ {
84
+ error: "Connect request rejected",
85
+ message:
86
+ error instanceof Error
87
+ ? error.message
88
+ : "Intelligence platform rejected the connect request",
89
+ },
90
+ { status },
91
+ );
55
92
  }
56
93
 
57
94
  console.error("Connect plan not available:", error);
@@ -1,10 +1,53 @@
1
- import { AbstractAgent, Message, RunAgentInput } from "@ag-ui/client";
1
+ import {
2
+ AbstractAgent,
3
+ BaseEvent,
4
+ EventType,
5
+ Message,
6
+ RunAgentInput,
7
+ } from "@ag-ui/client";
2
8
  import { CopilotIntelligenceRuntimeLike } from "../../core/runtime";
3
9
  import { generateThreadNameForNewThread } from "./thread-names";
4
10
  import { logger } from "@copilotkit/shared";
5
11
  import { telemetry } from "../../telemetry";
6
12
  import { resolveIntelligenceUser } from "../shared/resolve-intelligence-user";
7
13
  import { isHandlerResponse } from "../shared/json-response";
14
+ import { AgentRunnerRunRequest } from "../../runner/agent-runner";
15
+ import { Observable } from "rxjs";
16
+
17
+ /**
18
+ * Builds browser-facing realtime connection metadata owned by the runtime.
19
+ */
20
+ function buildRealtimeConnectionInfo(params: {
21
+ clientUrl: string;
22
+ threadId: string;
23
+ }): { clientUrl: string; topic: string } {
24
+ return {
25
+ clientUrl: params.clientUrl,
26
+ topic: `thread:${params.threadId}`,
27
+ };
28
+ }
29
+
30
+ interface RunnerStartupBoundary {
31
+ events: Observable<BaseEvent>;
32
+ startup: Promise<void>;
33
+ }
34
+
35
+ interface RunnerWithStartupBoundary {
36
+ runWithStartupBoundary(request: AgentRunnerRunRequest): RunnerStartupBoundary;
37
+ }
38
+
39
+ function hasRunnerStartupBoundary(
40
+ runner: CopilotIntelligenceRuntimeLike["runner"],
41
+ ): runner is CopilotIntelligenceRuntimeLike["runner"] &
42
+ RunnerWithStartupBoundary {
43
+ const candidate = runner as { runWithStartupBoundary?: unknown };
44
+
45
+ return (
46
+ typeof candidate.runWithStartupBoundary === "function" &&
47
+ (Object.prototype.hasOwnProperty.call(runner, "runWithStartupBoundary") ||
48
+ Object.prototype.hasOwnProperty.call(runner, "threads"))
49
+ );
50
+ }
8
51
 
9
52
  interface HandleIntelligenceRunParams {
10
53
  runtime: CopilotIntelligenceRuntimeLike;
@@ -66,20 +109,23 @@ export async function handleIntelligenceRun({
66
109
  );
67
110
  }
68
111
 
69
- let joinCode: string | undefined;
112
+ let canonicalThreadId = input.threadId;
113
+ let canonicalRunId = input.runId;
70
114
  let joinToken: string | undefined;
71
115
  try {
72
116
  const lockResult = await runtime.intelligence.ɵacquireThreadLock({
73
117
  threadId: input.threadId,
74
118
  runId: input.runId,
75
119
  userId,
120
+ agentId,
76
121
  ...(runtime.lockKeyPrefix !== undefined
77
122
  ? { lockKeyPrefix: runtime.lockKeyPrefix }
78
123
  : {}),
79
124
  ttlSeconds: runtime.lockTtlSeconds,
80
125
  });
126
+ canonicalThreadId = lockResult.threadId;
127
+ canonicalRunId = lockResult.runId;
81
128
  joinToken = lockResult.joinToken;
82
- joinCode = lockResult.joinCode;
83
129
  } catch (error) {
84
130
  logger.error("Thread lock denied:", error);
85
131
  return Response.json(
@@ -90,21 +136,42 @@ export async function handleIntelligenceRun({
90
136
  );
91
137
  }
92
138
 
93
- if (!joinToken) {
139
+ const cleanupLock = (reason: string): Promise<void> =>
140
+ runtime.intelligence
141
+ .ɵcleanupThreadLock({
142
+ threadId: canonicalThreadId || input.threadId,
143
+ runId: canonicalRunId || input.runId,
144
+ })
145
+ .catch((cleanupError) => {
146
+ logger.error(
147
+ { err: cleanupError, reason },
148
+ "Failed to cleanup thread lock",
149
+ );
150
+ });
151
+
152
+ if (!canonicalThreadId || !canonicalRunId || !joinToken) {
153
+ await cleanupLock("malformed-lock-response");
94
154
  return Response.json(
95
155
  {
96
- error: "Join token not available",
97
- message: "Intelligence platform did not return a join token",
156
+ error: "Run connection credentials not available",
157
+ message:
158
+ "Intelligence platform did not return canonical threadId, runId, and joinToken",
98
159
  },
99
160
  { status: 502 },
100
161
  );
101
162
  }
102
163
 
164
+ const canonicalInput: RunAgentInput = {
165
+ ...input,
166
+ threadId: canonicalThreadId,
167
+ runId: canonicalRunId,
168
+ };
169
+
103
170
  let persistedInputMessages: Message[] | undefined;
104
171
  if (Array.isArray(input.messages)) {
105
172
  try {
106
173
  const history = await runtime.intelligence.getThreadMessages({
107
- threadId: input.threadId,
174
+ threadId: canonicalThreadId,
108
175
  });
109
176
  const historicMessageIds = new Set(
110
177
  history.messages.map((message) => message.id),
@@ -114,6 +181,7 @@ export async function handleIntelligenceRun({
114
181
  );
115
182
  } catch (error) {
116
183
  logger.error("Thread history lookup failed:", error);
184
+ await cleanupLock("thread-history-lookup-failed");
117
185
  return Response.json(
118
186
  {
119
187
  error: "Thread history lookup failed",
@@ -130,8 +198,8 @@ export async function handleIntelligenceRun({
130
198
  heartbeatTimer = setInterval(() => {
131
199
  runtime.intelligence
132
200
  .ɵrenewThreadLock({
133
- threadId: input.threadId,
134
- runId: input.runId,
201
+ threadId: canonicalThreadId,
202
+ runId: canonicalRunId,
135
203
  ttlSeconds: runtime.lockTtlSeconds,
136
204
  ...(runtime.lockKeyPrefix !== undefined
137
205
  ? { lockKeyPrefix: runtime.lockKeyPrefix }
@@ -139,6 +207,15 @@ export async function handleIntelligenceRun({
139
207
  })
140
208
  .catch((err) => {
141
209
  logger.error("Failed to renew thread lock:", err);
210
+ clearHeartbeat();
211
+ try {
212
+ agent.abortRun();
213
+ } catch (abortError) {
214
+ logger.error(
215
+ "Failed to abort agent after lock renewal failure:",
216
+ abortError,
217
+ );
218
+ }
142
219
  });
143
220
  }, runtime.lockHeartbeatIntervalSeconds * 1_000);
144
221
 
@@ -149,19 +226,48 @@ export async function handleIntelligenceRun({
149
226
  }
150
227
  };
151
228
 
152
- runtime.runner
153
- .run({
154
- threadId: input.threadId,
155
- agent,
156
- input,
157
- ...(persistedInputMessages !== undefined
158
- ? { persistedInputMessages }
159
- : {}),
160
- ...(joinCode ? { joinCode } : {}),
161
- })
162
- .subscribe({
229
+ const runStarted = { current: false };
230
+ let immediateStartupErrorMessage: string | undefined;
231
+ let immediateStartupCleanup: Promise<void> | undefined;
232
+
233
+ const runRequest: AgentRunnerRunRequest = {
234
+ threadId: canonicalThreadId,
235
+ agent,
236
+ input: canonicalInput,
237
+ ...(persistedInputMessages !== undefined ? { persistedInputMessages } : {}),
238
+ };
239
+
240
+ try {
241
+ const runStart = hasRunnerStartupBoundary(runtime.runner)
242
+ ? runtime.runner.runWithStartupBoundary(runRequest)
243
+ : {
244
+ events: runtime.runner.run(runRequest),
245
+ startup: Promise.resolve(),
246
+ };
247
+
248
+ runStart.events.subscribe({
249
+ next: (event: BaseEvent) => {
250
+ if (event.type === EventType.RUN_STARTED) {
251
+ runStarted.current = true;
252
+ }
253
+ if (event.type === EventType.RUN_ERROR && !runStarted.current) {
254
+ clearHeartbeat();
255
+ immediateStartupErrorMessage =
256
+ "message" in event && typeof event.message === "string"
257
+ ? event.message
258
+ : "Runner failed before the run started";
259
+ immediateStartupCleanup = cleanupLock("runner-start-failed");
260
+ }
261
+ },
163
262
  error: (error) => {
164
263
  clearHeartbeat();
264
+ if (!runStarted.current) {
265
+ immediateStartupErrorMessage =
266
+ error instanceof Error ? error.message : String(error);
267
+ immediateStartupCleanup = cleanupLock("runner-start-error");
268
+ } else {
269
+ cleanupLock("runner-error");
270
+ }
165
271
  telemetry.capture("oss.runtime.agent_execution_stream_errored", {
166
272
  error: error instanceof Error ? error.message : String(error),
167
273
  });
@@ -173,8 +279,43 @@ export async function handleIntelligenceRun({
173
279
  },
174
280
  });
175
281
 
282
+ await runStart.startup;
283
+ } catch (error) {
284
+ clearHeartbeat();
285
+ await (immediateStartupCleanup ?? cleanupLock("runner-start-threw"));
286
+ logger.error("Error starting agent runner:", error);
287
+ return Response.json(
288
+ {
289
+ error: "Failed to start runner",
290
+ message: error instanceof Error ? error.message : String(error),
291
+ },
292
+ { status: 502 },
293
+ );
294
+ }
295
+
296
+ if (immediateStartupErrorMessage) {
297
+ await immediateStartupCleanup;
298
+ return Response.json(
299
+ {
300
+ error: "Failed to start runner",
301
+ message: immediateStartupErrorMessage,
302
+ },
303
+ { status: 502 },
304
+ );
305
+ }
306
+
307
+ // IntelligenceAgentRunner resolves this boundary after Phoenix channel join.
308
+ // Other runner implementations fall back to construction/subscription errors.
176
309
  return Response.json(
177
- { joinToken },
310
+ {
311
+ threadId: canonicalThreadId,
312
+ runId: canonicalRunId,
313
+ joinToken,
314
+ realtime: buildRealtimeConnectionInfo({
315
+ clientUrl: runtime.intelligence.ɵgetClientWsUrl(),
316
+ threadId: canonicalThreadId,
317
+ }),
318
+ },
178
319
  {
179
320
  headers: { "Cache-Control": "no-cache" },
180
321
  },