@datalayer/agent-runtimes 1.3.61 → 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.
Files changed (35) hide show
  1. package/lib/chat/assistant/AssistantStage.js +5 -0
  2. package/lib/chat/assistant/SpriteCharacter.js +1 -1
  3. package/lib/chat/assistant/characters.js +1 -5
  4. package/lib/config/AgentConfiguration.js +0 -6
  5. package/lib/examples/AgentA2ATeamExample.d.ts +23 -0
  6. package/lib/examples/AgentA2ATeamExample.js +296 -0
  7. package/lib/examples/ChatAssistantExample.d.ts +3 -1
  8. package/lib/examples/ChatAssistantExample.js +75 -7
  9. package/lib/examples/ChatAssistantGalleryExample.d.ts +3 -2
  10. package/lib/examples/ChatAssistantGalleryExample.js +11 -6
  11. package/lib/examples/DecksAgent.js +4 -2
  12. package/lib/examples/LoopShellExample.js +4 -2
  13. package/lib/examples/LoopStrategyExample.d.ts +12 -0
  14. package/lib/examples/{AgentStrategyExample.js → LoopStrategyExample.js} +9 -9
  15. package/lib/examples/example-selector.js +2 -1
  16. package/lib/examples/index.d.ts +2 -1
  17. package/lib/examples/index.js +2 -1
  18. package/lib/examples/main.js +7 -8
  19. package/lib/examples/utils/clippyJsCharacters.d.ts +18 -0
  20. package/lib/examples/utils/clippyJsCharacters.js +129 -0
  21. package/lib/hooks/useA2A.d.ts +37 -1
  22. package/lib/loop/apps/checks.js +1 -1
  23. package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +60 -0
  24. package/lib/runtimes/browser/a2aPeer.d.ts +93 -0
  25. package/lib/runtimes/browser/a2aPeer.js +241 -0
  26. package/lib/specs/actions.js +54 -0
  27. package/lib/specs/apps.d.ts +2 -0
  28. package/lib/specs/apps.js +387 -0
  29. package/lib/specs/teams/teams.d.ts +1 -0
  30. package/lib/specs/teams/teams.js +80 -0
  31. package/lib/types/agents.d.ts +1 -1
  32. package/lib/types/teams.d.ts +20 -0
  33. package/package.json +2 -2
  34. package/scripts/codegen/generate_teams.py +42 -3
  35. package/lib/examples/AgentStrategyExample.d.ts +0 -12
@@ -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([
@@ -0,0 +1,12 @@
1
+ import { ServiceManager } from '@jupyterlab/services';
2
+ interface LoopStrategyExampleInnerProps {
3
+ serviceManager?: ServiceManager.IManager;
4
+ }
5
+ export declare function LoopStrategyExampleInner({ serviceManager, }: LoopStrategyExampleInnerProps): import("react").JSX.Element;
6
+ /**
7
+ * Main example component with Jupyter provider wrapper.
8
+ */
9
+ export declare function LoopStrategyExample({ serviceManager, }: {
10
+ serviceManager?: ServiceManager.IManager;
11
+ }): import("react").JSX.Element;
12
+ export default LoopStrategyExample;
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
6
  /**
7
- * AgentStrategyExample - Define and launch an agent reasoning *strategy*.
7
+ * LoopStrategyExample - Define and launch an agent reasoning *strategy*.
8
8
  *
9
9
  * This example demonstrates the control-loop paradigm (observe → think → act →
10
10
  * evaluate) instead of one-shot prompting. It is fully driven by the generic
@@ -24,7 +24,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
24
24
  * 1. Start the agent-runtimes server.
25
25
  * 2. Select this example from the header dropdown (Agent group).
26
26
  *
27
- * @module examples/AgentStrategyExample
27
+ * @module examples/LoopStrategyExample
28
28
  */
29
29
  import { useMemo, useState } from 'react';
30
30
  import { Box } from '@datalayer/primer-addons';
@@ -131,7 +131,7 @@ function StrategySummary({ strategy }) {
131
131
  fontWeight: 'bold',
132
132
  }, children: [index + 1, ". ", phase] }, phase))) }), strategy.objective && (_jsxs(Box, { sx: { color: 'fg.muted' }, children: [_jsx("strong", { children: "Objective:" }), " ", strategy.objective] })), strategy.termination && (_jsxs(Box, { sx: { color: 'fg.muted', mt: 1 }, children: [_jsx("strong", { children: "Max iterations:" }), " ", strategy.termination.maxIterations, ' ', "\u00B7 ", _jsx("strong", { children: "On blocked:" }), " ", strategy.termination.onBlocked] }))] }));
133
133
  }
134
- export function AgentStrategyExampleInner({ serviceManager, }) {
134
+ export function LoopStrategyExampleInner({ serviceManager, }) {
135
135
  // All available strategies come from the generated strategy catalogue.
136
136
  const strategies = useMemo(() => listStrategies(), []);
137
137
  const [selectedStrategyId, setSelectedStrategyId] = useState(DEFAULT_STRATEGY);
@@ -141,9 +141,9 @@ export function AgentStrategyExampleInner({ serviceManager, }) {
141
141
  // where the strategy turns, and the hook decides that.
142
142
  const tools = useNotebookTools(NOTEBOOK_ID);
143
143
  const { agentReady, protocol: protocolConfig, chatFrontendTools, error, unavailableReason, createAttempted, } = useExampleJupyterAgent({
144
- exampleId: 'AgentStrategyExample',
144
+ exampleId: 'LoopStrategyExample',
145
145
  agentName: DEFAULT_AGENT_ID,
146
- description: `Strategy agent (${selectedStrategy.name}) for AgentStrategyExample`,
146
+ description: `Strategy agent (${selectedStrategy.name}) for LoopStrategyExample`,
147
147
  systemPrompt,
148
148
  serviceManager,
149
149
  frontendTools: tools,
@@ -204,7 +204,7 @@ export function AgentStrategyExampleInner({ serviceManager, }) {
204
204
  justifyContent: 'space-between',
205
205
  gap: 3,
206
206
  flexWrap: 'wrap',
207
- }, children: [_jsxs(Box, { children: [_jsxs("h1", { style: { margin: 0, fontSize: '1.5rem' }, children: [selectedStrategy.emoji, " Agent Strategy Example"] }), _jsx("p", { style: { margin: '8px 0 0', color: 'var(--fgColor-muted)' }, children: "Define and launch an agent reasoning strategy \u2014 observe, think, act, evaluate \u2014 over a live notebook." })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, title: isStrategySelectorDisabled
207
+ }, children: [_jsxs(Box, { children: [_jsxs("h1", { style: { margin: 0, fontSize: '1.5rem' }, children: [selectedStrategy.emoji, " Loop Strategy Example"] }), _jsx("p", { style: { margin: '8px 0 0', color: 'var(--fgColor-muted)' }, children: "Define and launch an agent reasoning strategy \u2014 observe, think, act, evaluate \u2014 over a live notebook." })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, title: isStrategySelectorDisabled
208
208
  ? 'Strategy selector is temporarily disabled while launching the agent'
209
209
  : 'Choose a strategy', children: [_jsx(Box, { as: "label", sx: { fontSize: 1, fontWeight: 'bold' }, children: "Strategy" }), _jsx(Box, { as: "select", value: selectedStrategyId, onChange: (e) => setSelectedStrategyId(e.target.value), disabled: isStrategySelectorDisabled, "aria-label": "Strategy specification", sx: {
210
210
  px: 2,
@@ -250,7 +250,7 @@ export function AgentStrategyExampleInner({ serviceManager, }) {
250
250
  /**
251
251
  * Main example component with Jupyter provider wrapper.
252
252
  */
253
- export function AgentStrategyExample({ serviceManager, }) {
254
- return (_jsx(ThemedProvider, { children: _jsx(AgentStrategyExampleInner, { serviceManager: serviceManager }) }));
253
+ export function LoopStrategyExample({ serviceManager, }) {
254
+ return (_jsx(ThemedProvider, { children: _jsx(LoopStrategyExampleInner, { serviceManager: serviceManager }) }));
255
255
  }
256
- export default AgentStrategyExample;
256
+ export default LoopStrategyExample;
@@ -113,7 +113,7 @@ export const EXAMPLE_ENTRIES = [
113
113
  // `owns-sandbox-control`: the shell is pinned to the browser sandbox, so
114
114
  // the page's selector would offer three targets it cannot move to.
115
115
  ['example', 'loop', 'shell', 'prompt', 'editors', 'owns-sandbox-control']),
116
- makeEntry('AgentStrategyExample', () => import('./AgentStrategyExample'), 'Define and launch an agent reasoning strategy (a control loop: observe/think/act/evaluate) over a live notebook, driven by generic strategy specs.', ['example', 'agent', 'strategy', 'notebook', 'agentspecs']),
116
+ makeEntry('LoopStrategyExample', () => import('./LoopStrategyExample'), 'Define and launch an agent reasoning strategy (a control loop: observe/think/act/evaluate) over a live notebook, driven by generic strategy specs.', ['example', 'agent', 'strategy', 'notebook', 'agentspecs']),
117
117
  makeEntry('AgentToolApprovalsExample', () => import('./AgentToolApprovalsExample'), 'Tool approval workflows and manual decisions.'),
118
118
  makeEntry('AgentMemoryExample', () => import('./AgentMemoryExample'), 'Memory-aware conversation and retrieval example.'),
119
119
  makeEntry('AgentSkillsExample', () => import('./AgentSkillsExample'), 'Skills discovery, execution, and monitoring example.'),
@@ -123,6 +123,7 @@ export const EXAMPLE_ENTRIES = [
123
123
  makeEntry('AgentMonitoringExample', () => import('./AgentMonitoringExample'), 'Runtime monitoring and live metrics example.'),
124
124
  makeEntry('AgentSubagentsExample', () => import('./AgentSubagentsExample'), 'Multi-agent delegation with the in-repo subagents capability.'),
125
125
  makeEntry('AgentA2AExample', () => import('./AgentA2AExample'), 'Delegation to separate agents over the A2A protocol, launched locally or on Datalayer runtimes.'),
126
+ makeEntry('AgentA2ATeamExample', () => import('./AgentA2ATeamExample'), 'Two applications as a team over A2A: Sales in the browser (@a2a-js/sdk) asks Accounting on a runtime (fasta2a, Odoo read only), each an Office Assistant character.'),
126
127
  makeEntry('AgentNotificationsExample', () => import('./AgentNotificationsExample'), 'Notifications and event routing example.'),
127
128
  makeEntry('AgentOutputsExample', () => import('./AgentOutputsExample'), 'Structured outputs and rendering patterns.'),
128
129
  makeEntry('AgentParametersExample', () => import('./AgentParametersExample'), 'Launch-time parameterized agent creation with JSON schema.'),
@@ -26,11 +26,12 @@ export { default as AgentCompactionExample } from './AgentCompactionExample';
26
26
  export { default as AgentCodemodeExample } from './AgentCodemodeExample';
27
27
  export { default as AgentCodeSandboxesExample } from './AgentCodeSandboxesExample';
28
28
  export { default as AgentDecideExample } from './AgentDecideExample';
29
+ export { default as AgentA2ATeamExample } from './AgentA2ATeamExample';
29
30
  export { default as AgentEvalsExample } from './AgentEvalsExample';
30
31
  export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
31
32
  export { default as AgentHooksExample } from './AgentHooksExample';
32
33
  export { default as AgentInferenceProviderExample } from './AgentInferenceProviderExample';
33
- export { default as AgentStrategyExample } from './AgentStrategyExample';
34
+ export { default as LoopStrategyExample } from './LoopStrategyExample';
34
35
  export { default as AgentToolApprovalsExample } from './AgentToolApprovalsExample';
35
36
  export { default as AgentMemoryExample } from './AgentMemoryExample';
36
37
  export { default as AgentOtelExample } from './AgentOtelExample';
@@ -32,11 +32,12 @@ export { default as AgentCompactionExample } from './AgentCompactionExample';
32
32
  export { default as AgentCodemodeExample } from './AgentCodemodeExample';
33
33
  export { default as AgentCodeSandboxesExample } from './AgentCodeSandboxesExample';
34
34
  export { default as AgentDecideExample } from './AgentDecideExample';
35
+ export { default as AgentA2ATeamExample } from './AgentA2ATeamExample';
35
36
  export { default as AgentEvalsExample } from './AgentEvalsExample';
36
37
  export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
37
38
  export { default as AgentHooksExample } from './AgentHooksExample';
38
39
  export { default as AgentInferenceProviderExample } from './AgentInferenceProviderExample';
39
- export { default as AgentStrategyExample } from './AgentStrategyExample';
40
+ export { default as LoopStrategyExample } from './LoopStrategyExample';
40
41
  export { default as AgentToolApprovalsExample } from './AgentToolApprovalsExample';
41
42
  export { default as AgentMemoryExample } from './AgentMemoryExample';
42
43
  export { default as AgentOtelExample } from './AgentOtelExample';
@@ -47,14 +47,14 @@ 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',
54
- 'Capabilities',
55
54
  'Chat',
56
55
  'Document',
57
56
  'Notebook',
57
+ 'Capabilities',
58
58
  'Cell',
59
59
  'CopilotKit',
60
60
  ];
@@ -91,11 +91,9 @@ const ANONYMOUS_EXAMPLES = new Set([
91
91
  ]);
92
92
  const getExampleGroup = (id) => {
93
93
  if (id === 'AgentspecsExample' ||
94
- id === 'AgentStrategyExample' ||
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
- 'AgentStrategyExample',
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
+ }
@@ -1,4 +1,40 @@
1
- import type { AgentCard, Task } from '@a2a-js/sdk';
1
+ /**
2
+ * The A2A objects this hook reads, as the JSON it receives (the 0.3 wire it
3
+ * was written against). Not the SDK's types: `@a2a-js/sdk` 1.x types are the
4
+ * 1.0 protobuf shapes, which `runtimes/browser/a2aPeer` uses with the SDK's
5
+ * own client.
6
+ */
7
+ export interface A2AWireTextPart {
8
+ kind: 'text';
9
+ text: string;
10
+ }
11
+ export type A2AWirePart = A2AWireTextPart | {
12
+ kind: string;
13
+ text?: string;
14
+ };
15
+ export interface A2AWireTask {
16
+ id: string;
17
+ contextId: string;
18
+ status: {
19
+ state: string;
20
+ message?: {
21
+ parts?: A2AWirePart[];
22
+ };
23
+ };
24
+ artifacts?: {
25
+ parts?: A2AWirePart[];
26
+ }[];
27
+ }
28
+ export interface A2AWireAgentCard {
29
+ name: string;
30
+ description?: string;
31
+ capabilities?: {
32
+ streaming?: boolean;
33
+ };
34
+ [key: string]: unknown;
35
+ }
36
+ type AgentCard = A2AWireAgentCard;
37
+ type Task = A2AWireTask;
2
38
  export interface Message {
3
39
  role: 'user' | 'assistant' | 'system';
4
40
  content: string;
@@ -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',
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "Accounting",
3
+ "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.",
4
+ "version": "0.0.1",
5
+ "supportedInterfaces": [
6
+ {
7
+ "protocolBinding": "JSONRPC",
8
+ "url": "http://runtime.test/api/v1/a2a/agents/accounting/",
9
+ "protocolVersion": "1.0"
10
+ }
11
+ ],
12
+ "skills": [
13
+ {
14
+ "id": "accounting",
15
+ "name": "Accounting",
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
+ "tags": ["example", "accounting", "finance", "odoo", "a2a", "team"],
18
+ "inputModes": ["text/plain"],
19
+ "outputModes": ["text/plain"],
20
+ "examples": [
21
+ "List the customer invoices that are still open, with the total due.",
22
+ "Give the aged receivables as of today, by customer.",
23
+ "Give the trial balance for last month."
24
+ ]
25
+ }
26
+ ],
27
+ "defaultInputModes": ["application/json"],
28
+ "defaultOutputModes": ["application/json"],
29
+ "capabilities": {
30
+ "streaming": true,
31
+ "pushNotifications": false,
32
+ "extensions": [
33
+ {
34
+ "uri": "https://datalayer.ai/extensions/orchestration/v1",
35
+ "description": "Pause at a checkpoint and resume from it; steer a task while it works.",
36
+ "required": false,
37
+ "params": {
38
+ "pause": "/api/v1/a2a/pause",
39
+ "steer": "/api/v1/a2a/steer"
40
+ }
41
+ }
42
+ ]
43
+ },
44
+ "securitySchemes": {
45
+ "datalayer": {
46
+ "httpAuthSecurityScheme": {
47
+ "scheme": "Bearer",
48
+ "bearerFormat": "JWT",
49
+ "description": "A Datalayer key granted to this route: a token exchanged from a task grant whose task is this application's route on this runtime. The machine itself needs none."
50
+ }
51
+ }
52
+ },
53
+ "securityRequirements": [
54
+ {
55
+ "schemes": {
56
+ "datalayer": []
57
+ }
58
+ }
59
+ ]
60
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Another application, asked over A2A from an agent whose loop turns in the page.
3
+ *
4
+ * A team of applications says who asks whom (agentspecs `teams`, `talks_to`
5
+ * over `a2a`). The application asked is served on a runtime with fasta2a; the
6
+ * one that asks may run here, in the browser, and asks with the A2A SDK
7
+ * (`@a2a-js/sdk` 1.x) itself, with no wrapper of ours between them:
8
+ *
9
+ * - {@link connectA2APeer} reads the peer's agent card and makes a client
10
+ * from it. The card says where to send a request, what to ask (its skill:
11
+ * description, examples, text in, text out) and that a key is needed.
12
+ * - {@link a2aPeerTool} is the tool the asking agent calls. It takes one
13
+ * request in words, as the skill does, streams the task and returns the
14
+ * peer's answer in words. Each step is told to {@link A2APeerEvent} as it
15
+ * happens, so a page can show the exchange: asked, working, answered.
16
+ *
17
+ * The key goes as a bearer token on every request, the card's too. The
18
+ * runtime hands it to the peer's run, which then reaches the peer's
19
+ * connections with it and with nothing broader.
20
+ *
21
+ * @module runtimes/browser/a2aPeer
22
+ */
23
+ import { type AgentCard, type AgentSkill, type Message, type Part } from '@a2a-js/sdk';
24
+ import { type Client } from '@a2a-js/sdk/client';
25
+ import { type Tool } from 'ai';
26
+ /** Where an A2A agent's card is, under the address it is served at. */
27
+ export declare const AGENT_CARD_PATH = ".well-known/agent-card.json";
28
+ /** A peer reached over A2A: its client, its card, and the skill it is asked for. */
29
+ export type A2APeer = {
30
+ client: Client;
31
+ card: AgentCard;
32
+ skill: AgentSkill;
33
+ };
34
+ export type ConnectA2APeerOptions = {
35
+ /** Where the peer is served: `…/api/v1/a2a/agents/<application id>`. */
36
+ url: string;
37
+ /** A key granted to the peer's route, sent as a bearer token. */
38
+ key?: string;
39
+ /** The fetch to use, for tests and hosts that wrap the network. */
40
+ fetch?: typeof globalThis.fetch;
41
+ };
42
+ /**
43
+ * Read a peer's agent card and make a client from it.
44
+ *
45
+ * The card's first skill is what the peer is asked for: a peer served from an
46
+ * application has one, named for it.
47
+ */
48
+ export declare function connectA2APeer(options: ConnectA2APeerOptions): Promise<A2APeer>;
49
+ /** What happens to one request, as it happens. */
50
+ export type A2APeerEvent = {
51
+ phase: 'asked';
52
+ request: string;
53
+ } | {
54
+ phase: 'working';
55
+ taskId: string;
56
+ note?: string;
57
+ } | {
58
+ phase: 'answered';
59
+ taskId: string;
60
+ answer: string;
61
+ } | {
62
+ phase: 'failed';
63
+ error: string;
64
+ };
65
+ /** The text of a message's or an artifact's parts. */
66
+ export declare function textOf(parts: Part[] | undefined): string;
67
+ /** A user's message of one text part, as the SDK's 1.x types write it. */
68
+ export declare function textMessage(text: string): Message;
69
+ /**
70
+ * Ask a peer once, streaming the task, and return its answer.
71
+ *
72
+ * The answer is the text of the task's artifact, assembled from its chunks,
73
+ * or the final status message's when there is no artifact. A task that ends
74
+ * other than completed is thrown, with what the peer said.
75
+ */
76
+ export declare function askA2APeer(peer: A2APeer, request: string, options?: {
77
+ signal?: AbortSignal;
78
+ onEvent?: (event: A2APeerEvent) => void;
79
+ }): Promise<string>;
80
+ export type A2APeerToolOptions = {
81
+ peer: A2APeer;
82
+ /** Told each step of each request, as it happens. */
83
+ onEvent?: (event: A2APeerEvent) => void;
84
+ };
85
+ /** The tool's description: the peer's skill, and what it may be asked. */
86
+ export declare function peerToolDescription(peer: A2APeer): string;
87
+ /**
88
+ * The tool an agent asks its peer with: a request in, the peer's answer out.
89
+ *
90
+ * A failed request is returned rather than thrown, so the agent can tell the
91
+ * person what the peer said instead of the whole run failing.
92
+ */
93
+ export declare function a2aPeerTool(options: A2APeerToolOptions): Tool;