@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.
@@ -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) {
@@ -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;