@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
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Telemetry lifecycle tests for `packages/runtime/src/v2/runtime/handlers/intelligence/run.ts`.
3
+ *
4
+ * intelligence/run.ts fires three events across the agent run lifecycle:
5
+ * - oss.runtime.agent_execution_stream_started (line 126, after thread lock)
6
+ * - oss.runtime.agent_execution_stream_errored (inside runner subscribe's error handler)
7
+ * - oss.runtime.agent_execution_stream_ended (inside runner subscribe's complete handler)
8
+ *
9
+ * This test verifies each fires under the expected condition. It's paired
10
+ * with sse-response-telemetry.test.ts which covers the SSE path of the
11
+ * same event names — kept separate so a regression in one file fails only
12
+ * its own test.
13
+ */
14
+ import type { AbstractAgent, BaseEvent } from "@ag-ui/client";
15
+ import { Observable } from "rxjs";
16
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
17
+
18
+ import { handleRunAgent } from "../handlers/handle-run";
19
+ import { IntelligenceAgentRunner } from "../runner/intelligence";
20
+ import { telemetry } from "../telemetry";
21
+ import type { CopilotRuntime } from "../core/runtime";
22
+
23
+ // --- Minimal helpers (mirroring handle-run.test.ts's intelligence block) ---
24
+
25
+ interface MockIntelligencePlatform {
26
+ [key: string]: ((...args: any[]) => any) | undefined;
27
+ }
28
+
29
+ function makeAgent(): AbstractAgent {
30
+ const makeClone = () =>
31
+ ({
32
+ clone: vi.fn(() => makeClone()),
33
+ setMessages: vi.fn(),
34
+ setState: vi.fn(),
35
+ threadId: undefined,
36
+ headers: {},
37
+ runAgent: vi.fn().mockResolvedValue(undefined),
38
+ }) as unknown as AbstractAgent;
39
+ const agent: any = {
40
+ clone: vi.fn(() => makeClone()),
41
+ setMessages: vi.fn(),
42
+ setState: vi.fn(),
43
+ threadId: undefined,
44
+ headers: {},
45
+ runAgent: vi.fn().mockResolvedValue(undefined),
46
+ };
47
+ return agent as AbstractAgent;
48
+ }
49
+
50
+ function makeIntelligenceRuntime(
51
+ runObservable: Observable<BaseEvent>,
52
+ extraPlatform: MockIntelligencePlatform = {},
53
+ ): CopilotRuntime {
54
+ const runner = Object.create(IntelligenceAgentRunner.prototype);
55
+ runner.run = vi.fn(() => runObservable);
56
+
57
+ const platform: MockIntelligencePlatform = {
58
+ getOrCreateThread: vi.fn().mockResolvedValue({
59
+ thread: { id: "thread-1", name: null },
60
+ created: false,
61
+ }),
62
+ getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
63
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
64
+ threadId: "thread-1",
65
+ runId: "run-1",
66
+ joinToken: "jt-1",
67
+ }),
68
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
69
+ ɵgetClientWsUrl: vi.fn(() => "wss://runtime.example/client"),
70
+ ɵrenewThreadLock: vi.fn().mockResolvedValue(undefined),
71
+ ...extraPlatform,
72
+ };
73
+
74
+ return {
75
+ agents: Promise.resolve({ "my-agent": makeAgent() }),
76
+ transcriptionService: undefined,
77
+ beforeRequestMiddleware: undefined,
78
+ afterRequestMiddleware: undefined,
79
+ runner,
80
+ mode: "intelligence",
81
+ generateThreadNames: false,
82
+ intelligence: platform,
83
+ identifyUser: vi.fn().mockResolvedValue({ id: "user-1", name: "User One" }),
84
+ lockTtlSeconds: 20,
85
+ lockHeartbeatIntervalSeconds: 15,
86
+ } as unknown as CopilotRuntime;
87
+ }
88
+
89
+ function makeRunRequest(): Request {
90
+ return new Request("https://example.com/agent/my-agent/run", {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({
94
+ threadId: "thread-1",
95
+ runId: "run-1",
96
+ state: {},
97
+ messages: [],
98
+ tools: [],
99
+ context: [],
100
+ forwardedProps: {},
101
+ }),
102
+ });
103
+ }
104
+
105
+ // --- Tests ---
106
+
107
+ describe("intelligence/run.ts — telemetry lifecycle", () => {
108
+ let captureSpy: ReturnType<typeof vi.spyOn>;
109
+ let errorSpy: ReturnType<typeof vi.spyOn>;
110
+
111
+ beforeEach(() => {
112
+ vi.useFakeTimers();
113
+ captureSpy = vi.spyOn(telemetry, "capture").mockResolvedValue(undefined);
114
+ // Swallow the logger.error that fires on simulated agent errors.
115
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
116
+ });
117
+
118
+ afterEach(() => {
119
+ vi.useRealTimers();
120
+ captureSpy.mockRestore();
121
+ errorSpy.mockRestore();
122
+ });
123
+
124
+ it("fires agent_execution_stream_started once thread lock is acquired", async () => {
125
+ // Observable that never completes — so only stream_started should fire.
126
+ const never = new Observable<BaseEvent>(() => {});
127
+ const runtime = makeIntelligenceRuntime(never);
128
+
129
+ await handleRunAgent({
130
+ runtime,
131
+ request: makeRunRequest(),
132
+ agentId: "my-agent",
133
+ });
134
+
135
+ expect(captureSpy).toHaveBeenCalledWith(
136
+ "oss.runtime.agent_execution_stream_started",
137
+ {},
138
+ );
139
+ // And NOT the other two
140
+ expect(captureSpy).not.toHaveBeenCalledWith(
141
+ "oss.runtime.agent_execution_stream_errored",
142
+ expect.anything(),
143
+ );
144
+ expect(captureSpy).not.toHaveBeenCalledWith(
145
+ "oss.runtime.agent_execution_stream_ended",
146
+ expect.anything(),
147
+ );
148
+ });
149
+
150
+ it("fires agent_execution_stream_ended when runner.run observable completes", async () => {
151
+ // Observable that completes immediately.
152
+ const completing = new Observable<BaseEvent>((subscriber) => {
153
+ subscriber.complete();
154
+ });
155
+ const runtime = makeIntelligenceRuntime(completing);
156
+
157
+ await handleRunAgent({
158
+ runtime,
159
+ request: makeRunRequest(),
160
+ agentId: "my-agent",
161
+ });
162
+
163
+ expect(captureSpy).toHaveBeenCalledWith(
164
+ "oss.runtime.agent_execution_stream_started",
165
+ {},
166
+ );
167
+ expect(captureSpy).toHaveBeenCalledWith(
168
+ "oss.runtime.agent_execution_stream_ended",
169
+ {},
170
+ );
171
+ });
172
+
173
+ it("fires agent_execution_stream_errored when runner.run observable errors", async () => {
174
+ const failing = new Observable<BaseEvent>((subscriber) => {
175
+ subscriber.error(new Error("agent exploded"));
176
+ });
177
+ const runtime = makeIntelligenceRuntime(failing);
178
+
179
+ await handleRunAgent({
180
+ runtime,
181
+ request: makeRunRequest(),
182
+ agentId: "my-agent",
183
+ });
184
+
185
+ expect(captureSpy).toHaveBeenCalledWith(
186
+ "oss.runtime.agent_execution_stream_started",
187
+ {},
188
+ );
189
+ expect(captureSpy).toHaveBeenCalledWith(
190
+ "oss.runtime.agent_execution_stream_errored",
191
+ expect.objectContaining({ error: "agent exploded" }),
192
+ );
193
+ });
194
+ });
@@ -11,7 +11,9 @@ import { IntelligenceAgentRunner } from "../runner/intelligence";
11
11
 
12
12
  describe("runtime construction", () => {
13
13
  const agents = {};
14
- const identifyUser = vi.fn().mockResolvedValue({ id: "user-1" });
14
+ const identifyUser = vi
15
+ .fn()
16
+ .mockResolvedValue({ id: "user-1", name: "User One" });
15
17
  const createMockIntelligence = (): CopilotKitIntelligence =>
16
18
  ({
17
19
  ɵgetRunnerWsUrl: vi.fn().mockReturnValue("ws://runner.example"),
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Telemetry lifecycle tests for `packages/runtime/src/v2/runtime/handlers/shared/sse-response.ts`.
3
+ *
4
+ * sse-response.ts fires three events across the SSE stream lifecycle:
5
+ * - oss.runtime.agent_execution_stream_started (line 73, right after observableFactory resolves)
6
+ * - oss.runtime.agent_execution_stream_errored (inside subscribe's error handler)
7
+ * - oss.runtime.agent_execution_stream_ended (inside subscribe's complete handler)
8
+ *
9
+ * Paired with intelligence-run-telemetry.test.ts which covers the
10
+ * intelligence/run path of the same event names — kept separate so a
11
+ * regression in one source file fails only its own test.
12
+ */
13
+ import { BaseEvent } from "@ag-ui/client";
14
+ import { Observable } from "rxjs";
15
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
16
+
17
+ import { createSseEventResponse } from "../handlers/shared/sse-response";
18
+ import { telemetry } from "../telemetry";
19
+
20
+ function makeRequest(): Request {
21
+ return new Request("https://example.com/agent/test/run", { method: "POST" });
22
+ }
23
+
24
+ describe("sse-response.ts — telemetry lifecycle", () => {
25
+ let captureSpy: ReturnType<typeof vi.spyOn>;
26
+ let errorSpy: ReturnType<typeof vi.spyOn>;
27
+
28
+ beforeEach(() => {
29
+ captureSpy = vi.spyOn(telemetry, "capture").mockResolvedValue(undefined);
30
+ // Swallow the console.error from SSE logError on simulated failures.
31
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
32
+ });
33
+
34
+ afterEach(() => {
35
+ captureSpy.mockRestore();
36
+ errorSpy.mockRestore();
37
+ });
38
+
39
+ it("fires agent_execution_stream_started once the observable factory resolves", async () => {
40
+ // Observable that never completes — only started should fire.
41
+ const never = new Observable<BaseEvent>(() => {});
42
+ createSseEventResponse({
43
+ request: makeRequest(),
44
+ observableFactory: () => never,
45
+ });
46
+
47
+ await vi.waitFor(() => {
48
+ expect(captureSpy).toHaveBeenCalledWith(
49
+ "oss.runtime.agent_execution_stream_started",
50
+ {},
51
+ );
52
+ });
53
+
54
+ expect(captureSpy).not.toHaveBeenCalledWith(
55
+ "oss.runtime.agent_execution_stream_errored",
56
+ expect.anything(),
57
+ );
58
+ expect(captureSpy).not.toHaveBeenCalledWith(
59
+ "oss.runtime.agent_execution_stream_ended",
60
+ expect.anything(),
61
+ );
62
+ });
63
+
64
+ it("fires agent_execution_stream_ended when the observable completes", async () => {
65
+ const completing = new Observable<BaseEvent>((subscriber) => {
66
+ subscriber.complete();
67
+ });
68
+ createSseEventResponse({
69
+ request: makeRequest(),
70
+ observableFactory: () => completing,
71
+ });
72
+
73
+ await vi.waitFor(() => {
74
+ expect(captureSpy).toHaveBeenCalledWith(
75
+ "oss.runtime.agent_execution_stream_ended",
76
+ {},
77
+ );
78
+ });
79
+
80
+ // started should also have fired before ended
81
+ expect(captureSpy).toHaveBeenCalledWith(
82
+ "oss.runtime.agent_execution_stream_started",
83
+ {},
84
+ );
85
+ });
86
+
87
+ it("fires agent_execution_stream_errored with the error message when the observable errors", async () => {
88
+ const failing = new Observable<BaseEvent>((subscriber) => {
89
+ subscriber.error(new Error("stream blew up"));
90
+ });
91
+ createSseEventResponse({
92
+ request: makeRequest(),
93
+ observableFactory: () => failing,
94
+ });
95
+
96
+ await vi.waitFor(() => {
97
+ expect(captureSpy).toHaveBeenCalledWith(
98
+ "oss.runtime.agent_execution_stream_errored",
99
+ expect.objectContaining({ error: "stream blew up" }),
100
+ );
101
+ });
102
+
103
+ expect(captureSpy).toHaveBeenCalledWith(
104
+ "oss.runtime.agent_execution_stream_started",
105
+ {},
106
+ );
107
+ });
108
+ });
@@ -2,27 +2,15 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { TelemetryClient } from "../telemetry/telemetry-client";
3
3
  import scarfClient from "../telemetry/scarf-client";
4
4
 
5
- const { segmentTrackMock } = vi.hoisted(() => ({
6
- segmentTrackMock: vi.fn(),
7
- }));
8
-
9
- vi.mock("@segment/analytics-node", () => ({
10
- Analytics: vi.fn().mockImplementation(() => ({
11
- track: segmentTrackMock,
12
- })),
13
- }));
14
-
15
5
  describe("TelemetryClient", () => {
16
6
  let scarfSpy: ReturnType<typeof vi.spyOn>;
17
7
 
18
8
  beforeEach(() => {
19
9
  scarfSpy = vi.spyOn(scarfClient, "logEvent").mockResolvedValue(undefined);
20
- segmentTrackMock.mockClear();
21
10
  });
22
11
 
23
12
  afterEach(() => {
24
13
  scarfSpy.mockRestore();
25
- segmentTrackMock.mockClear();
26
14
  });
27
15
 
28
16
  it("sends event to scarf when sampled in", async () => {
@@ -44,33 +32,6 @@ describe("TelemetryClient", () => {
44
32
  });
45
33
  });
46
34
 
47
- it("sends event with properties to segment when sampled in", async () => {
48
- vi.spyOn(Math, "random").mockReturnValue(0);
49
- const client = new TelemetryClient({
50
- telemetryDisabled: false,
51
- sampleRate: 1,
52
- });
53
-
54
- await client.capture("oss.runtime.instance_created", {
55
- actionsAmount: 5,
56
- endpointTypes: ["rest"],
57
- endpointsAmount: 2,
58
- "cloud.api_key_provided": true,
59
- "cloud.public_api_key": "pk_test_123",
60
- });
61
-
62
- expect(segmentTrackMock).toHaveBeenCalledTimes(1);
63
- const call = segmentTrackMock.mock.calls[0][0];
64
- expect(call.event).toBe("oss.runtime.instance_created");
65
- expect(call.anonymousId).toMatch(/^anon_/);
66
- expect(call.properties).toMatchObject({
67
- actionsAmount: 5,
68
- endpointsAmount: 2,
69
- "cloud.api_key_provided": true,
70
- "cloud.public_api_key": "pk_test_123",
71
- });
72
- });
73
-
74
35
  it("only sends event name to scarf, not properties", async () => {
75
36
  const client = new TelemetryClient({
76
37
  telemetryDisabled: false,
@@ -106,7 +67,6 @@ describe("TelemetryClient", () => {
106
67
  });
107
68
 
108
69
  expect(scarfSpy).not.toHaveBeenCalled();
109
- expect(segmentTrackMock).not.toHaveBeenCalled();
110
70
  });
111
71
 
112
72
  it("does not send events when sampled out", async () => {
@@ -124,7 +84,6 @@ describe("TelemetryClient", () => {
124
84
  });
125
85
 
126
86
  expect(scarfSpy).not.toHaveBeenCalled();
127
- expect(segmentTrackMock).not.toHaveBeenCalled();
128
87
  });
129
88
 
130
89
  it("respects sample rate boundary", async () => {
@@ -139,26 +98,6 @@ describe("TelemetryClient", () => {
139
98
  expect(scarfSpy).toHaveBeenCalled();
140
99
  });
141
100
 
142
- it("includes global properties in segment track call", async () => {
143
- vi.spyOn(Math, "random").mockReturnValue(0);
144
- const client = new TelemetryClient({
145
- telemetryDisabled: false,
146
- sampleRate: 1,
147
- });
148
-
149
- client.setGlobalProperties({ "copilotkit.package.name": "test-pkg" });
150
-
151
- await client.capture("oss.runtime.instance_created", {
152
- actionsAmount: 1,
153
- endpointTypes: [],
154
- endpointsAmount: 0,
155
- "cloud.api_key_provided": false,
156
- });
157
-
158
- const call = segmentTrackMock.mock.calls[0][0];
159
- expect(call.properties["copilotkit.package.name"]).toBe("test-pkg");
160
- });
161
-
162
101
  it("throws when sample rate is out of range", () => {
163
102
  expect(() => new TelemetryClient({ sampleRate: 1.5 })).toThrow(
164
103
  "Sample rate must be between 0 and 1",
@@ -0,0 +1,156 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { EventType } from "@ag-ui/client";
3
+ import type { BaseEvent } from "@ag-ui/client";
4
+ import type { DebugEventEnvelope } from "@copilotkit/shared";
5
+ import { DebugEventBus } from "../debug-event-bus";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Helpers
9
+ // ---------------------------------------------------------------------------
10
+
11
+ function createBaseEvent(
12
+ overrides: Partial<BaseEvent> & { type: EventType } = {
13
+ type: EventType.RUN_STARTED,
14
+ },
15
+ ): BaseEvent {
16
+ return { type: overrides.type, ...overrides };
17
+ }
18
+
19
+ const defaultMetadata = {
20
+ agentId: "test-agent",
21
+ threadId: "thread-1",
22
+ runId: "run-1",
23
+ };
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Tests
27
+ // ---------------------------------------------------------------------------
28
+
29
+ describe("DebugEventBus", () => {
30
+ it("subscribe adds a listener and broadcast calls it with the correct envelope shape", () => {
31
+ const bus = new DebugEventBus();
32
+ const listener = vi.fn<[DebugEventEnvelope], void>();
33
+ const event = createBaseEvent({ type: EventType.RUN_STARTED });
34
+
35
+ bus.subscribe(listener);
36
+ bus.broadcast(event, defaultMetadata);
37
+
38
+ expect(listener).toHaveBeenCalledOnce();
39
+ const envelope = listener.mock.calls[0][0];
40
+ expect(envelope).toEqual(
41
+ expect.objectContaining({
42
+ agentId: "test-agent",
43
+ threadId: "thread-1",
44
+ runId: "run-1",
45
+ event,
46
+ }),
47
+ );
48
+ expect(typeof envelope.timestamp).toBe("number");
49
+ });
50
+
51
+ it("unsubscribe removes the listener so subsequent broadcasts don't reach it", () => {
52
+ const bus = new DebugEventBus();
53
+ const listener = vi.fn<[DebugEventEnvelope], void>();
54
+ const event = createBaseEvent();
55
+
56
+ const unsub = bus.subscribe(listener);
57
+ unsub();
58
+ bus.broadcast(event, defaultMetadata);
59
+
60
+ expect(listener).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it("multiple listeners all receive the same broadcast", () => {
64
+ const bus = new DebugEventBus();
65
+ const listenerA = vi.fn<[DebugEventEnvelope], void>();
66
+ const listenerB = vi.fn<[DebugEventEnvelope], void>();
67
+ const event = createBaseEvent();
68
+
69
+ bus.subscribe(listenerA);
70
+ bus.subscribe(listenerB);
71
+ bus.broadcast(event, defaultMetadata);
72
+
73
+ expect(listenerA).toHaveBeenCalledOnce();
74
+ expect(listenerB).toHaveBeenCalledOnce();
75
+ // Both receive the same envelope object
76
+ expect(listenerA.mock.calls[0][0]).toBe(listenerB.mock.calls[0][0]);
77
+ });
78
+
79
+ it("listener errors are swallowed and other listeners still receive the event", () => {
80
+ const bus = new DebugEventBus();
81
+ const failingListener = vi
82
+ .fn<[DebugEventEnvelope], void>()
83
+ .mockImplementation(() => {
84
+ throw new Error("boom");
85
+ });
86
+ const healthyListener = vi.fn<[DebugEventEnvelope], void>();
87
+ const event = createBaseEvent();
88
+
89
+ bus.subscribe(failingListener);
90
+ bus.subscribe(healthyListener);
91
+
92
+ // Should not throw
93
+ expect(() => bus.broadcast(event, defaultMetadata)).not.toThrow();
94
+ expect(failingListener).toHaveBeenCalledOnce();
95
+ expect(healthyListener).toHaveBeenCalledOnce();
96
+ });
97
+
98
+ it("broadcasting with no listeners does not throw", () => {
99
+ const bus = new DebugEventBus();
100
+ const event = createBaseEvent();
101
+
102
+ expect(() => bus.broadcast(event, defaultMetadata)).not.toThrow();
103
+ });
104
+
105
+ it("listenerCount reflects current count after subscribe and unsubscribe", () => {
106
+ const bus = new DebugEventBus();
107
+
108
+ expect(bus.listenerCount).toBe(0);
109
+
110
+ const unsub1 = bus.subscribe(vi.fn());
111
+ expect(bus.listenerCount).toBe(1);
112
+
113
+ const unsub2 = bus.subscribe(vi.fn());
114
+ expect(bus.listenerCount).toBe(2);
115
+
116
+ unsub1();
117
+ expect(bus.listenerCount).toBe(1);
118
+
119
+ unsub2();
120
+ expect(bus.listenerCount).toBe(0);
121
+ });
122
+
123
+ it("envelope has correct timestamp, agentId, threadId, runId, and the original event", () => {
124
+ const bus = new DebugEventBus();
125
+ const listener = vi.fn<[DebugEventEnvelope], void>();
126
+ const event = createBaseEvent({ type: EventType.STEP_STARTED });
127
+ const metadata = { agentId: "agent-x", threadId: "t-42", runId: "r-99" };
128
+
129
+ const before = Date.now();
130
+ bus.subscribe(listener);
131
+ bus.broadcast(event, metadata);
132
+ const after = Date.now();
133
+
134
+ const envelope = listener.mock.calls[0][0];
135
+ expect(envelope.agentId).toBe("agent-x");
136
+ expect(envelope.threadId).toBe("t-42");
137
+ expect(envelope.runId).toBe("r-99");
138
+ expect(envelope.event).toBe(event);
139
+ expect(envelope.timestamp).toBeGreaterThanOrEqual(before);
140
+ expect(envelope.timestamp).toBeLessThanOrEqual(after);
141
+ });
142
+
143
+ it("RUN_STARTED event type passthrough: envelope.event is the original object, not a copy", () => {
144
+ const bus = new DebugEventBus();
145
+ const listener = vi.fn<[DebugEventEnvelope], void>();
146
+ const event = createBaseEvent({ type: EventType.RUN_STARTED });
147
+
148
+ bus.subscribe(listener);
149
+ bus.broadcast(event, defaultMetadata);
150
+
151
+ const envelope = listener.mock.calls[0][0];
152
+ // Strict referential equality — the event is passed through, not cloned
153
+ expect(envelope.event).toBe(event);
154
+ expect(envelope.event.type).toBe(EventType.RUN_STARTED);
155
+ });
156
+ });
@@ -0,0 +1,45 @@
1
+ import { BaseEvent } from "@ag-ui/client";
2
+ import { DebugEventEnvelope } from "@copilotkit/shared";
3
+
4
+ export type DebugEventListener = (envelope: DebugEventEnvelope) => void;
5
+
6
+ export class DebugEventBus {
7
+ private listeners = new Set<DebugEventListener>();
8
+
9
+ subscribe(listener: DebugEventListener): () => void {
10
+ this.listeners.add(listener);
11
+ return () => {
12
+ this.listeners.delete(listener);
13
+ };
14
+ }
15
+
16
+ broadcast(
17
+ event: BaseEvent,
18
+ metadata: { agentId: string; threadId: string; runId: string },
19
+ ): void {
20
+ if (this.listeners.size === 0) return;
21
+
22
+ const envelope: DebugEventEnvelope = {
23
+ timestamp: Date.now(),
24
+ agentId: metadata.agentId,
25
+ threadId: metadata.threadId,
26
+ runId: metadata.runId,
27
+ event,
28
+ };
29
+
30
+ for (const listener of this.listeners) {
31
+ try {
32
+ listener(envelope);
33
+ } catch (err) {
34
+ console.warn(
35
+ "[DebugEventBus] Listener error suppressed:",
36
+ err instanceof Error ? err.message : err,
37
+ );
38
+ }
39
+ }
40
+ }
41
+
42
+ get listenerCount(): number {
43
+ return this.listeners.size;
44
+ }
45
+ }
@@ -46,6 +46,7 @@ import { handleConnectAgent } from "../handlers/handle-connect";
46
46
  import { handleStopAgent } from "../handlers/handle-stop";
47
47
  import { handleGetRuntimeInfo } from "../handlers/get-runtime-info";
48
48
  import { handleTranscribe } from "../handlers/handle-transcribe";
49
+ import { handleDebugEvents } from "../handlers/handle-debug-events";
49
50
  import {
50
51
  handleListThreads,
51
52
  handleSubscribeToThreads,
@@ -61,6 +62,7 @@ import {
61
62
  type MethodCall,
62
63
  } from "../endpoints/single-route-helpers";
63
64
  import { logger } from "@copilotkit/shared";
65
+ import { fireInstanceCreatedTelemetry } from "../telemetry/instance-created";
64
66
 
65
67
  /* ------------------------------------------------------------------------------------------------
66
68
  * Public types
@@ -113,6 +115,8 @@ export function createCopilotRuntimeHandler(
113
115
  ): CopilotRuntimeFetchHandler {
114
116
  const { runtime, basePath, mode = "multi-route", cors, hooks } = options;
115
117
 
118
+ fireInstanceCreatedTelemetry({ runtime });
119
+
116
120
  const corsConfig = resolveCorsConfig(cors);
117
121
 
118
122
  return async (request: Request): Promise<Response> => {
@@ -339,6 +343,8 @@ function dispatchRoute(
339
343
  request,
340
344
  threadId: route.threadId,
341
345
  });
346
+ case "cpk-debug-events":
347
+ return Promise.resolve(handleDebugEvents({ runtime, request }));
342
348
  }
343
349
  }
344
350
 
@@ -414,6 +420,7 @@ function validateHttpMethod(
414
420
  case "info":
415
421
  case "threads/list":
416
422
  case "threads/messages":
423
+ case "cpk-debug-events":
417
424
  if (method === "GET") return null;
418
425
  return jsonResponse({ error: "Method not allowed" }, 405, {
419
426
  Allow: "GET",
@@ -74,6 +74,17 @@ function matchSegments(path: string): RouteInfo | null {
74
74
  return { method: "transcribe" };
75
75
  }
76
76
 
77
+ // /cpk-debug-events (1 segment)
78
+ // Reserved route name: the `cpk-` prefix makes collision with a
79
+ // user-named agent essentially impossible (the router only treats
80
+ // `agent/:agentId/...` patterns as agent lookups, so a bare
81
+ // `cpk-debug-events` segment would never fall through to one —
82
+ // the prefix is the real guard, not this branch's position).
83
+ // Handler returns 404 in production.
84
+ if (len >= 1 && segments[len - 1] === "cpk-debug-events") {
85
+ return { method: "cpk-debug-events" };
86
+ }
87
+
77
88
  // /agent/:agentId/run (3 segments)
78
89
  if (
79
90
  len >= 3 &&
@@ -43,7 +43,8 @@ export type RouteInfo =
43
43
  | { method: "threads/subscribe" }
44
44
  | { method: "threads/update"; threadId: string }
45
45
  | { method: "threads/archive"; threadId: string }
46
- | { method: "threads/messages"; threadId: string };
46
+ | { method: "threads/messages"; threadId: string }
47
+ | { method: "cpk-debug-events" };
47
48
 
48
49
  /* ------------------------------------------------------------------------------------------------
49
50
  * Hook contexts