@agents24/chat-react 0.3.0 → 0.3.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 CHANGED
@@ -22,7 +22,8 @@ The package owns thread hydration, older-page pagination, stream/reattach state,
22
22
  - `appendStableStreamingTurn(messages, userMessage, assistantMessage)`
23
23
  - `upsertStableAssistantMessage(messages, input)`
24
24
  - normalized chat message, part, transport, and storage types
25
- - typed V2 `pendingHitl`, `isResolvingHitl`, and `resumeHitl(...)` controller state/actions in package `0.3.0`
25
+ - typed V2 `pendingHitl`, `isResolvingHitl`, and `resumeHitl(...)` controller state/actions in the current `0.3.x` contract
26
+ - strict HITL response-block parsing and `waitForMcpOauthComplete(...)` for validated host-owned OAuth popup completion
26
27
  - `partsFromResponseBlocks(blocks, fallbackText)`
27
28
  - `renderChatPart(part, message, renderOptions)`
28
29
  - `@agents24/chat-react/ui` for shared shadcn-derived `Message`, `Bubble`, `Attachment`, `Marker`, `MessageResponse`, `AgentChatMessage`, `AgentChatComposer`, `McpConnectRequiredCard`, and Agents24 message/attachment adapters
@@ -32,7 +33,9 @@ The package owns thread hydration, older-page pagination, stream/reattach state,
32
33
 
33
34
  Hosts can pass `onRuntimeEvent(event, context)` and `onThreadDetailLoaded(detail)` to layer product-specific runtime UI, trace panels, analytics, or metadata over the shared chat lifecycle without forking stream handling. That surface is enough for running-history badges and read-only live thread inspectors without a separate host-owned reattach state machine.
34
35
 
35
- `ChatMessage.parts` is the only render model. Backend `response_blocks` are normalized into ordered parts such as `text`, `tool-get_meteo`, `ui-blocks`, `reasoning`, `hitl`, `error`, and `data`. The shared compact HITL card renders pending and terminal states for Tool review, MCP authentication, User Approval, and app-data permission. Tool review exposes only **Allow once** and **Don't allow**; Edit and Respond are not part of the contract. Tool UI is client-owned through `renderPart`, `toolRenderers`, and `fallbackToolRenderer`; the package does not infer English tool labels or group tool rows.
36
+ `ChatMessage.parts` is the only render model. Backend `response_blocks` are normalized into ordered parts such as `text`, `tool-get_meteo`, `ui-blocks`, `reasoning`, `hitl`, `error`, and `data`. The shared HITL action strip renders a neutral, borderless pending state and a compact terminal outcome for Tool review, MCP authentication, User Approval, and app-data permission. Tool review exposes only **Allow once** and **Don't allow**; verbose risk metadata, argument details, Edit, and Respond are not part of the chat presentation. Tool UI is client-owned through `renderPart`, `toolRenderers`, and `fallbackToolRenderer`; the package does not infer English tool labels or group tool rows.
37
+
38
+ MCP OAuth remains a host side effect. `startMcpAuth(...)` returns both `authorization_url` and the authoritative `callback_origin`; hosts open the popup and pass both values to `waitForMcpOauthComplete(...)` before calling `resumeHitl(...)`. The utility verifies popup identity, callback origin, server identity, and connection UUID and owns timeout/close cleanup.
36
39
 
37
40
  `useStreamingText` owns reusable visual pacing for active assistant text. It returns `displayedText`, `isAnimating`, `mode`, and `parseIncompleteMarkdown` so host renderers can pass those values into their markdown component without the package depending on any markdown/UI library. The package only chooses text timing, cache behavior, reduced-motion handling, and active text-part helpers.
38
41
 
package/dist/index.cjs CHANGED
@@ -81,7 +81,8 @@ __export(index_exports, {
81
81
  useMessageScroller: () => import_message_scroller2.useMessageScroller,
82
82
  useMessageScrollerScrollable: () => import_message_scroller2.useMessageScrollerScrollable,
83
83
  useMessageScrollerVisibility: () => import_message_scroller2.useMessageScrollerVisibility,
84
- useStreamingText: () => useStreamingText
84
+ useStreamingText: () => useStreamingText,
85
+ waitForMcpOauthComplete: () => waitForMcpOauthComplete
85
86
  });
86
87
  module.exports = __toCommonJS(index_exports);
87
88
 
@@ -385,6 +386,22 @@ var assistantTextFromEvents = (events) => {
385
386
  };
386
387
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
387
388
  var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
389
+ var HITL_ACTIONS_BY_KIND = {
390
+ tool_review: ["approve", "reject"],
391
+ mcp_auth: ["connect", "skip"],
392
+ user_approval: ["approve", "reject"],
393
+ app_data_permission: ["approve", "reject"]
394
+ };
395
+ var HITL_STATUSES = ["pending", "resolved", "expired", "cancelled", "invalidated"];
396
+ function exactHitlActions(kind, value) {
397
+ if (!Array.isArray(value)) throw new Error("Invalid V2 HITL response block.");
398
+ const actions = value.map((item) => String(item));
399
+ const expected = HITL_ACTIONS_BY_KIND[kind];
400
+ if (actions.length !== expected.length || actions.some((action, index) => action !== expected[index])) {
401
+ throw new Error("Invalid V2 HITL response block.");
402
+ }
403
+ return actions;
404
+ }
388
405
  var toolStateFromStatus = (status) => {
389
406
  const normalized = String(status || "").trim().toLowerCase();
390
407
  if (["failed", "error"].includes(normalized)) return "output-error";
@@ -491,17 +508,19 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
491
508
  const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
492
509
  const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
493
510
  const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
494
- const allowedActions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.filter((value) => ["approve", "reject", "connect", "skip"].includes(String(value))) : [];
495
- if (!interruptId || allowedActions.length === 0 || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind))) {
511
+ if (!interruptId || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind)) || !HITL_STATUSES.includes(status)) {
496
512
  throw new Error("Invalid V2 HITL response block.");
497
513
  }
514
+ const kind = hitlKind;
515
+ const allowedActions = exactHitlActions(kind, hitl.allowed_actions);
498
516
  const resolution = asRecord(block.resolution);
517
+ const resolver = asRecord(resolution?.resolver);
499
518
  parts.push({
500
519
  id,
501
520
  type: "hitl",
502
521
  kind: "hitl",
503
522
  interruptId,
504
- hitlKind,
523
+ hitlKind: kind,
505
524
  message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
506
525
  allowedActions,
507
526
  status,
@@ -511,7 +530,10 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
511
530
  outcome: optionalString(resolution.outcome),
512
531
  reason: optionalString(resolution.reason),
513
532
  resolvedAt: optionalString(resolution.resolved_at),
514
- resolver: asRecord(resolution.resolver)
533
+ resolver: resolver ? {
534
+ principalType: optionalString(resolver.principal_type) || optionalString(resolver.principalType) || null,
535
+ principalId: optionalString(resolver.principal_id) || optionalString(resolver.principalId) || null
536
+ } : null
515
537
  } : null,
516
538
  raw: block
517
539
  });
@@ -1961,6 +1983,59 @@ var LatestThreadScroller = {
1961
1983
  Outline: LatestThreadScrollerOutline
1962
1984
  };
1963
1985
 
1986
+ // src/mcp-oauth.ts
1987
+ var CONNECTION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1988
+ function normalizedOrigin(value) {
1989
+ try {
1990
+ return new URL(value).origin;
1991
+ } catch {
1992
+ throw new Error("MCP authorization returned an invalid callback origin.");
1993
+ }
1994
+ }
1995
+ function waitForMcpOauthComplete({
1996
+ popup,
1997
+ callbackOrigin,
1998
+ serverId,
1999
+ timeoutMs = 12e4
2000
+ }) {
2001
+ if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
2002
+ const expectedOrigin = normalizedOrigin(callbackOrigin);
2003
+ const expectedServerId = String(serverId || "").trim();
2004
+ if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
2005
+ return new Promise((resolve, reject) => {
2006
+ let settled = false;
2007
+ const finish = (result) => {
2008
+ if (settled) return;
2009
+ settled = true;
2010
+ window.clearTimeout(timeout);
2011
+ window.clearInterval(closeTimer);
2012
+ window.removeEventListener("message", onMessage);
2013
+ if (result instanceof Error) reject(result);
2014
+ else resolve(result);
2015
+ };
2016
+ const onMessage = (event) => {
2017
+ if (event.source !== popup || event.origin !== expectedOrigin) return;
2018
+ const data = event.data;
2019
+ if (data?.type !== "mcp-oauth-complete" || data.server_id !== expectedServerId) return;
2020
+ if (!data.success) {
2021
+ finish(new Error("MCP authorization failed."));
2022
+ return;
2023
+ }
2024
+ const connectionId = String(data.connection_id || "").trim();
2025
+ if (!CONNECTION_ID_PATTERN.test(connectionId)) return;
2026
+ finish({ connectionId });
2027
+ };
2028
+ const timeout = window.setTimeout(
2029
+ () => finish(new Error("MCP authorization timed out.")),
2030
+ Math.max(1, timeoutMs)
2031
+ );
2032
+ const closeTimer = window.setInterval(() => {
2033
+ if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
2034
+ }, 500);
2035
+ window.addEventListener("message", onMessage);
2036
+ });
2037
+ }
2038
+
1964
2039
  // src/renderers.tsx
1965
2040
  var import_jsx_runtime2 = require("react/jsx-runtime");
1966
2041
  var DefaultToolPart = ({ part }) => {
@@ -2398,6 +2473,7 @@ var import_message_scroller2 = require("@shadcn/react/message-scroller");
2398
2473
  useMessageScroller,
2399
2474
  useMessageScrollerScrollable,
2400
2475
  useMessageScrollerVisibility,
2401
- useStreamingText
2476
+ useStreamingText,
2477
+ waitForMcpOauthComplete
2402
2478
  });
2403
2479
  //# sourceMappingURL=index.cjs.map