@datalayer/agent-runtimes 1.0.17 → 1.0.19
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/README.md +3 -3
- package/lib/AgentNode.js +440 -41
- package/lib/agent-node/AgentNodeGallery.d.ts +80 -0
- package/lib/agent-node/AgentNodeGallery.js +464 -0
- package/lib/agent-notebook-main.js +0 -1
- package/lib/chat/Chat.d.ts +14 -1
- package/lib/chat/Chat.js +22 -7
- package/lib/chat/base/ChatBase.js +38 -12
- package/lib/chat/document/EphemeralDocument.d.ts +45 -1
- package/lib/chat/document/EphemeralDocument.js +135 -11
- package/lib/chat/notebook/EphemeralNotebook.d.ts +36 -1
- package/lib/chat/notebook/EphemeralNotebook.js +42 -9
- package/lib/examples/AgentCodeSandboxesExample.d.ts +3 -0
- package/lib/examples/AgentCodeSandboxesExample.js +192 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/identity/AgentIdentity.js +1 -2
- package/lib/shims/primerAddonsBox.d.ts +1 -0
- package/lib/shims/primerAddonsBox.js +7 -0
- package/lib/specs/agents/agents.d.ts +8 -0
- package/lib/specs/agents/agents.js +440 -0
- package/lib/types/agentspecs.d.ts +1 -1
- package/lib/types/chat.d.ts +71 -0
- package/lib/types/config.d.ts +1 -1
- package/package.json +4 -3
- package/scripts/codegen/generate_sandbox_agents.py +145 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single agent specification returned by ``GET /api/v1/agents/library``.
|
|
3
|
+
* Only the fields consumed by the gallery cards are typed here.
|
|
4
|
+
*/
|
|
5
|
+
export type AgentspecSummary = {
|
|
6
|
+
id: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
model?: string;
|
|
13
|
+
inference_provider?: string;
|
|
14
|
+
icon?: string;
|
|
15
|
+
emoji?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
};
|
|
18
|
+
export type AgentNodeGalleryProps = {
|
|
19
|
+
/** Base URL of the local Agent Runtimes server. */
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
/** Bearer token used for authenticated requests. */
|
|
22
|
+
token?: string | null;
|
|
23
|
+
/** Id of the agent currently marked active on the node, if any. */
|
|
24
|
+
activeAgentId?: string | null;
|
|
25
|
+
/** Called with the launched agent id once it is running and set active. */
|
|
26
|
+
onLaunched: (agentId: string) => void;
|
|
27
|
+
/** Called once the active agent is terminated and cleared on the node. */
|
|
28
|
+
onTerminated?: (agentId: string) => void;
|
|
29
|
+
/** Optional callback used by parent to display launch errors (e.g. toast). */
|
|
30
|
+
onLaunchError?: (message: string) => void;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Agent gallery picker shown on the Agent Node.
|
|
34
|
+
if (!createResponse.ok && createResponse.status === 409) {
|
|
35
|
+
// If the named agent already exists, reload running agents and try
|
|
36
|
+
// to reuse the matching instance instead of failing the launch.
|
|
37
|
+
const reloadResponse = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
38
|
+
headers: authHeaders,
|
|
39
|
+
});
|
|
40
|
+
if (reloadResponse.ok) {
|
|
41
|
+
const reloadPayload = await reloadResponse
|
|
42
|
+
.json()
|
|
43
|
+
.catch(() => null);
|
|
44
|
+
const nextRunning: RunningAgent[] = Array.isArray(reloadPayload)
|
|
45
|
+
? reloadPayload
|
|
46
|
+
: reloadPayload?.agents || reloadPayload?.items || [];
|
|
47
|
+
setRunning(nextRunning);
|
|
48
|
+
const existing = nextRunning.find(candidate => {
|
|
49
|
+
const candidateId = candidate.agent_id || candidate.id || '';
|
|
50
|
+
return (
|
|
51
|
+
candidate.agent_spec_id === spec.id ||
|
|
52
|
+
candidate.name === requestedName ||
|
|
53
|
+
candidateId === requestedName
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
agentId = existing?.agent_id || existing?.id || null;
|
|
57
|
+
|
|
58
|
+
// If a same-name agent exists but is not tied to this spec,
|
|
59
|
+
// recreate it so configure/spec lookups and sandbox settings
|
|
60
|
+
// match the selected gallery card.
|
|
61
|
+
if (existing && existing.agent_spec_id !== spec.id) {
|
|
62
|
+
const staleId = String(existing.agent_id || existing.id || '').trim();
|
|
63
|
+
if (staleId) {
|
|
64
|
+
await fetch(
|
|
65
|
+
`${baseUrl}/api/v1/agents/${encodeURIComponent(staleId)}`,
|
|
66
|
+
{
|
|
67
|
+
method: 'DELETE',
|
|
68
|
+
headers: authHeaders,
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
agentId = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
* Sources cards from the agentspec library and highlights the agent that is
|
|
76
|
+
* already running on the node. Launching a card creates the agent locally and
|
|
77
|
+
* marks it active so tunneled prompts from the main UI reach it.
|
|
78
|
+
*/
|
|
79
|
+
export declare function AgentNodeGallery({ baseUrl, token, activeAgentId, onLaunched, onTerminated, onLaunchError, }: AgentNodeGalleryProps): JSX.Element;
|
|
80
|
+
export default AgentNodeGallery;
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import { Box, Button, Heading, Label, Spinner, Text, TextInput, } from '@primer/react';
|
|
8
|
+
import { CheckCircleFillIcon, RocketIcon } from '@primer/octicons-react';
|
|
9
|
+
const GRID_TEMPLATE = ['1fr', '1fr 1fr', '1fr 1fr 1fr'];
|
|
10
|
+
/**
|
|
11
|
+
* URL (optionally with `?token=`) of the LOCAL Jupyter server that the node's
|
|
12
|
+
* agents should use as their `jupyter` code sandbox. This mirrors
|
|
13
|
+
* `NotebookAgentExample`: the agent and the chat's ephemeral notebook share the
|
|
14
|
+
* same local Jupyter server directly — no proxy or tunnel is involved. When it
|
|
15
|
+
* is undefined (e.g. SaaS deployments) the launch falls back to the backend's
|
|
16
|
+
* default sandbox configuration.
|
|
17
|
+
*/
|
|
18
|
+
const LOCAL_JUPYTER_SANDBOX_URL = import.meta.env.VITE_JUPYTER_SANDBOX_URL || undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Agent gallery picker shown on the Agent Node.
|
|
21
|
+
if (!createResponse.ok && createResponse.status === 409) {
|
|
22
|
+
// If the named agent already exists, reload running agents and try
|
|
23
|
+
// to reuse the matching instance instead of failing the launch.
|
|
24
|
+
const reloadResponse = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
25
|
+
headers: authHeaders,
|
|
26
|
+
});
|
|
27
|
+
if (reloadResponse.ok) {
|
|
28
|
+
const reloadPayload = await reloadResponse
|
|
29
|
+
.json()
|
|
30
|
+
.catch(() => null);
|
|
31
|
+
const nextRunning: RunningAgent[] = Array.isArray(reloadPayload)
|
|
32
|
+
? reloadPayload
|
|
33
|
+
: reloadPayload?.agents || reloadPayload?.items || [];
|
|
34
|
+
setRunning(nextRunning);
|
|
35
|
+
const existing = nextRunning.find(candidate => {
|
|
36
|
+
const candidateId = candidate.agent_id || candidate.id || '';
|
|
37
|
+
return (
|
|
38
|
+
candidate.agent_spec_id === spec.id ||
|
|
39
|
+
candidate.name === requestedName ||
|
|
40
|
+
candidateId === requestedName
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
agentId = existing?.agent_id || existing?.id || null;
|
|
44
|
+
|
|
45
|
+
// If a same-name agent exists but is not tied to this spec,
|
|
46
|
+
// recreate it so configure/spec lookups and sandbox settings
|
|
47
|
+
// match the selected gallery card.
|
|
48
|
+
if (existing && existing.agent_spec_id !== spec.id) {
|
|
49
|
+
const staleId = String(existing.agent_id || existing.id || '').trim();
|
|
50
|
+
if (staleId) {
|
|
51
|
+
await fetch(
|
|
52
|
+
`${baseUrl}/api/v1/agents/${encodeURIComponent(staleId)}`,
|
|
53
|
+
{
|
|
54
|
+
method: 'DELETE',
|
|
55
|
+
headers: authHeaders,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
agentId = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
* Sources cards from the agentspec library and highlights the agent that is
|
|
63
|
+
* already running on the node. Launching a card creates the agent locally and
|
|
64
|
+
* marks it active so tunneled prompts from the main UI reach it.
|
|
65
|
+
*/
|
|
66
|
+
export function AgentNodeGallery({ baseUrl, token, activeAgentId, onLaunched, onTerminated, onLaunchError, }) {
|
|
67
|
+
const [specs, setSpecs] = useState([]);
|
|
68
|
+
const [running, setRunning] = useState([]);
|
|
69
|
+
const [loading, setLoading] = useState(true);
|
|
70
|
+
const [error, setError] = useState(null);
|
|
71
|
+
const [launchingId, setLaunchingId] = useState(null);
|
|
72
|
+
const [terminating, setTerminating] = useState(false);
|
|
73
|
+
const [search, setSearch] = useState('');
|
|
74
|
+
const authHeaders = useMemo(() => {
|
|
75
|
+
const headers = {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
};
|
|
78
|
+
if (token) {
|
|
79
|
+
headers.Authorization = `Bearer ${token}`;
|
|
80
|
+
}
|
|
81
|
+
return headers;
|
|
82
|
+
}, [token]);
|
|
83
|
+
const load = useCallback(async () => {
|
|
84
|
+
setLoading(true);
|
|
85
|
+
setError(null);
|
|
86
|
+
try {
|
|
87
|
+
const [libraryResponse, agentsResponse] = await Promise.all([
|
|
88
|
+
fetch(`${baseUrl}/api/v1/agents/library`, { headers: authHeaders }),
|
|
89
|
+
fetch(`${baseUrl}/api/v1/agents`, { headers: authHeaders }),
|
|
90
|
+
]);
|
|
91
|
+
if (!libraryResponse.ok) {
|
|
92
|
+
throw new Error(`Failed to load agent library (${libraryResponse.status})`);
|
|
93
|
+
}
|
|
94
|
+
const libraryPayload = await libraryResponse.json().catch(() => null);
|
|
95
|
+
const nextSpecs = Array.isArray(libraryPayload)
|
|
96
|
+
? libraryPayload
|
|
97
|
+
: libraryPayload?.agents || libraryPayload?.items || [];
|
|
98
|
+
setSpecs(nextSpecs);
|
|
99
|
+
if (agentsResponse.ok) {
|
|
100
|
+
const agentsPayload = await agentsResponse.json().catch(() => null);
|
|
101
|
+
const nextRunning = Array.isArray(agentsPayload)
|
|
102
|
+
? agentsPayload
|
|
103
|
+
: agentsPayload?.agents || agentsPayload?.items || [];
|
|
104
|
+
setRunning(nextRunning);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (reason) {
|
|
108
|
+
setError(reason?.message || 'Unable to load agents.');
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
setLoading(false);
|
|
112
|
+
}
|
|
113
|
+
}, [authHeaders, baseUrl]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
void load();
|
|
116
|
+
}, [load]);
|
|
117
|
+
const runningBySpec = useMemo(() => {
|
|
118
|
+
const map = new Map();
|
|
119
|
+
for (const agent of running) {
|
|
120
|
+
const agentId = agent.agent_id || agent.id;
|
|
121
|
+
if (agent.agent_spec_id && agentId) {
|
|
122
|
+
map.set(agent.agent_spec_id, agentId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return map;
|
|
126
|
+
}, [running]);
|
|
127
|
+
const normalizedActiveAgentId = String(activeAgentId || '').trim();
|
|
128
|
+
const activeAgent = useMemo(() => {
|
|
129
|
+
if (!normalizedActiveAgentId) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return (running.find(candidate => {
|
|
133
|
+
const candidateId = String(candidate.agent_id || candidate.id || '').trim();
|
|
134
|
+
return candidateId === normalizedActiveAgentId;
|
|
135
|
+
}) || null);
|
|
136
|
+
}, [running, normalizedActiveAgentId]);
|
|
137
|
+
// An "active" agent must be both configured and currently running.
|
|
138
|
+
const hasActiveAgent = Boolean(normalizedActiveAgentId && activeAgent);
|
|
139
|
+
const normalizedSearch = search.trim().toLowerCase();
|
|
140
|
+
const filteredSpecs = useMemo(() => {
|
|
141
|
+
if (!normalizedSearch) {
|
|
142
|
+
return specs;
|
|
143
|
+
}
|
|
144
|
+
return specs.filter(spec => {
|
|
145
|
+
const haystack = [
|
|
146
|
+
spec.id,
|
|
147
|
+
spec.name,
|
|
148
|
+
spec.description,
|
|
149
|
+
spec.model,
|
|
150
|
+
spec.inference_provider,
|
|
151
|
+
...(spec.tags || []),
|
|
152
|
+
]
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join(' ')
|
|
155
|
+
.toLowerCase();
|
|
156
|
+
return haystack.includes(normalizedSearch);
|
|
157
|
+
});
|
|
158
|
+
}, [specs, normalizedSearch]);
|
|
159
|
+
const launch = useCallback(async (spec) => {
|
|
160
|
+
setLaunchingId(spec.id);
|
|
161
|
+
setError(null);
|
|
162
|
+
try {
|
|
163
|
+
// Reuse the already-running agent for this spec when present so we do
|
|
164
|
+
// not spawn duplicates on the node.
|
|
165
|
+
let agentId = runningBySpec.get(spec.id) || null;
|
|
166
|
+
if (!agentId) {
|
|
167
|
+
const requestedName = spec.id;
|
|
168
|
+
const createResponse = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: authHeaders,
|
|
171
|
+
// Keep local node launches aligned with the working examples:
|
|
172
|
+
// POST /api/v1/agents requires `name` and optionally agent_spec_id.
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
name: requestedName,
|
|
175
|
+
agent_spec_id: spec.id,
|
|
176
|
+
transport: 'ag-ui',
|
|
177
|
+
sandbox_variant: 'jupyter',
|
|
178
|
+
sandboxVariant: 'jupyter',
|
|
179
|
+
...(LOCAL_JUPYTER_SANDBOX_URL
|
|
180
|
+
? { jupyter_sandbox: LOCAL_JUPYTER_SANDBOX_URL }
|
|
181
|
+
: {}),
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
if (!createResponse.ok && createResponse.status === 409) {
|
|
185
|
+
// If the named agent already exists, reload running agents and try
|
|
186
|
+
// to reuse the matching instance instead of failing the launch.
|
|
187
|
+
const reloadResponse = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
188
|
+
headers: authHeaders,
|
|
189
|
+
});
|
|
190
|
+
if (reloadResponse.ok) {
|
|
191
|
+
const reloadPayload = await reloadResponse
|
|
192
|
+
.json()
|
|
193
|
+
.catch(() => null);
|
|
194
|
+
const nextRunning = Array.isArray(reloadPayload)
|
|
195
|
+
? reloadPayload
|
|
196
|
+
: reloadPayload?.agents || reloadPayload?.items || [];
|
|
197
|
+
setRunning(nextRunning);
|
|
198
|
+
const existing = nextRunning.find(candidate => {
|
|
199
|
+
const candidateId = candidate.agent_id || candidate.id || '';
|
|
200
|
+
return (candidate.agent_spec_id === spec.id ||
|
|
201
|
+
candidate.name === requestedName ||
|
|
202
|
+
candidateId === requestedName);
|
|
203
|
+
});
|
|
204
|
+
agentId = existing?.agent_id || existing?.id || null;
|
|
205
|
+
// If a same-name agent exists but is not tied to this spec,
|
|
206
|
+
// recreate it so configure/spec lookups and sandbox settings
|
|
207
|
+
// match the selected gallery card.
|
|
208
|
+
if (existing && existing.agent_spec_id !== spec.id) {
|
|
209
|
+
const staleId = String(existing.agent_id || existing.id || '').trim();
|
|
210
|
+
if (staleId) {
|
|
211
|
+
await fetch(`${baseUrl}/api/v1/agents/${encodeURIComponent(staleId)}`, {
|
|
212
|
+
method: 'DELETE',
|
|
213
|
+
headers: authHeaders,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
agentId = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!createResponse.ok && !agentId) {
|
|
221
|
+
// Conflict with stale same-name agent is handled above by deleting
|
|
222
|
+
// and recreating. Other failures remain hard errors.
|
|
223
|
+
if (createResponse.status !== 409) {
|
|
224
|
+
throw new Error(`Failed to launch agent (${createResponse.status})`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!agentId && createResponse.ok) {
|
|
228
|
+
const createPayload = await createResponse.json().catch(() => null);
|
|
229
|
+
agentId =
|
|
230
|
+
createPayload?.agent_id ||
|
|
231
|
+
createPayload?.id ||
|
|
232
|
+
createPayload?.agent?.agent_id ||
|
|
233
|
+
createPayload?.agent?.id ||
|
|
234
|
+
null;
|
|
235
|
+
}
|
|
236
|
+
if (!agentId) {
|
|
237
|
+
const recreateResponse = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: authHeaders,
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
name: requestedName,
|
|
242
|
+
agent_spec_id: spec.id,
|
|
243
|
+
transport: 'ag-ui',
|
|
244
|
+
sandbox_variant: 'jupyter',
|
|
245
|
+
sandboxVariant: 'jupyter',
|
|
246
|
+
...(LOCAL_JUPYTER_SANDBOX_URL
|
|
247
|
+
? { jupyter_sandbox: LOCAL_JUPYTER_SANDBOX_URL }
|
|
248
|
+
: {}),
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
if (!recreateResponse.ok) {
|
|
252
|
+
throw new Error(`Failed to launch agent (${recreateResponse.status})`);
|
|
253
|
+
}
|
|
254
|
+
const recreatePayload = await recreateResponse.json().catch(() => null);
|
|
255
|
+
agentId =
|
|
256
|
+
recreatePayload?.agent_id ||
|
|
257
|
+
recreatePayload?.id ||
|
|
258
|
+
recreatePayload?.agent?.agent_id ||
|
|
259
|
+
recreatePayload?.agent?.id ||
|
|
260
|
+
null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (!agentId) {
|
|
264
|
+
throw new Error('Launch did not return an agent id.');
|
|
265
|
+
}
|
|
266
|
+
// Best-effort guard: if spec metadata is missing for this agent id,
|
|
267
|
+
// or if it is not configured with a jupyter sandbox, recreate once
|
|
268
|
+
// from the selected spec so notebook/document chat surfaces can bind
|
|
269
|
+
// a live local kernel.
|
|
270
|
+
const specResponse = await fetch(`${baseUrl}/api/v1/configure/agents/${encodeURIComponent(agentId)}/spec`, { headers: authHeaders });
|
|
271
|
+
let mustRecreateForSandbox = false;
|
|
272
|
+
if (specResponse.ok) {
|
|
273
|
+
const specPayload = await specResponse.json().catch(() => null);
|
|
274
|
+
const variant = String(specPayload?.sandbox_variant || specPayload?.sandboxVariant || '')
|
|
275
|
+
.trim()
|
|
276
|
+
.toLowerCase();
|
|
277
|
+
mustRecreateForSandbox = variant !== 'jupyter';
|
|
278
|
+
}
|
|
279
|
+
if (specResponse.status === 404 || mustRecreateForSandbox) {
|
|
280
|
+
await fetch(`${baseUrl}/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
281
|
+
method: 'DELETE',
|
|
282
|
+
headers: authHeaders,
|
|
283
|
+
});
|
|
284
|
+
const recreateResponse = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
headers: authHeaders,
|
|
287
|
+
body: JSON.stringify({
|
|
288
|
+
name: spec.id,
|
|
289
|
+
agent_spec_id: spec.id,
|
|
290
|
+
transport: 'ag-ui',
|
|
291
|
+
sandbox_variant: 'jupyter',
|
|
292
|
+
sandboxVariant: 'jupyter',
|
|
293
|
+
...(LOCAL_JUPYTER_SANDBOX_URL
|
|
294
|
+
? { jupyter_sandbox: LOCAL_JUPYTER_SANDBOX_URL }
|
|
295
|
+
: {}),
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
298
|
+
if (!recreateResponse.ok) {
|
|
299
|
+
throw new Error(`Failed to recreate agent with spec (${recreateResponse.status})`);
|
|
300
|
+
}
|
|
301
|
+
const recreatePayload = await recreateResponse.json().catch(() => null);
|
|
302
|
+
agentId =
|
|
303
|
+
recreatePayload?.agent_id ||
|
|
304
|
+
recreatePayload?.id ||
|
|
305
|
+
recreatePayload?.agent?.agent_id ||
|
|
306
|
+
recreatePayload?.agent?.id ||
|
|
307
|
+
null;
|
|
308
|
+
}
|
|
309
|
+
if (!agentId) {
|
|
310
|
+
throw new Error('Launch did not return an agent id.');
|
|
311
|
+
}
|
|
312
|
+
const activeResponse = await fetch(`${baseUrl}/api/v1/agent-node/active-agent`, {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: authHeaders,
|
|
315
|
+
body: JSON.stringify({ agent_id: agentId }),
|
|
316
|
+
});
|
|
317
|
+
if (!activeResponse.ok) {
|
|
318
|
+
throw new Error(`Failed to set active agent (${activeResponse.status})`);
|
|
319
|
+
}
|
|
320
|
+
onLaunched(agentId);
|
|
321
|
+
}
|
|
322
|
+
catch (reason) {
|
|
323
|
+
const message = reason?.message || 'Unable to launch agent.';
|
|
324
|
+
setError(message);
|
|
325
|
+
onLaunchError?.(message);
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
setLaunchingId(null);
|
|
329
|
+
}
|
|
330
|
+
}, [authHeaders, baseUrl, onLaunched, onLaunchError, runningBySpec]);
|
|
331
|
+
const terminateActiveAgent = useCallback(async () => {
|
|
332
|
+
if (!normalizedActiveAgentId) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
setTerminating(true);
|
|
336
|
+
setError(null);
|
|
337
|
+
try {
|
|
338
|
+
const deleteResponse = await fetch(`${baseUrl}/api/v1/agents/${encodeURIComponent(normalizedActiveAgentId)}`, {
|
|
339
|
+
method: 'DELETE',
|
|
340
|
+
headers: authHeaders,
|
|
341
|
+
});
|
|
342
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
343
|
+
throw new Error(`Failed to terminate active agent (${deleteResponse.status})`);
|
|
344
|
+
}
|
|
345
|
+
const clearResponse = await fetch(`${baseUrl}/api/v1/agent-node/active-agent`, {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
headers: authHeaders,
|
|
348
|
+
body: JSON.stringify({ agent_id: null }),
|
|
349
|
+
});
|
|
350
|
+
if (!clearResponse.ok) {
|
|
351
|
+
throw new Error(`Failed to clear active agent (${clearResponse.status})`);
|
|
352
|
+
}
|
|
353
|
+
setRunning(prev => prev.filter(candidate => {
|
|
354
|
+
const candidateId = String(candidate.agent_id || candidate.id || '').trim();
|
|
355
|
+
return candidateId !== normalizedActiveAgentId;
|
|
356
|
+
}));
|
|
357
|
+
onTerminated?.(normalizedActiveAgentId);
|
|
358
|
+
}
|
|
359
|
+
catch (reason) {
|
|
360
|
+
const message = reason?.message || 'Unable to terminate active agent.';
|
|
361
|
+
setError(message);
|
|
362
|
+
onLaunchError?.(message);
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
setTerminating(false);
|
|
366
|
+
}
|
|
367
|
+
}, [authHeaders, baseUrl, normalizedActiveAgentId, onTerminated, onLaunchError]);
|
|
368
|
+
if (loading) {
|
|
369
|
+
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 6 }, children: _jsx(Spinner, { size: "large" }) }));
|
|
370
|
+
}
|
|
371
|
+
return (_jsxs(Box, { children: [_jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Choose an agent" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Pick an agent from the gallery to launch it on this node. The active agent handles prompts sent directly to the node and through the Datalayer platform." })] }), error && (_jsx(Box, { sx: {
|
|
372
|
+
mb: 3,
|
|
373
|
+
p: 2,
|
|
374
|
+
borderRadius: 2,
|
|
375
|
+
bg: 'danger.subtle',
|
|
376
|
+
color: 'danger.fg',
|
|
377
|
+
fontSize: 1,
|
|
378
|
+
}, children: error })), hasActiveAgent ? (_jsxs(Box, { sx: {
|
|
379
|
+
p: 4,
|
|
380
|
+
border: '1px solid',
|
|
381
|
+
borderColor: 'success.muted',
|
|
382
|
+
borderRadius: 2,
|
|
383
|
+
bg: 'success.subtle',
|
|
384
|
+
display: 'flex',
|
|
385
|
+
flexDirection: 'column',
|
|
386
|
+
gap: 3,
|
|
387
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsxs(Label, { variant: "success", sx: { display: 'inline-flex', alignItems: 'center', gap: 1 }, children: [_jsx(CheckCircleFillIcon, { size: 12 }), "Active agent"] }), _jsx(Text, { sx: { fontWeight: 600 }, children: activeAgent?.name || activeAgent?.agent_spec_id || normalizedActiveAgentId })] }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Chat is enabled while an active agent is running on this node. Terminate it to return to the agent cards picker." }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-start' }, children: _jsx(Button, { variant: "danger", disabled: terminating, onClick: () => void terminateActiveAgent(), children: terminating ? 'Terminating…' : 'Terminate' }) })] })) : specs.length === 0 ? (_jsx(Box, { sx: {
|
|
388
|
+
p: 4,
|
|
389
|
+
textAlign: 'center',
|
|
390
|
+
border: '1px solid',
|
|
391
|
+
borderColor: 'border.default',
|
|
392
|
+
borderRadius: 2,
|
|
393
|
+
color: 'fg.muted',
|
|
394
|
+
}, children: "No agents are available in the library yet." })) : (_jsxs(_Fragment, { children: [_jsx(Box, { sx: { mb: 3, maxWidth: 420 }, children: _jsx(TextInput, { value: search, onChange: event => setSearch(event.target.value), placeholder: "Filter agents by name, description, model, or tag...", block: true }) }), _jsx(Box, { sx: {
|
|
395
|
+
display: 'grid',
|
|
396
|
+
gridTemplateColumns: GRID_TEMPLATE,
|
|
397
|
+
gap: 3,
|
|
398
|
+
}, children: filteredSpecs.map(spec => {
|
|
399
|
+
const isActive = !!activeAgentId &&
|
|
400
|
+
runningBySpec.get(spec.id) === activeAgentId;
|
|
401
|
+
const isLaunching = launchingId === spec.id;
|
|
402
|
+
const isRunning = runningBySpec.has(spec.id);
|
|
403
|
+
return (_jsxs(Box, { sx: {
|
|
404
|
+
display: 'flex',
|
|
405
|
+
flexDirection: 'column',
|
|
406
|
+
p: 3,
|
|
407
|
+
border: '1px solid',
|
|
408
|
+
borderColor: isActive ? 'success.emphasis' : 'border.default',
|
|
409
|
+
borderRadius: 2,
|
|
410
|
+
bg: 'canvas.default',
|
|
411
|
+
transition: 'box-shadow 0.15s ease, transform 0.15s ease',
|
|
412
|
+
':hover': {
|
|
413
|
+
boxShadow: 'shadow.medium',
|
|
414
|
+
transform: 'translateY(-2px)',
|
|
415
|
+
},
|
|
416
|
+
}, children: [_jsxs(Box, { sx: {
|
|
417
|
+
display: 'flex',
|
|
418
|
+
alignItems: 'center',
|
|
419
|
+
gap: 2,
|
|
420
|
+
mb: 2,
|
|
421
|
+
}, children: [_jsx(Box, { sx: {
|
|
422
|
+
display: 'flex',
|
|
423
|
+
alignItems: 'center',
|
|
424
|
+
justifyContent: 'center',
|
|
425
|
+
width: 40,
|
|
426
|
+
height: 40,
|
|
427
|
+
borderRadius: 2,
|
|
428
|
+
fontSize: 3,
|
|
429
|
+
bg: 'canvas.subtle',
|
|
430
|
+
}, children: spec.emoji || '🤖' }), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Heading, { as: "h3", sx: {
|
|
431
|
+
fontSize: 2,
|
|
432
|
+
mb: 0,
|
|
433
|
+
overflow: 'hidden',
|
|
434
|
+
textOverflow: 'ellipsis',
|
|
435
|
+
whiteSpace: 'nowrap',
|
|
436
|
+
}, children: spec.name || spec.id }), spec.version && (_jsxs(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: ["v", spec.version] }))] }), isActive && (_jsxs(Label, { variant: "success", sx: { display: 'inline-flex', alignItems: 'center', gap: 1 }, children: [_jsx(CheckCircleFillIcon, { size: 12 }), "Active"] }))] }), spec.description && (_jsx(Text, { sx: {
|
|
437
|
+
color: 'fg.muted',
|
|
438
|
+
fontSize: 1,
|
|
439
|
+
mb: 2,
|
|
440
|
+
display: '-webkit-box',
|
|
441
|
+
WebkitLineClamp: 3,
|
|
442
|
+
WebkitBoxOrient: 'vertical',
|
|
443
|
+
overflow: 'hidden',
|
|
444
|
+
}, children: spec.description })), spec.tags && spec.tags.length > 0 && (_jsx(Box, { sx: {
|
|
445
|
+
display: 'flex',
|
|
446
|
+
flexWrap: 'wrap',
|
|
447
|
+
gap: 1,
|
|
448
|
+
mb: 3,
|
|
449
|
+
}, children: spec.tags.slice(0, 4).map(tag => (_jsx(Label, { size: "small", variant: "secondary", children: tag }, tag))) })), _jsx(Box, { sx: { mt: 'auto', pt: 1 }, children: _jsx(Button, { variant: isActive ? 'default' : 'primary', leadingVisual: RocketIcon, block: true, disabled: isLaunching, onClick: () => void launch(spec), children: isActive
|
|
450
|
+
? 'Open chat'
|
|
451
|
+
: isLaunching
|
|
452
|
+
? 'Launching…'
|
|
453
|
+
: isRunning
|
|
454
|
+
? 'Set active'
|
|
455
|
+
: 'Launch' }) })] }, spec.id));
|
|
456
|
+
}) }), filteredSpecs.length === 0 && (_jsx(Box, { sx: {
|
|
457
|
+
mt: 3,
|
|
458
|
+
p: 3,
|
|
459
|
+
border: '1px solid',
|
|
460
|
+
borderColor: 'border.default',
|
|
461
|
+
borderRadius: 2,
|
|
462
|
+
}, children: _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "No agents match this filter." }) }))] }))] }));
|
|
463
|
+
}
|
|
464
|
+
export default AgentNodeGallery;
|
|
@@ -4,7 +4,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
6
|
import { createRoot } from 'react-dom/client';
|
|
7
|
-
// import { Agent as AgentNotebook } from './Agent';
|
|
8
7
|
import { AgentNotebook } from './AgentNotebook';
|
|
9
8
|
const rootElement = document.getElementById('root');
|
|
10
9
|
if (rootElement) {
|
package/lib/chat/Chat.d.ts
CHANGED
|
@@ -10,6 +10,19 @@ export interface ChatProps extends ChatCommonProps {
|
|
|
10
10
|
extensions?: Extension[];
|
|
11
11
|
/** Base URL of the server (for HTTP-based protocols) */
|
|
12
12
|
baseUrl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Explicit endpoint URL override. When set, it is used verbatim instead of
|
|
15
|
+
* deriving `${baseUrl}${getEndpointPath(protocol, agentId)}`. Useful for
|
|
16
|
+
* proxied endpoints such as the Agent Node tunnel proxy.
|
|
17
|
+
*/
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Explicit config endpoint override. When provided (including `null`/empty
|
|
21
|
+
* to disable), it replaces the default `${baseUrl}/api/v1/configure`.
|
|
22
|
+
*/
|
|
23
|
+
configEndpoint?: string;
|
|
24
|
+
/** Override whether the protocol queries the backend for models/tools. */
|
|
25
|
+
enableConfigQuery?: boolean;
|
|
13
26
|
/** WebSocket URL (for WebSocket-based protocols like ACP) */
|
|
14
27
|
wsUrl?: string;
|
|
15
28
|
/** Agent ID */
|
|
@@ -94,5 +107,5 @@ export interface ChatProps extends ChatCommonProps {
|
|
|
94
107
|
* />
|
|
95
108
|
* ```
|
|
96
109
|
*/
|
|
97
|
-
export declare function Chat({ protocol: transport, extensions: _extensions, baseUrl, wsUrl, agentId, authToken: authTokenProp, placeholder, title, subtitle, brandIcon, autoConnect, streaming: _streaming, onMessageSent: _onMessageSent, onMessageReceived: _onMessageReceived, onDisconnect, onLogout: _onLogout, onCollapsePanel: _onCollapsePanel, className, height, showHeader, showNewChatButton, showClearButton, showModelSelector, showToolsMenu, showInput, disableInputPrompt, overlay, launching, launchingMessage, showSkillsMenu, codemodeEnabled, onToggleCodemode, showTokenUsage, initialModel, availableModels, mcpServers, initialSkills, clearOnMount: _clearOnMount, suggestions, submitOnSuggestionClick, description, headerContent, headerActions, autoFocus, identityProviders, onIdentityConnect, onIdentityDisconnect, runtimeId, historyEndpoint, pendingPrompt, errorBanner, showInformation, chatViewMode, onChatViewModeChange, frontendTools, enableEphemeralNotebook, initialEphemeralNotebookOpen, onEphemeralNotebookOpenChange, enableEphemeralDocument, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed, onExpandFromCollapsed, ephemeralNotebookToolbar, onToolCallStart, onToolCallComplete, renderToolResult, hideMessagesAfterToolUI, contextSnapshot, mcpStatusData, codemodeStatusData, sandboxStatusData, kernel, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, disableInternalJupyterTheme, }: ChatProps): import("react/jsx-runtime").JSX.Element;
|
|
110
|
+
export declare function Chat({ protocol: transport, extensions: _extensions, baseUrl, endpoint: endpointProp, configEndpoint: configEndpointProp, enableConfigQuery: enableConfigQueryProp, wsUrl, agentId, authToken: authTokenProp, placeholder, title, subtitle, brandIcon, autoConnect, streaming: _streaming, onMessageSent: _onMessageSent, onMessageReceived: _onMessageReceived, onDisconnect, onLogout: _onLogout, onCollapsePanel: _onCollapsePanel, className, height, showHeader, showNewChatButton, showClearButton, showModelSelector, showToolsMenu, showInput, disableInputPrompt, overlay, launching, launchingMessage, showSkillsMenu, codemodeEnabled, onToggleCodemode, showTokenUsage, initialModel, availableModels, mcpServers, initialSkills, clearOnMount: _clearOnMount, suggestions, submitOnSuggestionClick, description, headerContent, headerActions, autoFocus, identityProviders, onIdentityConnect, onIdentityDisconnect, runtimeId, historyEndpoint, pendingPrompt, errorBanner, showInformation, chatViewMode, onChatViewModeChange, frontendTools, enableEphemeralNotebook, initialEphemeralNotebookOpen, onEphemeralNotebookOpenChange, enableEphemeralDocument, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride, onToolCallStart, onToolCallComplete, renderToolResult, hideMessagesAfterToolUI, contextSnapshot, mcpStatusData, codemodeStatusData, sandboxStatusData, kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, disableInternalJupyterTheme, }: ChatProps): import("react/jsx-runtime").JSX.Element;
|
|
98
111
|
export default Chat;
|