cortena-ui 1.4.2 → 1.6.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.
- package/CHANGELOG.md +107 -0
- package/LICENSE +7 -0
- package/README.md +235 -3
- package/dist/a2ui/views.js +2 -2
- package/dist/agent-chat/a2ui-block.d.ts +60 -0
- package/dist/agent-chat/a2ui-block.js +69 -0
- package/dist/agent-chat/a2ui-block.js.map +1 -0
- package/dist/agent-chat/agui-client.d.ts +40 -0
- package/dist/agent-chat/agui-client.js +251 -0
- package/dist/agent-chat/agui-client.js.map +1 -0
- package/dist/agent-chat/bridge.d.ts +109 -0
- package/dist/agent-chat/bridge.js +353 -0
- package/dist/agent-chat/bridge.js.map +1 -0
- package/dist/agent-chat/session.d.ts +79 -0
- package/dist/agent-chat/session.js +391 -0
- package/dist/agent-chat/session.js.map +1 -0
- package/dist/agent-chat/step-label.d.ts +99 -0
- package/dist/agent-chat/step-label.js +116 -0
- package/dist/agent-chat/step-label.js.map +1 -0
- package/dist/agent-chat/store.d.ts +102 -0
- package/dist/agent-chat/store.js +876 -0
- package/dist/agent-chat/store.js.map +1 -0
- package/dist/agent-chat/types.d.ts +277 -0
- package/dist/agent-chat/types.js +17 -0
- package/dist/agent-chat/types.js.map +1 -0
- package/dist/agent-chat.d.ts +11 -0
- package/dist/agent-chat.js +11 -0
- package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
- package/dist/components/admin-permissions/admin-permissions.js +101 -0
- package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
- package/dist/components/admin-permissions/context.d.ts +70 -0
- package/dist/components/admin-permissions/context.js +258 -0
- package/dist/components/admin-permissions/context.js.map +1 -0
- package/dist/components/admin-permissions/index.d.ts +10 -0
- package/dist/components/admin-permissions/licence.d.ts +15 -0
- package/dist/components/admin-permissions/licence.js +78 -0
- package/dist/components/admin-permissions/licence.js.map +1 -0
- package/dist/components/admin-permissions/matrix.d.ts +20 -0
- package/dist/components/admin-permissions/matrix.js +191 -0
- package/dist/components/admin-permissions/matrix.js.map +1 -0
- package/dist/components/admin-permissions/members.d.ts +18 -0
- package/dist/components/admin-permissions/members.js +185 -0
- package/dist/components/admin-permissions/members.js.map +1 -0
- package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
- package/dist/components/admin-permissions/role-assignment.js +174 -0
- package/dist/components/admin-permissions/role-assignment.js.map +1 -0
- package/dist/components/admin-permissions/roles.d.ts +25 -0
- package/dist/components/admin-permissions/roles.js +168 -0
- package/dist/components/admin-permissions/roles.js.map +1 -0
- package/dist/components/admin-permissions/types.d.ts +152 -0
- package/dist/components/admin-permissions/types.js +63 -0
- package/dist/components/admin-permissions/types.js.map +1 -0
- package/dist/components/agent-chat-popup.d.ts +29 -0
- package/dist/components/agent-chat-popup.js +188 -0
- package/dist/components/agent-chat-popup.js.map +1 -0
- package/dist/components/agent-chat.d.ts +163 -0
- package/dist/components/agent-chat.js +673 -0
- package/dist/components/agent-chat.js.map +1 -0
- package/dist/components/app-shell.d.ts +126 -0
- package/dist/components/app-shell.js +297 -0
- package/dist/components/app-shell.js.map +1 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button-link.js +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/consent-screen.d.ts +65 -0
- package/dist/components/consent-screen.js +123 -0
- package/dist/components/consent-screen.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +15 -1
- package/dist/components/data-table/data-table.js +18 -4
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/data-table/index.d.ts +4 -4
- package/dist/components/data-table/parts.d.ts +27 -3
- package/dist/components/data-table/parts.js +175 -55
- package/dist/components/data-table/parts.js.map +1 -1
- package/dist/components/data-table/types.d.ts +61 -0
- package/dist/components/data-table/use-data-table.js +91 -6
- package/dist/components/data-table/use-data-table.js.map +1 -1
- package/dist/components/data-table/use-server-source.js +119 -28
- package/dist/components/data-table/use-server-source.js.map +1 -1
- package/dist/components/help-panel.d.ts +131 -0
- package/dist/components/help-panel.js +545 -0
- package/dist/components/help-panel.js.map +1 -0
- package/dist/components/login-screen.d.ts +127 -0
- package/dist/components/login-screen.js +339 -0
- package/dist/components/login-screen.js.map +1 -0
- package/dist/components/session-guard.d.ts +268 -0
- package/dist/components/session-guard.js +632 -0
- package/dist/components/session-guard.js.map +1 -0
- package/dist/components/toast.d.ts +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +11 -7
- package/dist/data-table.d.ts +13 -4
- package/dist/data-table.js +10 -2
- package/dist/hooks/use-cortena-theme.js +49 -3
- package/dist/hooks/use-cortena-theme.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +21 -8
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -1
- package/package.json +18 -5
- package/src/agent-chat/a2ui-block.ts +118 -0
- package/src/agent-chat/agui-client.ts +405 -0
- package/src/agent-chat/bridge.ts +445 -0
- package/src/agent-chat/session.ts +549 -0
- package/src/agent-chat/step-label.ts +177 -0
- package/src/agent-chat/store.ts +1234 -0
- package/src/agent-chat/types.ts +308 -0
- package/src/components/admin-permissions/admin-permissions.tsx +130 -0
- package/src/components/admin-permissions/context.tsx +376 -0
- package/src/components/admin-permissions/index.tsx +32 -0
- package/src/components/admin-permissions/licence.tsx +84 -0
- package/src/components/admin-permissions/matrix.tsx +257 -0
- package/src/components/admin-permissions/members.tsx +204 -0
- package/src/components/admin-permissions/role-assignment.tsx +239 -0
- package/src/components/admin-permissions/roles.tsx +169 -0
- package/src/components/admin-permissions/types.ts +231 -0
- package/src/components/agent-chat-popup.tsx +289 -0
- package/src/components/agent-chat.tsx +1006 -0
- package/src/components/app-shell.tsx +502 -0
- package/src/components/consent-screen.tsx +239 -0
- package/src/components/data-table/data-table.tsx +36 -0
- package/src/components/data-table/index.tsx +6 -1
- package/src/components/data-table/parts.tsx +223 -47
- package/src/components/data-table/types.ts +68 -0
- package/src/components/data-table/use-data-table.ts +152 -4
- package/src/components/data-table/use-server-source.ts +150 -12
- package/src/components/help-panel.tsx +765 -0
- package/src/components/login-screen.tsx +479 -0
- package/src/components/session-guard.tsx +1071 -0
- package/src/entries/agent-chat.ts +137 -0
- package/src/entries/core.ts +8 -0
- package/src/entries/data-table.ts +41 -0
- package/src/entries/markdown.ts +25 -0
- package/src/hooks/use-cortena-theme.ts +63 -4
- package/src/index.ts +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# cortena-ui
|
|
2
|
+
|
|
3
|
+
Notable changes per release. Versions before 1.6.0 are recorded in the git log
|
|
4
|
+
and in `../../CONSUMING.md`; this file starts where the changelog does.
|
|
5
|
+
|
|
6
|
+
## 1.6.0
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`cortena-ui/agent-chat`: one plain progress line per tool call** (SKILLS-9).
|
|
11
|
+
Each AG-UI tool call becomes a sentence under the assistant turn — a spinner
|
|
12
|
+
while it runs, a tick when it lands, a cross and the error envelope's message
|
|
13
|
+
when it fails, and the elapsed time on anything still running after eight
|
|
14
|
+
seconds. The list is labelled `Progress` and is deliberately NOT a live
|
|
15
|
+
region of its own: it is drawn inside the transcript, which is already
|
|
16
|
+
`role="log" aria-live="polite"`.
|
|
17
|
+
- `agent-chat/step-label.ts`: `stepLabel(toolName, args, phase)`,
|
|
18
|
+
`confirmationRequiredLabel(args)` and `humaniseId(id)` — the wording map for
|
|
19
|
+
`engram_search`, `engram_describe` and `broker_invoke`, pure and with no
|
|
20
|
+
dependency on the result.
|
|
21
|
+
- `useAgentChat` gains `steps: AgentChatStep[]`, paired by AG-UI
|
|
22
|
+
`toolCallId`. A `409 confirmation_required` result relabels the step rather
|
|
23
|
+
than failing it; an aborted or errored run ends every step still running.
|
|
24
|
+
- Steps are kept in `sessionStorage` beside the session key, so a collapsed
|
|
25
|
+
pop-up, a reload or a remount mid-run does not lose them —
|
|
26
|
+
`chat.history` cannot bring them back, because the server never sees them.
|
|
27
|
+
- New exports: `AgentSteps`, `AgentStepsProps`, `AgentChatStep`,
|
|
28
|
+
`AgentStepPhase`, `stepLabel`, `confirmationRequiredLabel`, `humaniseId`,
|
|
29
|
+
`genericLabel`, `isGenericLabel`, `truncate`, `readResultMetadata`,
|
|
30
|
+
`AgentToolResultMetadata`, `AgentToolResultMeta`, `readStoredSteps`,
|
|
31
|
+
`writeStoredSteps`, `serialiseSteps`, `stepsStorageKey`, `STEP_LIMIT`.
|
|
32
|
+
|
|
33
|
+
- **The outcome of a tool call is read from `TOOL_CALL_RESULT.metadata`**
|
|
34
|
+
(SKILLS-9 review). `AgentChatBridge` forwards the metadata into
|
|
35
|
+
`AgentToolEvent.data.metadata`, and the store decides from
|
|
36
|
+
`{ toolName, isError, meta?: { status, code, message } }` — an error with
|
|
37
|
+
`meta.code === "confirmation_required"` is the broker asking a question, any
|
|
38
|
+
other error is worded from `meta.message` or the first 200 characters of the
|
|
39
|
+
result text, and everything else is done. `content` is never parsed for an
|
|
40
|
+
outcome: a Cortena tool result is `{ content: [...], details }`, so the
|
|
41
|
+
`ok: false` the client used to look for was never at the top level and every
|
|
42
|
+
failed call was drawn as a success.
|
|
43
|
+
|
|
44
|
+
- **A step always stops spinning, and never claims more than it knows.** A
|
|
45
|
+
plain `RUN_FINISHED` marks every open step done with its label untouched (the
|
|
46
|
+
run succeeded, so the call did); an abort marks them `stopped` — a new,
|
|
47
|
+
neutral status, because the user asked for it and a red cross reads as a
|
|
48
|
+
fault they caused; and a step restored from storage as `running` or `paused`
|
|
49
|
+
comes back `stopped` with no message, because the page went away, not the
|
|
50
|
+
tool call. A `tool.start` for a step that has already ended is a reconnect
|
|
51
|
+
replay and is ignored.
|
|
52
|
+
|
|
53
|
+
- **An interrupt is not a success** (SKILLS-9 review 2). `RUN_FINISHED` with
|
|
54
|
+
`outcome.type === "interrupt"` is cortenacore pausing the run for an approval
|
|
55
|
+
or a frontend tool with the tool call STILL OPEN, and ticking it told the user
|
|
56
|
+
a write had gone through while the card asking their permission for it was on
|
|
57
|
+
the screen. `AgentChatEvent` carries `interrupted`, and such a step becomes
|
|
58
|
+
`paused` — a second neutral status, "Waiting for your approval", no spinner
|
|
59
|
+
and no elapsed counter. The result on the continuation resolves it; a `Deny`
|
|
60
|
+
marks it `stopped`. A stored `status` is validated against the known set on
|
|
61
|
+
the way back in, and anything else is `stopped`.
|
|
62
|
+
|
|
63
|
+
- **A late result cannot resurrect a dropped step** (SKILLS-9 review 2). A
|
|
64
|
+
runaway turn pushes its first calls past `STEP_LIMIT`, and their results
|
|
65
|
+
arrive afterwards; the store keeps the last 100 dropped ids and ignores them
|
|
66
|
+
rather than appending a `Working: tool` line for the oldest call of the turn
|
|
67
|
+
at the bottom of the strip.
|
|
68
|
+
|
|
69
|
+
- **`isError` is believed from either witness** (SKILLS-9 review 2). A
|
|
70
|
+
`tool.error` whose metadata arrived without the field was read as a success.
|
|
71
|
+
|
|
72
|
+
- **The id and the tool name are bounded** (SKILLS-9 review 2) — 80 characters,
|
|
73
|
+
in the stored projection and on `data-tool`. Neither is written by this
|
|
74
|
+
package, and 9 kB of either filled the 64 kB slot on its own.
|
|
75
|
+
|
|
76
|
+
- **Bounded and sanitised labels.** Every interpolated string — an operation id,
|
|
77
|
+
a tool name, a server message — goes through `truncate`, which strips
|
|
78
|
+
`\p{Cf}` and `\p{Cc}` before the length check, so a bidi override cannot
|
|
79
|
+
reverse a line and 8 kB of catalogue id cannot fill the strip.
|
|
80
|
+
|
|
81
|
+
- **Only a projection is persisted.** `id`, `toolName`, `label`, `status`,
|
|
82
|
+
`startedAt`, `endedAt`, `errorMessage` — never `args`, which are the user's
|
|
83
|
+
data. The write is debounced 250 ms and flushed on unmount, the list is capped
|
|
84
|
+
at `STEP_LIMIT` in the reducer, and the slot is capped at 64 kB, oldest first.
|
|
85
|
+
|
|
86
|
+
- **Accessibility.** The elapsed counter is `aria-hidden` (it changed once a
|
|
87
|
+
second inside a live region), a running step is held out of the announcement
|
|
88
|
+
until its arguments have produced a real sentence rather than reading out
|
|
89
|
+
"Working: broker_invoke" and correcting itself, and the label span is keyed on
|
|
90
|
+
the words, so a re-word is a node replacement rather than a mutated text node
|
|
91
|
+
some screen readers never announce.
|
|
92
|
+
|
|
93
|
+
### Unchanged
|
|
94
|
+
|
|
95
|
+
- No change to AG-UI protocol handling beyond forwarding
|
|
96
|
+
`TOOL_CALL_RESULT.metadata`, which the bridge already parsed and dropped.
|
|
97
|
+
Streamed reasoning keeps the fix that stops a later event wiping it, now with
|
|
98
|
+
the re-attach rewind covered by a test.
|
|
99
|
+
- `agent-chat` remains the only entry that can reach `@ag-ui/client`; every
|
|
100
|
+
other bundle probe is unchanged.
|
|
101
|
+
|
|
102
|
+
### Bundle
|
|
103
|
+
|
|
104
|
+
- `agent-chat` grows **+6.8 kB** total (2,738.6 → 2,745.4 kB) and **+5.9 kB**
|
|
105
|
+
eager (843.4 → 849.3 kB): the wording map, the step reducer and the strip.
|
|
106
|
+
Not "unmoved" — it is a new surface, and it costs what it costs. Every other
|
|
107
|
+
entry is byte-identical, and `CONSUMING.md`'s table carries the numbers.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026 Ascendence AI Technology Pvt Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This package is published for use with the Cortena platform by licensed
|
|
4
|
+
Cortena customers and partners only. No permission is granted to copy,
|
|
5
|
+
modify, redistribute or use it outside a Cortena deployment.
|
|
6
|
+
|
|
7
|
+
Provided as is, without warranty.
|
package/README.md
CHANGED
|
@@ -28,18 +28,59 @@ consumer's bundler getting tree-shaking right:
|
|
|
28
28
|
|
|
29
29
|
| entry | what it holds | what it costs |
|
|
30
30
|
| --- | --- | --- |
|
|
31
|
-
| `cortena-ui/core` | primitives, overlays, light form controls, `cn`, `useCortenaTheme` | nothing heavy |
|
|
31
|
+
| `cortena-ui/core` | primitives, overlays, light form controls, `AppShell`, `cn`, `useCortenaTheme` | nothing heavy |
|
|
32
32
|
| `cortena-ui/form` | `Form`, `useForm`, `zodResolver`, date pickers, `Dropzone` | react-hook-form, zod, react-day-picker, react-dropzone |
|
|
33
33
|
| `cortena-ui/data-table` | `DataTable` and its parts | TanStack Table and Virtual; exceljs on the first export |
|
|
34
34
|
| `cortena-ui/chart` | `Chart`, themed Recharts primitives | an engine on the first chart drawn, not on import |
|
|
35
35
|
| `cortena-ui/markdown` | `Markdown` | react-markdown, remark-gfm, rehype-sanitize |
|
|
36
36
|
| `cortena-ui/a2ui` | the A2UI catalogue and renderer | most of the package, by design |
|
|
37
37
|
| `cortena-ui/sortable-list` | `SortableList`, `SortableHandle`, `arrayMove` | dnd-kit |
|
|
38
|
+
| `cortena-ui/agent-chat` | `AgentChatPopup`, `AgentChat`, `createAguiAgentChatClient` | the A2UI catalogue, plus `@ag-ui/client` (rxjs, zod 3, uuid, protobuf) |
|
|
38
39
|
|
|
39
40
|
Each component has exactly one home, so the barrel re-exports every entry
|
|
40
41
|
without an ambiguous name. **Adding a component means adding it to its area
|
|
41
42
|
entry in `src/entries/`, not to `src/index.ts`.**
|
|
42
43
|
|
|
44
|
+
`AgentChat` has its own entry for the same kind of reason: `@ag-ui/client` is
|
|
45
|
+
the run transport, and an extension that draws a button should not download a
|
|
46
|
+
chat stream it never opens. It is also **not re-exported from the root barrel**,
|
|
47
|
+
so `import { Button } from "cortena-ui"` cannot reach it however the consumer's
|
|
48
|
+
bundler behaves. `pnpm bundle:probe` measures it, and
|
|
49
|
+
`test/bundle-probe.test.mjs` fails if `@ag-ui/*` turns up in any other probe.
|
|
50
|
+
|
|
51
|
+
`@ag-ui/client` is an **optional peer**, not a dependency: an extension that
|
|
52
|
+
uses this entry installs it itself, at a version its own package.json names.
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
"peerDependencies": { "@ag-ui/client": "^0.0.59" },
|
|
56
|
+
"peerDependenciesMeta": { "@ag-ui/client": { "optional": true } }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
As a dependency it was pinned to one exact build for the whole fleet, and an
|
|
60
|
+
app that also talks AG-UI directly — which is every app with a second agent
|
|
61
|
+
surface — resolved two copies. Two copies of an rxjs-based client is two event
|
|
62
|
+
pipelines and two zod registries, and the symptom is a subscription that never
|
|
63
|
+
fires. Optional, because only `createAguiAgentChatClient` imports it: `AgentChat`,
|
|
64
|
+
`AgentChatPopup` and `useAgentChat` are written against the `AgentChatClient`
|
|
65
|
+
interface, so an extension on another transport installs nothing.
|
|
66
|
+
|
|
67
|
+
And it is loaded **lazily**, inside `createAguiAgentChatClient`, by dynamic
|
|
68
|
+
import. A static import meant the optional peer was not optional at all: with
|
|
69
|
+
it uninstalled the whole `cortena-ui/agent-chat` module failed to evaluate, so
|
|
70
|
+
an extension on another transport could not draw `AgentChatPopup` either — a
|
|
71
|
+
package it had never heard of decided whether its chat rendered, and said so in
|
|
72
|
+
a resolution error. The entry now loads without the peer, and only a run needs
|
|
73
|
+
it; when it is absent the chat shows
|
|
74
|
+
|
|
75
|
+
> cortena-ui/agent-chat: the AG-UI transport needs the optional peer
|
|
76
|
+
> "@ag-ui/client". Install it (pnpm add @ag-ui/client), or pass your own
|
|
77
|
+
> AgentChatClient to AgentChat.
|
|
78
|
+
|
|
79
|
+
The specifier stays a literal, so a bundler still splits the transport into a
|
|
80
|
+
chunk of its own and `test/bundle-probe.test.mjs` still measures that the
|
|
81
|
+
agent-chat entry carries it. `test/agent-chat-peer.test.mjs` runs the built
|
|
82
|
+
entry in a child process with the peer made unresolvable.
|
|
83
|
+
|
|
43
84
|
`SortableList` has its own entry for a specific reason rather than a stylistic
|
|
44
85
|
one: `@dnd-kit/core` and `@dnd-kit/sortable` declare no `"sideEffects"` in their
|
|
45
86
|
package.json, so a bundler must assume importing them does something observable
|
|
@@ -86,6 +127,197 @@ regression; `test/chart-bundle.test.tsx` says which line caused it.
|
|
|
86
127
|
check in light, dark and system-dark; add an entry with every variant and
|
|
87
128
|
size when adding a component.
|
|
88
129
|
|
|
130
|
+
## Shell chrome
|
|
131
|
+
|
|
132
|
+
`AppShell` is the chrome every extension wears (§9 of
|
|
133
|
+
how-to-create-a-cortena-extension, audit rule P-10): the registered brand mark
|
|
134
|
+
and the extension name top left, the avatar menu — the only settings entry
|
|
135
|
+
point — top right, and one fixed bottom-right cluster holding the theme toggle
|
|
136
|
+
then the help button. `BrandMark`, `BottomRightCluster`, `ThemeToggle`,
|
|
137
|
+
`HelpButton` and `documentTitle` are exported alongside it.
|
|
138
|
+
|
|
139
|
+
The mark comes from `cortena-design/marks` by `extension.id`, which is the same
|
|
140
|
+
file the Apps tile, the MCP app card, the agent pop-up pill and the favicon
|
|
141
|
+
draw. Adding a mark, and generating the three favicon files from it with
|
|
142
|
+
`cortena-design-favicon`, are both in `../../CONSUMING.md`, "Shell chrome and
|
|
143
|
+
favicon".
|
|
144
|
+
|
|
145
|
+
`helpPanel` is a slot until `HelpPanel` lands (EXTBP-24); `help.source` is the
|
|
146
|
+
functional document the panel will read.
|
|
147
|
+
|
|
148
|
+
## The agent pop-up
|
|
149
|
+
|
|
150
|
+
`AgentChatPopup` is the extension's own Cortena Agent, in the corner of every
|
|
151
|
+
screen (§17 of how-to-create-a-cortena-extension, audit rule P-25). It mounts in
|
|
152
|
+
`AppShell`'s `agentSlot`, so it stacks above the theme/help cluster rather than
|
|
153
|
+
beside it.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { AppShell } from "cortena-ui";
|
|
157
|
+
import { AgentChatPopup, createAguiAgentChatClient } from "cortena-ui/agent-chat";
|
|
158
|
+
|
|
159
|
+
const extension = { id: "tasks", name: "Tasks" };
|
|
160
|
+
|
|
161
|
+
const client = createAguiAgentChatClient({
|
|
162
|
+
baseUrl: "/api/agent", // this extension's own gateway, not cortenacore
|
|
163
|
+
getToken: () => jwt, // the signed-in USER's JWT, never a service token
|
|
164
|
+
agentId: "tasks", // = the AgentTemplate slug = the extension id
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
<AppShell
|
|
168
|
+
extension={extension}
|
|
169
|
+
user={user}
|
|
170
|
+
agentSlot={<AgentChatPopup extension={extension} client={client} />}
|
|
171
|
+
/>;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A collapsed pill opens to a panel (420 x 640) and the panel header expands it to
|
|
175
|
+
full screen. **Cmd/Ctrl+J** toggles the pill and the panel; **Escape** steps full
|
|
176
|
+
screen -> panel -> pill, except while a run is streaming, where the composer
|
|
177
|
+
takes Escape to mean "stop the run". The chat is hidden rather than unmounted
|
|
178
|
+
while collapsed, so a run that started before the user collapsed it keeps
|
|
179
|
+
streaming and the pill shows an unread dot.
|
|
180
|
+
|
|
181
|
+
**Sessions are per window.** The current session key lives in `sessionStorage`
|
|
182
|
+
under a name scoped to the extension id, not in `localStorage`: opening the
|
|
183
|
+
extension in a second window starts a second session rather than adopting the
|
|
184
|
+
first window's. Every session stays listed and is resumable from any window, and
|
|
185
|
+
two windows on one session may both send — cortenacore serialises the runs. A
|
|
186
|
+
new key is minted client-side as `agent:<id>:new-<Date.now()>-<random36>` and
|
|
187
|
+
used immediately, so an in-flight first message is never lost.
|
|
188
|
+
|
|
189
|
+
### Progress steps
|
|
190
|
+
|
|
191
|
+
Every AG-UI tool call becomes **one plain sentence** under the assistant turn,
|
|
192
|
+
streamed as it happens: a spinner while it runs, a tick and muted text when it
|
|
193
|
+
lands, a cross and the failure's `message` when it fails, and a neutral
|
|
194
|
+
"Stopped" when the user pressed Stop. A step still running after eight seconds
|
|
195
|
+
appends the time it has taken. The list is an `aria-live="polite"` region, so a
|
|
196
|
+
screen reader hears the run rather than silence — the seconds are `aria-hidden`
|
|
197
|
+
(they change every second) and a step is held out of the announcement until its
|
|
198
|
+
arguments have produced a real sentence.
|
|
199
|
+
|
|
200
|
+
This is not the tool strip at the foot of the panel. The strip is the record —
|
|
201
|
+
tool name, arguments, output, the `ui://` resource `renderToolCall` mounts an
|
|
202
|
+
MCP App from — and reading it means knowing what `broker_invoke` is. The steps
|
|
203
|
+
are for the person waiting.
|
|
204
|
+
|
|
205
|
+
The wording is `stepLabel(toolName, args, phase)` in `agent-chat/step-label.ts`,
|
|
206
|
+
a pure function with no network and no result:
|
|
207
|
+
|
|
208
|
+
| the call | the line |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `engram_search {intent}` | Looking for a way to \<intent\>, truncated at 80 characters |
|
|
211
|
+
| `engram_describe {id: "skill.…"}` | Reading skill: \<the call's `title`, or the id's last segment\> |
|
|
212
|
+
| `engram_describe {id}` | Checking how to \<id as words: `tasks.task.list` → "tasks task list"\> |
|
|
213
|
+
| `broker_invoke {operation, confirmed: true}` | Doing: \<operation\>, then Done: \<operation\> |
|
|
214
|
+
| `broker_invoke {operation}` | Fetching \<operation\> |
|
|
215
|
+
| anything else, or arguments not yet arrived | Working: \<tool name\> |
|
|
216
|
+
|
|
217
|
+
A step that finished on that last line is re-worded if its arguments arrive
|
|
218
|
+
late; a step that already has a real sentence keeps it.
|
|
219
|
+
|
|
220
|
+
Two of those need saying out loud. **An unconfirmed invoke is worded as a
|
|
221
|
+
read** — "Fetching …" — because the client cannot know read from write before
|
|
222
|
+
the reply comes back; only the catalogue knows, and the model only sets
|
|
223
|
+
`confirmed` once the user has said yes. And a result whose code is
|
|
224
|
+
`confirmation_required` is **not** drawn as a failure: the step keeps its tick
|
|
225
|
+
and is relabelled "Needs your confirmation: \<operation\>", which is what the
|
|
226
|
+
broker is actually saying.
|
|
227
|
+
|
|
228
|
+
Every string interpolated into a label — an intent, a title, an operation id, a
|
|
229
|
+
tool name, a server's message — is bounded at 80 characters (200 for a failure
|
|
230
|
+
message) and stripped of control and format characters first, so a catalogue
|
|
231
|
+
entry cannot fill the strip and a U+202E cannot reverse it.
|
|
232
|
+
|
|
233
|
+
#### How a step knows it failed
|
|
234
|
+
|
|
235
|
+
**`TOOL_CALL_RESULT.metadata`, and nothing else.** The contract cortenacore
|
|
236
|
+
emits, and the only thing this client reads:
|
|
237
|
+
|
|
238
|
+
```jsonc
|
|
239
|
+
metadata: {
|
|
240
|
+
toolName: "broker_invoke",
|
|
241
|
+
isError: true,
|
|
242
|
+
meta: { status: 409, code: "confirmation_required", message: "Set confirmed=true." }
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`meta` is optional; where it is absent, only `isError` can be trusted. An error
|
|
247
|
+
with `meta.code === "confirmation_required"` is the question above; any other
|
|
248
|
+
error is worded from `meta.message`, or from the first 200 characters of the
|
|
249
|
+
result text when there is none.
|
|
250
|
+
|
|
251
|
+
`content` is **never** parsed for an outcome. It is the result as the model sees
|
|
252
|
+
it — for a Cortena tool, `{ content: [{ type: "text", text }], details }` — and
|
|
253
|
+
looking for an `ok: false` in it is guessing at a shape nobody promised.
|
|
254
|
+
|
|
255
|
+
Steps live in `useAgentChat`'s `steps`, are cleared when the next run starts,
|
|
256
|
+
and are kept in `sessionStorage` beside the session key. That last part is not
|
|
257
|
+
decoration: `chat.history` has no steps in it — the server never sees them — so
|
|
258
|
+
a collapsed pop-up, a reload or a host remount mid-run would otherwise lose the
|
|
259
|
+
whole strip. `AgentSteps` is exported for a host that wants to draw them
|
|
260
|
+
somewhere else.
|
|
261
|
+
|
|
262
|
+
#### What is kept, and where
|
|
263
|
+
|
|
264
|
+
Steps are held in memory with their `args`, and **only a projection is
|
|
265
|
+
persisted**: `id`, `toolName`, `label`, `status`, `startedAt`, `endedAt`,
|
|
266
|
+
`errorMessage`. A call's arguments are the user's data and do not belong in a
|
|
267
|
+
store every script on the origin can read; the label is already derived from
|
|
268
|
+
them. The write is debounced by 250 ms and flushed on unmount, the list is
|
|
269
|
+
capped at 50 steps, and the slot is capped at 64 kB with the oldest dropped
|
|
270
|
+
first. A step restored with `status: "running"` comes back as "Interrupted." —
|
|
271
|
+
the run that owned it is gone, and no result will ever arrive.
|
|
272
|
+
|
|
273
|
+
### What the extension must proxy
|
|
274
|
+
|
|
275
|
+
The pop-up talks to one origin, `baseUrl`, and every call carries the user's
|
|
276
|
+
JWT. An extension's gateway proxies these to cortenacore; nothing here holds a
|
|
277
|
+
service credential.
|
|
278
|
+
|
|
279
|
+
| method + path | what it is |
|
|
280
|
+
| --- | --- |
|
|
281
|
+
| `POST {base}/agui/run` | the run stream. AG-UI `RunAgentInput` in, `text/event-stream` out. `threadId` is the session key verbatim, prefix included |
|
|
282
|
+
| `POST {base}/agui/abort` | `{ runId, threadId }` |
|
|
283
|
+
| `POST {base}/agui/approval` | `{ id, decision, threadId }` — `allow-once`, `allow-always`, `deny`. A separate endpoint on purpose: a decision sent as state on a second `/agui/run` opens a second concurrent stream |
|
|
284
|
+
| `GET {base}/api/core/sessions/list` | the session drawer, filtered client-side to `agent:<id>:` |
|
|
285
|
+
| `GET {base}/api/core/chat/history` | `?sessionKey=&limit=100&offset=` |
|
|
286
|
+
| `POST {base}/tools/invoke` | only for an MCP App frame the host wires in — the app's own `tools/call`, scoped to its `mcp__<server>__` prefix |
|
|
287
|
+
| `GET /api/design-tokens` | only for an MCP App frame: the token stylesheet the sandboxed document themes itself from |
|
|
288
|
+
|
|
289
|
+
The stream ends by **closing the connection** after `RUN_FINISHED` or
|
|
290
|
+
`RUN_ERROR`. There is no `data: [DONE]` sentinel; emitting one makes the AG-UI
|
|
291
|
+
client error the subject, because it parses every `data:` line as JSON.
|
|
292
|
+
`: keepalive` comment frames are fine.
|
|
293
|
+
|
|
294
|
+
**Two server toggles must be on**, in every environment that runs an extension.
|
|
295
|
+
Both default to false, and while either is off its paths answer `404` before
|
|
296
|
+
authentication, which reads as "the agent does not exist":
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
cortenacore.http.endpoints.agui.enabled = true
|
|
300
|
+
cortenacore.http.endpoints.controlPlane.enabled = true
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### The MCP App frame
|
|
304
|
+
|
|
305
|
+
Rendering an extension's own screen means a sandboxed iframe, a `srcdoc` CSP, a
|
|
306
|
+
JSON-RPC postMessage bridge and a `tools/invoke` proxy holding a credential —
|
|
307
|
+
host concerns, not component ones. `renderToolCall` is the seam: it is handed
|
|
308
|
+
each tool call and the `ui://` resource its result carries, and a node it
|
|
309
|
+
returns replaces the default card.
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
<AgentChatPopup
|
|
313
|
+
extension={extension}
|
|
314
|
+
client={client}
|
|
315
|
+
renderToolCall={({ entry, mcpAppUri }) =>
|
|
316
|
+
mcpAppUri ? <McpAppFrame uri={mcpAppUri} result={entry.output} /> : undefined
|
|
317
|
+
}
|
|
318
|
+
/>
|
|
319
|
+
```
|
|
320
|
+
|
|
89
321
|
## Charts in tests
|
|
90
322
|
|
|
91
323
|
Both chart engines size themselves from the parent box, and under jsdom every
|
|
@@ -175,7 +407,7 @@ from `@a2ui/*` is installed, so `vitest.config.ts` has no entry for it.
|
|
|
175
407
|
pnpm check # types
|
|
176
408
|
pnpm lint:design # no literals where a token exists; no dangling var()
|
|
177
409
|
pnpm build # dist/ via tsdown (ESM + d.ts); also runs on prepack
|
|
178
|
-
pnpm test # the
|
|
410
|
+
pnpm test # changed-only: the suites of this package your diff affects
|
|
179
411
|
pnpm test:bundle # bundle budgets alone (builds first, then real Vite apps)
|
|
180
412
|
pnpm bundle:probe # the size table, printed, without asserting anything
|
|
181
413
|
pnpm guide # three-theme guide on a local Vite server
|
|
@@ -205,7 +437,7 @@ guide/ Vite app: ?theme=light|dark|system frames, or all three
|
|
|
205
437
|
## Releasing
|
|
206
438
|
|
|
207
439
|
```bash
|
|
208
|
-
pnpm ui:check && pnpm
|
|
440
|
+
pnpm ui:check && FORCE_FULL=1 pnpm test:all # types, token lint, every suite
|
|
209
441
|
cd packages/ui && npm version minor && npm publish # prepack builds dist/
|
|
210
442
|
git push --follow-tags
|
|
211
443
|
```
|
package/dist/a2ui/views.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { Alert, AlertDescription, AlertIcon, AlertTitle } from "../components/alert.js";
|
|
5
|
-
import { Badge } from "../components/badge.js";
|
|
6
5
|
import { Button } from "../components/button.js";
|
|
6
|
+
import { Input } from "../components/input.js";
|
|
7
|
+
import { Badge } from "../components/badge.js";
|
|
7
8
|
import { ButtonLink } from "../components/button-link.js";
|
|
8
9
|
import { Card, CardContent } from "../components/card.js";
|
|
9
10
|
import { Checkbox } from "../components/checkbox.js";
|
|
10
|
-
import { Input } from "../components/input.js";
|
|
11
11
|
import { EmptyState } from "../components/empty-state.js";
|
|
12
12
|
import { Field, FieldDescription, FieldError, FieldLabel } from "../components/field.js";
|
|
13
13
|
import { Progress, ProgressLabel, ProgressValue } from "../components/progress.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
//#region src/agent-chat/a2ui-block.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A2UI blocks as the chat carries them.
|
|
5
|
+
*
|
|
6
|
+
* Ported from `cortena-shared/src/a2ui/block.ts` (Cortena monorepo, DESIGN-35).
|
|
7
|
+
* `cortena-shared` is not published, so the parts this package needs are copied
|
|
8
|
+
* rather than imported; keep the two in step when either changes.
|
|
9
|
+
*
|
|
10
|
+
* An agent draws UI by writing a ```a2ui fenced block, cortenacore strips it out
|
|
11
|
+
* of the prose, and it reaches the client on a channel of its own — a
|
|
12
|
+
* `CUSTOM cortena.a2ui` event on the AG-UI transport. Both the streaming bubble
|
|
13
|
+
* and the finished message carry the result here, so the bubble has exactly one
|
|
14
|
+
* thing to render and no markdown to parse.
|
|
15
|
+
*
|
|
16
|
+
* Nothing in this module knows about React: the `messages` array is handed to
|
|
17
|
+
* `<A2UIRenderer message={...} />` verbatim, and folding a surface, validating a
|
|
18
|
+
* component and drawing it are the renderer's job.
|
|
19
|
+
*/
|
|
20
|
+
/** One block, as the transport delivers it. */
|
|
21
|
+
export interface A2UIChatBlock {
|
|
22
|
+
/**
|
|
23
|
+
* Accumulation key: the surface id the payload names, or a key private to the
|
|
24
|
+
* block when it names none. Two blocks with the same key are two pushes to one
|
|
25
|
+
* surface and fold together.
|
|
26
|
+
*/
|
|
27
|
+
surfaceId: string;
|
|
28
|
+
/** A2UI messages, in arrival order. */
|
|
29
|
+
messages: unknown[];
|
|
30
|
+
/** Where the block came from: assistant text, or a `canvas` tool push. */
|
|
31
|
+
source?: "text" | "canvas";
|
|
32
|
+
/** Assistant message the block belonged to, when the transport knows it. */
|
|
33
|
+
messageId?: string;
|
|
34
|
+
/** Set when the fence never closed or its body did not parse. */
|
|
35
|
+
error?: string;
|
|
36
|
+
/** The raw fence body; only present with `error`. */
|
|
37
|
+
raw?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Read one block out of an unknown wire value; `null` when it is not one. */
|
|
40
|
+
export declare function readA2UIBlock(value: unknown): A2UIChatBlock | null;
|
|
41
|
+
/**
|
|
42
|
+
* Pull the `a2ui` content parts out of a chat event message.
|
|
43
|
+
*
|
|
44
|
+
* A client that does not know the type ignores it, which is what lets the same
|
|
45
|
+
* message shape carry blocks to a reader that cannot draw them.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractA2UIBlocks(message?: {
|
|
48
|
+
content?: Array<Record<string, unknown>>;
|
|
49
|
+
} | null): A2UIChatBlock[];
|
|
50
|
+
/**
|
|
51
|
+
* Fold blocks that share a surface id into one, concatenating their messages.
|
|
52
|
+
*
|
|
53
|
+
* An agent that streams a surface writes the shell, then the rows, then a
|
|
54
|
+
* correction — three fences naming one surface. The renderer folds a message
|
|
55
|
+
* list onto a surface, so handing it the concatenation renders the finished
|
|
56
|
+
* surface; handing it three separate blocks would draw the shell three times.
|
|
57
|
+
*/
|
|
58
|
+
export declare function foldA2UIBlocks(blocks: readonly A2UIChatBlock[]): A2UIChatBlock[];
|
|
59
|
+
//#endregion
|
|
60
|
+
//# sourceMappingURL=a2ui-block.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
//#region src/agent-chat/a2ui-block.ts
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
/** Read one block out of an unknown wire value; `null` when it is not one. */
|
|
7
|
+
function readA2UIBlock(value) {
|
|
8
|
+
if (!isRecord(value)) return null;
|
|
9
|
+
const messages = Array.isArray(value.messages) ? value.messages : [];
|
|
10
|
+
const surfaceId = typeof value.surfaceId === "string" && value.surfaceId ? value.surfaceId : "@default";
|
|
11
|
+
const error = typeof value.error === "string" && value.error ? value.error : void 0;
|
|
12
|
+
if (messages.length === 0 && !error) return null;
|
|
13
|
+
return {
|
|
14
|
+
surfaceId,
|
|
15
|
+
messages,
|
|
16
|
+
...value.source === "canvas" || value.source === "text" ? { source: value.source } : {},
|
|
17
|
+
...typeof value.messageId === "string" && value.messageId ? { messageId: value.messageId } : {},
|
|
18
|
+
...error ? { error } : {},
|
|
19
|
+
...typeof value.raw === "string" ? { raw: value.raw } : {}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pull the `a2ui` content parts out of a chat event message.
|
|
24
|
+
*
|
|
25
|
+
* A client that does not know the type ignores it, which is what lets the same
|
|
26
|
+
* message shape carry blocks to a reader that cannot draw them.
|
|
27
|
+
*/
|
|
28
|
+
function extractA2UIBlocks(message) {
|
|
29
|
+
const blocks = [];
|
|
30
|
+
for (const part of message?.content ?? []) {
|
|
31
|
+
if (!isRecord(part) || part.type !== "a2ui") continue;
|
|
32
|
+
const block = readA2UIBlock(part.a2ui ?? part);
|
|
33
|
+
if (block) blocks.push(block);
|
|
34
|
+
}
|
|
35
|
+
return foldA2UIBlocks(blocks);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fold blocks that share a surface id into one, concatenating their messages.
|
|
39
|
+
*
|
|
40
|
+
* An agent that streams a surface writes the shell, then the rows, then a
|
|
41
|
+
* correction — three fences naming one surface. The renderer folds a message
|
|
42
|
+
* list onto a surface, so handing it the concatenation renders the finished
|
|
43
|
+
* surface; handing it three separate blocks would draw the shell three times.
|
|
44
|
+
*/
|
|
45
|
+
function foldA2UIBlocks(blocks) {
|
|
46
|
+
const byId = /* @__PURE__ */ new Map();
|
|
47
|
+
const order = [];
|
|
48
|
+
for (const block of blocks) {
|
|
49
|
+
const existing = byId.get(block.surfaceId);
|
|
50
|
+
if (!existing) {
|
|
51
|
+
byId.set(block.surfaceId, {
|
|
52
|
+
...block,
|
|
53
|
+
messages: [...block.messages]
|
|
54
|
+
});
|
|
55
|
+
order.push(block.surfaceId);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
existing.messages.push(...block.messages);
|
|
59
|
+
if (block.error && !existing.error) {
|
|
60
|
+
existing.error = block.error;
|
|
61
|
+
if (block.raw !== void 0) existing.raw = block.raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return order.map((id) => byId.get(id)).filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { extractA2UIBlocks, foldA2UIBlocks, readA2UIBlock };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=a2ui-block.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a2ui-block.js","names":[],"sources":["../../src/agent-chat/a2ui-block.ts"],"sourcesContent":["/**\n * A2UI blocks as the chat carries them.\n *\n * Ported from `cortena-shared/src/a2ui/block.ts` (Cortena monorepo, DESIGN-35).\n * `cortena-shared` is not published, so the parts this package needs are copied\n * rather than imported; keep the two in step when either changes.\n *\n * An agent draws UI by writing a ```a2ui fenced block, cortenacore strips it out\n * of the prose, and it reaches the client on a channel of its own — a\n * `CUSTOM cortena.a2ui` event on the AG-UI transport. Both the streaming bubble\n * and the finished message carry the result here, so the bubble has exactly one\n * thing to render and no markdown to parse.\n *\n * Nothing in this module knows about React: the `messages` array is handed to\n * `<A2UIRenderer message={...} />` verbatim, and folding a surface, validating a\n * component and drawing it are the renderer's job.\n */\n\n/** One block, as the transport delivers it. */\nexport interface A2UIChatBlock {\n /**\n * Accumulation key: the surface id the payload names, or a key private to the\n * block when it names none. Two blocks with the same key are two pushes to one\n * surface and fold together.\n */\n surfaceId: string;\n /** A2UI messages, in arrival order. */\n messages: unknown[];\n /** Where the block came from: assistant text, or a `canvas` tool push. */\n source?: \"text\" | \"canvas\";\n /** Assistant message the block belonged to, when the transport knows it. */\n messageId?: string;\n /** Set when the fence never closed or its body did not parse. */\n error?: string;\n /** The raw fence body; only present with `error`. */\n raw?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Read one block out of an unknown wire value; `null` when it is not one. */\nexport function readA2UIBlock(value: unknown): A2UIChatBlock | null {\n if (!isRecord(value)) {\n return null;\n }\n const messages = Array.isArray(value.messages) ? value.messages : [];\n const surfaceId =\n typeof value.surfaceId === \"string\" && value.surfaceId ? value.surfaceId : \"@default\";\n const error = typeof value.error === \"string\" && value.error ? value.error : undefined;\n if (messages.length === 0 && !error) {\n return null;\n }\n return {\n surfaceId,\n messages,\n ...(value.source === \"canvas\" || value.source === \"text\" ? { source: value.source } : {}),\n ...(typeof value.messageId === \"string\" && value.messageId\n ? { messageId: value.messageId }\n : {}),\n ...(error ? { error } : {}),\n ...(typeof value.raw === \"string\" ? { raw: value.raw } : {}),\n };\n}\n\n/**\n * Pull the `a2ui` content parts out of a chat event message.\n *\n * A client that does not know the type ignores it, which is what lets the same\n * message shape carry blocks to a reader that cannot draw them.\n */\nexport function extractA2UIBlocks(\n message?: { content?: Array<Record<string, unknown>> } | null,\n): A2UIChatBlock[] {\n const blocks: A2UIChatBlock[] = [];\n for (const part of message?.content ?? []) {\n if (!isRecord(part) || part.type !== \"a2ui\") {\n continue;\n }\n const block = readA2UIBlock(part.a2ui ?? part);\n if (block) {\n blocks.push(block);\n }\n }\n return foldA2UIBlocks(blocks);\n}\n\n/**\n * Fold blocks that share a surface id into one, concatenating their messages.\n *\n * An agent that streams a surface writes the shell, then the rows, then a\n * correction — three fences naming one surface. The renderer folds a message\n * list onto a surface, so handing it the concatenation renders the finished\n * surface; handing it three separate blocks would draw the shell three times.\n */\nexport function foldA2UIBlocks(blocks: readonly A2UIChatBlock[]): A2UIChatBlock[] {\n const byId = new Map<string, A2UIChatBlock>();\n const order: string[] = [];\n for (const block of blocks) {\n const existing = byId.get(block.surfaceId);\n if (!existing) {\n byId.set(block.surfaceId, { ...block, messages: [...block.messages] });\n order.push(block.surfaceId);\n continue;\n }\n existing.messages.push(...block.messages);\n // The first failure is the one worth showing: later ones are usually the\n // same malformed payload arriving again.\n if (block.error && !existing.error) {\n existing.error = block.error;\n if (block.raw !== undefined) {\n existing.raw = block.raw;\n }\n }\n }\n return order.map((id) => byId.get(id)!).filter(Boolean);\n}\n"],"mappings":";;AAsCA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;AAGA,SAAgB,cAAc,OAAsC;CAClE,IAAI,CAAC,SAAS,KAAK,GACjB,OAAO;CAET,MAAM,WAAW,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,WAAW,CAAC;CACnE,MAAM,YACJ,OAAO,MAAM,cAAc,YAAY,MAAM,YAAY,MAAM,YAAY;CAC7E,MAAM,QAAQ,OAAO,MAAM,UAAU,YAAY,MAAM,QAAQ,MAAM,QAAQ,KAAA;CAC7E,IAAI,SAAS,WAAW,KAAK,CAAC,OAC5B,OAAO;CAET,OAAO;EACL;EACA;EACA,GAAI,MAAM,WAAW,YAAY,MAAM,WAAW,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;EACvF,GAAI,OAAO,MAAM,cAAc,YAAY,MAAM,YAC7C,EAAE,WAAW,MAAM,UAAU,IAC7B,CAAC;EACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,GAAI,OAAO,MAAM,QAAQ,WAAW,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;CAC5D;AACF;;;;;;;AAQA,SAAgB,kBACd,SACiB;CACjB,MAAM,SAA0B,CAAC;CACjC,KAAK,MAAM,QAAQ,SAAS,WAAW,CAAC,GAAG;EACzC,IAAI,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS,QACnC;EAEF,MAAM,QAAQ,cAAc,KAAK,QAAQ,IAAI;EAC7C,IAAI,OACF,OAAO,KAAK,KAAK;CAErB;CACA,OAAO,eAAe,MAAM;AAC9B;;;;;;;;;AAUA,SAAgB,eAAe,QAAmD;CAChF,MAAM,uBAAO,IAAI,IAA2B;CAC5C,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,KAAK,IAAI,MAAM,SAAS;EACzC,IAAI,CAAC,UAAU;GACb,KAAK,IAAI,MAAM,WAAW;IAAE,GAAG;IAAO,UAAU,CAAC,GAAG,MAAM,QAAQ;GAAE,CAAC;GACrE,MAAM,KAAK,MAAM,SAAS;GAC1B;EACF;EACA,SAAS,SAAS,KAAK,GAAG,MAAM,QAAQ;EAGxC,IAAI,MAAM,SAAS,CAAC,SAAS,OAAO;GAClC,SAAS,QAAQ,MAAM;GACvB,IAAI,MAAM,QAAQ,KAAA,GAChB,SAAS,MAAM,MAAM;EAEzB;CACF;CACA,OAAO,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE,CAAE,CAAC,CAAC,OAAO,OAAO;AACxD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { AgentChatClient } from "./types.js";
|
|
3
|
+
//#region src/agent-chat/agui-client.d.ts
|
|
4
|
+
export interface CreateAguiAgentChatClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Origin the AG-UI and control-plane paths hang off, no trailing slash.
|
|
7
|
+
* For an extension this is its own gateway, which proxies to cortenacore.
|
|
8
|
+
*/
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
/** The signed-in USER's JWT. Read on every request, never captured once. */
|
|
11
|
+
getToken: () => string;
|
|
12
|
+
/** The agent template slug — the extension id. Sessions are `agent:<id>:…`. */
|
|
13
|
+
agentId: string;
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
/** Frontend tools advertised on every run; AG-UI requires a description. */
|
|
16
|
+
tools?: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
parameters?: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
/** Backoff before re-attaching a dropped stream. */
|
|
22
|
+
reconnectDelays?: readonly number[];
|
|
23
|
+
maxReconnectAttempts?: number;
|
|
24
|
+
sleep?: (ms: number) => Promise<void>;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* An `AgentChatClient` that streams over AG-UI and reads sessions and history
|
|
29
|
+
* over the REST control plane.
|
|
30
|
+
*
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const client = React.useMemo(
|
|
33
|
+
* () => createAguiAgentChatClient({ baseUrl: "/api/agent", getToken: () => jwt, agentId: "tasks" }),
|
|
34
|
+
* [jwt],
|
|
35
|
+
* );
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function createAguiAgentChatClient(options: CreateAguiAgentChatClientOptions): AgentChatClient;
|
|
39
|
+
//#endregion
|
|
40
|
+
//# sourceMappingURL=agui-client.d.ts.map
|