@iloveagents/foundry-web-ui 0.3.1 → 0.5.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 (227) hide show
  1. package/README.md +13 -4
  2. package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
  3. package/dist/components/ag-ui-runtime-provider.js +71 -0
  4. package/dist/components/app-brand.d.ts +6 -0
  5. package/dist/components/app-brand.js +13 -0
  6. package/dist/components/assistant-chat.d.ts +26 -0
  7. package/dist/components/assistant-chat.js +94 -0
  8. package/dist/components/chat-attachments.d.ts +25 -0
  9. package/dist/components/chat-attachments.js +20 -0
  10. package/dist/components/chat-bubble.d.ts +1 -0
  11. package/dist/components/chat-bubble.js +105 -0
  12. package/dist/components/chat-header.d.ts +2 -0
  13. package/dist/components/chat-header.js +16 -0
  14. package/dist/components/client-tool-executor.d.ts +12 -0
  15. package/dist/components/client-tool-executor.js +108 -0
  16. package/dist/components/collection-empty-state.d.ts +9 -0
  17. package/dist/components/collection-empty-state.js +6 -0
  18. package/dist/components/collection-filter-bar.d.ts +19 -0
  19. package/dist/components/collection-filter-bar.js +28 -0
  20. package/dist/components/collection-search-input.d.ts +6 -0
  21. package/dist/components/collection-search-input.js +8 -0
  22. package/dist/components/collection-skeleton.d.ts +6 -0
  23. package/dist/components/collection-skeleton.js +10 -0
  24. package/dist/components/collection-sort-menu.d.ts +10 -0
  25. package/dist/components/collection-sort-menu.js +9 -0
  26. package/dist/components/collection-surface.d.ts +8 -0
  27. package/dist/components/collection-surface.js +5 -0
  28. package/dist/components/collection-toolbar.d.ts +9 -0
  29. package/dist/components/collection-toolbar.js +5 -0
  30. package/dist/components/collection-view-toggle.d.ts +7 -0
  31. package/dist/components/collection-view-toggle.js +17 -0
  32. package/dist/components/composer-submit-bridge.d.ts +1 -0
  33. package/dist/components/composer-submit-bridge.js +15 -0
  34. package/dist/components/confirm-dialog.d.ts +11 -0
  35. package/dist/components/confirm-dialog.js +6 -0
  36. package/dist/components/confirmation-card.d.ts +14 -0
  37. package/dist/components/confirmation-card.js +12 -0
  38. package/dist/components/context-badges.d.ts +16 -0
  39. package/dist/components/context-badges.js +56 -0
  40. package/dist/components/context-bar.d.ts +41 -0
  41. package/dist/components/context-bar.js +123 -0
  42. package/dist/components/form-dialog.d.ts +12 -0
  43. package/dist/components/form-dialog.js +13 -0
  44. package/dist/components/global-selection-popover.d.ts +7 -0
  45. package/dist/components/global-selection-popover.js +167 -0
  46. package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
  47. package/dist/components/infinite-scroll-sentinel.js +25 -0
  48. package/dist/components/json-viewer.d.ts +10 -0
  49. package/dist/components/json-viewer.js +80 -0
  50. package/dist/components/loading-indicator.d.ts +13 -0
  51. package/dist/components/loading-indicator.js +35 -0
  52. package/dist/components/markdown-text.d.ts +13 -0
  53. package/dist/components/markdown-text.js +221 -0
  54. package/dist/components/message-error.d.ts +10 -0
  55. package/dist/components/message-error.js +10 -0
  56. package/dist/components/message-timestamp.d.ts +10 -0
  57. package/dist/components/message-timestamp.js +18 -0
  58. package/dist/components/name-dialog.d.ts +16 -0
  59. package/dist/components/name-dialog.js +23 -0
  60. package/dist/components/selection-popover.d.ts +12 -0
  61. package/dist/components/selection-popover.js +111 -0
  62. package/dist/components/show-document-tool-ui.d.ts +1 -0
  63. package/dist/components/show-document-tool-ui.js +53 -0
  64. package/dist/components/sidebar.d.ts +4 -0
  65. package/dist/components/sidebar.js +904 -0
  66. package/dist/components/surface-card.d.ts +9 -0
  67. package/dist/components/surface-card.js +33 -0
  68. package/dist/components/theme-runtime-provider.d.ts +15 -0
  69. package/dist/components/theme-runtime-provider.js +48 -0
  70. package/dist/components/theme-toggle.d.ts +2 -0
  71. package/dist/components/theme-toggle.js +19 -0
  72. package/dist/components/tool-call-card.d.ts +27 -0
  73. package/dist/components/tool-call-card.js +13 -0
  74. package/dist/components/tool-fallback.d.ts +26 -0
  75. package/dist/components/tool-fallback.js +129 -0
  76. package/dist/components/tool-panel-layout.d.ts +10 -0
  77. package/dist/components/tool-panel-layout.js +12 -0
  78. package/dist/components/tool-panel.d.ts +1 -0
  79. package/dist/components/tool-panel.js +89 -0
  80. package/dist/components/tooltip-icon-button.d.ts +7 -0
  81. package/dist/components/tooltip-icon-button.js +9 -0
  82. package/dist/components/user-menu.d.ts +2 -0
  83. package/dist/components/user-menu.js +14 -0
  84. package/dist/index.d.ts +68 -0
  85. package/dist/index.js +83 -0
  86. package/dist/lib/ag-ui-adapter.d.ts +29 -0
  87. package/dist/lib/ag-ui-adapter.js +382 -0
  88. package/dist/lib/app-store.d.ts +115 -0
  89. package/dist/lib/app-store.js +199 -0
  90. package/dist/lib/attachment-adapter.d.ts +20 -0
  91. package/dist/lib/attachment-adapter.js +99 -0
  92. package/dist/lib/auth-provider.d.ts +16 -0
  93. package/dist/lib/auth-provider.js +88 -0
  94. package/dist/lib/chat-bubble-store.d.ts +30 -0
  95. package/dist/lib/chat-bubble-store.js +23 -0
  96. package/dist/lib/chat-lifecycle-store.d.ts +10 -0
  97. package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
  98. package/dist/lib/client-tools.d.ts +11 -0
  99. package/dist/lib/client-tools.js +274 -0
  100. package/dist/lib/composer-submit-store.d.ts +12 -0
  101. package/dist/lib/composer-submit-store.js +17 -0
  102. package/dist/lib/dev-store.d.ts +38 -0
  103. package/dist/lib/dev-store.js +32 -0
  104. package/dist/lib/nav-config.d.ts +159 -0
  105. package/dist/lib/nav-config.js +162 -0
  106. package/dist/lib/nav-dnd.d.ts +8 -0
  107. package/dist/lib/nav-dnd.js +9 -0
  108. package/dist/lib/nav-store.d.ts +45 -0
  109. package/dist/lib/nav-store.js +49 -0
  110. package/dist/lib/selection-context.d.ts +21 -0
  111. package/dist/lib/selection-context.js +34 -0
  112. package/dist/lib/sidebar-store.d.ts +23 -0
  113. package/dist/lib/sidebar-store.js +98 -0
  114. package/dist/lib/theme-runtime.d.ts +130 -0
  115. package/dist/lib/theme-runtime.js +479 -0
  116. package/dist/lib/theme-store.d.ts +15 -0
  117. package/dist/lib/theme-store.js +53 -0
  118. package/dist/lib/tool-panel-store.d.ts +48 -0
  119. package/dist/lib/tool-panel-store.js +69 -0
  120. package/dist/lib/use-new-conversation.d.ts +8 -0
  121. package/dist/lib/use-new-conversation.js +62 -0
  122. package/dist/lib/use-page-tools.d.ts +27 -0
  123. package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
  124. package/dist/styles.css +142 -0
  125. package/dist/ui/collapsible.d.ts +5 -0
  126. package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
  127. package/dist/ui/dialog.d.ts +15 -0
  128. package/dist/ui/dialog.js +25 -0
  129. package/dist/ui/dropdown-menu.d.ts +7 -0
  130. package/dist/ui/dropdown-menu.js +19 -0
  131. package/dist/ui/popover.d.ts +7 -0
  132. package/dist/ui/popover.js +10 -0
  133. package/dist/ui/select.d.ts +4 -0
  134. package/dist/ui/select.js +9 -0
  135. package/dist/ui/tooltip.d.ts +7 -0
  136. package/dist/ui/tooltip.js +10 -0
  137. package/package.json +31 -12
  138. package/AGENTS.md +0 -59
  139. package/CHANGELOG.md +0 -211
  140. package/CLAUDE.md +0 -1
  141. package/src/__tests__/no-spaces-imports.test.ts +0 -59
  142. package/src/__tests__/open-core-guards.test.ts +0 -307
  143. package/src/__tests__/theme-runtime.test.ts +0 -81
  144. package/src/__tests__/tool-fallback.test.tsx +0 -109
  145. package/src/components/__tests__/context-bar.test.ts +0 -112
  146. package/src/components/ag-ui-runtime-provider.tsx +0 -170
  147. package/src/components/app-brand.tsx +0 -38
  148. package/src/components/assistant-chat.tsx +0 -461
  149. package/src/components/chat-attachments.tsx +0 -68
  150. package/src/components/chat-bubble.tsx +0 -373
  151. package/src/components/chat-header.tsx +0 -44
  152. package/src/components/client-tool-executor.tsx +0 -230
  153. package/src/components/collection-empty-state.tsx +0 -37
  154. package/src/components/collection-filter-bar.tsx +0 -168
  155. package/src/components/collection-search-input.tsx +0 -41
  156. package/src/components/collection-skeleton.tsx +0 -55
  157. package/src/components/collection-sort-menu.tsx +0 -62
  158. package/src/components/collection-surface.tsx +0 -46
  159. package/src/components/collection-toolbar.tsx +0 -33
  160. package/src/components/collection-view-toggle.tsx +0 -61
  161. package/src/components/composer-submit-bridge.tsx +0 -19
  162. package/src/components/confirm-dialog.tsx +0 -49
  163. package/src/components/confirmation-card.tsx +0 -32
  164. package/src/components/context-badges.tsx +0 -136
  165. package/src/components/context-bar.tsx +0 -238
  166. package/src/components/form-dialog.tsx +0 -56
  167. package/src/components/global-selection-popover.tsx +0 -266
  168. package/src/components/infinite-scroll-sentinel.tsx +0 -48
  169. package/src/components/json-viewer.tsx +0 -232
  170. package/src/components/loading-indicator.tsx +0 -40
  171. package/src/components/markdown-text-citations.test.tsx +0 -108
  172. package/src/components/markdown-text.tsx +0 -508
  173. package/src/components/name-dialog.tsx +0 -87
  174. package/src/components/selection-popover.tsx +0 -156
  175. package/src/components/show-document-tool-ui.tsx +0 -90
  176. package/src/components/sidebar.test.tsx +0 -611
  177. package/src/components/sidebar.tsx +0 -1689
  178. package/src/components/surface-card.tsx +0 -45
  179. package/src/components/theme-runtime-provider.tsx +0 -93
  180. package/src/components/theme-toggle.tsx +0 -27
  181. package/src/components/tool-call-card.tsx +0 -126
  182. package/src/components/tool-fallback.tsx +0 -169
  183. package/src/components/tool-panel-layout.tsx +0 -36
  184. package/src/components/tool-panel.tsx +0 -233
  185. package/src/components/tooltip-icon-button.tsx +0 -28
  186. package/src/components/user-menu.tsx +0 -60
  187. package/src/index.ts +0 -185
  188. package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -279
  189. package/src/lib/__tests__/app-store.test.ts +0 -405
  190. package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
  191. package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
  192. package/src/lib/__tests__/client-tools.test.ts +0 -124
  193. package/src/lib/__tests__/dev-store.test.ts +0 -78
  194. package/src/lib/__tests__/nav-config.test.ts +0 -135
  195. package/src/lib/__tests__/nav-dnd.test.ts +0 -24
  196. package/src/lib/__tests__/nav-store.test.ts +0 -59
  197. package/src/lib/__tests__/selection-context.test.ts +0 -114
  198. package/src/lib/__tests__/sidebar-store.test.ts +0 -144
  199. package/src/lib/__tests__/theme-store.test.ts +0 -83
  200. package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
  201. package/src/lib/__tests__/use-page-context.test.ts +0 -58
  202. package/src/lib/ag-ui-adapter.ts +0 -426
  203. package/src/lib/app-store.ts +0 -361
  204. package/src/lib/attachment-adapter.ts +0 -92
  205. package/src/lib/auth-provider.tsx +0 -144
  206. package/src/lib/chat-bubble-store.ts +0 -46
  207. package/src/lib/client-tools.ts +0 -293
  208. package/src/lib/composer-submit-store.ts +0 -32
  209. package/src/lib/dev-store.ts +0 -69
  210. package/src/lib/nav-config.ts +0 -329
  211. package/src/lib/nav-dnd.ts +0 -14
  212. package/src/lib/nav-store.ts +0 -89
  213. package/src/lib/selection-context.ts +0 -65
  214. package/src/lib/sidebar-store.ts +0 -113
  215. package/src/lib/theme-runtime.ts +0 -660
  216. package/src/lib/theme-store.ts +0 -67
  217. package/src/lib/tool-panel-store.ts +0 -121
  218. package/src/lib/use-new-conversation.test.tsx +0 -168
  219. package/src/lib/use-new-conversation.ts +0 -64
  220. package/src/ui/dialog.tsx +0 -109
  221. package/src/ui/dropdown-menu.tsx +0 -75
  222. package/src/ui/popover.tsx +0 -34
  223. package/src/ui/select.tsx +0 -15
  224. package/src/ui/tooltip.tsx +0 -30
  225. package/src/vite-env.d.ts +0 -1
  226. package/tsconfig.json +0 -16
  227. package/vitest.config.ts +0 -8
@@ -1,426 +0,0 @@
1
- /**
2
- * AG-UI Adapter for assistant-ui.
3
- *
4
- * Thin shim around `@iloveagents/foundry-agent`'s `AGUIRunner`:
5
- * - converts assistant-ui `Message[]` → AG-UI `Message[]` on input,
6
- * - feeds the runner, drains its `RunnerEvent` stream,
7
- * - accumulates text + tool-call state,
8
- * - yields cumulative `ChatModelRunResult` snapshots (assistant-ui requires
9
- * each yield to carry the *complete* content array — never deltas).
10
- *
11
- * Wires the React-only stores that don't belong in `@iloveagents/foundry-agent`:
12
- * - `useAppStore` for context-item snapshot + lifecycle hooks
13
- * - `useDevStore` for dev-panel request/tool capture (driven by the
14
- * runner's `request-sent` event).
15
- *
16
- * @see https://docs.ag-ui.com/sdk/js/client/http-agent
17
- * @see https://www.assistant-ui.com/docs/runtimes/custom/local
18
- */
19
-
20
- import type {
21
- ChatModelAdapter,
22
- ChatModelRunOptions,
23
- ChatModelRunResult,
24
- TextMessagePart,
25
- ToolCallMessagePart,
26
- } from "@assistant-ui/react";
27
- import type { Context, Message as AGUIMessage } from "@ag-ui/core";
28
- import { AGUIRunner, clientToolRegistry, streamingStatusStore } from "@iloveagents/foundry-agent";
29
- import { tokenFetch } from "@iloveagents/foundry-agent/msal";
30
- import { useAppStore, type AgentStateProjection } from "./app-store.ts";
31
- import { useDevStore } from "./dev-store.ts";
32
-
33
- interface ToolCallSnapshot {
34
- id: string;
35
- name: string;
36
- args: string;
37
- result?: unknown;
38
- isError?: boolean;
39
- }
40
-
41
- export class AGUIAdapterSDK implements ChatModelAdapter {
42
- private readonly runner: AGUIRunner;
43
-
44
- constructor(url: string = "/api/agent", options?: { threadId?: string; fetchFn?: typeof fetch }) {
45
- this.runner = new AGUIRunner({
46
- url,
47
- threadId: options?.threadId,
48
- fetchFn: options?.fetchFn ?? tokenFetch,
49
- });
50
- }
51
-
52
- get threadId(): string {
53
- return this.runner.threadId;
54
- }
55
-
56
- get state(): unknown {
57
- return this.runner.state;
58
- }
59
-
60
- async *run({ messages, abortSignal }: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult> {
61
- const aguiMessages = convertMessagesToAGUI(messages);
62
-
63
- // App-level lifecycle: mark thread active, snapshot context, consume
64
- // ephemeral context off the last user message.
65
- useAppStore.getState().markThreadActive();
66
- const appState = useAppStore.getState().getAgentState();
67
- const context = buildAGUIContext(appState);
68
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
69
- if (lastUserMsg) {
70
- useAppStore.getState().consumeContextForMessage(lastUserMsg.id);
71
- }
72
-
73
- let currentText = "";
74
- const toolCalls = new Map<string, ToolCallSnapshot>();
75
- let runError: string | null = null;
76
-
77
- /**
78
- * Build a `ChatModelRunResult` from the accumulated state.
79
- *
80
- * Per assistant-ui v0.12 contract, every yield must carry the COMPLETE
81
- * cumulative content array (not deltas). The runner drives multi-turn
82
- * follow-ups internally; we yield `running` for every intermediate
83
- * yield, then a single terminal `complete` after the runner's
84
- * generator returns. assistant-ui leaves the message stuck in the
85
- * loading state if the last yield's status is `running`.
86
- */
87
- const buildResult = (statusOverride?: ChatModelRunResult["status"]): ChatModelRunResult => {
88
- const content: (ToolCallMessagePart | TextMessagePart)[] = [];
89
-
90
- for (const [id, tc] of toolCalls) {
91
- let parsedArgs: unknown = {};
92
- try {
93
- parsedArgs = JSON.parse(tc.args || "{}");
94
- } catch {
95
- // Keep empty object for invalid JSON
96
- }
97
- content.push({
98
- type: "tool-call",
99
- toolCallId: id,
100
- toolName: tc.name,
101
- args: parsedArgs as ToolCallMessagePart["args"],
102
- argsText: tc.args,
103
- result: tc.result,
104
- isError: tc.isError,
105
- });
106
- }
107
-
108
- if (currentText) {
109
- content.push({ type: "text", text: currentText });
110
- }
111
-
112
- const status: ChatModelRunResult["status"] = statusOverride ?? { type: "running" };
113
-
114
- return {
115
- content,
116
- status,
117
- metadata: {
118
- custom: {
119
- threadId: this.runner.threadId,
120
- timestamp: Date.now(),
121
- state: this.runner.state,
122
- },
123
- },
124
- };
125
- };
126
-
127
- try {
128
- for await (const event of this.runner.run({
129
- messages: aguiMessages,
130
- state: appState as unknown as Record<string, unknown>,
131
- context,
132
- registry: clientToolRegistry.getState(),
133
- abortSignal,
134
- })) {
135
- let shouldYield = false;
136
-
137
- switch (event.type) {
138
- case "request-sent":
139
- if (import.meta.env.DEV) {
140
- useDevStore.getState().captureRequest(event.input);
141
- }
142
- break;
143
-
144
- case "turn-started":
145
- currentText = "";
146
- shouldYield = true;
147
- break;
148
-
149
- case "run-started":
150
- // Per-turn signal — the runner may issue multiple turns for
151
- // multi-turn tool follow-ups. Terminal completion is yielded
152
- // post-loop, not on the per-turn run-finished.
153
- break;
154
-
155
- case "run-finished":
156
- // Intermediate yield — keeps the message in the running state
157
- // while the runner decides whether to re-issue.
158
- shouldYield = true;
159
- break;
160
-
161
- case "run-error":
162
- runError = event.message;
163
- console.error("Run error:", event.message);
164
- shouldYield = true;
165
- break;
166
-
167
- case "streaming-status":
168
- streamingStatusStore.getState().setStreamingStatus(event.status);
169
- break;
170
-
171
- case "text-delta":
172
- currentText += event.delta;
173
- shouldYield = true;
174
- break;
175
-
176
- case "text-message-end":
177
- shouldYield = true;
178
- break;
179
-
180
- case "tool-call-start":
181
- toolCalls.set(event.id, { id: event.id, name: event.name, args: "" });
182
- if (import.meta.env.DEV) {
183
- useDevStore.getState().addToolCall({
184
- id: event.id,
185
- name: event.name,
186
- args: "",
187
- isClientSide: event.isClientSide,
188
- timestamp: Date.now(),
189
- });
190
- }
191
- shouldYield = true;
192
- break;
193
-
194
- case "tool-call-args": {
195
- const tc = toolCalls.get(event.id);
196
- if (tc) {
197
- tc.args += event.delta;
198
- shouldYield = true;
199
- }
200
- break;
201
- }
202
-
203
- case "tool-call-end": {
204
- const tc = toolCalls.get(event.id);
205
- if (tc) {
206
- tc.args = event.args;
207
- if (event.result !== undefined) tc.result = event.result;
208
- if (event.isError !== undefined) tc.isError = event.isError;
209
- if (import.meta.env.DEV) {
210
- useDevStore.getState().updateToolCall(event.id, {
211
- args: tc.args,
212
- result: tc.result,
213
- });
214
- }
215
- shouldYield = true;
216
- }
217
- break;
218
- }
219
-
220
- case "tool-call-result": {
221
- const tc = toolCalls.get(event.id);
222
- if (tc) {
223
- tc.result = event.result;
224
- tc.isError = event.isError;
225
- if (import.meta.env.DEV) {
226
- useDevStore.getState().updateToolCall(event.id, {
227
- result: tc.result,
228
- });
229
- }
230
- shouldYield = true;
231
- }
232
- break;
233
- }
234
- }
235
-
236
- if (shouldYield) {
237
- const result = buildResult();
238
- if ((result.content?.length ?? 0) > 0) {
239
- yield result;
240
- }
241
- }
242
- }
243
-
244
- // Terminal yield — runner generator returned. assistant-ui leaves
245
- // the message stuck in the loading state if the last yield's
246
- // `status` is `running`, so we always emit a final settle once the
247
- // runner is fully done (after every turn + every follow-up).
248
- //
249
- // If the run errored (the AG-UI backend emitted RUN_ERROR — e.g. the
250
- // agent exhausted rate-limit/transient retries, or a non-retryable
251
- // 4xx), settle as `incomplete`/`error` instead of `complete`/`stop`,
252
- // carrying the error message. This keeps any partial content AND
253
- // flips the message into assistant-ui's error state so the existing
254
- // `<MessagePrimitive.Error>` renders a visible, retryable error —
255
- // not a silent empty bubble. We yield the terminal status rather
256
- // than throwing; an unhandled throw re-introduces the stuck-running
257
- // bug (the runner generator has already returned here).
258
- if (runError !== null) {
259
- yield buildResult({
260
- type: "incomplete",
261
- reason: "error",
262
- error: runError || "The run failed.",
263
- });
264
- } else {
265
- yield buildResult({ type: "complete", reason: "stop" });
266
- }
267
- } finally {
268
- streamingStatusStore.getState().setStreamingStatus({ status: "idle" });
269
- }
270
- }
271
- }
272
-
273
- function buildAGUIContext(appState: AgentStateProjection): Context[] {
274
- return appState.contextItems.map((item, index) => ({
275
- description: describeContextItem(item, index),
276
- value: stringifyContextItem(item),
277
- }));
278
- }
279
-
280
- function describeContextItem(item: Record<string, unknown>, index: number): string {
281
- const type = typeof item.type === "string" ? item.type : "context";
282
- const label = typeof item.label === "string" ? item.label : "";
283
-
284
- if (type === "ref") {
285
- const refType = typeof item.refType === "string" ? item.refType : "reference";
286
- const target = isRecord(item.target) ? item.target : undefined;
287
- const targetPath =
288
- typeof item.targetPath === "string"
289
- ? item.targetPath
290
- : typeof target?.path === "string"
291
- ? target.path
292
- : undefined;
293
- const targetLabel = typeof target?.label === "string" ? target.label : undefined;
294
- const targetScope = typeof target?.scope === "string" ? target.scope : undefined;
295
- if (targetPath) {
296
- if (!targetLabel && !targetScope) {
297
- return `Selected ${refType} atom. Use targetPath as the exact edit target when the user asks to modify, rewrite, or transform selected content.`;
298
- }
299
- const targetText = targetLabel || targetScope || "JSON atom";
300
- return `Selected ${refType} ${targetText}. Use targetPath as the exact JSON edit target when the user asks to modify, rewrite, or transform this reference.`;
301
- }
302
- return `Selected ${refType} reference. Use refId as the edit target when the user asks to modify, rewrite, or transform selected content.`;
303
- }
304
-
305
- if (type === "selection") {
306
- return "Selected text from the current UI. Use it as the focus when the user asks about, rewrites, or modifies selected content.";
307
- }
308
-
309
- if (type === "page") {
310
- return `Pinned page context${label ? `: ${label}` : ""}.`;
311
- }
312
-
313
- return `Context item ${index + 1}${label ? `: ${label}` : ""}.`;
314
- }
315
-
316
- function stringifyContextItem(item: Record<string, unknown>): string {
317
- try {
318
- return JSON.stringify(item);
319
- } catch {
320
- const type = typeof item.type === "string" ? item.type : "context";
321
- const label = typeof item.label === "string" ? item.label : "";
322
- return JSON.stringify({ type, label });
323
- }
324
- }
325
-
326
- function isRecord(value: unknown): value is Record<string, unknown> {
327
- return typeof value === "object" && value !== null && !Array.isArray(value);
328
- }
329
-
330
- /**
331
- * Convert assistant-ui's `Message[]` into AG-UI's wire format.
332
- *
333
- * Our Python backend doesn't keep server-side thread state, so prior turns'
334
- * tool calls + results need to ride along on every request.
335
- */
336
- function convertMessagesToAGUI(messages: ChatModelRunOptions["messages"]): AGUIMessage[] {
337
- const out: AGUIMessage[] = [];
338
-
339
- for (let idx = 0; idx < messages.length; idx++) {
340
- const msg = messages[idx];
341
- const id = msg.id || `msg-${idx}`;
342
- const textContent = msg.content
343
- .filter((c): c is Extract<typeof c, { type: "text" }> => c.type === "text")
344
- .map((c) => c.text)
345
- .join("\n");
346
-
347
- if (msg.role === "system") {
348
- out.push({ id, role: "system", content: textContent });
349
- continue;
350
- }
351
-
352
- if (msg.role === "assistant") {
353
- const toolCallParts = msg.content.filter(
354
- (c): c is Extract<typeof c, { type: "tool-call" }> => c.type === "tool-call",
355
- );
356
-
357
- if (toolCallParts.length > 0) {
358
- out.push({
359
- id,
360
- role: "assistant",
361
- content: textContent || undefined,
362
- toolCalls: toolCallParts.map((tc) => ({
363
- id: tc.toolCallId,
364
- type: "function" as const,
365
- function: {
366
- name: tc.toolName,
367
- arguments: tc.argsText ?? JSON.stringify(tc.args ?? {}),
368
- },
369
- })),
370
- });
371
- for (const tc of toolCallParts) {
372
- if (tc.result !== undefined) {
373
- out.push({
374
- id: `tool-result-${tc.toolCallId}`,
375
- role: "tool",
376
- toolCallId: tc.toolCallId,
377
- content: typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result),
378
- });
379
- }
380
- }
381
- } else {
382
- out.push({ id, role: "assistant", content: textContent });
383
- }
384
- continue;
385
- }
386
-
387
- // User role: extract binary attachments if present
388
- const parts: (
389
- | { type: "text"; text: string }
390
- | { type: "binary"; mimeType: string; data?: string; filename?: string }
391
- )[] = [];
392
- for (const c of msg.content) {
393
- if (c.type === "text") {
394
- parts.push({ type: "text", text: c.text });
395
- }
396
- }
397
- if ("attachments" in msg && Array.isArray(msg.attachments)) {
398
- for (const att of msg.attachments) {
399
- if (!att.content) continue;
400
- for (const part of att.content) {
401
- if (part.type === "text") {
402
- parts.push({ type: "text", text: part.text });
403
- } else if (part.type === "file" && "data" in part) {
404
- parts.push({
405
- type: "binary",
406
- mimeType: part.mimeType,
407
- data: part.data as string,
408
- filename: part.filename,
409
- });
410
- } else if (part.type === "image" && "image" in part) {
411
- parts.push({
412
- type: "binary",
413
- mimeType: "image/*",
414
- data: part.image as string,
415
- });
416
- }
417
- }
418
- }
419
- }
420
-
421
- const hasBinary = parts.some((p) => p.type === "binary");
422
- out.push({ id, role: "user", content: hasBinary ? parts : textContent });
423
- }
424
-
425
- return out;
426
- }