@fragno-dev/pi-fragment 0.0.1 → 0.0.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 (98) hide show
  1. package/README.md +39 -3
  2. package/dist/browser/client/react.d.ts +44 -36
  3. package/dist/browser/client/react.d.ts.map +1 -1
  4. package/dist/browser/client/react.js +105 -22
  5. package/dist/browser/client/react.js.map +1 -1
  6. package/dist/browser/client/solid.d.ts +42 -36
  7. package/dist/browser/client/solid.d.ts.map +1 -1
  8. package/dist/browser/client/solid.js +27 -13
  9. package/dist/browser/client/solid.js.map +1 -1
  10. package/dist/browser/client/svelte.d.ts +42 -36
  11. package/dist/browser/client/svelte.d.ts.map +1 -1
  12. package/dist/browser/client/svelte.js +14 -6
  13. package/dist/browser/client/svelte.js.map +1 -1
  14. package/dist/browser/client/vanilla.d.ts +99 -39
  15. package/dist/browser/client/vanilla.d.ts.map +1 -1
  16. package/dist/browser/client/vanilla.js +151 -3
  17. package/dist/browser/client/vanilla.js.map +1 -1
  18. package/dist/browser/client/vue.d.ts +54 -38
  19. package/dist/browser/client/vue.d.ts.map +1 -1
  20. package/dist/browser/client/vue.js +25 -17
  21. package/dist/browser/client/vue.js.map +1 -1
  22. package/dist/browser/{factory-DKoO_lRA.js → clients-BscY_HVe.js} +1051 -799
  23. package/dist/browser/clients-BscY_HVe.js.map +1 -0
  24. package/dist/browser/index.d.ts +3 -776
  25. package/dist/browser/index.js +801 -2
  26. package/dist/browser/index.js.map +1 -0
  27. package/dist/browser/routes-CpL_YGWK.d.ts +1560 -0
  28. package/dist/browser/routes-CpL_YGWK.d.ts.map +1 -0
  29. package/dist/cli/mod.d.ts.map +1 -1
  30. package/dist/cli/mod.js +245 -7
  31. package/dist/cli/mod.js.map +1 -1
  32. package/dist/node/{pi → client}/clients.d.ts +46 -36
  33. package/dist/node/client/clients.d.ts.map +1 -0
  34. package/dist/node/client/clients.js +54 -0
  35. package/dist/node/client/clients.js.map +1 -0
  36. package/dist/node/client/session-controller.d.ts +31 -0
  37. package/dist/node/client/session-controller.d.ts.map +1 -0
  38. package/dist/node/client/session-controller.js +33 -0
  39. package/dist/node/client/session-controller.js.map +1 -0
  40. package/dist/node/client/session-store.d.ts +71 -0
  41. package/dist/node/client/session-store.d.ts.map +1 -0
  42. package/dist/node/client/session-store.js +637 -0
  43. package/dist/node/client/session-store.js.map +1 -0
  44. package/dist/node/debug-log.d.ts +9 -0
  45. package/dist/node/debug-log.d.ts.map +1 -0
  46. package/dist/node/debug-log.js +58 -0
  47. package/dist/node/debug-log.js.map +1 -0
  48. package/dist/node/index.d.ts +5 -4
  49. package/dist/node/index.js +5 -3
  50. package/dist/node/pi/definition.d.ts +1 -1
  51. package/dist/node/pi/definition.d.ts.map +1 -1
  52. package/dist/node/pi/dsl.d.ts +5 -2
  53. package/dist/node/pi/dsl.d.ts.map +1 -1
  54. package/dist/node/pi/dsl.js +22 -3
  55. package/dist/node/pi/dsl.js.map +1 -1
  56. package/dist/node/pi/factory.d.ts +37 -34
  57. package/dist/node/pi/factory.d.ts.map +1 -1
  58. package/dist/node/pi/factory.js.map +1 -1
  59. package/dist/node/pi/mappers.js +0 -1
  60. package/dist/node/pi/mappers.js.map +1 -1
  61. package/dist/node/pi/route-schemas.js +42 -10
  62. package/dist/node/pi/route-schemas.js.map +1 -1
  63. package/dist/node/pi/types.d.ts +155 -7
  64. package/dist/node/pi/types.d.ts.map +1 -1
  65. package/dist/node/pi/types.js +6 -0
  66. package/dist/node/pi/types.js.map +1 -0
  67. package/dist/node/pi/workflow/active-session.d.ts +2 -0
  68. package/dist/node/pi/workflow/active-session.js +107 -0
  69. package/dist/node/pi/workflow/active-session.js.map +1 -0
  70. package/dist/node/pi/workflow/agent-runner.d.ts +13 -0
  71. package/dist/node/pi/workflow/agent-runner.d.ts.map +1 -0
  72. package/dist/node/pi/workflow/agent-runner.js +228 -0
  73. package/dist/node/pi/workflow/agent-runner.js.map +1 -0
  74. package/dist/node/pi/workflow/tool-journal.js +157 -0
  75. package/dist/node/pi/workflow/tool-journal.js.map +1 -0
  76. package/dist/node/pi/workflow/workflow.d.ts +29 -0
  77. package/dist/node/pi/workflow/workflow.d.ts.map +1 -0
  78. package/dist/node/pi/workflow/workflow.js +219 -0
  79. package/dist/node/pi/workflow/workflow.js.map +1 -0
  80. package/dist/node/routes.d.ts +38 -35
  81. package/dist/node/routes.d.ts.map +1 -1
  82. package/dist/node/routes.js +203 -132
  83. package/dist/node/routes.js.map +1 -1
  84. package/dist/node/schema.js +1 -1
  85. package/dist/node/schema.js.map +1 -1
  86. package/package.json +30 -29
  87. package/dist/browser/client-Bk-J98pf.d.ts +0 -679
  88. package/dist/browser/client-Bk-J98pf.d.ts.map +0 -1
  89. package/dist/browser/factory-DKoO_lRA.js.map +0 -1
  90. package/dist/browser/index.d.ts.map +0 -1
  91. package/dist/node/pi/clients.d.ts.map +0 -1
  92. package/dist/node/pi/clients.js +0 -18
  93. package/dist/node/pi/clients.js.map +0 -1
  94. package/dist/node/pi/workflow.d.ts +0 -31
  95. package/dist/node/pi/workflow.d.ts.map +0 -1
  96. package/dist/node/pi/workflow.js +0 -242
  97. package/dist/node/pi/workflow.js.map +0 -1
  98. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,9 +1,6 @@
1
+ import { atom, batched, computed, map, onMount, onStart, onStop, startTask, task } from "nanostores";
1
2
  import { defineFragment, defineRoutes } from "@fragno-dev/core";
2
3
  import { z } from "zod";
3
- import { Agent } from "@mariozechner/pi-agent-core";
4
- import { createAssistantMessageEventStream } from "@mariozechner/pi-ai";
5
- import { NonRetryableError, defineWorkflow } from "@fragno-dev/workflows";
6
- import { column, idColumn, schema } from "@fragno-dev/db/schema";
7
4
 
8
5
  //#region ../fragno/dist/api/route.js
9
6
  /**
@@ -19,6 +16,80 @@ function resolveRouteFactories(context, routesOrFactories) {
19
16
  return routes;
20
17
  }
21
18
 
19
+ //#endregion
20
+ //#region ../fragno/dist/api/internal/path.js
21
+ /**
22
+ * Extract parameter names from a path pattern at runtime.
23
+ * Examples:
24
+ * - "/users/:id" => ["id"]
25
+ * - "/files/**" => ["**"]
26
+ * - "/files/**:rest" => ["rest"]
27
+ */
28
+ function extractPathParams(pathPattern) {
29
+ const segments = pathPattern.split("/").filter((s) => s.length > 0);
30
+ const names = [];
31
+ for (const segment of segments) {
32
+ if (segment.startsWith(":")) {
33
+ names.push(segment.slice(1));
34
+ continue;
35
+ }
36
+ if (segment === "**") {
37
+ names.push("**");
38
+ continue;
39
+ }
40
+ if (segment.startsWith("**:")) {
41
+ names.push(segment.slice(3));
42
+ continue;
43
+ }
44
+ }
45
+ return names;
46
+ }
47
+ /**
48
+ * Build a concrete path by replacing placeholders in a path pattern with values.
49
+ *
50
+ * Supports the same placeholder syntax as the matcher:
51
+ * - Named parameter ":name" is URL-encoded as a single segment
52
+ * - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
53
+ * - Named wildcard "**:name" inserts the remainder from the named key
54
+ *
55
+ * Examples:
56
+ * - buildPath("/users/:id", { id: "123" }) => "/users/123"
57
+ * - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
58
+ * - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
59
+ */
60
+ function buildPath(pathPattern, params) {
61
+ const patternSegments = pathPattern.split("/");
62
+ const builtSegments = [];
63
+ for (const segment of patternSegments) {
64
+ if (segment.length === 0) {
65
+ builtSegments.push("");
66
+ continue;
67
+ }
68
+ if (segment.startsWith(":")) {
69
+ const name = segment.slice(1);
70
+ const value = params[name];
71
+ if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
72
+ builtSegments.push(encodeURIComponent(value));
73
+ continue;
74
+ }
75
+ if (segment === "**") {
76
+ const value = params["**"];
77
+ if (value === void 0) throw new Error("Missing value for path wildcard **");
78
+ builtSegments.push(value);
79
+ continue;
80
+ }
81
+ if (segment.startsWith("**:")) {
82
+ const name = segment.slice(3);
83
+ const value = params[name];
84
+ if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
85
+ builtSegments.push(value);
86
+ continue;
87
+ }
88
+ builtSegments.push(segment);
89
+ }
90
+ return builtSegments.join("/");
91
+ }
92
+
22
93
  //#endregion
23
94
  //#region ../fragno/dist/api/internal/route.js
24
95
  function getMountRoute(opts) {
@@ -449,77 +520,95 @@ var RequestOutputContext = class extends OutputContext {
449
520
  };
450
521
 
451
522
  //#endregion
452
- //#region ../fragno/dist/api/internal/path.js
523
+ //#region ../fragno/dist/util/content-type.js
453
524
  /**
454
- * Extract parameter names from a path pattern at runtime.
455
- * Examples:
456
- * - "/users/:id" => ["id"]
457
- * - "/files/**" => ["**"]
458
- * - "/files/**:rest" => ["rest"]
525
+ * Parses a content-type header string into its components
526
+ *
527
+ * @param contentType - The content-type header value to parse
528
+ * @returns A ParsedContentType object or null if the input is invalid
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * const { type, subtype, mediaType, parameters }
533
+ * = parseContentType("application/json; charset=utf-8");
534
+ * console.assert(type === "application");
535
+ * console.assert(subtype === "json");
536
+ * console.assert(mediaType === "application/json");
537
+ * console.assert(parameters["charset"] === "utf-8");
459
538
  */
460
- function extractPathParams(pathPattern) {
461
- const segments = pathPattern.split("/").filter((s) => s.length > 0);
462
- const names = [];
463
- for (const segment of segments) {
464
- if (segment.startsWith(":")) {
465
- names.push(segment.slice(1));
466
- continue;
467
- }
468
- if (segment === "**") {
469
- names.push("**");
470
- continue;
471
- }
472
- if (segment.startsWith("**:")) {
473
- names.push(segment.slice(3));
474
- continue;
539
+ function parseContentType(contentType) {
540
+ if (!contentType || typeof contentType !== "string") return null;
541
+ const trimmed = contentType.trim();
542
+ if (!trimmed) return null;
543
+ const parts = trimmed.split(";").map((part) => part.trim());
544
+ const mediaType = parts[0];
545
+ if (!mediaType) return null;
546
+ const typeParts = mediaType.split("/");
547
+ if (typeParts.length !== 2) return null;
548
+ const [type, subtype] = typeParts.map((part) => part.trim().toLowerCase());
549
+ if (!type || !subtype) return null;
550
+ const parameters = {};
551
+ for (let i = 1; i < parts.length; i++) {
552
+ const param = parts[i];
553
+ const equalIndex = param.indexOf("=");
554
+ if (equalIndex > 0) {
555
+ const key = param.slice(0, equalIndex).trim().toLowerCase();
556
+ let value = param.slice(equalIndex + 1).trim();
557
+ if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
558
+ if (key) parameters[key] = value;
475
559
  }
476
560
  }
477
- return names;
561
+ return {
562
+ type,
563
+ subtype,
564
+ mediaType: `${type}/${subtype}`,
565
+ parameters
566
+ };
478
567
  }
568
+
569
+ //#endregion
570
+ //#region ../fragno/dist/util/nanostores.js
479
571
  /**
480
- * Build a concrete path by replacing placeholders in a path pattern with values.
481
- *
482
- * Supports the same placeholder syntax as the matcher:
483
- * - Named parameter ":name" is URL-encoded as a single segment
484
- * - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
485
- * - Named wildcard "**:name" inserts the remainder from the named key
486
- *
487
- * Examples:
488
- * - buildPath("/users/:id", { id: "123" }) => "/users/123"
489
- * - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
490
- * - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
572
+ * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
491
573
  */
492
- function buildPath(pathPattern, params) {
493
- const patternSegments = pathPattern.split("/");
494
- const builtSegments = [];
495
- for (const segment of patternSegments) {
496
- if (segment.length === 0) {
497
- builtSegments.push("");
498
- continue;
499
- }
500
- if (segment.startsWith(":")) {
501
- const name = segment.slice(1);
502
- const value = params[name];
503
- if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
504
- builtSegments.push(encodeURIComponent(value));
505
- continue;
506
- }
507
- if (segment === "**") {
508
- const value = params["**"];
509
- if (value === void 0) throw new Error("Missing value for path wildcard **");
510
- builtSegments.push(value);
511
- continue;
512
- }
513
- if (segment.startsWith("**:")) {
514
- const name = segment.slice(3);
515
- const value = params[name];
516
- if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
517
- builtSegments.push(value);
518
- continue;
519
- }
520
- builtSegments.push(segment);
574
+ function unwrapAtom(value) {
575
+ if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
576
+ return value;
577
+ }
578
+ /**
579
+ * Normalizes an object where values can be plain values, Atoms, or Vue Refs.
580
+ * Returns a new object with all values normalized to plain values.
581
+ */
582
+ function unwrapObject(params) {
583
+ if (!params) return;
584
+ return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
585
+ }
586
+ function isReadableAtom(value) {
587
+ if (!value) return false;
588
+ if (typeof value !== "object" || value === null) return false;
589
+ if (!("get" in value) || typeof value.get !== "function") return false;
590
+ if (!("lc" in value) || typeof value.lc !== "number") return false;
591
+ if (!("notify" in value) || typeof value.notify !== "function") return false;
592
+ if (!("off" in value) || typeof value.off !== "function") return false;
593
+ if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
594
+ if (!("value" in value)) return false;
595
+ return true;
596
+ }
597
+
598
+ //#endregion
599
+ //#region ../fragno/dist/util/ssr.js
600
+ let stores = [];
601
+ const SSR_ENABLED = false;
602
+ function addStore(store) {
603
+ stores.push(store);
604
+ }
605
+ let clientInitialData;
606
+ function getInitialData(key) {
607
+ if (clientInitialData?.has(key)) {
608
+ const data = clientInitialData.get(key);
609
+ clientInitialData.delete(key);
610
+ return data;
521
611
  }
522
- return builtSegments.join("/");
523
612
  }
524
613
 
525
614
  //#endregion
@@ -613,51 +702,38 @@ var FragnoClientApiError = class FragnoClientApiError extends FragnoClientError
613
702
  };
614
703
 
615
704
  //#endregion
616
- //#region ../fragno/dist/util/content-type.js
705
+ //#region ../fragno/dist/client/internal/fetcher-merge.js
617
706
  /**
618
- * Parses a content-type header string into its components
619
- *
620
- * @param contentType - The content-type header value to parse
621
- * @returns A ParsedContentType object or null if the input is invalid
622
- *
623
- * @example
624
- * ```ts
625
- * const { type, subtype, mediaType, parameters }
626
- * = parseContentType("application/json; charset=utf-8");
627
- * console.assert(type === "application");
628
- * console.assert(subtype === "json");
629
- * console.assert(mediaType === "application/json");
630
- * console.assert(parameters["charset"] === "utf-8");
707
+ * Merge two fetcher configurations, with user config taking precedence.
708
+ * If user provides a custom function, it takes full precedence.
709
+ * Otherwise, deep merge RequestInit options.
631
710
  */
632
- function parseContentType(contentType) {
633
- if (!contentType || typeof contentType !== "string") return null;
634
- const trimmed = contentType.trim();
635
- if (!trimmed) return null;
636
- const parts = trimmed.split(";").map((part) => part.trim());
637
- const mediaType = parts[0];
638
- if (!mediaType) return null;
639
- const typeParts = mediaType.split("/");
640
- if (typeParts.length !== 2) return null;
641
- const [type, subtype] = typeParts.map((part) => part.trim().toLowerCase());
642
- if (!type || !subtype) return null;
643
- const parameters = {};
644
- for (let i = 1; i < parts.length; i++) {
645
- const param = parts[i];
646
- const equalIndex = param.indexOf("=");
647
- if (equalIndex > 0) {
648
- const key = param.slice(0, equalIndex).trim().toLowerCase();
649
- let value = param.slice(equalIndex + 1).trim();
650
- if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
651
- if (key) parameters[key] = value;
652
- }
653
- }
711
+ function mergeFetcherConfigs(authorConfig, userConfig) {
712
+ if (userConfig?.type === "function") return userConfig;
713
+ if (!userConfig && authorConfig?.type === "function") return authorConfig;
714
+ const authorOpts = authorConfig?.type === "options" ? authorConfig.options : {};
715
+ const userOpts = userConfig?.type === "options" ? userConfig.options : {};
716
+ if (Object.keys(authorOpts).length === 0 && Object.keys(userOpts).length === 0) return;
654
717
  return {
655
- type,
656
- subtype,
657
- mediaType: `${type}/${subtype}`,
658
- parameters
718
+ type: "options",
719
+ options: {
720
+ ...authorOpts,
721
+ ...userOpts,
722
+ headers: mergeHeaders(authorOpts.headers, userOpts.headers)
723
+ }
659
724
  };
660
725
  }
726
+ /**
727
+ * Merge headers from author and user configs.
728
+ * User headers override author headers.
729
+ */
730
+ function mergeHeaders(author, user) {
731
+ if (!author && !user) return;
732
+ const merged = new Headers(author);
733
+ new Headers(user).forEach((value, key) => merged.set(key, value));
734
+ if (merged.keys().next().done) return;
735
+ return merged;
736
+ }
661
737
 
662
738
  //#endregion
663
739
  //#region ../fragno/dist/client/internal/ndjson-streaming.js
@@ -742,380 +818,56 @@ async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
742
818
  }
743
819
  }
744
820
  /**
745
- * Continues streaming the remaining items in the background
746
- */
747
- async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
748
- let buffer = initialBuffer;
749
- try {
750
- while (true) {
751
- if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
752
- const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
753
- if (done) {
754
- if (buffer.trim()) {
755
- const lines$1 = buffer.split("\n");
756
- for (const line of lines$1) {
757
- if (!line.trim()) continue;
758
- try {
759
- const jsonObject = JSON.parse(line);
760
- items.push(jsonObject);
761
- store?.setData([...items]);
762
- } catch (parseError) {
763
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
764
- }
765
- }
766
- }
767
- break;
768
- }
769
- buffer += decoder.decode(value, { stream: true });
770
- const lines = buffer.split("\n");
771
- buffer = lines.pop() || "";
772
- for (const line of lines) {
773
- if (!line.trim()) continue;
774
- try {
775
- const jsonObject = JSON.parse(line);
776
- items.push(jsonObject);
777
- store?.setData([...items]);
778
- } catch (parseError) {
779
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
780
- }
781
- }
782
- }
783
- } catch (error) {
784
- if (error instanceof FragnoClientError) store?.setError(error);
785
- else {
786
- const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
787
- store?.setError(clientError);
788
- throw clientError;
789
- }
790
- throw error;
791
- } finally {
792
- reader.releaseLock();
793
- }
794
- return items;
795
- }
796
-
797
- //#endregion
798
- //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/task/index.js
799
- let tasks = 0;
800
- let resolves = [];
801
- function startTask() {
802
- tasks += 1;
803
- return () => {
804
- tasks -= 1;
805
- if (tasks === 0) {
806
- let prevResolves = resolves;
807
- resolves = [];
808
- for (let i of prevResolves) i();
809
- }
810
- };
811
- }
812
- function task(cb) {
813
- let endTask = startTask();
814
- let promise = cb().finally(endTask);
815
- promise.t = true;
816
- return promise;
817
- }
818
-
819
- //#endregion
820
- //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.js
821
- let clean = Symbol("clean");
822
-
823
- //#endregion
824
- //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/atom/index.js
825
- let listenerQueue = [];
826
- let lqIndex = 0;
827
- const QUEUE_ITEMS_PER_LISTENER = 4;
828
- let epoch = 0;
829
- const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
830
- let listeners = [];
831
- let $atom = {
832
- get() {
833
- if (!$atom.lc) $atom.listen(() => {})();
834
- return $atom.value;
835
- },
836
- lc: 0,
837
- listen(listener) {
838
- $atom.lc = listeners.push(listener);
839
- return () => {
840
- for (let i = lqIndex + QUEUE_ITEMS_PER_LISTENER; i < listenerQueue.length;) if (listenerQueue[i] === listener) listenerQueue.splice(i, QUEUE_ITEMS_PER_LISTENER);
841
- else i += QUEUE_ITEMS_PER_LISTENER;
842
- let index = listeners.indexOf(listener);
843
- if (~index) {
844
- listeners.splice(index, 1);
845
- if (!--$atom.lc) $atom.off();
846
- }
847
- };
848
- },
849
- notify(oldValue, changedKey) {
850
- epoch++;
851
- let runListenerQueue = !listenerQueue.length;
852
- for (let listener of listeners) listenerQueue.push(listener, $atom.value, oldValue, changedKey);
853
- if (runListenerQueue) {
854
- for (lqIndex = 0; lqIndex < listenerQueue.length; lqIndex += QUEUE_ITEMS_PER_LISTENER) listenerQueue[lqIndex](listenerQueue[lqIndex + 1], listenerQueue[lqIndex + 2], listenerQueue[lqIndex + 3]);
855
- listenerQueue.length = 0;
856
- }
857
- },
858
- off() {},
859
- set(newValue) {
860
- let oldValue = $atom.value;
861
- if (oldValue !== newValue) {
862
- $atom.value = newValue;
863
- $atom.notify(oldValue);
864
- }
865
- },
866
- subscribe(listener) {
867
- let unbind = $atom.listen(listener);
868
- listener($atom.value);
869
- return unbind;
870
- },
871
- value: initialValue
872
- };
873
- $atom[clean] = () => {
874
- listeners = [];
875
- $atom.lc = 0;
876
- $atom.off();
877
- };
878
- return $atom;
879
- };
880
-
881
- //#endregion
882
- //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/lifecycle/index.js
883
- const START = 0;
884
- const STOP = 1;
885
- const MOUNT = 5;
886
- const UNMOUNT = 6;
887
- const REVERT_MUTATION = 10;
888
- let on = (object, listener, eventKey, mutateStore) => {
889
- object.events = object.events || {};
890
- if (!object.events[eventKey + REVERT_MUTATION]) object.events[eventKey + REVERT_MUTATION] = mutateStore((eventProps) => {
891
- object.events[eventKey].reduceRight((event, l) => (l(event), event), {
892
- shared: {},
893
- ...eventProps
894
- });
895
- });
896
- object.events[eventKey] = object.events[eventKey] || [];
897
- object.events[eventKey].push(listener);
898
- return () => {
899
- let currentListeners = object.events[eventKey];
900
- let index = currentListeners.indexOf(listener);
901
- currentListeners.splice(index, 1);
902
- if (!currentListeners.length) {
903
- delete object.events[eventKey];
904
- object.events[eventKey + REVERT_MUTATION]();
905
- delete object.events[eventKey + REVERT_MUTATION];
906
- }
907
- };
908
- };
909
- let onStart = ($store, listener) => on($store, listener, START, (runListeners) => {
910
- let originListen = $store.listen;
911
- $store.listen = (arg) => {
912
- if (!$store.lc && !$store.starting) {
913
- $store.starting = true;
914
- runListeners();
915
- delete $store.starting;
916
- }
917
- return originListen(arg);
918
- };
919
- return () => {
920
- $store.listen = originListen;
921
- };
922
- });
923
- let onStop = ($store, listener) => on($store, listener, STOP, (runListeners) => {
924
- let originOff = $store.off;
925
- $store.off = () => {
926
- runListeners();
927
- originOff();
928
- };
929
- return () => {
930
- $store.off = originOff;
931
- };
932
- });
933
- let STORE_UNMOUNT_DELAY = 1e3;
934
- let onMount = ($store, initialize) => {
935
- let listener = (payload) => {
936
- let destroy = initialize(payload);
937
- if (destroy) $store.events[UNMOUNT].push(destroy);
938
- };
939
- return on($store, listener, MOUNT, (runListeners) => {
940
- let originListen = $store.listen;
941
- $store.listen = (...args) => {
942
- if (!$store.lc && !$store.active) {
943
- $store.active = true;
944
- runListeners();
945
- }
946
- return originListen(...args);
947
- };
948
- let originOff = $store.off;
949
- $store.events[UNMOUNT] = [];
950
- $store.off = () => {
951
- originOff();
952
- setTimeout(() => {
953
- if ($store.active && !$store.lc) {
954
- $store.active = false;
955
- for (let destroy of $store.events[UNMOUNT]) destroy();
956
- $store.events[UNMOUNT] = [];
957
- }
958
- }, STORE_UNMOUNT_DELAY);
959
- };
960
- {
961
- let originClean = $store[clean];
962
- $store[clean] = () => {
963
- for (let destroy of $store.events[UNMOUNT]) destroy();
964
- $store.events[UNMOUNT] = [];
965
- $store.active = false;
966
- originClean();
967
- };
968
- }
969
- return () => {
970
- $store.listen = originListen;
971
- $store.off = originOff;
972
- };
973
- });
974
- };
975
-
976
- //#endregion
977
- //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/computed/index.js
978
- let computedStore = (stores, cb, batched) => {
979
- if (!Array.isArray(stores)) stores = [stores];
980
- let previousArgs;
981
- let currentEpoch;
982
- let set = () => {
983
- if (currentEpoch === epoch) return;
984
- currentEpoch = epoch;
985
- let args = stores.map(($store) => $store.get());
986
- if (!previousArgs || args.some((arg, i) => arg !== previousArgs[i])) {
987
- previousArgs = args;
988
- let value = cb(...args);
989
- if (value && value.then && value.t) value.then((asyncValue) => {
990
- if (previousArgs === args) $computed.set(asyncValue);
991
- });
992
- else {
993
- $computed.set(value);
994
- currentEpoch = epoch;
995
- }
996
- }
997
- };
998
- let $computed = /* @__PURE__ */ atom(void 0);
999
- let get = $computed.get;
1000
- $computed.get = () => {
1001
- set();
1002
- return get();
1003
- };
1004
- let timer;
1005
- let run = batched ? () => {
1006
- clearTimeout(timer);
1007
- timer = setTimeout(set);
1008
- } : set;
1009
- onMount($computed, () => {
1010
- let unbinds = stores.map(($store) => $store.listen(run));
1011
- set();
1012
- return () => {
1013
- for (let unbind of unbinds) unbind();
1014
- };
1015
- });
1016
- return $computed;
1017
- };
1018
- const computed = /* @__NO_SIDE_EFFECTS__ */ (stores, fn) => computedStore(stores, fn);
1019
- const batched = /* @__NO_SIDE_EFFECTS__ */ (stores, fn) => computedStore(stores, fn, true);
1020
-
1021
- //#endregion
1022
- //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/map/index.js
1023
- const map = /* @__NO_SIDE_EFFECTS__ */ (initial = {}) => {
1024
- let $map = /* @__PURE__ */ atom(initial);
1025
- $map.setKey = function(key, value) {
1026
- let oldMap = $map.value;
1027
- if (typeof value === "undefined" && key in $map.value) {
1028
- $map.value = { ...$map.value };
1029
- delete $map.value[key];
1030
- $map.notify(oldMap, key);
1031
- } else if ($map.value[key] !== value) {
1032
- $map.value = {
1033
- ...$map.value,
1034
- [key]: value
1035
- };
1036
- $map.notify(oldMap, key);
1037
- }
1038
- };
1039
- return $map;
1040
- };
1041
-
1042
- //#endregion
1043
- //#region ../fragno/dist/util/ssr.js
1044
- let stores = [];
1045
- const SSR_ENABLED = false;
1046
- function addStore(store) {
1047
- stores.push(store);
1048
- }
1049
- let clientInitialData;
1050
- function getInitialData(key) {
1051
- if (clientInitialData?.has(key)) {
1052
- const data = clientInitialData.get(key);
1053
- clientInitialData.delete(key);
1054
- return data;
1055
- }
1056
- }
1057
-
1058
- //#endregion
1059
- //#region ../fragno/dist/util/nanostores.js
1060
- /**
1061
- * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
1062
- */
1063
- function unwrapAtom(value) {
1064
- if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
1065
- return value;
1066
- }
1067
- /**
1068
- * Normalizes an object where values can be plain values, Atoms, or Vue Refs.
1069
- * Returns a new object with all values normalized to plain values.
1070
- */
1071
- function unwrapObject(params) {
1072
- if (!params) return;
1073
- return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
1074
- }
1075
- function isReadableAtom(value) {
1076
- if (!value) return false;
1077
- if (typeof value !== "object" || value === null) return false;
1078
- if (!("get" in value) || typeof value.get !== "function") return false;
1079
- if (!("lc" in value) || typeof value.lc !== "number") return false;
1080
- if (!("notify" in value) || typeof value.notify !== "function") return false;
1081
- if (!("off" in value) || typeof value.off !== "function") return false;
1082
- if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
1083
- if (!("value" in value)) return false;
1084
- return true;
1085
- }
1086
-
1087
- //#endregion
1088
- //#region ../fragno/dist/client/internal/fetcher-merge.js
1089
- /**
1090
- * Merge two fetcher configurations, with user config taking precedence.
1091
- * If user provides a custom function, it takes full precedence.
1092
- * Otherwise, deep merge RequestInit options.
1093
- */
1094
- function mergeFetcherConfigs(authorConfig, userConfig) {
1095
- if (userConfig?.type === "function") return userConfig;
1096
- if (!userConfig && authorConfig?.type === "function") return authorConfig;
1097
- const authorOpts = authorConfig?.type === "options" ? authorConfig.options : {};
1098
- const userOpts = userConfig?.type === "options" ? userConfig.options : {};
1099
- if (Object.keys(authorOpts).length === 0 && Object.keys(userOpts).length === 0) return;
1100
- return {
1101
- type: "options",
1102
- options: {
1103
- ...authorOpts,
1104
- ...userOpts,
1105
- headers: mergeHeaders(authorOpts.headers, userOpts.headers)
1106
- }
1107
- };
1108
- }
1109
- /**
1110
- * Merge headers from author and user configs.
1111
- * User headers override author headers.
821
+ * Continues streaming the remaining items in the background
1112
822
  */
1113
- function mergeHeaders(author, user) {
1114
- if (!author && !user) return;
1115
- const merged = new Headers(author);
1116
- new Headers(user).forEach((value, key) => merged.set(key, value));
1117
- if (merged.keys().next().done) return;
1118
- return merged;
823
+ async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
824
+ let buffer = initialBuffer;
825
+ try {
826
+ while (true) {
827
+ if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
828
+ const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
829
+ if (done) {
830
+ if (buffer.trim()) {
831
+ const lines$1 = buffer.split("\n");
832
+ for (const line of lines$1) {
833
+ if (!line.trim()) continue;
834
+ try {
835
+ const jsonObject = JSON.parse(line);
836
+ items.push(jsonObject);
837
+ store?.setData([...items]);
838
+ } catch (parseError) {
839
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
840
+ }
841
+ }
842
+ }
843
+ break;
844
+ }
845
+ buffer += decoder.decode(value, { stream: true });
846
+ const lines = buffer.split("\n");
847
+ buffer = lines.pop() || "";
848
+ for (const line of lines) {
849
+ if (!line.trim()) continue;
850
+ try {
851
+ const jsonObject = JSON.parse(line);
852
+ items.push(jsonObject);
853
+ store?.setData([...items]);
854
+ } catch (parseError) {
855
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
856
+ }
857
+ }
858
+ }
859
+ } catch (error) {
860
+ if (error instanceof FragnoClientError) store?.setError(error);
861
+ else {
862
+ const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
863
+ store?.setError(clientError);
864
+ throw clientError;
865
+ }
866
+ throw error;
867
+ } finally {
868
+ reader.releaseLock();
869
+ }
870
+ return items;
1119
871
  }
1120
872
 
1121
873
  //#endregion
@@ -1134,7 +886,7 @@ let createNanoEvents = () => ({
1134
886
  });
1135
887
 
1136
888
  //#endregion
1137
- //#region ../../node_modules/.pnpm/@nanostores+query@0.3.4_nanostores@1.1.0/node_modules/@nanostores/query/dist/nanoquery.js
889
+ //#region ../../node_modules/.pnpm/@nanostores+query@0.3.4_nanostores@1.2.0/node_modules/@nanostores/query/dist/nanoquery.js
1138
890
  function defaultOnErrorRetry({ retryCount }) {
1139
891
  return ~~((Math.random() + .5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3;
1140
892
  }
@@ -1240,7 +992,7 @@ const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectCha
1240
992
  };
1241
993
  const createFetcherStore = (keyInput, { fetcher = globalFetcher, ...fetcherSettings } = {}) => {
1242
994
  if (!fetcher) throw new Error("You need to set up either global fetcher of fetcher in createFetcherStore");
1243
- const fetcherStore = /* @__PURE__ */ map({ ...notLoading }), settings = {
995
+ const fetcherStore = map({ ...notLoading }), settings = {
1244
996
  ...globalSettings,
1245
997
  ...fetcherSettings,
1246
998
  fetcher
@@ -1400,7 +1152,7 @@ const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectCha
1400
1152
  keysToRevalidate.forEach(revalidateKeys);
1401
1153
  }
1402
1154
  };
1403
- const store = /* @__PURE__ */ map({
1155
+ const store = map({
1404
1156
  mutate,
1405
1157
  ...notLoading
1406
1158
  });
@@ -1430,9 +1182,9 @@ const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectCha
1430
1182
  return typeof key === "string" || typeof key === "number" || key === true;
1431
1183
  }
1432
1184
  const getKeyStore = (keys) => {
1433
- if (isSomeKey(keys)) return [/* @__PURE__ */ atom(["" + keys, [keys]]), () => {}];
1185
+ if (isSomeKey(keys)) return [atom(["" + keys, [keys]]), () => {}];
1434
1186
  const keyParts = [];
1435
- const $key = /* @__PURE__ */ atom(null);
1187
+ const $key = atom(null);
1436
1188
  const keysAsStoresToIndexes = /* @__PURE__ */ new Map();
1437
1189
  const setKeyStoreValue = () => {
1438
1190
  if (keyParts.some((v) => v === null || v === void 0 || v === false)) $key.set(null);
@@ -1447,7 +1199,7 @@ const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectCha
1447
1199
  }
1448
1200
  }
1449
1201
  const storesAsArray = [...keysAsStoresToIndexes.keys()];
1450
- const $storeKeys = /* @__PURE__ */ batched(storesAsArray, (...storeValues) => {
1202
+ const $storeKeys = batched(storesAsArray, (...storeValues) => {
1451
1203
  for (let i = 0; i < storeValues.length; i++) {
1452
1204
  const store = storesAsArray[i], partIndex = keysAsStoresToIndexes.get(store);
1453
1205
  keyParts[partIndex] = store._ === fetcherSymbol ? store.value && "data" in store.value ? store.key : null : storeValues[i];
@@ -1540,6 +1292,18 @@ function prepareRequestBody(body, contentType) {
1540
1292
  headers: { "Content-Type": "application/json" }
1541
1293
  };
1542
1294
  }
1295
+ async function schemaAllowsUndefined(schema) {
1296
+ try {
1297
+ return !(await schema["~standard"].validate(void 0)).issues;
1298
+ } catch {
1299
+ return false;
1300
+ }
1301
+ }
1302
+ async function assertBodyProvided(body, inputSchema, errorMessage) {
1303
+ if (typeof body !== "undefined" || inputSchema === void 0) return;
1304
+ if (await schemaAllowsUndefined(inputSchema)) return;
1305
+ throw new Error(errorMessage);
1306
+ }
1543
1307
  /**
1544
1308
  * Merge request headers from multiple sources.
1545
1309
  * Returns undefined if there are no headers to merge.
@@ -1594,7 +1358,7 @@ function getCacheKey(method, path, params) {
1594
1358
  const pathParamValues = extractPathParams(path).map((name) => pathParams?.[name] ?? "<missing>");
1595
1359
  const queryParamValues = queryParams ? Object.keys(queryParams).sort().map((key) => {
1596
1360
  const value = queryParams[key];
1597
- if (value && typeof value === "object" && "get" in value) return /* @__PURE__ */ computed(value, (v) => v ?? "");
1361
+ if (value && typeof value === "object" && "get" in value) return computed(value, (v) => v ?? "");
1598
1362
  return value ?? "";
1599
1363
  }) : [];
1600
1364
  return [
@@ -1650,9 +1414,13 @@ var ClientBuilder = class {
1650
1414
  get cacheEntries() {
1651
1415
  return Object.fromEntries(this.#cache.entries());
1652
1416
  }
1653
- createStore(obj) {
1417
+ createStore(input) {
1418
+ if (typeof input === "function") return {
1419
+ factory: input,
1420
+ [STORE_SYMBOL]: true
1421
+ };
1654
1422
  return {
1655
- obj,
1423
+ obj: input,
1656
1424
  [STORE_SYMBOL]: true
1657
1425
  };
1658
1426
  }
@@ -1685,7 +1453,7 @@ var ClientBuilder = class {
1685
1453
  }
1686
1454
  #getFetcher() {
1687
1455
  if (this.#fetcherConfig?.type === "function") return this.#fetcherConfig.fetcher;
1688
- return fetch;
1456
+ return globalThis.fetch.bind(globalThis);
1689
1457
  }
1690
1458
  #getFetcherOptions() {
1691
1459
  if (this.#fetcherConfig?.type === "options") return this.#fetcherConfig.options;
@@ -1859,7 +1627,7 @@ var ClientBuilder = class {
1859
1627
  const mutatorStore = this.#createMutatorStore(async ({ data }) => {
1860
1628
  if (typeof window === "undefined") {}
1861
1629
  const { body, path, query } = data;
1862
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required.");
1630
+ await assertBodyProvided(body, route.inputSchema, "Body is required.");
1863
1631
  const response = await executeMutateQuery({
1864
1632
  body,
1865
1633
  path,
@@ -1898,7 +1666,7 @@ var ClientBuilder = class {
1898
1666
  } });
1899
1667
  const mutateQuery = async (data) => {
1900
1668
  const { body, path, query } = data;
1901
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required for mutateQuery");
1669
+ await assertBodyProvided(body, route.inputSchema, "Body is required for mutateQuery");
1902
1670
  const response = await executeMutateQuery({
1903
1671
  body,
1904
1672
  path,
@@ -1960,6 +1728,62 @@ function createClientBuilder(definition, publicConfig, routesOrFactories, author
1960
1728
  //#region src/pi/definition.ts
1961
1729
  const piFragmentDefinition = defineFragment("pi-fragment").extend((x) => x).usesService("workflows").build();
1962
1730
 
1731
+ //#endregion
1732
+ //#region src/debug-log.ts
1733
+ const LOG_LEVEL_PRIORITY = {
1734
+ off: 0,
1735
+ error: 1,
1736
+ warn: 2,
1737
+ info: 3,
1738
+ debug: 4
1739
+ };
1740
+ const DEFAULT_CONFIG = {
1741
+ enabled: false,
1742
+ level: "off"
1743
+ };
1744
+ var PiLogger = class PiLogger {
1745
+ static #enabled = DEFAULT_CONFIG.enabled;
1746
+ static #level = DEFAULT_CONFIG.level;
1747
+ static reset() {
1748
+ PiLogger.#enabled = DEFAULT_CONFIG.enabled;
1749
+ PiLogger.#level = DEFAULT_CONFIG.level;
1750
+ }
1751
+ static configure(config) {
1752
+ if (!config) return;
1753
+ if (config.enabled !== void 0) PiLogger.#enabled = config.enabled;
1754
+ if (config.level !== void 0) PiLogger.#level = config.level;
1755
+ }
1756
+ static enable() {
1757
+ PiLogger.#enabled = true;
1758
+ }
1759
+ static disable() {
1760
+ PiLogger.#enabled = false;
1761
+ }
1762
+ static setLogLevel(level) {
1763
+ PiLogger.#level = level;
1764
+ }
1765
+ static debug(message, fields) {
1766
+ PiLogger.#log("debug", message, fields);
1767
+ }
1768
+ static info(message, fields) {
1769
+ PiLogger.#log("info", message, fields);
1770
+ }
1771
+ static warn(message, fields) {
1772
+ PiLogger.#log("warn", message, fields);
1773
+ }
1774
+ static error(message, fields) {
1775
+ PiLogger.#log("error", message, fields);
1776
+ }
1777
+ static #log(level, message, fields) {
1778
+ if (!PiLogger.#enabled) return;
1779
+ if (LOG_LEVEL_PRIORITY[level] > LOG_LEVEL_PRIORITY[PiLogger.#level]) return;
1780
+ console[level](`[pi-fragment] ${message}`, {
1781
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1782
+ ...fields
1783
+ });
1784
+ }
1785
+ };
1786
+
1963
1787
  //#endregion
1964
1788
  //#region src/pi/constants.ts
1965
1789
  const SESSION_STATUSES = [
@@ -1982,12 +1806,16 @@ const THINKING_LEVELS = [
1982
1806
 
1983
1807
  //#endregion
1984
1808
  //#region src/pi/route-schemas.ts
1809
+ const AGENT_LOOP_PHASES = [
1810
+ "waiting-for-user",
1811
+ "running-agent",
1812
+ "complete"
1813
+ ];
1985
1814
  const sessionBaseSchema = z.object({
1986
1815
  id: z.string(),
1987
1816
  name: z.string().nullable(),
1988
1817
  status: z.enum(SESSION_STATUSES),
1989
1818
  agent: z.string(),
1990
- workflowInstanceId: z.string().nullable(),
1991
1819
  steeringMode: z.enum(STEERING_MODES),
1992
1820
  metadata: z.any().nullable(),
1993
1821
  tags: z.array(z.string()),
@@ -2016,12 +1844,13 @@ const ToolCallSchema = z.object({
2016
1844
  arguments: z.record(z.string(), z.unknown()),
2017
1845
  thoughtSignature: z.string().optional()
2018
1846
  });
2019
- const ContentSchema = z.union([
1847
+ const UserContentSchema = z.union([TextContentSchema, ImageContentSchema]);
1848
+ const AssistantContentSchema = z.union([
2020
1849
  TextContentSchema,
2021
1850
  ThinkingContentSchema,
2022
- ImageContentSchema,
2023
1851
  ToolCallSchema
2024
1852
  ]);
1853
+ const ToolResultContentSchema = z.union([TextContentSchema, ImageContentSchema]);
2025
1854
  const UsageSchema = z.object({
2026
1855
  input: z.number(),
2027
1856
  output: z.number(),
@@ -2039,17 +1868,23 @@ const UsageSchema = z.object({
2039
1868
  const MessageSchema = z.union([
2040
1869
  z.object({
2041
1870
  role: z.literal("user"),
2042
- content: z.union([z.string(), z.array(ContentSchema)]),
1871
+ content: z.union([z.string(), z.array(UserContentSchema)]),
2043
1872
  timestamp: z.number()
2044
1873
  }),
2045
1874
  z.object({
2046
1875
  role: z.literal("assistant"),
2047
- content: z.array(ContentSchema),
1876
+ content: z.array(AssistantContentSchema),
2048
1877
  api: z.string(),
2049
1878
  provider: z.string(),
2050
1879
  model: z.string(),
2051
1880
  usage: UsageSchema,
2052
- stopReason: z.string(),
1881
+ stopReason: z.enum([
1882
+ "stop",
1883
+ "length",
1884
+ "toolUse",
1885
+ "error",
1886
+ "aborted"
1887
+ ]),
2053
1888
  errorMessage: z.string().optional(),
2054
1889
  timestamp: z.number()
2055
1890
  }),
@@ -2057,16 +1892,32 @@ const MessageSchema = z.union([
2057
1892
  role: z.literal("toolResult"),
2058
1893
  toolCallId: z.string(),
2059
1894
  toolName: z.string(),
2060
- content: z.array(ContentSchema),
1895
+ content: z.array(ToolResultContentSchema),
2061
1896
  details: z.unknown().optional(),
2062
1897
  isError: z.boolean(),
2063
1898
  timestamp: z.number()
2064
1899
  })
2065
1900
  ]);
2066
- const TraceSchema = z.object({
1901
+ const TraceSchema = z.custom();
1902
+ const EventSchema = z.object({
1903
+ id: z.string(),
1904
+ runNumber: z.number().nullable().optional(),
2067
1905
  type: z.string(),
2068
- timestamp: z.number().optional()
1906
+ payload: z.unknown().nullable(),
1907
+ createdAt: z.date(),
1908
+ deliveredAt: z.date().nullable(),
1909
+ consumedByStepKey: z.string().nullable()
2069
1910
  });
1911
+ const WaitingForSchema = z.union([z.object({
1912
+ type: z.literal("user_message"),
1913
+ turn: z.number(),
1914
+ stepKey: z.string(),
1915
+ timeoutMs: z.number().nullable()
1916
+ }), z.object({
1917
+ type: z.literal("assistant"),
1918
+ turn: z.number(),
1919
+ stepKey: z.string()
1920
+ })]).nullable();
2070
1921
  const workflowStatusSchema = z.object({
2071
1922
  status: z.enum(SESSION_STATUSES),
2072
1923
  error: z.object({
@@ -2078,7 +1929,11 @@ const workflowStatusSchema = z.object({
2078
1929
  const sessionDetailSchema = sessionBaseSchema.extend({
2079
1930
  workflow: workflowStatusSchema,
2080
1931
  messages: z.array(MessageSchema),
1932
+ events: z.array(EventSchema),
2081
1933
  trace: z.array(TraceSchema),
1934
+ turn: z.number(),
1935
+ phase: z.enum(AGENT_LOOP_PHASES),
1936
+ waitingFor: WaitingForSchema,
2082
1937
  summaries: z.array(z.object({
2083
1938
  turn: z.number(),
2084
1939
  assistant: z.any().nullable(),
@@ -2086,10 +1941,13 @@ const sessionDetailSchema = sessionBaseSchema.extend({
2086
1941
  }))
2087
1942
  });
2088
1943
  const messageAckSchema = z.object({ status: z.enum(SESSION_STATUSES) });
1944
+ const activeSessionStreamItemSchema = z.custom();
2089
1945
 
2090
1946
  //#endregion
2091
1947
  //#region src/routes.ts
2092
1948
  const piRoutesFactory = defineRoutes(piFragmentDefinition).create(({ config, defineRoute, serviceDeps }) => {
1949
+ PiLogger.reset();
1950
+ if (config.logging) PiLogger.configure(config.logging);
2093
1951
  return [
2094
1952
  defineRoute({
2095
1953
  method: "POST",
@@ -2097,16 +1955,13 @@ const piRoutesFactory = defineRoutes(piFragmentDefinition).create(({ config, def
2097
1955
  inputSchema: z.object({
2098
1956
  agent: z.string(),
2099
1957
  name: z.string().optional(),
1958
+ systemMessage: z.string().optional(),
2100
1959
  metadata: z.any().optional(),
2101
1960
  tags: z.array(z.string()).optional(),
2102
1961
  steeringMode: z.enum(["all", "one-at-a-time"]).optional()
2103
1962
  }),
2104
1963
  outputSchema: sessionBaseSchema,
2105
- errorCodes: [
2106
- "AGENT_NOT_FOUND",
2107
- "WORKFLOWS_REQUIRED",
2108
- "WORKFLOW_CREATE_FAILED"
2109
- ],
1964
+ errorCodes: ["AGENT_NOT_FOUND", "WORKFLOW_CREATE_FAILED"],
2110
1965
  handler: () => {}
2111
1966
  }),
2112
1967
  defineRoute({
@@ -2119,12 +1974,20 @@ const piRoutesFactory = defineRoutes(piFragmentDefinition).create(({ config, def
2119
1974
  defineRoute({
2120
1975
  method: "GET",
2121
1976
  path: "/sessions/:sessionId",
2122
- outputSchema: sessionDetailSchema,
2123
- errorCodes: [
2124
- "SESSION_NOT_FOUND",
2125
- "WORKFLOWS_REQUIRED",
2126
- "WORKFLOW_INSTANCE_MISSING"
1977
+ queryParameters: [
1978
+ "events",
1979
+ "trace",
1980
+ "summaries"
2127
1981
  ],
1982
+ outputSchema: sessionDetailSchema,
1983
+ errorCodes: ["SESSION_NOT_FOUND", "WORKFLOW_INSTANCE_MISSING"],
1984
+ handler: () => {}
1985
+ }),
1986
+ defineRoute({
1987
+ method: "GET",
1988
+ path: "/sessions/:sessionId/active",
1989
+ outputSchema: z.array(activeSessionStreamItemSchema),
1990
+ errorCodes: ["SESSION_NOT_FOUND", "WORKFLOW_INSTANCE_MISSING"],
2128
1991
  handler: () => {}
2129
1992
  }),
2130
1993
  defineRoute({
@@ -2138,7 +2001,7 @@ const piRoutesFactory = defineRoutes(piFragmentDefinition).create(({ config, def
2138
2001
  outputSchema: messageAckSchema,
2139
2002
  errorCodes: [
2140
2003
  "SESSION_NOT_FOUND",
2141
- "WORKFLOWS_REQUIRED",
2004
+ "SESSION_NOT_READY",
2142
2005
  "WORKFLOW_INSTANCE_MISSING"
2143
2006
  ],
2144
2007
  handler: () => {}
@@ -2147,53 +2010,24 @@ const piRoutesFactory = defineRoutes(piFragmentDefinition).create(({ config, def
2147
2010
  });
2148
2011
 
2149
2012
  //#endregion
2150
- //#region src/pi/clients.ts
2151
- function createPiFragmentClients(fragnoConfig) {
2152
- const b = createClientBuilder(piFragmentDefinition, fragnoConfig, [piRoutesFactory]);
2153
- return {
2154
- useSessions: b.createHook("/sessions"),
2155
- useSession: b.createHook("/sessions/:sessionId"),
2156
- useCreateSession: b.createMutator("POST", "/sessions"),
2157
- useSendMessage: b.createMutator("POST", "/sessions/:sessionId/messages")
2158
- };
2159
- }
2160
-
2161
- //#endregion
2162
- //#region src/schema.ts
2163
- const piSchema = schema("pi-fragment", (s) => {
2164
- return s.addTable("session", (t) => {
2165
- return t.addColumn("id", idColumn()).addColumn("name", column("string").nullable()).addColumn("agent", column("string")).addColumn("status", column("string")).addColumn("workflowInstanceId", column("string").nullable()).addColumn("steeringMode", column("string")).addColumn("metadata", column("json").nullable()).addColumn("tags", column("json").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_session_status", ["status"]).createIndex("idx_session_created", ["createdAt"]);
2166
- });
2167
- });
2168
-
2169
- //#endregion
2170
- //#region src/pi/mappers.ts
2171
- const normalizeSteeringMode = (value) => {
2172
- return STEERING_MODES.includes(value) ? value : "one-at-a-time";
2173
- };
2174
-
2175
- //#endregion
2176
- //#region src/pi/workflow.ts
2177
- const PI_WORKFLOW_NAME = "agent-loop-workflow";
2178
- const agentLoopParamsSchema = z.object({
2179
- sessionId: z.string(),
2180
- agentName: z.string(),
2181
- systemPrompt: z.string().optional(),
2182
- initialMessages: z.array(z.custom()).optional()
2013
+ //#region src/client/session-store.ts
2014
+ const createOverlayState = (sessionId) => ({
2015
+ sessionId,
2016
+ connection: "idle",
2017
+ error: null,
2018
+ sendError: null,
2019
+ sending: false,
2020
+ activity: { kind: "none" },
2021
+ pendingTurn: false,
2022
+ pendingSnapshotReconcile: false,
2023
+ readyForInputOverride: null,
2024
+ optimisticMessages: [],
2025
+ streamedMessages: [],
2026
+ draftAssistant: null,
2027
+ runningTools: [],
2028
+ trace: []
2183
2029
  });
2184
- const userMessageSchema = z.object({
2185
- text: z.string().optional(),
2186
- done: z.boolean().optional(),
2187
- steeringMode: z.enum(["all", "one-at-a-time"]).optional()
2188
- });
2189
- const findLastAssistantMessage = (messages) => {
2190
- for (let i = messages.length - 1; i >= 0; i -= 1) {
2191
- const message = messages[i];
2192
- if (message && typeof message === "object" && "role" in message && message.role === "assistant") return message;
2193
- }
2194
- return null;
2195
- };
2196
- const buildUserMessage = (text) => ({
2030
+ const buildOptimisticUserMessage = (text) => ({
2197
2031
  role: "user",
2198
2032
  content: [{
2199
2033
  type: "text",
@@ -2201,270 +2035,688 @@ const buildUserMessage = (text) => ({
2201
2035
  }],
2202
2036
  timestamp: Date.now()
2203
2037
  });
2204
- const buildErrorMessage = (model, error) => ({
2205
- role: "assistant",
2206
- content: [{
2207
- type: "text",
2208
- text: ""
2209
- }],
2210
- api: model.api,
2211
- provider: model.provider,
2212
- model: model.id,
2213
- usage: {
2214
- input: 0,
2215
- output: 0,
2216
- cacheRead: 0,
2217
- cacheWrite: 0,
2218
- totalTokens: 0,
2219
- cost: {
2220
- input: 0,
2221
- output: 0,
2222
- cacheRead: 0,
2223
- cacheWrite: 0,
2224
- total: 0
2225
- }
2226
- },
2227
- stopReason: "error",
2228
- errorMessage: error instanceof Error ? error.message : String(error),
2229
- timestamp: Date.now()
2230
- });
2231
- const wrapStreamFn = (streamFn) => streamFn ? async (...args) => {
2038
+ const formatJson = (value) => {
2232
2039
  try {
2233
- return await streamFn(...args);
2234
- } catch (error) {
2235
- const stream = createAssistantMessageEventStream();
2236
- const [model] = args;
2237
- stream.push({
2238
- type: "error",
2239
- reason: "error",
2240
- error: buildErrorMessage(model, error)
2241
- });
2242
- return stream;
2040
+ return JSON.stringify(value, null, 2);
2041
+ } catch {
2042
+ return String(value);
2243
2043
  }
2244
- } : void 0;
2245
- const getAssistantErrorMessage = (assistant) => {
2246
- if (!assistant || typeof assistant !== "object") return;
2247
- if ("errorMessage" in assistant && typeof assistant.errorMessage === "string") return assistant.errorMessage;
2248
2044
  };
2249
- const buildSessionContext = (options) => {
2250
- const now = /* @__PURE__ */ new Date();
2045
+ const buildLiveToolResultMessage = (message) => {
2046
+ const result = message.result;
2047
+ const content = result && Array.isArray(result.content) ? result.content : [{
2048
+ type: "text",
2049
+ text: formatJson(message.result)
2050
+ }];
2251
2051
  return {
2252
- id: options.params.sessionId,
2253
- name: null,
2254
- status: "active",
2255
- agent: options.params.agentName,
2256
- workflowInstanceId: options.instanceId,
2257
- steeringMode: options.steeringMode,
2258
- metadata: null,
2259
- tags: [],
2260
- createdAt: now,
2261
- updatedAt: now
2052
+ role: "toolResult",
2053
+ toolCallId: message.toolCallId,
2054
+ toolName: message.toolName,
2055
+ content,
2056
+ details: result?.details,
2057
+ isError: message.isError,
2058
+ timestamp: Date.now()
2262
2059
  };
2263
2060
  };
2264
- const resolveTool = async (name, factory, context) => {
2265
- if (!factory) throw new NonRetryableError(`Tool ${name} not found.`);
2266
- if (typeof factory === "function") {
2267
- const tool = await factory(context);
2268
- if (!tool) throw new NonRetryableError(`Tool ${name} returned no definition.`);
2269
- return tool;
2270
- }
2271
- return factory;
2061
+ const appendOrReplaceLiveMessage = (messages, nextMessage) => {
2062
+ if (nextMessage.role === "assistant") {
2063
+ const existingIndex = messages.findIndex((message) => message.role === "assistant" && message.timestamp === nextMessage.timestamp);
2064
+ if (existingIndex >= 0) return [
2065
+ ...messages.slice(0, existingIndex),
2066
+ nextMessage,
2067
+ ...messages.slice(existingIndex + 1)
2068
+ ];
2069
+ }
2070
+ if (nextMessage.role === "toolResult") {
2071
+ const existingIndex = messages.findIndex((message) => message.role === "toolResult" && message.toolCallId === nextMessage.toolCallId);
2072
+ if (existingIndex >= 0) return [
2073
+ ...messages.slice(0, existingIndex),
2074
+ nextMessage,
2075
+ ...messages.slice(existingIndex + 1)
2076
+ ];
2077
+ }
2078
+ return [...messages, nextMessage];
2079
+ };
2080
+ const mergeMessages = (snapshotMessages, optimisticMessages, streamedMessages, draftAssistant) => {
2081
+ let messages = [...snapshotMessages];
2082
+ for (const optimisticMessage of optimisticMessages) messages = appendOrReplaceLiveMessage(messages, optimisticMessage);
2083
+ for (const streamedMessage of streamedMessages) messages = appendOrReplaceLiveMessage(messages, streamedMessage);
2084
+ if (draftAssistant) messages = appendOrReplaceLiveMessage(messages, draftAssistant);
2085
+ return messages;
2272
2086
  };
2273
- const resolveTools = async (options) => {
2274
- const toolNames = options.agent.tools ?? [];
2275
- if (toolNames.length === 0) return [];
2276
- const toolConfig = options.agent.toolConfig ?? null;
2277
- const context = {
2278
- session: options.session,
2279
- turnId: options.turnId,
2280
- toolConfig,
2281
- messages: options.messages
2282
- };
2283
- const resolved = [];
2284
- for (const name of toolNames) {
2285
- const tool = await resolveTool(name, options.tools[name], context);
2286
- resolved.push(tool);
2087
+ const hasActiveOverlay = (state) => state.draftAssistant !== null || state.runningTools.length > 0 || state.streamedMessages.length > 0;
2088
+ const activityLabel = (activity) => {
2089
+ switch (activity.kind) {
2090
+ case "none": return null;
2091
+ case "sending-message": return "Sending message";
2092
+ case "assistant-responding": return "Assistant responding";
2093
+ case "assistant-ready": return "Assistant response ready";
2094
+ case "tool-running": return `Running ${activity.toolName}`;
2095
+ case "tool-updating": return `Updating ${activity.toolName}`;
2096
+ case "tool-finished": return `${activity.toolName} finished`;
2097
+ case "agent-started": return "Agent started";
2098
+ case "agent-finished": return "Agent finished";
2099
+ case "turn-started": return "Turn started";
2100
+ case "turn-finished": return "Turn finished";
2101
+ default: return null;
2287
2102
  }
2288
- return resolved;
2289
2103
  };
2290
- const createAgent = async (options) => {
2291
- const session = buildSessionContext({
2292
- params: options.params,
2293
- instanceId: options.instanceId,
2294
- steeringMode: options.steeringMode
2295
- });
2296
- const agentTools = await resolveTools({
2297
- agent: options.agent,
2298
- tools: options.tools,
2299
- session,
2300
- turnId: options.turnId,
2301
- messages: options.messages
2302
- });
2303
- const initialState = {
2304
- systemPrompt: options.params.systemPrompt ?? options.agent.systemPrompt,
2305
- model: options.agent.model,
2306
- tools: agentTools,
2307
- messages: options.messages
2104
+ const connectionStatusText = (connection) => {
2105
+ switch (connection) {
2106
+ case "connecting": return "Connecting to live updates";
2107
+ case "reconnecting": return "Reconnecting to live updates";
2108
+ default: return null;
2109
+ }
2110
+ };
2111
+ const reduceProtocolMessage = (state, message) => {
2112
+ if (message.layer === "system") switch (message.type) {
2113
+ case "snapshot": {
2114
+ const readyForInput = isSessionReadyForInput(message.phase, message.waitingFor);
2115
+ return {
2116
+ ...state,
2117
+ error: null,
2118
+ activity: readyForInput ? { kind: "none" } : state.activity,
2119
+ pendingTurn: !readyForInput,
2120
+ readyForInputOverride: readyForInput
2121
+ };
2122
+ }
2123
+ case "inactive": return {
2124
+ ...state,
2125
+ connection: "idle",
2126
+ error: null,
2127
+ readyForInputOverride: false
2128
+ };
2129
+ case "settled": return {
2130
+ ...state,
2131
+ connection: "reconnecting",
2132
+ activity: { kind: "turn-finished" },
2133
+ pendingTurn: true,
2134
+ pendingSnapshotReconcile: true,
2135
+ readyForInputOverride: false,
2136
+ error: null
2137
+ };
2138
+ default: return state;
2139
+ }
2140
+ const event = message.event;
2141
+ const trace = [...state.trace, event];
2142
+ switch (event.type) {
2143
+ case "message_start":
2144
+ case "message_update": return {
2145
+ ...state,
2146
+ trace,
2147
+ draftAssistant: event.message,
2148
+ activity: { kind: "assistant-responding" },
2149
+ pendingTurn: true,
2150
+ error: null
2151
+ };
2152
+ case "message_end": return {
2153
+ ...state,
2154
+ trace,
2155
+ streamedMessages: appendOrReplaceLiveMessage(state.streamedMessages, event.message),
2156
+ draftAssistant: null,
2157
+ activity: { kind: "assistant-ready" },
2158
+ pendingTurn: true,
2159
+ error: null
2160
+ };
2161
+ case "tool_execution_start": return {
2162
+ ...state,
2163
+ trace,
2164
+ runningTools: [...state.runningTools.filter((tool) => tool.toolCallId !== event.toolCallId), {
2165
+ toolCallId: event.toolCallId,
2166
+ toolName: event.toolName,
2167
+ args: event.args,
2168
+ partialResult: null
2169
+ }],
2170
+ activity: {
2171
+ kind: "tool-running",
2172
+ toolName: event.toolName
2173
+ },
2174
+ pendingTurn: true,
2175
+ error: null
2176
+ };
2177
+ case "tool_execution_update": return {
2178
+ ...state,
2179
+ trace,
2180
+ runningTools: state.runningTools.map((tool) => tool.toolCallId === event.toolCallId ? {
2181
+ ...tool,
2182
+ partialResult: event.partialResult
2183
+ } : tool),
2184
+ activity: {
2185
+ kind: "tool-updating",
2186
+ toolName: event.toolName
2187
+ },
2188
+ pendingTurn: true,
2189
+ error: null
2190
+ };
2191
+ case "tool_execution_end": return {
2192
+ ...state,
2193
+ trace,
2194
+ streamedMessages: appendOrReplaceLiveMessage(state.streamedMessages, buildLiveToolResultMessage(event)),
2195
+ runningTools: state.runningTools.filter((tool) => tool.toolCallId !== event.toolCallId),
2196
+ activity: {
2197
+ kind: "tool-finished",
2198
+ toolName: event.toolName
2199
+ },
2200
+ pendingTurn: true,
2201
+ error: null
2202
+ };
2203
+ case "agent_start": return {
2204
+ ...state,
2205
+ trace,
2206
+ activity: { kind: "agent-started" },
2207
+ pendingTurn: true,
2208
+ error: null
2209
+ };
2210
+ case "agent_end": return {
2211
+ ...state,
2212
+ trace,
2213
+ activity: { kind: "agent-finished" },
2214
+ pendingTurn: true,
2215
+ error: null
2216
+ };
2217
+ case "turn_start": return {
2218
+ ...state,
2219
+ trace,
2220
+ activity: { kind: "turn-started" },
2221
+ pendingTurn: true,
2222
+ error: null
2223
+ };
2224
+ case "turn_end": return {
2225
+ ...state,
2226
+ trace,
2227
+ activity: { kind: "turn-finished" },
2228
+ pendingTurn: true,
2229
+ error: null
2230
+ };
2231
+ default: return {
2232
+ ...state,
2233
+ trace,
2234
+ pendingTurn: true,
2235
+ error: null
2236
+ };
2237
+ }
2238
+ };
2239
+ const reduceOverlayState = (state, action) => {
2240
+ switch (action.type) {
2241
+ case "snapshot-updated":
2242
+ if (action.sessionId !== state.sessionId) return createOverlayState(action.sessionId);
2243
+ if (!state.pendingSnapshotReconcile) return {
2244
+ ...state,
2245
+ optimisticMessages: [],
2246
+ sendError: null,
2247
+ error: null,
2248
+ readyForInputOverride: null
2249
+ };
2250
+ return {
2251
+ ...state,
2252
+ optimisticMessages: [],
2253
+ streamedMessages: [],
2254
+ draftAssistant: null,
2255
+ runningTools: [],
2256
+ trace: [],
2257
+ error: null,
2258
+ sendError: null,
2259
+ activity: { kind: "none" },
2260
+ pendingTurn: false,
2261
+ pendingSnapshotReconcile: false,
2262
+ readyForInputOverride: null
2263
+ };
2264
+ case "send-started": return {
2265
+ ...state,
2266
+ sending: true,
2267
+ sendError: null,
2268
+ optimisticMessages: [...state.optimisticMessages, {
2269
+ clientId: action.clientId,
2270
+ message: buildOptimisticUserMessage(action.text)
2271
+ }],
2272
+ activity: { kind: "sending-message" },
2273
+ pendingTurn: true,
2274
+ readyForInputOverride: false,
2275
+ error: null
2276
+ };
2277
+ case "send-acknowledged": return {
2278
+ ...state,
2279
+ sending: false,
2280
+ sendError: null
2281
+ };
2282
+ case "send-failed": {
2283
+ const optimisticMessages = state.optimisticMessages.filter((message) => message.clientId !== action.clientId);
2284
+ const keepPending = state.pendingSnapshotReconcile || hasActiveOverlay(state);
2285
+ return {
2286
+ ...state,
2287
+ sending: false,
2288
+ sendError: action.message,
2289
+ optimisticMessages,
2290
+ activity: keepPending ? state.activity : { kind: "none" },
2291
+ pendingTurn: keepPending,
2292
+ readyForInputOverride: keepPending ? state.readyForInputOverride : null
2293
+ };
2294
+ }
2295
+ case "stream-connecting": {
2296
+ const connection = action.reconnecting ? "reconnecting" : "connecting";
2297
+ return {
2298
+ ...state,
2299
+ connection,
2300
+ error: null
2301
+ };
2302
+ }
2303
+ case "stream-open": return {
2304
+ ...state,
2305
+ connection: "listening",
2306
+ error: null
2307
+ };
2308
+ case "stream-message": return reduceProtocolMessage(state, action.message);
2309
+ case "stream-settled": return {
2310
+ ...state,
2311
+ connection: "reconnecting"
2312
+ };
2313
+ case "stream-inactive": return {
2314
+ ...state,
2315
+ connection: "idle",
2316
+ error: null
2317
+ };
2318
+ case "stream-error": return {
2319
+ ...state,
2320
+ connection: "error",
2321
+ error: action.message
2322
+ };
2323
+ case "stream-idle": return {
2324
+ ...state,
2325
+ connection: "idle",
2326
+ error: null,
2327
+ activity: state.pendingTurn ? state.activity : { kind: "none" }
2328
+ };
2329
+ default: return state;
2330
+ }
2331
+ };
2332
+ const readPiRouteError = async (response) => {
2333
+ const text = await response.text();
2334
+ if (!text) return `Request failed (${response.status}).`;
2335
+ try {
2336
+ const parsed = JSON.parse(text);
2337
+ return parsed.message ?? parsed.error ?? text;
2338
+ } catch {
2339
+ return text;
2340
+ }
2341
+ };
2342
+ const consumeProtocolStream = async (body, onMessage, signal) => {
2343
+ const reader = body.getReader();
2344
+ const decoder = new TextDecoder();
2345
+ let buffer = "";
2346
+ try {
2347
+ while (true) {
2348
+ if (signal.aborted) {
2349
+ await reader.cancel();
2350
+ return;
2351
+ }
2352
+ const { done, value } = await reader.read();
2353
+ if (done) break;
2354
+ buffer += decoder.decode(value, { stream: true });
2355
+ const lines = buffer.split("\n");
2356
+ buffer = lines.pop() ?? "";
2357
+ for (const line of lines) {
2358
+ const trimmed = line.trim();
2359
+ if (!trimmed) continue;
2360
+ onMessage(JSON.parse(trimmed));
2361
+ }
2362
+ }
2363
+ const trailing = `${buffer}${decoder.decode()}`.trim();
2364
+ if (trailing) onMessage(JSON.parse(trailing));
2365
+ } finally {
2366
+ reader.releaseLock();
2367
+ }
2368
+ };
2369
+ const waitWithAbort = (ms, signal) => new Promise((resolve, reject) => {
2370
+ const timeoutId = setTimeout(() => {
2371
+ signal.removeEventListener("abort", handleAbort);
2372
+ resolve();
2373
+ }, ms);
2374
+ function handleAbort() {
2375
+ clearTimeout(timeoutId);
2376
+ signal.removeEventListener("abort", handleAbort);
2377
+ reject(new DOMException("Aborted", "AbortError"));
2378
+ }
2379
+ if (signal.aborted) {
2380
+ handleAbort();
2381
+ return;
2382
+ }
2383
+ signal.addEventListener("abort", handleAbort, { once: true });
2384
+ });
2385
+ const isSessionReadyForInput = (phase, waitingFor) => phase === "waiting-for-user" && waitingFor?.type === "user_message";
2386
+ const shouldKeepLiveConnection = (session) => session?.phase !== "complete";
2387
+ const withAcceptHeader = (defaultOptions) => {
2388
+ const headers = new Headers(defaultOptions?.headers);
2389
+ headers.set("accept", "application/x-ndjson");
2390
+ return {
2391
+ ...defaultOptions,
2392
+ headers
2308
2393
  };
2309
- if (options.agent.thinkingLevel) initialState.thinkingLevel = options.agent.thinkingLevel;
2310
- const agent = new Agent({
2311
- initialState,
2312
- streamFn: wrapStreamFn(options.agent.streamFn),
2313
- convertToLlm: options.agent.convertToLlm,
2314
- transformContext: options.agent.transformContext,
2315
- getApiKey: options.agent.getApiKey,
2316
- thinkingBudgets: options.agent.thinkingBudgets,
2317
- maxRetryDelayMs: options.agent.maxRetryDelayMs,
2318
- sessionId: options.params.sessionId
2319
- });
2320
- agent.setSteeringMode(options.steeringMode);
2321
- const trace = [];
2322
- const unsubscribe = agent.subscribe((event) => {
2323
- trace.push(event);
2324
- if (!options.agent.onEvent) return;
2325
- try {
2326
- options.agent.onEvent(event, {
2327
- sessionId: options.params.sessionId,
2328
- turnId: options.turnId
2394
+ };
2395
+ function createPiSessionStore(deps, args) {
2396
+ const detailStore = deps.createDetailStore(args.sessionId);
2397
+ const snapshotStore = atom(args.initialData ?? null);
2398
+ const overlayStore = atom(createOverlayState(args.sessionId));
2399
+ const lastSnapshotVersion = { current: args.initialData ? `${args.initialData.id}:${String(args.initialData.updatedAt)}` : null };
2400
+ let destroyed = false;
2401
+ let mounted = false;
2402
+ let activeAbortController = null;
2403
+ let activeLoopPromise = null;
2404
+ let detailUnsubscribe = null;
2405
+ const logActive = (event, details) => {
2406
+ deps.activeLogger?.(event, {
2407
+ sessionId: args.sessionId,
2408
+ ...details
2409
+ });
2410
+ };
2411
+ const updateSnapshot = (session) => {
2412
+ if (!session) return;
2413
+ snapshotStore.set(session);
2414
+ const nextVersion = `${session.id}:${String(session.updatedAt)}`;
2415
+ if (nextVersion !== lastSnapshotVersion.current) {
2416
+ logActive("snapshot:updated", {
2417
+ version: nextVersion,
2418
+ phase: session.phase,
2419
+ status: session.status,
2420
+ turn: session.turn
2329
2421
  });
2330
- } catch (error) {
2331
- console.warn("Agent onEvent hook failed.", {
2332
- error,
2333
- sessionId: options.params.sessionId,
2334
- turnId: options.turnId,
2335
- agent: options.agent.name
2422
+ lastSnapshotVersion.current = nextVersion;
2423
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
2424
+ type: "snapshot-updated",
2425
+ sessionId: session.id
2426
+ }));
2427
+ }
2428
+ };
2429
+ const refetch = () => {
2430
+ logActive("detail:refetch");
2431
+ detailStore.revalidate();
2432
+ };
2433
+ const stopActiveLoop = () => {
2434
+ if (activeAbortController || activeLoopPromise) logActive("stream:stop");
2435
+ activeAbortController?.abort();
2436
+ activeAbortController = null;
2437
+ activeLoopPromise = null;
2438
+ };
2439
+ const ensureActiveLoop = () => {
2440
+ if (destroyed || !mounted || activeLoopPromise || deps.enableActiveStream === false) return;
2441
+ const currentSession = snapshotStore.get();
2442
+ if (!shouldKeepLiveConnection(currentSession)) {
2443
+ logActive("stream:idle", {
2444
+ reason: "session-complete",
2445
+ phase: currentSession?.phase ?? null,
2446
+ status: currentSession?.status ?? null
2336
2447
  });
2448
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-idle" }));
2449
+ return;
2337
2450
  }
2338
- });
2339
- await agent.continue();
2340
- unsubscribe();
2341
- const assistant = findLastAssistantMessage(agent.state.messages);
2342
- const assistantError = getAssistantErrorMessage(assistant);
2343
- if (agent.state.error || assistantError) throw new Error(assistantError || agent.state.error || "Agent error");
2344
- return {
2345
- agent,
2346
- trace,
2347
- assistant
2451
+ logActive("stream:start", {
2452
+ phase: currentSession?.phase ?? null,
2453
+ status: currentSession?.status ?? null,
2454
+ turn: currentSession?.turn ?? null
2455
+ });
2456
+ const abortController = new AbortController();
2457
+ activeAbortController = abortController;
2458
+ activeLoopPromise = (async () => {
2459
+ let reconnecting = false;
2460
+ while (!abortController.signal.aborted && !destroyed) {
2461
+ if (!shouldKeepLiveConnection(snapshotStore.get())) {
2462
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-idle" }));
2463
+ return;
2464
+ }
2465
+ const activeUrl = deps.buildActiveUrl(args.sessionId);
2466
+ logActive("stream:connecting", {
2467
+ reconnecting,
2468
+ url: activeUrl
2469
+ });
2470
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
2471
+ type: "stream-connecting",
2472
+ reconnecting
2473
+ }));
2474
+ try {
2475
+ const response = await deps.fetcher(activeUrl, {
2476
+ ...withAcceptHeader(deps.defaultOptions),
2477
+ method: "GET",
2478
+ cache: "no-store",
2479
+ signal: abortController.signal
2480
+ });
2481
+ logActive("stream:response", {
2482
+ ok: response.ok,
2483
+ status: response.status,
2484
+ statusText: response.statusText,
2485
+ url: activeUrl
2486
+ });
2487
+ if (!response.ok) throw new Error(await readPiRouteError(response));
2488
+ if (!response.body) throw new Error("The active session stream did not return a response body.");
2489
+ logActive("stream:open", { url: activeUrl });
2490
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-open" }));
2491
+ let sawInactive = false;
2492
+ let sawSettled = false;
2493
+ await consumeProtocolStream(response.body, (message) => {
2494
+ logActive("stream:message", { message });
2495
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
2496
+ type: "stream-message",
2497
+ message
2498
+ }));
2499
+ if (message.layer === "system" && message.type === "inactive") sawInactive = true;
2500
+ if (message.layer === "system" && message.type === "settled") sawSettled = true;
2501
+ }, abortController.signal);
2502
+ if (abortController.signal.aborted || destroyed) return;
2503
+ if (sawSettled || sawInactive) {
2504
+ logActive("stream:settled", {
2505
+ sawSettled,
2506
+ sawInactive
2507
+ });
2508
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-settled" }));
2509
+ detailStore.revalidate();
2510
+ }
2511
+ if (sawInactive) {
2512
+ logActive("stream:inactive");
2513
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-inactive" }));
2514
+ return;
2515
+ }
2516
+ reconnecting = true;
2517
+ logActive("stream:retry", { delayMs: 250 });
2518
+ await waitWithAbort(250, abortController.signal);
2519
+ } catch (error) {
2520
+ if (abortController.signal.aborted || destroyed) return;
2521
+ const message = error instanceof Error ? error.message : "Failed to stream the active session.";
2522
+ logActive("stream:error", { message });
2523
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
2524
+ type: "stream-error",
2525
+ message
2526
+ }));
2527
+ reconnecting = true;
2528
+ logActive("stream:retry", { delayMs: 1e3 });
2529
+ await waitWithAbort(1e3, abortController.signal).catch(() => void 0);
2530
+ }
2531
+ }
2532
+ })().finally(() => {
2533
+ logActive("stream:end");
2534
+ if (activeAbortController === abortController) activeAbortController = null;
2535
+ if (activeLoopPromise) activeLoopPromise = null;
2536
+ });
2348
2537
  };
2349
- };
2350
- const runAgentTurn = async (options) => {
2351
- const result = await createAgent({
2352
- agent: options.agent,
2353
- tools: options.tools,
2354
- params: options.params,
2355
- messages: options.messages,
2356
- steeringMode: options.steeringMode,
2357
- turnId: options.turnId,
2358
- instanceId: options.instanceId
2538
+ const store = computed([
2539
+ detailStore,
2540
+ snapshotStore,
2541
+ overlayStore
2542
+ ], (detailValue, session, overlay) => {
2543
+ const optimisticMessages = overlay.optimisticMessages.map((entry) => entry.message);
2544
+ const messages = mergeMessages(session?.messages ?? [], optimisticMessages, overlay.streamedMessages, overlay.draftAssistant);
2545
+ const statusText = activityLabel(overlay.activity) ?? connectionStatusText(overlay.connection);
2546
+ const readyForInput = session !== null && (overlay.readyForInputOverride ?? (isSessionReadyForInput(session.phase, session.waitingFor) && !overlay.pendingTurn));
2547
+ return {
2548
+ loading: detailValue.loading,
2549
+ session,
2550
+ messages,
2551
+ traceEvents: [...session?.trace ?? [], ...overlay.trace],
2552
+ runningTools: overlay.runningTools,
2553
+ connection: overlay.connection,
2554
+ statusText,
2555
+ readyForInput,
2556
+ sending: overlay.sending,
2557
+ error: detailValue.error?.message ?? overlay.error,
2558
+ sendError: overlay.sendError
2559
+ };
2560
+ });
2561
+ onMount(store, () => {
2562
+ mounted = true;
2563
+ logActive("store:mount");
2564
+ updateSnapshot(detailStore.get().data);
2565
+ detailUnsubscribe = detailStore.listen((value) => {
2566
+ updateSnapshot(value.data);
2567
+ if (destroyed) return;
2568
+ if (!shouldKeepLiveConnection(snapshotStore.get())) {
2569
+ logActive("stream:idle", { reason: "detail-store-update" });
2570
+ stopActiveLoop();
2571
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-idle" }));
2572
+ return;
2573
+ }
2574
+ ensureActiveLoop();
2575
+ });
2576
+ ensureActiveLoop();
2577
+ return () => {
2578
+ logActive("store:unmount");
2579
+ mounted = false;
2580
+ stopActiveLoop();
2581
+ detailUnsubscribe?.();
2582
+ detailUnsubscribe = null;
2583
+ };
2359
2584
  });
2585
+ const sendMessage = (input) => {
2586
+ const text = input.text.trim();
2587
+ if (!text) return false;
2588
+ const current = store.get();
2589
+ if (current.sending || !current.readyForInput) return false;
2590
+ const clientId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
2591
+ logActive("send:start", {
2592
+ clientId,
2593
+ text
2594
+ });
2595
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
2596
+ type: "send-started",
2597
+ clientId,
2598
+ text
2599
+ }));
2600
+ ensureActiveLoop();
2601
+ deps.sendMessage({
2602
+ sessionId: args.sessionId,
2603
+ text,
2604
+ done: input.done,
2605
+ steeringMode: input.steeringMode
2606
+ }).then(() => {
2607
+ logActive("send:acknowledged", { clientId });
2608
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "send-acknowledged" }));
2609
+ }).catch((error) => {
2610
+ const message = error instanceof Error ? error.message : "Failed to send message.";
2611
+ logActive("send:failed", {
2612
+ clientId,
2613
+ message
2614
+ });
2615
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
2616
+ type: "send-failed",
2617
+ clientId,
2618
+ message
2619
+ }));
2620
+ });
2621
+ return true;
2622
+ };
2623
+ const deactivate = () => {
2624
+ logActive("store:dispose");
2625
+ mounted = false;
2626
+ stopActiveLoop();
2627
+ detailUnsubscribe?.();
2628
+ detailUnsubscribe = null;
2629
+ };
2630
+ const destroy = () => {
2631
+ if (destroyed) return;
2632
+ logActive("store:destroy");
2633
+ destroyed = true;
2634
+ deactivate();
2635
+ };
2360
2636
  return {
2361
- messages: result.agent.state.messages,
2362
- trace: result.trace,
2363
- assistant: result.assistant
2637
+ store,
2638
+ sendMessage,
2639
+ refetch,
2640
+ deactivate,
2641
+ destroy
2364
2642
  };
2365
- };
2366
- const agentLoopWorkflow = (options) => defineWorkflow({
2367
- name: PI_WORKFLOW_NAME,
2368
- schema: agentLoopParamsSchema
2369
- }, async (event, step) => {
2370
- const params = agentLoopParamsSchema.parse(event.payload ?? {});
2371
- const agentDefinition = options.agents[params.agentName];
2372
- if (!agentDefinition) throw new NonRetryableError(`Agent ${params.agentName} not found.`);
2373
- let messages = Array.isArray(params.initialMessages) ? params.initialMessages : [];
2374
- let turn = 0;
2375
- while (true) {
2376
- const userEvent = await step.waitForEvent(`wait-user-${turn}`, {
2377
- type: "user_message",
2378
- timeout: "1 hour"
2643
+ }
2644
+
2645
+ //#endregion
2646
+ //#region src/client/session-controller.ts
2647
+ const select = (store, selector) => computed(store, selector);
2648
+ function createPiSessionControllerStore(input) {
2649
+ return ({ path, initialData }) => {
2650
+ const controller = createPiSessionStore(input, {
2651
+ sessionId: path.sessionId,
2652
+ initialData
2379
2653
  });
2380
- const payload = userMessageSchema.parse(userEvent.payload ?? {});
2381
- const steeringMode = normalizeSteeringMode(payload.steeringMode);
2382
- const turnId = `${event.instanceId}:${turn}`;
2383
- messages = (await step.do(`user-${turn}`, async () => {
2384
- const userMessage = buildUserMessage(payload.text ?? "");
2385
- return {
2386
- messages: [...messages, userMessage],
2387
- user: userMessage
2388
- };
2389
- })).messages;
2390
- messages = (await step.do(`assistant-${turn}`, { retries: {
2391
- limit: 1,
2392
- delay: "10 ms",
2393
- backoff: "constant"
2394
- } }, async () => await runAgentTurn({
2395
- params,
2396
- agent: agentDefinition,
2397
- tools: options.tools,
2398
- messages,
2399
- steeringMode,
2400
- turnId,
2401
- instanceId: event.instanceId
2402
- }))).messages;
2403
- if (payload.done) return { messages };
2404
- turn += 1;
2405
- }
2406
- });
2407
- const createPiWorkflows = (options) => ({ agentLoop: agentLoopWorkflow(options) });
2654
+ return {
2655
+ loading: select(controller.store, (state) => state.loading),
2656
+ session: select(controller.store, (state) => state.session),
2657
+ messages: select(controller.store, (state) => state.messages),
2658
+ traceEvents: select(controller.store, (state) => state.traceEvents),
2659
+ runningTools: select(controller.store, (state) => state.runningTools),
2660
+ connection: select(controller.store, (state) => state.connection),
2661
+ statusText: select(controller.store, (state) => state.statusText),
2662
+ readyForInput: select(controller.store, (state) => state.readyForInput),
2663
+ sending: select(controller.store, (state) => state.sending),
2664
+ error: select(controller.store, (state) => state.error),
2665
+ sendError: select(controller.store, (state) => state.sendError),
2666
+ sendMessage: controller.sendMessage,
2667
+ refetch: controller.refetch,
2668
+ [Symbol.dispose]: controller.deactivate
2669
+ };
2670
+ };
2671
+ }
2408
2672
 
2409
2673
  //#endregion
2410
- //#region src/pi/dsl.ts
2411
- const defineAgent = (name, definition) => {
2412
- if (definition.name && definition.name !== name) throw new Error(`defineAgent name mismatch: expected ${name}, got ${definition.name}`);
2413
- return {
2414
- ...definition,
2415
- name
2674
+ //#region src/client/clients.ts
2675
+ const createActiveSessionLogger = (enabled) => {
2676
+ if (!enabled) return;
2677
+ return (event, details) => {
2678
+ console.log(`[pi-active] ${event}`, details ?? {});
2416
2679
  };
2417
2680
  };
2418
- const createPi = () => {
2419
- const agents = {};
2420
- const tools = {};
2421
- let defaultSteeringMode;
2422
- const builder = {
2423
- agent(definition) {
2424
- agents[definition.name] = definition;
2425
- return builder;
2426
- },
2427
- agents(registry) {
2428
- Object.assign(agents, registry);
2429
- return builder;
2430
- },
2431
- tool(name, tool) {
2432
- tools[name] = tool;
2433
- return builder;
2434
- },
2435
- tools(registry) {
2436
- Object.assign(tools, registry);
2437
- return builder;
2438
- },
2439
- defaultSteeringMode(mode) {
2440
- defaultSteeringMode = mode;
2441
- return builder;
2442
- },
2443
- build() {
2444
- const agentsSnapshot = { ...agents };
2445
- const toolsSnapshot = { ...tools };
2446
- return {
2447
- config: {
2448
- agents: agentsSnapshot,
2449
- tools: toolsSnapshot,
2450
- defaultSteeringMode
2451
- },
2452
- workflows: createPiWorkflows({
2453
- agents: agentsSnapshot,
2454
- tools: toolsSnapshot
2455
- })
2456
- };
2457
- }
2681
+ function createPiFragmentClients(fragnoConfig) {
2682
+ const builder = createClientBuilder(piFragmentDefinition, fragnoConfig, [piRoutesFactory]);
2683
+ const useSessionDetail = builder.createHook("/sessions/:sessionId");
2684
+ const useSendMessage = builder.createMutator("POST", "/sessions/:sessionId/messages", (invalidate, params) => {
2685
+ const sessionId = params.pathParams.sessionId;
2686
+ if (!sessionId) return;
2687
+ invalidate("GET", "/sessions/:sessionId", { pathParams: { sessionId } });
2688
+ invalidate("GET", "/sessions", {});
2689
+ });
2690
+ const { fetcher, defaultOptions } = builder.getFetcher();
2691
+ const sessionStoreDependencies = {
2692
+ createDetailStore: (sessionId) => useSessionDetail.store({ path: { sessionId } }),
2693
+ sendMessage: ({ sessionId, text, done, steeringMode }) => useSendMessage.mutateQuery({
2694
+ path: { sessionId },
2695
+ body: {
2696
+ text,
2697
+ done,
2698
+ steeringMode
2699
+ }
2700
+ }).then((result) => {
2701
+ if (!result) throw new Error("The message mutation did not return a status response.");
2702
+ return result;
2703
+ }),
2704
+ buildActiveUrl: (sessionId) => builder.buildUrl("/sessions/:sessionId/active", { path: { sessionId } }),
2705
+ fetcher,
2706
+ defaultOptions,
2707
+ enableActiveStream: typeof window === "undefined" ? false : void 0,
2708
+ activeLogger: createActiveSessionLogger(fragnoConfig.debugActiveSession)
2709
+ };
2710
+ return {
2711
+ useSessions: builder.createHook("/sessions"),
2712
+ useSessionDetail,
2713
+ useSession: builder.createStore(createPiSessionControllerStore(sessionStoreDependencies)),
2714
+ useCreateSession: builder.createMutator("POST", "/sessions"),
2715
+ useActiveSession: builder.createHook("/sessions/:sessionId/active"),
2716
+ useSendMessage
2458
2717
  };
2459
- return builder;
2460
- };
2461
-
2462
- //#endregion
2463
- //#region src/pi/factory.ts
2464
- function createPiFragment(config, options, services) {
2465
- return {};
2466
2718
  }
2467
2719
 
2468
2720
  //#endregion
2469
- export { createPiWorkflows as a, SESSION_STATUSES as c, piFragmentDefinition as d, isGetHook as f, atom as g, isReadableAtom as h, PI_WORKFLOW_NAME as i, STEERING_MODES as l, isStore as m, createPi as n, createPiFragmentClients as o, isMutatorHook as p, defineAgent as r, piRoutesFactory as s, createPiFragment as t, THINKING_LEVELS as u };
2470
- //# sourceMappingURL=factory-DKoO_lRA.js.map
2721
+ export { STEERING_MODES as a, piFragmentDefinition as c, isStore as d, isReadableAtom as f, SESSION_STATUSES as i, isGetHook as l, createPiSessionStore as n, THINKING_LEVELS as o, piRoutesFactory as r, PiLogger as s, createPiFragmentClients as t, isMutatorHook as u };
2722
+ //# sourceMappingURL=clients-BscY_HVe.js.map