@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,6 +1,6 @@
1
1
  import { Observable } from "rxjs";
2
2
  import { describe, it, expect, vi } from "vitest";
3
- import { AbstractAgent, HttpAgent } from "@ag-ui/client";
3
+ import { AbstractAgent, BaseEvent, EventType, HttpAgent } from "@ag-ui/client";
4
4
  import { A2UIMiddleware } from "@ag-ui/a2ui-middleware";
5
5
  import { handleRunAgent } from "../handlers/handle-run";
6
6
  import { CopilotRuntime } from "../core/runtime";
@@ -285,7 +285,7 @@ describe("handleRunAgent", () => {
285
285
  expect(useSpy).not.toHaveBeenCalled();
286
286
  });
287
287
 
288
- describe("IntelligenceAgentRunner join code path", () => {
288
+ describe("IntelligenceAgentRunner realtime credentials path", () => {
289
289
  /** Loose mock type for CopilotKitIntelligence — avoids `as any` while the class has private fields. */
290
290
  interface MockIntelligencePlatform {
291
291
  [key: string]: ((...args: any[]) => any) | undefined;
@@ -296,6 +296,8 @@ describe("handleRunAgent", () => {
296
296
  platform?: MockIntelligencePlatform,
297
297
  options?: {
298
298
  generateThreadNames?: boolean;
299
+ lockHeartbeatIntervalSeconds?: number;
300
+ lockTtlSeconds?: number;
299
301
  identifyUser?: (
300
302
  request: Request,
301
303
  ) =>
@@ -318,7 +320,13 @@ describe("handleRunAgent", () => {
318
320
  runner,
319
321
  mode: "intelligence",
320
322
  generateThreadNames: options?.generateThreadNames ?? false,
321
- intelligence: platform,
323
+ lockHeartbeatIntervalSeconds:
324
+ options?.lockHeartbeatIntervalSeconds ?? 15,
325
+ lockTtlSeconds: options?.lockTtlSeconds ?? 20,
326
+ intelligence: {
327
+ ɵgetClientWsUrl: vi.fn(() => "wss://runtime.example/client"),
328
+ ...platform,
329
+ },
322
330
  identifyUser:
323
331
  options?.identifyUser ??
324
332
  vi.fn().mockResolvedValue({ id: "user-1", name: "User One" }),
@@ -331,6 +339,7 @@ describe("handleRunAgent", () => {
331
339
  clone: vi.fn(() => createClone()),
332
340
  setMessages: vi.fn(),
333
341
  setState: vi.fn(),
342
+ abortRun: vi.fn(),
334
343
  threadId: undefined,
335
344
  headers: {},
336
345
  runAgent: vi.fn().mockResolvedValue(undefined),
@@ -340,6 +349,7 @@ describe("handleRunAgent", () => {
340
349
  clone: vi.fn(() => createClone()),
341
350
  setMessages: vi.fn(),
342
351
  setState: vi.fn(),
352
+ abortRun: vi.fn(),
343
353
  threadId: undefined,
344
354
  headers: {},
345
355
  runAgent: vi.fn().mockResolvedValue(undefined),
@@ -355,9 +365,12 @@ describe("handleRunAgent", () => {
355
365
  created: false,
356
366
  }),
357
367
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
358
- ɵacquireThreadLock: vi
359
- .fn()
360
- .mockResolvedValue({ joinToken: "jt-123", joinCode: "jc-123" }),
368
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
369
+ threadId: "thread-1",
370
+ runId: "run-1",
371
+ joinToken: "jt-123",
372
+ }),
373
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
361
374
  };
362
375
  const runtime = createIntelligenceRuntime(agent, platform);
363
376
 
@@ -370,7 +383,15 @@ describe("handleRunAgent", () => {
370
383
  expect(response.status).toBe(200);
371
384
  expect(response.headers.get("Content-Type")).toBe("application/json");
372
385
  const body = await response.json();
373
- expect(body).toEqual({ joinToken: "jt-123" });
386
+ expect(body).toEqual({
387
+ threadId: "thread-1",
388
+ runId: "run-1",
389
+ joinToken: "jt-123",
390
+ realtime: {
391
+ clientUrl: "wss://runtime.example/client",
392
+ topic: "thread:thread-1",
393
+ },
394
+ });
374
395
  expect(platform.getOrCreateThread).toHaveBeenCalledWith({
375
396
  threadId: "thread-1",
376
397
  userId: "user-1",
@@ -380,6 +401,8 @@ describe("handleRunAgent", () => {
380
401
  threadId: "thread-1",
381
402
  runId: "run-1",
382
403
  userId: "user-1",
404
+ agentId: "my-agent",
405
+ ttlSeconds: 20,
383
406
  });
384
407
  expect(platform.getThreadMessages).toHaveBeenCalledWith({
385
408
  threadId: "thread-1",
@@ -394,9 +417,12 @@ describe("handleRunAgent", () => {
394
417
  created: false,
395
418
  }),
396
419
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
397
- ɵacquireThreadLock: vi
398
- .fn()
399
- .mockResolvedValue({ joinToken: "jt-123", joinCode: "jc-123" }),
420
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
421
+ threadId: "thread-1",
422
+ runId: "run-1",
423
+ joinToken: "jt-123",
424
+ }),
425
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
400
426
  };
401
427
  const identifyUser = vi
402
428
  .fn()
@@ -424,10 +450,12 @@ describe("handleRunAgent", () => {
424
450
  threadId: "thread-1",
425
451
  runId: "run-1",
426
452
  userId: "resolved-user",
453
+ agentId: "my-agent",
454
+ ttlSeconds: 20,
427
455
  });
428
456
  });
429
457
 
430
- it("passes joinCode to runner.run() when provided", async () => {
458
+ it("starts the runner with canonical threadId and runId from the lock response", async () => {
431
459
  const agent = createAgentForIntelligence();
432
460
  const platform = {
433
461
  getOrCreateThread: vi.fn().mockResolvedValue({
@@ -435,9 +463,11 @@ describe("handleRunAgent", () => {
435
463
  created: false,
436
464
  }),
437
465
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
438
- ɵacquireThreadLock: vi
439
- .fn()
440
- .mockResolvedValue({ joinToken: "jt-456", joinCode: "jc-456" }),
466
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
467
+ threadId: "canonical-thread",
468
+ runId: "canonical-run",
469
+ joinToken: "jt-456",
470
+ }),
441
471
  };
442
472
  const runtime = createIntelligenceRuntime(agent, platform);
443
473
 
@@ -448,11 +478,17 @@ describe("handleRunAgent", () => {
448
478
  });
449
479
 
450
480
  expect(runtime.runner.run).toHaveBeenCalledWith(
451
- expect.objectContaining({ joinCode: "jc-456" }),
481
+ expect.objectContaining({
482
+ threadId: "canonical-thread",
483
+ input: expect.objectContaining({
484
+ threadId: "canonical-thread",
485
+ runId: "canonical-run",
486
+ }),
487
+ }),
452
488
  );
453
489
  });
454
490
 
455
- it("returns 502 when joinToken is missing", async () => {
491
+ it("cleans up the lock and returns 502 when joinToken is missing", async () => {
456
492
  const agent = createAgentForIntelligence();
457
493
  const platform = {
458
494
  getOrCreateThread: vi.fn().mockResolvedValue({
@@ -460,7 +496,11 @@ describe("handleRunAgent", () => {
460
496
  created: false,
461
497
  }),
462
498
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
463
- ɵacquireThreadLock: vi.fn().mockResolvedValue({ joinCode: "jc-789" }),
499
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
500
+ threadId: "thread-1",
501
+ runId: "run-1",
502
+ }),
503
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
464
504
  };
465
505
  const runtime = createIntelligenceRuntime(agent, platform);
466
506
 
@@ -472,7 +512,40 @@ describe("handleRunAgent", () => {
472
512
 
473
513
  expect(response.status).toBe(502);
474
514
  const body = await response.json();
475
- expect(body.error).toBe("Join token not available");
515
+ expect(body.error).toBe("Run connection credentials not available");
516
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledWith({
517
+ threadId: "thread-1",
518
+ runId: "run-1",
519
+ });
520
+ expect(runtime.runner.run).not.toHaveBeenCalled();
521
+ });
522
+
523
+ it("uses the requested lock owner when malformed credentials omit canonical IDs", async () => {
524
+ const agent = createAgentForIntelligence();
525
+ const platform = {
526
+ getOrCreateThread: vi.fn().mockResolvedValue({
527
+ thread: { id: "thread-1", name: null },
528
+ created: false,
529
+ }),
530
+ getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
531
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
532
+ joinToken: "jt-123",
533
+ }),
534
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
535
+ };
536
+ const runtime = createIntelligenceRuntime(agent, platform);
537
+
538
+ const response = await handleRunAgent({
539
+ runtime,
540
+ request: createRunRequest(),
541
+ agentId: "my-agent",
542
+ });
543
+
544
+ expect(response.status).toBe(502);
545
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledWith({
546
+ threadId: "thread-1",
547
+ runId: "run-1",
548
+ });
476
549
  expect(runtime.runner.run).not.toHaveBeenCalled();
477
550
  });
478
551
 
@@ -501,6 +574,206 @@ describe("handleRunAgent", () => {
501
574
  expect(body.error).toBe("Thread lock denied");
502
575
  });
503
576
 
577
+ it("cleans up the canonical lock and returns 502 when runner start fails immediately", async () => {
578
+ const agent = createAgentForIntelligence();
579
+ const platform = {
580
+ getOrCreateThread: vi.fn().mockResolvedValue({
581
+ thread: { id: "thread-1", name: null },
582
+ created: false,
583
+ }),
584
+ getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
585
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
586
+ threadId: "canonical-thread",
587
+ runId: "canonical-run",
588
+ joinToken: "jt-123",
589
+ }),
590
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
591
+ };
592
+ const runtime = createIntelligenceRuntime(agent, platform);
593
+ runtime.runner.run = vi.fn(
594
+ () =>
595
+ new Observable<BaseEvent>((subscriber) => {
596
+ subscriber.next({
597
+ type: EventType.RUN_ERROR,
598
+ message: "join failed",
599
+ } as BaseEvent);
600
+ subscriber.complete();
601
+ }),
602
+ );
603
+
604
+ const response = await handleRunAgent({
605
+ runtime,
606
+ request: createRunRequest(),
607
+ agentId: "my-agent",
608
+ });
609
+
610
+ expect(response.status).toBe(502);
611
+ const body = await response.json();
612
+ expect(body).toEqual({
613
+ error: "Failed to start runner",
614
+ message: "join failed",
615
+ });
616
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledWith({
617
+ threadId: "canonical-thread",
618
+ runId: "canonical-run",
619
+ });
620
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledTimes(1);
621
+ });
622
+
623
+ it("delays the run success response until the runner startup boundary resolves", async () => {
624
+ const agent = createAgentForIntelligence();
625
+ let resolveStartup: (() => void) | undefined;
626
+ const startup = new Promise<void>((resolve) => {
627
+ resolveStartup = resolve;
628
+ });
629
+ const platform = {
630
+ getOrCreateThread: vi.fn().mockResolvedValue({
631
+ thread: { id: "thread-1", name: null },
632
+ created: false,
633
+ }),
634
+ getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
635
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
636
+ threadId: "canonical-thread",
637
+ runId: "canonical-run",
638
+ joinToken: "jt-123",
639
+ }),
640
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
641
+ };
642
+ const runtime = createIntelligenceRuntime(agent, platform);
643
+ runtime.runner.runWithStartupBoundary = vi.fn(() => ({
644
+ events: new Observable<BaseEvent>(() => {}),
645
+ startup,
646
+ }));
647
+ let settled = false;
648
+
649
+ const responsePromise = handleRunAgent({
650
+ runtime,
651
+ request: createRunRequest(),
652
+ agentId: "my-agent",
653
+ }).then((response) => {
654
+ settled = true;
655
+ return response;
656
+ });
657
+
658
+ await Promise.resolve();
659
+
660
+ expect(settled).toBe(false);
661
+
662
+ resolveStartup?.();
663
+ const response = await responsePromise;
664
+
665
+ expect(response.status).toBe(200);
666
+ expect(runtime.runner.runWithStartupBoundary).toHaveBeenCalledWith(
667
+ expect.objectContaining({
668
+ threadId: "canonical-thread",
669
+ input: expect.objectContaining({
670
+ threadId: "canonical-thread",
671
+ runId: "canonical-run",
672
+ }),
673
+ }),
674
+ );
675
+ });
676
+
677
+ it("cleans up the lock and returns 502 when the runner startup boundary rejects", async () => {
678
+ const agent = createAgentForIntelligence();
679
+ const platform = {
680
+ getOrCreateThread: vi.fn().mockResolvedValue({
681
+ thread: { id: "thread-1", name: null },
682
+ created: false,
683
+ }),
684
+ getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
685
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
686
+ threadId: "canonical-thread",
687
+ runId: "canonical-run",
688
+ joinToken: "jt-123",
689
+ }),
690
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
691
+ };
692
+ const runtime = createIntelligenceRuntime(agent, platform);
693
+ runtime.runner.runWithStartupBoundary = vi.fn(() => ({
694
+ events: new Observable<BaseEvent>(() => {}),
695
+ startup: Promise.reject(new Error("Failed to join channel: denied")),
696
+ }));
697
+
698
+ const response = await handleRunAgent({
699
+ runtime,
700
+ request: createRunRequest(),
701
+ agentId: "my-agent",
702
+ });
703
+
704
+ expect(response.status).toBe(502);
705
+ const body = await response.json();
706
+ expect(body).toEqual({
707
+ error: "Failed to start runner",
708
+ message: "Failed to join channel: denied",
709
+ });
710
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledWith({
711
+ threadId: "canonical-thread",
712
+ runId: "canonical-run",
713
+ });
714
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledTimes(1);
715
+ });
716
+
717
+ it("aborts the agent when lock renewal fails", async () => {
718
+ vi.useFakeTimers();
719
+ const runningAgent = {
720
+ clone: vi.fn(),
721
+ setMessages: vi.fn(),
722
+ setState: vi.fn(),
723
+ abortRun: vi.fn(),
724
+ threadId: undefined,
725
+ headers: {},
726
+ runAgent: vi.fn().mockResolvedValue(undefined),
727
+ } as unknown as AbstractAgent;
728
+ const baseAgent = {
729
+ clone: vi.fn(() => runningAgent),
730
+ setMessages: vi.fn(),
731
+ setState: vi.fn(),
732
+ abortRun: vi.fn(),
733
+ threadId: undefined,
734
+ headers: {},
735
+ runAgent: vi.fn().mockResolvedValue(undefined),
736
+ } as unknown as AbstractAgent;
737
+ const platform = {
738
+ getOrCreateThread: vi.fn().mockResolvedValue({
739
+ thread: { id: "thread-1", name: null },
740
+ created: false,
741
+ }),
742
+ getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
743
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
744
+ threadId: "canonical-thread",
745
+ runId: "canonical-run",
746
+ joinToken: "jt-123",
747
+ }),
748
+ ɵrenewThreadLock: vi.fn().mockRejectedValue(new Error("lost lock")),
749
+ };
750
+ const runtime = createIntelligenceRuntime(baseAgent, platform, {
751
+ lockHeartbeatIntervalSeconds: 1,
752
+ lockTtlSeconds: 5,
753
+ });
754
+ runtime.runner.run = vi.fn(() => new Observable<BaseEvent>(() => {}));
755
+
756
+ try {
757
+ const response = await handleRunAgent({
758
+ runtime,
759
+ request: createRunRequest(),
760
+ agentId: "my-agent",
761
+ });
762
+ expect(response.status).toBe(200);
763
+
764
+ await vi.advanceTimersByTimeAsync(1_000);
765
+
766
+ expect(platform.ɵrenewThreadLock).toHaveBeenCalledWith({
767
+ threadId: "canonical-thread",
768
+ runId: "canonical-run",
769
+ ttlSeconds: 5,
770
+ });
771
+ expect(runningAgent.abortRun).toHaveBeenCalledTimes(1);
772
+ } finally {
773
+ vi.useRealTimers();
774
+ }
775
+ });
776
+
504
777
  it("passes only unseen input messages to the runner for durable persistence", async () => {
505
778
  const agent = createAgentForIntelligence();
506
779
  const platform = {
@@ -517,9 +790,11 @@ describe("handleRunAgent", () => {
517
790
  },
518
791
  ],
519
792
  }),
520
- ɵacquireThreadLock: vi
521
- .fn()
522
- .mockResolvedValue({ joinToken: "jt-123", joinCode: "jc-123" }),
793
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
794
+ threadId: "thread-1",
795
+ runId: "run-1",
796
+ joinToken: "jt-123",
797
+ }),
523
798
  };
524
799
  const runtime = createIntelligenceRuntime(agent, platform);
525
800
  const response = await handleRunAgent({
@@ -577,9 +852,12 @@ describe("handleRunAgent", () => {
577
852
  getThreadMessages: vi
578
853
  .fn()
579
854
  .mockRejectedValue(new Error("history unavailable")),
580
- ɵacquireThreadLock: vi
581
- .fn()
582
- .mockResolvedValue({ joinToken: "jt-123", joinCode: "jc-123" }),
855
+ ɵacquireThreadLock: vi.fn().mockResolvedValue({
856
+ threadId: "thread-1",
857
+ runId: "run-1",
858
+ joinToken: "jt-123",
859
+ }),
860
+ ɵcleanupThreadLock: vi.fn().mockResolvedValue(undefined),
583
861
  };
584
862
  const runtime = createIntelligenceRuntime(agent, platform);
585
863
 
@@ -592,6 +870,10 @@ describe("handleRunAgent", () => {
592
870
  expect(response.status).toBe(502);
593
871
  const body = await response.json();
594
872
  expect(body.error).toBe("Thread history lookup failed");
873
+ expect(platform.ɵcleanupThreadLock).toHaveBeenCalledWith({
874
+ threadId: "thread-1",
875
+ runId: "run-1",
876
+ });
595
877
  expect(runtime.runner.run).not.toHaveBeenCalled();
596
878
  });
597
879
 
@@ -604,8 +886,9 @@ describe("handleRunAgent", () => {
604
886
  }),
605
887
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
606
888
  ɵacquireThreadLock: vi.fn().mockResolvedValue({
889
+ threadId: "thread-1",
890
+ runId: "run-1",
607
891
  joinToken: "jt-created",
608
- joinCode: "jc-created",
609
892
  }),
610
893
  };
611
894
  const runtime = createIntelligenceRuntime(agent, platform);
@@ -626,6 +909,8 @@ describe("handleRunAgent", () => {
626
909
  threadId: "thread-1",
627
910
  runId: "run-1",
628
911
  userId: "user-1",
912
+ agentId: "my-agent",
913
+ ttlSeconds: 20,
629
914
  });
630
915
  });
631
916
 
@@ -675,8 +960,9 @@ describe("handleRunAgent", () => {
675
960
  }),
676
961
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
677
962
  ɵacquireThreadLock: vi.fn().mockResolvedValue({
963
+ threadId: "thread-1",
964
+ runId: "run-1",
678
965
  joinToken: "jt-created",
679
- joinCode: "jc-created",
680
966
  }),
681
967
  };
682
968
  const runtime = createIntelligenceRuntime(baseAgent, platform, {
@@ -730,8 +1016,9 @@ describe("handleRunAgent", () => {
730
1016
  updateThread: vi.fn(),
731
1017
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
732
1018
  ɵacquireThreadLock: vi.fn().mockResolvedValue({
1019
+ threadId: "thread-1",
1020
+ runId: "run-1",
733
1021
  joinToken: "jt-created",
734
- joinCode: "jc-created",
735
1022
  }),
736
1023
  };
737
1024
  const runtime = createIntelligenceRuntime(agent, platform, {
@@ -759,8 +1046,9 @@ describe("handleRunAgent", () => {
759
1046
  updateThread: vi.fn(),
760
1047
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
761
1048
  ɵacquireThreadLock: vi.fn().mockResolvedValue({
1049
+ threadId: "thread-1",
1050
+ runId: "run-1",
762
1051
  joinToken: "jt-created",
763
- joinCode: "jc-created",
764
1052
  }),
765
1053
  };
766
1054
  const runtime = createIntelligenceRuntime(agent, platform, {
@@ -815,8 +1103,9 @@ describe("handleRunAgent", () => {
815
1103
  updateThread: vi.fn(),
816
1104
  getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }),
817
1105
  ɵacquireThreadLock: vi.fn().mockResolvedValue({
1106
+ threadId: "thread-1",
1107
+ runId: "run-1",
818
1108
  joinToken: "jt-created",
819
- joinCode: "jc-created",
820
1109
  }),
821
1110
  };
822
1111
  const runtime = createIntelligenceRuntime(baseAgent, platform, {
@@ -938,4 +1227,66 @@ describe("handleRunAgent", () => {
938
1227
  }
939
1228
  });
940
1229
  });
1230
+
1231
+ describe("telemetry", () => {
1232
+ it("captures oss.runtime.copilot_request_created on every invocation", async () => {
1233
+ // Dynamic import so we spy on the module singleton the handler uses.
1234
+ const { telemetry } = await import("../telemetry");
1235
+ const captureSpy = vi
1236
+ .spyOn(telemetry, "capture")
1237
+ .mockResolvedValue(undefined);
1238
+
1239
+ try {
1240
+ const runtime = createMockRuntime({});
1241
+ await handleRunAgent({
1242
+ runtime,
1243
+ request: createMockRequest(),
1244
+ agentId: "nonexistent-agent",
1245
+ });
1246
+
1247
+ expect(captureSpy).toHaveBeenCalledWith(
1248
+ "oss.runtime.copilot_request_created",
1249
+ expect.objectContaining({
1250
+ requestType: "run",
1251
+ "cloud.api_key_provided": false,
1252
+ }),
1253
+ );
1254
+ } finally {
1255
+ captureSpy.mockRestore();
1256
+ }
1257
+ });
1258
+
1259
+ it("includes cloud.public_api_key when x-copilotcloud-public-api-key header is set", async () => {
1260
+ const { telemetry } = await import("../telemetry");
1261
+ const captureSpy = vi
1262
+ .spyOn(telemetry, "capture")
1263
+ .mockResolvedValue(undefined);
1264
+
1265
+ try {
1266
+ const runtime = createMockRuntime({});
1267
+ const request = new Request("https://example.com/agent/test/run", {
1268
+ method: "POST",
1269
+ headers: {
1270
+ "x-copilotcloud-public-api-key": "ck_pub_run_test",
1271
+ },
1272
+ });
1273
+
1274
+ await handleRunAgent({
1275
+ runtime,
1276
+ request,
1277
+ agentId: "nonexistent-agent",
1278
+ });
1279
+
1280
+ expect(captureSpy).toHaveBeenCalledWith(
1281
+ "oss.runtime.copilot_request_created",
1282
+ expect.objectContaining({
1283
+ "cloud.api_key_provided": true,
1284
+ "cloud.public_api_key": "ck_pub_run_test",
1285
+ }),
1286
+ );
1287
+ } finally {
1288
+ captureSpy.mockRestore();
1289
+ }
1290
+ });
1291
+ });
941
1292
  });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Integration test: Hono single-route adapter (deprecated path) + telemetry.
3
+ *
4
+ * `createCopilotEndpointSingleRoute` is the legacy direct single-route entry
5
+ * point, superseded by `createCopilotHonoHandler({ mode: "single-route" })`
6
+ * but still exported.
7
+ */
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
9
+ import type { AbstractAgent } from "@ag-ui/client";
10
+
11
+ import { telemetry } from "../telemetry";
12
+ import { createCopilotEndpointSingleRoute } from "../endpoints/hono-single";
13
+ import { CopilotRuntime } from "../core/runtime";
14
+
15
+ function makeAgent(): AbstractAgent {
16
+ const a: unknown = { execute: async () => ({ events: [] }) };
17
+ (a as { clone: () => unknown }).clone = () => makeAgent();
18
+ return a as AbstractAgent;
19
+ }
20
+
21
+ describe("Hono single-route adapter — telemetry firing (integration)", () => {
22
+ let captureSpy: ReturnType<typeof vi.spyOn>;
23
+
24
+ beforeEach(() => {
25
+ captureSpy = vi.spyOn(telemetry, "capture").mockResolvedValue(undefined);
26
+ });
27
+
28
+ afterEach(() => {
29
+ captureSpy.mockRestore();
30
+ });
31
+
32
+ it("fires instance_created on handler creation", async () => {
33
+ const runtime = new CopilotRuntime({ agents: { default: makeAgent() } });
34
+ createCopilotEndpointSingleRoute({ runtime, basePath: "/api/copilotkit" });
35
+
36
+ await vi.waitFor(() => {
37
+ expect(captureSpy).toHaveBeenCalledWith(
38
+ "oss.runtime.instance_created",
39
+ expect.objectContaining({
40
+ agentsAmount: 1,
41
+ "cloud.api_key_provided": false,
42
+ }),
43
+ );
44
+ });
45
+ });
46
+ });