@datalayer/agent-runtimes 1.3.86 → 1.3.88
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/lib/components/a2ui/datalayer/Download.d.ts +21 -0
- package/lib/components/a2ui/datalayer/Download.js +66 -0
- package/lib/components/a2ui/datalayer/drawn.d.ts +1 -1
- package/lib/components/a2ui/datalayer/drawn.js +1 -0
- package/lib/components/a2ui/datalayer/index.d.ts +2 -1
- package/lib/components/a2ui/datalayer/index.js +3 -0
- package/lib/components/inspector/a2aSpans.d.ts +9 -0
- package/lib/components/inspector/a2aSpans.js +11 -1
- package/lib/components/teams/A2ATeamGraph.d.ts +2 -2
- package/lib/components/teams/A2ATeamGraph.js +39 -22
- package/lib/components/teams/AnswerSurfaces.d.ts +56 -0
- package/lib/components/teams/AnswerSurfaces.js +27 -0
- package/lib/components/teams/AnswerSurfacesView.d.ts +14 -0
- package/lib/components/teams/AnswerSurfacesView.js +30 -0
- package/lib/components/teams/index.d.ts +1 -0
- package/lib/components/teams/index.js +1 -0
- package/lib/components/teams/useA2ATeam.d.ts +56 -2
- package/lib/components/teams/useA2ATeam.js +122 -3
- package/lib/hooks/useAgentRuntimes.d.ts +13 -0
- package/lib/hooks/useAgentRuntimes.js +11 -1
- package/lib/loop/apps/AppFeedback.js +11 -2
- package/lib/loop/apps/AppRenderer.d.ts +13 -1
- package/lib/loop/apps/AppRenderer.js +34 -1
- package/lib/loop/apps/AppRuntimePlugins.d.ts +8 -0
- package/lib/loop/apps/AppRuntimePlugins.js +87 -0
- package/lib/loop/apps/feedback.d.ts +7 -0
- package/lib/loop/apps/feedback.js +3 -0
- package/lib/loop/apps/pluginPair.d.ts +60 -0
- package/lib/loop/apps/pluginPair.js +44 -0
- package/lib/loop/apps/threads.d.ts +35 -0
- package/lib/loop/apps/threads.js +79 -0
- package/lib/loop/embed/LoopEmbed.d.ts +4 -3
- package/lib/loop/embed/hostBridge.d.ts +27 -0
- package/lib/loop/embed/hostBridge.js +47 -0
- package/lib/loop/index.d.ts +1 -1
- package/lib/loop/index.js +1 -1
- package/lib/loop/plugins/agent-capacity/index.d.ts +7 -0
- package/lib/loop/plugins/agent-capacity/index.js +3 -2
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +11 -0
- package/lib/loop/plugins/agents/appLaunch.d.ts +15 -0
- package/lib/loop/plugins/agents/appLaunch.js +28 -0
- package/lib/loop/plugins/chat/ChatView.js +177 -109
- package/lib/loop/shell/LoopWorkspace.d.ts +3 -3
- package/lib/loop/shell/LoopWorkspace.js +1 -1
- package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +10 -2
- package/lib/runtimes/actions.js +23 -0
- package/lib/runtimes/apis.d.ts +18 -0
- package/lib/runtimes/browser/a2aPeer.d.ts +19 -2
- package/lib/runtimes/browser/a2aPeer.js +13 -7
- package/lib/specs/apps.js +102 -30
- package/lib/specs/envvars.d.ts +2 -0
- package/lib/specs/envvars.js +22 -0
- package/lib/specs/outputs.d.ts +1 -0
- package/lib/specs/outputs.js +12 -0
- package/lib/specs/sceneSchema.d.ts +8 -0
- package/lib/specs/sceneSchema.js +863 -0
- package/lib/specs/scenes.js +238 -121
- package/lib/specs/teams/teams.js +23 -7
- package/lib/specs/uiPlugins.js +55 -0
- package/lib/stores/agentRuntimeStore.js +4 -0
- package/lib/types/chat.d.ts +6 -0
- package/lib/types/scenes.d.ts +7 -1
- package/package.json +1 -1
- package/scripts/codegen/generate_envvars.py +1 -1
- package/scripts/codegen/generate_mcp_servers.py +11 -7
- package/scripts/codegen/generate_scenes.py +43 -1
|
@@ -46,6 +46,8 @@ import { messageText, turnWritersOf } from './turnState';
|
|
|
46
46
|
import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
|
|
47
47
|
import { useAgentCommandTools } from '../../../tools/adapters/commands';
|
|
48
48
|
import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
|
|
49
|
+
import { generateMessageId } from '../../../types/messages';
|
|
50
|
+
import { openThread, THREAD_WORDS } from '../../apps/threads';
|
|
49
51
|
import { FaceDrawing, PresenceFace, PresenceLine, } from '../../../chat/presence/Presence';
|
|
50
52
|
import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
|
|
51
53
|
import { latestSaying, newestIsAnswer, } from '../../../chat/assistant/state';
|
|
@@ -1180,6 +1182,74 @@ export default function ChatView({ workspace }) {
|
|
|
1180
1182
|
// not know (LOOP R-20): it runs a session of its deployment, and no more.
|
|
1181
1183
|
const embedToken = reactor.getConfig(AGENTS_PLUGIN_NAME)?.embedToken;
|
|
1182
1184
|
const iamToken = visitors ? visitor.token : embedToken || memberToken;
|
|
1185
|
+
/*
|
|
1186
|
+
* A thread opened from the person's history (LOOP P-24): its conversation
|
|
1187
|
+
* read from the runtime once it answers — held there, else resumed from
|
|
1188
|
+
* its record — then drawn and gone on with on the same thread. One that
|
|
1189
|
+
* cannot be opened is said, and a new one starts.
|
|
1190
|
+
*/
|
|
1191
|
+
const givenThread = config?.thread;
|
|
1192
|
+
const [openedThread, setOpenedThread] = useState(null);
|
|
1193
|
+
const opening = Boolean(givenThread?.fromRuntime && openedThread?.given !== givenThread.id);
|
|
1194
|
+
useEffect(() => {
|
|
1195
|
+
if (!givenThread?.fromRuntime || !runsApp || !agentServerUrl) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
let current = true;
|
|
1199
|
+
void (async () => {
|
|
1200
|
+
let failure = '';
|
|
1201
|
+
for (let attempt = 0; attempt < 4 && current; attempt += 1) {
|
|
1202
|
+
try {
|
|
1203
|
+
const messages = await openThread({
|
|
1204
|
+
agentBaseUrl: agentServerUrl,
|
|
1205
|
+
agentId,
|
|
1206
|
+
uid: givenThread.id,
|
|
1207
|
+
token: iamToken,
|
|
1208
|
+
});
|
|
1209
|
+
if (current) {
|
|
1210
|
+
setOpenedThread({
|
|
1211
|
+
given: givenThread.id,
|
|
1212
|
+
thread: { ...givenThread, fromRuntime: false, messages },
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
1219
|
+
// The runtime may still be making the application's agent.
|
|
1220
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (current) {
|
|
1224
|
+
setOpenedThread({
|
|
1225
|
+
given: givenThread.id,
|
|
1226
|
+
thread: {
|
|
1227
|
+
id: generateMessageId(),
|
|
1228
|
+
messages: [],
|
|
1229
|
+
onStarted: givenThread.onStarted,
|
|
1230
|
+
},
|
|
1231
|
+
said: THREAD_WORDS.notOpened(failure),
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
})();
|
|
1235
|
+
return () => {
|
|
1236
|
+
current = false;
|
|
1237
|
+
};
|
|
1238
|
+
// By the thread's id: its object is the host's.
|
|
1239
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1240
|
+
}, [
|
|
1241
|
+
givenThread?.id,
|
|
1242
|
+
givenThread?.fromRuntime,
|
|
1243
|
+
runsApp,
|
|
1244
|
+
agentServerUrl,
|
|
1245
|
+
agentId,
|
|
1246
|
+
iamToken,
|
|
1247
|
+
]);
|
|
1248
|
+
const chatThread = givenThread?.fromRuntime
|
|
1249
|
+
? openedThread?.given === givenThread.id
|
|
1250
|
+
? openedThread.thread
|
|
1251
|
+
: undefined
|
|
1252
|
+
: givenThread;
|
|
1183
1253
|
const protocol = useMemo(() => inPage
|
|
1184
1254
|
? browserProtocolConfig({
|
|
1185
1255
|
agentId: member?.specId ?? agentId,
|
|
@@ -1561,44 +1631,42 @@ export default function ChatView({ workspace }) {
|
|
|
1561
1631
|
borderColor: chatExtras.errorBanner.variant === 'warning'
|
|
1562
1632
|
? 'attention.muted'
|
|
1563
1633
|
: 'danger.muted',
|
|
1564
|
-
}, children: chatExtras.errorBanner.message })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: _jsx(ChatBase
|
|
1634
|
+
}, children: chatExtras.errorBanner.message })) : null, openedThread?.said && openedThread.given === givenThread?.id ? (_jsx(Text, { as: "p", role: "status", sx: { m: 0, px: 3, py: 2, fontSize: 1, color: 'fg.muted' }, children: openedThread.said })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: opening ? (_jsx(Text, { as: "p", role: "status", sx: { m: 0, p: 3, fontSize: 1, color: 'fg.muted' }, children: THREAD_WORDS.opening })) : (_jsx(ChatBase
|
|
1565
1635
|
// The theme it wears, when the host names one (an application's).
|
|
1566
1636
|
, {
|
|
1567
1637
|
// The theme it wears, when the host names one (an application's).
|
|
1568
|
-
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode,
|
|
1569
|
-
// The session an embed goes on with after a reload (LOOP D-13).
|
|
1570
|
-
thread: config?.thread,
|
|
1638
|
+
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode, thread: chatThread,
|
|
1571
1639
|
// The header says why, beside the title, for the same reason the
|
|
1572
1640
|
// placeholder does: a dead control with no explanation is worse
|
|
1573
1641
|
// than an absent one.
|
|
1574
1642
|
disabled: chatDisabled, disableReason: disabledReason, protocol: protocol,
|
|
1575
1643
|
/*
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1644
|
+
The model this view is on, as the chat's opening pick.
|
|
1645
|
+
|
|
1646
|
+
`ChatBase` keeps the model it sends with, and opened on the
|
|
1647
|
+
first row of whatever catalogue it had before the server
|
|
1648
|
+
answered — Alibaba — while the footer, drawn from
|
|
1649
|
+
`activeModel`, said Claude. Told the same model, both agree
|
|
1650
|
+
from the first message.
|
|
1651
|
+
*/
|
|
1584
1652
|
initialModel: activeModel || undefined,
|
|
1585
1653
|
/*
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1654
|
+
Who is answering, in the words its spec uses.
|
|
1655
|
+
|
|
1656
|
+
`ChatBase` falls back to "Start a conversation with the AI
|
|
1657
|
+
agent", which is true of every chat ever built and says
|
|
1658
|
+
nothing about this one. The empty state is the first thing a
|
|
1659
|
+
person sees and the only place the agent introduces itself.
|
|
1660
|
+
*/
|
|
1593
1661
|
title: presence?.name ?? member?.name ?? spec?.name ?? agentId, description: presence?.welcome ?? spec?.description,
|
|
1594
1662
|
/*
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1663
|
+
Two sizes, because it is drawn in two places.
|
|
1664
|
+
|
|
1665
|
+
The header wants a mark beside a line of text; the empty state
|
|
1666
|
+
wants the thing a person's eye lands on first. One `brandIcon`
|
|
1667
|
+
served both, so a 48px icon meant for the empty state sat in
|
|
1668
|
+
the header at three times the height of the words next to it.
|
|
1669
|
+
*/
|
|
1602
1670
|
// Big enough to read as the agent's mark rather than as
|
|
1603
1671
|
// punctuation before its name, and still short enough not to
|
|
1604
1672
|
// set the header's height.
|
|
@@ -1615,11 +1683,11 @@ export default function ChatView({ workspace }) {
|
|
|
1615
1683
|
// conversation".
|
|
1616
1684
|
title: presence?.name ?? member?.name ?? spec?.name ?? agentId,
|
|
1617
1685
|
/*
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1686
|
+
Two levels when there is a team: the team first — its name,
|
|
1687
|
+
what it is for, what it can be asked — and under it the
|
|
1688
|
+
member being addressed, with its own description and its
|
|
1689
|
+
own openers. The groups match the ones the openers carry.
|
|
1690
|
+
*/
|
|
1623
1691
|
...(team && member
|
|
1624
1692
|
? {
|
|
1625
1693
|
sections: [
|
|
@@ -1640,11 +1708,11 @@ export default function ChatView({ workspace }) {
|
|
|
1640
1708
|
: null),
|
|
1641
1709
|
},
|
|
1642
1710
|
/*
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1711
|
+
And what it can be asked. From the team rather than the
|
|
1712
|
+
member: the supervisor answers first, and somebody who has
|
|
1713
|
+
just opened the workspace does not yet know there are two
|
|
1714
|
+
agents behind it.
|
|
1715
|
+
*/
|
|
1648
1716
|
// With the prompt on top the openers are already chips under
|
|
1649
1717
|
// it; the empty state repeating them was the same three buttons
|
|
1650
1718
|
// twice on one screen.
|
|
@@ -1652,11 +1720,11 @@ export default function ChatView({ workspace }) {
|
|
|
1652
1720
|
? []
|
|
1653
1721
|
: chatSuggestions,
|
|
1654
1722
|
/*
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1723
|
+
The title bar arrives as a plugin: the chat-header plugin
|
|
1724
|
+
contributes the component, this view hands it the assembled
|
|
1725
|
+
props through `renderHeader`. No contribution — or a host
|
|
1726
|
+
that set `hideHeader` — means no bar.
|
|
1727
|
+
*/
|
|
1660
1728
|
showHeader: !config?.hideHeader && !!HeaderComponent,
|
|
1661
1729
|
// The `+` and the bin in the title bar, when the host asked for
|
|
1662
1730
|
// them; the header draws whatever `ChatBase` assembles here.
|
|
@@ -1667,19 +1735,19 @@ export default function ChatView({ workspace }) {
|
|
|
1667
1735
|
? headerProps => (_jsx(HeaderComponent, { workspace: workspace, header: headerProps }))
|
|
1668
1736
|
: undefined,
|
|
1669
1737
|
/*
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1738
|
+
The (i), which opens the agent's details over the transcript.
|
|
1739
|
+
|
|
1740
|
+
Worth having here in particular: this workspace can be moved
|
|
1741
|
+
between four runtimes and several agents, and the details pane
|
|
1742
|
+
is the only place that says which one is actually answering
|
|
1743
|
+
and where it is running.
|
|
1744
|
+
*/
|
|
1677
1745
|
showInformation: true,
|
|
1678
1746
|
/*
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1747
|
+
Full screen: this view, on the whole screen, editor and all.
|
|
1748
|
+
See `toggleFullScreen` for why it is the browser's API rather
|
|
1749
|
+
than a big box.
|
|
1750
|
+
*/
|
|
1683
1751
|
headerActions: _jsxs(Box, { sx: { display: 'inline-flex', alignItems: 'center', gap: 2 }, children: [workspace.chatHeaderActions, chatHeaderItems.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.chatHeader, props: { workspace } })) : null, _jsx(IconButton, { icon: fullScreen ? ScreenNormalIcon : ScreenFullIcon, "aria-label": fullScreen ? 'Exit full screen' : 'Enter full screen', variant: "invisible", size: "small", onClick: () => {
|
|
1684
1752
|
// Whether or not it takes, the hint has been answered:
|
|
1685
1753
|
// they found the control, which is all it was for.
|
|
@@ -1688,62 +1756,62 @@ export default function ChatView({ workspace }) {
|
|
|
1688
1756
|
}, sx: hintFullScreen
|
|
1689
1757
|
? {
|
|
1690
1758
|
/*
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1759
|
+
Colour and opacity, never geometry.
|
|
1760
|
+
|
|
1761
|
+
The colour is the theme's own `primary` — the
|
|
1762
|
+
same brand the page around this workspace is
|
|
1763
|
+
already using — because a grey icon in a row of
|
|
1764
|
+
grey icons cannot be picked out however hard it
|
|
1765
|
+
fades. Colour is what makes it findable; the
|
|
1766
|
+
fade is what makes it look like it is asking.
|
|
1767
|
+
|
|
1768
|
+
It never fades to nothing: a control that
|
|
1769
|
+
vanishes and returns reads as a rendering fault,
|
|
1770
|
+
not an invitation. And nothing here moves or
|
|
1771
|
+
resizes, so the header does not reflow and the
|
|
1772
|
+
controls beside it stay where a reader last saw
|
|
1773
|
+
them.
|
|
1774
|
+
*/
|
|
1707
1775
|
'@keyframes dla-fullscreen-hint': {
|
|
1708
1776
|
'0%, 100%': { opacity: 1 },
|
|
1709
1777
|
'50%': { opacity: 0.4 },
|
|
1710
1778
|
},
|
|
1711
1779
|
/* `&&` doubles the specificity, because Primer
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1780
|
+
ships Button as a CSS module and
|
|
1781
|
+
`prc-Button-*` outranks a single generated
|
|
1782
|
+
class — without it the keyframes register and
|
|
1783
|
+
the button sits there at `animation: none`.
|
|
1784
|
+
The colour needs the same treatment: an
|
|
1785
|
+
invisible IconButton sets `color` itself.
|
|
1786
|
+
|
|
1787
|
+
And no `:focus` or `:hover` clause to stop it:
|
|
1788
|
+
anything that matches the button while it is
|
|
1789
|
+
being pointed at would cancel the pointing —
|
|
1790
|
+
which is exactly how the first version of this
|
|
1791
|
+
silently did nothing. It ends when the reader
|
|
1792
|
+
takes the offer, or arrives without it. */
|
|
1725
1793
|
'&&': {
|
|
1726
1794
|
/*
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1795
|
+
The brand on the box, not on the glyph.
|
|
1796
|
+
|
|
1797
|
+
Painting the icon `palette.primary` was the
|
|
1798
|
+
obvious reading and it is measurably
|
|
1799
|
+
backwards: in the theme this workspace ships
|
|
1800
|
+
on, `primary` is #FFC107, which sits at 1.55
|
|
1801
|
+
contrast against the header — where the
|
|
1802
|
+
icon's ordinary olive sits at 5.07. Recolouring
|
|
1803
|
+
the stroke made the control roughly three
|
|
1804
|
+
times harder to see, and at the bottom of the
|
|
1805
|
+
fade it was 1.2 and simply gone.
|
|
1806
|
+
|
|
1807
|
+
A ring and a wash carry the same colour on a
|
|
1808
|
+
far larger area, and they do it without asking
|
|
1809
|
+
the glyph to be legible in a brand colour that
|
|
1810
|
+
changes with every theme — this palette has
|
|
1811
|
+
six, from a dark teal to this amber, and a
|
|
1812
|
+
rule that works for one fails for another. The
|
|
1813
|
+
glyph keeps the colour it can be read in.
|
|
1814
|
+
*/
|
|
1747
1815
|
bg: `color-mix(in srgb, ${palette.primary} 16%, transparent)`,
|
|
1748
1816
|
boxShadow: `inset 0 0 0 1.5px ${palette.primary}`,
|
|
1749
1817
|
animation: `dla-fullscreen-hint ${FULLSCREEN_HINT_PERIOD_MS}ms ease-in-out infinite`,
|
|
@@ -1761,18 +1829,18 @@ export default function ChatView({ workspace }) {
|
|
|
1761
1829
|
// second one: two input boxes on one screen is one too many.
|
|
1762
1830
|
showInput: false, onSendReady: handleSendReady,
|
|
1763
1831
|
/*
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1832
|
+
The notebook tools' results, drawn into the transcript — the
|
|
1833
|
+
chat's own machinery, switched on only while no editor is on
|
|
1834
|
+
screen: the hidden notebook is where the agent works, and the
|
|
1835
|
+
transcript is then the one place the reader can see what a
|
|
1836
|
+
tool did. With an editor open the change is visible where it
|
|
1837
|
+
happened, and the default tool row is enough.
|
|
1838
|
+
*/
|
|
1771
1839
|
notebookToolSurfacesId: active ? undefined : workspace.surfaceId,
|
|
1772
1840
|
// A host example's own tool-result renderer, when it feeds one
|
|
1773
1841
|
// through the extras channel — the A2UI examples draw their
|
|
1774
1842
|
// surface here. Wins over the notebook surfaces in ChatBase.
|
|
1775
|
-
renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }) }), besideChat && !promptHidden ? prompt : null] }) }));
|
|
1843
|
+
renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }, chatThread?.id)) }), besideChat && !promptHidden ? prompt : null] }) }));
|
|
1776
1844
|
// Off when the host asked for the menu alone: the composer's suggestions
|
|
1777
1845
|
// control carries the same openers (`suggestions` above).
|
|
1778
1846
|
const chipsEl = config?.suggestionLabels !== false && chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": chatText.suggestedPrompts, sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { JSX } from 'react';
|
|
23
23
|
import type { ReactNode } from 'react';
|
|
24
|
-
import { type PluginRef, type ReactorPlatform } from '@datalayer/reactor';
|
|
24
|
+
import { type PlatformInput, type PluginRef, type ReactorPlatform } from '@datalayer/reactor';
|
|
25
25
|
import { type LoopWorkspaceContext, type SandboxSnapshot } from '../core';
|
|
26
26
|
export type LoopWorkspaceProps = {
|
|
27
27
|
/** Server backing this session. */
|
|
@@ -125,6 +125,6 @@ export type LoopWorkspaceProps = {
|
|
|
125
125
|
*/
|
|
126
126
|
sidebarRail?: boolean;
|
|
127
127
|
};
|
|
128
|
-
/** Build the platform for a set of plugins. */
|
|
129
|
-
export declare function buildLoopReactor(extensions:
|
|
128
|
+
/** Build the platform for a set of plugins, and of extensions grouping them. */
|
|
129
|
+
export declare function buildLoopReactor(extensions: PlatformInput[]): ReactorPlatform;
|
|
130
130
|
export declare function LoopWorkspace(props: LoopWorkspaceProps): JSX.Element;
|
|
@@ -8,7 +8,7 @@ import { LoopCommand, LoopSlots, LoopViewType, canOpenView, createPromptChannel,
|
|
|
8
8
|
import { SIDEBAR_WIDTH } from '../plugins/plugins-panel';
|
|
9
9
|
import { ViewSwitcher } from './ViewSwitcher';
|
|
10
10
|
import { SidebarRail } from './SidebarRail';
|
|
11
|
-
/** Build the platform for a set of plugins. */
|
|
11
|
+
/** Build the platform for a set of plugins, and of extensions grouping them. */
|
|
12
12
|
export function buildLoopReactor(extensions) {
|
|
13
13
|
return buildReactorFromPlugins(extensions);
|
|
14
14
|
}
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"description": "Answers requests for financial reports, such as open invoices, aged balances, a trial balance or a customer's ledger, from the Odoo books, which it only reads.",
|
|
17
17
|
"tags": ["example", "accounting", "finance", "odoo", "a2a", "team"],
|
|
18
18
|
"inputModes": ["text/plain"],
|
|
19
|
-
"outputModes": [
|
|
19
|
+
"outputModes": [
|
|
20
|
+
"text/markdown",
|
|
21
|
+
"application/x-ipynb+json",
|
|
22
|
+
"application/json+a2ui"
|
|
23
|
+
],
|
|
20
24
|
"examples": [
|
|
21
25
|
"List the customer invoices that are still open, with the total due.",
|
|
22
26
|
"Give the aged receivables as of today, by customer.",
|
|
@@ -25,7 +29,11 @@
|
|
|
25
29
|
}
|
|
26
30
|
],
|
|
27
31
|
"defaultInputModes": ["text/plain"],
|
|
28
|
-
"defaultOutputModes": [
|
|
32
|
+
"defaultOutputModes": [
|
|
33
|
+
"text/markdown",
|
|
34
|
+
"application/x-ipynb+json",
|
|
35
|
+
"application/json+a2ui"
|
|
36
|
+
],
|
|
29
37
|
"capabilities": {
|
|
30
38
|
"streaming": true,
|
|
31
39
|
"pushNotifications": false,
|
package/lib/runtimes/actions.js
CHANGED
|
@@ -49,6 +49,20 @@ export async function createRuntime(options) {
|
|
|
49
49
|
throw new Error(`Invalid runtime creditsLimit for environment ${options.environmentName}. ` +
|
|
50
50
|
'A number of zero or more is required.');
|
|
51
51
|
}
|
|
52
|
+
/*
|
|
53
|
+
* An application's launch carries its Appspec (STUDIO R-19), as
|
|
54
|
+
* `RuntimesClient.create` does in Python: the Operator hands it to the
|
|
55
|
+
* companion, which gives the runtime the secrets its connections declare
|
|
56
|
+
* before its agent is made. Refused here, before any request, as the
|
|
57
|
+
* Operator would refuse it (422).
|
|
58
|
+
*/
|
|
59
|
+
if (options.deploymentUid && !options.appUid) {
|
|
60
|
+
throw new Error(`Deployment ${options.deploymentUid} is named without its application: give appUid.`);
|
|
61
|
+
}
|
|
62
|
+
if (options.appUid && !options.appSpec) {
|
|
63
|
+
throw new Error(`Application ${options.appUid} is launched without its Appspec: give appSpec, ` +
|
|
64
|
+
'so its runtime is given the secrets its connections declare.');
|
|
65
|
+
}
|
|
52
66
|
/*
|
|
53
67
|
* `environment: {name, version}` — and the version is ADDITIVE.
|
|
54
68
|
*
|
|
@@ -75,6 +89,15 @@ export async function createRuntime(options) {
|
|
|
75
89
|
if (options.snapshot) {
|
|
76
90
|
body['from'] = options.snapshot;
|
|
77
91
|
}
|
|
92
|
+
if (options.appUid) {
|
|
93
|
+
body['app_uid'] = options.appUid;
|
|
94
|
+
}
|
|
95
|
+
if (options.deploymentUid) {
|
|
96
|
+
body['deployment_uid'] = options.deploymentUid;
|
|
97
|
+
}
|
|
98
|
+
if (options.appSpec) {
|
|
99
|
+
body['app_spec'] = options.appSpec;
|
|
100
|
+
}
|
|
78
101
|
const data = await requestDatalayerAPI({
|
|
79
102
|
url: runtimesUrl(runtimesStore.getState().runtimesUrl),
|
|
80
103
|
method: 'POST',
|
package/lib/runtimes/apis.d.ts
CHANGED
|
@@ -51,6 +51,24 @@ export interface IRuntimeOptions {
|
|
|
51
51
|
* Kernel snapshot to restore
|
|
52
52
|
*/
|
|
53
53
|
snapshot?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The Appspec of the application the runtime is launched for (STUDIO
|
|
56
|
+
* R-19), as its file holds it. The Operator hands it to the runtime's
|
|
57
|
+
* companion, which gives the runtime the secrets its connections declare
|
|
58
|
+
* before its agent is made. An application's launch carries it:
|
|
59
|
+
* `appUid` without it is refused.
|
|
60
|
+
*/
|
|
61
|
+
appSpec?: Record<string, unknown>;
|
|
62
|
+
/**
|
|
63
|
+
* The saved application the runtime runs for (LOOP R-09): written on its
|
|
64
|
+
* reservation, so its running time is counted per application.
|
|
65
|
+
*/
|
|
66
|
+
appUid?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The deployment of that application it runs for. A deployment is always
|
|
69
|
+
* of an application: one named without `appUid` is refused.
|
|
70
|
+
*/
|
|
71
|
+
deploymentUid?: string;
|
|
54
72
|
}
|
|
55
73
|
/**
|
|
56
74
|
* Interface for the Environments Manager
|
|
@@ -85,6 +85,21 @@ export type A2APeerToolStep = {
|
|
|
85
85
|
export declare const TEXT_MEDIA_TYPES: readonly string[];
|
|
86
86
|
/** A Jupyter notebook, as A2A carries it. */
|
|
87
87
|
export declare const NOTEBOOK_MEDIA_TYPE = "application/x-ipynb+json";
|
|
88
|
+
/**
|
|
89
|
+
* Components of the catalog, as A2A carries them: one A2UI surface per
|
|
90
|
+
* artifact (`{surfaceId, catalogId, title, messages}`), A2UI's media type.
|
|
91
|
+
*/
|
|
92
|
+
export declare const A2UI_MEDIA_TYPE = "application/json+a2ui";
|
|
93
|
+
/**
|
|
94
|
+
* A button pressed on an answer's surface, sent back with the reader's turn:
|
|
95
|
+
* its name and what choosing it does (`payload.does`). The runtime reads it
|
|
96
|
+
* from the message's metadata (`loop.action`): one that does more than read
|
|
97
|
+
* is refused to a visitor in a sentence, before any model runs.
|
|
98
|
+
*/
|
|
99
|
+
export type A2APeerAction = {
|
|
100
|
+
name: string;
|
|
101
|
+
payload: Record<string, unknown>;
|
|
102
|
+
};
|
|
88
103
|
/**
|
|
89
104
|
* What a peer gave besides words: one part of an artifact, by its media type.
|
|
90
105
|
*
|
|
@@ -135,8 +150,8 @@ export declare function artifactsOf(artifact: {
|
|
|
135
150
|
name?: string;
|
|
136
151
|
parts?: Part[];
|
|
137
152
|
} | undefined): A2APeerArtifact[];
|
|
138
|
-
/** A user's message of one text part, as the SDK's 1.x types write it. */
|
|
139
|
-
export declare function textMessage(text: string): Message;
|
|
153
|
+
/** A user's message of one text part, as the SDK's 1.x types write it; a pressed button's action in its metadata. */
|
|
154
|
+
export declare function textMessage(text: string, action?: A2APeerAction): Message;
|
|
140
155
|
/**
|
|
141
156
|
* What a working status says while the peer works: in a few words, and the
|
|
142
157
|
* tool step when it is one.
|
|
@@ -159,6 +174,8 @@ export type AskA2APeerOptions = {
|
|
|
159
174
|
* the peer answers in words alone.
|
|
160
175
|
*/
|
|
161
176
|
accept?: readonly string[];
|
|
177
|
+
/** The button pressed on one of its answers' surfaces, when this request is one. */
|
|
178
|
+
action?: A2APeerAction;
|
|
162
179
|
};
|
|
163
180
|
/**
|
|
164
181
|
* Ask a peer once, streaming the task, and return its answer.
|
|
@@ -92,9 +92,15 @@ export const TEXT_MEDIA_TYPES = [
|
|
|
92
92
|
];
|
|
93
93
|
/** A Jupyter notebook, as A2A carries it. */
|
|
94
94
|
export const NOTEBOOK_MEDIA_TYPE = 'application/x-ipynb+json';
|
|
95
|
+
/**
|
|
96
|
+
* Components of the catalog, as A2A carries them: one A2UI surface per
|
|
97
|
+
* artifact (`{surfaceId, catalogId, title, messages}`), A2UI's media type.
|
|
98
|
+
*/
|
|
99
|
+
export const A2UI_MEDIA_TYPE = 'application/json+a2ui';
|
|
95
100
|
/** What a format is, in words, for a model reading a tool's description. */
|
|
96
101
|
const FORMAT_NAMES = {
|
|
97
102
|
[NOTEBOOK_MEDIA_TYPE]: 'a Jupyter notebook',
|
|
103
|
+
[A2UI_MEDIA_TYPE]: 'components drawn for the person: the sources as cards, a table, a chart, a choice of buttons',
|
|
98
104
|
};
|
|
99
105
|
/** The text of a message's or an artifact's parts. */
|
|
100
106
|
export function textOf(parts) {
|
|
@@ -131,8 +137,8 @@ export function artifactsOf(artifact) {
|
|
|
131
137
|
}
|
|
132
138
|
return found;
|
|
133
139
|
}
|
|
134
|
-
/** A user's message of one text part, as the SDK's 1.x types write it. */
|
|
135
|
-
export function textMessage(text) {
|
|
140
|
+
/** A user's message of one text part, as the SDK's 1.x types write it; a pressed button's action in its metadata. */
|
|
141
|
+
export function textMessage(text, action) {
|
|
136
142
|
return {
|
|
137
143
|
messageId: globalThis.crypto.randomUUID(),
|
|
138
144
|
contextId: '',
|
|
@@ -146,7 +152,7 @@ export function textMessage(text) {
|
|
|
146
152
|
mediaType: '',
|
|
147
153
|
},
|
|
148
154
|
],
|
|
149
|
-
metadata: undefined,
|
|
155
|
+
metadata: action ? { loop: { action } } : undefined,
|
|
150
156
|
extensions: [],
|
|
151
157
|
referenceTaskIds: [],
|
|
152
158
|
};
|
|
@@ -205,14 +211,14 @@ const ENDED_BADLY = {
|
|
|
205
211
|
* completed is thrown, with what the peer said.
|
|
206
212
|
*/
|
|
207
213
|
export async function askA2APeer(peer, request, options = {}) {
|
|
208
|
-
const { signal, onEvent, accept } = options;
|
|
214
|
+
const { signal, onEvent, accept, action } = options;
|
|
209
215
|
onEvent?.({
|
|
210
216
|
phase: 'asked',
|
|
211
217
|
request,
|
|
212
218
|
...(accept?.length ? { accept: [...accept] } : {}),
|
|
213
219
|
});
|
|
214
220
|
try {
|
|
215
|
-
return await streamTask(peer, request, signal, onEvent, accept);
|
|
221
|
+
return await streamTask(peer, request, signal, onEvent, accept, action);
|
|
216
222
|
}
|
|
217
223
|
catch (reason) {
|
|
218
224
|
const error = reason instanceof Error ? reason.message : String(reason);
|
|
@@ -220,7 +226,7 @@ export async function askA2APeer(peer, request, options = {}) {
|
|
|
220
226
|
throw reason;
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
|
-
async function streamTask(peer, request, signal, onEvent, accept) {
|
|
229
|
+
async function streamTask(peer, request, signal, onEvent, accept, action) {
|
|
224
230
|
const texts = new Map();
|
|
225
231
|
// What it gave besides words, by artifact: the last chunk says it whole.
|
|
226
232
|
const given = new Map();
|
|
@@ -229,7 +235,7 @@ async function streamTask(peer, request, signal, onEvent, accept) {
|
|
|
229
235
|
let ended;
|
|
230
236
|
const stream = peer.client.sendMessageStream({
|
|
231
237
|
tenant: '',
|
|
232
|
-
message: textMessage(request),
|
|
238
|
+
message: textMessage(request, action),
|
|
233
239
|
configuration: accept?.length
|
|
234
240
|
? {
|
|
235
241
|
acceptedOutputModes: [...accept],
|