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
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { AgentChatBridge } from "./bridge.js";
|
|
3
|
+
//#region src/agent-chat/agui-client.ts
|
|
4
|
+
const DEFAULT_RECONNECT_DELAYS = [
|
|
5
|
+
500,
|
|
6
|
+
1e3,
|
|
7
|
+
2e3,
|
|
8
|
+
5e3,
|
|
9
|
+
1e4
|
|
10
|
+
];
|
|
11
|
+
const DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
|
|
12
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
/**
|
|
14
|
+
* What a relative `baseUrl` resolves against.
|
|
15
|
+
*
|
|
16
|
+
* The browser's own origin, and a placeholder under Node — where this module
|
|
17
|
+
* is exercised by tests with a `fetchImpl` stub and there is no origin to
|
|
18
|
+
* speak of. The placeholder never reaches the network: the stub sees the
|
|
19
|
+
* resolved string, and `/api/core/...` is what it asserts on.
|
|
20
|
+
*/
|
|
21
|
+
function requestBase() {
|
|
22
|
+
return typeof location === "undefined" ? "http://cortena.invalid" : location.origin;
|
|
23
|
+
}
|
|
24
|
+
const MISSING_AGUI_PEER = "cortena-ui/agent-chat: the AG-UI transport needs the optional peer \"@ag-ui/client\". Install it (pnpm add @ag-ui/client), or pass your own AgentChatClient to AgentChat.";
|
|
25
|
+
/**
|
|
26
|
+
* `@ag-ui/client`, on demand.
|
|
27
|
+
*
|
|
28
|
+
* The specifier is a literal so a bundler still sees the dependency and puts
|
|
29
|
+
* it in a chunk of its own — `test/bundle-probe.test.mjs` measures that the
|
|
30
|
+
* agent-chat entry carries it. What changes is *when* it is evaluated, and
|
|
31
|
+
* what happens when it is not there at all.
|
|
32
|
+
*/
|
|
33
|
+
async function loadHttpAgent() {
|
|
34
|
+
try {
|
|
35
|
+
return (await import("@ag-ui/client")).HttpAgent;
|
|
36
|
+
} catch (cause) {
|
|
37
|
+
throw new Error(MISSING_AGUI_PEER, { cause });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var AguiAgentChatClient = class {
|
|
41
|
+
agentId;
|
|
42
|
+
opts;
|
|
43
|
+
delays;
|
|
44
|
+
maxAttempts;
|
|
45
|
+
sleep;
|
|
46
|
+
chatSinks = /* @__PURE__ */ new Set();
|
|
47
|
+
toolSinks = /* @__PURE__ */ new Set();
|
|
48
|
+
approvalSinks = /* @__PURE__ */ new Set();
|
|
49
|
+
active = null;
|
|
50
|
+
/** The optional peer, requested when the client is created and awaited per run. */
|
|
51
|
+
httpAgent;
|
|
52
|
+
constructor(opts) {
|
|
53
|
+
this.opts = opts;
|
|
54
|
+
this.agentId = opts.agentId;
|
|
55
|
+
this.delays = opts.reconnectDelays ?? DEFAULT_RECONNECT_DELAYS;
|
|
56
|
+
this.maxAttempts = opts.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
57
|
+
this.sleep = opts.sleep ?? defaultSleep;
|
|
58
|
+
this.httpAgent = loadHttpAgent();
|
|
59
|
+
this.httpAgent.catch(() => {});
|
|
60
|
+
}
|
|
61
|
+
onChat(sink) {
|
|
62
|
+
this.chatSinks.add(sink);
|
|
63
|
+
return () => this.chatSinks.delete(sink);
|
|
64
|
+
}
|
|
65
|
+
onTool(sink) {
|
|
66
|
+
this.toolSinks.add(sink);
|
|
67
|
+
return () => this.toolSinks.delete(sink);
|
|
68
|
+
}
|
|
69
|
+
onApproval(sink) {
|
|
70
|
+
this.approvalSinks.add(sink);
|
|
71
|
+
return () => this.approvalSinks.delete(sink);
|
|
72
|
+
}
|
|
73
|
+
get runUrl() {
|
|
74
|
+
return `${this.opts.baseUrl}/agui/run`;
|
|
75
|
+
}
|
|
76
|
+
get abortUrl() {
|
|
77
|
+
return `${this.opts.baseUrl}/agui/abort`;
|
|
78
|
+
}
|
|
79
|
+
get approvalUrl() {
|
|
80
|
+
return `${this.opts.baseUrl}/agui/approval`;
|
|
81
|
+
}
|
|
82
|
+
headers() {
|
|
83
|
+
return {
|
|
84
|
+
authorization: `Bearer ${this.opts.getToken()}`,
|
|
85
|
+
"content-type": "application/json"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
get fetch() {
|
|
89
|
+
return this.opts.fetchImpl ?? globalThis.fetch;
|
|
90
|
+
}
|
|
91
|
+
/** `sessions.list` and `chat.history` over the REST control plane. */
|
|
92
|
+
async request(method, params) {
|
|
93
|
+
const path = `${this.opts.baseUrl}/api/core/${method.split(".").join("/")}`;
|
|
94
|
+
const readOnly = method === "sessions.list" || method === "chat.history";
|
|
95
|
+
const url = new URL(path, requestBase());
|
|
96
|
+
if (readOnly) {
|
|
97
|
+
for (const [key, value] of Object.entries(params)) if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
98
|
+
}
|
|
99
|
+
const response = await this.fetch(url.toString(), {
|
|
100
|
+
method: readOnly ? "GET" : "POST",
|
|
101
|
+
headers: this.headers(),
|
|
102
|
+
...readOnly ? {} : { body: JSON.stringify(params) }
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) throw new Error(await readError(response, method));
|
|
105
|
+
return await response.json();
|
|
106
|
+
}
|
|
107
|
+
async send(input) {
|
|
108
|
+
this.startRun(input);
|
|
109
|
+
}
|
|
110
|
+
async startRun(input) {
|
|
111
|
+
const bridge = new AgentChatBridge({
|
|
112
|
+
runId: input.runId,
|
|
113
|
+
sessionKey: input.sessionKey,
|
|
114
|
+
chat: (event) => this.emit(this.chatSinks, event),
|
|
115
|
+
tool: (event) => this.emit(this.toolSinks, event),
|
|
116
|
+
approval: (event) => this.emit(this.approvalSinks, event),
|
|
117
|
+
...this.opts.now ? { now: this.opts.now } : {}
|
|
118
|
+
});
|
|
119
|
+
const forwarded = {
|
|
120
|
+
...input.thinking ? { thinking: input.thinking } : {},
|
|
121
|
+
...input.attachments?.length ? { attachments: input.attachments } : {},
|
|
122
|
+
...input.model ? { model: input.model } : {},
|
|
123
|
+
...input.a2uiAction ? { a2uiAction: input.a2uiAction } : {},
|
|
124
|
+
...input.mcpAppAction ? { mcpAppAction: input.mcpAppAction } : {}
|
|
125
|
+
};
|
|
126
|
+
let Agent;
|
|
127
|
+
try {
|
|
128
|
+
Agent = await this.httpAgent;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
bridge.fail(err instanceof Error ? err.message : MISSING_AGUI_PEER);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
let attempt = 0;
|
|
134
|
+
let resume = false;
|
|
135
|
+
for (;;) {
|
|
136
|
+
const runId = resume ? `${input.runId}:resume-${attempt}` : input.runId;
|
|
137
|
+
const agent = new Agent({
|
|
138
|
+
url: this.runUrl,
|
|
139
|
+
headers: this.headers(),
|
|
140
|
+
threadId: input.sessionKey,
|
|
141
|
+
initialMessages: resume ? [] : [{
|
|
142
|
+
id: input.runId,
|
|
143
|
+
role: "user",
|
|
144
|
+
content: input.message
|
|
145
|
+
}],
|
|
146
|
+
...this.opts.fetchImpl ? { fetch: this.opts.fetchImpl } : {}
|
|
147
|
+
});
|
|
148
|
+
this.active = {
|
|
149
|
+
runId: input.runId,
|
|
150
|
+
sessionKey: input.sessionKey,
|
|
151
|
+
bridge,
|
|
152
|
+
agent,
|
|
153
|
+
aborted: this.active?.aborted === true && this.active.runId === input.runId
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
await agent.runAgent({
|
|
157
|
+
runId,
|
|
158
|
+
...this.opts.tools?.length ? { tools: this.opts.tools } : {},
|
|
159
|
+
forwardedProps: {
|
|
160
|
+
...forwarded,
|
|
161
|
+
...resume ? { resumeRunId: input.runId } : {}
|
|
162
|
+
}
|
|
163
|
+
}, { onEvent: ({ event }) => bridge.handleEvent(event) });
|
|
164
|
+
return;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (bridge.finished || this.active?.aborted) return;
|
|
167
|
+
attempt += 1;
|
|
168
|
+
if (attempt > this.maxAttempts) {
|
|
169
|
+
bridge.fail(err instanceof Error ? err.message : "The agent stream failed");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await this.sleep(this.delays[Math.min(attempt - 1, this.delays.length - 1)] ?? 1e3);
|
|
173
|
+
if (bridge.finished || this.active?.aborted) return;
|
|
174
|
+
resume = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async abort(params) {
|
|
179
|
+
const active = this.active;
|
|
180
|
+
if (active) {
|
|
181
|
+
active.aborted = true;
|
|
182
|
+
try {
|
|
183
|
+
active.agent.abortRun();
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
const runId = params?.runId ?? active?.runId;
|
|
187
|
+
const threadId = params?.sessionKey ?? active?.sessionKey;
|
|
188
|
+
if (!runId || !threadId) return;
|
|
189
|
+
try {
|
|
190
|
+
await this.fetch(this.abortUrl, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: this.headers(),
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
runId,
|
|
195
|
+
threadId
|
|
196
|
+
})
|
|
197
|
+
});
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Answer one approval, on the thread and run it arrived on.
|
|
202
|
+
*
|
|
203
|
+
* `this.active` is deliberately not consulted. An approval pauses a run, and
|
|
204
|
+
* a user is free to open another session from the drawer or start another
|
|
205
|
+
* turn while it waits — at which point `active.sessionKey` is a different
|
|
206
|
+
* conversation and "allow" would be sent against it. The request carried its
|
|
207
|
+
* own `{ runId, sessionKey }`; those are what go on the wire.
|
|
208
|
+
*/
|
|
209
|
+
async resolveApproval(id, decision, context) {
|
|
210
|
+
const response = await this.fetch(this.approvalUrl, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: this.headers(),
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
id,
|
|
215
|
+
decision,
|
|
216
|
+
threadId: context.sessionKey,
|
|
217
|
+
runId: context.runId
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
if (!response.ok) throw new Error(await readError(response, "approval"));
|
|
221
|
+
}
|
|
222
|
+
emit(sinks, event) {
|
|
223
|
+
for (const sink of [...sinks]) sink(event);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* An `AgentChatClient` that streams over AG-UI and reads sessions and history
|
|
228
|
+
* over the REST control plane.
|
|
229
|
+
*
|
|
230
|
+
* ```tsx
|
|
231
|
+
* const client = React.useMemo(
|
|
232
|
+
* () => createAguiAgentChatClient({ baseUrl: "/api/agent", getToken: () => jwt, agentId: "tasks" }),
|
|
233
|
+
* [jwt],
|
|
234
|
+
* );
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
function createAguiAgentChatClient(options) {
|
|
238
|
+
return new AguiAgentChatClient(options);
|
|
239
|
+
}
|
|
240
|
+
async function readError(response, method) {
|
|
241
|
+
try {
|
|
242
|
+
const body = await response.json();
|
|
243
|
+
if (body?.error?.message) return body.error.message;
|
|
244
|
+
} catch {}
|
|
245
|
+
if (response.status === 404) return `${method} is unavailable (404). Check that the AG-UI and control-plane endpoints are enabled.`;
|
|
246
|
+
return `${method} failed (${response.status})`;
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
export { MISSING_AGUI_PEER, createAguiAgentChatClient };
|
|
250
|
+
|
|
251
|
+
//# sourceMappingURL=agui-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-client.js","names":[],"sources":["../../src/agent-chat/agui-client.ts"],"sourcesContent":["/**\n * `AgentChatClient` over AG-UI.\n *\n * Ported from `cortena-shared/src/agui/client.ts` (Cortena monorepo,\n * DESIGN-37). `cortena-shared` is not published, so what this package needs is\n * copied rather than imported; keep the two in step.\n *\n * **This is the only module in cortena-ui that imports `@ag-ui/client`**, and\n * `cortena-ui/agent-chat` is the only entry that reaches it. `@ag-ui/client`\n * brings rxjs, zod 3, uuid and protobuf; `test/bundle-probe.test.mjs` asserts\n * none of it is reachable from `cortena-ui` or `cortena-ui/core`.\n *\n * It is an OPTIONAL peer, and the import is therefore **lazy**: a static\n * `import { HttpAgent }` made the whole `cortena-ui/agent-chat` entry fail to\n * load for a consumer that had not installed it, so an extension on another\n * transport could not draw `AgentChatPopup` at all — a package it never asked\n * for decided whether its chat rendered. Loading it inside the factory keeps\n * the entry resolvable, and moves the failure to the one operation that\n * genuinely needs the peer, with a message that says what to install.\n *\n * What an extension must expose for this to work is in README.md, \"The agent\n * pop-up\": `POST /agui/run`, `/agui/abort`, `/agui/approval` and the two\n * control-plane reads, all proxied through the extension's own gateway with the\n * signed-in user's JWT — never a service token.\n *\n * Two things about the wire that are easy to get wrong:\n *\n * - The stream ends by **closing the connection** after `RUN_FINISHED` or\n * `RUN_ERROR`. There is no `data: [DONE]` sentinel; emitting one makes the\n * AG-UI client error the subject, because it `JSON.parse`s every `data:` line.\n * - Approvals go to their own endpoint. Sending a decision as `state` on a\n * second `/agui/run` opens a second concurrent stream, and two mappers then\n * write two sets of message ids into one bridge, which resets its text buffer\n * whenever the message id changes.\n */\n\nimport type { HttpAgent } from \"@ag-ui/client\";\nimport { AgentChatBridge } from \"./bridge\";\nimport type {\n AgentApprovalContext,\n AgentApprovalDecision,\n AgentApprovalEvent,\n AgentChatClient,\n AgentChatEvent,\n AgentChatSendInput,\n AgentToolEvent,\n} from \"./types\";\n\nexport interface CreateAguiAgentChatClientOptions {\n /**\n * Origin the AG-UI and control-plane paths hang off, no trailing slash.\n * For an extension this is its own gateway, which proxies to cortenacore.\n */\n baseUrl: string;\n /** The signed-in USER's JWT. Read on every request, never captured once. */\n getToken: () => string;\n /** The agent template slug — the extension id. Sessions are `agent:<id>:…`. */\n agentId: string;\n fetchImpl?: typeof fetch;\n /** Frontend tools advertised on every run; AG-UI requires a description. */\n tools?: Array<{ name: string; description: string; parameters?: unknown }>;\n /** Backoff before re-attaching a dropped stream. */\n reconnectDelays?: readonly number[];\n maxReconnectAttempts?: number;\n sleep?: (ms: number) => Promise<void>;\n now?: () => number;\n}\n\nconst DEFAULT_RECONNECT_DELAYS: readonly number[] = [500, 1_000, 2_000, 5_000, 10_000];\nconst DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;\n\nconst defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * What a relative `baseUrl` resolves against.\n *\n * The browser's own origin, and a placeholder under Node — where this module\n * is exercised by tests with a `fetchImpl` stub and there is no origin to\n * speak of. The placeholder never reaches the network: the stub sees the\n * resolved string, and `/api/core/...` is what it asserts on.\n */\nfunction requestBase(): string {\n return typeof location === \"undefined\" ? \"http://cortena.invalid\" : location.origin;\n}\n\n/** What `@ag-ui/client` exports and this module constructs. */\ntype HttpAgentConstructor = typeof import(\"@ag-ui/client\").HttpAgent;\n\nexport const MISSING_AGUI_PEER =\n 'cortena-ui/agent-chat: the AG-UI transport needs the optional peer \"@ag-ui/client\". ' +\n \"Install it (pnpm add @ag-ui/client), or pass your own AgentChatClient to AgentChat.\";\n\n/**\n * `@ag-ui/client`, on demand.\n *\n * The specifier is a literal so a bundler still sees the dependency and puts\n * it in a chunk of its own — `test/bundle-probe.test.mjs` measures that the\n * agent-chat entry carries it. What changes is *when* it is evaluated, and\n * what happens when it is not there at all.\n */\nasync function loadHttpAgent(): Promise<HttpAgentConstructor> {\n try {\n return (await import(\"@ag-ui/client\")).HttpAgent;\n } catch (cause) {\n throw new Error(MISSING_AGUI_PEER, { cause });\n }\n}\n\ntype Sink<T> = (event: T) => void;\n\ninterface ActiveRun {\n runId: string;\n sessionKey: string;\n bridge: AgentChatBridge;\n agent: HttpAgent;\n aborted: boolean;\n}\n\nclass AguiAgentChatClient implements AgentChatClient {\n readonly agentId: string;\n\n private readonly opts: CreateAguiAgentChatClientOptions;\n private readonly delays: readonly number[];\n private readonly maxAttempts: number;\n private readonly sleep: (ms: number) => Promise<void>;\n private readonly chatSinks = new Set<Sink<AgentChatEvent>>();\n private readonly toolSinks = new Set<Sink<AgentToolEvent>>();\n private readonly approvalSinks = new Set<Sink<AgentApprovalEvent>>();\n private active: ActiveRun | null = null;\n /** The optional peer, requested when the client is created and awaited per run. */\n private readonly httpAgent: Promise<HttpAgentConstructor>;\n\n constructor(opts: CreateAguiAgentChatClientOptions) {\n this.opts = opts;\n this.agentId = opts.agentId;\n this.delays = opts.reconnectDelays ?? DEFAULT_RECONNECT_DELAYS;\n this.maxAttempts = opts.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n this.sleep = opts.sleep ?? defaultSleep;\n // Started here so a missing peer is discovered when the client is made\n // rather than mid-stream. The rejection is answered immediately: a client\n // created and never sent to must not raise an unhandled rejection, and\n // `startRun` awaits the same promise where the user can be told.\n this.httpAgent = loadHttpAgent();\n this.httpAgent.catch(() => {});\n }\n\n onChat(sink: Sink<AgentChatEvent>): () => void {\n this.chatSinks.add(sink);\n return () => this.chatSinks.delete(sink);\n }\n\n onTool(sink: Sink<AgentToolEvent>): () => void {\n this.toolSinks.add(sink);\n return () => this.toolSinks.delete(sink);\n }\n\n onApproval(sink: Sink<AgentApprovalEvent>): () => void {\n this.approvalSinks.add(sink);\n return () => this.approvalSinks.delete(sink);\n }\n\n get runUrl(): string {\n return `${this.opts.baseUrl}/agui/run`;\n }\n\n get abortUrl(): string {\n return `${this.opts.baseUrl}/agui/abort`;\n }\n\n get approvalUrl(): string {\n return `${this.opts.baseUrl}/agui/approval`;\n }\n\n private headers(): Record<string, string> {\n return {\n authorization: `Bearer ${this.opts.getToken()}`,\n \"content-type\": \"application/json\",\n };\n }\n\n private get fetch(): typeof fetch {\n return this.opts.fetchImpl ?? globalThis.fetch;\n }\n\n /** `sessions.list` and `chat.history` over the REST control plane. */\n async request<T = unknown>(method: string, params: Record<string, unknown>): Promise<T> {\n const path = `${this.opts.baseUrl}/api/core/${method.split(\".\").join(\"/\")}`;\n const readOnly = method === \"sessions.list\" || method === \"chat.history\";\n // `new URL(path)` with no base throws on a relative `baseUrl`, and the one\n // in this module's own example is `\"/api/agent\"` — the shape every\n // extension uses, since the gateway that proxies these calls is the app's\n // own origin. `sessions.list` therefore threw before it was ever sent, and\n // the chat opened on \"The agent could not be reached\".\n const url = new URL(path, requestBase());\n if (readOnly) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n const response = await this.fetch(url.toString(), {\n method: readOnly ? \"GET\" : \"POST\",\n headers: this.headers(),\n ...(readOnly ? {} : { body: JSON.stringify(params) }),\n });\n if (!response.ok) {\n throw new Error(await readError(response, method));\n }\n return (await response.json()) as T;\n }\n\n async send(input: AgentChatSendInput): Promise<void> {\n // Fire and forget: the caller only awaits the acceptance. Events follow on\n // the stream, exactly as they do when the server acknowledges a send.\n void this.startRun(input);\n }\n\n private async startRun(input: AgentChatSendInput): Promise<void> {\n const bridge = new AgentChatBridge({\n runId: input.runId,\n sessionKey: input.sessionKey,\n chat: (event) => this.emit(this.chatSinks, event),\n tool: (event) => this.emit(this.toolSinks, event),\n approval: (event) => this.emit(this.approvalSinks, event),\n ...(this.opts.now ? { now: this.opts.now } : {}),\n });\n\n const forwarded: Record<string, unknown> = {\n ...(input.thinking ? { thinking: input.thinking } : {}),\n ...(input.attachments?.length ? { attachments: input.attachments } : {}),\n ...(input.model ? { model: input.model } : {}),\n // A click inside a rendered A2UI surface or MCP App. Structured rather\n // than typed prose: cortenacore turns it into the documented envelope.\n ...(input.a2uiAction ? { a2uiAction: input.a2uiAction } : {}),\n ...(input.mcpAppAction ? { mcpAppAction: input.mcpAppAction } : {}),\n };\n\n let Agent: HttpAgentConstructor;\n try {\n Agent = await this.httpAgent;\n } catch (err) {\n // The one operation that needs the peer, and the only place it can be\n // reported: in the chat, as a failed run, rather than as a rejected\n // promise nobody is holding.\n bridge.fail(err instanceof Error ? err.message : MISSING_AGUI_PEER);\n return;\n }\n\n let attempt = 0;\n let resume = false;\n for (;;) {\n const runId = resume ? `${input.runId}:resume-${attempt}` : input.runId;\n const agent = new Agent({\n url: this.runUrl,\n headers: this.headers(),\n threadId: input.sessionKey,\n // A re-attach carries no new user message; the server re-streams the\n // run named by forwardedProps.resumeRunId instead of starting one.\n initialMessages: resume\n ? []\n : [{ id: input.runId, role: \"user\", content: input.message }],\n ...(this.opts.fetchImpl ? { fetch: this.opts.fetchImpl } : {}),\n });\n this.active = {\n runId: input.runId,\n sessionKey: input.sessionKey,\n bridge,\n agent,\n aborted: this.active?.aborted === true && this.active.runId === input.runId,\n };\n try {\n await agent.runAgent(\n {\n runId,\n ...(this.opts.tools?.length ? { tools: this.opts.tools } : {}),\n forwardedProps: {\n ...forwarded,\n ...(resume ? { resumeRunId: input.runId } : {}),\n },\n },\n { onEvent: ({ event }) => bridge.handleEvent(event as never) },\n );\n return;\n } catch (err) {\n if (bridge.finished || this.active?.aborted) {\n return;\n }\n attempt += 1;\n if (attempt > this.maxAttempts) {\n bridge.fail(err instanceof Error ? err.message : \"The agent stream failed\");\n return;\n }\n await this.sleep(this.delays[Math.min(attempt - 1, this.delays.length - 1)] ?? 1_000);\n // The backoff is up to ten seconds long, and everything can change in\n // it: the user presses Stop, or a late RUN_FINISHED lands on the\n // bridge. Checking only before the sleep re-attached to a run that had\n // already ended — a second stream, a second set of message ids into\n // one bridge, and an abort the user watched do nothing.\n if (bridge.finished || this.active?.aborted) {\n return;\n }\n resume = true;\n }\n }\n }\n\n async abort(params?: { runId?: string; sessionKey?: string }): Promise<void> {\n const active = this.active;\n if (active) {\n active.aborted = true;\n try {\n active.agent.abortRun();\n } catch {\n // Already finished; the POST below is still worth making.\n }\n }\n const runId = params?.runId ?? active?.runId;\n const threadId = params?.sessionKey ?? active?.sessionKey;\n if (!runId || !threadId) {\n return;\n }\n try {\n await this.fetch(this.abortUrl, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify({ runId, threadId }),\n });\n } catch {\n // Best-effort: dropping the stream aborts the run server-side anyway.\n }\n }\n\n /**\n * Answer one approval, on the thread and run it arrived on.\n *\n * `this.active` is deliberately not consulted. An approval pauses a run, and\n * a user is free to open another session from the drawer or start another\n * turn while it waits — at which point `active.sessionKey` is a different\n * conversation and \"allow\" would be sent against it. The request carried its\n * own `{ runId, sessionKey }`; those are what go on the wire.\n */\n async resolveApproval(\n id: string,\n decision: AgentApprovalDecision,\n context: AgentApprovalContext,\n ): Promise<void> {\n const response = await this.fetch(this.approvalUrl, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify({\n id,\n decision,\n threadId: context.sessionKey,\n runId: context.runId,\n }),\n });\n // A rejected decision has to reach the user. Swallowing it left the card\n // gone, the run paused server-side and nothing on screen saying so — the\n // user believes they answered and the agent believes nobody did.\n if (!response.ok) {\n throw new Error(await readError(response, \"approval\"));\n }\n }\n\n private emit<T>(sinks: Set<Sink<T>>, event: T): void {\n for (const sink of [...sinks]) {\n sink(event);\n }\n }\n}\n\n/**\n * An `AgentChatClient` that streams over AG-UI and reads sessions and history\n * over the REST control plane.\n *\n * ```tsx\n * const client = React.useMemo(\n * () => createAguiAgentChatClient({ baseUrl: \"/api/agent\", getToken: () => jwt, agentId: \"tasks\" }),\n * [jwt],\n * );\n * ```\n */\nexport function createAguiAgentChatClient(\n options: CreateAguiAgentChatClientOptions,\n): AgentChatClient {\n return new AguiAgentChatClient(options);\n}\n\nasync function readError(response: Response, method: string): Promise<string> {\n try {\n const body = (await response.json()) as { error?: { message?: string } };\n if (body?.error?.message) {\n return body.error.message;\n }\n } catch {\n // Not JSON. The status is all there is.\n }\n if (response.status === 404) {\n // The single most common misconfiguration: the control-plane endpoints are\n // off, so every path under /api/core answers 404 before authentication.\n return `${method} is unavailable (404). Check that the AG-UI and control-plane endpoints are enabled.`;\n }\n return `${method} failed (${response.status})`;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { AgentApprovalEvent, AgentChatEvent, AgentToolEvent } from "./types.js";
|
|
3
|
+
//#region src/agent-chat/bridge.d.ts
|
|
4
|
+
/** The subset of an AG-UI event this bridge reads. */
|
|
5
|
+
export interface AguiIncomingEvent {
|
|
6
|
+
type: string;
|
|
7
|
+
messageId?: string;
|
|
8
|
+
delta?: string;
|
|
9
|
+
toolCallId?: string;
|
|
10
|
+
toolCallName?: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
value?: unknown;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
snapshot?: unknown;
|
|
17
|
+
outcome?: {
|
|
18
|
+
type?: string;
|
|
19
|
+
interrupts?: Array<Record<string, unknown>>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** `CUSTOM` event names cortenacore emits that this bridge acts on. */
|
|
23
|
+
export declare const AGUI_CUSTOM_NAMES: {
|
|
24
|
+
readonly CHAT_ABORTED: "cortena.chat.aborted";
|
|
25
|
+
readonly TOOL_PROGRESS: "cortena.tool.progress";
|
|
26
|
+
readonly APPROVAL_REQUEST: "cortena.approval.request";
|
|
27
|
+
readonly APPROVAL_RESOLVED: "cortena.approval.resolved";
|
|
28
|
+
readonly A2UI: "cortena.a2ui";
|
|
29
|
+
};
|
|
30
|
+
export interface AgentChatBridgeOptions {
|
|
31
|
+
runId: string;
|
|
32
|
+
sessionKey: string;
|
|
33
|
+
chat: (event: AgentChatEvent) => void;
|
|
34
|
+
tool?: (event: AgentToolEvent) => void;
|
|
35
|
+
approval?: (event: AgentApprovalEvent) => void;
|
|
36
|
+
now?: () => number;
|
|
37
|
+
}
|
|
38
|
+
export declare class AgentChatBridge {
|
|
39
|
+
readonly runId: string;
|
|
40
|
+
readonly sessionKey: string;
|
|
41
|
+
private readonly chat;
|
|
42
|
+
private readonly tool;
|
|
43
|
+
private readonly approval;
|
|
44
|
+
private readonly now;
|
|
45
|
+
private seq;
|
|
46
|
+
/** Text of the assistant message currently streaming. */
|
|
47
|
+
private text;
|
|
48
|
+
private textMessageId;
|
|
49
|
+
private reasoning;
|
|
50
|
+
/**
|
|
51
|
+
* The reasoning accumulated before each assistant message began, by message
|
|
52
|
+
* id, and the whole of the re-attach defence for the chain of thought.
|
|
53
|
+
*
|
|
54
|
+
* A re-attach re-streams the run from the top, so every REASONING delta
|
|
55
|
+
* arrives a second time. Restoring this snapshot when a message id starts
|
|
56
|
+
* again rewinds the buffer to exactly the point the first pass reached,
|
|
57
|
+
* whichever side of `TEXT_MESSAGE_START` the reasoning arrived on.
|
|
58
|
+
*/
|
|
59
|
+
private readonly reasoningAtStart;
|
|
60
|
+
/**
|
|
61
|
+
* A2UI blocks seen this run. The chat treats the content array as a
|
|
62
|
+
* snapshot, so every emission carries all of them — the same rule the text
|
|
63
|
+
* follows, and what makes a re-attach land in the same place.
|
|
64
|
+
*/
|
|
65
|
+
private a2ui;
|
|
66
|
+
/** Accumulated `TOOL_CALL_ARGS` deltas, per tool call id. */
|
|
67
|
+
private readonly args;
|
|
68
|
+
/**
|
|
69
|
+
* A2UI messages already folded onto each surface, by their serialised form.
|
|
70
|
+
*
|
|
71
|
+
* A re-attach re-streams the run from the beginning, so every
|
|
72
|
+
* `CUSTOM cortena.a2ui` arrives again. Folding concatenates, so without this
|
|
73
|
+
* the surface's message list doubled on the first reconnection and trebled
|
|
74
|
+
* on the second — a table drawn once, then drawn again underneath itself.
|
|
75
|
+
*/
|
|
76
|
+
private readonly seenA2ui;
|
|
77
|
+
private terminal;
|
|
78
|
+
private readonly pendingInterrupts;
|
|
79
|
+
constructor(opts: AgentChatBridgeOptions);
|
|
80
|
+
/** True once RUN_FINISHED or RUN_ERROR has been seen; no retry after this. */
|
|
81
|
+
get finished(): boolean;
|
|
82
|
+
/** Interrupts carried by the terminal RUN_FINISHED, if any. */
|
|
83
|
+
get interrupts(): ReadonlyArray<Record<string, unknown>>;
|
|
84
|
+
private blocks;
|
|
85
|
+
private emitDelta;
|
|
86
|
+
private emitFinal;
|
|
87
|
+
/** Surface a transport failure the way a run error is surfaced. */
|
|
88
|
+
fail(message: string): void;
|
|
89
|
+
handleEvent(event: AguiIncomingEvent): void;
|
|
90
|
+
/**
|
|
91
|
+
* Accumulate one `TOOL_CALL_ARGS` delta and publish the best reading of it.
|
|
92
|
+
*
|
|
93
|
+
* Published on every delta rather than only at `TOOL_CALL_END`, because a
|
|
94
|
+
* run that is interrupted between the two — an approval, an abort, a dropped
|
|
95
|
+
* stream — would otherwise show a tool card with no input at all, which is
|
|
96
|
+
* the state the card exists to avoid.
|
|
97
|
+
*/
|
|
98
|
+
private accumulateArgs;
|
|
99
|
+
private flushArgs;
|
|
100
|
+
private publishArgs;
|
|
101
|
+
/**
|
|
102
|
+
* The block with any message this surface has already been sent removed;
|
|
103
|
+
* `null` when nothing in it is new.
|
|
104
|
+
*/
|
|
105
|
+
private freshA2UI;
|
|
106
|
+
private handleCustom;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//# sourceMappingURL=bridge.d.ts.map
|