@datalayer/agent-runtimes 1.3.85 → 1.3.87
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/teams/A2ATeamGraph.d.ts +115 -20
- package/lib/components/teams/A2ATeamGraph.js +246 -109
- package/lib/components/teams/index.d.ts +1 -0
- package/lib/components/teams/index.js +1 -0
- package/lib/components/teams/sceneStage.d.ts +120 -0
- package/lib/components/teams/sceneStage.js +170 -0
- package/lib/components/teams/useA2ATeam.d.ts +92 -20
- package/lib/components/teams/useA2ATeam.js +313 -101
- package/lib/examples/ScenesExample.d.ts +26 -0
- package/lib/examples/ScenesExample.js +241 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/exampleGroups.js +3 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/examples/main.js +4 -0
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/mcpServers.js +8 -2
- package/lib/specs/sceneSchema.d.ts +8 -0
- package/lib/specs/sceneSchema.js +852 -0
- package/lib/specs/scenes.d.ts +21 -0
- package/lib/specs/scenes.js +1191 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/scenes.d.ts +207 -0
- package/lib/types/scenes.js +5 -0
- package/package.json +3 -3
- package/scripts/codegen/generate_agents.py +1 -0
- package/scripts/codegen/generate_envvars.py +1 -1
- package/scripts/codegen/generate_scenes.py +343 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { APP_CATALOGUE } from '../../specs/apps';
|
|
6
|
+
import { listSceneSpecs } from '../../specs/scenes';
|
|
7
|
+
/** A reference without its version: `accounting:0.0.1` is `accounting`. */
|
|
8
|
+
export function refId(ref) {
|
|
9
|
+
const at = ref.lastIndexOf(':');
|
|
10
|
+
return at > 0 && ref.slice(at + 1).includes('.') ? ref.slice(0, at) : ref;
|
|
11
|
+
}
|
|
12
|
+
/** The application a cast member is, from the catalogue, or undefined. */
|
|
13
|
+
export function appOfMember(member) {
|
|
14
|
+
const id = refId(member.app);
|
|
15
|
+
return id && Object.prototype.hasOwnProperty.call(APP_CATALOGUE, id)
|
|
16
|
+
? APP_CATALOGUE[id]
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The links of a team over A2A, in the order its members say them: each
|
|
21
|
+
* member's `talks_to … over: a2a`. A link to a server (over `mcp`) is a
|
|
22
|
+
* connection, drawn under the member, not an edge between members.
|
|
23
|
+
*/
|
|
24
|
+
export function teamLinksOf(team) {
|
|
25
|
+
const members = 'cast' in team
|
|
26
|
+
? team.cast.map(member => ({
|
|
27
|
+
id: member.member,
|
|
28
|
+
talksTo: member.talksTo,
|
|
29
|
+
}))
|
|
30
|
+
: team.agents;
|
|
31
|
+
return members.flatMap(member => (member.talksTo ?? [])
|
|
32
|
+
.filter(link => link.over === 'a2a')
|
|
33
|
+
.map(link => ({ from: member.id, to: link.member })));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The entry's suggestions: the script's cues, each labelled by the starter
|
|
37
|
+
* of the entry's Appspec that says the same words, or by its first words.
|
|
38
|
+
* Without a scene, the starters themselves.
|
|
39
|
+
*/
|
|
40
|
+
export function sceneCuesOf(scene, starters) {
|
|
41
|
+
const fromStarters = starters.map(starter => ({
|
|
42
|
+
label: starter.label,
|
|
43
|
+
prompt: starter.message,
|
|
44
|
+
}));
|
|
45
|
+
if (!scene) {
|
|
46
|
+
return fromStarters;
|
|
47
|
+
}
|
|
48
|
+
return scene.script
|
|
49
|
+
.map(beat => beat.cue.say.trim())
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.map(prompt => ({
|
|
52
|
+
label: fromStarters.find(starter => starter.prompt === prompt)?.label ??
|
|
53
|
+
prompt.split(/\s+/).slice(0, 4).join(' '),
|
|
54
|
+
prompt,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read a scene's stage: its members with their applications, who the entry
|
|
59
|
+
* is and whom it asks, the links, the positions. Refused when a cast member
|
|
60
|
+
* is an application the catalogue does not have, or the entry is not in the
|
|
61
|
+
* cast: a page cannot stage what it cannot draw.
|
|
62
|
+
*/
|
|
63
|
+
export function sceneStageOf(scene) {
|
|
64
|
+
const members = scene.cast
|
|
65
|
+
.filter(member => member.app)
|
|
66
|
+
.map((member) => {
|
|
67
|
+
const app = appOfMember(member);
|
|
68
|
+
if (!app) {
|
|
69
|
+
throw new Error(`The scene ${scene.id} casts ${member.member} as ${member.app}, which the catalogue does not have.`);
|
|
70
|
+
}
|
|
71
|
+
const position = scene.stage.positions[member.member];
|
|
72
|
+
const variable = scene.deployment.addresses[member.member];
|
|
73
|
+
return {
|
|
74
|
+
id: member.member,
|
|
75
|
+
app,
|
|
76
|
+
cast: member,
|
|
77
|
+
entry: member.member === scene.entry,
|
|
78
|
+
runsIn: member.runsIn ?? 'runtime',
|
|
79
|
+
...(variable ? { addressVariable: variable } : {}),
|
|
80
|
+
...(position ? { position } : {}),
|
|
81
|
+
setup: app.setup ?? [],
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
const entry = members.find(member => member.entry);
|
|
85
|
+
if (!entry) {
|
|
86
|
+
throw new Error(`The scene ${scene.id} says its entry is ${scene.entry}, which is not in its cast.`);
|
|
87
|
+
}
|
|
88
|
+
const links = teamLinksOf(scene);
|
|
89
|
+
const asked = links
|
|
90
|
+
.filter(link => link.from === entry.id)
|
|
91
|
+
.map(link => link.to);
|
|
92
|
+
const peers = members.filter(member => !member.entry && asked.includes(member.id));
|
|
93
|
+
return {
|
|
94
|
+
scene,
|
|
95
|
+
entry,
|
|
96
|
+
peers,
|
|
97
|
+
members: [entry, ...members.filter(member => !member.entry)],
|
|
98
|
+
links,
|
|
99
|
+
positions: scene.stage.positions,
|
|
100
|
+
opensFirst: scene.stage.opensFirst || entry.id,
|
|
101
|
+
assumes: scene.setting.assumes,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** The port the examples serve Accounting on, as `npm run examples` starts it. */
|
|
105
|
+
export const ACCOUNTING_PORT = 8767;
|
|
106
|
+
/** The first port `serve_scenes.py` starts a runtime on for the other members. */
|
|
107
|
+
export const SCENE_PORTS_FROM = 8768;
|
|
108
|
+
/**
|
|
109
|
+
* The members on a runtime across the catalogue's scenes, each once, in
|
|
110
|
+
* the catalogue's order (a scene's cast in its order): what
|
|
111
|
+
* `examples/sales-accounting-a2a/serve_scenes.py` serves, in that order.
|
|
112
|
+
*/
|
|
113
|
+
export function sceneRuntimeMembers(scenes = listSceneSpecs()) {
|
|
114
|
+
const seen = [];
|
|
115
|
+
for (const scene of scenes) {
|
|
116
|
+
for (const member of scene.cast) {
|
|
117
|
+
const id = refId(member.app);
|
|
118
|
+
if (id &&
|
|
119
|
+
(member.runsIn ?? 'runtime') === 'runtime' &&
|
|
120
|
+
!seen.includes(id)) {
|
|
121
|
+
seen.push(id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return seen;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The port each runtime member is served on locally: Accounting on
|
|
129
|
+
* {@link ACCOUNTING_PORT}, the others from {@link SCENE_PORTS_FROM} on, in
|
|
130
|
+
* the order of {@link sceneRuntimeMembers}. `serve_scenes.py` gives the
|
|
131
|
+
* same ports by the same rule.
|
|
132
|
+
*/
|
|
133
|
+
export function sceneRuntimePorts(scenes = listSceneSpecs()) {
|
|
134
|
+
const ports = {};
|
|
135
|
+
let next = SCENE_PORTS_FROM;
|
|
136
|
+
for (const id of sceneRuntimeMembers(scenes)) {
|
|
137
|
+
if (id === 'accounting') {
|
|
138
|
+
ports[id] = ACCOUNTING_PORT;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
ports[id] = next;
|
|
142
|
+
next += 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return ports;
|
|
146
|
+
}
|
|
147
|
+
/** The variable the examples read a member's local address from: `VITE_A2A_CHANGE_DETECTION_URL`. */
|
|
148
|
+
export function sceneAddressVariable(appId) {
|
|
149
|
+
return `VITE_A2A_${appId.replace(/-/g, '_').toUpperCase()}_URL`;
|
|
150
|
+
}
|
|
151
|
+
/** Where a member is served over A2A on a local runtime at `port`. */
|
|
152
|
+
export function localA2AUrl(appId, port) {
|
|
153
|
+
return `http://127.0.0.1:${port}/api/v1/a2a/agents/${appId}`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Where a member on a runtime is reached by the examples: its variable
|
|
157
|
+
* (`VITE_A2A_<MEMBER>_URL`) when set in `env`, else the local runtime
|
|
158
|
+
* `serve_scenes.py` serves it on.
|
|
159
|
+
*/
|
|
160
|
+
export function sceneMemberUrl(appId, env, ports = sceneRuntimePorts()) {
|
|
161
|
+
const port = ports[appId];
|
|
162
|
+
const set = env[sceneAddressVariable(appId)];
|
|
163
|
+
if (set) {
|
|
164
|
+
return set;
|
|
165
|
+
}
|
|
166
|
+
if (port === undefined) {
|
|
167
|
+
throw new Error(`${appId} runs on no local runtime the examples serve: set ${sceneAddressVariable(appId)}.`);
|
|
168
|
+
}
|
|
169
|
+
return localA2AUrl(appId, port);
|
|
170
|
+
}
|
|
@@ -38,36 +38,104 @@ export declare function answeredLine(artifacts: A2APeerArtifact[]): string;
|
|
|
38
38
|
export declare function notebookAmong(artifacts: A2APeerArtifact[]): A2APeerArtifact | null;
|
|
39
39
|
/** A line short enough for a balloon. */
|
|
40
40
|
export declare function balloonLine(text: string, length?: number): string;
|
|
41
|
+
/** The name of the entry's tool for a peer, as its instructions call it: `ask_<peer id>`. */
|
|
42
|
+
export declare function askToolOf(peerId: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* What a member says when it is asked, before it works: the books when it
|
|
45
|
+
* reaches Odoo, the system it reaches otherwise, or just that it is on it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function askedLine(connections: readonly A2ATeamConnection[]): string;
|
|
48
|
+
/**
|
|
49
|
+
* The entry's instructions for a team of more than one peer: its own, and
|
|
50
|
+
* which tool asks whom, so that it names each. A team of two keeps the
|
|
51
|
+
* entry's instructions as they are.
|
|
52
|
+
*/
|
|
53
|
+
export declare function teamInstructions(entry: Pick<AppSpec, 'instructions'>, peers: readonly {
|
|
54
|
+
app: Pick<AppSpec, 'name'>;
|
|
55
|
+
askTool: string;
|
|
56
|
+
}[]): string;
|
|
57
|
+
/** A member the entry asks over A2A. */
|
|
58
|
+
export type A2ATeamPeerOptions = {
|
|
59
|
+
/** The member asked. */
|
|
60
|
+
app: AppSpec;
|
|
61
|
+
/** The peer, once its card is read; `null` until then. */
|
|
62
|
+
peer: A2APeer | null;
|
|
63
|
+
/** The name of the entry's tool for it; `ask_<its id>` unsaid. */
|
|
64
|
+
askTool?: string;
|
|
65
|
+
/** Its connections (`teamConnectionsOf(app)`): the calls to them are kept. */
|
|
66
|
+
connections?: A2ATeamConnection[];
|
|
67
|
+
/**
|
|
68
|
+
* What its balloon says before it is asked (a scene's `opens_first`, its
|
|
69
|
+
* persona's line); unsaid, it waits quietly.
|
|
70
|
+
*/
|
|
71
|
+
greeting?: string;
|
|
72
|
+
};
|
|
41
73
|
export type UseA2ATeamOptions = {
|
|
42
74
|
/** The member that talks to the person and asks: it runs in this page. */
|
|
43
75
|
entry: AppSpec;
|
|
44
|
-
/**
|
|
45
|
-
peerApp
|
|
46
|
-
/**
|
|
47
|
-
peer
|
|
76
|
+
/** A team of two: the member it asks over A2A. */
|
|
77
|
+
peerApp?: AppSpec;
|
|
78
|
+
/** A team of two: the peer, once its card is read; `null` until then. */
|
|
79
|
+
peer?: A2APeer | null;
|
|
80
|
+
/** A team of two: the name of the entry's tool, as its instructions call it: `ask_<peer id>`. */
|
|
81
|
+
askTool?: string;
|
|
82
|
+
/** A team of two: the peer's connections (`teamConnectionsOf(peerApp)`). */
|
|
83
|
+
peerConnections?: A2ATeamConnection[];
|
|
84
|
+
/**
|
|
85
|
+
* A team of N: the members the entry asks, in the order its tools are
|
|
86
|
+
* offered. Give the same array while nothing changed (memoised): a new
|
|
87
|
+
* one makes the entry's agent again.
|
|
88
|
+
*/
|
|
89
|
+
peers?: readonly A2ATeamPeerOptions[];
|
|
90
|
+
/**
|
|
91
|
+
* The entry itself, when it runs on a runtime and is asked over A2A from
|
|
92
|
+
* the page: no agent turns in the browser, `send` asks it directly, and
|
|
93
|
+
* its persona follows what it tells (asked, working, its tools, answered).
|
|
94
|
+
* `null` until its card is read.
|
|
95
|
+
*/
|
|
96
|
+
entryPeer?: A2APeer | null;
|
|
48
97
|
/** Where the entry's model is asked, and with whose token. */
|
|
49
98
|
inference: Omit<BrowserModelOptions, 'model'>;
|
|
50
|
-
/** The name of the entry's tool, as its instructions call it: `ask_<peer id>`. */
|
|
51
|
-
askTool?: string;
|
|
52
99
|
/** The most steps of one turn. */
|
|
53
100
|
maxSteps?: number;
|
|
54
|
-
/** The peer's connections (`teamConnectionsOf(peerApp)`): the calls to them are kept. */
|
|
55
|
-
peerConnections?: A2ATeamConnection[];
|
|
56
101
|
/**
|
|
57
|
-
* The media types the page accepts from the
|
|
102
|
+
* The media types the page accepts from the peers (`acceptedOutputModes`):
|
|
58
103
|
* words, and the formats it can show, such as a notebook. Unsaid, words alone.
|
|
59
104
|
*/
|
|
60
105
|
accept?: readonly string[];
|
|
61
106
|
/**
|
|
62
|
-
* The Agent Inspector's tracer: the entry's turns and its
|
|
63
|
-
*
|
|
107
|
+
* The Agent Inspector's tracer: the entry's turns and its calls to the
|
|
108
|
+
* peers are recorded there as spans.
|
|
64
109
|
*/
|
|
65
110
|
inspector?: OtelLiveTracer | null;
|
|
66
111
|
};
|
|
112
|
+
/** One peer of the team, as the team keeps it. */
|
|
113
|
+
export type A2ATeamPeer = {
|
|
114
|
+
/** Its id: its application's. */
|
|
115
|
+
id: string;
|
|
116
|
+
app: AppSpec;
|
|
117
|
+
/** The name of the entry's tool for it. */
|
|
118
|
+
askTool: string;
|
|
119
|
+
connections: A2ATeamConnection[];
|
|
120
|
+
/** Whether it is reached: its card was read. */
|
|
121
|
+
connected: boolean;
|
|
122
|
+
persona: A2ATeamPersona;
|
|
123
|
+
/** What it was asked, the tools it called and what it answered. */
|
|
124
|
+
history: DisplayItem[];
|
|
125
|
+
/** Its last answer, as it gave it. */
|
|
126
|
+
report: string | null;
|
|
127
|
+
/** What it gave besides words with its last answer. */
|
|
128
|
+
artifacts: A2APeerArtifact[];
|
|
129
|
+
/** Which way its link carries a message now. */
|
|
130
|
+
flow: A2ATeamFlow;
|
|
131
|
+
setAway: (away: boolean) => void;
|
|
132
|
+
};
|
|
67
133
|
export type A2ATeam = {
|
|
68
134
|
entryPersona: A2ATeamPersona;
|
|
135
|
+
/** The first peer's persona: the peer of a team of two. */
|
|
69
136
|
peerPersona: A2ATeamPersona;
|
|
70
137
|
setEntryAway: (away: boolean) => void;
|
|
138
|
+
/** Sends the first peer away, or calls it back. */
|
|
71
139
|
setPeerAway: (away: boolean) => void;
|
|
72
140
|
turns: A2ATeamTurn[];
|
|
73
141
|
/**
|
|
@@ -76,26 +144,30 @@ export type A2ATeam = {
|
|
|
76
144
|
*/
|
|
77
145
|
entryHistory: DisplayItem[];
|
|
78
146
|
/**
|
|
79
|
-
* What the peer was asked, the tools it called and what it
|
|
80
|
-
* the chat's model (messages and tool calls).
|
|
147
|
+
* What the first peer was asked, the tools it called and what it
|
|
148
|
+
* answered, in the chat's model (messages and tool calls).
|
|
81
149
|
*/
|
|
82
150
|
peerHistory: DisplayItem[];
|
|
83
|
-
/** The
|
|
151
|
+
/** The peers, in their order, each as the team keeps it. */
|
|
152
|
+
peers: A2ATeamPeer[];
|
|
153
|
+
/** Which way each peer's link carries a message now, by the peer's id. */
|
|
154
|
+
flows: Record<string, A2ATeamFlow>;
|
|
155
|
+
/** The last answer a peer gave, as it gave it. */
|
|
84
156
|
report: string | null;
|
|
85
|
-
/** What
|
|
157
|
+
/** What a peer gave besides words with the last answer, by media type. */
|
|
86
158
|
artifacts: A2APeerArtifact[];
|
|
87
|
-
/** The latest notebook
|
|
159
|
+
/** The latest notebook a peer gave, kept until another one comes. */
|
|
88
160
|
notebook: A2APeerArtifact | null;
|
|
89
|
-
/** Which way the link carries a message now. */
|
|
161
|
+
/** Which way the first peer's link carries a message now. */
|
|
90
162
|
flow: A2ATeamFlow;
|
|
91
|
-
/** The tool calls the
|
|
163
|
+
/** The tool calls the peers' connections are answering now. */
|
|
92
164
|
calls: A2ATeamCall[];
|
|
93
165
|
/** Whether a turn is under way. */
|
|
94
166
|
busy: boolean;
|
|
95
|
-
/** Whether the entry can be asked:
|
|
167
|
+
/** Whether the entry can be asked: every peer is connected, or the entry is. */
|
|
96
168
|
ready: boolean;
|
|
97
169
|
send: (text: string) => Promise<void>;
|
|
98
170
|
stop: () => void;
|
|
99
171
|
};
|
|
100
|
-
/** Run a team
|
|
172
|
+
/** Run a team over A2A in the page. */
|
|
101
173
|
export declare function useA2ATeam(options: UseA2ATeamOptions): A2ATeam;
|