@iloveagents/foundry-web-ui 0.3.0 → 0.3.1
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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/lib/__tests__/ag-ui-adapter.test.ts +16 -10
- package/src/lib/ag-ui-adapter.ts +21 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @iloveagents/foundry-web-ui
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9ec870e: Surface AG-UI run errors in chat instead of rendering an empty assistant bubble.
|
|
8
|
+
- @iloveagents/foundry-agent@0.3.1
|
|
9
|
+
- @iloveagents/foundry-web-primitives@0.3.1
|
|
10
|
+
|
|
3
11
|
## 0.3.0
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -36,6 +36,6 @@ Feature modules (like SPACES) plug into `@iloveagents/foundry-web-ui` via regist
|
|
|
36
36
|
- **NavItem.dnd** — drag-and-drop behavior on sidebar items
|
|
37
37
|
- **Panel renderer registry** — custom content renderers for the side panel
|
|
38
38
|
- **Citation handler** — handles `[n]` citation clicks in markdown
|
|
39
|
-
- **Tool UI** — custom rendering for agent tools via `
|
|
39
|
+
- **Tool UI** — custom rendering for agent tools via `makeAssistantToolUI`
|
|
40
40
|
|
|
41
41
|
See [AGENTS.md](./AGENTS.md) for architecture details and import boundary rules.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-web-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"react-markdown": "^10.0.0",
|
|
47
47
|
"remark-gfm": "^4.0.0",
|
|
48
48
|
"shiki": "^4.0.0",
|
|
49
|
-
"@iloveagents/foundry-agent": "0.3.
|
|
50
|
-
"@iloveagents/foundry-web-primitives": "0.3.
|
|
49
|
+
"@iloveagents/foundry-agent": "0.3.1",
|
|
50
|
+
"@iloveagents/foundry-web-primitives": "0.3.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"typescript": "~5.9.3",
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* The shim is a generator-driven assistant-ui `ChatModelAdapter` wrapping
|
|
5
5
|
* `@iloveagents/foundry-agent`'s `AGUIRunner`. The contract under test:
|
|
6
6
|
* - Every intermediate yield carries `status: { type: "running" }`.
|
|
7
|
-
* - The FINAL yield
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* - The FINAL yield settles the message so the bubble unsticks (the
|
|
8
|
+
* previous adapter left it `running` and it got stuck loading — PR #116):
|
|
9
|
+
* `{ type: "complete", reason: "stop" }` on success, or
|
|
10
|
+
* `{ type: "incomplete", reason: "error", error }` on RUN_ERROR so the
|
|
11
|
+
* UI shows a visible error instead of a silent empty bubble.
|
|
11
12
|
* - `content` is cumulative — every yield holds the full content array,
|
|
12
13
|
* not deltas (assistant-ui v0.12 contract).
|
|
13
14
|
*/
|
|
@@ -170,7 +171,7 @@ describe("AGUIAdapterSDK", () => {
|
|
|
170
171
|
expect(finalContent.find((p) => p.type === "text")?.text).toBe("Done");
|
|
171
172
|
});
|
|
172
173
|
|
|
173
|
-
it("
|
|
174
|
+
it("settles run-error as incomplete/error so the UI shows it (does not throw)", async () => {
|
|
174
175
|
runMock.mockImplementation(async function* (): AsyncGenerator<RunnerEvent> {
|
|
175
176
|
yield { type: "turn-started" };
|
|
176
177
|
yield { type: "request-sent", input: {} };
|
|
@@ -181,15 +182,20 @@ describe("AGUIAdapterSDK", () => {
|
|
|
181
182
|
|
|
182
183
|
const adapter = new AGUIAdapterSDK();
|
|
183
184
|
const out: ChatModelRunResultLike[] = [];
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
185
|
+
// On RUN_ERROR the adapter settles the message as incomplete/error
|
|
186
|
+
// (NOT a silent complete/stop) so assistant-ui's <MessagePrimitive.Error>
|
|
187
|
+
// renders a visible, retryable error. It must still settle via a
|
|
188
|
+
// terminal yield rather than throw — an unhandled throw re-introduces
|
|
189
|
+
// the stuck-running regression (message abandoned mid-run).
|
|
188
190
|
for await (const r of adapter.run(makeRunInput())) {
|
|
189
191
|
out.push({ status: r.status, content: r.content });
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
expect(out[out.length - 1].status).toEqual({
|
|
194
|
+
expect(out[out.length - 1].status).toEqual({
|
|
195
|
+
type: "incomplete",
|
|
196
|
+
reason: "error",
|
|
197
|
+
error: "boom",
|
|
198
|
+
});
|
|
193
199
|
});
|
|
194
200
|
|
|
195
201
|
it("forwards app context items through the native AG-UI context field", async () => {
|
package/src/lib/ag-ui-adapter.ts
CHANGED
|
@@ -243,13 +243,27 @@ export class AGUIAdapterSDK implements ChatModelAdapter {
|
|
|
243
243
|
|
|
244
244
|
// Terminal yield — runner generator returned. assistant-ui leaves
|
|
245
245
|
// the message stuck in the loading state if the last yield's
|
|
246
|
-
// `status` is `running`, so we always emit a final
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}
|
|
253
267
|
} finally {
|
|
254
268
|
streamingStatusStore.getState().setStreamingStatus({ status: "idle" });
|
|
255
269
|
}
|