@datalayer/agent-runtimes 1.3.113 → 1.4.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/dist-embed/chunks/{ChatView-D41Nx5rC.js → ChatView-BpKZPdcp.js} +4 -4
- package/dist-embed/datalayer-app-main.js +150 -150
- package/lib/apps/apps/appspec.d.ts +13 -0
- package/lib/apps/apps/appspec.js +125 -0
- package/lib/apps/apps/checks.d.ts +1 -1
- package/lib/apps/apps/checks.js +2 -2
- package/lib/apps/apps/customComponents.js +1 -1
- package/lib/apps/core/canvasBlocks.d.ts +3 -3
- package/lib/apps/core/canvasParts.d.ts +6 -4
- package/lib/apps/embed/AppEmbed.js +3 -7
- package/lib/apps/embed/embedSession.d.ts +8 -4
- package/lib/apps/embed/embedSession.js +16 -9
- package/lib/apps/plugins/a2ui-surface/AnswerShows.d.ts +21 -0
- package/lib/apps/plugins/a2ui-surface/AnswerShows.js +29 -0
- package/lib/apps/plugins/a2ui-surface/SurfaceToolResult.js +4 -7
- package/lib/apps/plugins/a2ui-surface/index.d.ts +2 -1
- package/lib/apps/plugins/a2ui-surface/index.js +10 -1
- package/lib/apps/plugins/a2ui-surface/toolResult.d.ts +20 -11
- package/lib/apps/plugins/a2ui-surface/toolResult.js +27 -11
- package/lib/apps/plugins/app-components/index.d.ts +1 -1
- package/lib/apps/plugins/app-components/index.js +2 -2
- package/lib/apps/plugins/app-elements/AppElements.d.ts +3 -3
- package/lib/apps/plugins/app-elements/AppElements.js +5 -20
- package/lib/apps/plugins/app-elements/QuestionView.d.ts +28 -0
- package/lib/apps/plugins/app-elements/QuestionView.js +57 -0
- package/lib/apps/plugins/app-elements/index.d.ts +7 -4
- package/lib/apps/plugins/app-elements/index.js +14 -5
- package/lib/apps/plugins/canvas-blocks/index.d.ts +13 -13
- package/lib/apps/plugins/canvas-blocks/index.js +24 -24
- package/lib/apps/plugins/canvas-parts/index.d.ts +7 -1
- package/lib/apps/plugins/canvas-parts/index.js +36 -0
- package/lib/apps/plugins/chat/ChatView.js +7 -0
- package/lib/chat/base/ChatBase.js +234 -43
- package/lib/chat/base/loopElement.d.ts +16 -17
- package/lib/chat/base/loopElement.js +11 -27
- package/lib/chat/base/loopStep.d.ts +15 -15
- package/lib/chat/base/loopStep.js +10 -13
- package/lib/chat/base/loopWindow.d.ts +15 -20
- package/lib/chat/base/loopWindow.js +26 -27
- package/lib/chat/base/uiOps.d.ts +96 -0
- package/lib/chat/base/uiOps.js +251 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +4 -4
- package/lib/components/a2ui/datalayer/Chart.d.ts +1 -1
- package/lib/components/a2ui/datalayer/schema.js +1 -1
- package/lib/components/teams/AnswerSurfaces.d.ts +3 -2
- package/lib/components/teams/AnswerSurfaces.js +7 -7
- package/lib/components/teams/useA2ATeam.d.ts +1 -1
- package/lib/config/AgentConfiguration.d.ts +1 -1
- package/lib/protocols/AGUIAdapter.d.ts +7 -0
- package/lib/protocols/AGUIAdapter.js +43 -3
- package/lib/protocols/agentic-commerce/index.d.ts +5 -9
- package/lib/protocols/agentic-commerce/index.js +6 -10
- package/lib/protocols/commerce.d.ts +8 -9
- package/lib/protocols/commerce.js +29 -0
- package/lib/protocols/ucp/index.d.ts +5 -10
- package/lib/protocols/ucp/index.js +6 -11
- package/lib/specs/actions.d.ts +8 -0
- package/lib/specs/actions.js +56 -0
- package/lib/specs/agents/agents.js +338 -169
- package/lib/specs/apps.d.ts +1 -0
- package/lib/specs/apps.js +272 -0
- package/lib/specs/appspecSchema.js +125 -0
- package/lib/specs/cogs.js +6 -3
- package/lib/specs/index.d.ts +2 -1
- package/lib/specs/index.js +2 -1
- package/lib/specs/protocols.d.ts +24 -0
- package/lib/specs/protocols.js +197 -0
- package/lib/specs/ui.d.ts +21 -0
- package/lib/specs/{uiPlugins.js → ui.js} +14 -14
- package/lib/types/agentspecs.d.ts +72 -5
- package/lib/types/messages.d.ts +7 -0
- package/lib/types/protocol.d.ts +8 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_agents.py +43 -8
- package/scripts/codegen/generate_apps.py +38 -1
- package/scripts/codegen/generate_protocols.py +207 -0
- package/scripts/codegen/generate_sandbox_agents.py +1 -1
- package/scripts/codegen/{generate_ui_plugins.py → generate_ui.py} +40 -40
- package/scripts/gen_workers.py +2 -2
- package/lib/chat/base/loopMessage.d.ts +0 -37
- package/lib/chat/base/loopMessage.js +0 -37
- package/lib/specs/uiPlugins.d.ts +0 -21
|
@@ -19,6 +19,8 @@ import { useContext } from 'react';
|
|
|
19
19
|
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
20
20
|
import { Text, Spinner, IconButton } from '@primer/react';
|
|
21
21
|
import { ReactorLazy } from '@datalayer/reactor/react';
|
|
22
|
+
import { ANSWER_ACTION, emptyUiModel, } from '@datalayer/reactor/ui';
|
|
23
|
+
import { UiSocket } from '@datalayer/reactor/ui/ws';
|
|
22
24
|
import { SkeletonText } from '@primer/react/experimental';
|
|
23
25
|
import { SidebarExpandIcon } from '@primer/octicons-react';
|
|
24
26
|
import { notebookStore, JupyterReactTheme } from '@datalayer/jupyter-react';
|
|
@@ -70,10 +72,10 @@ const ephemeralNotebookRefused = lazyViewRefused('notebook');
|
|
|
70
72
|
import { useNotebookTools } from '../../tools/adapters/agent-runtimes/notebookHooks';
|
|
71
73
|
import { useAgentInspectorTracer } from '../../components/inspector/agentSpans';
|
|
72
74
|
import { chatSpanRecorder, } from '../../components/inspector/chatSpans';
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
75
|
+
import { isStepDisplay, withLoopStep } from './loopStep';
|
|
76
|
+
import { applyLoopElement } from './loopElement';
|
|
77
|
+
import { tellLoopWindow } from './loopWindow';
|
|
78
|
+
import { displayPressOf, placeable, routeUiOp, withPress, withUiOp, } from './uiOps';
|
|
77
79
|
import { useChatWords } from '../ChatLanguage';
|
|
78
80
|
// Tracks pending prompts already auto-sent for a given conversation scope.
|
|
79
81
|
// This prevents layout-driven unmount/remount cycles from re-sending prompts.
|
|
@@ -1196,6 +1198,10 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1196
1198
|
const [historyLoaded, setHistoryLoaded] = useState(!historyScopeId);
|
|
1197
1199
|
const [historyRefreshTick, setHistoryRefreshTick] = useState(0);
|
|
1198
1200
|
const historyRetryAttemptsRef = useRef(new Map());
|
|
1201
|
+
// The steps of an application's code shown so far, as displays (patched
|
|
1202
|
+
// as they stream), and the elements it showed (REACTOR_UI RU-3).
|
|
1203
|
+
const uiStepsRef = useRef(emptyUiModel());
|
|
1204
|
+
const uiElementsRef = useRef(new Set());
|
|
1199
1205
|
// Adapter-ready flag — flipped to true once the protocol adapter is initialised
|
|
1200
1206
|
const [adapterReady, setAdapterReady] = useState(false);
|
|
1201
1207
|
// Guard so each distinct pending prompt is sent at most once. Stores the
|
|
@@ -1578,6 +1584,171 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1578
1584
|
threadGivenRef.current?.onStarted?.(id);
|
|
1579
1585
|
}
|
|
1580
1586
|
}, []);
|
|
1587
|
+
/*
|
|
1588
|
+
* The conversation's ui channel (REACTOR_UI RU-W): one WebSocket per
|
|
1589
|
+
* conversation, its thread the scope, opened once its session is there
|
|
1590
|
+
* (the first event of its first run, or a thread reopened). What its
|
|
1591
|
+
* messages show, its steps, its elements, its authors and its window
|
|
1592
|
+
* messages arrive here as `reactor.ui` ops; the presses of what they
|
|
1593
|
+
* offered go up it. An op about a message the run stream has not brought
|
|
1594
|
+
* yet — they are two connections — waits for it (`placeable`).
|
|
1595
|
+
*/
|
|
1596
|
+
const uiSocketRef = useRef(null);
|
|
1597
|
+
const parkedUiOpsRef = useRef([]);
|
|
1598
|
+
const historyStepsRef = useRef(new Set());
|
|
1599
|
+
const displayItemsRef = useRef([]);
|
|
1600
|
+
const uiSocketUrl = protocol?.uiSocket;
|
|
1601
|
+
const uiSocketTokenRef = useRef(protocol?.authToken);
|
|
1602
|
+
uiSocketTokenRef.current = protocol?.authToken;
|
|
1603
|
+
const applyToMessages = useCallback((op) => {
|
|
1604
|
+
const apply = (items) => {
|
|
1605
|
+
try {
|
|
1606
|
+
return withUiOp(items, op);
|
|
1607
|
+
}
|
|
1608
|
+
catch (error) {
|
|
1609
|
+
console.error('[ChatBase] reactor.ui op not applied:', error);
|
|
1610
|
+
return items;
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
setDisplayItems(prev => apply(prev));
|
|
1614
|
+
if (useStoreMode) {
|
|
1615
|
+
const store = useChatStore.getState();
|
|
1616
|
+
store.setMessages(apply(store.messages));
|
|
1617
|
+
}
|
|
1618
|
+
}, [useStoreMode]);
|
|
1619
|
+
const applySocketOp = useCallback((op, history) => {
|
|
1620
|
+
// The past is drawn again, never acted on: a window message is not
|
|
1621
|
+
// raised again, and the steps of turns long over are not drawn again.
|
|
1622
|
+
if (history) {
|
|
1623
|
+
if (op.op === 'window')
|
|
1624
|
+
return;
|
|
1625
|
+
if ((op.op === 'show' || op.op === 'ask') && isStepDisplay(op.body)) {
|
|
1626
|
+
historyStepsRef.current.add(op.body.id);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
if ((op.op === 'patch' || op.op === 'append') &&
|
|
1630
|
+
historyStepsRef.current.has(op.id)) {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
let next;
|
|
1635
|
+
try {
|
|
1636
|
+
next = routeUiOp(op, uiStepsRef.current, uiElementsRef.current);
|
|
1637
|
+
}
|
|
1638
|
+
catch (error) {
|
|
1639
|
+
console.error('[ChatBase] reactor.ui op not applied:', error);
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
uiStepsRef.current = next.steps;
|
|
1643
|
+
const routed = next.routed;
|
|
1644
|
+
if (routed.to === 'window') {
|
|
1645
|
+
// For the page it sits in, outside the conversation (LOOP P-25).
|
|
1646
|
+
tellLoopWindow(routed.data);
|
|
1647
|
+
}
|
|
1648
|
+
else if (routed.to === 'step') {
|
|
1649
|
+
// A step of its code, drawn as it runs (LOOP P-16).
|
|
1650
|
+
const step = routed.step;
|
|
1651
|
+
setDisplayItems(prev => withLoopStep(prev, step));
|
|
1652
|
+
}
|
|
1653
|
+
else if (routed.to === 'element') {
|
|
1654
|
+
// In a side panel or on a page of its own (LOOP P-18).
|
|
1655
|
+
uiElementsRef.current.add(routed.change.id);
|
|
1656
|
+
applyLoopElement(routed.change);
|
|
1657
|
+
}
|
|
1658
|
+
else if (!placeable(displayItemsRef.current, routed.op)) {
|
|
1659
|
+
const under = routed.op.op === 'show' || routed.op.op === 'ask'
|
|
1660
|
+
? routed.op.body.data?.message_id
|
|
1661
|
+
: undefined;
|
|
1662
|
+
if (history && typeof under === 'string') {
|
|
1663
|
+
// Shown in the past under a message that said nothing: the
|
|
1664
|
+
// thread did not draw it, the chat draws it for what it showed.
|
|
1665
|
+
const placeholder = {
|
|
1666
|
+
id: under,
|
|
1667
|
+
role: 'assistant',
|
|
1668
|
+
content: '',
|
|
1669
|
+
createdAt: new Date(),
|
|
1670
|
+
};
|
|
1671
|
+
displayItemsRef.current = [...displayItemsRef.current, placeholder];
|
|
1672
|
+
setDisplayItems(prev => prev.some(item => item.id === under)
|
|
1673
|
+
? prev
|
|
1674
|
+
: [...prev, placeholder]);
|
|
1675
|
+
if (useStoreMode)
|
|
1676
|
+
useChatStore.getState().addMessage(placeholder);
|
|
1677
|
+
applyToMessages(routed.op);
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
parkedUiOpsRef.current.push(routed.op);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
applyToMessages(routed.op);
|
|
1685
|
+
}
|
|
1686
|
+
}, [applyToMessages, useStoreMode]);
|
|
1687
|
+
const applySocketOpRef = useRef(applySocketOp);
|
|
1688
|
+
applySocketOpRef.current = applySocketOp;
|
|
1689
|
+
// What waited for its message, applied once the message is here.
|
|
1690
|
+
useEffect(() => {
|
|
1691
|
+
displayItemsRef.current = displayItems;
|
|
1692
|
+
if (parkedUiOpsRef.current.length === 0)
|
|
1693
|
+
return;
|
|
1694
|
+
const parked = parkedUiOpsRef.current.splice(0);
|
|
1695
|
+
let items = displayItems;
|
|
1696
|
+
for (const op of parked) {
|
|
1697
|
+
if (!placeable(items, op)) {
|
|
1698
|
+
parkedUiOpsRef.current.push(op);
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
try {
|
|
1702
|
+
items = withUiOp(items, op);
|
|
1703
|
+
}
|
|
1704
|
+
catch {
|
|
1705
|
+
// Said by `applyToMessages`, which refuses it the same way.
|
|
1706
|
+
}
|
|
1707
|
+
applyToMessages(op);
|
|
1708
|
+
}
|
|
1709
|
+
}, [displayItems, applyToMessages]);
|
|
1710
|
+
/** The conversation's ui socket, opened for its thread when it is not yet. */
|
|
1711
|
+
const ensureUiSocket = useCallback(() => {
|
|
1712
|
+
if (!uiSocketUrl)
|
|
1713
|
+
return null;
|
|
1714
|
+
const scope = threadIdRef.current;
|
|
1715
|
+
const open = uiSocketRef.current;
|
|
1716
|
+
if (open && open.scope === scope)
|
|
1717
|
+
return open.socket;
|
|
1718
|
+
// Another conversation: what the last one showed goes with it.
|
|
1719
|
+
open?.socket.close();
|
|
1720
|
+
uiStepsRef.current = emptyUiModel();
|
|
1721
|
+
uiElementsRef.current = new Set();
|
|
1722
|
+
parkedUiOpsRef.current = [];
|
|
1723
|
+
historyStepsRef.current = new Set();
|
|
1724
|
+
const socket = new UiSocket({
|
|
1725
|
+
url: uiSocketUrl,
|
|
1726
|
+
scope,
|
|
1727
|
+
token: () => uiSocketTokenRef.current ?? '',
|
|
1728
|
+
onOp: (op, _seq, history) => applySocketOpRef.current(op, history),
|
|
1729
|
+
onStatus: (status, reason) => {
|
|
1730
|
+
if (status === 'refused' || status === 'gone') {
|
|
1731
|
+
console.error(`[ChatBase] ui socket ${status}: ${reason ?? ''}`);
|
|
1732
|
+
}
|
|
1733
|
+
},
|
|
1734
|
+
});
|
|
1735
|
+
uiSocketRef.current = { socket, scope };
|
|
1736
|
+
socket.connect();
|
|
1737
|
+
return socket;
|
|
1738
|
+
}, [uiSocketUrl]);
|
|
1739
|
+
const ensureUiSocketRef = useRef(ensureUiSocket);
|
|
1740
|
+
ensureUiSocketRef.current = ensureUiSocket;
|
|
1741
|
+
// A thread reopened has its session already: its socket opens now.
|
|
1742
|
+
useEffect(() => {
|
|
1743
|
+
if (thread?.messages.length)
|
|
1744
|
+
ensureUiSocketRef.current();
|
|
1745
|
+
return () => {
|
|
1746
|
+
uiSocketRef.current?.socket.close();
|
|
1747
|
+
uiSocketRef.current = null;
|
|
1748
|
+
};
|
|
1749
|
+
// At mount: the thread it was given.
|
|
1750
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1751
|
+
}, []);
|
|
1581
1752
|
const messagesContainerRef = useRef(null);
|
|
1582
1753
|
const messagesEndRef = useRef(null);
|
|
1583
1754
|
const inputRef = useRef(null);
|
|
@@ -2378,6 +2549,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2378
2549
|
adapterRef.current = adapter;
|
|
2379
2550
|
setAdapterReady(true);
|
|
2380
2551
|
unsubscribeRef.current = adapter.subscribe((event) => {
|
|
2552
|
+
// The run's session is there once it answers: its ui socket opens.
|
|
2553
|
+
ensureUiSocketRef.current();
|
|
2381
2554
|
/*
|
|
2382
2555
|
* Nothing more from a turn the reader has stopped.
|
|
2383
2556
|
*
|
|
@@ -2707,45 +2880,11 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2707
2880
|
}
|
|
2708
2881
|
}
|
|
2709
2882
|
break;
|
|
2710
|
-
case 'activity':
|
|
2711
|
-
//
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
setDisplayItems(prev => withLoopStep(prev, step));
|
|
2715
|
-
break;
|
|
2716
|
-
}
|
|
2717
|
-
// What its code tells the page it sits in, outside the
|
|
2718
|
-
// conversation (LOOP P-25): handed to whoever listens.
|
|
2719
|
-
const windowMessage = loopWindowMessage(event.activity);
|
|
2720
|
-
if (windowMessage) {
|
|
2721
|
-
tellLoopWindow(windowMessage.data);
|
|
2722
|
-
break;
|
|
2723
|
-
}
|
|
2724
|
-
// An element its code opened in a side panel or on a page of its
|
|
2725
|
-
// own, changed or closed (LOOP P-18): kept for whoever draws them.
|
|
2726
|
-
const element = loopElementChange(event.activity);
|
|
2727
|
-
if (element) {
|
|
2728
|
-
applyLoopElement(element);
|
|
2729
|
-
break;
|
|
2730
|
-
}
|
|
2731
|
-
// An application's code changed a message it sent: its author
|
|
2732
|
-
// said, or the message taken away (LOOP P-15).
|
|
2733
|
-
const change = loopMessageChange(event.activity);
|
|
2734
|
-
if (!change)
|
|
2735
|
-
break;
|
|
2736
|
-
setDisplayItems(prev => withLoopMessage(prev, change));
|
|
2737
|
-
if (useStoreMode) {
|
|
2738
|
-
if ('removed' in change) {
|
|
2739
|
-
useChatStore.getState().deleteMessage(change.id);
|
|
2740
|
-
}
|
|
2741
|
-
else {
|
|
2742
|
-
useChatStore.getState().updateMessage(change.id, {
|
|
2743
|
-
speaker: speakerOf(change.author),
|
|
2744
|
-
});
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2883
|
+
case 'activity':
|
|
2884
|
+
// What the server says besides the conversation's text comes on
|
|
2885
|
+
// the ui socket (REACTOR_UI RU-W); the run's other activities —
|
|
2886
|
+
// `loop.session`, `loop.page` — are their readers'.
|
|
2747
2887
|
break;
|
|
2748
|
-
}
|
|
2749
2888
|
case 'done':
|
|
2750
2889
|
/*
|
|
2751
2890
|
* What the turn cost, when nobody else is counting.
|
|
@@ -2949,6 +3088,28 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2949
3088
|
return;
|
|
2950
3089
|
if (!adapterRef.current && !onSendMessage)
|
|
2951
3090
|
return;
|
|
3091
|
+
// A press of what a display offered goes up the ui socket, checked
|
|
3092
|
+
// there; the run that answers it names it only (REACTOR_UI RU-W).
|
|
3093
|
+
const pressed = displayPressOf(forwardedPropsGiven);
|
|
3094
|
+
if (pressed && forwardedPropsGiven) {
|
|
3095
|
+
const socket = uiSocketRef.current?.socket;
|
|
3096
|
+
if (!socket) {
|
|
3097
|
+
setError(new Error('This conversation’s ui channel is not open.'));
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
try {
|
|
3101
|
+
const result = await socket.act(pressed);
|
|
3102
|
+
if (!result.ok) {
|
|
3103
|
+
setError(new Error(result.detail || `The press was refused (${result.status}).`));
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
forwardedPropsGiven = withPress(forwardedPropsGiven, result.value);
|
|
3107
|
+
}
|
|
3108
|
+
catch (failure) {
|
|
3109
|
+
setError(failure instanceof Error ? failure : new Error(String(failure)));
|
|
3110
|
+
return;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
2952
3113
|
stoppedRef.current = false;
|
|
2953
3114
|
inspectAnswerRef.current = '';
|
|
2954
3115
|
recorderRef.current?.recorder.turnStarted(messageContent, selectedModel || undefined);
|
|
@@ -3221,6 +3382,36 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3221
3382
|
protocol?.authToken,
|
|
3222
3383
|
protocol?.agentId,
|
|
3223
3384
|
]);
|
|
3385
|
+
/*
|
|
3386
|
+
* How a message's display presses what it offered (REACTOR_UI RU-4,
|
|
3387
|
+
* RU-W): with the token of the offer, as the run's press — which
|
|
3388
|
+
* `handleSend` sends up the ui socket before the run, the run naming it
|
|
3389
|
+
* only. An answer is the person's words in the conversation; any other
|
|
3390
|
+
* press, the action's name.
|
|
3391
|
+
*/
|
|
3392
|
+
const actOnDisplay = useCallback((display) => async (action) => {
|
|
3393
|
+
const offer = display.actions?.find(one => one.name === action.action);
|
|
3394
|
+
if (!offer) {
|
|
3395
|
+
throw new Error(`“${action.action}” was not offered here.`);
|
|
3396
|
+
}
|
|
3397
|
+
const value = action.payload
|
|
3398
|
+
?.value;
|
|
3399
|
+
const words = action.action === ANSWER_ACTION
|
|
3400
|
+
? typeof value === 'string'
|
|
3401
|
+
? value
|
|
3402
|
+
: 'Answered.'
|
|
3403
|
+
: action.action;
|
|
3404
|
+
await handleSend(words, {
|
|
3405
|
+
loop: {
|
|
3406
|
+
action: {
|
|
3407
|
+
name: action.action,
|
|
3408
|
+
display_id: display.id,
|
|
3409
|
+
token: offer.token,
|
|
3410
|
+
payload: action.payload ?? {},
|
|
3411
|
+
},
|
|
3412
|
+
},
|
|
3413
|
+
});
|
|
3414
|
+
}, [handleSend]);
|
|
3224
3415
|
// ---- handleNewChat ----
|
|
3225
3416
|
const handleNewChat = useCallback(() => {
|
|
3226
3417
|
setDisplayItems([]);
|
|
@@ -3618,7 +3809,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3618
3809
|
rows inside carry their own horizontal padding, which becomes the
|
|
3619
3810
|
margin once the cap does bind.
|
|
3620
3811
|
*/
|
|
3621
|
-
width: "100%", maxWidth: 920, mx: "auto", bg: "canvas.default", children: [_jsx(ChatMessageList, { displayItems: welcomedItems, isLoading: isLoading, isStreaming: isStreaming, showLoadingIndicator: showLoadingIndicator, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: defaultAvatarConfig, padding: padding,
|
|
3812
|
+
width: "100%", maxWidth: 920, mx: "auto", bg: "canvas.default", children: [_jsx(ChatMessageList, { displayItems: welcomedItems, actOn: protocol?.uiSocket ? actOnDisplay : undefined, isLoading: isLoading, isStreaming: isStreaming, showLoadingIndicator: showLoadingIndicator, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: defaultAvatarConfig, padding: padding,
|
|
3622
3813
|
// Tool calls are drawn by the page's Reactor plugins; a named
|
|
3623
3814
|
// notebook's cell surfaces stand in for the default row of its tools,
|
|
3624
3815
|
// so every chat with a headless notebook gets the same transcript.
|
|
@@ -3,16 +3,14 @@
|
|
|
3
3
|
* panel beside the conversation, or on a page of its own over it. Inline,
|
|
4
4
|
* an element is a message, and the chat draws it as one (P-04).
|
|
5
5
|
*
|
|
6
|
-
* The session API
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* application on it.
|
|
6
|
+
* The session API shows it as a `reactor.ui` display that says where it
|
|
7
|
+
* shows in its data (`where: "panel" | "page"`, with its `title`) — an
|
|
8
|
+
* answer's display (REACTOR_UI RU-2) — and closes it with a `remove` of its
|
|
9
|
+
* id (RU-3). The chat does not draw it: it keeps what is open here, and
|
|
10
|
+
* whoever draws panels and pages (the `app-elements` plugin) listens. One
|
|
11
|
+
* store per page, as there is one chat per application on it.
|
|
13
12
|
*/
|
|
14
|
-
|
|
15
|
-
export declare const LOOP_ELEMENT = "loop.element";
|
|
13
|
+
import type { Display } from '@datalayer/reactor/ui';
|
|
16
14
|
/** Where an element opens, besides inline. */
|
|
17
15
|
export type LoopElementWhere = 'panel' | 'page';
|
|
18
16
|
/** An element open in a side panel or on a page. */
|
|
@@ -20,19 +18,20 @@ export type LoopElement = {
|
|
|
20
18
|
id: string;
|
|
21
19
|
where: LoopElementWhere;
|
|
22
20
|
title: string;
|
|
23
|
-
/** The
|
|
24
|
-
shows:
|
|
21
|
+
/** The display it draws: what an answer shows (`AnswerShows`). */
|
|
22
|
+
shows: Display;
|
|
25
23
|
};
|
|
26
|
-
/** What
|
|
24
|
+
/** What changed: an element opened or changed, or one closed. */
|
|
27
25
|
export type LoopElementChange = LoopElement | {
|
|
28
26
|
id: string;
|
|
29
27
|
closed: true;
|
|
30
28
|
};
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The element a display is, or `null` for one shown inline, under its
|
|
31
|
+
* message: a display is an element when it says it shows in a panel or on
|
|
32
|
+
* a page.
|
|
33
|
+
*/
|
|
34
|
+
export declare function elementOfDisplay(display: Display): LoopElement | null;
|
|
36
35
|
/**
|
|
37
36
|
* The elements open with a change applied: one opened is added last, one
|
|
38
37
|
* changed is replaced where it is, one closed is taken away.
|
|
@@ -3,36 +3,20 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* The session API says it with a `CUSTOM` event named `loop.element` —
|
|
11
|
-
* `{id, where, title, shows}`, `shows` the A2UI surface an answer's
|
|
12
|
-
* components are drawn from, or `{id, closed: true}` — which the AG-UI
|
|
13
|
-
* adapter hands on as an `activity`. The chat does not draw it: it keeps
|
|
14
|
-
* what is open here, and whoever draws panels and pages (the `app-elements`
|
|
15
|
-
* plugin) listens. One store per page, as there is one chat per
|
|
16
|
-
* application on it.
|
|
6
|
+
* The element a display is, or `null` for one shown inline, under its
|
|
7
|
+
* message: a display is an element when it says it shows in a panel or on
|
|
8
|
+
* a page.
|
|
17
9
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export function loopElementChange(activity) {
|
|
22
|
-
if (!activity || activity.type !== LOOP_ELEMENT)
|
|
23
|
-
return null;
|
|
24
|
-
const data = activity.data;
|
|
25
|
-
if (!data || typeof data.id !== 'string' || !data.id)
|
|
26
|
-
return null;
|
|
27
|
-
if (data.closed === true)
|
|
28
|
-
return { id: data.id, closed: true };
|
|
29
|
-
if (data.where !== 'panel' && data.where !== 'page')
|
|
10
|
+
export function elementOfDisplay(display) {
|
|
11
|
+
const where = display.data?.where;
|
|
12
|
+
if (where !== 'panel' && where !== 'page')
|
|
30
13
|
return null;
|
|
14
|
+
const title = display.data?.title;
|
|
31
15
|
return {
|
|
32
|
-
id:
|
|
33
|
-
where
|
|
34
|
-
title: typeof
|
|
35
|
-
shows:
|
|
16
|
+
id: display.id,
|
|
17
|
+
where,
|
|
18
|
+
title: typeof title === 'string' ? title : '',
|
|
19
|
+
shows: display,
|
|
36
20
|
};
|
|
37
21
|
}
|
|
38
22
|
/**
|
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
* (LOOP P-16): the chain of thought.
|
|
4
4
|
*
|
|
5
5
|
* AG-UI's `STEP_STARTED` and `STEP_FINISHED` carry a step's name only; the
|
|
6
|
-
* session API
|
|
7
|
-
* `loop.step`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* session API shows the step whole beside them as a `reactor.ui` display of
|
|
7
|
+
* type `application/vnd.datalayer.loop.step+json` (REACTOR_UI RU-3) — its
|
|
8
|
+
* data `{name, kind, parent_id, input, output, error, started_at,
|
|
9
|
+
* ended_at}`, its id the step's — shown as it starts and again as it ends,
|
|
10
|
+
* its output patched in as it streams (LOOP P-31). A step is drawn as the
|
|
11
|
+
* chat draws a tool call: one row, collapsed, its input and output inside,
|
|
12
|
+
* running until it ends; a nested step says the step it is in.
|
|
13
13
|
*/
|
|
14
|
+
import type { Display } from '@datalayer/reactor/ui';
|
|
14
15
|
import type { DisplayItem, ToolCallMessage } from '../../types/chat';
|
|
15
|
-
/** The
|
|
16
|
-
export declare const
|
|
17
|
-
/** A step, as
|
|
16
|
+
/** The display type of a step. */
|
|
17
|
+
export declare const STEP_DISPLAY_TYPE = "application/vnd.datalayer.loop.step+json";
|
|
18
|
+
/** A step, as its display says it. */
|
|
18
19
|
export type LoopStep = {
|
|
19
20
|
id: string;
|
|
20
21
|
name: string;
|
|
@@ -25,11 +26,10 @@ export type LoopStep = {
|
|
|
25
26
|
error: string;
|
|
26
27
|
ended: boolean;
|
|
27
28
|
};
|
|
28
|
-
/**
|
|
29
|
-
export declare
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} | undefined): LoopStep | null;
|
|
29
|
+
/** Whether a display is a step's. */
|
|
30
|
+
export declare const isStepDisplay: (display: Display) => boolean;
|
|
31
|
+
/** The step a step's display says. Throws for one that is not a step's. */
|
|
32
|
+
export declare function stepOfDisplay(display: Display): LoopStep;
|
|
33
33
|
/** The id of the row a step is drawn as. */
|
|
34
34
|
export declare const stepCallId: (id: string) => string;
|
|
35
35
|
/** A step as the row the chat draws a tool call as. */
|
|
@@ -2,21 +2,18 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
/** The
|
|
6
|
-
export const
|
|
7
|
-
/**
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const data =
|
|
12
|
-
if (!data
|
|
13
|
-
|
|
14
|
-
!data.id ||
|
|
15
|
-
typeof data.name !== 'string') {
|
|
16
|
-
return null;
|
|
5
|
+
/** The display type of a step. */
|
|
6
|
+
export const STEP_DISPLAY_TYPE = 'application/vnd.datalayer.loop.step+json';
|
|
7
|
+
/** Whether a display is a step's. */
|
|
8
|
+
export const isStepDisplay = (display) => display.type === STEP_DISPLAY_TYPE;
|
|
9
|
+
/** The step a step's display says. Throws for one that is not a step's. */
|
|
10
|
+
export function stepOfDisplay(display) {
|
|
11
|
+
const data = display.data ?? {};
|
|
12
|
+
if (!isStepDisplay(display) || typeof data.name !== 'string') {
|
|
13
|
+
throw new Error(`Display ${display.id} is not a step`);
|
|
17
14
|
}
|
|
18
15
|
return {
|
|
19
|
-
id:
|
|
16
|
+
id: display.id,
|
|
20
17
|
name: data.name,
|
|
21
18
|
kind: typeof data.kind === 'string' ? data.kind : 'run',
|
|
22
19
|
parentId: typeof data.parent_id === 'string' && data.parent_id
|
|
@@ -2,30 +2,25 @@
|
|
|
2
2
|
* Window messages (LOOP P-25): what an application's code tells the page it
|
|
3
3
|
* sits in, outside the conversation — ``session.send_window_message(data)``.
|
|
4
4
|
*
|
|
5
|
-
* The session
|
|
6
|
-
* `{data}`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The session says it with a `reactor.ui` `window` op named `message`, its
|
|
6
|
+
* `args` `{data}` (REACTOR_UI RU-3), on the conversation's ui socket (RU-W). The chat does not draw it: it hands
|
|
7
|
+
* it to whoever listens here — the embed's host bridge, which raises it on
|
|
8
|
+
* the element as its `window-message` event. A window op of another name is
|
|
9
|
+
* not one this page acts on: refused, it throws.
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
data: unknown;
|
|
16
|
-
} | undefined): {
|
|
17
|
-
data: unknown;
|
|
18
|
-
} | null;
|
|
11
|
+
import { type WindowOp } from '@datalayer/reactor/ui';
|
|
12
|
+
/** The name of the window op that hands the page a message. */
|
|
13
|
+
export declare const WINDOW_MESSAGE = "message";
|
|
14
|
+
/** What a window op hands the page. Throws for a window op of another name. */
|
|
15
|
+
export declare function windowMessageOf(op: WindowOp): unknown;
|
|
19
16
|
/** Hand a window message to whoever listens. */
|
|
20
17
|
export declare function tellLoopWindow(data: unknown): void;
|
|
21
18
|
/** Listen for window messages. Returns the unsubscribe. */
|
|
22
19
|
export declare function onLoopWindowMessage(listener: (data: unknown) => void): () => void;
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Why the turn a window message posted by the page ran failed — its
|
|
22
|
+
* `RUN_ERROR`'s sentence — or `null`. What its code tells the page back comes
|
|
23
|
+
* on the conversation's ui socket (REACTOR_UI RU-W), never on this stream: a
|
|
24
|
+
* `reactor.ui` event here is refused.
|
|
27
25
|
*/
|
|
28
|
-
export declare function
|
|
29
|
-
said: unknown[];
|
|
30
|
-
error: string | null;
|
|
31
|
-
};
|
|
26
|
+
export declare function windowTurnErrorOf(stream: string): string | null;
|
|
@@ -6,21 +6,25 @@
|
|
|
6
6
|
* Window messages (LOOP P-25): what an application's code tells the page it
|
|
7
7
|
* sits in, outside the conversation — ``session.send_window_message(data)``.
|
|
8
8
|
*
|
|
9
|
-
* The session
|
|
10
|
-
* `{data}`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* The session says it with a `reactor.ui` `window` op named `message`, its
|
|
10
|
+
* `args` `{data}` (REACTOR_UI RU-3), on the conversation's ui socket (RU-W). The chat does not draw it: it hands
|
|
11
|
+
* it to whoever listens here — the embed's host bridge, which raises it on
|
|
12
|
+
* the element as its `window-message` event. A window op of another name is
|
|
13
|
+
* not one this page acts on: refused, it throws.
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
import { AGUI_NAME } from '@datalayer/reactor/ui';
|
|
16
|
+
/** The name of the window op that hands the page a message. */
|
|
17
|
+
export const WINDOW_MESSAGE = 'message';
|
|
18
|
+
/** What a window op hands the page. Throws for a window op of another name. */
|
|
19
|
+
export function windowMessageOf(op) {
|
|
20
|
+
if (op.body.name !== WINDOW_MESSAGE) {
|
|
21
|
+
throw new Error(`A window op named ${op.body.name} is not one this page acts on`);
|
|
22
|
+
}
|
|
23
|
+
const args = op.body.args;
|
|
24
|
+
if (!args || !('data' in args)) {
|
|
25
|
+
throw new Error('A window message says no data');
|
|
26
|
+
}
|
|
27
|
+
return args.data;
|
|
24
28
|
}
|
|
25
29
|
const listeners = new Set();
|
|
26
30
|
/** Hand a window message to whoever listens. */
|
|
@@ -36,12 +40,12 @@ export function onLoopWindowMessage(listener) {
|
|
|
36
40
|
};
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
43
|
+
* Why the turn a window message posted by the page ran failed — its
|
|
44
|
+
* `RUN_ERROR`'s sentence — or `null`. What its code tells the page back comes
|
|
45
|
+
* on the conversation's ui socket (REACTOR_UI RU-W), never on this stream: a
|
|
46
|
+
* `reactor.ui` event here is refused.
|
|
42
47
|
*/
|
|
43
|
-
export function
|
|
44
|
-
const said = [];
|
|
48
|
+
export function windowTurnErrorOf(stream) {
|
|
45
49
|
let error = null;
|
|
46
50
|
for (const block of stream.split('\n\n')) {
|
|
47
51
|
const data = block
|
|
@@ -61,14 +65,9 @@ export function windowTurnOf(stream) {
|
|
|
61
65
|
if (event.type === 'RUN_ERROR') {
|
|
62
66
|
error = String(event.message ?? 'The turn failed.');
|
|
63
67
|
}
|
|
64
|
-
else if (event.type === 'CUSTOM') {
|
|
65
|
-
|
|
66
|
-
type: String(event.name),
|
|
67
|
-
data: event.value,
|
|
68
|
-
});
|
|
69
|
-
if (message)
|
|
70
|
-
said.push(message.data);
|
|
68
|
+
else if (event.type === 'CUSTOM' && event.name === AGUI_NAME) {
|
|
69
|
+
throw new Error('A reactor.ui op came on the run stream: it comes over the ui socket.');
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
|
-
return
|
|
72
|
+
return error;
|
|
74
73
|
}
|