@chelone/core 0.6.0 → 0.7.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 (242) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +21 -17
  10. package/dist/auth/auth-options.js +80 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +60 -0
  15. package/dist/block-editor/block-document.js +143 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.js +8 -11
  21. package/dist/command-menu/registry.d.ts +9 -5
  22. package/dist/command-menu/registry.js +36 -46
  23. package/dist/controller/calendar-dates.js +82 -113
  24. package/dist/controller/calendar-events.js +93 -88
  25. package/dist/controller/client-list-controller.d.ts +2 -2
  26. package/dist/controller/client-list-controller.js +251 -254
  27. package/dist/controller/data-table-columns.d.ts +2 -0
  28. package/dist/controller/data-table-columns.js +50 -56
  29. package/dist/controller/deep-equal.d.ts +1 -0
  30. package/dist/controller/deep-equal.js +14 -0
  31. package/dist/controller/filter-toggle.js +36 -44
  32. package/dist/controller/in-place-editor.d.ts +42 -0
  33. package/dist/controller/in-place-editor.js +42 -0
  34. package/dist/controller/infinite-list-controller.js +219 -189
  35. package/dist/controller/inspector.d.ts +49 -0
  36. package/dist/controller/inspector.js +64 -0
  37. package/dist/controller/job-monitor.d.ts +3 -4
  38. package/dist/controller/job-monitor.js +37 -47
  39. package/dist/controller/kanban-board.d.ts +2 -3
  40. package/dist/controller/kanban-board.js +43 -24
  41. package/dist/controller/list-controller.d.ts +5 -5
  42. package/dist/controller/list-controller.js +444 -400
  43. package/dist/controller/list-params.js +96 -87
  44. package/dist/controller/pivot-grid.js +89 -95
  45. package/dist/controller/prev-next.d.ts +2 -2
  46. package/dist/controller/prev-next.js +74 -54
  47. package/dist/controller/record-controllers.d.ts +14 -6
  48. package/dist/controller/record-controllers.js +288 -257
  49. package/dist/controller/record-diff.d.ts +21 -0
  50. package/dist/controller/record-diff.js +50 -0
  51. package/dist/controller/record-timeline.js +42 -30
  52. package/dist/controller/record-tree.js +57 -47
  53. package/dist/controller/record-workflow.d.ts +38 -16
  54. package/dist/controller/record-workflow.js +85 -64
  55. package/dist/controller/reference-many-controller.js +198 -192
  56. package/dist/controller/reference-one-controller.d.ts +1 -1
  57. package/dist/controller/reference-one-controller.js +63 -56
  58. package/dist/controller/saved-queries.js +104 -91
  59. package/dist/controller/translate-fallback.js +10 -6
  60. package/dist/csv/export.d.ts +2 -1
  61. package/dist/csv/export.js +43 -40
  62. package/dist/csv/import-controller.js +185 -169
  63. package/dist/csv/match-headers.js +29 -22
  64. package/dist/csv/parse-with.js +60 -54
  65. package/dist/csv/run-import.js +69 -72
  66. package/dist/csv/validate-rows.d.ts +1 -1
  67. package/dist/csv/validate-rows.js +65 -52
  68. package/dist/data/cache-ops.d.ts +5 -4
  69. package/dist/data/cache-ops.js +208 -188
  70. package/dist/data/combine-data-providers.js +25 -23
  71. package/dist/data/data-provider-log.d.ts +45 -0
  72. package/dist/data/data-provider-log.js +93 -0
  73. package/dist/data/errors.js +14 -10
  74. package/dist/data/fake-provider.d.ts +6 -3
  75. package/dist/data/fake-provider.js +129 -114
  76. package/dist/data/fetch-utils.js +61 -44
  77. package/dist/data/filter-branch.d.ts +5 -0
  78. package/dist/data/filter-branch.js +10 -0
  79. package/dist/data/get-many-aggregate.js +84 -76
  80. package/dist/data/http-error.js +17 -12
  81. package/dist/data/keys.js +50 -36
  82. package/dist/data/mutation-options.d.ts +14 -9
  83. package/dist/data/mutation-options.js +93 -109
  84. package/dist/data/query-options.js +73 -68
  85. package/dist/data/remove-empty.d.ts +3 -0
  86. package/dist/data/remove-empty.js +11 -0
  87. package/dist/data/undoable.d.ts +2 -2
  88. package/dist/data/undoable.js +190 -174
  89. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  90. package/dist/data/with-lifecycle-callbacks.js +66 -52
  91. package/dist/environment/application-update-store.js +58 -55
  92. package/dist/environment/offline-store.js +21 -21
  93. package/dist/form/array-binding.d.ts +30 -11
  94. package/dist/form/array-binding.js +184 -166
  95. package/dist/form/choice-ids.d.ts +7 -0
  96. package/dist/form/choice-ids.js +11 -0
  97. package/dist/form/choices-controller.d.ts +3 -2
  98. package/dist/form/choices-controller.js +302 -275
  99. package/dist/form/date-format.d.ts +38 -0
  100. package/dist/form/date-format.js +89 -0
  101. package/dist/form/dirty-values.d.ts +2 -2
  102. package/dist/form/dirty-values.js +22 -11
  103. package/dist/form/duration.d.ts +22 -0
  104. package/dist/form/duration.js +49 -0
  105. package/dist/form/edit-create-form.d.ts +12 -5
  106. package/dist/form/edit-create-form.js +243 -188
  107. package/dist/form/event-value.d.ts +6 -0
  108. package/dist/form/event-value.js +17 -0
  109. package/dist/form/field-error.d.ts +14 -0
  110. package/dist/form/field-error.js +28 -0
  111. package/dist/form/field-name.d.ts +20 -0
  112. package/dist/form/field-name.js +6 -0
  113. package/dist/form/filter-form-values.js +63 -49
  114. package/dist/form/form-controller.d.ts +9 -1
  115. package/dist/form/form-controller.js +165 -160
  116. package/dist/form/form-groups.d.ts +4 -3
  117. package/dist/form/form-groups.js +79 -79
  118. package/dist/form/form-handle.d.ts +175 -0
  119. package/dist/form/form-handle.js +68 -0
  120. package/dist/form/input-binding.d.ts +56 -31
  121. package/dist/form/input-binding.js +169 -118
  122. package/dist/form/input-values.d.ts +130 -0
  123. package/dist/form/input-values.js +177 -0
  124. package/dist/form/json-text.js +51 -37
  125. package/dist/form/save-pipeline.d.ts +6 -8
  126. package/dist/form/save-pipeline.js +91 -82
  127. package/dist/form/set-form-values.d.ts +6 -0
  128. package/dist/form/set-form-values.js +10 -0
  129. package/dist/form/source.js +97 -65
  130. package/dist/form/text-of.d.ts +2 -0
  131. package/dist/form/text-of.js +7 -0
  132. package/dist/form/validators.d.ts +58 -19
  133. package/dist/form/validators.js +97 -71
  134. package/dist/geo/bbox.d.ts +5 -4
  135. package/dist/geo/bbox.js +69 -48
  136. package/dist/geo/bounds.d.ts +14 -3
  137. package/dist/geo/bounds.js +98 -68
  138. package/dist/geo/constraints.js +88 -93
  139. package/dist/geo/coordinates.js +31 -23
  140. package/dist/geo/editor.d.ts +13 -13
  141. package/dist/geo/editor.js +176 -153
  142. package/dist/geo/geocoding.d.ts +4 -4
  143. package/dist/geo/geocoding.js +38 -41
  144. package/dist/geo/geojson.d.ts +12 -74
  145. package/dist/geo/geojson.js +52 -40
  146. package/dist/geo/geometry-math.d.ts +4 -4
  147. package/dist/geo/geometry-math.js +100 -93
  148. package/dist/geo/index.d.ts +7 -6
  149. package/dist/geo/map-layers.d.ts +42 -7
  150. package/dist/geo/map-layers.js +88 -62
  151. package/dist/geo/map-style.js +31 -32
  152. package/dist/geo/messages.js +48 -45
  153. package/dist/geo/nominatim.js +88 -80
  154. package/dist/geo/osm-presets.d.ts +4 -3
  155. package/dist/geo/osm-presets.js +108 -86
  156. package/dist/geo/osm-tags.js +14 -12
  157. package/dist/geo/overpass.d.ts +14 -2
  158. package/dist/geo/overpass.js +83 -90
  159. package/dist/geo/shape-value.d.ts +9 -5
  160. package/dist/geo/shape-value.js +169 -146
  161. package/dist/geo/snap-to-roads.d.ts +3 -3
  162. package/dist/geo/snap-to-roads.js +55 -50
  163. package/dist/geo/throttle.js +36 -39
  164. package/dist/guards.d.ts +37 -0
  165. package/dist/guards.js +62 -0
  166. package/dist/i18n/default-messages.js +34 -29
  167. package/dist/i18n/english-messages.js +187 -189
  168. package/dist/i18n/i18n.d.ts +2 -2
  169. package/dist/i18n/i18n.js +47 -60
  170. package/dist/i18n/inflection.d.ts +34 -0
  171. package/dist/i18n/inflection.js +82 -0
  172. package/dist/i18n/messages.d.ts +10 -2
  173. package/dist/i18n/messages.js +17 -4
  174. package/dist/i18n/missing-keys.js +66 -62
  175. package/dist/i18n/translatable.d.ts +16 -1
  176. package/dist/i18n/translatable.js +40 -47
  177. package/dist/index.d.ts +71 -24
  178. package/dist/index.js +40 -288
  179. package/dist/json/deep-equal.d.ts +8 -0
  180. package/dist/json/deep-equal.js +24 -0
  181. package/dist/json/strip-json-comments.js +38 -42
  182. package/dist/noop.d.ts +2 -0
  183. package/dist/noop.js +4 -0
  184. package/dist/notification/error-message.d.ts +2 -0
  185. package/dist/notification/error-message.js +10 -0
  186. package/dist/notification/notification-store.d.ts +3 -2
  187. package/dist/notification/notification-store.js +41 -45
  188. package/dist/onboarding-tour/placement.js +63 -78
  189. package/dist/onboarding-tour/tour-controller.js +46 -54
  190. package/dist/realtime/add-events-for-mutations.js +75 -81
  191. package/dist/realtime/announce-lock.js +16 -13
  192. package/dist/realtime/broadcast-channel-transport.js +65 -59
  193. package/dist/realtime/copy-members.js +22 -14
  194. package/dist/realtime/in-memory-lock-provider.js +40 -48
  195. package/dist/realtime/live.js +95 -101
  196. package/dist/realtime/lock-options.js +16 -16
  197. package/dist/realtime/lock-session.js +91 -78
  198. package/dist/realtime/memory-transport.js +25 -25
  199. package/dist/realtime/presence.js +120 -122
  200. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  201. package/dist/realtime/realtime-data-provider.js +45 -42
  202. package/dist/realtime/reconnect-backoff.js +17 -0
  203. package/dist/realtime/sse-transport.d.ts +31 -0
  204. package/dist/realtime/sse-transport.js +235 -0
  205. package/dist/realtime/status-store.js +23 -28
  206. package/dist/realtime/topic-hub.js +62 -71
  207. package/dist/realtime/topics.js +10 -12
  208. package/dist/realtime/types.js +10 -12
  209. package/dist/realtime/unread-count.js +21 -21
  210. package/dist/realtime/websocket-transport.d.ts +32 -0
  211. package/dist/realtime/websocket-transport.js +306 -0
  212. package/dist/resource/describe-resource.d.ts +2 -0
  213. package/dist/resource/describe-resource.js +232 -219
  214. package/dist/resource/get-at-path.js +29 -14
  215. package/dist/resource/identifier.d.ts +18 -0
  216. package/dist/resource/identifier.js +27 -0
  217. package/dist/resource/record.d.ts +4 -0
  218. package/dist/resource/record.js +10 -0
  219. package/dist/resource/register.d.ts +7 -0
  220. package/dist/resource/registry-store.d.ts +45 -15
  221. package/dist/resource/registry-store.js +67 -60
  222. package/dist/resource/registry.d.ts +10 -0
  223. package/dist/resource/registry.js +8 -4
  224. package/dist/resource/source-path.d.ts +16 -1
  225. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  226. package/dist/rich-text/rich-text-extras.js +179 -0
  227. package/dist/rich-text/rich-text-image.d.ts +124 -0
  228. package/dist/rich-text/rich-text-image.js +163 -0
  229. package/dist/rich-text/rich-text.d.ts +6 -0
  230. package/dist/rich-text/rich-text.js +125 -88
  231. package/dist/router/location.d.ts +22 -4
  232. package/dist/router/location.js +41 -18
  233. package/dist/router/match.js +47 -39
  234. package/dist/router/paths.js +40 -34
  235. package/dist/router/record-from-location.d.ts +1 -1
  236. package/dist/router/record-from-location.js +25 -17
  237. package/dist/store/adapters.js +51 -55
  238. package/dist/store/micro-store.js +57 -59
  239. package/dist/store/shallow-equal.d.ts +12 -0
  240. package/dist/store/shallow-equal.js +20 -0
  241. package/dist/store/store.js +54 -57
  242. package/package.json +13 -11
@@ -16,6 +16,12 @@ export interface CreateAssistantSessionOptions {
16
16
  export interface AssistantSession extends MicroStore<AssistantSessionState> {
17
17
  /** Resolves when the turn, with its tool rounds, ends. */
18
18
  send(text: string): Promise<void>;
19
+ /**
20
+ * After an error, runs the last user message again: whatever the failed
21
+ * turn added after it is dropped and no second user message is added.
22
+ * Does nothing unless the status is `error`.
23
+ */
24
+ retry(): Promise<void>;
19
25
  stop(): void;
20
26
  reset(): void;
21
27
  }
@@ -1,185 +1,208 @@
1
1
  import { createMicroStore } from "../store/micro-store.js";
2
- const toError = (error) => error instanceof Error ? error : new Error(String(error));
3
- const issuesText = (issues) => issues.map((issue) => issue.message).join("; ");
2
+ // src/assistant/assistant-session.ts
3
+ var toError = (error) => error instanceof Error ? error : new Error(String(error));
4
+ var issuesText = (issues) => issues.map((issue) => issue.message).join("; ");
5
+ /** Resolves with `undefined` when the signal aborts before the promise settles. */
4
6
  function raceAbort(promise, signal) {
5
- return new Promise((resolve, reject) => {
6
- const onAbort = () => resolve(void 0);
7
- if (signal.aborted) return onAbort();
8
- signal.addEventListener("abort", onAbort, { once: true });
9
- promise.then(resolve, reject).finally(() => {
10
- signal.removeEventListener("abort", onAbort);
11
- });
12
- });
7
+ return new Promise((resolve, reject) => {
8
+ const onAbort = () => resolve(void 0);
9
+ if (signal.aborted) return onAbort();
10
+ signal.addEventListener("abort", onAbort, { once: true });
11
+ promise.then(resolve, reject).finally(() => {
12
+ signal.removeEventListener("abort", onAbort);
13
+ });
14
+ });
13
15
  }
14
- function createAssistantSession({
15
- transport,
16
- tools,
17
- maxToolRounds = 5,
18
- confirmTool
19
- }) {
20
- const store = createMicroStore({
21
- messages: [],
22
- status: "idle",
23
- error: null
24
- });
25
- const declarations = Object.fromEntries(
26
- Object.entries(tools).map(([name, { description, parameters }]) => [
27
- name,
28
- { description, parameters }
29
- ])
30
- );
31
- let active = null;
32
- let destroyed = false;
33
- store.onDestroy(() => {
34
- destroyed = true;
35
- active?.abort();
36
- active = null;
37
- });
38
- async function runTool(call, signal) {
39
- const base = {
40
- role: "tool",
41
- id: call.id,
42
- toolName: call.toolName
43
- };
44
- const definition = Object.hasOwn(tools, call.toolName) ? tools[call.toolName] : void 0;
45
- if (!definition) {
46
- return { ...base, error: `Unknown tool: ${call.toolName}` };
47
- }
48
- const stopped = { ...base, error: "Stopped" };
49
- try {
50
- let args = call.args;
51
- if (definition.schema) {
52
- const outcome = await definition.schema["~standard"].validate(args);
53
- if (signal.aborted) return stopped;
54
- if (outcome.issues) {
55
- return {
56
- ...base,
57
- error: `Invalid arguments: ${issuesText(outcome.issues)}`
58
- };
59
- }
60
- args = outcome.value;
61
- }
62
- if (definition.confirm) {
63
- const answer = await confirmTool?.(call);
64
- if (signal.aborted) return stopped;
65
- if (!answer) {
66
- return { ...base, error: "The user declined to run this tool" };
67
- }
68
- }
69
- return { ...base, result: await definition.handler(args) };
70
- } catch (error) {
71
- return { ...base, error: toError(error).message };
72
- }
73
- }
74
- async function run(controller) {
75
- const { signal } = controller;
76
- const update = (patch) => {
77
- if (!signal.aborted) store.setState(patch);
78
- };
79
- for (let round = 0; ; round++) {
80
- let content = "";
81
- let index = -1;
82
- const calls = [];
83
- let iterator;
84
- try {
85
- iterator = transport.send(store.getSnapshot().messages, declarations, { signal })[Symbol.asyncIterator]();
86
- for (; ; ) {
87
- const step = await raceAbort(iterator.next(), signal);
88
- if (!step || step.done || signal.aborted) break;
89
- const chunk = step.value;
90
- if (chunk.type === "done") break;
91
- if (chunk.type === "tool-call") {
92
- const { id, toolName, args } = chunk;
93
- calls.push({ id, toolName, args });
94
- continue;
95
- }
96
- content += chunk.delta;
97
- const text = content;
98
- if (index < 0) {
99
- index = store.getSnapshot().messages.length;
100
- update((s) => ({
101
- messages: [...s.messages, { role: "assistant", content: text }]
102
- }));
103
- } else {
104
- update((s) => ({
105
- messages: s.messages.map(
106
- (m, i) => i === index ? { role: "assistant", content: text } : m
107
- )
108
- }));
109
- }
110
- }
111
- } catch (error) {
112
- if (!signal.aborted) {
113
- update(() => ({ status: "error", error: toError(error) }));
114
- }
115
- return;
116
- } finally {
117
- iterator?.return?.().catch(() => {
118
- });
119
- }
120
- if (signal.aborted) return;
121
- if (calls.length === 0) break;
122
- if (round >= maxToolRounds) {
123
- update(() => ({
124
- status: "error",
125
- error: new Error(
126
- `The assistant kept calling tools after ${maxToolRounds} rounds`
127
- )
128
- }));
129
- return;
130
- }
131
- const results = [];
132
- for (const call of calls) {
133
- const result = await raceAbort(runTool(call, signal), signal);
134
- if (!result) return;
135
- results.push(result);
136
- if (signal.aborted) return;
137
- }
138
- const assistant = {
139
- role: "assistant",
140
- content,
141
- toolCalls: calls
142
- };
143
- update((s) => ({
144
- messages: index < 0 ? [...s.messages, assistant, ...results] : [
145
- ...s.messages.map((m, i) => i === index ? assistant : m),
146
- ...results
147
- ]
148
- }));
149
- }
150
- update(() => ({ status: "idle" }));
151
- }
152
- return {
153
- ...store,
154
- async send(text) {
155
- if (destroyed) return;
156
- if (!text.trim() || store.getSnapshot().status === "streaming") return;
157
- const controller = new AbortController();
158
- active = controller;
159
- store.setState((s) => ({
160
- messages: [...s.messages, { role: "user", content: text }],
161
- status: "streaming",
162
- error: null
163
- }));
164
- try {
165
- await run(controller);
166
- } finally {
167
- if (active === controller) active = null;
168
- }
169
- },
170
- stop() {
171
- if (!active) return;
172
- active.abort();
173
- active = null;
174
- store.setState({ status: "idle" });
175
- },
176
- reset() {
177
- active?.abort();
178
- active = null;
179
- store.setState({ messages: [], status: "idle", error: null });
180
- }
181
- };
16
+ function createAssistantSession({ transport, tools, maxToolRounds = 5, confirmTool }) {
17
+ const store = createMicroStore({
18
+ messages: [],
19
+ status: "idle",
20
+ error: null
21
+ });
22
+ const declarations = Object.fromEntries(Object.entries(tools).map(([name, { description, parameters }]) => [name, {
23
+ description,
24
+ parameters
25
+ }]));
26
+ let active = null;
27
+ let destroyed = false;
28
+ store.onDestroy(() => {
29
+ destroyed = true;
30
+ active?.abort();
31
+ active = null;
32
+ });
33
+ async function runTool(call, signal) {
34
+ const base = {
35
+ role: "tool",
36
+ id: call.id,
37
+ toolName: call.toolName
38
+ };
39
+ const definition = Object.hasOwn(tools, call.toolName) ? tools[call.toolName] : void 0;
40
+ if (!definition) return {
41
+ ...base,
42
+ error: `Unknown tool: ${call.toolName}`
43
+ };
44
+ const stopped = {
45
+ ...base,
46
+ error: "Stopped"
47
+ };
48
+ try {
49
+ let args = call.args;
50
+ if (definition.schema) {
51
+ const outcome = await definition.schema["~standard"].validate(args);
52
+ if (signal.aborted) return stopped;
53
+ if (outcome.issues) return {
54
+ ...base,
55
+ error: `Invalid arguments: ${issuesText(outcome.issues)}`
56
+ };
57
+ args = outcome.value;
58
+ }
59
+ if (definition.confirm) {
60
+ const answer = await confirmTool?.(call);
61
+ if (signal.aborted) return stopped;
62
+ if (!answer) return {
63
+ ...base,
64
+ error: "The user declined to run this tool"
65
+ };
66
+ }
67
+ return {
68
+ ...base,
69
+ result: await definition.handler(args)
70
+ };
71
+ } catch (error) {
72
+ return {
73
+ ...base,
74
+ error: toError(error).message
75
+ };
76
+ }
77
+ }
78
+ async function run(controller) {
79
+ const { signal } = controller;
80
+ const update = (patch) => {
81
+ if (!signal.aborted) store.setState(patch);
82
+ };
83
+ for (let round = 0;; round++) {
84
+ let content = "";
85
+ let index = -1;
86
+ const calls = [];
87
+ let iterator;
88
+ try {
89
+ iterator = transport.send(store.getSnapshot().messages, declarations, { signal })[Symbol.asyncIterator]();
90
+ for (;;) {
91
+ const step = await raceAbort(iterator.next(), signal);
92
+ if (!step || step.done || signal.aborted) break;
93
+ const chunk = step.value;
94
+ if (chunk.type === "done") break;
95
+ if (chunk.type === "tool-call") {
96
+ const { id, toolName, args } = chunk;
97
+ calls.push({
98
+ id,
99
+ toolName,
100
+ args
101
+ });
102
+ continue;
103
+ }
104
+ content += chunk.delta;
105
+ const text = content;
106
+ if (index < 0) {
107
+ index = store.getSnapshot().messages.length;
108
+ update((s) => ({ messages: [...s.messages, {
109
+ role: "assistant",
110
+ content: text
111
+ }] }));
112
+ } else update((s) => ({ messages: s.messages.map((m, i) => i === index ? {
113
+ role: "assistant",
114
+ content: text
115
+ } : m) }));
116
+ }
117
+ } catch (error) {
118
+ if (!signal.aborted) update(() => ({
119
+ status: "error",
120
+ error: toError(error)
121
+ }));
122
+ return;
123
+ } finally {
124
+ iterator?.return?.().catch(() => {});
125
+ }
126
+ if (signal.aborted) return;
127
+ if (calls.length === 0) break;
128
+ if (round >= maxToolRounds) {
129
+ update(() => ({
130
+ status: "error",
131
+ error: /* @__PURE__ */ new Error(`The assistant kept calling tools after ${maxToolRounds} rounds`)
132
+ }));
133
+ return;
134
+ }
135
+ const results = [];
136
+ for (const call of calls) {
137
+ const result = await raceAbort(runTool(call, signal), signal);
138
+ if (!result) return;
139
+ results.push(result);
140
+ if (signal.aborted) return;
141
+ }
142
+ const assistant = {
143
+ role: "assistant",
144
+ content,
145
+ toolCalls: calls
146
+ };
147
+ update((s) => ({ messages: index < 0 ? [
148
+ ...s.messages,
149
+ assistant,
150
+ ...results
151
+ ] : [...s.messages.map((m, i) => i === index ? assistant : m), ...results] }));
152
+ }
153
+ update(() => ({ status: "idle" }));
154
+ }
155
+ async function start(controller) {
156
+ active = controller;
157
+ try {
158
+ await run(controller);
159
+ } finally {
160
+ if (active === controller) active = null;
161
+ }
162
+ }
163
+ return {
164
+ ...store,
165
+ async send(text) {
166
+ if (destroyed) return;
167
+ if (!text.trim() || store.getSnapshot().status === "streaming") return;
168
+ store.setState((s) => ({
169
+ messages: [...s.messages, {
170
+ role: "user",
171
+ content: text
172
+ }],
173
+ status: "streaming",
174
+ error: null
175
+ }));
176
+ await start(new AbortController());
177
+ },
178
+ async retry() {
179
+ if (destroyed || store.getSnapshot().status !== "error") return;
180
+ const { messages } = store.getSnapshot();
181
+ let last = messages.length - 1;
182
+ while (last >= 0 && messages[last]?.role !== "user") last--;
183
+ if (last < 0) return;
184
+ store.setState({
185
+ messages: messages.slice(0, last + 1),
186
+ status: "streaming",
187
+ error: null
188
+ });
189
+ await start(new AbortController());
190
+ },
191
+ stop() {
192
+ if (!active) return;
193
+ active.abort();
194
+ active = null;
195
+ store.setState({ status: "idle" });
196
+ },
197
+ reset() {
198
+ active?.abort();
199
+ active = null;
200
+ store.setState({
201
+ messages: [],
202
+ status: "idle",
203
+ error: null
204
+ });
205
+ }
206
+ };
182
207
  }
183
- export {
184
- createAssistantSession
185
- };
208
+ export { createAssistantSession };
@@ -1,31 +1,28 @@
1
- const wait = (ms, signal) => new Promise((resolve) => {
2
- const timer = setTimeout(done, ms);
3
- function done() {
4
- clearTimeout(timer);
5
- signal.removeEventListener("abort", done);
6
- resolve();
7
- }
8
- signal.addEventListener("abort", done, { once: true });
1
+ // src/assistant/echo-transport.ts
2
+ var wait = (ms, signal) => new Promise((resolve) => {
3
+ const timer = setTimeout(done, ms);
4
+ function done() {
5
+ clearTimeout(timer);
6
+ signal.removeEventListener("abort", done);
7
+ resolve();
8
+ }
9
+ signal.addEventListener("abort", done, { once: true });
9
10
  });
10
- function echoAssistantTransport({
11
- delayMs = 0
12
- } = {}) {
13
- return {
14
- async *send(messages, _tools, { signal }) {
15
- let said = "";
16
- for (const message of messages) {
17
- if (message.role === "user") said = message.content;
18
- }
19
- const reply = `Echo: ${said}`;
20
- for (const delta of reply) {
21
- if (delayMs > 0) await wait(delayMs, signal);
22
- if (signal.aborted) return;
23
- yield { type: "text", delta };
24
- }
25
- yield { type: "done" };
26
- }
27
- };
11
+ /** Replies `Echo: <last user text>` one character at a time, with no model behind it. */
12
+ function echoAssistantTransport({ delayMs = 0 } = {}) {
13
+ return { async *send(messages, _tools, { signal }) {
14
+ let said = "";
15
+ for (const message of messages) if (message.role === "user") said = message.content;
16
+ const reply = `Echo: ${said}`;
17
+ for (const delta of reply) {
18
+ if (delayMs > 0) await wait(delayMs, signal);
19
+ if (signal.aborted) return;
20
+ yield {
21
+ type: "text",
22
+ delta
23
+ };
24
+ }
25
+ yield { type: "done" };
26
+ } };
28
27
  }
29
- export {
30
- echoAssistantTransport
31
- };
28
+ export { echoAssistantTransport };
@@ -0,0 +1,10 @@
1
+ import type { AssistantTransport } from "./types.js";
2
+ export interface HttpAssistantTransportOptions {
3
+ url: string;
4
+ /** Resolved for each request so tokens can change between turns. */
5
+ headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
6
+ credentials?: RequestCredentials;
7
+ fetch?: typeof globalThis.fetch;
8
+ }
9
+ /** Posts a conversation to an app endpoint that replies with NDJSON AssistantChunks. */
10
+ export declare function httpAssistantTransport(options: HttpAssistantTransportOptions): AssistantTransport;
@@ -0,0 +1,98 @@
1
+ import { HttpError } from "../data/http-error.js";
2
+ import { isJsonObject, isRecord } from "../guards.js";
3
+ // src/assistant/http-transport.ts
4
+ function parseChunk(line, lineNumber) {
5
+ const invalid = () => /* @__PURE__ */ new Error(`Invalid assistant chunk at line ${lineNumber}`);
6
+ let chunk;
7
+ try {
8
+ chunk = JSON.parse(line);
9
+ } catch {
10
+ throw invalid();
11
+ }
12
+ if (isRecord(chunk)) switch (chunk.type) {
13
+ case "text":
14
+ if (typeof chunk.delta === "string") return {
15
+ type: "text",
16
+ delta: chunk.delta
17
+ };
18
+ break;
19
+ case "tool-call":
20
+ if (typeof chunk.id === "string" && typeof chunk.toolName === "string" && isJsonObject(chunk.args)) return {
21
+ type: "tool-call",
22
+ id: chunk.id,
23
+ toolName: chunk.toolName,
24
+ args: chunk.args
25
+ };
26
+ break;
27
+ case "done": return { type: "done" };
28
+ }
29
+ throw invalid();
30
+ }
31
+ /** Posts a conversation to an app endpoint that replies with NDJSON AssistantChunks. */
32
+ function httpAssistantTransport(options) {
33
+ const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
34
+ return { async *send(messages, tools, { signal }) {
35
+ try {
36
+ if (signal.aborted) return;
37
+ const headers = new Headers(typeof options.headers === "function" ? await options.headers() : options.headers);
38
+ if (signal.aborted) return;
39
+ headers.set("Content-Type", "application/json");
40
+ const response = await doFetch(options.url, {
41
+ method: "POST",
42
+ headers,
43
+ credentials: options.credentials,
44
+ body: JSON.stringify({
45
+ messages,
46
+ tools
47
+ }),
48
+ signal
49
+ });
50
+ if (!response.ok) throw new HttpError(`Assistant request failed (HTTP ${response.status})`, response.status, await response.text());
51
+ const reader = response.body?.getReader();
52
+ if (reader) {
53
+ const decoder = new TextDecoder();
54
+ let pending = "";
55
+ let lineNumber = 0;
56
+ let released = false;
57
+ const release = () => {
58
+ if (released) return;
59
+ released = true;
60
+ reader.cancel().catch(() => {});
61
+ reader.releaseLock();
62
+ };
63
+ signal.addEventListener("abort", release, { once: true });
64
+ try {
65
+ for (;;) {
66
+ if (signal.aborted) return;
67
+ const step = await reader.read();
68
+ if (signal.aborted) return;
69
+ pending += decoder.decode(step.value, { stream: !step.done });
70
+ if (step.done) pending += "\n";
71
+ let end = pending.indexOf("\n");
72
+ while (end >= 0) {
73
+ if (signal.aborted) return;
74
+ lineNumber++;
75
+ const line = pending.slice(0, end).trim();
76
+ pending = pending.slice(end + 1);
77
+ if (line) {
78
+ const chunk = parseChunk(line, lineNumber);
79
+ yield chunk;
80
+ if (chunk.type === "done") return;
81
+ }
82
+ end = pending.indexOf("\n");
83
+ }
84
+ if (step.done) break;
85
+ }
86
+ } finally {
87
+ signal.removeEventListener("abort", release);
88
+ release();
89
+ }
90
+ }
91
+ if (signal.aborted) return;
92
+ yield { type: "done" };
93
+ } catch (error) {
94
+ if (!signal.aborted) throw error;
95
+ }
96
+ } };
97
+ }
98
+ export { httpAssistantTransport };
@@ -1,11 +1,5 @@
1
1
  import type { StandardSchemaV1 } from "@tanstack/form-core";
2
- /** What crosses the model boundary: arguments in, results out. */
3
- export type JsonValue = string | number | boolean | null | JsonValue[] | {
4
- [key: string]: JsonValue;
5
- };
6
- export type JsonObject = {
7
- [key: string]: JsonValue;
8
- };
2
+ import type { JsonObject, JsonValue } from "../guards.js";
9
3
  /** A tool call the model asked for. `id` ties the result back to it. */
10
4
  export interface AssistantToolCall {
11
5
  id: string;
@@ -1,9 +1,17 @@
1
+ import type { ResourceRecord } from "../resource/record.js";
1
2
  import type { CanAccessParams } from "./types.js";
2
- /** One grant. `record` predicate (when present) must pass against a provided record. */
3
- export interface Permission {
3
+ /**
4
+ * One grant. `record` predicate (when present) must pass against a provided
5
+ * record, and reads it as `TRecord` — the same record shape the check is given.
6
+ */
7
+ export interface Permission<TRecord extends ResourceRecord = ResourceRecord> {
4
8
  action: string | string[];
5
9
  resource: string;
6
- record?: (record: unknown) => boolean;
10
+ record?: (record: TRecord) => boolean;
7
11
  }
8
- /** Deny-by-default permission matcher (OR across permissions). */
9
- export declare function canAccessWithPermissions(permissions: Permission[], params: CanAccessParams): boolean;
12
+ /**
13
+ * Deny-by-default permission matcher (OR across permissions). `TRecord` is fixed
14
+ * by the permissions and the params together, so a grant's predicate and the
15
+ * record it reads are the same type.
16
+ */
17
+ export declare function canAccessWithPermissions<TRecord extends ResourceRecord = ResourceRecord>(permissions: Permission<TRecord>[], params: CanAccessParams<TRecord>): boolean;