@assistant-ui/react-ag-ui 0.1.1 → 0.1.3

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 (43) hide show
  1. package/README.md +2 -0
  2. package/dist/runtime/AgUiThreadController.d.ts +11 -0
  3. package/dist/runtime/AgUiThreadController.d.ts.map +1 -1
  4. package/dist/runtime/AgUiThreadController.js +242 -25
  5. package/dist/runtime/AgUiThreadController.js.map +1 -1
  6. package/dist/runtime/adapter/conversions.d.ts +9 -1
  7. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  8. package/dist/runtime/adapter/conversions.js +184 -9
  9. package/dist/runtime/adapter/conversions.js.map +1 -1
  10. package/dist/runtime/adapter/run-aggregator.d.ts +2 -0
  11. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  12. package/dist/runtime/adapter/run-aggregator.js +42 -12
  13. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  14. package/dist/runtime/adapter/subscriber.d.ts +0 -1
  15. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  16. package/dist/runtime/adapter/subscriber.js +1 -8
  17. package/dist/runtime/adapter/subscriber.js.map +1 -1
  18. package/dist/runtime/event-parser.d.ts.map +1 -1
  19. package/dist/runtime/event-parser.js +1 -0
  20. package/dist/runtime/event-parser.js.map +1 -1
  21. package/dist/runtime/types.d.ts +2 -0
  22. package/dist/runtime/types.d.ts.map +1 -1
  23. package/dist/runtime/types.js +2 -1
  24. package/dist/runtime/types.js.map +1 -1
  25. package/package.json +6 -5
  26. package/src/runtime/AgUiThreadController.disconnect.test.ts +914 -0
  27. package/src/runtime/AgUiThreadController.interrupt-history.test.ts +392 -0
  28. package/src/runtime/AgUiThreadController.replay.test.ts +8 -1
  29. package/src/runtime/AgUiThreadController.snapshot-head-move.test.ts +346 -0
  30. package/src/runtime/AgUiThreadController.subagent-snapshot.test.ts +854 -0
  31. package/src/runtime/AgUiThreadController.test.ts +173 -1
  32. package/src/runtime/AgUiThreadController.ts +528 -33
  33. package/src/runtime/adapter/conversions.content.test.ts +129 -0
  34. package/src/runtime/adapter/conversions.subagent-messages.test.ts +550 -0
  35. package/src/runtime/adapter/conversions.ts +331 -20
  36. package/src/runtime/adapter/run-aggregator.metadata.test.ts +151 -0
  37. package/src/runtime/adapter/run-aggregator.ts +63 -9
  38. package/src/runtime/adapter/subscriber.test.ts +1 -1
  39. package/src/runtime/adapter/subscriber.ts +1 -6
  40. package/src/runtime/event-parser.ts +3 -0
  41. package/src/runtime/types.ts +5 -0
  42. package/src/useAgUiRuntime.cloud-run-leases.test.tsx +384 -0
  43. package/src/useAgUiRuntime.state.test.tsx +87 -0
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi } from "vitest";
2
- import type { AbstractAgent } from "@ag-ui/client";
2
+ import { HttpAgent, type AbstractAgent } from "@ag-ui/client";
3
3
  import type {
4
4
  AppendMessage,
5
5
  ThreadAssistantMessage,
@@ -539,6 +539,178 @@ describe("AgUiThreadController activity deltas", () => {
539
539
  });
540
540
  });
541
541
 
542
+ describe("AgUiThreadController run state", () => {
543
+ const createStateAgent = (sentStates: unknown[]) =>
544
+ new HttpAgent({
545
+ url: "http://agent.test/agent",
546
+ initialState: { fromInitialState: true },
547
+ fetch: async (_url, init) => {
548
+ const body = JSON.parse(String(init?.body));
549
+ sentStates.push(body.state);
550
+ const events = [
551
+ { type: "RUN_STARTED", threadId: body.threadId, runId: body.runId },
552
+ { type: "STATE_SNAPSHOT", snapshot: { a: 1 } },
553
+ { type: "RUN_FINISHED", threadId: body.threadId, runId: body.runId },
554
+ ];
555
+ return new Response(
556
+ events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join(""),
557
+ { headers: { "content-type": "text/event-stream" } },
558
+ );
559
+ },
560
+ });
561
+
562
+ type Subscriber = {
563
+ onStateSnapshotEvent?: (payload: {
564
+ event: { type: "STATE_SNAPSHOT"; snapshot: { count: number } };
565
+ }) => void;
566
+ };
567
+
568
+ it("sends an empty state when no snapshot exists and preserves null", async () => {
569
+ const runAgent = vi.fn(
570
+ async (_input: { state: unknown }, subscriber: Subscriber) => {
571
+ if (runAgent.mock.calls.length === 1) {
572
+ subscriber.onStateSnapshotEvent?.({
573
+ event: { type: "STATE_SNAPSHOT", snapshot: { count: 1 } },
574
+ });
575
+ }
576
+ },
577
+ );
578
+ const agent = {
579
+ runAgent,
580
+ abortRun: vi.fn(),
581
+ } as unknown as AbstractAgent;
582
+ const { core } = createCore(undefined, agent);
583
+
584
+ await core.append({ ...userMessage("initial"), startRun: true });
585
+ expect(runAgent.mock.calls[0]?.[0].state).toEqual({});
586
+ expect(agent.state).toEqual({});
587
+
588
+ core.resetState();
589
+ expect(core.getState()).toBeUndefined();
590
+ await core.append({ ...userMessage("after reset"), startRun: true });
591
+ expect(runAgent.mock.calls[1]?.[0].state).toEqual({});
592
+ expect(agent.state).toEqual({});
593
+
594
+ core.setState(null);
595
+ await core.append({ ...userMessage("explicit null"), startRun: true });
596
+ expect(runAgent.mock.calls[2]?.[0].state).toBeNull();
597
+ expect(agent.state).toBeNull();
598
+ });
599
+
600
+ it("isolates mounted threads sharing one agent through runs and setState", async () => {
601
+ const sentStates: unknown[] = [];
602
+ const agent = createStateAgent(sentStates);
603
+ const { core: threadA } = createCore(undefined, agent);
604
+ const { core: threadB } = createCore(undefined, agent);
605
+
606
+ await threadA.append({ ...userMessage("A"), startRun: true });
607
+ expect(threadA.getState()).toEqual({ a: 1 });
608
+ expect.soft(threadB.getState()).toEqual({ fromInitialState: true });
609
+
610
+ threadA.setState({ x: 1 });
611
+ await flush();
612
+ expect(threadA.getState()).toEqual({ x: 1 });
613
+ expect.soft(threadB.getState()).toEqual({ fromInitialState: true });
614
+
615
+ await threadB.append({ ...userMessage("B"), startRun: true });
616
+ expect
617
+ .soft(sentStates)
618
+ .toEqual([{ fromInitialState: true }, { fromInitialState: true }]);
619
+ expect.soft(threadA.getState()).toEqual({ x: 1 });
620
+ });
621
+
622
+ it("starts a later thread from the shared agent's initial state after a run", async () => {
623
+ const sentStates: unknown[] = [];
624
+ const agent = createStateAgent(sentStates);
625
+ const { core: threadA } = createCore(undefined, agent);
626
+
627
+ await threadA.append({ ...userMessage("A"), startRun: true });
628
+ expect(agent.state).toEqual({ a: 1 });
629
+
630
+ const { core: threadB } = createCore(undefined, agent);
631
+ expect.soft(threadB.getState()).toEqual({ fromInitialState: true });
632
+ await threadB.append({ ...userMessage("B"), startRun: true });
633
+ expect(sentStates).toEqual([
634
+ { fromInitialState: true },
635
+ { fromInitialState: true },
636
+ ]);
637
+ });
638
+
639
+ it("copies the initial state before the agent state is mutated", () => {
640
+ const agent = new HttpAgent({
641
+ url: "http://agent.test/agent",
642
+ initialState: { nested: { count: 0 } },
643
+ });
644
+ const { core } = createCore(undefined, agent);
645
+
646
+ agent.state.nested.count = 1;
647
+ expect(core.getState()).toEqual({ nested: { count: 0 } });
648
+
649
+ const { core: laterThread } = createCore(undefined, agent);
650
+ expect(laterThread.getState()).toEqual({ nested: { count: 0 } });
651
+ });
652
+
653
+ it("records an undefined initial state without adopting later agent state", () => {
654
+ const agent = {} as AbstractAgent;
655
+ createCore(undefined, agent);
656
+ agent.state = { fromPreviousThread: true };
657
+
658
+ const { core } = createCore(undefined, agent);
659
+ expect(core.getState()).toBeUndefined();
660
+ });
661
+
662
+ it("leaves the state undefined for an agent without initialState", () => {
663
+ const agent = new HttpAgent({ url: "http://agent.test/agent" });
664
+ const { core } = createCore(undefined, agent);
665
+
666
+ expect(agent.state).toEqual({});
667
+ expect(core.getState()).toBeUndefined();
668
+
669
+ const swapped = new HttpAgent({ url: "http://agent.test/other" });
670
+ core.updateOptions({
671
+ agent: swapped,
672
+ logger: makeLogger(),
673
+ showThinking: true,
674
+ });
675
+ expect(core.getState()).toBeUndefined();
676
+ });
677
+
678
+ it("seeds a new agent's initial state on swap when the thread has no snapshot", () => {
679
+ const { core } = createCore();
680
+ const agent = new HttpAgent({
681
+ url: "http://agent.test/agent",
682
+ initialState: { a: 1 },
683
+ });
684
+ const listener = vi.fn();
685
+ core.getSnapshot();
686
+ core.subscribe(listener);
687
+
688
+ core.updateOptions({ agent, logger: makeLogger(), showThinking: true });
689
+
690
+ expect(agent.state).toEqual({ a: 1 });
691
+ expect(core.getState()).toEqual({ a: 1 });
692
+ expect(core.getSnapshot().state).toEqual({ a: 1 });
693
+ expect(listener).toHaveBeenCalledOnce();
694
+ });
695
+
696
+ it("preserves the thread snapshot on swap and records the new agent's initial state", async () => {
697
+ const { core } = createCore();
698
+ core.setState({ fromThread: true });
699
+ const sentStates: unknown[] = [];
700
+ const agent = createStateAgent(sentStates);
701
+
702
+ core.updateOptions({ agent, logger: makeLogger(), showThinking: true });
703
+
704
+ expect(agent.state).toEqual({ fromInitialState: true });
705
+ expect(core.getState()).toEqual({ fromThread: true });
706
+ await core.append({ ...userMessage("after swap"), startRun: true });
707
+ expect(sentStates).toEqual([{ fromThread: true }]);
708
+
709
+ const { core: laterThread } = createCore(undefined, agent);
710
+ expect(laterThread.getState()).toEqual({ fromInitialState: true });
711
+ });
712
+ });
713
+
542
714
  describe("AgUiThreadController setState", () => {
543
715
  it("keeps a cancel the updater makes while a send waits for history", async () => {
544
716
  const { history, finish } = deferredHistory();