@datalayer/agent-runtimes 1.3.62 → 1.3.63

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.
@@ -275,6 +275,11 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
275
275
  '&[data-assistant-state="goodbye"] .assistant-body': {
276
276
  animation: 'assistantLeave 0.6s ease-in forwards',
277
277
  },
278
+ // A sprite plays its own goodbye, and leaves as the drawn ones do:
279
+ // a character whose file has no goodbye would otherwise stay.
280
+ '&[data-assistant-state="goodbye"] .assistant-sprite': {
281
+ animation: 'assistantLeave 0.6s ease-in forwards',
282
+ },
278
283
  // One still frame per state for a reader who asks for no motion.
279
284
  '@media (prefers-reduced-motion: reduce)': {
280
285
  '& *': { animation: 'none !important' },
@@ -51,7 +51,7 @@ export function SpriteCharacter({ character, state, size, }) {
51
51
  }, [animation, frame, play, character, state, still]);
52
52
  const { width, height } = character.frameSize;
53
53
  const scale = size / Math.max(width, height);
54
- return (_jsx("span", { role: "img", "aria-label": character.name, "data-sprite-animation": play.name, style: {
54
+ return (_jsx("span", { className: "assistant-sprite", role: "img", "aria-label": character.name, "data-sprite-animation": play.name, style: {
55
55
  position: 'relative',
56
56
  display: 'block',
57
57
  width: size,
@@ -40,7 +40,6 @@ const WIZARD = {
40
40
  brim: '#4A3C9E',
41
41
  robe: '#5B4BB7',
42
42
  face: '#F2D3B3',
43
- faceEdge: '#A0694A',
44
43
  beard: '#F4F4F6',
45
44
  beardEdge: '#7A808A',
46
45
  star: '#F8D469',
@@ -51,7 +50,6 @@ const WIZARD = {
51
50
  brim: '#7B67DB',
52
51
  robe: '#8F7CE8',
53
52
  face: '#E8C4A0',
54
- faceEdge: '#E8C4A0',
55
53
  beard: '#E4E4EA',
56
54
  beardEdge: loopColors.grayDark,
57
55
  star: '#F8D469',
@@ -100,7 +98,7 @@ function PaperClip({ size, mode = 'light', }) {
100
98
  /** A wizard: a starred hat, a white beard, kind eyes. */
101
99
  function Wizard({ size, mode = 'light', }) {
102
100
  const c = WIZARD[mode];
103
- return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "Wizard", "data-assistant-mode": mode, children: _jsxs("g", { className: "assistant-body", children: [_jsx("path", { d: "M24 92 Q48 58 72 92 Z", fill: c.robe }), _jsx("circle", { cx: 48, cy: 48, r: 17, fill: c.face, stroke: c.faceEdge, strokeWidth: 1.5 }), _jsx("path", { d: "M31 50 Q48 92 65 50 Q57 60 48 60 Q39 60 31 50 Z", fill: c.beard, stroke: c.beardEdge, strokeWidth: 1.2 }), _jsx("path", { d: "M22 36 L48 2 L74 36 Q48 30 22 36 Z", fill: c.hat }), _jsx("path", { d: "M20 37 Q48 29 76 37 Q48 43 20 37 Z", fill: c.brim }), _jsx("path", { d: "M52 14 l2 4 4 1 -3 3 1 4 -4 -2 -4 2 1 -4 -3 -3 4 -1 Z", fill: c.star }), _jsx("circle", { cx: 40, cy: 26, r: 1.8, fill: c.star }), _jsx(Eyes, { cx: 48, cy: 46, gap: 13, r: 5, colours: c.eye }), _jsx("path", { className: "assistant-mouth", d: "M44 58 Q48 61 52 58", stroke: INK, strokeWidth: 2, fill: "none", strokeLinecap: "round" })] }) }));
101
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "Wizard", "data-assistant-mode": mode, children: _jsxs("g", { className: "assistant-body", children: [_jsx("path", { d: "M34 58 Q48 52 62 58 Q70 74 76 92 Q48 98 20 92 Q26 74 34 58 Z", fill: c.robe }), _jsx("path", { d: "M21 90 Q48 96 75 90 L76 92 Q48 98 20 92 Z", fill: c.brim }), _jsx("circle", { cx: 30, cy: 82, r: 1.6, fill: c.star }), _jsx("circle", { cx: 67, cy: 78, r: 1.3, fill: c.star }), _jsx("ellipse", { cx: 48, cy: 46, rx: 13, ry: 12, fill: c.face }), _jsx("path", { d: "M33 38 Q31 66 40 77 Q48 89 56 77 Q65 66 63 38 Q61 50 56 53 Q48 58 40 53 Q35 50 33 38 Z", fill: c.beard, stroke: c.beardEdge, strokeWidth: 1, strokeLinejoin: "round" }), _jsx("path", { className: "assistant-mouth", d: "M44 59 Q48 62 52 59", stroke: INK, strokeWidth: 1.8, fill: "none", strokeLinecap: "round" }), _jsx("path", { d: "M39 55 Q44 51 48 54 Q52 51 57 55 Q52 57 48 56 Q44 57 39 55 Z", fill: c.beard, stroke: c.beardEdge, strokeWidth: 0.8 }), _jsx("path", { d: "M25 36 Q35 26 39 14 Q43 2 58 3 Q50 8 53 18 Q58 30 71 36 Q48 30 25 36 Z", fill: c.hat }), _jsx("path", { d: "M18 37 Q48 27 78 37 Q48 45 18 37 Z", fill: c.brim }), _jsx("path", { d: "M47 17 l1.8 3.6 4 .6 -2.9 2.8 .7 4 -3.6 -1.9 -3.6 1.9 .7 -4 -2.9 -2.8 4 -.6 Z", fill: c.star }), _jsx("circle", { cx: 56, cy: 28, r: 1.4, fill: c.star }), _jsx(Eyes, { cx: 48, cy: 46, gap: 11, r: 4, colours: c.eye })] }) }));
104
102
  }
105
103
  /** A cat: round, two ears, whiskers, a tail. */
106
104
  function Cat({ size, mode = 'light', }) {
@@ -146,7 +144,6 @@ export const ASSISTANT_CHARACTERS = [
146
144
  WIZARD.light.hat,
147
145
  WIZARD.light.brim,
148
146
  WIZARD.light.robe,
149
- WIZARD.light.faceEdge,
150
147
  WIZARD.light.beardEdge,
151
148
  ],
152
149
  eye: eyeOf(WIZARD.light.eye, WIZARD.light.face),
@@ -156,7 +153,6 @@ export const ASSISTANT_CHARACTERS = [
156
153
  WIZARD.dark.hat,
157
154
  WIZARD.dark.brim,
158
155
  WIZARD.dark.robe,
159
- WIZARD.dark.faceEdge,
160
156
  WIZARD.dark.beardEdge,
161
157
  ],
162
158
  eye: eyeOf(WIZARD.dark.eye, WIZARD.dark.face),
@@ -193,12 +193,6 @@ const AGENT_LIBRARIES = [
193
193
  description: 'LangChain agent implementations and tools',
194
194
  disabled: true,
195
195
  },
196
- {
197
- value: 'google-adk',
198
- label: 'Google ADK',
199
- description: 'Google’s Agent Development Kit',
200
- disabled: true,
201
- },
202
196
  ];
203
197
  const TRANSPORTS = [
204
198
  {
@@ -2,6 +2,8 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Button, Flash, Heading, Label, Text, Textarea } from '@primer/react';
4
4
  import { Box } from '@datalayer/primer-addons';
5
+ import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
6
+ import { AnonymousKeyTimer } from '@datalayer/core/lib/components/anonymous/AnonymousKeyTimer';
5
7
  import { stepCountIs, ToolLoopAgent } from 'ai';
6
8
  import { Streamdown } from 'streamdown';
7
9
  import { ThemedProvider } from './utils/themedProvider';
@@ -9,6 +11,7 @@ import { useExampleAgentRuntimesUrl } from './utils/useExampleAgentRuntimesUrl';
9
11
  import { AssistantStage } from '../chat/assistant/AssistantStage';
10
12
  import { useBrowserInference } from '../hooks/useBrowserInference';
11
13
  import { createBrowserModel } from '../runtimes/browser/model';
14
+ import { readJwtClaims, tokenLifetimeMs, useAnonymousSessionStore, } from '../runtimes/browser/anonymousToken';
12
15
  import { a2aPeerTool, connectA2APeer, } from '../runtimes/browser/a2aPeer';
13
16
  import { ACCOUNTING_APP_0_0_1, SALES_APP_0_0_1 } from '../specs/apps';
14
17
  import { SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1 } from '../specs/teams/teams';
@@ -60,7 +63,17 @@ function AgentA2ATeam() {
60
63
  const accountingUrl = import.meta.env.VITE_A2A_ACCOUNTING_URL ||
61
64
  `${runtimeUrl}/api/v1/a2a/agents/${ACCOUNTING.id}`;
62
65
  const accountingKey = import.meta.env.VITE_A2A_ACCOUNTING_KEY || undefined;
63
- const { inference, needsSignIn } = useBrowserInference(true);
66
+ const { inference, needsSignIn, anonymous } = useBrowserInference(true);
67
+ // The temporary key's own clock, read out of it: when it ends and how long
68
+ // it was signed for, as core's countdown draws them.
69
+ const accountingKeyClock = useMemo(() => {
70
+ const exp = accountingKey ? readJwtClaims(accountingKey)?.exp : undefined;
71
+ return accountingKey && exp
72
+ ? { expiresAt: exp * 1000, grantedMs: tokenLifetimeMs(accountingKey) }
73
+ : undefined;
74
+ }, [accountingKey]);
75
+ const [accountingKeyEnded, setAccountingKeyEnded] = useState(() => accountingKeyClock !== undefined &&
76
+ accountingKeyClock.expiresAt <= Date.now());
64
77
  const [peer, setPeer] = useState(null);
65
78
  const [peerError, setPeerError] = useState(null);
66
79
  const [sales, setSales] = useState({
@@ -235,7 +248,11 @@ function AgentA2ATeam() {
235
248
  const accountingWhere = accountingKey
236
249
  ? `on a runtime, ${new URL(accountingUrl).host}, with a temporary key`
237
250
  : `on a runtime, ${new URL(accountingUrl).host}`;
238
- return (_jsxs(Box, { sx: { p: 4, maxWidth: 1080, mx: 'auto' }, children: [_jsxs(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: [TEAM.emoji, " ", TEAM.name] }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, children: TEAM.description }), needsSignIn && (_jsx(Flash, { variant: "warning", sx: { mb: 3 }, children: "Sales asks its model through the Datalayer inference service: sign in, or wait for a visitor's trial key." })), peerError && (_jsxs(Flash, { variant: "danger", sx: { mb: 3 }, children: ["Accounting is not reachable at ", accountingUrl, ": ", peerError, " Serve it with", ' ', _jsx("code", { children: "python examples/sales-accounting-a2a/serve_accounting.py" }), ' ', "on the local server, or", ' ', _jsx("code", { children: "loop apps run accounting.yaml --cloud --a2a" }), " and set", ' ', _jsx("code", { children: "VITE_A2A_ACCOUNTING_URL" }), "."] })), _jsxs(Box, { sx: {
251
+ return (_jsxs(Box, { sx: { p: 4, maxWidth: 1080, mx: 'auto' }, children: [_jsxs(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: [TEAM.emoji, " ", TEAM.name] }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, children: TEAM.description }), ((anonymous.status === 'active' && anonymous.expiresAt) ||
252
+ (accountingKeyClock && !accountingKeyEnded)) && (_jsxs(Box, { sx: { display: 'flex', gap: 4, flexWrap: 'wrap', mb: 3 }, children: [anonymous.status === 'active' && anonymous.expiresAt && (_jsx(AnonymousKeyTimer, { expiresAt: anonymous.expiresAt, grantedMs: anonymous.grantedMs, label: `${SALES.name}'s trial key`, onExpire: () => useAnonymousSessionStore.getState().expire() })), accountingKeyClock && !accountingKeyEnded && (_jsx(AnonymousKeyTimer, { expiresAt: accountingKeyClock.expiresAt, grantedMs: accountingKeyClock.grantedMs, label: `${ACCOUNTING.name}'s temporary key`, onExpire: () => setAccountingKeyEnded(true) }))] })), anonymous.status === 'expired' && (_jsx(Box, { sx: { mb: 3 }, children: _jsx(AnonymousKeyExpired, { agentName: SALES.name,
253
+ // Sales runs in this page: what ended is its model's key, and
254
+ // signing in gives it the person's own.
255
+ onSignedIn: () => useAnonymousSessionStore.getState().clear() }) })), accountingKeyEnded && (_jsxs(Flash, { variant: "danger", sx: { mb: 3 }, children: [ACCOUNTING.name, "\u2019s temporary key has ended: it no longer answers. Make a new one with", ' ', _jsx("code", { children: "examples/sales-accounting-a2a/make_temp_key.py" }), " and reload."] })), needsSignIn && (_jsx(Flash, { variant: "warning", sx: { mb: 3 }, children: "Sales asks its model through the Datalayer inference service: sign in, or wait for a visitor's trial key." })), peerError && (_jsxs(Flash, { variant: "danger", sx: { mb: 3 }, children: ["Accounting is not reachable at ", accountingUrl, ": ", peerError, " Serve it with", ' ', _jsx("code", { children: "python examples/sales-accounting-a2a/serve_accounting.py" }), ' ', "on the local server, or", ' ', _jsx("code", { children: "loop apps run accounting.yaml --cloud --a2a" }), " and set", ' ', _jsx("code", { children: "VITE_A2A_ACCOUNTING_URL" }), "."] })), _jsxs(Box, { sx: {
239
256
  position: 'relative',
240
257
  height: STAGE_HEIGHT,
241
258
  border: '1px solid',
@@ -5,7 +5,9 @@
5
5
  *
6
6
  * The characters offered are what the enabled plugins contribute to
7
7
  * `loop.assistant.character` (T-24): Datalayer's four, and an owl from a
8
- * small example plugin (`utils/owlCharacterPlugin`).
8
+ * small example plugin (`utils/owlCharacterPlugin`). Pixel, the test sprite,
9
+ * is read through the clippy.js reader, as a character a person brings is,
10
+ * and so are the characters clippy.js publishes, fetched when picked.
9
11
  */
10
12
  import React from 'react';
11
13
  declare const ChatAssistantExample: React.FC;
@@ -10,9 +10,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  *
11
11
  * The characters offered are what the enabled plugins contribute to
12
12
  * `loop.assistant.character` (T-24): Datalayer's four, and an owl from a
13
- * small example plugin (`utils/owlCharacterPlugin`).
13
+ * small example plugin (`utils/owlCharacterPlugin`). Pixel, the test sprite,
14
+ * is read through the clippy.js reader, as a character a person brings is,
15
+ * and so are the characters clippy.js publishes, fetched when picked.
14
16
  */
15
- import { useState } from 'react';
17
+ import { useEffect, useRef, useState } from 'react';
16
18
  import { buildReactorFromPlugins } from '@datalayer/reactor';
17
19
  import { Button, Heading, Text } from '@primer/react';
18
20
  import { Box } from '@datalayer/primer-addons';
@@ -20,6 +22,8 @@ import { ThemedProvider } from './utils/themedProvider';
20
22
  import { ChatFloating } from '../chat';
21
23
  import { AssistantCharactersPlugin, assistantCharacterNamed, assistantCharactersOf, } from '../loop/plugins/assistant-characters';
22
24
  import { OwlCharacterPlugin } from './utils/owlCharacterPlugin';
25
+ import { readTestSpriteCharacter } from './utils/testSpriteCharacter';
26
+ import { CLIPPY_JS_CHARACTERS, CLIPPY_JS_VERSION, readClippyJsCharacter, } from './utils/clippyJsCharacters';
23
27
  import { readAcsCharacter, readClippyCharacter, } from '../chat/assistant/formats';
24
28
  /**
25
29
  * The agent-runtimes server the assistant talks to: `?agentRuntimesUrl=`
@@ -69,10 +73,72 @@ const ChatAssistantExample = () => {
69
73
  ? assistantCharacterNamed(reactor, character)
70
74
  : character;
71
75
  const [loadError, setLoadError] = useState();
72
- return (_jsx(ThemedProvider, { children: _jsxs(Box, { sx: { minHeight: '100vh', bg: 'canvas.default', p: 4 }, children: [_jsxs(Box, { sx: { maxWidth: 720, mx: 'auto' }, children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Chat Assistant" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: "The chat as a character on the page. It greets you, acts out what the agent is doing, and says the agent\u2019s words in a balloon while the conversation is closed. Click it to talk, drag it to move it, hover it to send it away." }), _jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: CATALOGUE.map(option => (_jsx(Button, { variant: option.id === character ? 'primary' : 'default', onClick: () => {
73
- setCharacter(option.id);
74
- setLoadError(undefined);
75
- }, "aria-pressed": option.id === character, "data-assistant-character": option.id, children: option.name }, option.id))) }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "A character you bring" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["The characters of Microsoft Office \u2014 Clippy, Merlin, Links and the others \u2014 are Microsoft\u2019s. Load only a character file you have the right to use: a Microsoft Agent ", _jsx("code", { children: ".acs" }), " file, or a clippy.js character\u2019s ", _jsx("code", { children: "agent.js" }), " and map image. It is read in this page and sent nowhere."] }), _jsx("input", { type: "file", multiple: true, accept: ".acs,.js,.png,.gif,.webp,.jpg,.jpeg", "aria-label": "Character files", onChange: async (event) => {
76
+ // Pixel, the test sprite, read once through the clippy.js reader.
77
+ const [pixel, setPixel] = useState();
78
+ useEffect(() => {
79
+ let cancelled = false;
80
+ readTestSpriteCharacter().then(read => {
81
+ if (!cancelled) {
82
+ setPixel(read);
83
+ }
84
+ }, error => {
85
+ if (!cancelled) {
86
+ setLoadError(error instanceof Error ? error.message : String(error));
87
+ }
88
+ });
89
+ return () => {
90
+ cancelled = true;
91
+ };
92
+ }, []);
93
+ // The object URL lives as long as the page does.
94
+ useEffect(() => () => {
95
+ if (pixel) {
96
+ URL.revokeObjectURL(pixel.sprite);
97
+ }
98
+ }, [pixel]);
99
+ // The clippy.js characters, each fetched the first time it is picked.
100
+ const [clippyJs, setClippyJs] = useState({});
101
+ const [fetching, setFetching] = useState();
102
+ const fetched = useRef([]);
103
+ useEffect(() => () => {
104
+ for (const read of fetched.current) {
105
+ URL.revokeObjectURL(read.sprite);
106
+ }
107
+ }, []);
108
+ const pickClippyJs = async (name) => {
109
+ const known = clippyJs[name];
110
+ if (known) {
111
+ setCharacter(known);
112
+ setLoadError(undefined);
113
+ return;
114
+ }
115
+ setFetching(name);
116
+ try {
117
+ const read = await readClippyJsCharacter(name);
118
+ fetched.current.push(read);
119
+ setClippyJs(previous => ({ ...previous, [name]: read }));
120
+ setCharacter(read);
121
+ setLoadError(undefined);
122
+ }
123
+ catch (error) {
124
+ setLoadError(error instanceof Error ? error.message : String(error));
125
+ }
126
+ finally {
127
+ setFetching(undefined);
128
+ }
129
+ };
130
+ const isClippyJs = typeof character !== 'string' &&
131
+ Object.values(clippyJs).includes(character);
132
+ return (_jsx(ThemedProvider, { children: _jsxs(Box, { sx: { minHeight: '100vh', bg: 'canvas.default', p: 4 }, children: [_jsxs(Box, { sx: { maxWidth: 720, mx: 'auto' }, children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Chat Assistant" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: "The chat as a character on the page. It greets you, acts out what the agent is doing, and says the agent\u2019s words in a balloon while the conversation is closed. Click it to talk, drag it to move it, hover it to send it away." }), _jsxs(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: [CATALOGUE.map(option => (_jsx(Button, { variant: option.id === character ? 'primary' : 'default', onClick: () => {
133
+ setCharacter(option.id);
134
+ setLoadError(undefined);
135
+ }, "aria-pressed": option.id === character, "data-assistant-character": option.id, children: option.name }, option.id))), pixel && (_jsxs(Button, { variant: character === pixel ? 'primary' : 'default', onClick: () => {
136
+ setCharacter(pixel);
137
+ setLoadError(undefined);
138
+ }, "aria-pressed": character === pixel, "data-assistant-character": "sprite", children: [pixel.name, " (sprite)"] }))] }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "The clippy.js characters" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["Fetched when picked from the", ' ', _jsxs("code", { children: ["clippyjs@", CLIPPY_JS_VERSION] }), " package on jsDelivr and read as data by the same reader. They are not in this repository: clippy.js\u2019s licence covers its code only, and the characters are Microsoft\u2019s."] }), _jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: CLIPPY_JS_CHARACTERS.map(name => {
139
+ const picked = clippyJs[name] !== undefined && character === clippyJs[name];
140
+ return (_jsx(Button, { variant: picked ? 'primary' : 'default', onClick: () => void pickClippyJs(name), disabled: fetching !== undefined, "aria-pressed": picked, "data-assistant-character": `clippy-js-${name.toLowerCase()}`, children: fetching === name ? `${name}…` : name }, name));
141
+ }) })] }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "A character you bring" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["The characters of Microsoft Office \u2014 Clippy, Merlin, Links and the others \u2014 are Microsoft\u2019s. Load only a character file you have the right to use: a Microsoft Agent ", _jsx("code", { children: ".acs" }), " file, or a clippy.js character\u2019s ", _jsx("code", { children: "agent.js" }), " and map image. It is read in this page and sent nowhere."] }), _jsx("input", { type: "file", multiple: true, accept: ".acs,.js,.png,.gif,.webp,.jpg,.jpeg", "aria-label": "Character files", onChange: async (event) => {
76
142
  const files = Array.from(event.target.files ?? []);
77
143
  if (!files.length) {
78
144
  return;
@@ -84,7 +150,9 @@ const ChatAssistantExample = () => {
84
150
  catch (error) {
85
151
  setLoadError(error instanceof Error ? error.message : String(error));
86
152
  }
87
- } }), loadError && (_jsx(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mt: 2 }, children: loadError })), typeof character !== 'string' && (_jsxs(Text, { as: "p", sx: { mt: 2 }, children: ["Playing ", character.name, "."] }))] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: drawn, protocol: "vercel-ai", endpoint: `${SERVER}/api/v1/vercel-ai/assistant`, title: "Assistant", description: "Hello! Ask me anything about this page.", position: "bottom-right", useStore: false,
153
+ } }), loadError && (_jsx(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mt: 2 }, children: loadError })), typeof character !== 'string' &&
154
+ character !== pixel &&
155
+ !isClippyJs && (_jsxs(Text, { as: "p", sx: { mt: 2 }, children: ["Playing ", character.name, "."] }))] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: drawn, protocol: "vercel-ai", endpoint: `${SERVER}/api/v1/vercel-ai/assistant`, title: "Assistant", description: "Hello! Ask me anything about this page.", position: "bottom-right", useStore: false,
88
156
  // Ask a decision beside the composer: Jev, through this server.
89
157
  decisions: { serverUrl: SERVER } }, typeof character === 'string' ? character : character.sprite)] }) }));
90
158
  };
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Every representation of the floating assistant, as a gallery (LOOP T-21
3
3
  * to T-27): each character — Datalayer's four, the owl an example plugin
4
- * contributes to `loop.assistant.character`, and a test sprite read through
5
- * the clippy.js reader — in each state it acts, stepped aside, with its
4
+ * contributes to `loop.assistant.character`, a test sprite read through
5
+ * the clippy.js reader, and, when asked, the characters clippy.js publishes,
6
+ * fetched from jsDelivr — in each state it acts, stepped aside, with its
6
7
  * balloon, in light and dark, moving or still.
7
8
  *
8
9
  * Static data, no agent: it opens without a server or an account.
@@ -6,8 +6,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
6
6
  /**
7
7
  * Every representation of the floating assistant, as a gallery (LOOP T-21
8
8
  * to T-27): each character — Datalayer's four, the owl an example plugin
9
- * contributes to `loop.assistant.character`, and a test sprite read through
10
- * the clippy.js reader — in each state it acts, stepped aside, with its
9
+ * contributes to `loop.assistant.character`, a test sprite read through
10
+ * the clippy.js reader, and, when asked, the characters clippy.js publishes,
11
+ * fetched from jsDelivr — in each state it acts, stepped aside, with its
11
12
  * balloon, in light and dark, moving or still.
12
13
  *
13
14
  * Static data, no agent: it opens without a server or an account.
@@ -21,6 +22,7 @@ import { AssistantStage } from '../chat/assistant/AssistantStage';
21
22
  import { useViewportDrag } from '../chat/useViewportDrag';
22
23
  import { AssistantGalleryGrid, GalleryObstacle, Still, useGalleryCharacters, } from './utils/AssistantGalleryGrid';
23
24
  import { GALLERY_POSES, GALLERY_POSE_LABELS, balloonForPose, sampleApproval, sampleSaying, stateOfPose, } from './utils/assistantGallery';
25
+ import { CLIPPY_JS_VERSION, useClippyJsCharacters, } from './utils/clippyJsCharacters';
24
26
  const STAGE_SIZE = 96;
25
27
  const PANEL_HEIGHT = 420;
26
28
  const LEAVE_MS = 700;
@@ -98,7 +100,10 @@ function InModes({ modes, stacked = false, children, }) {
98
100
  }, children: shown.map(mode => (_jsx(Box, { sx: { flex: '1 1 320px', minWidth: 0 }, children: _jsx(DatalayerThemeProvider, { colorMode: mode, theme: config.primerTheme, themeStyles: config.themeStyles, children: children(mode) }) }, mode))) }));
99
101
  }
100
102
  const ChatAssistantGalleryExample = () => {
101
- const { characters, problem } = useGalleryCharacters();
103
+ const { characters: ours, problem } = useGalleryCharacters();
104
+ const [withClippyJs, setWithClippyJs] = useState(false);
105
+ const clippyJs = useClippyJsCharacters(withClippyJs);
106
+ const characters = [...ours, ...clippyJs.characters];
102
107
  const [characterId, setCharacterId] = useState('paperclip');
103
108
  const [pose, setPose] = useState('idle');
104
109
  const [view, setView] = useState('stage');
@@ -142,7 +147,7 @@ const ChatAssistantGalleryExample = () => {
142
147
  bg: 'canvas.default',
143
148
  color: 'fg.default',
144
149
  p: 4,
145
- }, children: _jsxs(Box, { sx: { maxWidth: 1200, mx: 'auto' }, children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Chat Assistant gallery" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3, maxWidth: 760 }, children: ["Every character in every state the floating assistant acts, with its balloon, in light and dark. Datalayer\u2019s four and the owl are what the enabled plugins contribute to", ' ', _jsx("code", { children: "loop.assistant.character" }), "; Pixel is a test sprite of our own, read through the clippy.js reader. No agent is behind it: the words are a sample conversation."] }), _jsxs(Box, { as: "section", "aria-label": "Gallery controls", sx: {
150
+ }, children: _jsxs(Box, { sx: { maxWidth: 1200, mx: 'auto' }, children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Chat Assistant gallery" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3, maxWidth: 760 }, children: ["Every character in every state the floating assistant acts, with its balloon, in light and dark. Datalayer\u2019s four and the owl are what the enabled plugins contribute to", ' ', _jsx("code", { children: "loop.assistant.character" }), "; Pixel is a test sprite of our own, read through the clippy.js reader. No agent is behind it: the words are a sample conversation."] }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3, maxWidth: 760 }, children: ["The characters clippy.js publishes \u2014 Clippy, Merlin, Links, Rover and the others \u2014 are loaded on request from the", ' ', _jsxs("code", { children: ["clippyjs@", CLIPPY_JS_VERSION] }), " package on jsDelivr (about 15 MB) and read by the same reader. They are not in this repository: clippy.js\u2019s licence covers its code only, and the characters are Microsoft\u2019s."] }), _jsxs(Box, { as: "section", "aria-label": "Gallery controls", sx: {
146
151
  display: 'grid',
147
152
  gap: 3,
148
153
  mb: 4,
@@ -160,7 +165,7 @@ const ChatAssistantGalleryExample = () => {
160
165
  ? 'Side by side'
161
166
  : option === 'light'
162
167
  ? 'Light'
163
- : 'Dark' }, option))) }), _jsxs(FormControl, { children: [_jsx(Checkbox, { checked: still, onChange: event => setStill(event.target.checked) }), _jsx(FormControl.Label, { children: "Reduced motion" })] }), _jsx(Button, { onClick: () => setReplay(replay + 1), children: "Replay the motions" })] }), view === 'stage' && (_jsxs(_Fragment, { children: [_jsx(Box, { role: "group", "aria-label": "Character", sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: characters.map(option => (_jsx(Button, { size: "small", variant: option.id === character.id ? 'primary' : 'default', "aria-pressed": option.id === character.id, "data-gallery-character": option.id, onClick: () => setCharacterId(option.id), children: option.name }, option.id))) }), _jsx(Box, { role: "group", "aria-label": "State", sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: GALLERY_POSES.map(option => (_jsx(Button, { size: "small", variant: option === pose ? 'primary' : 'default', "aria-pressed": option === pose, "data-gallery-pose": option, onClick: () => {
168
+ : 'Dark' }, option))) }), _jsxs(FormControl, { children: [_jsx(Checkbox, { checked: still, onChange: event => setStill(event.target.checked) }), _jsx(FormControl.Label, { children: "Reduced motion" })] }), _jsxs(FormControl, { children: [_jsx(Checkbox, { checked: withClippyJs, onChange: event => setWithClippyJs(event.target.checked), "data-gallery-clippy-js": "" }), _jsxs(FormControl.Label, { children: ["clippy.js characters", clippyJs.loading ? ' (loading…)' : ''] })] }), _jsx(Button, { onClick: () => setReplay(replay + 1), children: "Replay the motions" })] }), view === 'stage' && (_jsxs(_Fragment, { children: [_jsx(Box, { role: "group", "aria-label": "Character", sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: characters.map(option => (_jsx(Button, { size: "small", variant: option.id === character.id ? 'primary' : 'default', "aria-pressed": option.id === character.id, "data-gallery-character": option.id, onClick: () => setCharacterId(option.id), children: option.name }, option.id))) }), _jsx(Box, { role: "group", "aria-label": "State", sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: GALLERY_POSES.map(option => (_jsx(Button, { size: "small", variant: option === pose ? 'primary' : 'default', "aria-pressed": option === pose, "data-gallery-pose": option, onClick: () => {
164
169
  setPose(option);
165
170
  setDecision(undefined);
166
171
  setReplay(replay + 1);
@@ -169,7 +174,7 @@ const ChatAssistantGalleryExample = () => {
169
174
  gap: 2,
170
175
  flexWrap: 'wrap',
171
176
  alignItems: 'center',
172
- }, children: [_jsx(Button, { onClick: say, "data-gallery-say": "", children: "Say something" }), saying !== undefined && (_jsx(Button, { variant: "invisible", onClick: () => setSaying(undefined), children: "Hush" })), presence === 'away' ? (_jsx(Button, { onClick: callBack, children: "Call it back" })) : (_jsx(Button, { onClick: () => sendAway('page'), disabled: presence !== 'here', "data-gallery-send-away": "", children: "Send it away" })), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Drag the character to move it; hover it for its own send-away menu." }), decision && (_jsx(Text, { role: "status", sx: { fontSize: 1 }, children: decision }))] })] }))] }), problem && (_jsxs(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mb: 3 }, children: ["The test sprite could not be read: ", problem] })), _jsx(Still, { still: still, children: view === 'stage' ? (_jsx(InModes, { modes: modes, children: mode => (_jsx(ModePanel, { mode: mode, character: character, pose: pose, presence: presence, balloon: balloon, insist: insist, onToggle: () => saying === undefined ? say() : setSaying(undefined), onDismiss: sendAway, onCallBack: callBack, away: away, replay: replay })) })) : (_jsx(InModes, { modes: modes, stacked: true, children: mode => (_jsx(Box, { "data-gallery-mode": mode, sx: {
177
+ }, children: [_jsx(Button, { onClick: say, "data-gallery-say": "", children: "Say something" }), saying !== undefined && (_jsx(Button, { variant: "invisible", onClick: () => setSaying(undefined), children: "Hush" })), presence === 'away' ? (_jsx(Button, { onClick: callBack, children: "Call it back" })) : (_jsx(Button, { onClick: () => sendAway('page'), disabled: presence !== 'here', "data-gallery-send-away": "", children: "Send it away" })), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Drag the character to move it; hover it for its own send-away menu." }), decision && (_jsx(Text, { role: "status", sx: { fontSize: 1 }, children: decision }))] })] }))] }), problem && (_jsxs(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mb: 3 }, children: ["The test sprite could not be read: ", problem] })), clippyJs.problem && (_jsxs(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mb: 3 }, children: ["A clippy.js character could not be read: ", clippyJs.problem] })), _jsx(Still, { still: still, children: view === 'stage' ? (_jsx(InModes, { modes: modes, children: mode => (_jsx(ModePanel, { mode: mode, character: character, pose: pose, presence: presence, balloon: balloon, insist: insist, onToggle: () => saying === undefined ? say() : setSaying(undefined), onDismiss: sendAway, onCallBack: callBack, away: away, replay: replay })) })) : (_jsx(InModes, { modes: modes, stacked: true, children: mode => (_jsx(Box, { "data-gallery-mode": mode, sx: {
173
178
  bg: 'canvas.default',
174
179
  border: '1px solid',
175
180
  borderColor: 'border.default',
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box } from '@datalayer/primer-addons';
3
3
  import { ThemedProvider } from './utils/themedProvider';
4
- import { registerDecks } from '@datalayer/decks';
5
- import { exampleDecks } from '@datalayer/decks/examples';
4
+ import { registerDeckComponents, registerDecks } from '@datalayer/decks';
5
+ import { exampleDeckComponents, exampleDecks } from '@datalayer/decks/examples';
6
6
  import { LoopEmbed } from '../loop';
7
7
  import { DeckViewPlugin, LoopDecksPlugin } from '../loop/plugins/decks';
8
8
  // The package's example decks, so the catalog has something to open and the
9
9
  // agent's suggestions point at decks that exist. Once, at module load.
10
10
  registerDecks(exampleDecks);
11
+ // And the components their slides name (AgentsHero, LiveAppearance…).
12
+ registerDeckComponents(exampleDeckComponents);
11
13
  // Module-level, so the embed builds its reactor once rather than on every
12
14
  // render of this component.
13
15
  const DECK_PLUGINS = [LoopDecksPlugin, DeckViewPlugin];
@@ -3,8 +3,8 @@ import { useMemo } from 'react';
3
3
  import { Box } from '@primer/react';
4
4
  import { QueryClientProvider } from '@tanstack/react-query';
5
5
  import { useReactor } from '@datalayer/reactor/react';
6
- import { registerDecks } from '@datalayer/decks';
7
- import { exampleDecks } from '@datalayer/decks/examples';
6
+ import { registerDeckComponents, registerDecks } from '@datalayer/decks';
7
+ import { exampleDeckComponents, exampleDecks } from '@datalayer/decks/examples';
8
8
  import { buildLoopReactor, LoopWorkspace } from '../loop/shell';
9
9
  import { DeckViewPlugin, LoopDecksPlugin } from '../loop/plugins/decks';
10
10
  import { loopPlugins } from '../loop/presets';
@@ -18,6 +18,8 @@ const FRONT_DOOR = 'jupyter-data-analyst';
18
18
  // The package's example decks, so the Decks member has something to open and
19
19
  // change before it has made one. Once, at module load.
20
20
  registerDecks(exampleDecks);
21
+ // And the components their slides name (AgentsHero, LiveAppearance…).
22
+ registerDeckComponents(exampleDeckComponents);
21
23
  export function LoopShellExample({ serverUrl = resolveExampleAgentRuntimesUrl('local'), agentId = FRONT_DOOR, defaultEditor = 'none', inheritTheme = false, }) {
22
24
  // Built once: rebuilding would restart every plugin on each render.
23
25
  const reactor = useMemo(() => buildLoopReactor([
@@ -47,7 +47,7 @@ const DEFAULT_LOCAL_JUPYTER_SERVER_URL = 'http://localhost:8888/api/jupyter-serv
47
47
  const DEFAULT_LOCAL_JUPYTER_SERVER_TOKEN = '60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6';
48
48
  const DEFAULT_CLOUD_RUNTIME_ENVIRONMENT = 'ai-agents-env';
49
49
  const EXAMPLE_GROUP_ORDER = [
50
- 'Loop',
50
+ 'Apps',
51
51
  'A2UI',
52
52
  'A2A',
53
53
  'AG-UI',
@@ -91,11 +91,9 @@ const ANONYMOUS_EXAMPLES = new Set([
91
91
  ]);
92
92
  const getExampleGroup = (id) => {
93
93
  if (id === 'AgentspecsExample' ||
94
- id === 'LoopStrategyExample' ||
95
- id === 'LoopWorkspaceExample' ||
96
- id === 'LoopShellExample' ||
94
+ id.startsWith('Loop') ||
97
95
  id === 'DecksAgent') {
98
- return 'Loop';
96
+ return 'Apps';
99
97
  }
100
98
  if (id.startsWith('A2Ui'))
101
99
  return 'A2UI';
@@ -1072,13 +1070,14 @@ const ExampleAppThemed = ({ selectedExample, isChangingExample, error, ExampleCo
1072
1070
  }
1073
1071
  for (const [groupName, examples] of groups) {
1074
1072
  examples.sort((left, right) => {
1075
- if (groupName === 'Loop') {
1073
+ if (groupName === 'Apps') {
1076
1074
  // The shells first, most naked first; then the strategy that drives a
1077
1075
  // notebook; then the library of specs behind them all.
1078
1076
  const LOOP_ORDER = [
1079
1077
  'LoopShellExample',
1080
1078
  'LoopWorkspaceExample',
1081
1079
  'LoopStrategyExample',
1080
+ 'LoopAppComputerExample',
1082
1081
  'AgentspecsExample',
1083
1082
  ];
1084
1083
  const loopOrder = (id) => {
@@ -0,0 +1,18 @@
1
+ import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
2
+ import type { GalleryCharacter } from './AssistantGalleryGrid';
3
+ /** The version of `clippyjs` the characters are read from. */
4
+ export declare const CLIPPY_JS_VERSION = "0.1.0";
5
+ /** The characters the package publishes: their names, and their folders. */
6
+ export declare const CLIPPY_JS_CHARACTERS: readonly ["Clippy", "Merlin", "Links", "Rover", "Genie", "Genius", "F1", "Peedy", "Rocky", "Bonzi"];
7
+ export type ClippyJsCharacterName = (typeof CLIPPY_JS_CHARACTERS)[number];
8
+ /** One clippy.js character, fetched and read. */
9
+ export declare function readClippyJsCharacter(name: ClippyJsCharacterName): Promise<AssistantCharacterData>;
10
+ /**
11
+ * The clippy.js characters as the gallery shows them, fetched once `enabled`
12
+ * and kept while it stays so; each as soon as it is read.
13
+ */
14
+ export declare function useClippyJsCharacters(enabled: boolean): {
15
+ characters: readonly GalleryCharacter[];
16
+ loading: boolean;
17
+ problem?: string;
18
+ };
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The characters clippy.js publishes — Clippy, Merlin, Links, Rover and the
7
+ * others — loaded into the floating assistant's gallery from the `clippyjs`
8
+ * package on jsDelivr, at the reader's request, and read through the same
9
+ * clippy.js reader a person's files go through.
10
+ *
11
+ * Nothing of them is in this repository: clippy.js's licence covers its code
12
+ * only, and the characters, their names and their drawings are Microsoft's
13
+ * (§6.9). The page fetches them when asked and keeps them in memory.
14
+ *
15
+ * The package ships each character as ES modules: `agent.mjs` exports the
16
+ * character's data, `map.mjs` its sprite sheet as a data URL. Both are read as
17
+ * text, never run: the data is rewritten as the `clippy.ready(...)` call the
18
+ * reader expects. Sounds are left out.
19
+ *
20
+ * @module examples/utils/clippyJsCharacters
21
+ */
22
+ import { useEffect, useState } from 'react';
23
+ import { readClippyCharacter } from '../../chat/assistant/formats/clippy';
24
+ /** The version of `clippyjs` the characters are read from. */
25
+ export const CLIPPY_JS_VERSION = '0.1.0';
26
+ const BASE = `https://cdn.jsdelivr.net/npm/clippyjs@${CLIPPY_JS_VERSION}/dist/agents`;
27
+ /** The characters the package publishes: their names, and their folders. */
28
+ export const CLIPPY_JS_CHARACTERS = [
29
+ 'Clippy',
30
+ 'Merlin',
31
+ 'Links',
32
+ 'Rover',
33
+ 'Genie',
34
+ 'Genius',
35
+ 'F1',
36
+ 'Peedy',
37
+ 'Rocky',
38
+ 'Bonzi',
39
+ ];
40
+ async function fetchText(url) {
41
+ const response = await fetch(url);
42
+ if (!response.ok) {
43
+ throw new Error(`${url} answered ${response.status}.`);
44
+ }
45
+ return response.text();
46
+ }
47
+ /** The value a module exports by default: `var x = <value>;\nexport …`. */
48
+ function defaultExportText(module, what) {
49
+ const match = /^var [A-Za-z_$][\w$]* = ([\s\S]*?);\s*export\s*\{/m.exec(module);
50
+ if (!match) {
51
+ throw new Error(`${what} does not export its value as clippy.js 0.1 does.`);
52
+ }
53
+ return match[1];
54
+ }
55
+ /** One clippy.js character, fetched and read. */
56
+ export async function readClippyJsCharacter(name) {
57
+ const folder = `${BASE}/${name.toLowerCase()}`;
58
+ const [agentModule, mapModule] = await Promise.all([
59
+ fetchText(`${folder}/agent.mjs`),
60
+ fetchText(`${folder}/map.mjs`),
61
+ ]);
62
+ const data = defaultExportText(agentModule, `${name}'s agent.mjs`);
63
+ const mapUrl = defaultExportText(mapModule, `${name}'s map.mjs`);
64
+ if (!/^"data:image\/(png|gif|webp|jpeg);base64,[A-Za-z0-9+/=]+"$/.test(mapUrl)) {
65
+ throw new Error(`${name}'s map.mjs does not hold an image.`);
66
+ }
67
+ const mapPng = await (await fetch(mapUrl.slice(1, -1))).blob();
68
+ return readClippyCharacter({
69
+ agentJs: `clippy.ready(${JSON.stringify(name)}, ${data});`,
70
+ mapPng,
71
+ });
72
+ }
73
+ /**
74
+ * The clippy.js characters as the gallery shows them, fetched once `enabled`
75
+ * and kept while it stays so; each as soon as it is read.
76
+ */
77
+ export function useClippyJsCharacters(enabled) {
78
+ const [read, setRead] = useState([]);
79
+ const [pending, setPending] = useState(0);
80
+ const [problem, setProblem] = useState();
81
+ useEffect(() => {
82
+ if (!enabled) {
83
+ return;
84
+ }
85
+ let cancelled = false;
86
+ const loaded = [];
87
+ setRead([]);
88
+ setProblem(undefined);
89
+ setPending(CLIPPY_JS_CHARACTERS.length);
90
+ for (const name of CLIPPY_JS_CHARACTERS) {
91
+ readClippyJsCharacter(name).then(character => {
92
+ if (cancelled) {
93
+ URL.revokeObjectURL(character.sprite);
94
+ return;
95
+ }
96
+ loaded.push(character);
97
+ setRead(previous => [
98
+ ...previous,
99
+ {
100
+ id: `clippy-js-${name.toLowerCase()}`,
101
+ name: character.name,
102
+ character,
103
+ from: `clippy.js ${CLIPPY_JS_VERSION}`,
104
+ },
105
+ ].sort((a, b) => CLIPPY_JS_CHARACTERS.indexOf(a.name) -
106
+ CLIPPY_JS_CHARACTERS.indexOf(b.name)));
107
+ setPending(count => count - 1);
108
+ }, error => {
109
+ if (cancelled) {
110
+ return;
111
+ }
112
+ setProblem(`${name}: ${error instanceof Error ? error.message : String(error)}`);
113
+ setPending(count => count - 1);
114
+ });
115
+ }
116
+ // The object URLs live as long as the characters are shown.
117
+ return () => {
118
+ cancelled = true;
119
+ for (const character of loaded) {
120
+ URL.revokeObjectURL(character.sprite);
121
+ }
122
+ };
123
+ }, [enabled]);
124
+ return {
125
+ characters: enabled ? read : [],
126
+ loading: enabled && pending > 0,
127
+ problem: enabled ? problem : undefined,
128
+ };
129
+ }
@@ -484,7 +484,7 @@ export function documentShapeProblems(document) {
484
484
  }
485
485
  for (const key of [
486
486
  'skills',
487
- 'tools',
487
+ 'backend_tools',
488
488
  'context',
489
489
  'contents',
490
490
  'notifications',
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import type { Agentspec } from './agentspecs';
5
5
  import type { AgentConnection } from './connection';
6
- export type AgentLibrary = 'pydantic-ai' | 'langchain' | 'google-adk';
6
+ export type AgentLibrary = 'pydantic-ai' | 'langchain';
7
7
  /**
8
8
  * Unified agent status covering runtime lifecycle and UI lifecycle.
9
9
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.3.62",
3
+ "version": "1.3.63",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",