@copilotkit/runtime 1.56.3 → 1.56.4

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 (92) hide show
  1. package/dist/package.cjs +1 -1
  2. package/dist/package.mjs +1 -1
  3. package/dist/v2/index.d.cts +2 -2
  4. package/dist/v2/index.d.mts +2 -2
  5. package/dist/v2/runtime/core/fetch-handler.cjs +2 -0
  6. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  7. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  8. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  9. package/dist/v2/runtime/core/fetch-handler.mjs +2 -0
  10. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  11. package/dist/v2/runtime/core/runtime.d.mts +0 -1
  12. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  13. package/dist/v2/runtime/handlers/handle-connect.cjs +2 -3
  14. package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
  15. package/dist/v2/runtime/handlers/handle-connect.mjs +2 -3
  16. package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
  17. package/dist/v2/runtime/handlers/intelligence/connect.cjs +21 -31
  18. package/dist/v2/runtime/handlers/intelligence/connect.cjs.map +1 -1
  19. package/dist/v2/runtime/handlers/intelligence/connect.mjs +22 -31
  20. package/dist/v2/runtime/handlers/intelligence/connect.mjs.map +1 -1
  21. package/dist/v2/runtime/handlers/intelligence/run.cjs +111 -26
  22. package/dist/v2/runtime/handlers/intelligence/run.cjs.map +1 -1
  23. package/dist/v2/runtime/handlers/intelligence/run.mjs +111 -26
  24. package/dist/v2/runtime/handlers/intelligence/run.mjs.map +1 -1
  25. package/dist/v2/runtime/handlers/shared/intelligence-utils.cjs +7 -3
  26. package/dist/v2/runtime/handlers/shared/intelligence-utils.cjs.map +1 -1
  27. package/dist/v2/runtime/handlers/shared/intelligence-utils.mjs +7 -3
  28. package/dist/v2/runtime/handlers/shared/intelligence-utils.mjs.map +1 -1
  29. package/dist/v2/runtime/index.d.cts +1 -1
  30. package/dist/v2/runtime/index.d.mts +1 -2
  31. package/dist/v2/runtime/index.d.mts.map +1 -1
  32. package/dist/v2/runtime/intelligence-platform/client.cjs +5 -2
  33. package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
  34. package/dist/v2/runtime/intelligence-platform/client.d.cts +16 -18
  35. package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
  36. package/dist/v2/runtime/intelligence-platform/client.d.mts +16 -18
  37. package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
  38. package/dist/v2/runtime/intelligence-platform/client.mjs +5 -2
  39. package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
  40. package/dist/v2/runtime/runner/agent-runner.cjs.map +1 -1
  41. package/dist/v2/runtime/runner/agent-runner.d.cts +0 -1
  42. package/dist/v2/runtime/runner/agent-runner.d.cts.map +1 -1
  43. package/dist/v2/runtime/runner/agent-runner.d.mts +0 -1
  44. package/dist/v2/runtime/runner/agent-runner.d.mts.map +1 -1
  45. package/dist/v2/runtime/runner/agent-runner.mjs.map +1 -1
  46. package/dist/v2/runtime/runner/index.d.cts +1 -1
  47. package/dist/v2/runtime/runner/index.d.mts +1 -1
  48. package/dist/v2/runtime/runner/intelligence.cjs +30 -5
  49. package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
  50. package/dist/v2/runtime/runner/intelligence.d.cts +7 -1
  51. package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
  52. package/dist/v2/runtime/runner/intelligence.d.mts +7 -1
  53. package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
  54. package/dist/v2/runtime/runner/intelligence.mjs +30 -5
  55. package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
  56. package/dist/v2/runtime/telemetry/instance-created.cjs +33 -0
  57. package/dist/v2/runtime/telemetry/instance-created.cjs.map +1 -0
  58. package/dist/v2/runtime/telemetry/instance-created.mjs +33 -0
  59. package/dist/v2/runtime/telemetry/instance-created.mjs.map +1 -0
  60. package/dist/v2/runtime/telemetry/telemetry-client.cjs +1 -38
  61. package/dist/v2/runtime/telemetry/telemetry-client.cjs.map +1 -1
  62. package/dist/v2/runtime/telemetry/telemetry-client.mjs +1 -37
  63. package/dist/v2/runtime/telemetry/telemetry-client.mjs.map +1 -1
  64. package/package.json +2 -2
  65. package/src/v2/runtime/__tests__/express-single-telemetry.integration.test.ts +65 -0
  66. package/src/v2/runtime/__tests__/express-telemetry.integration.test.ts +101 -0
  67. package/src/v2/runtime/__tests__/handle-connect.test.ts +155 -48
  68. package/src/v2/runtime/__tests__/handle-run.test.ts +380 -29
  69. package/src/v2/runtime/__tests__/hono-single-telemetry.integration.test.ts +46 -0
  70. package/src/v2/runtime/__tests__/hono-telemetry.integration.test.ts +99 -0
  71. package/src/v2/runtime/__tests__/intelligence-run-telemetry.test.ts +194 -0
  72. package/src/v2/runtime/__tests__/sse-response-telemetry.test.ts +108 -0
  73. package/src/v2/runtime/__tests__/telemetry.test.ts +0 -61
  74. package/src/v2/runtime/core/fetch-handler.ts +3 -0
  75. package/src/v2/runtime/handlers/handle-connect.ts +1 -2
  76. package/src/v2/runtime/handlers/intelligence/connect.ts +48 -68
  77. package/src/v2/runtime/handlers/intelligence/run.ts +162 -21
  78. package/src/v2/runtime/handlers/shared/intelligence-utils.ts +21 -1
  79. package/src/v2/runtime/intelligence-platform/__tests__/client.test.ts +33 -39
  80. package/src/v2/runtime/intelligence-platform/client.ts +36 -31
  81. package/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts +15 -7
  82. package/src/v2/runtime/runner/agent-runner.ts +0 -1
  83. package/src/v2/runtime/runner/intelligence.ts +47 -6
  84. package/src/v2/runtime/telemetry/__tests__/instance-created.test.ts +96 -0
  85. package/src/v2/runtime/telemetry/instance-created.ts +44 -0
  86. package/src/v2/runtime/telemetry/telemetry-client.ts +1 -57
  87. package/dist/v2/runtime/intelligence-platform/index.d.mts +0 -2
  88. package/dist/v2/runtime/telemetry/utils.cjs +0 -15
  89. package/dist/v2/runtime/telemetry/utils.cjs.map +0 -1
  90. package/dist/v2/runtime/telemetry/utils.mjs +0 -14
  91. package/dist/v2/runtime/telemetry/utils.mjs.map +0 -1
  92. package/src/v2/runtime/telemetry/utils.ts +0 -15
@@ -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
  },
@@ -1,7 +1,27 @@
1
1
  import { PlatformRequestError } from "../../intelligence-platform/client";
2
2
 
3
+ /**
4
+ * Returns the HTTP status carried by platform request errors.
5
+ */
6
+ export function getPlatformErrorStatus(error: unknown): number | undefined {
7
+ if (error instanceof PlatformRequestError) {
8
+ return error.status;
9
+ }
10
+
11
+ if (
12
+ error !== null &&
13
+ typeof error === "object" &&
14
+ "status" in error &&
15
+ typeof error.status === "number"
16
+ ) {
17
+ return error.status;
18
+ }
19
+
20
+ return undefined;
21
+ }
22
+
3
23
  export function isPlatformNotFoundError(error: unknown): boolean {
4
- return error instanceof PlatformRequestError && error.status === 404;
24
+ return getPlatformErrorStatus(error) === 404;
5
25
  }
6
26
 
7
27
  const MAX_ID_LENGTH = 128;
@@ -385,24 +385,34 @@ describe("CopilotKitIntelligence", () => {
385
385
  });
386
386
 
387
387
  describe("acquireThreadLock", () => {
388
- it("sends POST to lock endpoint and returns thread connection credentials", async () => {
388
+ it("sends POST to lock endpoint and returns canonical run credentials", async () => {
389
389
  fetchMock.mockReturnValue(
390
- jsonResponse({ joinToken: "jt-lock", joinCode: "jc-lock" }),
390
+ jsonResponse({
391
+ threadId: "t-1",
392
+ runId: "r-1",
393
+ joinToken: "jt-lock",
394
+ }),
391
395
  );
392
396
 
393
397
  const result = await client.ɵacquireThreadLock({
394
398
  threadId: "t-1",
395
399
  runId: "r-1",
396
400
  userId: "user-1",
401
+ agentId: "agent-1",
397
402
  });
398
403
 
399
- expect(result).toEqual({ joinToken: "jt-lock", joinCode: "jc-lock" });
404
+ expect(result).toEqual({
405
+ threadId: "t-1",
406
+ runId: "r-1",
407
+ joinToken: "jt-lock",
408
+ });
400
409
  const [url, opts] = fetchMock.mock.calls[0];
401
410
  expect(url).toBe("https://api.example.com/api/threads/t-1/lock");
402
411
  expect(opts.method).toBe("POST");
403
412
  expect(JSON.parse(opts.body)).toEqual({
404
413
  runId: "r-1",
405
414
  userId: "user-1",
415
+ agentId: "agent-1",
406
416
  });
407
417
  });
408
418
 
@@ -413,9 +423,24 @@ describe("CopilotKitIntelligence", () => {
413
423
  threadId: "t-1",
414
424
  runId: "r-1",
415
425
  userId: "user-1",
426
+ agentId: "agent-1",
416
427
  }),
417
428
  ).rejects.toThrow(/409/);
418
429
  });
430
+
431
+ it("sends compare-delete cleanup to the lock endpoint", async () => {
432
+ fetchMock.mockReturnValue(emptyResponse());
433
+
434
+ await client.ɵcleanupThreadLock({
435
+ threadId: "t-1",
436
+ runId: "r-1",
437
+ });
438
+
439
+ const [url, opts] = fetchMock.mock.calls[0];
440
+ expect(url).toBe("https://api.example.com/api/threads/t-1/lock");
441
+ expect(opts.method).toBe("DELETE");
442
+ expect(JSON.parse(opts.body)).toEqual({ runId: "r-1" });
443
+ });
419
444
  });
420
445
 
421
446
  describe("getActiveJoinCode", () => {
@@ -544,8 +569,7 @@ describe("CopilotKitIntelligence", () => {
544
569
  const result = await client.ɵconnectThread({
545
570
  threadId: "t-1",
546
571
  userId: "user-1",
547
- runId: "run-1",
548
- lastSeenEventId: "event-1",
572
+ agentId: "agent-1",
549
573
  });
550
574
 
551
575
  expect(result).toBeNull();
@@ -554,51 +578,21 @@ describe("CopilotKitIntelligence", () => {
554
578
  expect(opts.method).toBe("POST");
555
579
  expect(JSON.parse(opts.body)).toEqual({
556
580
  userId: "user-1",
557
- runId: "run-1",
558
- lastSeenEventId: "event-1",
581
+ agentId: "agent-1",
559
582
  });
560
583
  });
561
584
 
562
- it("returns a bootstrap connect plan", async () => {
585
+ it("returns credentials-only connect response", async () => {
563
586
  const payload = {
564
- mode: "bootstrap",
565
- latestEventId: "event-2",
566
- events: [
567
- {
568
- type: "RUN_STARTED",
569
- threadId: "t-1",
570
- run_id: "backend-run-1",
571
- input: { messages: [] },
572
- },
573
- { type: "RUN_FINISHED" },
574
- ],
575
- };
576
- fetchMock.mockReturnValue(jsonResponse(payload));
577
-
578
- const result = await client.ɵconnectThread({
579
587
  threadId: "t-1",
580
- userId: "user-1",
581
- runId: "run-1",
582
- lastSeenEventId: "event-1",
583
- });
584
-
585
- expect(result).toEqual(payload);
586
- });
587
-
588
- it("returns a live connect plan", async () => {
589
- const payload = {
590
- mode: "live",
591
- joinToken: "jt-live",
592
- joinFromEventId: "event-2",
593
- events: [],
588
+ joinToken: "jt-connect",
594
589
  };
595
590
  fetchMock.mockReturnValue(jsonResponse(payload));
596
591
 
597
592
  const result = await client.ɵconnectThread({
598
593
  threadId: "t-1",
599
594
  userId: "user-1",
600
- runId: "run-1",
601
- lastSeenEventId: "event-2",
595
+ agentId: "agent-1",
602
596
  });
603
597
 
604
598
  expect(result).toEqual(payload);
@@ -1,5 +1,4 @@
1
1
  import { logger } from "@copilotkit/shared";
2
- import type { BaseEvent } from "@ag-ui/client";
3
2
 
4
3
  /**
5
4
  * Error thrown when an Intelligence platform HTTP request returns a non-2xx
@@ -150,10 +149,12 @@ export interface CreateThreadRequest {
150
149
 
151
150
  /** Credentials returned when locking or joining a thread's realtime channel. */
152
151
  export interface ThreadConnectionResponse {
152
+ /** Canonical platform thread identifier for the run or connection. */
153
+ threadId: string;
154
+ /** Canonical platform run identifier for an active run lock. */
155
+ runId?: string;
153
156
  /** Short-lived token for authenticating the Phoenix channel join. */
154
157
  joinToken: string;
155
- /** Optional join code that can be shared with other clients to join the same channel. */
156
- joinCode?: string;
157
158
  /** Lock metadata echoed back by the platform. */
158
159
  lock?: ThreadLockInfo;
159
160
  }
@@ -166,24 +167,13 @@ export interface SubscribeToThreadsResponse {
166
167
  joinToken: string;
167
168
  }
168
169
 
169
- export interface ConnectThreadBootstrapResponse {
170
- mode: "bootstrap";
171
- latestEventId: string | null;
172
- events: BaseEvent[];
173
- }
170
+ export type ConnectThreadResponse = ThreadConnectionResponse | null;
174
171
 
175
- export interface ConnectThreadLiveResponse {
176
- mode: "live";
177
- joinToken: string;
178
- joinFromEventId: string | null;
179
- events: BaseEvent[];
172
+ export interface AcquireThreadLockResponse extends ThreadConnectionResponse {
173
+ /** Canonical platform run identifier for the acquired lock. */
174
+ runId: string;
180
175
  }
181
176
 
182
- export type ConnectThreadResponse =
183
- | ConnectThreadBootstrapResponse
184
- | ConnectThreadLiveResponse
185
- | null;
186
-
187
177
  /** A single message within a thread's persisted history. */
188
178
  export interface ThreadMessage {
189
179
  /** Unique identifier for this message. */
@@ -212,6 +202,7 @@ export interface AcquireThreadLockRequest {
212
202
  threadId: string;
213
203
  runId: string;
214
204
  userId: string;
205
+ agentId: string;
215
206
  /** Custom Redis key prefix for the lock (default: "thread"). */
216
207
  lockKeyPrefix?: string;
217
208
  /** Lock TTL in seconds. When set, the lock auto-expires after this duration. */
@@ -227,6 +218,11 @@ export interface RenewThreadLockRequest {
227
218
  lockKeyPrefix?: string;
228
219
  }
229
220
 
221
+ export interface CleanupThreadLockRequest {
222
+ threadId: string;
223
+ runId: string;
224
+ }
225
+
230
226
  export interface RenewThreadLockResponse {
231
227
  ttlSeconds: number;
232
228
  }
@@ -606,13 +602,14 @@ export class CopilotKitIntelligence {
606
602
 
607
603
  async ɵacquireThreadLock(
608
604
  params: AcquireThreadLockRequest,
609
- ): Promise<ThreadConnectionResponse> {
610
- return this.#request<ThreadConnectionResponse>(
605
+ ): Promise<AcquireThreadLockResponse> {
606
+ return this.#request<AcquireThreadLockResponse>(
611
607
  "POST",
612
608
  `/api/threads/${encodeURIComponent(params.threadId)}/lock`,
613
609
  {
614
610
  runId: params.runId,
615
611
  userId: params.userId,
612
+ agentId: params.agentId,
616
613
  ...(params.lockKeyPrefix !== undefined
617
614
  ? { lockKeyPrefix: params.lockKeyPrefix }
618
615
  : {}),
@@ -623,6 +620,16 @@ export class CopilotKitIntelligence {
623
620
  );
624
621
  }
625
622
 
623
+ async ɵcleanupThreadLock(params: CleanupThreadLockRequest): Promise<void> {
624
+ return this.#request<void>(
625
+ "DELETE",
626
+ `/api/threads/${encodeURIComponent(params.threadId)}/lock`,
627
+ {
628
+ runId: params.runId,
629
+ },
630
+ );
631
+ }
632
+
626
633
  async ɵrenewThreadLock(
627
634
  params: RenewThreadLockRequest,
628
635
  ): Promise<RenewThreadLockResponse> {
@@ -653,18 +660,16 @@ export class CopilotKitIntelligence {
653
660
  async ɵconnectThread(params: {
654
661
  threadId: string;
655
662
  userId: string;
656
- runId?: string;
657
- lastSeenEventId?: string | null;
663
+ agentId: string;
658
664
  }): Promise<ConnectThreadResponse> {
659
- const result = await this.#request<
660
- ConnectThreadBootstrapResponse | ConnectThreadLiveResponse
661
- >("POST", `/api/threads/${encodeURIComponent(params.threadId)}/connect`, {
662
- userId: params.userId,
663
- ...(params.runId !== undefined ? { runId: params.runId } : {}),
664
- ...(params.lastSeenEventId !== undefined
665
- ? { lastSeenEventId: params.lastSeenEventId }
666
- : {}),
667
- });
665
+ const result = await this.#request<ThreadConnectionResponse>(
666
+ "POST",
667
+ `/api/threads/${encodeURIComponent(params.threadId)}/connect`,
668
+ {
669
+ userId: params.userId,
670
+ agentId: params.agentId,
671
+ },
672
+ );
668
673
 
669
674
  // request() returns undefined for empty/204 responses
670
675
  return result ?? null;
@@ -619,10 +619,9 @@ describe("IntelligenceAgentRunner", () => {
619
619
  });
620
620
  });
621
621
 
622
- describe("run with joinCode", () => {
623
- it("uses joinCode for the channel topic when provided", async () => {
622
+ describe("run channel ownership", () => {
623
+ it("uses runId for the ingestion channel topic", async () => {
624
624
  const threadId = "t-jc";
625
- const joinCode = "join-abc-123";
626
625
  const input = createRunInput({ threadId, runId: "r-jc" });
627
626
  const agent = new MockAgent([
628
627
  {
@@ -633,15 +632,16 @@ describe("IntelligenceAgentRunner", () => {
633
632
  ]);
634
633
 
635
634
  const eventsPromise = collectEvents(
636
- runner.run({ threadId, agent, input, joinCode }),
635
+ runner.run({ threadId, agent, input }),
637
636
  );
638
637
  const ch = mockChannels[0];
639
- expect(ch.topic).toBe(`ingestion:${joinCode}`);
638
+ expect(ch.topic).toBe("ingestion:r-jc");
639
+ expect(ch.params).toEqual({ thread_id: threadId, run_id: "r-jc" });
640
640
  ch.triggerJoin("ok");
641
641
  await eventsPromise;
642
642
  });
643
643
 
644
- it("falls back to threadId when joinCode is not provided", async () => {
644
+ it("keeps pushed event payload ownership on canonical threadId and runId", async () => {
645
645
  const threadId = "t-no-jc";
646
646
  const input = createRunInput({ threadId, runId: "r-no-jc" });
647
647
  const agent = new MockAgent([
@@ -656,9 +656,17 @@ describe("IntelligenceAgentRunner", () => {
656
656
  runner.run({ threadId, agent, input }),
657
657
  );
658
658
  const ch = mockChannels[0];
659
- expect(ch.topic).toBe(`ingestion:${threadId}`);
660
659
  ch.triggerJoin("ok");
661
660
  await eventsPromise;
661
+
662
+ expect(ch.pushLog[0].payload).toEqual(
663
+ expect.objectContaining({
664
+ threadId,
665
+ runId: "r-no-jc",
666
+ thread_id: threadId,
667
+ run_id: "r-no-jc",
668
+ }),
669
+ );
662
670
  });
663
671
  });
664
672
 
@@ -10,7 +10,6 @@ export interface AgentRunnerRunRequest {
10
10
  threadId: string;
11
11
  agent: AbstractAgent;
12
12
  input: RunAgentInput;
13
- joinCode?: string;
14
13
  persistedInputMessages?: Message[];
15
14
  }
16
15