@artooi/ag-ui-web-component 0.11.0 → 0.13.0

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 (41) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +98 -5
  3. package/dist/ag-ui-web-component.bundle.js +126 -50
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +9 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +29 -4
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +11 -0
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/core/create_http_agent.d.ts +6 -0
  12. package/dist/core/create_http_agent.d.ts.map +1 -1
  13. package/dist/core/run_index.d.ts +50 -0
  14. package/dist/core/run_index.d.ts.map +1 -0
  15. package/dist/index.d.ts +9 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +453 -63
  18. package/dist/index.js.map +3 -3
  19. package/dist/tools/page_state.d.ts +40 -0
  20. package/dist/tools/page_state.d.ts.map +1 -0
  21. package/dist/ui/checkpoint_menu.d.ts +32 -0
  22. package/dist/ui/checkpoint_menu.d.ts.map +1 -0
  23. package/dist/ui/styles.d.ts +1 -1
  24. package/dist/ui/styles.d.ts.map +1 -1
  25. package/dist/ui/ui_strings.d.ts +10 -0
  26. package/dist/ui/ui_strings.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/constants.ts +10 -0
  29. package/src/core/ag_ui_chat.ts +153 -6
  30. package/src/core/agui_client.ts +28 -0
  31. package/src/core/create_http_agent.ts +7 -0
  32. package/src/core/run_index.ts +91 -0
  33. package/src/index.ts +14 -1
  34. package/src/tools/page_state.ts +72 -0
  35. package/src/ui/checkpoint_menu.ts +153 -0
  36. package/src/ui/styles.ts +76 -0
  37. package/src/ui/ui_strings.ts +15 -0
  38. package/src/version.ts +1 -1
  39. package/dist/tools/state_hook.d.ts +0 -23
  40. package/dist/tools/state_hook.d.ts.map +0 -1
  41. package/src/tools/state_hook.ts +0 -53
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  var ELEMENT_TAG = "ag-ui-chat";
3
3
  var SUBMIT_EVENT = "ag-ui-submit";
4
4
  var TOGGLE_EVENT = "ag-ui-toggle";
5
+ var STATE_EVENT = "ag-ui-state";
5
6
  var MESSAGE_ROLE = {
6
7
  USER: "user",
7
8
  ASSISTANT: "assistant"
@@ -318,6 +319,38 @@ function createPageMapContext(getPageMap, autoInject) {
318
319
  return [{ description: "page_map", value: JSON.stringify(getPageMap()) }];
319
320
  }
320
321
 
322
+ // src/tools/page_state.ts
323
+ function createPageStateTools(binding) {
324
+ const tools = [
325
+ {
326
+ name: `read_${binding.name}`,
327
+ description: `Read the "${binding.name}" state.`,
328
+ parameters: {
329
+ type: "object",
330
+ properties: {},
331
+ required: [],
332
+ [X_SUMMARY_KEY]: `Read ${binding.name}`
333
+ },
334
+ handler: () => binding.read()
335
+ }
336
+ ];
337
+ const write = binding.write;
338
+ if (write !== void 0) {
339
+ tools.push({
340
+ name: `set_${binding.name}`,
341
+ description: `Update the "${binding.name}" state.`,
342
+ parameters: {
343
+ ...binding.schema ?? { type: "object" },
344
+ [X_DESTRUCTIVE_KEY]: true,
345
+ [X_SUMMARY_KEY]: `Update ${binding.name}`
346
+ },
347
+ handler: (args) => write(args)
348
+ });
349
+ }
350
+ return tools;
351
+ }
352
+ var createStateHookTools = createPageStateTools;
353
+
321
354
  // src/tools/parse_tool_catalog.ts
322
355
  function parseToolCatalog(data) {
323
356
  const out = {};
@@ -412,37 +445,6 @@ function createRouteTools(getRouteMap, getNavigate) {
412
445
  ];
413
446
  }
414
447
 
415
- // src/tools/state_hook.ts
416
- function createStateHookTools(hook) {
417
- const tools = [
418
- {
419
- name: `read_${hook.name}`,
420
- description: `Read the "${hook.name}" state.`,
421
- parameters: {
422
- type: "object",
423
- properties: {},
424
- required: [],
425
- [X_SUMMARY_KEY]: `Read ${hook.name}`
426
- },
427
- handler: () => hook.read()
428
- }
429
- ];
430
- const write = hook.write;
431
- if (write !== void 0) {
432
- tools.push({
433
- name: `set_${hook.name}`,
434
- description: `Update the "${hook.name}" state.`,
435
- parameters: {
436
- ...hook.schema ?? { type: "object" },
437
- [X_DESTRUCTIVE_KEY]: true,
438
- [X_SUMMARY_KEY]: `Update ${hook.name}`
439
- },
440
- handler: (args) => write(args)
441
- });
442
- }
443
- return tools;
444
- }
445
-
446
448
  // src/ui/ui_strings.ts
447
449
  var DEFAULT_UI_STRINGS = {
448
450
  title: "Assistant",
@@ -451,6 +453,11 @@ var DEFAULT_UI_STRINGS = {
451
453
  collapse: "Collapse",
452
454
  expand: "Expand",
453
455
  toggleTheme: "Toggle theme",
456
+ checkpoints: "Continue a run",
457
+ noCheckpoints: "Nothing to continue yet.",
458
+ resumeRun: "Resume",
459
+ forkRun: "Fork",
460
+ forkedRun: "branched",
454
461
  conversation: "Conversation",
455
462
  thinking: "Assistant is thinking\u2026",
456
463
  thoughts: "Thoughts",
@@ -841,6 +848,138 @@ function accepts(accept, file) {
841
848
  });
842
849
  }
843
850
 
851
+ // src/ui/relative_time.ts
852
+ function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
853
+ if (!Number.isFinite(timestamp)) {
854
+ return strings.justNow;
855
+ }
856
+ const seconds = Math.round((now - timestamp) / 1e3);
857
+ if (seconds < 60) {
858
+ return strings.justNow;
859
+ }
860
+ const minutes = Math.round(seconds / 60);
861
+ if (minutes < 60) {
862
+ return strings.minutesAgo.replace("{n}", String(minutes));
863
+ }
864
+ const hours = Math.round(minutes / 60);
865
+ if (hours < 24) {
866
+ return strings.hoursAgo.replace("{n}", String(hours));
867
+ }
868
+ const days = Math.round(hours / 24);
869
+ if (days < 7) {
870
+ return strings.daysAgo.replace("{n}", String(days));
871
+ }
872
+ return strings.weeksAgo.replace("{n}", String(Math.round(days / 7)));
873
+ }
874
+
875
+ // src/ui/checkpoint_menu.ts
876
+ var CheckpointMenu = class {
877
+ /** The panel root. Append to the chat shell; hidden until opened. */
878
+ element;
879
+ #onPick;
880
+ #list;
881
+ #heading;
882
+ #strings;
883
+ #runs = [];
884
+ constructor(onPick, strings = DEFAULT_UI_STRINGS) {
885
+ this.#onPick = onPick;
886
+ this.#strings = strings;
887
+ this.element = document.createElement("div");
888
+ this.element.className = "checkpoints";
889
+ this.element.setAttribute("part", "checkpoints");
890
+ this.element.setAttribute("role", "dialog");
891
+ this.element.setAttribute("aria-label", strings.checkpoints);
892
+ this.element.hidden = true;
893
+ const header = document.createElement("div");
894
+ header.className = "checkpoints-header";
895
+ header.setAttribute("part", "checkpoints-header");
896
+ this.#heading = document.createElement("span");
897
+ this.#heading.className = "checkpoints-title";
898
+ this.#heading.textContent = strings.checkpoints;
899
+ header.append(this.#heading);
900
+ this.#list = document.createElement("div");
901
+ this.#list.className = "checkpoints-list";
902
+ this.#list.setAttribute("part", "checkpoints-list");
903
+ this.element.append(header, this.#list);
904
+ this.element.addEventListener("keydown", (event) => {
905
+ if (event.key === "Escape") {
906
+ event.stopPropagation();
907
+ this.close();
908
+ }
909
+ });
910
+ }
911
+ /** Replace the rows. The host passes only `continuable` runs. */
912
+ setRuns(runs) {
913
+ this.#runs = runs;
914
+ this.#render();
915
+ }
916
+ /** Re-localize a panel built before the host's strings resolved. */
917
+ setStrings(strings) {
918
+ this.#strings = strings;
919
+ this.element.setAttribute("aria-label", strings.checkpoints);
920
+ this.#heading.textContent = strings.checkpoints;
921
+ this.#render();
922
+ }
923
+ open() {
924
+ this.element.hidden = false;
925
+ }
926
+ close() {
927
+ this.element.hidden = true;
928
+ }
929
+ get open_() {
930
+ return !this.element.hidden;
931
+ }
932
+ #render() {
933
+ this.#list.replaceChildren();
934
+ if (this.#runs.length === 0) {
935
+ const empty = document.createElement("div");
936
+ empty.className = "checkpoints-empty";
937
+ empty.setAttribute("part", "checkpoints-empty");
938
+ empty.textContent = this.#strings.noCheckpoints;
939
+ this.#list.append(empty);
940
+ return;
941
+ }
942
+ for (const run of this.#runs) {
943
+ this.#list.append(this.#row(run));
944
+ }
945
+ }
946
+ #row(run) {
947
+ const row = document.createElement("div");
948
+ row.className = "checkpoint-row";
949
+ row.setAttribute("part", "checkpoint-row");
950
+ const label = document.createElement("span");
951
+ label.className = "checkpoint-label";
952
+ label.textContent = run.started_at === null ? run.run_id : relativeTime(Date.parse(run.started_at), Date.now(), this.#strings);
953
+ label.title = run.run_id;
954
+ row.append(label);
955
+ if (run.parent_run_id !== null) {
956
+ const branch = document.createElement("span");
957
+ branch.className = "checkpoint-branch";
958
+ branch.setAttribute("part", "checkpoint-branch");
959
+ branch.textContent = this.#strings.forkedRun;
960
+ branch.title = run.parent_run_id;
961
+ row.append(branch);
962
+ }
963
+ row.append(
964
+ this.#action(run.run_id, "resume", this.#strings.resumeRun),
965
+ this.#action(run.run_id, "fork", this.#strings.forkRun)
966
+ );
967
+ return row;
968
+ }
969
+ #action(runId, verb, label) {
970
+ const button = document.createElement("button");
971
+ button.type = "button";
972
+ button.className = `checkpoint-action checkpoint-${verb}`;
973
+ button.setAttribute("part", `checkpoint-action checkpoint-${verb}`);
974
+ button.textContent = label;
975
+ button.addEventListener("click", () => {
976
+ this.close();
977
+ this.#onPick(runId, verb);
978
+ });
979
+ return button;
980
+ }
981
+ };
982
+
844
983
  // src/ui/confirmation_card.ts
845
984
  function actionButton2(modifier, label) {
846
985
  const button = document.createElement("button");
@@ -4952,6 +5091,82 @@ var STYLES = `
4952
5091
  display: none;
4953
5092
  }
4954
5093
 
5094
+ .checkpoints {
5095
+ position: absolute;
5096
+ inset-block-start: 3rem;
5097
+ inset-inline: 0.75rem;
5098
+ z-index: 6;
5099
+ display: flex;
5100
+ flex-direction: column;
5101
+ gap: 0.25rem;
5102
+ padding: 0.5rem;
5103
+ border: 1px solid var(--agui-border, #d7d7dc);
5104
+ border-radius: 0.5rem;
5105
+ background: var(--agui-surface, #fff);
5106
+ box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
5107
+ max-height: 60%;
5108
+ overflow-y: auto;
5109
+ }
5110
+
5111
+ .checkpoints[hidden] {
5112
+ display: none;
5113
+ }
5114
+
5115
+ .checkpoints-title {
5116
+ font-size: 0.75rem;
5117
+ font-weight: 600;
5118
+ opacity: 0.7;
5119
+ }
5120
+
5121
+ .checkpoints-empty {
5122
+ padding: 0.5rem 0.25rem;
5123
+ font-size: 0.8125rem;
5124
+ opacity: 0.7;
5125
+ }
5126
+
5127
+ .checkpoint-row {
5128
+ display: flex;
5129
+ align-items: center;
5130
+ gap: 0.5rem;
5131
+ padding: 0.25rem;
5132
+ border-radius: 0.375rem;
5133
+ }
5134
+
5135
+ .checkpoint-row:hover {
5136
+ background: var(--agui-hover, #f3f3f5);
5137
+ }
5138
+
5139
+ .checkpoint-label {
5140
+ flex: 1;
5141
+ font-size: 0.8125rem;
5142
+ white-space: nowrap;
5143
+ overflow: hidden;
5144
+ text-overflow: ellipsis;
5145
+ }
5146
+
5147
+ .checkpoint-branch {
5148
+ font-size: 0.6875rem;
5149
+ padding: 0 0.375rem;
5150
+ border-radius: 999px;
5151
+ background: var(--agui-hover, #f3f3f5);
5152
+ opacity: 0.8;
5153
+ }
5154
+
5155
+ .checkpoint-action {
5156
+ font: inherit;
5157
+ font-size: 0.75rem;
5158
+ cursor: pointer;
5159
+ padding: 0.125rem 0.5rem;
5160
+ border: 1px solid var(--agui-border, #d7d7dc);
5161
+ border-radius: 0.375rem;
5162
+ background: transparent;
5163
+ color: inherit;
5164
+ }
5165
+
5166
+ .checkpoint-action:hover {
5167
+ background: var(--agui-hover, #f3f3f5);
5168
+ }
5169
+
4955
5170
  .drawer-backdrop {
4956
5171
  position: absolute;
4957
5172
  inset: 0;
@@ -5182,30 +5397,6 @@ var ThoughtsBlock = class {
5182
5397
  }
5183
5398
  };
5184
5399
 
5185
- // src/ui/relative_time.ts
5186
- function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
5187
- if (!Number.isFinite(timestamp)) {
5188
- return strings.justNow;
5189
- }
5190
- const seconds = Math.round((now - timestamp) / 1e3);
5191
- if (seconds < 60) {
5192
- return strings.justNow;
5193
- }
5194
- const minutes = Math.round(seconds / 60);
5195
- if (minutes < 60) {
5196
- return strings.minutesAgo.replace("{n}", String(minutes));
5197
- }
5198
- const hours = Math.round(minutes / 60);
5199
- if (hours < 24) {
5200
- return strings.hoursAgo.replace("{n}", String(hours));
5201
- }
5202
- const days = Math.round(hours / 24);
5203
- if (days < 7) {
5204
- return strings.daysAgo.replace("{n}", String(days));
5205
- }
5206
- return strings.weeksAgo.replace("{n}", String(Math.round(days / 7)));
5207
- }
5208
-
5209
5400
  // src/ui/thread_drawer.ts
5210
5401
  var ThreadDrawer = class {
5211
5402
  /** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
@@ -5728,6 +5919,22 @@ var AgUiClient = class {
5728
5919
  this.#onPersist = config.onPersist ?? (() => {
5729
5920
  });
5730
5921
  this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
5922
+ const onStateChanged = config.onStateChanged;
5923
+ if (onStateChanged !== void 0) {
5924
+ this.#agent.subscribe({
5925
+ onStateChanged: ({ state }) => {
5926
+ onStateChanged(state);
5927
+ }
5928
+ });
5929
+ }
5930
+ }
5931
+ /** The current AG-UI shared state, as the agent last applied it. */
5932
+ get state() {
5933
+ return this.#agent.state;
5934
+ }
5935
+ /** Replace the shared state; the next run sends it as `RunAgentInput.state`. */
5936
+ setState(state) {
5937
+ this.#agent.setState({ ...state });
5731
5938
  }
5732
5939
  /** Whether a run is currently in flight. */
5733
5940
  get running() {
@@ -6125,6 +6332,7 @@ function createHttpAgent(options) {
6125
6332
  return new HttpAgent({
6126
6333
  url: options.endpoint,
6127
6334
  headers: options.headers ?? {},
6335
+ initialState: { ...options.initialState ?? {} },
6128
6336
  // HttpAgent invokes its configured fetch as a method (`this.fetch(...)`),
6129
6337
  // which would rebind the global `fetch` to the agent instance and trigger
6130
6338
  // "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
@@ -6256,6 +6464,60 @@ var RemoteConversationStore = class {
6256
6464
  }
6257
6465
  };
6258
6466
 
6467
+ // src/core/run_index.ts
6468
+ var RunIndex = class {
6469
+ #url;
6470
+ #headers;
6471
+ constructor(url, headers = () => ({})) {
6472
+ this.#url = url.endsWith("/") ? url : `${url}/`;
6473
+ this.#headers = headers;
6474
+ }
6475
+ /**
6476
+ * The user's runs, or `[]` when the endpoint is unreachable or answers with
6477
+ * an error. A history affordance that cannot load is empty, never broken:
6478
+ * the caller renders its empty state rather than surfacing a transport fault
6479
+ * the user can do nothing about.
6480
+ */
6481
+ async list() {
6482
+ try {
6483
+ const response = await fetch(this.#url, {
6484
+ method: "GET",
6485
+ headers: { Accept: "application/json", ...this.#headers() }
6486
+ });
6487
+ if (!response.ok) {
6488
+ return [];
6489
+ }
6490
+ const body = await response.json();
6491
+ return body.runs ?? [];
6492
+ } catch {
6493
+ return [];
6494
+ }
6495
+ }
6496
+ /** The rows a client may actually continue. */
6497
+ async continuable() {
6498
+ return (await this.list()).filter((run) => run.continuable);
6499
+ }
6500
+ /** The endpoint that continues `runId` as a new run. */
6501
+ resumeUrl(runId) {
6502
+ return this.#sibling("resume", runId);
6503
+ }
6504
+ /** The endpoint that branches `runId` into a new run, leaving the source untouched. */
6505
+ forkUrl(runId) {
6506
+ return this.#sibling("fork", runId);
6507
+ }
6508
+ /**
6509
+ * `<mount>/<verb>/<runId>/`, derived from the index URL's own prefix.
6510
+ *
6511
+ * Built by string surgery on the trailing `runs/` rather than with `new URL`,
6512
+ * because the configured value may be root-relative (`/agent/runs/`) — the
6513
+ * common case in a Django template — and `new URL` needs an absolute base.
6514
+ */
6515
+ #sibling(verb, runId) {
6516
+ const prefix = this.#url.slice(0, -"runs/".length);
6517
+ return `${prefix}${verb}/${encodeURIComponent(runId)}/`;
6518
+ }
6519
+ };
6520
+
6259
6521
  // src/core/transcribe_audio.ts
6260
6522
  async function transcribeAudio(audio, options) {
6261
6523
  const form = new FormData();
@@ -6404,7 +6666,7 @@ var AgUiChat = class extends HTMLElement {
6404
6666
  /**
6405
6667
  * Per-run frontend tool catalog provider. Defaults to the built-in
6406
6668
  * `route.*` tools (when a {@link routeMap} is set) plus the tools registered
6407
- * via {@link registerTool} / {@link registerStateHook}; override to supply a
6669
+ * via {@link registerTool} / {@link registerPageState}; override to supply a
6408
6670
  * fully custom catalog.
6409
6671
  */
6410
6672
  getTools = () => [
@@ -6528,6 +6790,10 @@ var AgUiChat = class extends HTMLElement {
6528
6790
  #title;
6529
6791
  #skillsMenu;
6530
6792
  #drawer;
6793
+ /** Checkpoint panel; rows load only when `data-runs-url` is set. */
6794
+ #checkpoints;
6795
+ /** Built lazily from `data-runs-url`; `null` when the host didn't opt in. */
6796
+ #runIndex = null;
6531
6797
  #skillHint;
6532
6798
  /** File-picker button + hidden input + tray slot; the tray mounts on connect. */
6533
6799
  #attachButton;
@@ -6548,6 +6814,10 @@ var AgUiChat = class extends HTMLElement {
6548
6814
  /** Refs attached to the message currently being sent (the context manifest). */
6549
6815
  #runAttachments = [];
6550
6816
  #client = null;
6817
+ // Seed for the next client. Once one exists it owns the live value (the
6818
+ // agent applies STATE_SNAPSHOT / STATE_DELTA into it), so this is only the
6819
+ // starting point — `sharedState` reads through to the client when present.
6820
+ #sharedState = {};
6551
6821
  // Whether an interaction is in flight (first onRunStart → onSettled). Drives
6552
6822
  // the Send⇄Stop button: `agent.isRunning` is false between frontend-tool
6553
6823
  // rounds, but the user must still be able to stop there.
@@ -6618,6 +6888,70 @@ var AgUiChat = class extends HTMLElement {
6618
6888
  this.#deleteThread(threadId);
6619
6889
  }
6620
6890
  });
6891
+ this.#checkpoints = new CheckpointMenu((runId, verb) => {
6892
+ void this.#continueRun(runId, verb);
6893
+ });
6894
+ }
6895
+ /** The run index, built once from `data-runs-url`; `null` when unset. */
6896
+ #runs() {
6897
+ const url = this.getAttribute("data-runs-url");
6898
+ if (url === null || url === "") {
6899
+ return null;
6900
+ }
6901
+ if (this.#runIndex === null) {
6902
+ this.#runIndex = new RunIndex(url, () => this.headers);
6903
+ }
6904
+ return this.#runIndex;
6905
+ }
6906
+ /**
6907
+ * Continue `runId` as a **new** run, seeded server-side from its snapshot.
6908
+ *
6909
+ * Uses a short-lived agent pointed at the resume / fork endpoint and seeded
6910
+ * with **no** history, so the request carries only the turn typed here — the
6911
+ * contract those endpoints assume, since the server supplies the prior turns
6912
+ * from the snapshot and re-sending them would duplicate. Building a separate
6913
+ * agent makes that structural: the main agent keeps its own history, and
6914
+ * "only the new turn" can't be got wrong by forgetting to clear it. The
6915
+ * fresh `run_id` the endpoints also require comes free — a new agent mints
6916
+ * one per run.
6917
+ *
6918
+ * Handlers are the element's own, so the continuation streams into the same
6919
+ * transcript the user is looking at.
6920
+ */
6921
+ async #continueRun(runId, verb) {
6922
+ const index = this.#runs();
6923
+ if (index === null) {
6924
+ return;
6925
+ }
6926
+ const content = this.#input.value.trim();
6927
+ if (content === "") {
6928
+ return;
6929
+ }
6930
+ this.#input.value = "";
6931
+ const endpoint = verb === "resume" ? index.resumeUrl(runId) : index.forkUrl(runId);
6932
+ const agent = this.agentFactory({
6933
+ endpoint,
6934
+ headers: this.headers,
6935
+ getHeaders: () => this.headers,
6936
+ threadId: this.#threadId,
6937
+ // The seed the endpoints assume: nothing. The snapshot is the history.
6938
+ initialMessages: []
6939
+ });
6940
+ const client = new AgUiClient({
6941
+ agent,
6942
+ handlers: this.#handlers(),
6943
+ getTools: () => this.getTools(),
6944
+ getContext: () => this.getContext(),
6945
+ executeTool: (call) => this.#executeTool(call),
6946
+ resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
6947
+ connectionLostMessage: this.#strings.connectionLost
6948
+ });
6949
+ await client.send(content);
6950
+ }
6951
+ /** Load the checkpoint panel with the runs that can actually be continued. */
6952
+ async #refreshCheckpoints() {
6953
+ const index = this.#runs();
6954
+ this.#checkpoints.setRuns(index === null ? [] : await index.continuable());
6621
6955
  }
6622
6956
  /** Attributes whose late changes must reflect in already-rendered chrome. */
6623
6957
  static get observedAttributes() {
@@ -6630,12 +6964,40 @@ var AgUiChat = class extends HTMLElement {
6630
6964
  registerTool(tool) {
6631
6965
  this.#toolRegistry.register(tool);
6632
6966
  }
6633
- /** Bind a piece of host state to `read_<name>` / `set_<name>` tools. */
6634
- registerStateHook(hook) {
6635
- for (const tool of createStateHookTools(hook)) {
6967
+ /**
6968
+ * AG-UI **shared state** for this conversation — the protocol's own state
6969
+ * channel, sent as `RunAgentInput.state` on every run and replaced in place
6970
+ * when the server streams `STATE_SNAPSHOT` / `STATE_DELTA`. Assigning seeds
6971
+ * the next run; reading returns whatever the agent last applied.
6972
+ *
6973
+ * Listen for {@link STATE_EVENT} to react to server-driven changes.
6974
+ *
6975
+ * Not to be confused with {@link registerPageState}, which exposes host
6976
+ * state to the agent as ordinary *tools*.
6977
+ */
6978
+ get sharedState() {
6979
+ return this.#client?.state ?? this.#sharedState;
6980
+ }
6981
+ set sharedState(state) {
6982
+ this.#sharedState = { ...state };
6983
+ this.#client?.setState(this.#sharedState);
6984
+ }
6985
+ /** Bind a piece of host page state to `read_<name>` / `set_<name>` tools. */
6986
+ registerPageState(binding) {
6987
+ for (const tool of createPageStateTools(binding)) {
6636
6988
  this.#toolRegistry.register(tool);
6637
6989
  }
6638
6990
  }
6991
+ /**
6992
+ * @deprecated Renamed to {@link registerPageState}. The old name read as
6993
+ * AG-UI shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this
6994
+ * component does not implement — these are ordinary client tools over host
6995
+ * page state. Behaviour is unchanged; this alias will be removed in a future
6996
+ * major.
6997
+ */
6998
+ registerStateHook(binding) {
6999
+ this.registerPageState(binding);
7000
+ }
6639
7001
  /** The built-in `route.*` tools, present only when a route map is set. */
6640
7002
  #routeTools() {
6641
7003
  if (this.routeMap.length === 0) {
@@ -6797,6 +7159,7 @@ var AgUiChat = class extends HTMLElement {
6797
7159
  }
6798
7160
  this.#render();
6799
7161
  this.#drawer.setStrings(this.#strings);
7162
+ this.#checkpoints.setStrings(this.#strings);
6800
7163
  if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
6801
7164
  this.setAttribute("collapsed", "");
6802
7165
  }
@@ -7338,11 +7701,20 @@ Use the read_attachment tool with an id to read a file's contents.`
7338
7701
  void this.#refreshDrawer();
7339
7702
  this.#drawer.open();
7340
7703
  });
7704
+ const checkpoints = this.#headerButton("checkpoints", this.#strings.checkpoints, "\u2B6F");
7705
+ checkpoints.addEventListener("click", () => {
7706
+ void this.#refreshCheckpoints();
7707
+ this.#checkpoints.open();
7708
+ });
7341
7709
  const newChat = this.#headerButton("new", this.#strings.newChat, "\u271A");
7342
7710
  newChat.addEventListener("click", () => this.newChat());
7343
7711
  const collapse = this.#headerButton("collapse", this.#strings.collapse, "\u2014");
7344
7712
  collapse.addEventListener("click", () => this.toggleCollapsed());
7345
- controls.append(history, newChat);
7713
+ if (this.#runs() !== null) {
7714
+ controls.append(history, checkpoints, newChat);
7715
+ } else {
7716
+ controls.append(history, newChat);
7717
+ }
7346
7718
  if (this.getAttribute("data-theme-toggle") !== null) {
7347
7719
  this.#themeToggle.type = "button";
7348
7720
  this.#themeToggle.className = "header-btn header-btn--theme";
@@ -7419,7 +7791,8 @@ Use the read_attachment tool with an id to read a file's contents.`
7419
7791
  this.#attachSlot,
7420
7792
  inputRow,
7421
7793
  footer,
7422
- this.#drawer.element
7794
+ this.#drawer.element,
7795
+ this.#checkpoints.element
7423
7796
  );
7424
7797
  this.#rail.className = "rail";
7425
7798
  this.#rail.type = "button";
@@ -7550,7 +7923,8 @@ Use the read_attachment tool with an id to read a file's contents.`
7550
7923
  // re-reads this on each call.
7551
7924
  getHeaders: () => this.headers,
7552
7925
  threadId: this.#threadId,
7553
- initialMessages: this.#initialMessages
7926
+ initialMessages: this.#initialMessages,
7927
+ initialState: this.#sharedState
7554
7928
  });
7555
7929
  this.#client = new AgUiClient({
7556
7930
  agent,
@@ -7560,11 +7934,23 @@ Use the read_attachment tool with an id to read a file's contents.`
7560
7934
  executeTool: (call) => this.#executeTool(call),
7561
7935
  resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
7562
7936
  onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages),
7937
+ onStateChanged: (state) => this.#onSharedStateChanged(state),
7563
7938
  connectionLostMessage: this.#strings.connectionLost
7564
7939
  });
7565
7940
  }
7566
7941
  return this.#client;
7567
7942
  }
7943
+ /** Mirror the agent's applied state and tell the host it moved. */
7944
+ #onSharedStateChanged(state) {
7945
+ this.#sharedState = { ...state };
7946
+ this.dispatchEvent(
7947
+ new CustomEvent(STATE_EVENT, {
7948
+ detail: { state: this.#sharedState },
7949
+ bubbles: true,
7950
+ composed: true
7951
+ })
7952
+ );
7953
+ }
7568
7954
  /** Whether ``call`` should be gated behind the confirmation card. */
7569
7955
  async #needsConfirmation(call, tool) {
7570
7956
  if (this.autoConfirm) {
@@ -7870,10 +8256,11 @@ function setControlValue(el, value) {
7870
8256
  }
7871
8257
 
7872
8258
  // src/version.ts
7873
- var VERSION = "0.11.0";
8259
+ var VERSION = "0.13.0";
7874
8260
  export {
7875
8261
  AgUiChat,
7876
8262
  AgUiClient,
8263
+ CheckpointMenu,
7877
8264
  ClientToolRegistry,
7878
8265
  ConnectionLostError,
7879
8266
  DEFAULT_UI_STRINGS,
@@ -7882,6 +8269,8 @@ export {
7882
8269
  MESSAGE_ROLE,
7883
8270
  PAGE_ACTIONS,
7884
8271
  RemoteConversationStore,
8272
+ RunIndex,
8273
+ STATE_EVENT,
7885
8274
  SUBMIT_EVENT,
7886
8275
  SessionStorageStore,
7887
8276
  TOGGLE_EVENT,
@@ -7897,6 +8286,7 @@ export {
7897
8286
  createHttpAgent,
7898
8287
  createPageActionTools,
7899
8288
  createPageMapContext,
8289
+ createPageStateTools,
7900
8290
  createRouteTools,
7901
8291
  createStateHookTools,
7902
8292
  defineAgUiChat,