@datalayer/agent-runtimes 1.0.3 → 1.0.5
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 +35 -119
- package/lib/App.js +1 -1
- package/lib/agents/AgentDetails.d.ts +22 -1
- package/lib/agents/AgentDetails.js +34 -47
- package/lib/api/index.d.ts +0 -1
- package/lib/api/index.js +4 -2
- package/lib/chat/Chat.d.ts +5 -104
- package/lib/chat/Chat.js +4 -4
- package/lib/chat/ChatFloating.d.ts +7 -140
- package/lib/chat/ChatFloating.js +2 -2
- package/lib/chat/ChatPopupStandalone.d.ts +8 -47
- package/lib/chat/ChatPopupStandalone.js +3 -3
- package/lib/chat/ChatSidebar.d.ts +4 -69
- package/lib/chat/ChatSidebar.js +2 -2
- package/lib/chat/ChatStandalone.d.ts +4 -54
- package/lib/chat/ChatStandalone.js +3 -3
- package/lib/chat/base/ChatBase.js +1118 -141
- package/lib/chat/header/ChatHeaderBase.d.ts +11 -6
- package/lib/chat/header/ChatHeaderBase.js +18 -16
- package/lib/chat/indicators/McpStatusIndicator.d.ts +7 -4
- package/lib/chat/indicators/McpStatusIndicator.js +7 -32
- package/lib/chat/indicators/SandboxStatusIndicator.d.ts +4 -1
- package/lib/chat/indicators/SandboxStatusIndicator.js +9 -9
- package/lib/chat/indicators/SkillsStatusIndicator.d.ts +7 -0
- package/lib/chat/indicators/SkillsStatusIndicator.js +88 -0
- package/lib/chat/indicators/index.d.ts +1 -0
- package/lib/chat/indicators/index.js +1 -0
- package/lib/chat/messages/ChatMessageList.d.ts +1 -1
- package/lib/chat/messages/ChatMessageList.js +110 -102
- package/lib/chat/prompt/InputFooter.d.ts +19 -6
- package/lib/chat/prompt/InputFooter.js +71 -18
- package/lib/chat/prompt/InputPrompt.d.ts +3 -1
- package/lib/chat/prompt/InputPrompt.js +4 -4
- package/lib/chat/prompt/InputPromptFooter.js +1 -1
- package/lib/chat/prompt/InputPromptLexical.d.ts +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +12 -5
- package/lib/chat/prompt/InputPromptText.d.ts +3 -1
- package/lib/chat/prompt/InputPromptText.js +2 -2
- package/lib/chat/tools/ToolApprovalBanner.js +1 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +3 -1
- package/lib/chat/tools/ToolCallDisplay.js +2 -2
- package/lib/chat/usage/TokenUsageBar.js +20 -2
- package/lib/client/AgentRuntimesClientContext.d.ts +53 -0
- package/lib/client/AgentRuntimesClientContext.js +55 -0
- package/lib/client/AgentsMixin.d.ts +48 -19
- package/lib/client/AgentsMixin.js +115 -30
- package/lib/client/IAgentRuntimesClient.d.ts +215 -0
- package/lib/client/IAgentRuntimesClient.js +5 -0
- package/lib/client/SdkAgentRuntimesClient.d.ts +151 -0
- package/lib/client/SdkAgentRuntimesClient.js +134 -0
- package/lib/client/index.d.ts +4 -1
- package/lib/client/index.js +3 -1
- package/lib/components/NotificationEventCard.js +55 -26
- package/lib/components/OutputCard.js +21 -7
- package/lib/components/ToolApprovalCard.js +20 -2
- package/lib/config/AgentConfiguration.js +3 -3
- package/lib/context/ContextDistribution.d.ts +3 -1
- package/lib/context/ContextDistribution.js +8 -27
- package/lib/context/ContextInspector.d.ts +3 -1
- package/lib/context/ContextInspector.js +19 -67
- package/lib/context/ContextPanel.d.ts +3 -1
- package/lib/context/ContextPanel.js +104 -64
- package/lib/context/ContextUsage.d.ts +3 -1
- package/lib/context/ContextUsage.js +3 -3
- package/lib/context/CostTracker.d.ts +9 -3
- package/lib/context/CostTracker.js +26 -47
- package/lib/context/CostUsageChart.d.ts +12 -0
- package/lib/context/CostUsageChart.js +378 -0
- package/lib/context/GraphFlowChart.d.ts +16 -0
- package/lib/context/GraphFlowChart.js +182 -0
- package/lib/context/TokenUsageChart.d.ts +8 -1
- package/lib/context/TokenUsageChart.js +349 -211
- package/lib/context/TurnGraphChart.d.ts +39 -0
- package/lib/context/TurnGraphChart.js +538 -0
- package/lib/context/otelWsPool.d.ts +20 -0
- package/lib/context/otelWsPool.js +69 -0
- package/lib/examples/A2UiComponentGalleryExample.d.ts +0 -17
- package/lib/examples/A2UiComponentGalleryExample.js +315 -522
- package/lib/examples/A2UiContactCardExample.d.ts +0 -18
- package/lib/examples/A2UiContactCardExample.js +154 -411
- package/lib/examples/A2UiRestaurantExample.d.ts +0 -30
- package/lib/examples/A2UiRestaurantExample.js +114 -212
- package/lib/examples/A2UiViewerExample.d.ts +0 -18
- package/lib/examples/A2UiViewerExample.js +283 -532
- package/lib/examples/AgUiBackendToolRenderingExample.js +1 -1
- package/lib/examples/AgUiHaikuGenUiExample.d.ts +1 -1
- package/lib/examples/AgUiHaikuGenUiExample.js +1 -1
- package/lib/examples/AgentCheckpointsExample.js +14 -34
- package/lib/examples/AgentCodemodeExample.d.ts +4 -6
- package/lib/examples/AgentCodemodeExample.js +591 -175
- package/lib/examples/AgentEvalsExample.js +13 -23
- package/lib/examples/AgentGuardrailsExample.js +371 -71
- package/lib/examples/AgentHooksExample.d.ts +3 -0
- package/lib/examples/AgentHooksExample.js +104 -0
- package/lib/examples/AgentMCPExample.d.ts +3 -0
- package/lib/examples/AgentMCPExample.js +480 -0
- package/lib/examples/AgentMemoryExample.js +14 -24
- package/lib/examples/AgentMonitoringExample.js +261 -206
- package/lib/examples/AgentNotificationsExample.js +50 -24
- package/lib/examples/AgentOtelExample.js +2 -3
- package/lib/examples/AgentOutputsExample.d.ts +11 -6
- package/lib/examples/AgentOutputsExample.js +383 -88
- package/lib/examples/AgentParametersExample.d.ts +3 -0
- package/lib/examples/AgentParametersExample.js +246 -0
- package/lib/examples/AgentSandboxExample.d.ts +2 -2
- package/lib/examples/AgentSandboxExample.js +69 -47
- package/lib/examples/AgentSkillsExample.js +92 -106
- package/lib/examples/{AgentspecExample.js → AgentSpecsExample.js} +10 -21
- package/lib/examples/AgentSubagentsExample.d.ts +14 -0
- package/lib/examples/AgentSubagentsExample.js +228 -0
- package/lib/examples/AgentToolApprovalsExample.js +30 -493
- package/lib/examples/AgentTriggersExample.js +1067 -246
- package/lib/examples/ChatCustomExample.js +11 -24
- package/lib/examples/ChatExample.js +9 -34
- package/lib/examples/CopilotKitLexicalExample.js +2 -1
- package/lib/examples/CopilotKitNotebookExample.js +2 -1
- package/lib/examples/HomeExample.d.ts +15 -0
- package/lib/examples/HomeExample.js +77 -0
- package/lib/examples/Lexical2Example.js +4 -2
- package/lib/examples/{LexicalExample.d.ts → LexicalAgentExample.d.ts} +4 -4
- package/lib/examples/{LexicalExample.js → LexicalAgentExample.js} +65 -16
- package/lib/examples/{LexicalSidebarExample.d.ts → LexicalAgentSidebarExample.d.ts} +5 -5
- package/lib/examples/LexicalAgentSidebarExample.js +261 -0
- package/lib/examples/NotebookAgentExample.d.ts +9 -0
- package/lib/examples/NotebookAgentExample.js +192 -0
- package/lib/examples/{NotebookSidebarExample.d.ts → NotebookAgentSidebarExample.d.ts} +2 -2
- package/lib/examples/NotebookAgentSidebarExample.js +221 -0
- package/lib/examples/{DatalayerNotebookExample.d.ts → NotebookCollaborationExample.d.ts} +4 -4
- package/lib/examples/{DatalayerNotebookExample.js → NotebookCollaborationExample.js} +3 -3
- package/lib/examples/NotebookExample.d.ts +4 -7
- package/lib/examples/NotebookExample.js +14 -146
- package/lib/examples/components/AuthRequiredView.d.ts +6 -0
- package/lib/examples/components/AuthRequiredView.js +33 -0
- package/lib/examples/components/ErrorView.d.ts +14 -0
- package/lib/examples/components/ErrorView.js +20 -0
- package/lib/examples/components/ExampleWrapper.d.ts +7 -0
- package/lib/examples/components/ExampleWrapper.js +25 -6
- package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.js +1 -1
- package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.js +1 -1
- package/lib/examples/{ag-ui → components}/haiku/index.d.ts +1 -1
- package/lib/examples/{ag-ui → components}/haiku/index.js +1 -1
- package/lib/examples/components/index.d.ts +5 -0
- package/lib/examples/components/index.js +5 -0
- package/lib/examples/{ag-ui → components}/weather/index.d.ts +1 -1
- package/lib/examples/{ag-ui → components}/weather/index.js +1 -1
- package/lib/examples/example-selector.d.ts +17 -4
- package/lib/examples/example-selector.js +107 -41
- package/lib/examples/index.d.ts +9 -6
- package/lib/examples/index.js +9 -6
- package/lib/examples/main.d.ts +1 -0
- package/lib/examples/main.js +218 -27
- package/lib/examples/utils/a2ui.d.ts +18 -0
- package/lib/examples/utils/a2ui.js +69 -0
- package/lib/examples/utils/a2uiMarkdownProvider.d.ts +7 -0
- package/lib/examples/utils/a2uiMarkdownProvider.js +9 -0
- package/lib/examples/utils/agentId.d.ts +18 -0
- package/lib/examples/utils/agentId.js +54 -0
- package/lib/examples/utils/agents/earthquake-detector.json +11 -11
- package/lib/examples/utils/agents/sales-forecaster.json +11 -11
- package/lib/examples/utils/agents/social-post-generator.json +11 -11
- package/lib/examples/utils/agents/stock-market.json +11 -11
- package/lib/examples/utils/examplesStore.js +82 -27
- package/lib/hooks/index.d.ts +8 -8
- package/lib/hooks/index.js +7 -7
- package/lib/hooks/useA2A.d.ts +2 -3
- package/lib/hooks/useAIAgentsWebSocket.d.ts +43 -4
- package/lib/hooks/useAIAgentsWebSocket.js +118 -12
- package/lib/hooks/useAcp.d.ts +1 -2
- package/lib/hooks/useAgUi.d.ts +1 -1
- package/lib/hooks/{useAgents.d.ts → useAgentRuntimes.d.ts} +39 -2
- package/lib/hooks/{useAgents.js → useAgentRuntimes.js} +125 -15
- package/lib/hooks/useAgentsCatalog.js +1 -1
- package/lib/hooks/useAgentsService.d.ts +2 -2
- package/lib/hooks/useAgentsService.js +7 -7
- package/lib/hooks/useCheckpoints.js +1 -1
- package/lib/hooks/useConfig.d.ts +4 -1
- package/lib/hooks/useConfig.js +10 -3
- package/lib/hooks/useContextSnapshot.d.ts +9 -4
- package/lib/hooks/useContextSnapshot.js +9 -37
- package/lib/hooks/useMonitoring.js +3 -0
- package/lib/hooks/useSandbox.d.ts +20 -8
- package/lib/hooks/useSandbox.js +105 -40
- package/lib/hooks/useSkills.d.ts +23 -5
- package/lib/hooks/useSkills.js +94 -39
- package/lib/hooks/useToolApprovals.d.ts +60 -36
- package/lib/hooks/useToolApprovals.js +318 -69
- package/lib/hooks/useVercelAI.d.ts +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -0
- package/lib/inference/index.d.ts +0 -1
- package/lib/middleware/index.d.ts +0 -1
- package/lib/protocols/AGUIAdapter.js +6 -0
- package/lib/protocols/VercelAIAdapter.d.ts +9 -0
- package/lib/protocols/VercelAIAdapter.js +144 -26
- package/lib/shims/json5.d.ts +4 -0
- package/lib/shims/json5.js +8 -0
- package/lib/specs/agents/agents.d.ts +10 -0
- package/lib/specs/agents/agents.js +752 -24
- package/lib/specs/envvars.d.ts +1 -0
- package/lib/specs/envvars.js +11 -0
- package/lib/specs/events.d.ts +1 -0
- package/lib/specs/events.js +1 -0
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/personas.d.ts +41 -0
- package/lib/specs/personas.js +168 -0
- package/lib/specs/skills.d.ts +2 -1
- package/lib/specs/skills.js +23 -5
- package/lib/specs/tools.js +3 -0
- package/lib/stores/agentRuntimeStore.d.ts +204 -0
- package/lib/stores/agentRuntimeStore.js +636 -0
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/tools/adapters/copilotkit/lexicalHooks.d.ts +1 -2
- package/lib/tools/adapters/copilotkit/lexicalHooks.js +1 -3
- package/lib/tools/adapters/copilotkit/notebookHooks.d.ts +1 -2
- package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -3
- package/lib/tools/index.d.ts +0 -2
- package/lib/tools/index.js +0 -1
- package/lib/types/agentspecs.d.ts +50 -1
- package/lib/types/chat.d.ts +309 -8
- package/lib/types/context.d.ts +27 -0
- package/lib/types/cost.d.ts +2 -2
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +2 -0
- package/lib/types/mcp.d.ts +8 -0
- package/lib/types/models.d.ts +2 -2
- package/lib/types/personas.d.ts +25 -0
- package/lib/types/personas.js +5 -0
- package/lib/types/skills.d.ts +43 -1
- package/lib/types/stream.d.ts +110 -0
- package/lib/types/stream.js +36 -0
- package/lib/types/tools.d.ts +2 -0
- package/lib/utils/utils.d.ts +9 -5
- package/lib/utils/utils.js +9 -5
- package/package.json +13 -9
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_events.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +106 -7
- package/scripts/codegen/generate_events.py +47 -17
- package/scripts/codegen/generate_personas.py +319 -0
- package/scripts/codegen/generate_skills.py +9 -9
- package/scripts/codegen/generate_tools.py +20 -0
- package/scripts/sync-jupyter.sh +26 -7
- package/style/primer-primitives.css +1 -6
- package/lib/api/tool-approvals.d.ts +0 -62
- package/lib/api/tool-approvals.js +0 -145
- package/lib/examples/LexicalSidebarExample.js +0 -163
- package/lib/examples/NotebookSidebarExample.js +0 -119
- package/lib/examples/NotebookSimpleExample.d.ts +0 -6
- package/lib/examples/NotebookSimpleExample.js +0 -22
- package/lib/examples/ag-ui/index.d.ts +0 -10
- package/lib/examples/ag-ui/index.js +0 -16
- package/lib/hooks/useAgentsRegistry.d.ts +0 -10
- package/lib/hooks/useAgentsRegistry.js +0 -20
- package/lib/stores/agentsStore.d.ts +0 -123
- package/lib/stores/agentsStore.js +0 -270
- package/scripts/codegen/__pycache__/generate_envvars.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_guardrails.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_mcp_servers.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_memory.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_models.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_notifications.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_outputs.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_skills.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_teams.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_tools.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_triggers.cpython-313.pyc +0 -0
- /package/lib/examples/{AgentspecExample.d.ts → AgentSpecsExample.d.ts} +0 -0
- /package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.js +0 -0
|
@@ -18,14 +18,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
19
19
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
20
20
|
import { Text, Button, Spinner, Heading, Label, Flash, ProgressBar, } from '@primer/react';
|
|
21
|
-
import {
|
|
21
|
+
import { BeakerIcon, CheckCircleIcon, XCircleIcon, PlayIcon, } from '@primer/octicons-react';
|
|
22
22
|
import { Box } from '@datalayer/primer-addons';
|
|
23
|
+
import { AuthRequiredView, ErrorView } from './components';
|
|
23
24
|
import { ThemedProvider } from './utils/themedProvider';
|
|
25
|
+
import { uniqueAgentId } from './utils/agentId';
|
|
24
26
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
25
|
-
import { SignInSimple } from '@datalayer/core/lib/views/iam';
|
|
26
|
-
import { UserBadge } from '@datalayer/core/lib/views/profile';
|
|
27
27
|
import { Chat } from '../chat';
|
|
28
|
-
import {
|
|
28
|
+
import { useAgentRuntimes } from '../hooks/useAgentRuntimes';
|
|
29
29
|
const queryClient = new QueryClient();
|
|
30
30
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
31
31
|
const AGENT_NAME = 'eval-demo-agent';
|
|
@@ -33,12 +33,14 @@ const AGENT_SPEC_ID = 'monitor-sales-kpis';
|
|
|
33
33
|
// ─── Inner component (rendered after auth) ─────────────────────────────────
|
|
34
34
|
const AgentEvalsInner = ({ onLogout }) => {
|
|
35
35
|
const { token } = useSimpleAuthStore();
|
|
36
|
-
const
|
|
36
|
+
const agentName = useRef(uniqueAgentId(AGENT_NAME)).current;
|
|
37
|
+
const { runtime, status: runtimeStatus, isReady, error: hookError, } = useAgentRuntimes({
|
|
37
38
|
agentSpecId: AGENT_SPEC_ID,
|
|
38
39
|
autoStart: true,
|
|
39
40
|
agentConfig: {
|
|
40
|
-
name:
|
|
41
|
-
|
|
41
|
+
name: agentName,
|
|
42
|
+
model: 'bedrock:us.anthropic.claude-3-5-haiku-20241022-v1:0',
|
|
43
|
+
protocol: 'vercel-ai',
|
|
42
44
|
description: 'Agent with evaluation and quality scoring',
|
|
43
45
|
},
|
|
44
46
|
});
|
|
@@ -113,14 +115,7 @@ const AgentEvalsInner = ({ onLogout }) => {
|
|
|
113
115
|
: 'Creating eval demo agent…' })] }));
|
|
114
116
|
}
|
|
115
117
|
if (runtimeStatus === 'error' || hookError) {
|
|
116
|
-
return (
|
|
117
|
-
display: 'flex',
|
|
118
|
-
flexDirection: 'column',
|
|
119
|
-
alignItems: 'center',
|
|
120
|
-
justifyContent: 'center',
|
|
121
|
-
height: '100vh',
|
|
122
|
-
gap: 3,
|
|
123
|
-
}, children: [_jsx(AlertIcon, { size: 48 }), _jsx(Text, { sx: { color: 'danger.fg' }, children: hookError || 'Agent failed to start' })] }));
|
|
118
|
+
return _jsx(ErrorView, { error: hookError, onLogout: onLogout });
|
|
124
119
|
}
|
|
125
120
|
const latestScore = evalRuns.length > 0 ? evalRuns[0].score : null;
|
|
126
121
|
return (_jsxs(Box, { sx: {
|
|
@@ -136,7 +131,7 @@ const AgentEvalsInner = ({ onLogout }) => {
|
|
|
136
131
|
borderBottom: '1px solid',
|
|
137
132
|
borderColor: 'border.default',
|
|
138
133
|
flexShrink: 0,
|
|
139
|
-
}, children: [_jsx(BeakerIcon, { size: 16 }), _jsxs(Heading, { as: "h3", sx: { fontSize: 2, flex: 1 }, children: ["Evaluation \u2014 ", podName] })
|
|
134
|
+
}, children: [_jsx(BeakerIcon, { size: 16 }), _jsxs(Heading, { as: "h3", sx: { fontSize: 2, flex: 1 }, children: ["Evaluation \u2014 ", podName] })] }), _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", placeholder: "Chat with the agent, then run evaluations\u2026", description: latestScore != null
|
|
140
135
|
? `Last score: ${(latestScore * 100).toFixed(0)}%`
|
|
141
136
|
: 'No evaluations run yet', showHeader: true, autoFocus: true, height: "100%", runtimeId: podName, historyEndpoint: `${agentBaseUrl}/api/v1/history`, suggestions: [
|
|
142
137
|
{
|
|
@@ -188,7 +183,7 @@ const syncTokenToIamStore = (token) => {
|
|
|
188
183
|
};
|
|
189
184
|
// ─── Main component with auth gate ─────────────────────────────────────────
|
|
190
185
|
const AgentEvalsExample = () => {
|
|
191
|
-
const { token,
|
|
186
|
+
const { token, clearAuth } = useSimpleAuthStore();
|
|
192
187
|
const hasSynced = useRef(false);
|
|
193
188
|
useEffect(() => {
|
|
194
189
|
if (token && !hasSynced.current) {
|
|
@@ -196,11 +191,6 @@ const AgentEvalsExample = () => {
|
|
|
196
191
|
syncTokenToIamStore(token);
|
|
197
192
|
}
|
|
198
193
|
}, [token]);
|
|
199
|
-
const handleSignIn = useCallback((newToken, handle) => {
|
|
200
|
-
setAuth(newToken, handle);
|
|
201
|
-
hasSynced.current = true;
|
|
202
|
-
syncTokenToIamStore(newToken);
|
|
203
|
-
}, [setAuth]);
|
|
204
194
|
const handleLogout = useCallback(() => {
|
|
205
195
|
clearAuth();
|
|
206
196
|
hasSynced.current = false;
|
|
@@ -209,7 +199,7 @@ const AgentEvalsExample = () => {
|
|
|
209
199
|
});
|
|
210
200
|
}, [clearAuth]);
|
|
211
201
|
if (!token) {
|
|
212
|
-
return (_jsx(ThemedProvider, { children: _jsx(
|
|
202
|
+
return (_jsx(ThemedProvider, { children: _jsx(AuthRequiredView, {}) }));
|
|
213
203
|
}
|
|
214
204
|
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThemedProvider, { children: _jsx(AgentEvalsInner, { onLogout: handleLogout }) }) }));
|
|
215
205
|
};
|
|
@@ -18,40 +18,213 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
19
19
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
20
20
|
import { Text, Button, Spinner, Heading, Label, Flash, ProgressBar, } from '@primer/react';
|
|
21
|
-
import {
|
|
21
|
+
import { ShieldCheckIcon, CheckIcon, XIcon, DotFillIcon, } from '@primer/octicons-react';
|
|
22
22
|
import { Box } from '@datalayer/primer-addons';
|
|
23
|
+
import { buildOtelWebSocketUrl } from '@datalayer/core/lib/otel';
|
|
24
|
+
import { useCoreStore } from '@datalayer/core/lib/state';
|
|
25
|
+
import { AuthRequiredView, ErrorView } from './components';
|
|
23
26
|
import { ThemedProvider } from './utils/themedProvider';
|
|
27
|
+
import { uniqueAgentId } from './utils/agentId';
|
|
28
|
+
import { parseAgentStreamMessage } from '../types/stream';
|
|
29
|
+
import { getAgentSpecs } from '../specs/agents';
|
|
30
|
+
import { subscribeOtelWs } from '../context/otelWsPool';
|
|
31
|
+
import { toMetricValue } from '../hooks/useMonitoring';
|
|
32
|
+
import { useAIAgentsWebSocket } from '../hooks';
|
|
24
33
|
const queryClient = new QueryClient();
|
|
25
34
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
26
|
-
import { SignInSimple } from '@datalayer/core/lib/views/iam';
|
|
27
|
-
import { UserBadge } from '@datalayer/core/lib/views/profile';
|
|
28
35
|
import { Chat } from '../chat';
|
|
29
|
-
import { useAgents } from '../hooks/useAgents';
|
|
30
36
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
31
37
|
const AGENT_NAME = 'guardrails-demo-agent';
|
|
32
|
-
const AGENT_SPEC_ID = '
|
|
33
|
-
const
|
|
38
|
+
const AGENT_SPEC_ID = 'demo-guardrails';
|
|
39
|
+
const DEFAULT_LOCAL_BASE_URL = import.meta.env.VITE_BASE_URL || 'http://localhost:8765';
|
|
40
|
+
const OTEL_BASE_URL_ENV = import.meta.env.VITE_OTEL_BASE_URL;
|
|
41
|
+
const DATALAYER_RUN_URL_ENV = import.meta.env.DATALAYER_RUN_URL;
|
|
42
|
+
const OTEL_SERVICE_NAME = 'agent-runtimes';
|
|
43
|
+
const COST_RUN_METRIC = 'agent_runtimes.capability.cost.run.usd';
|
|
44
|
+
const COST_CUMULATIVE_METRIC = 'agent_runtimes.capability.cost.cumulative.usd';
|
|
45
|
+
const toNumberOrNull = (value) => {
|
|
46
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
50
|
+
const parsed = Number(value);
|
|
51
|
+
if (Number.isFinite(parsed)) {
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
const resolvePerRunBudgetFromSpec = (agentSpecId) => {
|
|
58
|
+
const spec = getAgentSpecs(agentSpecId);
|
|
59
|
+
if (!spec?.guardrails?.length) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
for (const guardrail of spec.guardrails) {
|
|
63
|
+
if (!guardrail || typeof guardrail !== 'object') {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const costBudget = guardrail.cost_budget;
|
|
67
|
+
if (!costBudget || typeof costBudget !== 'object') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const perRunUsd = toNumberOrNull(costBudget.per_run_usd);
|
|
71
|
+
if (perRunUsd != null) {
|
|
72
|
+
return perRunUsd;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
};
|
|
77
|
+
const DEFAULT_RUN_BUDGET_USD = resolvePerRunBudgetFromSpec(AGENT_SPEC_ID);
|
|
78
|
+
const toApprovalRequest = (payload) => ({
|
|
79
|
+
id: payload.id,
|
|
80
|
+
tool_name: payload.tool_name,
|
|
81
|
+
tool_args: payload.tool_args ?? {},
|
|
82
|
+
created_at: payload.created_at ?? new Date().toISOString(),
|
|
83
|
+
});
|
|
84
|
+
const normalizeBaseUrl = (rawBaseUrl) => {
|
|
85
|
+
if (rawBaseUrl.startsWith('http://') ||
|
|
86
|
+
rawBaseUrl.startsWith('https://') ||
|
|
87
|
+
rawBaseUrl.startsWith('ws://') ||
|
|
88
|
+
rawBaseUrl.startsWith('wss://')) {
|
|
89
|
+
return rawBaseUrl;
|
|
90
|
+
}
|
|
91
|
+
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:'
|
|
92
|
+
? 'https:'
|
|
93
|
+
: 'http:';
|
|
94
|
+
const host = typeof window !== 'undefined' ? window.location.host : '';
|
|
95
|
+
return `${protocol}//${host}${rawBaseUrl}`;
|
|
96
|
+
};
|
|
97
|
+
const parseAttributes = (attrs) => {
|
|
98
|
+
if (attrs && typeof attrs === 'object' && !Array.isArray(attrs)) {
|
|
99
|
+
return attrs;
|
|
100
|
+
}
|
|
101
|
+
if (typeof attrs === 'string') {
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(attrs);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {};
|
|
110
|
+
};
|
|
111
|
+
const extractServiceName = (row) => {
|
|
112
|
+
const directCandidates = [row.service_name, row.service, row.serviceName];
|
|
113
|
+
for (const candidate of directCandidates) {
|
|
114
|
+
if (typeof candidate === 'string' && candidate.length > 0) {
|
|
115
|
+
return candidate;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const resourceAttributes = row.resource_attributes;
|
|
119
|
+
if (resourceAttributes && typeof resourceAttributes === 'object') {
|
|
120
|
+
const nested = resourceAttributes['service.name'];
|
|
121
|
+
if (typeof nested === 'string' && nested.length > 0) {
|
|
122
|
+
return nested;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
};
|
|
127
|
+
const extractAgentId = (row) => {
|
|
128
|
+
const attrs = parseAttributes(row.attributes);
|
|
129
|
+
const aid = attrs['agent.id'];
|
|
130
|
+
return typeof aid === 'string' ? aid : undefined;
|
|
131
|
+
};
|
|
132
|
+
const rowTimestampMs = (row) => {
|
|
133
|
+
const nanoTs = row.timestamp_unix_nano ?? row.observed_timestamp_unix_nano;
|
|
134
|
+
if (typeof nanoTs === 'number' && nanoTs > 0) {
|
|
135
|
+
return nanoTs / 1_000_000;
|
|
136
|
+
}
|
|
137
|
+
if (typeof nanoTs === 'string' && nanoTs.length > 0) {
|
|
138
|
+
const parsed = Number(nanoTs);
|
|
139
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
140
|
+
return parsed / 1_000_000;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const isoTs = row.timestamp;
|
|
144
|
+
if (typeof isoTs === 'string' && isoTs.length > 0) {
|
|
145
|
+
const ms = new Date(isoTs).getTime();
|
|
146
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
147
|
+
return ms;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return 0;
|
|
151
|
+
};
|
|
152
|
+
const selectLatestOtelCost = (rows, agentId) => {
|
|
153
|
+
const filtered = rows.filter(row => {
|
|
154
|
+
const metricName = row.metric_name;
|
|
155
|
+
if (metricName !== COST_RUN_METRIC &&
|
|
156
|
+
metricName !== COST_CUMULATIVE_METRIC) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (extractServiceName(row) !== OTEL_SERVICE_NAME) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return extractAgentId(row) === agentId;
|
|
163
|
+
});
|
|
164
|
+
if (filtered.length === 0) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
let latestTimestampMs = 0;
|
|
168
|
+
let runUsd = 0;
|
|
169
|
+
let cumulativeUsd = 0;
|
|
170
|
+
for (const row of filtered) {
|
|
171
|
+
const ts = rowTimestampMs(row);
|
|
172
|
+
if (!Number.isFinite(ts) || ts <= 0) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (ts > latestTimestampMs) {
|
|
176
|
+
latestTimestampMs = ts;
|
|
177
|
+
runUsd = 0;
|
|
178
|
+
cumulativeUsd = 0;
|
|
179
|
+
}
|
|
180
|
+
if (ts !== latestTimestampMs) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const value = Math.max(0, toMetricValue(row));
|
|
184
|
+
if (row.metric_name === COST_RUN_METRIC) {
|
|
185
|
+
runUsd += value;
|
|
186
|
+
}
|
|
187
|
+
else if (row.metric_name === COST_CUMULATIVE_METRIC) {
|
|
188
|
+
cumulativeUsd = Math.max(cumulativeUsd, value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (latestTimestampMs <= 0) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
timestampMs: latestTimestampMs,
|
|
196
|
+
runUsd,
|
|
197
|
+
cumulativeUsd,
|
|
198
|
+
};
|
|
199
|
+
};
|
|
34
200
|
// ─── Inner component (rendered after auth) ─────────────────────────────────
|
|
35
201
|
const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
36
202
|
const { token } = useSimpleAuthStore();
|
|
37
|
-
const {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
});
|
|
203
|
+
const { configuration } = useCoreStore();
|
|
204
|
+
const agentName = useRef(uniqueAgentId(AGENT_NAME)).current;
|
|
205
|
+
const [runtimeStatus, setRuntimeStatus] = useState('launching');
|
|
206
|
+
const [isReady, setIsReady] = useState(false);
|
|
207
|
+
const [hookError, setHookError] = useState(null);
|
|
208
|
+
const [agentId, setAgentId] = useState(agentName);
|
|
209
|
+
const [isReconnectedAgent, setIsReconnectedAgent] = useState(false);
|
|
46
210
|
// Cost tracking
|
|
47
|
-
const [
|
|
211
|
+
const [snapshotRunCostUsd, setSnapshotRunCostUsd] = useState(0);
|
|
212
|
+
const [runBudgetUsd, setRunBudgetUsd] = useState(DEFAULT_RUN_BUDGET_USD);
|
|
48
213
|
const [totalTokens, setTotalTokens] = useState(0);
|
|
214
|
+
const [otelRunCostUsd, setOtelRunCostUsd] = useState(null);
|
|
215
|
+
const [otelCumulativeCostUsd, setOtelCumulativeCostUsd] = useState(null);
|
|
216
|
+
const [otelSampleTimestamp, setOtelSampleTimestamp] = useState(null);
|
|
49
217
|
// Tool approval queue
|
|
50
218
|
const [approvals, setApprovals] = useState([]);
|
|
51
219
|
const [approvalLoading, setApprovalLoading] = useState(null);
|
|
52
|
-
const agentBaseUrl =
|
|
53
|
-
const
|
|
54
|
-
|
|
220
|
+
const agentBaseUrl = DEFAULT_LOCAL_BASE_URL;
|
|
221
|
+
const otelBaseUrl = configuration?.otelRunUrl ||
|
|
222
|
+
configuration?.runUrl ||
|
|
223
|
+
OTEL_BASE_URL_ENV ||
|
|
224
|
+
DATALAYER_RUN_URL_ENV ||
|
|
225
|
+
'https://prod1.datalayer.run';
|
|
226
|
+
const podName = agentId;
|
|
227
|
+
const chatAuthToken = token === null ? undefined : token;
|
|
55
228
|
// Authenticated fetch helper (for sidecar endpoints)
|
|
56
229
|
const authFetch = useCallback((url, opts = {}) => fetch(url, {
|
|
57
230
|
...opts,
|
|
@@ -61,46 +234,158 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
61
234
|
...(opts.headers ?? {}),
|
|
62
235
|
},
|
|
63
236
|
}), [token]);
|
|
64
|
-
// ── Poll cost + tool approvals ───────────────────────────────────────────
|
|
65
237
|
useEffect(() => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
238
|
+
let isCancelled = false;
|
|
239
|
+
const createLocalAgent = async () => {
|
|
240
|
+
setRuntimeStatus('launching');
|
|
241
|
+
setIsReady(false);
|
|
242
|
+
setHookError(null);
|
|
243
|
+
setIsReconnectedAgent(false);
|
|
69
244
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
245
|
+
const response = await authFetch(`${agentBaseUrl}/api/v1/agents`, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
name: agentName,
|
|
249
|
+
description: 'Agent with cost budget and tool approval guardrails',
|
|
250
|
+
agent_library: 'pydantic-ai',
|
|
251
|
+
transport: 'vercel-ai',
|
|
252
|
+
agent_spec_id: AGENT_SPEC_ID,
|
|
253
|
+
enable_skills: true,
|
|
254
|
+
tools: [],
|
|
255
|
+
}),
|
|
256
|
+
});
|
|
257
|
+
let resolvedAgentId = agentName;
|
|
258
|
+
let isAlreadyRunning = false;
|
|
259
|
+
if (response.ok) {
|
|
260
|
+
const data = await response.json();
|
|
261
|
+
resolvedAgentId = data?.id || agentName;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
const contentType = response.headers.get('content-type') || '';
|
|
265
|
+
let detail = '';
|
|
266
|
+
if (contentType.includes('application/json')) {
|
|
267
|
+
const data = await response.json().catch(() => null);
|
|
268
|
+
detail =
|
|
269
|
+
(typeof data?.detail === 'string' && data.detail) ||
|
|
270
|
+
(typeof data?.message === 'string' && data.message) ||
|
|
271
|
+
'';
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
detail = await response.text();
|
|
275
|
+
}
|
|
276
|
+
if (response.status === 409 || /already exists/i.test(detail || '')) {
|
|
277
|
+
isAlreadyRunning = true;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
throw new Error(detail || `Failed to create local agent: ${response.status}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!isCancelled) {
|
|
284
|
+
setAgentId(resolvedAgentId);
|
|
285
|
+
setIsReconnectedAgent(isAlreadyRunning);
|
|
286
|
+
setIsReady(true);
|
|
287
|
+
setRuntimeStatus('ready');
|
|
76
288
|
}
|
|
77
289
|
}
|
|
78
|
-
catch {
|
|
79
|
-
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (!isCancelled) {
|
|
292
|
+
setHookError(error instanceof Error ? error.message : 'Agent failed to start');
|
|
293
|
+
setRuntimeStatus('error');
|
|
294
|
+
}
|
|
80
295
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
296
|
+
};
|
|
297
|
+
void createLocalAgent();
|
|
298
|
+
return () => {
|
|
299
|
+
isCancelled = true;
|
|
300
|
+
};
|
|
301
|
+
}, [agentBaseUrl, agentName, authFetch]);
|
|
302
|
+
const handleGuardrailsStreamMessage = useCallback((message) => {
|
|
303
|
+
try {
|
|
304
|
+
const stream = parseAgentStreamMessage(message?.raw ?? message);
|
|
305
|
+
if (!stream) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (stream.type === 'agent.snapshot') {
|
|
309
|
+
const payload = stream.payload;
|
|
310
|
+
setApprovals((payload.approvals ?? []).map(toApprovalRequest));
|
|
311
|
+
const snapshotCost = payload.contextSnapshot?.costUsage ?? payload.costUsage;
|
|
312
|
+
if (snapshotCost) {
|
|
313
|
+
setSnapshotRunCostUsd(Number(snapshotCost.cumulativeCostUsd ??
|
|
314
|
+
snapshotCost.lastTurnCostUsd ??
|
|
315
|
+
0));
|
|
316
|
+
setRunBudgetUsd(prev => snapshotCost.perRunBudgetUsd == null
|
|
317
|
+
? (prev ?? DEFAULT_RUN_BUDGET_USD)
|
|
318
|
+
: Number(snapshotCost.perRunBudgetUsd));
|
|
319
|
+
setTotalTokens(Number(snapshotCost.totalTokensUsed ?? 0));
|
|
87
320
|
}
|
|
321
|
+
return;
|
|
88
322
|
}
|
|
89
|
-
|
|
90
|
-
|
|
323
|
+
if (stream.type === 'tool_approval_created') {
|
|
324
|
+
const approval = toApprovalRequest(stream.payload);
|
|
325
|
+
setApprovals(prev => {
|
|
326
|
+
const next = prev.filter(item => item.id !== approval.id);
|
|
327
|
+
next.unshift(approval);
|
|
328
|
+
return next;
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
91
331
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
332
|
+
if (stream.type === 'tool_approval_approved' ||
|
|
333
|
+
stream.type === 'tool_approval_rejected') {
|
|
334
|
+
const approval = stream.payload;
|
|
335
|
+
setApprovals(prev => prev.filter(item => item.id !== approval.id));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Ignore malformed stream payloads.
|
|
340
|
+
}
|
|
341
|
+
}, []);
|
|
342
|
+
const approvalSocket = useAIAgentsWebSocket({
|
|
343
|
+
enabled: isReady && Boolean(agentBaseUrl),
|
|
344
|
+
baseUrl: agentBaseUrl,
|
|
345
|
+
path: '/api/v1/tool-approvals/ws',
|
|
346
|
+
queryParams: { agent_id: agentId },
|
|
347
|
+
onMessage: handleGuardrailsStreamMessage,
|
|
348
|
+
reconnectDelayMs: attempt => Math.min(1000 * 2 ** Math.max(0, attempt - 1), 10000),
|
|
349
|
+
});
|
|
350
|
+
const wsState = approvalSocket.connectionState;
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
setOtelRunCostUsd(null);
|
|
353
|
+
setOtelCumulativeCostUsd(null);
|
|
354
|
+
setOtelSampleTimestamp(null);
|
|
355
|
+
if (!isReady || !token) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
let wsUrl;
|
|
359
|
+
try {
|
|
360
|
+
wsUrl = buildOtelWebSocketUrl({
|
|
361
|
+
baseUrl: normalizeBaseUrl(otelBaseUrl),
|
|
362
|
+
token,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const unsubscribe = subscribeOtelWs(wsUrl, msg => {
|
|
369
|
+
if (msg.signal !== 'metrics' || !Array.isArray(msg.data)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const sample = selectLatestOtelCost(msg.data, agentId);
|
|
373
|
+
if (!sample) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
setOtelRunCostUsd(sample.runUsd);
|
|
377
|
+
setOtelCumulativeCostUsd(sample.cumulativeUsd);
|
|
378
|
+
setOtelSampleTimestamp(sample.timestampMs);
|
|
379
|
+
});
|
|
380
|
+
return unsubscribe;
|
|
381
|
+
}, [agentId, isReady, otelBaseUrl, token]);
|
|
97
382
|
// ── Approve / Reject ─────────────────────────────────────────────────────
|
|
98
383
|
const handleApprove = useCallback(async (requestId) => {
|
|
99
384
|
if (!agentBaseUrl)
|
|
100
385
|
return;
|
|
101
386
|
setApprovalLoading(requestId);
|
|
102
387
|
try {
|
|
103
|
-
await authFetch(`${agentBaseUrl}/api/v1/
|
|
388
|
+
await authFetch(`${agentBaseUrl}/api/v1/tool-approvals/${requestId}/approve`, { method: 'POST' });
|
|
104
389
|
setApprovals(prev => prev.filter(a => a.id !== requestId));
|
|
105
390
|
}
|
|
106
391
|
catch {
|
|
@@ -109,13 +394,13 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
109
394
|
finally {
|
|
110
395
|
setApprovalLoading(null);
|
|
111
396
|
}
|
|
112
|
-
}, [agentBaseUrl,
|
|
397
|
+
}, [agentBaseUrl, authFetch]);
|
|
113
398
|
const handleReject = useCallback(async (requestId) => {
|
|
114
399
|
if (!agentBaseUrl)
|
|
115
400
|
return;
|
|
116
401
|
setApprovalLoading(requestId);
|
|
117
402
|
try {
|
|
118
|
-
await authFetch(`${agentBaseUrl}/api/v1/
|
|
403
|
+
await authFetch(`${agentBaseUrl}/api/v1/tool-approvals/${requestId}/reject`, {
|
|
119
404
|
method: 'POST',
|
|
120
405
|
body: JSON.stringify({ reason: 'User rejected' }),
|
|
121
406
|
});
|
|
@@ -127,7 +412,7 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
127
412
|
finally {
|
|
128
413
|
setApprovalLoading(null);
|
|
129
414
|
}
|
|
130
|
-
}, [agentBaseUrl,
|
|
415
|
+
}, [agentBaseUrl, authFetch]);
|
|
131
416
|
// ── Loading / Error ──────────────────────────────────────────────────────
|
|
132
417
|
if (!isReady && runtimeStatus !== 'error') {
|
|
133
418
|
return (_jsxs(Box, { sx: {
|
|
@@ -137,21 +422,31 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
137
422
|
justifyContent: 'center',
|
|
138
423
|
height: '100vh',
|
|
139
424
|
gap: 3,
|
|
140
|
-
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children:
|
|
141
|
-
? 'Launching runtime for guardrails agent…'
|
|
142
|
-
: 'Creating guardrails demo agent…' })] }));
|
|
425
|
+
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Launching guardrails demo agent..." })] }));
|
|
143
426
|
}
|
|
144
427
|
if (runtimeStatus === 'error' || hookError) {
|
|
145
|
-
return (
|
|
146
|
-
display: 'flex',
|
|
147
|
-
flexDirection: 'column',
|
|
148
|
-
alignItems: 'center',
|
|
149
|
-
justifyContent: 'center',
|
|
150
|
-
height: '100vh',
|
|
151
|
-
gap: 3,
|
|
152
|
-
}, children: [_jsx(AlertIcon, { size: 48 }), _jsx(Text, { sx: { color: 'danger.fg' }, children: hookError || 'Agent failed to start' })] }));
|
|
428
|
+
return _jsx(ErrorView, { error: hookError, onLogout: onLogout });
|
|
153
429
|
}
|
|
154
|
-
const
|
|
430
|
+
const runCostUsd = Math.max(snapshotRunCostUsd, otelRunCostUsd ?? 0);
|
|
431
|
+
const cumulativeCostUsd = otelCumulativeCostUsd;
|
|
432
|
+
const isOverRunBudget = runBudgetUsd != null && runBudgetUsd > 0 && runCostUsd > runBudgetUsd;
|
|
433
|
+
const runBudgetDisplayUsd = runBudgetUsd != null ? runBudgetUsd.toFixed(2) : '0.00';
|
|
434
|
+
const overBudgetBanner = isOverRunBudget
|
|
435
|
+
? {
|
|
436
|
+
variant: 'danger',
|
|
437
|
+
message: `Run budget exceeded: $${runCostUsd.toFixed(4)} / $${runBudgetDisplayUsd}. Start a new run or adjust the budget before sending more messages.`,
|
|
438
|
+
}
|
|
439
|
+
: undefined;
|
|
440
|
+
const budgetForProgress = runBudgetUsd && runBudgetUsd > 0 ? runBudgetUsd : 1;
|
|
441
|
+
const usagePercentRaw = (runCostUsd / budgetForProgress) * 100;
|
|
442
|
+
const costPercent = Math.min(usagePercentRaw, 100);
|
|
443
|
+
const usageSafePercent = Math.min(costPercent, 50);
|
|
444
|
+
const usageWatchPercent = Math.min(Math.max(costPercent - 50, 0), 30);
|
|
445
|
+
const usageDangerPercent = Math.min(Math.max(costPercent - 80, 0), 20);
|
|
446
|
+
const overBudgetPercent = Math.max(usagePercentRaw - 100, 0);
|
|
447
|
+
const overBudgetAmountUsd = runBudgetUsd != null && runBudgetUsd > 0
|
|
448
|
+
? Math.max(runCostUsd - runBudgetUsd, 0)
|
|
449
|
+
: 0;
|
|
155
450
|
const costColor = costPercent > 80
|
|
156
451
|
? 'danger.fg'
|
|
157
452
|
: costPercent > 50
|
|
@@ -170,14 +465,24 @@ const AgentGuardrailsInner = ({ onLogout, }) => {
|
|
|
170
465
|
borderBottom: '1px solid',
|
|
171
466
|
borderColor: 'border.default',
|
|
172
467
|
flexShrink: 0,
|
|
173
|
-
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(ShieldCheckIcon, { size: 16 }), _jsxs(Heading, { as: "h3", sx: { fontSize: 2 }, children: ["Guardrails Demo \u2014 ", podName] })] }), _jsxs(Box, { sx: { flex: 1, maxWidth: 300 }, children: [_jsxs(Box, { sx: {
|
|
468
|
+
}, children: [isReconnectedAgent && (_jsx(Label, { variant: "secondary", size: "small", children: "Reconnected" })), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(ShieldCheckIcon, { size: 16 }), _jsxs(Heading, { as: "h3", sx: { fontSize: 2 }, children: ["Guardrails Demo \u2014 ", podName] })] }), _jsx(Label, { variant: wsState === 'connected' ? 'success' : 'secondary', children: wsState }), _jsxs(Box, { sx: { flex: 1, maxWidth: 300 }, children: [_jsxs(Box, { sx: {
|
|
174
469
|
display: 'flex',
|
|
175
470
|
justifyContent: 'space-between',
|
|
176
471
|
fontSize: 0,
|
|
177
472
|
mb: 1,
|
|
178
|
-
}, children: [_jsxs(Text, { sx: { color: costColor, fontWeight: 'semibold' }, children: ["$",
|
|
179
|
-
|
|
180
|
-
|
|
473
|
+
}, children: [_jsxs(Text, { sx: { color: costColor, fontWeight: 'semibold' }, children: ["$", runCostUsd.toFixed(4)] }), _jsx(Text, { sx: { color: 'fg.muted' }, children: runBudgetUsd != null
|
|
474
|
+
? ` / $${runBudgetUsd.toFixed(2)} run budget`
|
|
475
|
+
: ' / no run budget' })] }), _jsxs(ProgressBar, { "aria-label": "Run budget usage", "aria-valuenow": Math.max(0, costPercent), sx: { height: 6 }, children: [_jsx(ProgressBar.Item, { progress: usageSafePercent, style: { backgroundColor: 'var(--bgColor-success-emphasis)' }, "aria-label": `Healthy usage ${usageSafePercent.toFixed(1)}%` }), _jsx(ProgressBar.Item, { progress: usageWatchPercent, style: { backgroundColor: 'var(--bgColor-accent-emphasis)' }, "aria-label": `Watch usage ${usageWatchPercent.toFixed(1)}%` }), _jsx(ProgressBar.Item, { progress: usageDangerPercent, style: { backgroundColor: 'var(--bgColor-danger-emphasis)' }, "aria-label": `Critical usage ${usageDangerPercent.toFixed(1)}%` })] }), _jsxs(Box, { sx: {
|
|
476
|
+
display: 'flex',
|
|
477
|
+
alignItems: 'center',
|
|
478
|
+
gap: 2,
|
|
479
|
+
flexWrap: 'wrap',
|
|
480
|
+
mt: 1,
|
|
481
|
+
}, role: "presentation", children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(DotFillIcon, { size: 12, fill: "var(--bgColor-success-emphasis)" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "0-50%" })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(DotFillIcon, { size: 12, fill: "var(--bgColor-accent-emphasis)" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "50-80%" })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(DotFillIcon, { size: 12, fill: "var(--bgColor-danger-emphasis)" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "80-100%" })] }), overBudgetPercent > 0 && (_jsxs(Label, { variant: "danger", size: "small", children: ["+", overBudgetPercent.toFixed(1), "% over"] }))] })] }), _jsxs(Label, { variant: otelSampleTimestamp == null ? 'secondary' : 'success', children: ["OTEL ", otelSampleTimestamp == null ? 'waiting' : 'live'] }), cumulativeCostUsd != null && (_jsxs(Label, { variant: "secondary", children: ["Total $", cumulativeCostUsd.toFixed(4)] })), _jsxs(Label, { variant: "secondary", children: [totalTokens.toLocaleString(), " tokens"] })] }), isOverRunBudget && (_jsx(Flash, { variant: "danger", sx: { mx: 3, mt: 2 }, children: _jsxs(Text, { sx: { fontSize: 1 }, children: [_jsx("strong", { children: "Run budget exceeded." }), " Current run cost is $", runCostUsd.toFixed(4), " against a budget of $", runBudgetDisplayUsd, overBudgetAmountUsd > 0
|
|
482
|
+
? ` (over by $${overBudgetAmountUsd.toFixed(4)}).`
|
|
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
|
+
? ` — ${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 manual tool approval gates", showHeader: false, showTokenUsage: true, errorBanner: overBudgetBanner, disableInputPrompt: isOverRunBudget, autoFocus: true, height: "100%", runtimeId: agentId, historyEndpoint: `${agentBaseUrl}/api/v1/history`, suggestions: [
|
|
181
486
|
{ title: 'Update CRM', message: 'Update the CRM records for Q3' },
|
|
182
487
|
{ title: 'Report', message: 'Generate the weekly KPI report' },
|
|
183
488
|
], submitOnSuggestionClick: true }) })] }));
|
|
@@ -190,7 +495,7 @@ const syncTokenToIamStore = (token) => {
|
|
|
190
495
|
};
|
|
191
496
|
// ─── Main component with auth gate ─────────────────────────────────────────
|
|
192
497
|
const AgentGuardrailsExample = () => {
|
|
193
|
-
const { token,
|
|
498
|
+
const { token, clearAuth } = useSimpleAuthStore();
|
|
194
499
|
const hasSynced = useRef(false);
|
|
195
500
|
useEffect(() => {
|
|
196
501
|
if (token && !hasSynced.current) {
|
|
@@ -198,11 +503,6 @@ const AgentGuardrailsExample = () => {
|
|
|
198
503
|
syncTokenToIamStore(token);
|
|
199
504
|
}
|
|
200
505
|
}, [token]);
|
|
201
|
-
const handleSignIn = useCallback((newToken, handle) => {
|
|
202
|
-
setAuth(newToken, handle);
|
|
203
|
-
hasSynced.current = true;
|
|
204
|
-
syncTokenToIamStore(newToken);
|
|
205
|
-
}, [setAuth]);
|
|
206
506
|
const handleLogout = useCallback(() => {
|
|
207
507
|
clearAuth();
|
|
208
508
|
hasSynced.current = false;
|
|
@@ -211,7 +511,7 @@ const AgentGuardrailsExample = () => {
|
|
|
211
511
|
});
|
|
212
512
|
}, [clearAuth]);
|
|
213
513
|
if (!token) {
|
|
214
|
-
return (_jsx(ThemedProvider, { children: _jsx(
|
|
514
|
+
return (_jsx(ThemedProvider, { children: _jsx(AuthRequiredView, {}) }));
|
|
215
515
|
}
|
|
216
516
|
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThemedProvider, { children: _jsx(AgentGuardrailsInner, { onLogout: handleLogout }) }) }));
|
|
217
517
|
};
|