@datalayer/agent-runtimes 1.0.5 → 1.0.6
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 +157 -10
- package/lib/AgentNode.d.ts +3 -0
- package/lib/AgentNode.js +676 -0
- package/lib/agent-node/themeStore.d.ts +3 -0
- package/lib/agent-node/themeStore.js +156 -0
- package/lib/agent-node-main.d.ts +1 -0
- package/lib/agent-node-main.js +14 -0
- package/lib/chat/Chat.js +16 -10
- package/lib/chat/ChatFloating.js +1 -1
- package/lib/chat/ChatSidebar.js +81 -49
- package/lib/chat/base/ChatBase.js +388 -74
- package/lib/chat/display/FloatingBrandButton.js +8 -1
- package/lib/chat/header/ChatHeader.d.ts +3 -1
- package/lib/chat/header/ChatHeader.js +15 -12
- package/lib/chat/header/ChatHeaderBase.d.ts +29 -9
- package/lib/chat/header/ChatHeaderBase.js +26 -3
- package/lib/chat/indicators/SandboxStatusIndicator.js +82 -47
- package/lib/chat/messages/ChatMessageList.js +46 -1
- package/lib/chat/messages/ChatMessages.js +6 -2
- package/lib/chat/prompt/InputFooter.d.ts +3 -1
- package/lib/chat/prompt/InputFooter.js +8 -5
- package/lib/chat/prompt/InputPrompt.d.ts +3 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptFooter.d.ts +3 -1
- package/lib/chat/prompt/InputPromptFooter.js +3 -3
- package/lib/client/AgentsMixin.js +14 -0
- package/lib/config/AgentConfiguration.d.ts +22 -0
- package/lib/config/AgentConfiguration.js +319 -64
- package/lib/examples/AgUiSharedStateExample.js +2 -1
- package/lib/examples/AgentCheckpointsExample.js +3 -3
- package/lib/examples/AgentCodemodeExample.d.ts +3 -3
- package/lib/examples/AgentCodemodeExample.js +24 -12
- package/lib/examples/AgentEvalsExample.js +330 -40
- package/lib/examples/AgentGuardrailsExample.js +16 -5
- package/lib/examples/AgentHooksExample.js +27 -9
- package/lib/examples/AgentInferenceProviderExample.d.ts +3 -0
- package/lib/examples/AgentInferenceProviderExample.js +329 -0
- package/lib/examples/AgentMCPExample.js +6 -5
- package/lib/examples/AgentMemoryExample.d.ts +1 -2
- package/lib/examples/AgentMemoryExample.js +71 -22
- package/lib/examples/AgentMonitoringExample.js +5 -5
- package/lib/examples/AgentNotificationsExample.d.ts +1 -2
- package/lib/examples/AgentNotificationsExample.js +71 -22
- package/lib/examples/AgentOtelExample.js +31 -40
- package/lib/examples/AgentOutputsExample.d.ts +1 -1
- package/lib/examples/AgentOutputsExample.js +67 -16
- package/lib/examples/AgentParametersExample.js +10 -8
- package/lib/examples/AgentSandboxExample.d.ts +1 -1
- package/lib/examples/AgentSandboxExample.js +7 -6
- package/lib/examples/AgentSkillsExample.js +6 -6
- package/lib/examples/AgentSubagentsExample.d.ts +1 -1
- package/lib/examples/AgentSubagentsExample.js +6 -6
- package/lib/examples/AgentToolApprovalsExample.js +27 -11
- package/lib/examples/AgentTriggersExample.js +5 -5
- package/lib/examples/{AgentSpecsExample.d.ts → AgentspecsExample.d.ts} +2 -2
- package/lib/examples/AgentspecsExample.js +1096 -0
- package/lib/examples/ChatCustomExample.js +6 -5
- package/lib/examples/ChatExample.js +6 -5
- package/lib/examples/Lexical2Example.js +1 -1
- package/lib/examples/LexicalAgentExample.js +1 -1
- package/lib/examples/NotebookAgentExample.js +3 -3
- package/lib/examples/components/ExampleWrapper.d.ts +6 -7
- package/lib/examples/components/ExampleWrapper.js +27 -10
- package/lib/examples/example-selector.js +2 -1
- package/lib/examples/index.d.ts +2 -1
- package/lib/examples/index.js +2 -1
- package/lib/examples/lexical/initial-content.json +6 -6
- package/lib/examples/main.js +56 -16
- package/lib/examples/utils/agentId.d.ts +1 -1
- package/lib/examples/utils/agentId.js +1 -1
- package/lib/examples/utils/useExampleAgentRuntimesUrl.d.ts +5 -0
- package/lib/examples/utils/useExampleAgentRuntimesUrl.js +19 -0
- package/lib/hooks/useAIAgentsWebSocket.js +35 -0
- package/lib/hooks/useAgentRuntimes.d.ts +32 -3
- package/lib/hooks/useAgentRuntimes.js +114 -19
- package/lib/index.d.ts +1 -1
- package/lib/specs/agents/agents.d.ts +20 -13
- package/lib/specs/agents/agents.js +1267 -581
- package/lib/specs/benchmarks.d.ts +20 -0
- package/lib/specs/benchmarks.js +205 -0
- package/lib/specs/envvars.d.ts +0 -1
- package/lib/specs/envvars.js +0 -11
- package/lib/specs/evals.d.ts +10 -9
- package/lib/specs/evals.js +128 -88
- package/lib/specs/index.d.ts +0 -1
- package/lib/specs/index.js +0 -1
- package/lib/specs/models.d.ts +0 -2
- package/lib/specs/models.js +0 -15
- package/lib/specs/skills.d.ts +0 -1
- package/lib/specs/skills.js +0 -18
- package/lib/stores/agentRuntimeStore.d.ts +5 -1
- package/lib/stores/agentRuntimeStore.js +22 -8
- package/lib/stores/conversationStore.js +2 -2
- package/lib/types/agents-lifecycle.d.ts +18 -0
- package/lib/types/agents.d.ts +6 -0
- package/lib/types/agentspecs.d.ts +4 -0
- package/lib/types/benchmarks.d.ts +43 -0
- package/lib/types/benchmarks.js +5 -0
- package/lib/types/chat.d.ts +16 -0
- package/lib/types/evals.d.ts +26 -17
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/package.json +9 -5
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_benchmarks.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +89 -43
- package/scripts/codegen/generate_benchmarks.py +441 -0
- package/scripts/codegen/generate_evals.py +94 -16
- package/scripts/codegen/generate_events.py +0 -1
- package/lib/examples/AgentSpecsExample.js +0 -694
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgentCodemodeExample
|
|
3
3
|
*
|
|
4
|
-
* Compares two
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
4
|
+
* Compares two tooling modes side-by-side:
|
|
5
|
+
* - MCP tools without codemode conversion
|
|
6
|
+
* - MCP tools with codemode conversion
|
|
7
7
|
*
|
|
8
8
|
* A sidebar gauge tracks consumed tokens for each agent in real time.
|
|
9
9
|
*/
|
|
@@ -6,9 +6,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
/**
|
|
7
7
|
* AgentCodemodeExample
|
|
8
8
|
*
|
|
9
|
-
* Compares two
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
9
|
+
* Compares two tooling modes side-by-side:
|
|
10
|
+
* - MCP tools without codemode conversion
|
|
11
|
+
* - MCP tools with codemode conversion
|
|
12
12
|
*
|
|
13
13
|
* A sidebar gauge tracks consumed tokens for each agent in real time.
|
|
14
14
|
*/
|
|
@@ -37,24 +37,24 @@ const NO_CODEMODE_BASE_URL = import.meta.env.VITE_BASE_URL_NO_CODEMODE ||
|
|
|
37
37
|
import.meta.env.VITE_BASE_URL ||
|
|
38
38
|
'http://localhost:8765';
|
|
39
39
|
const CODEMODE_BASE_URL = import.meta.env.VITE_BASE_URL_CODEMODE || 'http://localhost:8766';
|
|
40
|
-
const NO_CODEMODE_SUGGESTION_MESSAGE = 'Use the
|
|
40
|
+
const NO_CODEMODE_SUGGESTION_MESSAGE = 'Use the MCP extract tool to extract information from https://datalayer.ai, then use your sandbox to persist that information in a variable named "about_datalayer".';
|
|
41
41
|
const CODEMODE_SUGGESTION_MESSAGE = 'Extract information from the https://datalayer.ai website and assign it to the variable "about_datalayer", all in one step using the sandbox';
|
|
42
42
|
const DEMO_AGENT_CONFIGS = [
|
|
43
43
|
{
|
|
44
44
|
key: 'no-codemode',
|
|
45
|
-
title: '
|
|
45
|
+
title: 'MCP Tools (No Codemode)',
|
|
46
46
|
subtitle: 'Raw MCP tools without codemode conversion',
|
|
47
47
|
suggestionMessage: NO_CODEMODE_SUGGESTION_MESSAGE,
|
|
48
|
-
specId: '
|
|
48
|
+
specId: 'example-no-codemode',
|
|
49
49
|
color: '#0969DA',
|
|
50
50
|
baseUrl: NO_CODEMODE_BASE_URL,
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
key: 'codemode',
|
|
54
|
-
title: '
|
|
54
|
+
title: 'Codemode Tools',
|
|
55
55
|
subtitle: 'MCP tools converted into programmatic tools',
|
|
56
56
|
suggestionMessage: CODEMODE_SUGGESTION_MESSAGE,
|
|
57
|
-
specId: '
|
|
57
|
+
specId: 'example-codemode',
|
|
58
58
|
color: '#8250DF',
|
|
59
59
|
baseUrl: CODEMODE_BASE_URL,
|
|
60
60
|
},
|
|
@@ -87,13 +87,19 @@ const AgentRuntimePane = ({ config, token, onTokenConsumed, onAgentIdChange, onC
|
|
|
87
87
|
}), [token]);
|
|
88
88
|
useEffect(() => {
|
|
89
89
|
let cancelled = false;
|
|
90
|
+
const launchTimeoutMs = 20_000;
|
|
90
91
|
const createLocalAgent = async () => {
|
|
91
92
|
setRuntimeStatus('launching');
|
|
92
93
|
setHookError(null);
|
|
93
94
|
setIsReconnectedAgent(false);
|
|
94
95
|
try {
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const timeoutId = window.setTimeout(() => {
|
|
98
|
+
controller.abort();
|
|
99
|
+
}, launchTimeoutMs);
|
|
95
100
|
const response = await authFetch(`${config.baseUrl}/api/v1/agents`, {
|
|
96
101
|
method: 'POST',
|
|
102
|
+
signal: controller.signal,
|
|
97
103
|
body: JSON.stringify({
|
|
98
104
|
name: runtimeName,
|
|
99
105
|
description: config.subtitle,
|
|
@@ -104,6 +110,7 @@ const AgentRuntimePane = ({ config, token, onTokenConsumed, onAgentIdChange, onC
|
|
|
104
110
|
tools: [],
|
|
105
111
|
}),
|
|
106
112
|
});
|
|
113
|
+
window.clearTimeout(timeoutId);
|
|
107
114
|
let resolvedAgentId = runtimeName;
|
|
108
115
|
let alreadyRunning = false;
|
|
109
116
|
if (response.ok) {
|
|
@@ -139,7 +146,12 @@ const AgentRuntimePane = ({ config, token, onTokenConsumed, onAgentIdChange, onC
|
|
|
139
146
|
}
|
|
140
147
|
catch (error) {
|
|
141
148
|
if (!cancelled) {
|
|
142
|
-
|
|
149
|
+
const isAbortError = error instanceof DOMException && error.name === 'AbortError';
|
|
150
|
+
setHookError(isAbortError
|
|
151
|
+
? `Timed out after ${Math.round(launchTimeoutMs / 1000)}s while creating '${config.specId}' at ${config.baseUrl}. Ensure the no-codemode endpoint is reachable.`
|
|
152
|
+
: error instanceof Error
|
|
153
|
+
? `${error.message} (endpoint: ${config.baseUrl}, spec: ${config.specId})`
|
|
154
|
+
: `Agent failed to start (endpoint: ${config.baseUrl}, spec: ${config.specId})`);
|
|
143
155
|
setRuntimeStatus('error');
|
|
144
156
|
}
|
|
145
157
|
}
|
|
@@ -349,7 +361,7 @@ const AgentRuntimePane = ({ config, token, onTokenConsumed, onAgentIdChange, onC
|
|
|
349
361
|
justifyContent: 'center',
|
|
350
362
|
flexDirection: 'column',
|
|
351
363
|
gap: 2,
|
|
352
|
-
}, children: [_jsx(Spinner, { size: "small" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Launching ", config.title, "..."] })] }));
|
|
364
|
+
}, children: [_jsx(Spinner, { size: "small" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Launching ", config.title, "..."] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: config.baseUrl })] }));
|
|
353
365
|
}
|
|
354
366
|
if (runtimeStatus === 'error' || hookError) {
|
|
355
367
|
return (_jsxs(Flash, { variant: "danger", sx: { borderRadius: 2 }, children: [config.title, ": ", hookError || 'Failed to start'] }));
|
|
@@ -362,7 +374,7 @@ const AgentRuntimePane = ({ config, token, onTokenConsumed, onAgentIdChange, onC
|
|
|
362
374
|
minHeight: 560,
|
|
363
375
|
display: 'flex',
|
|
364
376
|
flexDirection: 'column',
|
|
365
|
-
}, children: _jsx(Box, { sx: { flex: 1, minHeight: 0 }, children: _jsx(Chat, { protocol: "vercel-ai", baseUrl: config.baseUrl, agentId: agentId, authToken: token, title: config.title, subtitle: config.subtitle, placeholder: "Ask both agents the same request to compare behavior...", description: config.subtitle, showHeader: true, headerActions: isReconnectedAgent ? (_jsx(Label, { size: "small", variant: "attention", children: "Reconnected" })) : undefined, autoFocus: false, height: "100%", runtimeId: agentId, historyEndpoint: `${config.baseUrl}/api/v1/history`, mcpStatusData: mcpStatusData, codemodeStatusData: codemodeStatusData, codemodeEnabled: codemodeEnabled, onToggleCodemode: handleToggleCodemode, suggestions: [
|
|
377
|
+
}, children: _jsx(Box, { sx: { flex: 1, minHeight: 0 }, children: _jsx(Chat, { protocol: "vercel-ai", baseUrl: config.baseUrl, agentId: agentId, authToken: token, title: config.title, brandIcon: _jsx(CodeIcon, { size: 16 }), subtitle: config.subtitle, placeholder: "Ask both agents the same request to compare behavior...", description: config.subtitle, showHeader: true, headerActions: isReconnectedAgent ? (_jsx(Label, { size: "small", variant: "attention", children: "Reconnected" })) : undefined, autoFocus: false, height: "100%", runtimeId: agentId, historyEndpoint: `${config.baseUrl}/api/v1/history`, mcpStatusData: mcpStatusData, codemodeStatusData: codemodeStatusData, codemodeEnabled: codemodeEnabled, onToggleCodemode: handleToggleCodemode, suggestions: [
|
|
366
378
|
{
|
|
367
379
|
title: 'Datalayer extraction',
|
|
368
380
|
message: config.suggestionMessage,
|
|
@@ -529,7 +541,7 @@ const AgentCodemodeInner = ({ onLogout, }) => {
|
|
|
529
541
|
borderBottom: '1px solid',
|
|
530
542
|
borderColor: 'border.default',
|
|
531
543
|
flexShrink: 0,
|
|
532
|
-
}, children: [_jsx(CodeIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2, flex: 1 }, children: "Codemode \u2014
|
|
544
|
+
}, children: [_jsx(CodeIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2, flex: 1 }, children: "Codemode \u2014 MCP Tools vs Codemode Tools" })] }), _jsxs(Box, { sx: { flex: 1, minHeight: 0, display: 'flex' }, children: [DEMO_AGENT_CONFIGS.filter(c => c.key === 'no-codemode').map(config => (() => {
|
|
533
545
|
const outcome = outcomeFor(config.key);
|
|
534
546
|
return (_jsxs(Box, { sx: {
|
|
535
547
|
width: 320,
|
|
@@ -15,41 +15,119 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
* pass/fail status, and the ability to run eval suites
|
|
16
16
|
*/
|
|
17
17
|
/// <reference types="vite/client" />
|
|
18
|
-
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
18
|
+
import { useEffect, useState, useCallback, useRef, useMemo, } from 'react';
|
|
19
19
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
20
|
-
import { Text, Button, Spinner, Heading, Label, Flash, ProgressBar, } from '@primer/react';
|
|
20
|
+
import { Text, Button, Spinner, Heading, Label, Flash, ProgressBar, Select, FormControl, } from '@primer/react';
|
|
21
21
|
import { BeakerIcon, CheckCircleIcon, XCircleIcon, PlayIcon, } from '@primer/octicons-react';
|
|
22
22
|
import { Box } from '@datalayer/primer-addons';
|
|
23
23
|
import { AuthRequiredView, ErrorView } from './components';
|
|
24
24
|
import { ThemedProvider } from './utils/themedProvider';
|
|
25
25
|
import { uniqueAgentId } from './utils/agentId';
|
|
26
|
+
import { useExampleAgentRuntimesUrl } from './utils/useExampleAgentRuntimesUrl';
|
|
26
27
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
28
|
+
import { useCoreStore } from '@datalayer/core';
|
|
27
29
|
import { Chat } from '../chat';
|
|
28
30
|
import { useAgentRuntimes } from '../hooks/useAgentRuntimes';
|
|
29
31
|
const queryClient = new QueryClient();
|
|
30
32
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
31
|
-
const AGENT_NAME = 'eval-
|
|
32
|
-
const AGENT_SPEC_ID = '
|
|
33
|
+
const AGENT_NAME = 'eval-example-agent';
|
|
34
|
+
const AGENT_SPEC_ID = 'example-evals';
|
|
35
|
+
const DEFAULT_EXECUTION_TARGET = (import.meta.env.VITE_AGENT_EVALS_TARGET || 'cloud').toLowerCase() === 'local'
|
|
36
|
+
? 'local'
|
|
37
|
+
: 'cloud';
|
|
38
|
+
const normalizeHttpUrl = (value) => {
|
|
39
|
+
if (typeof value !== 'string') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
if (!trimmed) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const url = new URL(trimmed);
|
|
48
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
url.pathname = '';
|
|
52
|
+
url.search = '';
|
|
53
|
+
url.hash = '';
|
|
54
|
+
return url.toString().replace(/\/$/, '');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const isLocalhostUrl = (value) => {
|
|
61
|
+
if (!value) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const url = new URL(value);
|
|
66
|
+
return (url.hostname === 'localhost' ||
|
|
67
|
+
url.hostname === '127.0.0.1' ||
|
|
68
|
+
url.hostname === '0.0.0.0');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
33
74
|
// ─── Inner component (rendered after auth) ─────────────────────────────────
|
|
34
|
-
const AgentEvalsInner = ({ onLogout }) => {
|
|
75
|
+
const AgentEvalsInner = ({ onLogout, executionTarget, onExecutionTargetChange }) => {
|
|
35
76
|
const { token } = useSimpleAuthStore();
|
|
77
|
+
const { configuration } = useCoreStore();
|
|
36
78
|
const agentName = useRef(uniqueAgentId(AGENT_NAME)).current;
|
|
37
|
-
const
|
|
79
|
+
const localRuntimeBaseUrl = useExampleAgentRuntimesUrl();
|
|
80
|
+
const cloudRuntimeBaseUrl = useMemo(() => {
|
|
81
|
+
const envRuntimesUrl = normalizeHttpUrl(import.meta.env.VITE_DATALAYER_RUNTIMES_URL);
|
|
82
|
+
const envAgentRuntimesUrl = normalizeHttpUrl(import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL);
|
|
83
|
+
const configuredRuntimesUrl = normalizeHttpUrl(configuration?.runtimesRunUrl);
|
|
84
|
+
if (envRuntimesUrl && !isLocalhostUrl(envRuntimesUrl)) {
|
|
85
|
+
return envRuntimesUrl;
|
|
86
|
+
}
|
|
87
|
+
if (configuredRuntimesUrl && !isLocalhostUrl(configuredRuntimesUrl)) {
|
|
88
|
+
return configuredRuntimesUrl;
|
|
89
|
+
}
|
|
90
|
+
if (envAgentRuntimesUrl && !isLocalhostUrl(envAgentRuntimesUrl)) {
|
|
91
|
+
return envAgentRuntimesUrl;
|
|
92
|
+
}
|
|
93
|
+
return 'https://r1.datalayer.run';
|
|
94
|
+
}, [configuration?.runtimesRunUrl]);
|
|
95
|
+
const { runtime, status: runtimeStatus, isReady, error: hookError, runtimeCreationBaseUrl, } = useAgentRuntimes({
|
|
38
96
|
agentSpecId: AGENT_SPEC_ID,
|
|
39
|
-
autoStart:
|
|
97
|
+
autoStart: executionTarget === 'cloud',
|
|
98
|
+
runtimeCreationTarget: executionTarget === 'local' ? 'local-agent-runtimes' : 'backend-services',
|
|
99
|
+
runtimeCreationBaseUrl: executionTarget === 'local' ? localRuntimeBaseUrl : cloudRuntimeBaseUrl,
|
|
40
100
|
agentConfig: {
|
|
41
101
|
name: agentName,
|
|
42
|
-
model: 'bedrock:us.anthropic.claude-
|
|
102
|
+
model: 'bedrock:us.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
|
43
103
|
protocol: 'vercel-ai',
|
|
44
104
|
description: 'Agent with evaluation and quality scoring',
|
|
45
105
|
},
|
|
46
106
|
});
|
|
107
|
+
const [localAgentId, setLocalAgentId] = useState(null);
|
|
108
|
+
const [localStatus, setLocalStatus] = useState('launching');
|
|
109
|
+
const [localError, setLocalError] = useState(null);
|
|
47
110
|
const [evalRuns, setEvalRuns] = useState([]);
|
|
48
111
|
const [isRunning, setIsRunning] = useState(false);
|
|
49
112
|
const [flash, setFlash] = useState(null);
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
113
|
+
const [evalId, setEvalId] = useState(null);
|
|
114
|
+
const [experimentId, setExperimentId] = useState(null);
|
|
115
|
+
const [isBootstrapping, setIsBootstrapping] = useState(true);
|
|
116
|
+
const cloudAgentBaseUrl = runtime?.agentBaseUrl || '';
|
|
117
|
+
const localAgentBaseUrl = runtimeCreationBaseUrl;
|
|
118
|
+
const agentBaseUrl = executionTarget === 'local' ? localAgentBaseUrl : cloudAgentBaseUrl;
|
|
119
|
+
const agentId = executionTarget === 'local'
|
|
120
|
+
? localAgentId || agentName
|
|
121
|
+
: runtime?.agentId || AGENT_NAME;
|
|
122
|
+
const podName = executionTarget === 'local'
|
|
123
|
+
? `local:${agentId}`
|
|
124
|
+
: runtime?.podName || '(launching…)';
|
|
125
|
+
const controlPlaneBaseUrl = import.meta.env.VITE_RUN_URL ||
|
|
126
|
+
configuration?.runUrl ||
|
|
127
|
+
(cloudAgentBaseUrl ? new URL(cloudAgentBaseUrl).origin : '');
|
|
128
|
+
const isAgentReady = executionTarget === 'local' ? localStatus === 'ready' : isReady;
|
|
129
|
+
const agentStatus = executionTarget === 'local' ? localStatus : runtimeStatus;
|
|
130
|
+
const effectiveError = executionTarget === 'local' ? localError : hookError;
|
|
53
131
|
// Authenticated fetch helper
|
|
54
132
|
const authFetch = useCallback((url, opts = {}) => fetch(url, {
|
|
55
133
|
...opts,
|
|
@@ -59,67 +137,271 @@ const AgentEvalsInner = ({ onLogout }) => {
|
|
|
59
137
|
...(opts.headers ?? {}),
|
|
60
138
|
},
|
|
61
139
|
}), [token]);
|
|
140
|
+
const evalApiFetch = useCallback(async (path, opts = {}) => {
|
|
141
|
+
const response = await authFetch(`${controlPlaneBaseUrl}/api/ai-agents/v1${path}`, opts);
|
|
142
|
+
const payload = await response.json().catch(() => ({}));
|
|
143
|
+
if (!response.ok || payload?.success === false) {
|
|
144
|
+
throw new Error(payload?.detail ||
|
|
145
|
+
payload?.message ||
|
|
146
|
+
`Eval API request failed (${response.status})`);
|
|
147
|
+
}
|
|
148
|
+
return payload;
|
|
149
|
+
}, [authFetch, controlPlaneBaseUrl]);
|
|
150
|
+
const mapRuns = useCallback((rows) => {
|
|
151
|
+
return rows.map((run) => {
|
|
152
|
+
const passRateRaw = run?.metrics?.pass_rate ??
|
|
153
|
+
run?.summary?.pass_rate ??
|
|
154
|
+
run?.summary?.score ??
|
|
155
|
+
0;
|
|
156
|
+
const score = Math.max(0, Math.min(1, Number(passRateRaw) || 0));
|
|
157
|
+
const passed = Number(run?.summary?.passed ?? Math.round(score * 100));
|
|
158
|
+
const failed = Number(run?.summary?.failed ?? Math.max(0, 100 - passed));
|
|
159
|
+
return {
|
|
160
|
+
id: String(run?.id || Math.random()),
|
|
161
|
+
timestamp: String(run?.created_at || run?.updated_at || new Date().toISOString()),
|
|
162
|
+
suiteName: String(run?.summary?.suite_name || run?.summary?.name || 'default-suite'),
|
|
163
|
+
passed,
|
|
164
|
+
failed,
|
|
165
|
+
score,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}, []);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (executionTarget !== 'local' || !agentBaseUrl) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let isCancelled = false;
|
|
174
|
+
const createLocalAgent = async () => {
|
|
175
|
+
setLocalStatus('launching');
|
|
176
|
+
setLocalError(null);
|
|
177
|
+
try {
|
|
178
|
+
const response = await authFetch(`${agentBaseUrl}/api/v1/agents`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
name: agentName,
|
|
182
|
+
description: 'Agent with evaluation and quality scoring',
|
|
183
|
+
agent_library: 'pydantic-ai',
|
|
184
|
+
transport: 'vercel-ai',
|
|
185
|
+
agent_spec_id: AGENT_SPEC_ID,
|
|
186
|
+
enable_skills: true,
|
|
187
|
+
tools: [],
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
let resolvedAgentId = agentName;
|
|
191
|
+
if (response.ok) {
|
|
192
|
+
const payload = await response.json().catch(() => ({}));
|
|
193
|
+
resolvedAgentId = payload?.id || agentName;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const contentType = response.headers.get('content-type') || '';
|
|
197
|
+
let detail = '';
|
|
198
|
+
if (contentType.includes('application/json')) {
|
|
199
|
+
const payload = await response.json().catch(() => null);
|
|
200
|
+
detail =
|
|
201
|
+
(typeof payload?.detail === 'string' && payload.detail) ||
|
|
202
|
+
(typeof payload?.message === 'string' && payload.message) ||
|
|
203
|
+
'';
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
detail = await response.text();
|
|
207
|
+
}
|
|
208
|
+
if (response.status !== 409 &&
|
|
209
|
+
!/already exists/i.test(detail || '')) {
|
|
210
|
+
throw new Error(detail || `Failed to create local agent: ${response.status}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!isCancelled) {
|
|
214
|
+
setLocalAgentId(resolvedAgentId);
|
|
215
|
+
setLocalStatus('ready');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (!isCancelled) {
|
|
220
|
+
setLocalError(error instanceof Error ? error.message : 'Agent failed to start');
|
|
221
|
+
setLocalStatus('error');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
void createLocalAgent();
|
|
226
|
+
return () => {
|
|
227
|
+
isCancelled = true;
|
|
228
|
+
};
|
|
229
|
+
}, [executionTarget, agentBaseUrl, agentName, authFetch]);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (!isAgentReady || !controlPlaneBaseUrl)
|
|
232
|
+
return;
|
|
233
|
+
const bootstrap = async () => {
|
|
234
|
+
setIsBootstrapping(true);
|
|
235
|
+
try {
|
|
236
|
+
const evalName = `agent-evals-${agentId}`;
|
|
237
|
+
const evalsRes = await evalApiFetch(`/evals/evals?source=hosted&q=${encodeURIComponent(evalName)}&limit=50`);
|
|
238
|
+
const evals = Array.isArray(evalsRes?.evals)
|
|
239
|
+
? evalsRes.evals
|
|
240
|
+
: [];
|
|
241
|
+
let evalRecord = evals.find((d) => d?.name === evalName);
|
|
242
|
+
if (!evalRecord) {
|
|
243
|
+
const createdEvalRes = await evalApiFetch('/evals/evals', {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
body: JSON.stringify({
|
|
246
|
+
name: evalName,
|
|
247
|
+
description: `Hosted eval for ${agentId}`,
|
|
248
|
+
source: 'hosted',
|
|
249
|
+
kind: 'agent-quality',
|
|
250
|
+
schema: {},
|
|
251
|
+
tags: ['agent-runtimes', 'example'],
|
|
252
|
+
metadata: { agent_id: agentId },
|
|
253
|
+
cases: [],
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
evalRecord = createdEvalRes?.eval;
|
|
257
|
+
}
|
|
258
|
+
if (!evalRecord?.id) {
|
|
259
|
+
throw new Error('Failed to initialize eval.');
|
|
260
|
+
}
|
|
261
|
+
setEvalId(evalRecord.id);
|
|
262
|
+
const experimentsRes = await evalApiFetch(`/evals/experiments?eval_id=${encodeURIComponent(evalRecord.id)}&limit=50`);
|
|
263
|
+
const experiments = Array.isArray(experimentsRes?.experiments)
|
|
264
|
+
? experimentsRes.experiments
|
|
265
|
+
: [];
|
|
266
|
+
let experiment = experiments.find((e) => e?.name === 'default-suite');
|
|
267
|
+
if (!experiment) {
|
|
268
|
+
const createdExperimentRes = await evalApiFetch('/evals/experiments', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
eval_id: evalRecord.id,
|
|
272
|
+
name: 'default-suite',
|
|
273
|
+
description: 'Default evaluation suite for AgentEvalsExample.',
|
|
274
|
+
status: 'ready',
|
|
275
|
+
config: {
|
|
276
|
+
mode: 'offline',
|
|
277
|
+
target_agent_id: agentId,
|
|
278
|
+
target_pod_name: podName,
|
|
279
|
+
},
|
|
280
|
+
summary: {},
|
|
281
|
+
tags: ['example'],
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
284
|
+
experiment = createdExperimentRes?.experiment;
|
|
285
|
+
}
|
|
286
|
+
if (!experiment?.id) {
|
|
287
|
+
throw new Error('Failed to initialize eval experiment.');
|
|
288
|
+
}
|
|
289
|
+
setExperimentId(experiment.id);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
const message = error instanceof Error ? error.message : 'Eval bootstrap failed.';
|
|
293
|
+
setFlash(message);
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
setIsBootstrapping(false);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
void bootstrap();
|
|
300
|
+
}, [isAgentReady, controlPlaneBaseUrl, agentId, podName, evalApiFetch]);
|
|
62
301
|
// ── Poll eval results ─────────────────────────────────────────────────
|
|
63
302
|
useEffect(() => {
|
|
64
|
-
if (!
|
|
303
|
+
if (!isAgentReady || !controlPlaneBaseUrl || !experimentId)
|
|
65
304
|
return;
|
|
66
305
|
const poll = async () => {
|
|
67
306
|
try {
|
|
68
|
-
const res = await
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
setEvalRuns(Array.isArray(d) ? d : (d.runs ?? []));
|
|
72
|
-
}
|
|
307
|
+
const res = await evalApiFetch(`/evals/experiments/${encodeURIComponent(experimentId)}/runs?limit=50`);
|
|
308
|
+
const rows = Array.isArray(res?.runs) ? res.runs : [];
|
|
309
|
+
setEvalRuns(mapRuns(rows));
|
|
73
310
|
}
|
|
74
311
|
catch {
|
|
75
312
|
/* ok */
|
|
76
313
|
}
|
|
77
314
|
};
|
|
78
|
-
poll();
|
|
315
|
+
void poll();
|
|
79
316
|
const interval = setInterval(poll, 15_000);
|
|
80
317
|
return () => clearInterval(interval);
|
|
81
|
-
}, [
|
|
318
|
+
}, [isAgentReady, controlPlaneBaseUrl, experimentId, evalApiFetch, mapRuns]);
|
|
82
319
|
// ── Run eval suite ────────────────────────────────────────────────────
|
|
83
320
|
const handleRunEval = useCallback(async () => {
|
|
84
|
-
if (!
|
|
321
|
+
if (!controlPlaneBaseUrl || !experimentId)
|
|
85
322
|
return;
|
|
86
323
|
setIsRunning(true);
|
|
87
324
|
setFlash(null);
|
|
88
325
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
326
|
+
const syntheticScore = Number((0.75 + Math.random() * 0.2).toFixed(3));
|
|
327
|
+
const passed = Math.round(syntheticScore * 100);
|
|
328
|
+
const failed = Math.max(0, 100 - passed);
|
|
329
|
+
await evalApiFetch(`/evals/experiments/${encodeURIComponent(experimentId)}/runs`, {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
body: JSON.stringify({
|
|
332
|
+
status: 'completed',
|
|
333
|
+
metrics: {
|
|
334
|
+
pass_rate: syntheticScore,
|
|
335
|
+
avg_score: syntheticScore,
|
|
336
|
+
},
|
|
337
|
+
summary: {
|
|
338
|
+
suite_name: 'default-suite',
|
|
339
|
+
passed,
|
|
340
|
+
failed,
|
|
341
|
+
runtime_id: podName,
|
|
342
|
+
},
|
|
343
|
+
report: {
|
|
344
|
+
source: 'AgentEvalsExample',
|
|
345
|
+
eval_id: evalId,
|
|
346
|
+
experiment_id: experimentId,
|
|
347
|
+
agent_id: agentId,
|
|
348
|
+
},
|
|
349
|
+
}),
|
|
350
|
+
});
|
|
351
|
+
setFlash('Evaluation run persisted');
|
|
352
|
+
const updatedRuns = await evalApiFetch(`/evals/experiments/${encodeURIComponent(experimentId)}/runs?limit=50`);
|
|
353
|
+
const rows = Array.isArray(updatedRuns?.runs)
|
|
354
|
+
? updatedRuns.runs
|
|
355
|
+
: [];
|
|
356
|
+
setEvalRuns(mapRuns(rows));
|
|
96
357
|
}
|
|
97
358
|
catch {
|
|
98
|
-
setFlash('
|
|
359
|
+
setFlash('Failed to persist evaluation run');
|
|
99
360
|
}
|
|
100
361
|
finally {
|
|
101
362
|
setIsRunning(false);
|
|
102
363
|
}
|
|
103
|
-
}, [
|
|
364
|
+
}, [
|
|
365
|
+
controlPlaneBaseUrl,
|
|
366
|
+
experimentId,
|
|
367
|
+
evalApiFetch,
|
|
368
|
+
mapRuns,
|
|
369
|
+
podName,
|
|
370
|
+
evalId,
|
|
371
|
+
agentId,
|
|
372
|
+
]);
|
|
104
373
|
// ── Loading / Error ───────────────────────────────────────────────────
|
|
105
|
-
if (!
|
|
374
|
+
if (!isAgentReady && agentStatus !== 'error') {
|
|
106
375
|
return (_jsxs(Box, { sx: {
|
|
107
376
|
display: 'flex',
|
|
108
377
|
flexDirection: 'column',
|
|
109
378
|
alignItems: 'center',
|
|
110
379
|
justifyContent: 'center',
|
|
111
|
-
height: '
|
|
380
|
+
height: '100%',
|
|
112
381
|
gap: 3,
|
|
113
|
-
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children:
|
|
114
|
-
?
|
|
115
|
-
|
|
382
|
+
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: agentStatus === 'launching'
|
|
383
|
+
? executionTarget === 'local'
|
|
384
|
+
? 'Launching local eval example agent…'
|
|
385
|
+
: 'Launching runtime for eval agent…'
|
|
386
|
+
: 'Creating eval example agent…' })] }));
|
|
387
|
+
}
|
|
388
|
+
if (agentStatus === 'error' || effectiveError) {
|
|
389
|
+
return _jsx(ErrorView, { error: effectiveError, onLogout: onLogout });
|
|
116
390
|
}
|
|
117
|
-
if (
|
|
118
|
-
return
|
|
391
|
+
if (isBootstrapping) {
|
|
392
|
+
return (_jsxs(Box, { sx: {
|
|
393
|
+
display: 'flex',
|
|
394
|
+
flexDirection: 'column',
|
|
395
|
+
alignItems: 'center',
|
|
396
|
+
justifyContent: 'center',
|
|
397
|
+
height: '100%',
|
|
398
|
+
gap: 3,
|
|
399
|
+
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Preparing hosted eval and experiment..." })] }));
|
|
119
400
|
}
|
|
120
401
|
const latestScore = evalRuns.length > 0 ? evalRuns[0].score : null;
|
|
121
402
|
return (_jsxs(Box, { sx: {
|
|
122
|
-
height: '
|
|
403
|
+
height: '100%',
|
|
404
|
+
minHeight: 0,
|
|
123
405
|
display: 'flex',
|
|
124
406
|
flexDirection: 'column',
|
|
125
407
|
}, children: [_jsxs(Box, { sx: {
|
|
@@ -131,7 +413,14 @@ const AgentEvalsInner = ({ onLogout }) => {
|
|
|
131
413
|
borderBottom: '1px solid',
|
|
132
414
|
borderColor: 'border.default',
|
|
133
415
|
flexShrink: 0,
|
|
134
|
-
}, children: [_jsx(BeakerIcon, { size: 16 }), _jsxs(
|
|
416
|
+
}, children: [_jsx(BeakerIcon, { size: 16 }), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2 }, children: ["Evaluation \u2014 ", podName] }), _jsxs(Text, { sx: {
|
|
417
|
+
fontSize: 0,
|
|
418
|
+
color: 'fg.muted',
|
|
419
|
+
display: 'block',
|
|
420
|
+
overflow: 'hidden',
|
|
421
|
+
textOverflow: 'ellipsis',
|
|
422
|
+
whiteSpace: 'nowrap',
|
|
423
|
+
}, children: ["Runtime API: ", runtimeCreationBaseUrl, "/api/runtimes/v1/runtimes"] })] }), _jsxs(FormControl, { sx: { minWidth: 160 }, children: [_jsx(FormControl.Label, { sx: { fontSize: 0, mb: 1 }, children: "Target" }), _jsxs(Select, { size: "small", value: executionTarget, onChange: e => onExecutionTargetChange(e.target.value), disabled: isRunning, children: [_jsx(Select.Option, { value: "cloud", children: "Cloud" }), _jsx(Select.Option, { value: "local", children: "Local" })] })] })] }), _jsxs(Box, { sx: { flex: 1, minHeight: 0, display: 'flex' }, children: [_jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: _jsx(Chat, { protocol: "vercel-ai", baseUrl: agentBaseUrl, agentId: agentId, title: "Eval Agent", brandIcon: _jsx(BeakerIcon, { size: 16 }), placeholder: "Chat with the agent, then run evaluations\u2026", description: latestScore != null
|
|
135
424
|
? `Last score: ${(latestScore * 100).toFixed(0)}%`
|
|
136
425
|
: 'No evaluations run yet', showHeader: true, autoFocus: true, height: "100%", runtimeId: podName, historyEndpoint: `${agentBaseUrl}/api/v1/history`, suggestions: [
|
|
137
426
|
{
|
|
@@ -153,7 +442,7 @@ const AgentEvalsInner = ({ onLogout }) => {
|
|
|
153
442
|
p: 3,
|
|
154
443
|
borderBottom: '1px solid',
|
|
155
444
|
borderColor: 'border.default',
|
|
156
|
-
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(BeakerIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Run Evaluation" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: "Execute the default evaluation suite
|
|
445
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(BeakerIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Run Evaluation" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: "Execute the default evaluation suite and persist results to /api/ai-agents/v1/evals." }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: PlayIcon, onClick: handleRunEval, disabled: isRunning, sx: { width: '100%' }, children: isRunning ? 'Running…' : 'Run Eval Suite' }), flash && (_jsx(Flash, { variant: flash.toLowerCase().includes('failed') ? 'danger' : 'success', sx: { mt: 2, fontSize: 0 }, children: flash }))] }), _jsxs(Box, { sx: { p: 3, flex: 1, overflow: 'auto' }, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mb: 2 }, children: "Evaluation History" }), evalRuns.length === 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "No evaluation runs recorded yet." })) : (evalRuns.slice(0, 20).map(run => (_jsxs(Box, { sx: {
|
|
157
446
|
p: 2,
|
|
158
447
|
mb: 2,
|
|
159
448
|
border: '1px solid',
|
|
@@ -185,6 +474,7 @@ const syncTokenToIamStore = (token) => {
|
|
|
185
474
|
const AgentEvalsExample = () => {
|
|
186
475
|
const { token, clearAuth } = useSimpleAuthStore();
|
|
187
476
|
const hasSynced = useRef(false);
|
|
477
|
+
const [executionTarget, setExecutionTarget] = useState(DEFAULT_EXECUTION_TARGET);
|
|
188
478
|
useEffect(() => {
|
|
189
479
|
if (token && !hasSynced.current) {
|
|
190
480
|
hasSynced.current = true;
|
|
@@ -201,6 +491,6 @@ const AgentEvalsExample = () => {
|
|
|
201
491
|
if (!token) {
|
|
202
492
|
return (_jsx(ThemedProvider, { children: _jsx(AuthRequiredView, {}) }));
|
|
203
493
|
}
|
|
204
|
-
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThemedProvider, { children: _jsx(AgentEvalsInner, { onLogout: handleLogout }) }) }));
|
|
494
|
+
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThemedProvider, { children: _jsx(AgentEvalsInner, { onLogout: handleLogout, executionTarget: executionTarget, onExecutionTargetChange: setExecutionTarget }, executionTarget) }) }));
|
|
205
495
|
};
|
|
206
496
|
export default AgentEvalsExample;
|
|
@@ -34,8 +34,8 @@ const queryClient = new QueryClient();
|
|
|
34
34
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
35
35
|
import { Chat } from '../chat';
|
|
36
36
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
37
|
-
const AGENT_NAME = 'guardrails-
|
|
38
|
-
const AGENT_SPEC_ID = '
|
|
37
|
+
const AGENT_NAME = 'guardrails-example-agent';
|
|
38
|
+
const AGENT_SPEC_ID = 'example-guardrails';
|
|
39
39
|
const DEFAULT_LOCAL_BASE_URL = import.meta.env.VITE_BASE_URL || 'http://localhost:8765';
|
|
40
40
|
const OTEL_BASE_URL_ENV = import.meta.env.VITE_OTEL_BASE_URL;
|
|
41
41
|
const DATALAYER_RUN_URL_ENV = import.meta.env.DATALAYER_RUN_URL;
|
|
@@ -422,7 +422,7 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
422
422
|
justifyContent: 'center',
|
|
423
423
|
height: '100vh',
|
|
424
424
|
gap: 3,
|
|
425
|
-
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Launching guardrails
|
|
425
|
+
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Launching guardrails example agent..." })] }));
|
|
426
426
|
}
|
|
427
427
|
if (runtimeStatus === 'error' || hookError) {
|
|
428
428
|
return _jsx(ErrorView, { error: hookError, onLogout: onLogout });
|
|
@@ -482,9 +482,20 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
482
482
|
? ` (over by $${overBudgetAmountUsd.toFixed(4)}).`
|
|
483
483
|
: '.', ' ', "Start a new run or increase the run budget before continuing."] }) })), approvals.map(req => (_jsx(Flash, { variant: "warning", sx: { mx: 3, mt: 2 }, children: _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsxs(Text, { sx: { flex: 1, fontSize: 1 }, children: [_jsx("strong", { children: req.tool_name }), " requests approval", req.tool_args
|
|
484
484
|
? ` — ${JSON.stringify(req.tool_args).slice(0, 120)}`
|
|
485
|
-
: ''] }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: CheckIcon, onClick: () => handleApprove(req.id), disabled: approvalLoading === req.id, children: "Approve" }), _jsx(Button, { size: "small", variant: "danger", leadingVisual: XIcon, onClick: () => handleReject(req.id), disabled: approvalLoading === req.id, children: "Reject" })] }) }, req.id))), _jsx(Box, { sx: { flex: 1, minHeight: 0 }, children: _jsx(Chat, { protocol: "vercel-ai", baseUrl: agentBaseUrl, agentId: agentId, authToken: chatAuthToken, title: "Guardrails Agent", placeholder: "Ask something that triggers tools\u2026", description: "Cost guardrail with OTEL-backed gauge and
|
|
485
|
+
: ''] }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: CheckIcon, onClick: () => handleApprove(req.id), disabled: approvalLoading === req.id, children: "Approve" }), _jsx(Button, { size: "small", variant: "danger", leadingVisual: XIcon, onClick: () => handleReject(req.id), disabled: approvalLoading === req.id, children: "Reject" })] }) }, req.id))), _jsx(Box, { sx: { flex: 1, minHeight: 0 }, children: _jsx(Chat, { protocol: "vercel-ai", baseUrl: agentBaseUrl, agentId: agentId, authToken: chatAuthToken, title: "Guardrails Agent", brandIcon: _jsx(ShieldCheckIcon, { size: 16 }), placeholder: "Ask something that triggers tools\u2026", description: "Cost guardrail with OTEL-backed gauge and hook-aware approvals (before_tool_execute, after_tool_execute, on_tool_execute_error, deferred_tool_calls)", showHeader: false, showTokenUsage: true, errorBanner: overBudgetBanner, disableInputPrompt: isOverRunBudget, autoFocus: true, height: "100%", runtimeId: agentId, historyEndpoint: `${agentBaseUrl}/api/v1/history`, suggestions: [
|
|
486
486
|
{ title: 'Update CRM', message: 'Update the CRM records for Q3' },
|
|
487
|
-
{
|
|
487
|
+
{
|
|
488
|
+
title: 'Trigger before_tool_execute',
|
|
489
|
+
message: "Call runtime_sensitive_echo with text 'hello' and reason 'audit', then explain the before_tool_execute authorization decision.",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
title: 'Trigger deny policy',
|
|
493
|
+
message: "Call runtime_sensitive_echo with text 'danger' and reason 'delete CRM rows', then explain why policy denied it.",
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
title: 'Explain deferred flow',
|
|
497
|
+
message: 'Explain how deferred_tool_calls and manual approvals interact in this guardrails run.',
|
|
498
|
+
},
|
|
488
499
|
], submitOnSuggestionClick: true }) })] }));
|
|
489
500
|
};
|
|
490
501
|
// ─── Sync token to core IAM store ──────────────────────────────────────────
|