@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.
Files changed (111) hide show
  1. package/README.md +157 -10
  2. package/lib/AgentNode.d.ts +3 -0
  3. package/lib/AgentNode.js +676 -0
  4. package/lib/agent-node/themeStore.d.ts +3 -0
  5. package/lib/agent-node/themeStore.js +156 -0
  6. package/lib/agent-node-main.d.ts +1 -0
  7. package/lib/agent-node-main.js +14 -0
  8. package/lib/chat/Chat.js +16 -10
  9. package/lib/chat/ChatFloating.js +1 -1
  10. package/lib/chat/ChatSidebar.js +81 -49
  11. package/lib/chat/base/ChatBase.js +388 -74
  12. package/lib/chat/display/FloatingBrandButton.js +8 -1
  13. package/lib/chat/header/ChatHeader.d.ts +3 -1
  14. package/lib/chat/header/ChatHeader.js +15 -12
  15. package/lib/chat/header/ChatHeaderBase.d.ts +29 -9
  16. package/lib/chat/header/ChatHeaderBase.js +26 -3
  17. package/lib/chat/indicators/SandboxStatusIndicator.js +82 -47
  18. package/lib/chat/messages/ChatMessageList.js +46 -1
  19. package/lib/chat/messages/ChatMessages.js +6 -2
  20. package/lib/chat/prompt/InputFooter.d.ts +3 -1
  21. package/lib/chat/prompt/InputFooter.js +8 -5
  22. package/lib/chat/prompt/InputPrompt.d.ts +3 -1
  23. package/lib/chat/prompt/InputPrompt.js +2 -2
  24. package/lib/chat/prompt/InputPromptFooter.d.ts +3 -1
  25. package/lib/chat/prompt/InputPromptFooter.js +3 -3
  26. package/lib/client/AgentsMixin.js +14 -0
  27. package/lib/config/AgentConfiguration.d.ts +22 -0
  28. package/lib/config/AgentConfiguration.js +319 -64
  29. package/lib/examples/AgUiSharedStateExample.js +2 -1
  30. package/lib/examples/AgentCheckpointsExample.js +3 -3
  31. package/lib/examples/AgentCodemodeExample.d.ts +3 -3
  32. package/lib/examples/AgentCodemodeExample.js +24 -12
  33. package/lib/examples/AgentEvalsExample.js +330 -40
  34. package/lib/examples/AgentGuardrailsExample.js +16 -5
  35. package/lib/examples/AgentHooksExample.js +27 -9
  36. package/lib/examples/AgentInferenceProviderExample.d.ts +3 -0
  37. package/lib/examples/AgentInferenceProviderExample.js +329 -0
  38. package/lib/examples/AgentMCPExample.js +6 -5
  39. package/lib/examples/AgentMemoryExample.d.ts +1 -2
  40. package/lib/examples/AgentMemoryExample.js +71 -22
  41. package/lib/examples/AgentMonitoringExample.js +5 -5
  42. package/lib/examples/AgentNotificationsExample.d.ts +1 -2
  43. package/lib/examples/AgentNotificationsExample.js +71 -22
  44. package/lib/examples/AgentOtelExample.js +31 -40
  45. package/lib/examples/AgentOutputsExample.d.ts +1 -1
  46. package/lib/examples/AgentOutputsExample.js +67 -16
  47. package/lib/examples/AgentParametersExample.js +10 -8
  48. package/lib/examples/AgentSandboxExample.d.ts +1 -1
  49. package/lib/examples/AgentSandboxExample.js +7 -6
  50. package/lib/examples/AgentSkillsExample.js +6 -6
  51. package/lib/examples/AgentSubagentsExample.d.ts +1 -1
  52. package/lib/examples/AgentSubagentsExample.js +6 -6
  53. package/lib/examples/AgentToolApprovalsExample.js +27 -11
  54. package/lib/examples/AgentTriggersExample.js +5 -5
  55. package/lib/examples/{AgentSpecsExample.d.ts → AgentspecsExample.d.ts} +2 -2
  56. package/lib/examples/AgentspecsExample.js +1096 -0
  57. package/lib/examples/ChatCustomExample.js +6 -5
  58. package/lib/examples/ChatExample.js +6 -5
  59. package/lib/examples/Lexical2Example.js +1 -1
  60. package/lib/examples/LexicalAgentExample.js +1 -1
  61. package/lib/examples/NotebookAgentExample.js +3 -3
  62. package/lib/examples/components/ExampleWrapper.d.ts +6 -7
  63. package/lib/examples/components/ExampleWrapper.js +27 -10
  64. package/lib/examples/example-selector.js +2 -1
  65. package/lib/examples/index.d.ts +2 -1
  66. package/lib/examples/index.js +2 -1
  67. package/lib/examples/lexical/initial-content.json +6 -6
  68. package/lib/examples/main.js +56 -16
  69. package/lib/examples/utils/agentId.d.ts +1 -1
  70. package/lib/examples/utils/agentId.js +1 -1
  71. package/lib/examples/utils/useExampleAgentRuntimesUrl.d.ts +5 -0
  72. package/lib/examples/utils/useExampleAgentRuntimesUrl.js +19 -0
  73. package/lib/hooks/useAIAgentsWebSocket.js +35 -0
  74. package/lib/hooks/useAgentRuntimes.d.ts +32 -3
  75. package/lib/hooks/useAgentRuntimes.js +114 -19
  76. package/lib/index.d.ts +1 -1
  77. package/lib/specs/agents/agents.d.ts +20 -13
  78. package/lib/specs/agents/agents.js +1267 -581
  79. package/lib/specs/benchmarks.d.ts +20 -0
  80. package/lib/specs/benchmarks.js +205 -0
  81. package/lib/specs/envvars.d.ts +0 -1
  82. package/lib/specs/envvars.js +0 -11
  83. package/lib/specs/evals.d.ts +10 -9
  84. package/lib/specs/evals.js +128 -88
  85. package/lib/specs/index.d.ts +0 -1
  86. package/lib/specs/index.js +0 -1
  87. package/lib/specs/models.d.ts +0 -2
  88. package/lib/specs/models.js +0 -15
  89. package/lib/specs/skills.d.ts +0 -1
  90. package/lib/specs/skills.js +0 -18
  91. package/lib/stores/agentRuntimeStore.d.ts +5 -1
  92. package/lib/stores/agentRuntimeStore.js +22 -8
  93. package/lib/stores/conversationStore.js +2 -2
  94. package/lib/types/agents-lifecycle.d.ts +18 -0
  95. package/lib/types/agents.d.ts +6 -0
  96. package/lib/types/agentspecs.d.ts +4 -0
  97. package/lib/types/benchmarks.d.ts +43 -0
  98. package/lib/types/benchmarks.js +5 -0
  99. package/lib/types/chat.d.ts +16 -0
  100. package/lib/types/evals.d.ts +26 -17
  101. package/lib/types/index.d.ts +1 -0
  102. package/lib/types/index.js +1 -0
  103. package/package.json +9 -5
  104. package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
  105. package/scripts/codegen/__pycache__/generate_benchmarks.cpython-313.pyc +0 -0
  106. package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
  107. package/scripts/codegen/generate_agents.py +89 -43
  108. package/scripts/codegen/generate_benchmarks.py +441 -0
  109. package/scripts/codegen/generate_evals.py +94 -16
  110. package/scripts/codegen/generate_events.py +0 -1
  111. package/lib/examples/AgentSpecsExample.js +0 -694
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * AgentCodemodeExample
3
3
  *
4
- * Compares two Tavily-based agents side-by-side:
5
- * - Tavily MCP without codemode conversion
6
- * - Tavily MCP with codemode conversion
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 Tavily-based agents side-by-side:
10
- * - Tavily MCP without codemode conversion
11
- * - Tavily MCP with codemode conversion
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 Tavily Extract tool to extract information from https://datalayer.ai, then use your sandbox to persist that information in a variable named "about_datalayer".';
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: 'Tavily MCP (No Codemode)',
45
+ title: 'MCP Tools (No Codemode)',
46
46
  subtitle: 'Raw MCP tools without codemode conversion',
47
47
  suggestionMessage: NO_CODEMODE_SUGGESTION_MESSAGE,
48
- specId: 'demo-tavily-no-codemode',
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: 'Tavily MCP (Codemode)',
54
+ title: 'Codemode Tools',
55
55
  subtitle: 'MCP tools converted into programmatic tools',
56
56
  suggestionMessage: CODEMODE_SUGGESTION_MESSAGE,
57
- specId: 'demo-tavily-codemode',
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
- setHookError(error instanceof Error ? error.message : 'Agent failed to start');
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 Tavily MCP vs Tavily Codemode" })] }), _jsxs(Box, { sx: { flex: 1, minHeight: 0, display: 'flex' }, children: [DEMO_AGENT_CONFIGS.filter(c => c.key === 'no-codemode').map(config => (() => {
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-demo-agent';
32
- const AGENT_SPEC_ID = 'monitor-sales-kpis';
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 { runtime, status: runtimeStatus, isReady, error: hookError, } = useAgentRuntimes({
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: true,
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-3-5-haiku-20241022-v1:0',
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 agentBaseUrl = runtime?.agentBaseUrl || '';
51
- const agentId = runtime?.agentId || AGENT_NAME;
52
- const podName = runtime?.podName || '(launching…)';
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 (!isReady || !agentBaseUrl)
303
+ if (!isAgentReady || !controlPlaneBaseUrl || !experimentId)
65
304
  return;
66
305
  const poll = async () => {
67
306
  try {
68
- const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/eval/runs`);
69
- if (res.ok) {
70
- const d = await res.json();
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
- }, [isReady, agentBaseUrl, agentId, authFetch]);
318
+ }, [isAgentReady, controlPlaneBaseUrl, experimentId, evalApiFetch, mapRuns]);
82
319
  // ── Run eval suite ────────────────────────────────────────────────────
83
320
  const handleRunEval = useCallback(async () => {
84
- if (!agentBaseUrl)
321
+ if (!controlPlaneBaseUrl || !experimentId)
85
322
  return;
86
323
  setIsRunning(true);
87
324
  setFlash(null);
88
325
  try {
89
- const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/eval/run`, { method: 'POST' });
90
- if (res.ok) {
91
- setFlash('Evaluation suite started');
92
- }
93
- else {
94
- setFlash(`Failed to start eval (${res.status})`);
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('Network error');
359
+ setFlash('Failed to persist evaluation run');
99
360
  }
100
361
  finally {
101
362
  setIsRunning(false);
102
363
  }
103
- }, [agentBaseUrl, agentId, authFetch]);
364
+ }, [
365
+ controlPlaneBaseUrl,
366
+ experimentId,
367
+ evalApiFetch,
368
+ mapRuns,
369
+ podName,
370
+ evalId,
371
+ agentId,
372
+ ]);
104
373
  // ── Loading / Error ───────────────────────────────────────────────────
105
- if (!isReady && runtimeStatus !== 'error') {
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: '100vh',
380
+ height: '100%',
112
381
  gap: 3,
113
- }, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: runtimeStatus === 'launching'
114
- ? 'Launching runtime for eval agent…'
115
- : 'Creating eval demo agent…' })] }));
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 (runtimeStatus === 'error' || hookError) {
118
- return _jsx(ErrorView, { error: hookError, onLogout: onLogout });
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: 'calc(100vh - 60px)',
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(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
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 against recent agent responses. Results are scored automatically." }), _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.includes('started') ? 'success' : 'danger', 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: {
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-demo-agent';
38
- const AGENT_SPEC_ID = 'demo-guardrails';
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 demo agent..." })] }));
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 manual tool approval gates", showHeader: false, showTokenUsage: true, errorBanner: overBudgetBanner, disableInputPrompt: isOverRunBudget, autoFocus: true, height: "100%", runtimeId: agentId, historyEndpoint: `${agentBaseUrl}/api/v1/history`, suggestions: [
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
- { title: 'Report', message: 'Generate the weekly KPI report' },
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 ──────────────────────────────────────────