@agents24/chat-react 0.5.0 → 0.5.2
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/README.md +90 -21
- package/compatibility.json +10 -10
- package/dist/chunk-SSUO4EPD.js +189 -0
- package/dist/chunk-SSUO4EPD.js.map +1 -0
- package/dist/chunk-ZCNHLFRB.js +108 -0
- package/dist/chunk-ZCNHLFRB.js.map +1 -0
- package/dist/index.cjs +112 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +362 -68
- package/dist/index.js.map +1 -1
- package/dist/mcp-oauth.d.ts +13 -0
- package/dist/runtime/index.cjs +594 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +458 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/input-contract.d.ts +20 -0
- package/dist/runtime/use-agent-chat-runtime.d.ts +83 -0
- package/dist/scaffold/app.d.ts +30 -0
- package/dist/scaffold/components/chat/chat-composer.d.ts +21 -0
- package/dist/scaffold/components/chat/chat-message.d.ts +21 -0
- package/dist/scaffold/components/chat/chat-shell.d.ts +8 -0
- package/dist/scaffold/components/chat/collapsible-user-message.d.ts +3 -0
- package/dist/scaffold/components/chat/external-link-dialog.d.ts +2 -0
- package/dist/scaffold/components/chat/identity-control.d.ts +4 -0
- package/dist/scaffold/components/chat/message-parts.d.ts +12 -0
- package/dist/scaffold/components/chat/theme-menu.d.ts +1 -0
- package/dist/scaffold/components/chat/thread-sidebar.d.ts +16 -0
- package/dist/scaffold/components/chat/timeline-loading.d.ts +7 -0
- package/dist/scaffold/components/chat/turn-outline.d.ts +4 -0
- package/dist/scaffold/components/theme-provider.d.ts +20 -0
- package/dist/scaffold/components/ui/attachment.d.ts +24 -0
- package/dist/scaffold/components/ui/bubble.d.ts +17 -0
- package/dist/scaffold/components/ui/button-group.d.ts +11 -0
- package/dist/scaffold/components/ui/button.d.ts +10 -0
- package/dist/scaffold/components/ui/dialog.d.ts +17 -0
- package/dist/scaffold/components/ui/dropdown-menu.d.ts +29 -0
- package/dist/scaffold/components/ui/input.d.ts +3 -0
- package/dist/scaffold/components/ui/marker.d.ts +11 -0
- package/dist/scaffold/components/ui/message-scroller.d.ts +10 -0
- package/dist/scaffold/components/ui/message.d.ts +10 -0
- package/dist/scaffold/components/ui/popover.d.ts +6 -0
- package/dist/scaffold/components/ui/select.d.ts +8 -0
- package/dist/scaffold/components/ui/separator.d.ts +4 -0
- package/dist/scaffold/components/ui/sheet.d.ts +14 -0
- package/dist/scaffold/components/ui/sidebar.d.ts +69 -0
- package/dist/scaffold/components/ui/skeleton.d.ts +2 -0
- package/dist/scaffold/components/ui/textarea.d.ts +3 -0
- package/dist/scaffold/components/ui/tooltip.d.ts +15 -0
- package/dist/scaffold/hooks/use-mobile.d.ts +1 -0
- package/dist/scaffold/identity.d.ts +10 -0
- package/dist/scaffold/index.cjs +2270 -0
- package/dist/scaffold/index.cjs.map +1 -0
- package/dist/scaffold/index.d.ts +14 -0
- package/dist/scaffold/index.js +2244 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/lib/utils.d.ts +2 -0
- package/dist/styles.css +137 -378
- package/dist/types.d.ts +3 -13
- package/dist/ui/adapters.d.ts +1 -1
- package/dist/ui/agent-chat-actions.d.ts +12 -5
- package/dist/ui/agent-chat-composer.d.ts +2 -1
- package/dist/ui/agent-chat-context-compression-row.d.ts +5 -0
- package/dist/ui/agent-chat-context-status.d.ts +13 -0
- package/dist/ui/agent-chat-message.d.ts +7 -5
- package/dist/ui/agent-response-timeline.d.ts +29 -7
- package/dist/ui/ask-user-interaction-state.d.ts +20 -0
- package/dist/ui/ask-user-interaction.d.ts +8 -0
- package/dist/ui/index.cjs +1391 -271
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.js +2380 -65
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/tool-sessions.d.ts +70 -0
- package/dist/ui/use-audio-recorder.d.ts +8 -0
- package/package.json +27 -12
- package/scaffold/components.json +25 -0
- package/scaffold/manifest.json +28 -0
- package/scaffold/src/app.tsx +71 -0
- package/scaffold/src/chat.css +54 -0
- package/scaffold/src/components/chat/chat-composer.tsx +237 -0
- package/scaffold/src/components/chat/chat-message.tsx +76 -0
- package/scaffold/src/components/chat/chat-shell.tsx +257 -0
- package/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
- package/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
- package/scaffold/src/components/chat/identity-control.tsx +69 -0
- package/scaffold/src/components/chat/message-parts.tsx +83 -0
- package/scaffold/src/components/chat/theme-menu.tsx +79 -0
- package/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
- package/scaffold/src/components/chat/timeline-loading.ts +17 -0
- package/scaffold/src/components/chat/turn-outline.tsx +73 -0
- package/scaffold/src/components/theme-provider.tsx +91 -0
- package/scaffold/src/components/ui/attachment.tsx +204 -0
- package/scaffold/src/components/ui/bubble.tsx +125 -0
- package/scaffold/src/components/ui/button-group.tsx +83 -0
- package/scaffold/src/components/ui/button.tsx +65 -0
- package/scaffold/src/components/ui/dialog.tsx +171 -0
- package/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
- package/scaffold/src/components/ui/input.tsx +19 -0
- package/scaffold/src/components/ui/marker.tsx +69 -0
- package/scaffold/src/components/ui/message-scroller.tsx +129 -0
- package/scaffold/src/components/ui/message.tsx +92 -0
- package/scaffold/src/components/ui/popover.tsx +40 -0
- package/scaffold/src/components/ui/select.tsx +92 -0
- package/scaffold/src/components/ui/separator.tsx +28 -0
- package/scaffold/src/components/ui/sheet.tsx +150 -0
- package/scaffold/src/components/ui/sidebar.tsx +677 -0
- package/scaffold/src/components/ui/skeleton.tsx +13 -0
- package/scaffold/src/components/ui/textarea.tsx +18 -0
- package/scaffold/src/components/ui/tooltip.tsx +56 -0
- package/scaffold/src/hooks/use-mobile.ts +24 -0
- package/scaffold/src/identity.ts +17 -0
- package/scaffold/src/index.css +222 -0
- package/scaffold/src/index.tsx +39 -0
- package/scaffold/src/lib/utils.ts +6 -0
- package/scaffold/styles.css +3 -0
- package/dist/chunk-CHAU7FNW.js +0 -140
- package/dist/chunk-CHAU7FNW.js.map +0 -1
- package/dist/chunk-LI67M74F.js +0 -445
- package/dist/chunk-LI67M74F.js.map +0 -1
- package/dist/chunk-R7WFL3YR.js +0 -1327
- package/dist/chunk-R7WFL3YR.js.map +0 -1
- package/dist/renderers.d.ts +0 -10
- package/dist/templates/agent-chat-app.d.ts +0 -18
- package/dist/templates/agent-chat-layout.d.ts +0 -77
- package/dist/templates/agent-chat-shell.d.ts +0 -37
- package/dist/templates/appearance-control.d.ts +0 -5
- package/dist/templates/index.cjs +0 -2600
- package/dist/templates/index.cjs.map +0 -1
- package/dist/templates/index.d.ts +0 -4
- package/dist/templates/index.js +0 -846
- package/dist/templates/index.js.map +0 -1
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/runtime/index.ts
|
|
31
|
+
var runtime_exports = {};
|
|
32
|
+
__export(runtime_exports, {
|
|
33
|
+
SUPPORTED_INPUT_MIME_TYPES: () => SUPPORTED_INPUT_MIME_TYPES,
|
|
34
|
+
agentChatRuntimeErrorMessage: () => agentChatRuntimeErrorMessage,
|
|
35
|
+
agentChatRuntimeStateFromError: () => agentChatRuntimeStateFromError,
|
|
36
|
+
canonicalInputMimeType: () => canonicalInputMimeType,
|
|
37
|
+
inputMimeTypes: () => inputMimeTypes,
|
|
38
|
+
modelsFromAgentChatBootstrap: () => modelsFromAgentChatBootstrap,
|
|
39
|
+
useAgentChatRuntime: () => useAgentChatRuntime,
|
|
40
|
+
validateAgentChatSubmission: () => validateAgentChatSubmission,
|
|
41
|
+
validateWorkflowInputSubmission: () => validateWorkflowInputSubmission,
|
|
42
|
+
workflowInputRule: () => workflowInputRule
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(runtime_exports);
|
|
45
|
+
|
|
46
|
+
// src/runtime/use-agent-chat-runtime.ts
|
|
47
|
+
var import_react = require("@agents24/react");
|
|
48
|
+
var React = __toESM(require("react"), 1);
|
|
49
|
+
|
|
50
|
+
// src/mcp-oauth.ts
|
|
51
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
52
|
+
function browserChatCrypto() {
|
|
53
|
+
const value = globalThis.crypto;
|
|
54
|
+
if (!value?.subtle || !value.randomUUID) {
|
|
55
|
+
throw new Error("Secure browser crypto is required for MCP authorization.");
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function base64Url(bytes) {
|
|
60
|
+
let binary = "";
|
|
61
|
+
bytes.forEach((byte) => {
|
|
62
|
+
binary += String.fromCharCode(byte);
|
|
63
|
+
});
|
|
64
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
65
|
+
}
|
|
66
|
+
async function createMcpPkceProof() {
|
|
67
|
+
const crypto = browserChatCrypto();
|
|
68
|
+
const verifier = base64Url(crypto.getRandomValues(new Uint8Array(32)));
|
|
69
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
70
|
+
return { challenge: base64Url(new Uint8Array(digest)), verifier };
|
|
71
|
+
}
|
|
72
|
+
function isLoopback(hostname) {
|
|
73
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
74
|
+
}
|
|
75
|
+
function normalizedOrigin(value) {
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(value);
|
|
78
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback(url.hostname))) {
|
|
79
|
+
throw new Error("unsafe callback origin");
|
|
80
|
+
}
|
|
81
|
+
if (url.origin === "null") throw new Error("opaque callback origin");
|
|
82
|
+
return url.origin;
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error("MCP authorization returned an invalid callback origin.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function validateMcpCallbackResult(result, expectedServerId) {
|
|
88
|
+
const serverId = String(result.server_id || "").trim();
|
|
89
|
+
const connectionId = String(result.connection_id || "").trim();
|
|
90
|
+
if (!serverId || serverId !== expectedServerId) {
|
|
91
|
+
throw new Error("MCP authorization completed for an unexpected server.");
|
|
92
|
+
}
|
|
93
|
+
if (!UUID_PATTERN.test(connectionId)) {
|
|
94
|
+
throw new Error("MCP authorization returned an invalid connection identity.");
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
function waitForMcpOauthComplete({
|
|
99
|
+
popup,
|
|
100
|
+
callbackOrigin,
|
|
101
|
+
popupNonce,
|
|
102
|
+
requireConnectionId = false,
|
|
103
|
+
serverId,
|
|
104
|
+
timeoutMs = 12e4
|
|
105
|
+
}) {
|
|
106
|
+
if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
|
|
107
|
+
const expectedOrigin = normalizedOrigin(callbackOrigin);
|
|
108
|
+
const expectedServerId = String(serverId || "").trim();
|
|
109
|
+
const expectedPopupNonce = String(popupNonce || "").trim();
|
|
110
|
+
if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
|
|
111
|
+
if (!expectedPopupNonce) return Promise.reject(new Error("MCP authorization is missing its popup binding."));
|
|
112
|
+
const returnFocusTo = typeof document !== "undefined" ? document.activeElement : null;
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
let settled = false;
|
|
115
|
+
const finish = (result) => {
|
|
116
|
+
if (settled) return;
|
|
117
|
+
settled = true;
|
|
118
|
+
window.clearTimeout(timeout);
|
|
119
|
+
window.clearInterval(closeTimer);
|
|
120
|
+
window.removeEventListener("message", onMessage);
|
|
121
|
+
returnFocusTo?.focus?.();
|
|
122
|
+
if (result instanceof Error) reject(result);
|
|
123
|
+
else resolve(result);
|
|
124
|
+
};
|
|
125
|
+
const onMessage = (event) => {
|
|
126
|
+
if (event.source !== popup || event.origin !== expectedOrigin) return;
|
|
127
|
+
const data = event.data;
|
|
128
|
+
if (data?.type !== "agents24.mcp.callback" || data.server_id !== expectedServerId || data.popup_nonce !== expectedPopupNonce) return;
|
|
129
|
+
const code = String(data.code || "").trim();
|
|
130
|
+
if (!code || code.length > 2048) return;
|
|
131
|
+
const connectionId = String(data.connection_id || "").trim();
|
|
132
|
+
if ((requireConnectionId || connectionId) && !UUID_PATTERN.test(connectionId)) return;
|
|
133
|
+
finish({
|
|
134
|
+
code,
|
|
135
|
+
...connectionId ? { connectionId } : {},
|
|
136
|
+
popupNonce: expectedPopupNonce,
|
|
137
|
+
serverId: expectedServerId
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
const timeout = window.setTimeout(
|
|
141
|
+
() => finish(new Error("MCP authorization timed out.")),
|
|
142
|
+
Math.max(1, timeoutMs)
|
|
143
|
+
);
|
|
144
|
+
const closeTimer = window.setInterval(() => {
|
|
145
|
+
if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
|
|
146
|
+
}, 500);
|
|
147
|
+
window.addEventListener("message", onMessage);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/runtime/input-contract.ts
|
|
152
|
+
var SUPPORTED_INPUT_MIME_TYPES = {
|
|
153
|
+
files: ["application/pdf", "text/plain", "text/markdown", "text/csv", "application/json"],
|
|
154
|
+
images: ["image/png", "image/jpeg", "image/webp"],
|
|
155
|
+
audio: ["audio/wav", "audio/mpeg", "audio/webm", "audio/mp4"]
|
|
156
|
+
};
|
|
157
|
+
function workflowInputRule(inputs, key) {
|
|
158
|
+
const input = inputs?.find((candidate) => candidate.key === key);
|
|
159
|
+
return {
|
|
160
|
+
enabled: input?.enabled !== false,
|
|
161
|
+
required: input?.required === true
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function validateWorkflowInputSubmission(inputs, text, attachmentMimeTypes) {
|
|
165
|
+
const present = { files: false, images: false, audio: false };
|
|
166
|
+
if (text.trim() && !workflowInputRule(inputs, "text").enabled) {
|
|
167
|
+
return "This workflow does not accept text input.";
|
|
168
|
+
}
|
|
169
|
+
for (const rawMimeType of attachmentMimeTypes) {
|
|
170
|
+
const mimeType = canonicalInputMimeType(rawMimeType);
|
|
171
|
+
const modality = mimeType.startsWith("image/") ? "images" : mimeType.startsWith("audio/") ? "audio" : "files";
|
|
172
|
+
const supported = SUPPORTED_INPUT_MIME_TYPES[modality].includes(mimeType);
|
|
173
|
+
if (!supported || !workflowInputRule(inputs, modality).enabled) {
|
|
174
|
+
return `The attachment type ${mimeType || "unknown"} is not accepted.`;
|
|
175
|
+
}
|
|
176
|
+
present[modality] = true;
|
|
177
|
+
}
|
|
178
|
+
if (workflowInputRule(inputs, "text").required && !text.trim()) return "Text input is required.";
|
|
179
|
+
for (const modality of ["files", "images", "audio"]) {
|
|
180
|
+
if (workflowInputRule(inputs, modality).required && !present[modality]) {
|
|
181
|
+
return `${modality[0].toUpperCase()}${modality.slice(1)} input is required.`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function canonicalInputMimeType(value) {
|
|
187
|
+
const normalized = value.split(";", 1)[0]?.trim().toLowerCase() || "";
|
|
188
|
+
if (normalized === "image/jpg") return "image/jpeg";
|
|
189
|
+
if (["audio/x-wav"].includes(normalized)) return "audio/wav";
|
|
190
|
+
if (["audio/x-m4a", "audio/m4a"].includes(normalized)) return "audio/mp4";
|
|
191
|
+
return normalized;
|
|
192
|
+
}
|
|
193
|
+
function uploadByteSize(data) {
|
|
194
|
+
if (data instanceof Uint8Array || data instanceof ArrayBuffer) return data.byteLength;
|
|
195
|
+
return data.size;
|
|
196
|
+
}
|
|
197
|
+
function inputMimeTypes(contract) {
|
|
198
|
+
if (!contract) return [];
|
|
199
|
+
return [
|
|
200
|
+
...contract.modalities.files.allowed_mime_types,
|
|
201
|
+
...contract.modalities.images.allowed_mime_types,
|
|
202
|
+
...contract.modalities.audio.allowed_mime_types
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
function validateAgentChatSubmission(contract, text, files) {
|
|
206
|
+
const { modalities, limits } = contract;
|
|
207
|
+
const normalizedText = text.trim();
|
|
208
|
+
if (normalizedText && !modalities.text.enabled) return "This agent does not accept text input.";
|
|
209
|
+
if (modalities.text.required && !normalizedText) return "Text input is required.";
|
|
210
|
+
if (files.length > limits.max_attachments_per_turn) {
|
|
211
|
+
return `You can attach up to ${limits.max_attachments_per_turn} items per message.`;
|
|
212
|
+
}
|
|
213
|
+
let totalBytes = 0;
|
|
214
|
+
const present = { files: false, images: false, audio: false };
|
|
215
|
+
for (const file of files) {
|
|
216
|
+
const mediaType = canonicalInputMimeType(file.mediaType);
|
|
217
|
+
const modality = mediaType.startsWith("image/") ? "images" : mediaType.startsWith("audio/") ? "audio" : "files";
|
|
218
|
+
const rule = modalities[modality];
|
|
219
|
+
if (!rule.enabled || !rule.allowed_mime_types.includes(mediaType)) {
|
|
220
|
+
return `The attachment type ${mediaType || "unknown"} is not accepted.`;
|
|
221
|
+
}
|
|
222
|
+
const byteSize = uploadByteSize(file.data);
|
|
223
|
+
if (byteSize > limits.max_file_bytes) return "An attachment exceeds the per-file size limit.";
|
|
224
|
+
totalBytes += byteSize;
|
|
225
|
+
present[modality] = true;
|
|
226
|
+
}
|
|
227
|
+
if (totalBytes > limits.max_total_bytes) return "Attachments exceed the total size limit.";
|
|
228
|
+
for (const modality of ["files", "images", "audio"]) {
|
|
229
|
+
if (modalities[modality].required && !present[modality]) return `${modality[0].toUpperCase()}${modality.slice(1)} input is required.`;
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/runtime/use-agent-chat-runtime.ts
|
|
235
|
+
function agentChatRuntimeStateFromError(error) {
|
|
236
|
+
const typed = error;
|
|
237
|
+
if (typed?.kind === "authentication") return "expired";
|
|
238
|
+
if (typed?.kind === "authorization") return "denied";
|
|
239
|
+
if (typed?.kind === "quota" || typed?.kind === "rate_limit") return "quota";
|
|
240
|
+
if (typed?.kind === "network") return "offline";
|
|
241
|
+
return "failed";
|
|
242
|
+
}
|
|
243
|
+
function agentChatRuntimeErrorMessage(error) {
|
|
244
|
+
if (!error) return null;
|
|
245
|
+
return error instanceof Error ? error.message : String(error);
|
|
246
|
+
}
|
|
247
|
+
function modelsFromAgentChatBootstrap(bootstrap) {
|
|
248
|
+
const modelFeatures = bootstrap.features.models;
|
|
249
|
+
const selectable = bootstrap.capabilities.includes("models.select") && modelFeatures?.selectable === true;
|
|
250
|
+
if (!selectable) return { defaultModelId: null, models: [] };
|
|
251
|
+
const models = Array.isArray(modelFeatures.options) ? modelFeatures.options.flatMap((option) => typeof option?.id === "string" && option.id.trim() ? [{ id: option.id, label: typeof option.label === "string" && option.label.trim() ? option.label : option.id }] : []) : [];
|
|
252
|
+
const configuredDefault = models.some((model) => model.id === modelFeatures.default_id) ? modelFeatures.default_id : null;
|
|
253
|
+
return {
|
|
254
|
+
defaultModelId: configuredDefault,
|
|
255
|
+
models
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function modelsFromEffectivePolicy(policy) {
|
|
259
|
+
const models = policy.model_selection.selectable ? policy.model_selection.options.map((option) => ({ id: option.id, label: option.label })) : [];
|
|
260
|
+
return {
|
|
261
|
+
defaultModelId: models.some((model) => model.id === policy.model_selection.default_id) ? policy.model_selection.default_id : null,
|
|
262
|
+
models
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function hitlKey(part) {
|
|
266
|
+
return part.interruptId || part.id;
|
|
267
|
+
}
|
|
268
|
+
function useAgentChatRuntime(options) {
|
|
269
|
+
const {
|
|
270
|
+
activeThreadId,
|
|
271
|
+
agent: agentOverride,
|
|
272
|
+
client,
|
|
273
|
+
completeMcpAuthorization,
|
|
274
|
+
mcpRedirectUri,
|
|
275
|
+
modelId,
|
|
276
|
+
onActiveThreadIdChange,
|
|
277
|
+
onModelChange
|
|
278
|
+
} = options;
|
|
279
|
+
const controller = (0, import_react.useAgentChat)(
|
|
280
|
+
activeThreadId === void 0 ? { client } : { activeThreadId, client }
|
|
281
|
+
);
|
|
282
|
+
const attachments = (0, import_react.useAgentAttachments)();
|
|
283
|
+
const mcp = (0, import_react.useAgentMcp)();
|
|
284
|
+
const [bootstrap, setBootstrap] = React.useState(null);
|
|
285
|
+
const [effectivePolicy, setEffectivePolicy] = React.useState(null);
|
|
286
|
+
const [isBootstrapping, setIsBootstrapping] = React.useState(true);
|
|
287
|
+
const [bootstrapError, setBootstrapError] = React.useState(null);
|
|
288
|
+
const [localOperationError, setLocalOperationError] = React.useState(null);
|
|
289
|
+
const [selectedModelId, setSelectedModelId] = React.useState(modelId ?? null);
|
|
290
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
291
|
+
const submitPromiseRef = React.useRef(null);
|
|
292
|
+
const [hitlStates, setHitlStates] = React.useState({});
|
|
293
|
+
const hitlStatesRef = React.useRef(hitlStates);
|
|
294
|
+
hitlStatesRef.current = hitlStates;
|
|
295
|
+
const mountedRef = React.useRef(true);
|
|
296
|
+
const clientRef = React.useRef(client);
|
|
297
|
+
clientRef.current = client;
|
|
298
|
+
const bootstrapPromiseRef = React.useRef(null);
|
|
299
|
+
React.useEffect(() => {
|
|
300
|
+
mountedRef.current = true;
|
|
301
|
+
return () => {
|
|
302
|
+
mountedRef.current = false;
|
|
303
|
+
};
|
|
304
|
+
}, []);
|
|
305
|
+
const previousControlledThreadRef = React.useRef(activeThreadId);
|
|
306
|
+
const controlledSyncTargetRef = React.useRef(null);
|
|
307
|
+
const previousControllerThreadRef = React.useRef(controller.activeThreadId);
|
|
308
|
+
const pendingThreadTransitionRef = React.useRef(null);
|
|
309
|
+
const lastReportedThreadRef = React.useRef(activeThreadId ?? controller.activeThreadId);
|
|
310
|
+
if (activeThreadId !== previousControlledThreadRef.current) {
|
|
311
|
+
previousControlledThreadRef.current = activeThreadId;
|
|
312
|
+
if (activeThreadId !== void 0) {
|
|
313
|
+
controlledSyncTargetRef.current = { value: activeThreadId };
|
|
314
|
+
lastReportedThreadRef.current = activeThreadId;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const reportActiveThread = React.useCallback((threadId, reason) => {
|
|
318
|
+
if (lastReportedThreadRef.current === threadId) return;
|
|
319
|
+
lastReportedThreadRef.current = threadId;
|
|
320
|
+
onActiveThreadIdChange?.({ threadId, reason });
|
|
321
|
+
}, [onActiveThreadIdChange]);
|
|
322
|
+
const retryBootstrap = React.useCallback(() => {
|
|
323
|
+
if (bootstrapPromiseRef.current?.client === client) return bootstrapPromiseRef.current.promise;
|
|
324
|
+
const requestClient = client;
|
|
325
|
+
const request = (async () => {
|
|
326
|
+
if (mountedRef.current) {
|
|
327
|
+
setIsBootstrapping(true);
|
|
328
|
+
setBootstrapError(null);
|
|
329
|
+
setBootstrap(null);
|
|
330
|
+
setEffectivePolicy(null);
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const result = await requestClient.bootstrap();
|
|
334
|
+
if (!mountedRef.current || clientRef.current !== requestClient) return;
|
|
335
|
+
setBootstrap(result);
|
|
336
|
+
const entitlement = await requestClient.resourcePolicies.getEffective();
|
|
337
|
+
if (!mountedRef.current || clientRef.current !== requestClient) return;
|
|
338
|
+
setEffectivePolicy(entitlement);
|
|
339
|
+
const availableModels = modelsFromEffectivePolicy(entitlement);
|
|
340
|
+
if (modelId === void 0) {
|
|
341
|
+
setSelectedModelId((current) => availableModels.models.some((item) => item.id === current) ? current : availableModels.defaultModelId);
|
|
342
|
+
}
|
|
343
|
+
await controller.refreshThreads().catch(() => void 0);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (mountedRef.current && clientRef.current === requestClient) setBootstrapError(error);
|
|
346
|
+
} finally {
|
|
347
|
+
if (mountedRef.current && clientRef.current === requestClient) setIsBootstrapping(false);
|
|
348
|
+
}
|
|
349
|
+
})();
|
|
350
|
+
bootstrapPromiseRef.current = { client: requestClient, promise: request };
|
|
351
|
+
void request.finally(() => {
|
|
352
|
+
if (bootstrapPromiseRef.current?.promise === request) bootstrapPromiseRef.current = null;
|
|
353
|
+
});
|
|
354
|
+
return request;
|
|
355
|
+
}, [client, controller.refreshThreads, modelId]);
|
|
356
|
+
const refreshEffectivePolicy = React.useCallback(async () => {
|
|
357
|
+
const entitlement = await client.resourcePolicies.getEffective();
|
|
358
|
+
if (!mountedRef.current || clientRef.current !== client) return;
|
|
359
|
+
setEffectivePolicy(entitlement);
|
|
360
|
+
if (modelId === void 0) {
|
|
361
|
+
const available = modelsFromEffectivePolicy(entitlement);
|
|
362
|
+
setSelectedModelId((current) => available.models.some((item) => item.id === current) ? current : available.defaultModelId);
|
|
363
|
+
}
|
|
364
|
+
}, [client, modelId]);
|
|
365
|
+
React.useEffect(() => {
|
|
366
|
+
void retryBootstrap();
|
|
367
|
+
}, [retryBootstrap]);
|
|
368
|
+
React.useEffect(() => {
|
|
369
|
+
if (modelId !== void 0) setSelectedModelId(modelId);
|
|
370
|
+
}, [modelId]);
|
|
371
|
+
React.useEffect(() => {
|
|
372
|
+
hitlStatesRef.current = {};
|
|
373
|
+
setHitlStates({});
|
|
374
|
+
}, [client, controller.activeThreadId]);
|
|
375
|
+
React.useEffect(() => {
|
|
376
|
+
if (controlledSyncTargetRef.current?.value === null && controller.activeThreadId) {
|
|
377
|
+
controller.startNewThread();
|
|
378
|
+
}
|
|
379
|
+
}, [activeThreadId, controller.activeThreadId, controller.startNewThread]);
|
|
380
|
+
React.useEffect(() => {
|
|
381
|
+
const previous = previousControllerThreadRef.current;
|
|
382
|
+
const current = controller.activeThreadId;
|
|
383
|
+
previousControllerThreadRef.current = current;
|
|
384
|
+
const controlledTarget = controlledSyncTargetRef.current;
|
|
385
|
+
if (controlledTarget) {
|
|
386
|
+
if (current === controlledTarget.value) controlledSyncTargetRef.current = null;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const pendingTransition = pendingThreadTransitionRef.current;
|
|
390
|
+
if (pendingTransition && current === pendingTransition.value) return;
|
|
391
|
+
if (previous === null && current !== null) reportActiveThread(current, "created");
|
|
392
|
+
}, [activeThreadId, controller.activeThreadId, reportActiveThread]);
|
|
393
|
+
const clearOperationError = React.useCallback(() => {
|
|
394
|
+
setLocalOperationError(null);
|
|
395
|
+
controller.clearOperationError();
|
|
396
|
+
}, [controller.clearOperationError]);
|
|
397
|
+
const submit = React.useCallback((input) => {
|
|
398
|
+
if (submitPromiseRef.current) return submitPromiseRef.current;
|
|
399
|
+
clearOperationError();
|
|
400
|
+
setIsSubmitting(true);
|
|
401
|
+
const request = (async () => {
|
|
402
|
+
try {
|
|
403
|
+
if (!bootstrap) throw new Error("The agent input contract is unavailable.");
|
|
404
|
+
const inputError = validateAgentChatSubmission(bootstrap.features.inputs, input.text, input.files || []);
|
|
405
|
+
if (inputError) throw new Error(inputError);
|
|
406
|
+
const uploaded = input.files?.length ? await attachments.uploadBatch({
|
|
407
|
+
uploads: input.files,
|
|
408
|
+
threadId: controller.activeThreadId || void 0
|
|
409
|
+
}) : [];
|
|
410
|
+
await controller.submit({
|
|
411
|
+
text: input.text,
|
|
412
|
+
attachmentIds: uploaded.flatMap((attachment) => attachment.id ? [attachment.id] : []),
|
|
413
|
+
attachments: uploaded,
|
|
414
|
+
requestedModelId: selectedModelId || void 0
|
|
415
|
+
});
|
|
416
|
+
} catch (error) {
|
|
417
|
+
setLocalOperationError(error);
|
|
418
|
+
const typed = error;
|
|
419
|
+
if (typed.kind === "quota" || typed.kind === "authorization") {
|
|
420
|
+
await refreshEffectivePolicy().catch(() => void 0);
|
|
421
|
+
}
|
|
422
|
+
throw error;
|
|
423
|
+
} finally {
|
|
424
|
+
setIsSubmitting(false);
|
|
425
|
+
}
|
|
426
|
+
})();
|
|
427
|
+
submitPromiseRef.current = request;
|
|
428
|
+
const clearRequest = () => {
|
|
429
|
+
if (submitPromiseRef.current === request) submitPromiseRef.current = null;
|
|
430
|
+
};
|
|
431
|
+
void request.then(clearRequest, clearRequest);
|
|
432
|
+
return request;
|
|
433
|
+
}, [attachments, bootstrap, clearOperationError, controller.activeThreadId, controller.submit, refreshEffectivePolicy, selectedModelId]);
|
|
434
|
+
const completeMcp = React.useCallback(async (part) => {
|
|
435
|
+
if (!mcpRedirectUri) throw new Error("mcpRedirectUri is required for MCP Connect.");
|
|
436
|
+
const serverId = String(part.presentation.server_id || "").trim();
|
|
437
|
+
if (!serverId) throw new Error("MCP authorization is missing its server identity.");
|
|
438
|
+
const runId = controller.activeRunId || controller.messages.find((message) => message.parts.includes(part))?.runId;
|
|
439
|
+
if (!runId) throw new Error("MCP authorization is missing its paused run.");
|
|
440
|
+
const popupNonce = browserChatCrypto().randomUUID();
|
|
441
|
+
const proof = await createMcpPkceProof();
|
|
442
|
+
const popup = window.open("about:blank", "agents24-mcp", "popup,width=560,height=720");
|
|
443
|
+
if (!popup) throw new Error("Allow popups to connect this MCP account.");
|
|
444
|
+
popup.document.title = "Connecting to MCP";
|
|
445
|
+
popup.document.body.textContent = "Opening secure connection\u2026";
|
|
446
|
+
try {
|
|
447
|
+
const started = await mcp.startAuthorization({
|
|
448
|
+
runId,
|
|
449
|
+
serverId,
|
|
450
|
+
interruptId: part.interruptId,
|
|
451
|
+
redirectUri: mcpRedirectUri,
|
|
452
|
+
popupNonce,
|
|
453
|
+
codeChallenge: proof.challenge
|
|
454
|
+
});
|
|
455
|
+
if (!started.callback_origin) throw new Error("MCP authorization did not return a callback origin.");
|
|
456
|
+
popup.location.replace(started.authorization_url);
|
|
457
|
+
const completion = await waitForMcpOauthComplete({
|
|
458
|
+
popup,
|
|
459
|
+
callbackOrigin: started.callback_origin,
|
|
460
|
+
popupNonce,
|
|
461
|
+
requireConnectionId: Boolean(completeMcpAuthorization),
|
|
462
|
+
serverId
|
|
463
|
+
});
|
|
464
|
+
if (completeMcpAuthorization) {
|
|
465
|
+
await completeMcpAuthorization({ completion, codeVerifier: proof.verifier, serverId });
|
|
466
|
+
} else {
|
|
467
|
+
const redeemed = await mcp.redeemCallback({ code: completion.code, codeVerifier: proof.verifier });
|
|
468
|
+
validateMcpCallbackResult(redeemed, serverId);
|
|
469
|
+
}
|
|
470
|
+
} finally {
|
|
471
|
+
popup.close();
|
|
472
|
+
}
|
|
473
|
+
}, [completeMcpAuthorization, controller.activeRunId, controller.messages, mcp, mcpRedirectUri]);
|
|
474
|
+
const onHitlAction = React.useCallback(async (part, response) => {
|
|
475
|
+
const action = response.action;
|
|
476
|
+
const key = hitlKey(part);
|
|
477
|
+
const current = hitlStatesRef.current[key];
|
|
478
|
+
if (current?.status === "connecting" || current?.status === "resuming") return;
|
|
479
|
+
clearOperationError();
|
|
480
|
+
const pending = {
|
|
481
|
+
action,
|
|
482
|
+
status: part.hitlKind === "mcp_auth" && action === "connect" ? "connecting" : "resuming"
|
|
483
|
+
};
|
|
484
|
+
hitlStatesRef.current = { ...hitlStatesRef.current, [key]: pending };
|
|
485
|
+
setHitlStates(hitlStatesRef.current);
|
|
486
|
+
try {
|
|
487
|
+
if (part.hitlKind === "mcp_auth" && action === "connect") await completeMcp(part);
|
|
488
|
+
await controller.resumeHitl(part, response);
|
|
489
|
+
setHitlStates((states) => {
|
|
490
|
+
const next = { ...states };
|
|
491
|
+
delete next[key];
|
|
492
|
+
hitlStatesRef.current = next;
|
|
493
|
+
return next;
|
|
494
|
+
});
|
|
495
|
+
} catch (error) {
|
|
496
|
+
setLocalOperationError(error);
|
|
497
|
+
const failedState = {
|
|
498
|
+
...hitlStatesRef.current,
|
|
499
|
+
[key]: { action, error: agentChatRuntimeErrorMessage(error), status: "error" }
|
|
500
|
+
};
|
|
501
|
+
hitlStatesRef.current = failedState;
|
|
502
|
+
setHitlStates(failedState);
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
}, [clearOperationError, completeMcp, controller.resumeHitl]);
|
|
506
|
+
const newThread = React.useCallback(() => {
|
|
507
|
+
clearOperationError();
|
|
508
|
+
pendingThreadTransitionRef.current = null;
|
|
509
|
+
reportActiveThread(null, "new");
|
|
510
|
+
controller.startNewThread();
|
|
511
|
+
}, [clearOperationError, controller.startNewThread, reportActiveThread]);
|
|
512
|
+
const openThread = React.useCallback(async (threadId) => {
|
|
513
|
+
clearOperationError();
|
|
514
|
+
pendingThreadTransitionRef.current = { reason: "opened", value: threadId };
|
|
515
|
+
try {
|
|
516
|
+
await controller.openThread(threadId);
|
|
517
|
+
reportActiveThread(threadId, "opened");
|
|
518
|
+
} catch (error) {
|
|
519
|
+
setLocalOperationError(error);
|
|
520
|
+
throw error;
|
|
521
|
+
} finally {
|
|
522
|
+
if (pendingThreadTransitionRef.current?.value === threadId) pendingThreadTransitionRef.current = null;
|
|
523
|
+
}
|
|
524
|
+
}, [clearOperationError, controller.openThread, reportActiveThread]);
|
|
525
|
+
const deleteThread = React.useCallback(async (threadId) => {
|
|
526
|
+
clearOperationError();
|
|
527
|
+
const wasActive = controller.activeThreadId === threadId;
|
|
528
|
+
if (wasActive) pendingThreadTransitionRef.current = { reason: "deleted", value: null };
|
|
529
|
+
try {
|
|
530
|
+
await controller.deleteThread(threadId);
|
|
531
|
+
if (wasActive) reportActiveThread(null, "deleted");
|
|
532
|
+
} catch (error) {
|
|
533
|
+
setLocalOperationError(error);
|
|
534
|
+
throw error;
|
|
535
|
+
} finally {
|
|
536
|
+
if (wasActive && pendingThreadTransitionRef.current?.reason === "deleted") {
|
|
537
|
+
pendingThreadTransitionRef.current = null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}, [clearOperationError, controller.activeThreadId, controller.deleteThread, reportActiveThread]);
|
|
541
|
+
const changeModel = React.useCallback((nextModelId) => {
|
|
542
|
+
if (modelId === void 0) setSelectedModelId(nextModelId);
|
|
543
|
+
onModelChange?.(nextModelId);
|
|
544
|
+
}, [modelId, onModelChange]);
|
|
545
|
+
const modelOptions = effectivePolicy ? modelsFromEffectivePolicy(effectivePolicy).models : bootstrap ? modelsFromAgentChatBootstrap(bootstrap).models : [];
|
|
546
|
+
const fatalError = bootstrapError || controller.error;
|
|
547
|
+
const operationError = localOperationError || controller.operationError?.cause;
|
|
548
|
+
const state = isBootstrapping ? "loading" : fatalError ? agentChatRuntimeStateFromError(fatalError) : controller.runState === "streaming" ? "streaming" : controller.runState === "reconnecting" ? "reconnecting" : controller.runState === "paused" ? "paused" : controller.runState === "cancelling" ? "cancelling" : controller.runState === "cancelled" ? "cancelled" : "ready";
|
|
549
|
+
const connectionState = state === "offline" ? "offline" : state === "expired" ? "expired" : state === "reconnecting" ? "reconnecting" : isBootstrapping ? "connecting" : "connected";
|
|
550
|
+
return {
|
|
551
|
+
agent: agentOverride || { name: bootstrap?.deployment.name || "Agent" },
|
|
552
|
+
inputContract: bootstrap?.features.inputs || null,
|
|
553
|
+
allowFeedback: Boolean(bootstrap?.capabilities.includes("feedback.write")),
|
|
554
|
+
backgroundError: controller.backgroundError?.cause || null,
|
|
555
|
+
bootstrap,
|
|
556
|
+
capabilities: bootstrap?.capabilities || [],
|
|
557
|
+
changeModel,
|
|
558
|
+
clearOperationError,
|
|
559
|
+
connectionState,
|
|
560
|
+
contextWindow: controller.contextWindow,
|
|
561
|
+
contextCompression: controller.contextCompression,
|
|
562
|
+
controller,
|
|
563
|
+
deleteThread,
|
|
564
|
+
errorMessage: agentChatRuntimeErrorMessage(fatalError || operationError),
|
|
565
|
+
fatalError,
|
|
566
|
+
getHitlActionState: (part) => hitlStates[hitlKey(part)],
|
|
567
|
+
isBootstrapping,
|
|
568
|
+
isSubmitting: isSubmitting || controller.isSubmitting,
|
|
569
|
+
isUploading: attachments.isUploading,
|
|
570
|
+
modelId: selectedModelId,
|
|
571
|
+
models: modelOptions,
|
|
572
|
+
newThread,
|
|
573
|
+
onHitlAction,
|
|
574
|
+
openThread,
|
|
575
|
+
operationError,
|
|
576
|
+
retryBootstrap,
|
|
577
|
+
state,
|
|
578
|
+
submit
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
582
|
+
0 && (module.exports = {
|
|
583
|
+
SUPPORTED_INPUT_MIME_TYPES,
|
|
584
|
+
agentChatRuntimeErrorMessage,
|
|
585
|
+
agentChatRuntimeStateFromError,
|
|
586
|
+
canonicalInputMimeType,
|
|
587
|
+
inputMimeTypes,
|
|
588
|
+
modelsFromAgentChatBootstrap,
|
|
589
|
+
useAgentChatRuntime,
|
|
590
|
+
validateAgentChatSubmission,
|
|
591
|
+
validateWorkflowInputSubmission,
|
|
592
|
+
workflowInputRule
|
|
593
|
+
});
|
|
594
|
+
//# sourceMappingURL=index.cjs.map
|