@agentuity/workbench 0.0.94 → 0.0.96

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 (31) hide show
  1. package/dist/components/App.d.ts.map +1 -1
  2. package/dist/components/App.js +3 -1
  3. package/dist/components/App.js.map +1 -1
  4. package/dist/components/internal/Chat.d.ts.map +1 -1
  5. package/dist/components/internal/Chat.js +3 -3
  6. package/dist/components/internal/Chat.js.map +1 -1
  7. package/dist/components/internal/InputSection.d.ts +2 -1
  8. package/dist/components/internal/InputSection.d.ts.map +1 -1
  9. package/dist/components/internal/InputSection.js +11 -11
  10. package/dist/components/internal/InputSection.js.map +1 -1
  11. package/dist/components/internal/MonacoJsonEditor.d.ts.map +1 -1
  12. package/dist/components/internal/MonacoJsonEditor.js +10 -1
  13. package/dist/components/internal/MonacoJsonEditor.js.map +1 -1
  14. package/dist/components/internal/WorkbenchProvider.d.ts +2 -1
  15. package/dist/components/internal/WorkbenchProvider.d.ts.map +1 -1
  16. package/dist/components/internal/WorkbenchProvider.js +218 -26
  17. package/dist/components/internal/WorkbenchProvider.js.map +1 -1
  18. package/dist/lib/utils.d.ts.map +1 -1
  19. package/dist/lib/utils.js +4 -3
  20. package/dist/lib/utils.js.map +1 -1
  21. package/dist/standalone.css +7 -0
  22. package/dist/types/config.d.ts +4 -0
  23. package/dist/types/config.d.ts.map +1 -1
  24. package/package.json +4 -4
  25. package/src/components/App.tsx +9 -4
  26. package/src/components/internal/Chat.tsx +5 -3
  27. package/src/components/internal/InputSection.tsx +65 -20
  28. package/src/components/internal/MonacoJsonEditor.tsx +8 -1
  29. package/src/components/internal/WorkbenchProvider.tsx +260 -26
  30. package/src/lib/utils.ts +4 -3
  31. package/src/types/config.ts +5 -0
@@ -6,6 +6,7 @@ import {
6
6
  SendIcon,
7
7
  Loader2Icon,
8
8
  Sparkles,
9
+ Trash2,
9
10
  } from 'lucide-react';
10
11
  import { MonacoJsonEditor } from './MonacoJsonEditor';
11
12
  import {
@@ -25,12 +26,13 @@ import {
25
26
  } from '../ui/command';
26
27
  import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
27
28
  import { Select, SelectContent, SelectItem, SelectTrigger } from '../ui/select';
29
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
28
30
  import { cn } from '../../lib/utils';
29
31
  import type { AgentSchemaData } from '../../hooks/useAgentSchemas';
30
32
  import { useLogger } from '../../hooks/useLogger';
31
33
  import type { JSONSchema7 } from 'ai';
34
+ import { useWorkbench } from './WorkbenchProvider';
32
35
  import { convertJsonSchemaToZod } from 'zod-from-json-schema';
33
- import { zocker } from 'zocker';
34
36
 
35
37
  export interface InputSectionProps {
36
38
  value: string;
@@ -43,6 +45,7 @@ export interface InputSectionProps {
43
45
  suggestions: string[];
44
46
  isSchemaOpen: boolean;
45
47
  onSchemaToggle: () => void;
48
+ clearAgentState?: (agentId: string) => Promise<void>;
46
49
  }
47
50
 
48
51
  function isSchemaRootObject(schemaJson?: JSONSchema7): boolean {
@@ -68,8 +71,10 @@ export function InputSection({
68
71
  suggestions,
69
72
  isSchemaOpen,
70
73
  onSchemaToggle,
74
+ clearAgentState,
71
75
  }: InputSectionProps) {
72
76
  const logger = useLogger('InputSection');
77
+ const { generateSample, isGeneratingSample, isAuthenticated } = useWorkbench();
73
78
  const [agentSelectOpen, setAgentSelectOpen] = useState(false);
74
79
  const [isValidInput, setIsValidInput] = useState(true);
75
80
  const [monacoHasErrors, setMonacoHasErrors] = useState<boolean | null>(null);
@@ -159,18 +164,14 @@ export function InputSection({
159
164
  monacoHasErrors,
160
165
  ]);
161
166
 
162
- const handleGenerateSample = () => {
163
- if (!selectedAgentData?.schema.input?.json || !isObjectSchema) return;
167
+ const handleGenerateSample = async () => {
168
+ if (!selectedAgentData?.schema.input?.json || !isObjectSchema || !selectedAgent) return;
164
169
 
165
170
  try {
166
- const jsonSchema = selectedAgentData.schema.input.json;
167
- const schemaObject = typeof jsonSchema === 'string' ? JSON.parse(jsonSchema) : jsonSchema;
168
-
169
- const zodSchema = convertJsonSchemaToZod(schemaObject);
170
- const sampleData = zocker(zodSchema).generate();
171
- const sampleJson = JSON.stringify(sampleData, null, 2);
171
+ const sampleJson = await generateSample(selectedAgent);
172
172
  onChange(sampleJson);
173
173
  } catch (error) {
174
+ logger.error('Failed to generate sample JSON:', error);
174
175
  console.error('Failed to generate sample JSON:', error);
175
176
  }
176
177
  };
@@ -268,17 +269,49 @@ export function InputSection({
268
269
  </Select>
269
270
  )}
270
271
 
271
- {isObjectSchema && (
272
- <Button
273
- aria-label="Generate Sample JSON"
274
- size="sm"
275
- variant="outline"
276
- className="bg-none font-normal"
277
- onClick={handleGenerateSample}
278
- >
279
- <Sparkles className="size-4" /> Sample
280
- </Button>
281
- )}
272
+ {isObjectSchema &&
273
+ (isAuthenticated ? (
274
+ <Button
275
+ aria-label="Generate Sample JSON"
276
+ size="sm"
277
+ variant="outline"
278
+ className="bg-none font-normal"
279
+ onClick={handleGenerateSample}
280
+ disabled={isGeneratingSample || !isAuthenticated}
281
+ >
282
+ {isGeneratingSample ? (
283
+ <Loader2Icon className="size-4 animate-spin" />
284
+ ) : (
285
+ <Sparkles className="size-4" />
286
+ )}{' '}
287
+ Sample
288
+ </Button>
289
+ ) : (
290
+ <Tooltip>
291
+ <TooltipTrigger asChild>
292
+ <span className="inline-flex">
293
+ <Button
294
+ aria-label="Generate Sample JSON"
295
+ size="sm"
296
+ variant="outline"
297
+ className="bg-none font-normal"
298
+ onClick={handleGenerateSample}
299
+ disabled={isGeneratingSample || !isAuthenticated}
300
+ >
301
+ {isGeneratingSample ? (
302
+ <Loader2Icon className="size-4 animate-spin" />
303
+ ) : (
304
+ <Sparkles className="size-4" />
305
+ )}{' '}
306
+ Sample
307
+ </Button>
308
+ </span>
309
+ </TooltipTrigger>
310
+ <TooltipContent>
311
+ <p>Login to generate a sample</p>
312
+ </TooltipContent>
313
+ </Tooltip>
314
+ ))}
282
315
 
283
316
  <Button
284
317
  aria-label={isSchemaOpen ? 'Hide Schema' : 'View Schema'}
@@ -289,6 +322,18 @@ export function InputSection({
289
322
  >
290
323
  <FileJson className="size-4" /> Schema
291
324
  </Button>
325
+
326
+ {clearAgentState && selectedAgent && (
327
+ <Button
328
+ aria-label="Clear conversation history"
329
+ size="sm"
330
+ variant="outline"
331
+ className="bg-none font-normal text-muted-foreground hover:text-destructive"
332
+ onClick={() => clearAgentState(selectedAgent)}
333
+ >
334
+ <Trash2 className="size-4" /> Clear
335
+ </Button>
336
+ )}
292
337
  </div>
293
338
 
294
339
  <PromptInput onSubmit={onSubmit} className="px-3 pb-3">
@@ -167,7 +167,8 @@ export function MonacoJsonEditor({
167
167
  style={{ minHeight: '64px', maxHeight: '192px', height: `${editorHeight}px` }}
168
168
  >
169
169
  <Editor
170
- value={value || '{}'}
170
+ // Allow the editor to be truly empty. We intentionally do NOT coerce to `{}`.
171
+ value={value}
171
172
  onChange={(newValue) => onChange(newValue || '')}
172
173
  language="json"
173
174
  theme={resolvedTheme === 'light' ? 'custom-light' : 'custom-dark'}
@@ -243,6 +244,12 @@ export function MonacoJsonEditor({
243
244
  const checkValidationErrors = () => {
244
245
  const model = editor.getModel();
245
246
  if (model) {
247
+ // Treat an empty editor as a valid "empty state" (avoid Monaco's JSON parse error).
248
+ if (!model.getValue().trim()) {
249
+ onValidationChange(false);
250
+ return;
251
+ }
252
+
246
253
  const markers = monaco.editor.getModelMarkers({ resource: model.uri });
247
254
  const hasErrors = markers.some(
248
255
  (marker: monaco.editor.IMarker) =>
@@ -22,10 +22,11 @@ interface WorkbenchProviderProps {
22
22
  baseUrl?: string | null;
23
23
  projectId?: string;
24
24
  };
25
+ isAuthenticated: boolean;
25
26
  children: React.ReactNode;
26
27
  }
27
28
 
28
- export function WorkbenchProvider({ config, children }: WorkbenchProviderProps) {
29
+ export function WorkbenchProvider({ config, isAuthenticated, children }: WorkbenchProviderProps) {
29
30
  const logger = useLogger('WorkbenchProvider');
30
31
 
31
32
  // localStorage utilities scoped by project
@@ -54,10 +55,51 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
54
55
  }
55
56
  }, [getStorageKey]);
56
57
 
58
+ const saveThreadId = useCallback(
59
+ (threadId: string) => {
60
+ try {
61
+ localStorage.setItem(getStorageKey('thread_id'), threadId);
62
+ } catch (error) {
63
+ console.warn('Failed to save thread id to localStorage:', error);
64
+ }
65
+ },
66
+ [getStorageKey]
67
+ );
68
+
69
+ const loadThreadId = useCallback((): string | null => {
70
+ try {
71
+ return localStorage.getItem(getStorageKey('thread_id'));
72
+ } catch (error) {
73
+ console.warn('Failed to load thread id from localStorage:', error);
74
+ return null;
75
+ }
76
+ }, [getStorageKey]);
77
+
78
+ const applyThreadIdHeader = useCallback(
79
+ (headers: Record<string, string>) => {
80
+ const threadId = loadThreadId();
81
+ if (threadId) {
82
+ headers['x-thread-id'] = threadId;
83
+ }
84
+ },
85
+ [loadThreadId]
86
+ );
87
+
88
+ const persistThreadIdFromResponse = useCallback(
89
+ (response: Response) => {
90
+ const threadId = response.headers.get('x-thread-id');
91
+ if (threadId) {
92
+ saveThreadId(threadId);
93
+ }
94
+ },
95
+ [saveThreadId]
96
+ );
97
+
57
98
  const [messages, setMessages] = useState<UIMessage[]>([]);
58
99
  const [selectedAgent, setSelectedAgent] = useState<string>('');
59
100
  const [inputMode, setInputMode] = useState<'text' | 'form'>('text');
60
101
  const [isLoading, setIsLoading] = useState(false);
102
+ const [isGeneratingSample, setIsGeneratingSample] = useState(false);
61
103
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('connected'); // Default to connected when websocket is disabled
62
104
 
63
105
  // Config values
@@ -148,25 +190,111 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
148
190
 
149
191
  const [suggestions, _setSuggestions] = useState<string[]>([]);
150
192
 
193
+ // Fetch state for an agent
194
+ const fetchAgentState = useCallback(
195
+ async (agentId: string) => {
196
+ if (!baseUrl) {
197
+ logger.debug('⚠️ No baseUrl configured, skipping state fetch');
198
+ return;
199
+ }
200
+
201
+ if (!agentId) {
202
+ logger.debug('⚠️ No agentId provided, skipping state fetch');
203
+ return;
204
+ }
205
+
206
+ try {
207
+ const headers: Record<string, string> = {};
208
+ if (apiKey) {
209
+ headers.Authorization = `Bearer ${apiKey}`;
210
+ }
211
+ applyThreadIdHeader(headers);
212
+
213
+ const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
214
+ logger.debug('📡 Fetching state for agent:', agentId);
215
+ const response = await fetch(url, {
216
+ method: 'GET',
217
+ headers,
218
+ credentials: 'include',
219
+ });
220
+ persistThreadIdFromResponse(response);
221
+
222
+ if (response.ok) {
223
+ const data = await response.json();
224
+ const stateMessages = (data.messages || []) as Array<{
225
+ type: 'input' | 'output';
226
+ data: unknown;
227
+ }>;
228
+
229
+ // Convert state messages to UIMessage format
230
+ // Use stable IDs based on message index to prevent unnecessary re-renders
231
+ const uiMessages: UIMessage[] = stateMessages.map((msg, index) => {
232
+ const text =
233
+ typeof msg.data === 'object'
234
+ ? JSON.stringify(msg.data, null, 2)
235
+ : String(msg.data);
236
+ // Use stable ID based on index and a hash of content to maintain identity
237
+ const contentHash = text.substring(0, 20).replace(/[^a-zA-Z0-9]/g, '');
238
+ return {
239
+ id: `state_${agentId}_${index}_${contentHash}`,
240
+ role: msg.type === 'input' ? 'user' : 'assistant',
241
+ parts: [{ type: 'text', text }],
242
+ };
243
+ });
244
+
245
+ setMessages(uiMessages);
246
+ logger.debug('✅ Loaded state messages:', uiMessages.length);
247
+ } else {
248
+ logger.debug('⚠️ Failed to fetch state, starting with empty messages');
249
+ setMessages([]);
250
+ }
251
+ } catch (error) {
252
+ logger.debug('⚠️ Error fetching state:', error);
253
+ setMessages([]);
254
+ }
255
+ },
256
+ [baseUrl, apiKey, logger, applyThreadIdHeader, persistThreadIdFromResponse]
257
+ );
258
+
151
259
  // Set initial agent selection
152
260
  useEffect(() => {
153
261
  if (agents && Object.keys(agents).length > 0 && !selectedAgent) {
154
262
  logger.debug('🔍 Available agents:', agents);
155
263
 
156
- // Try to load previously selected agent from localStorage
157
- const savedAgentId = loadSelectedAgent();
158
- logger.debug('💾 Saved agent from localStorage:', savedAgentId);
264
+ // First, check for agent query parameter in URL
265
+ const urlParams = new URLSearchParams(window.location.search);
266
+ const agentFromUrl = urlParams.get('agent');
267
+ logger.debug('🔗 Agent from URL query param:', agentFromUrl);
268
+
269
+ // Try to find agent by URL param (matches agentId only)
270
+ let agentToSelect: string | null = null;
271
+ if (agentFromUrl) {
272
+ const matchedAgent = Object.values(agents).find(
273
+ (agent) => agent.metadata.agentId === agentFromUrl
274
+ );
275
+ if (matchedAgent) {
276
+ logger.debug('✅ Found agent from URL param:', matchedAgent.metadata.name);
277
+ agentToSelect = matchedAgent.metadata.agentId;
278
+ }
279
+ }
280
+
281
+ // If no URL param match, try localStorage
282
+ if (!agentToSelect) {
283
+ const savedAgentId = loadSelectedAgent();
284
+ logger.debug('💾 Saved agent from localStorage:', savedAgentId);
159
285
 
160
- // Check if saved agent still exists in available agents
161
- const savedAgent = savedAgentId
162
- ? Object.values(agents).find((agent) => agent.metadata.agentId === savedAgentId)
163
- : null;
286
+ const savedAgent = savedAgentId
287
+ ? Object.values(agents).find((agent) => agent.metadata.agentId === savedAgentId)
288
+ : null;
164
289
 
165
- if (savedAgent && savedAgentId) {
166
- logger.debug('✅ Restoring saved agent:', savedAgent.metadata.name);
167
- setSelectedAgent(savedAgentId);
168
- } else {
169
- // Fallback to first agent alphabetically
290
+ if (savedAgent && savedAgentId) {
291
+ logger.debug('✅ Restoring saved agent:', savedAgent.metadata.name);
292
+ agentToSelect = savedAgentId;
293
+ }
294
+ }
295
+
296
+ // Fallback to first agent alphabetically
297
+ if (!agentToSelect) {
170
298
  const sortedAgents = Object.values(agents).sort((a, b) =>
171
299
  a.metadata.name.localeCompare(b.metadata.name)
172
300
  );
@@ -175,13 +303,15 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
175
303
  '🎯 No saved agent found, using first agent (alphabetically):',
176
304
  firstAgent
177
305
  );
178
- logger.debug('🆔 Setting selectedAgent to:', firstAgent.metadata.agentId);
179
- setSelectedAgent(firstAgent.metadata.agentId);
180
- // Save this selection for next time
181
- saveSelectedAgent(firstAgent.metadata.agentId);
306
+ agentToSelect = firstAgent.metadata.agentId;
182
307
  }
308
+
309
+ logger.debug('🆔 Setting selectedAgent to:', agentToSelect);
310
+ setSelectedAgent(agentToSelect);
311
+ saveSelectedAgent(agentToSelect);
312
+ fetchAgentState(agentToSelect);
183
313
  }
184
- }, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger]);
314
+ }, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger, fetchAgentState]);
185
315
 
186
316
  // Fetch suggestions from API if endpoint is provided
187
317
  useEffect(() => {
@@ -212,23 +342,33 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
212
342
  logger.debug('✅ Validation passed, continuing with message submission...');
213
343
 
214
344
  // Add user message
345
+ // Note: We also add a placeholder assistant message so only the last message
346
+ // shows a loading state while the request is in-flight.
347
+ const now = Date.now();
215
348
  const displayText = hasInputSchema
216
349
  ? value
217
350
  : `Running ${selectedAgentData?.metadata.name || 'agent'}...`;
218
351
  const userMessage: UIMessage = {
219
- id: Date.now().toString(),
352
+ id: now.toString(),
220
353
  role: 'user',
221
354
  parts: [{ type: 'text', text: displayText }],
222
355
  };
223
356
 
224
- setMessages((prev) => [...prev, userMessage]);
357
+ const assistantMessageId = (now + 1).toString();
358
+ const placeholderAssistantMessage: UIMessage = {
359
+ id: assistantMessageId,
360
+ role: 'assistant',
361
+ parts: [{ type: 'text', text: '', state: 'streaming' }],
362
+ };
363
+
364
+ setMessages((prev) => [...prev, userMessage, placeholderAssistantMessage]);
225
365
  setIsLoading(true);
226
366
 
227
367
  logger.debug('🔗 baseUrl:', baseUrl, 'isBaseUrlNull:', isBaseUrlNull);
228
368
  if (!baseUrl || isBaseUrlNull) {
229
369
  logger.debug('❌ Message submission blocked - baseUrl is null or missing');
230
370
  const errorMessage: UIMessage = {
231
- id: (Date.now() + 1).toString(),
371
+ id: assistantMessageId,
232
372
  role: 'assistant',
233
373
  parts: [
234
374
  {
@@ -237,7 +377,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
237
377
  },
238
378
  ],
239
379
  };
240
- setMessages((prev) => [...prev, errorMessage]);
380
+ setMessages((prev) => prev.map((m) => (m.id === assistantMessageId ? errorMessage : m)));
241
381
  setIsLoading(false);
242
382
  return;
243
383
  }
@@ -264,6 +404,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
264
404
  if (apiKey) {
265
405
  headers.Authorization = `Bearer ${apiKey}`;
266
406
  }
407
+ applyThreadIdHeader(headers);
267
408
 
268
409
  const controller = new AbortController();
269
410
  const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
@@ -281,7 +422,9 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
281
422
  headers,
282
423
  body: JSON.stringify(requestPayload),
283
424
  signal: controller.signal,
425
+ credentials: 'include',
284
426
  });
427
+ persistThreadIdFromResponse(response);
285
428
  clearTimeout(timeoutId);
286
429
 
287
430
  if (!response.ok) {
@@ -320,14 +463,16 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
320
463
  typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
321
464
 
322
465
  const assistantMessage: UIMessage & { tokens?: string; duration?: string } = {
323
- id: (Date.now() + 1).toString(),
466
+ id: assistantMessageId,
324
467
  role: 'assistant',
325
468
  parts: [{ type: 'text', text: resultText }],
326
469
  tokens: totalTokens?.toString(),
327
470
  duration,
328
471
  };
329
472
 
330
- setMessages((prev) => [...prev, assistantMessage]);
473
+ setMessages((prev) =>
474
+ prev.map((m) => (m.id === assistantMessageId ? assistantMessage : m))
475
+ );
331
476
  } catch (fetchError) {
332
477
  clearTimeout(timeoutId);
333
478
  throw fetchError;
@@ -342,7 +487,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
342
487
  : 'Sorry, I encountered an error processing your message.';
343
488
 
344
489
  const errorMessage: UIMessage = {
345
- id: (Date.now() + 1).toString(),
490
+ id: assistantMessageId,
346
491
  role: 'assistant',
347
492
  parts: [
348
493
  {
@@ -351,19 +496,103 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
351
496
  },
352
497
  ],
353
498
  };
354
- setMessages((prev) => [...prev, errorMessage]);
499
+ setMessages((prev) => prev.map((m) => (m.id === assistantMessageId ? errorMessage : m)));
355
500
  } finally {
356
501
  setIsLoading(false);
357
502
  }
358
503
  };
359
504
 
505
+ const generateSample = async (agentId: string): Promise<string> => {
506
+ if (!baseUrl || isBaseUrlNull) {
507
+ throw new Error('Base URL not configured');
508
+ }
509
+
510
+ setIsGeneratingSample(true);
511
+ try {
512
+ const url = `${baseUrl}/_agentuity/workbench/sample?agentId=${encodeURIComponent(agentId)}`;
513
+ const headers: HeadersInit = {
514
+ 'Content-Type': 'application/json',
515
+ };
516
+
517
+ if (apiKey) {
518
+ headers['Authorization'] = `Bearer ${apiKey}`;
519
+ }
520
+ // Keep thread id stable across workbench endpoints.
521
+ if (typeof headers === 'object' && headers && !Array.isArray(headers)) {
522
+ applyThreadIdHeader(headers as Record<string, string>);
523
+ }
524
+
525
+ const response = await fetch(url, {
526
+ method: 'GET',
527
+ headers,
528
+ credentials: 'include',
529
+ });
530
+ persistThreadIdFromResponse(response);
531
+
532
+ if (!response.ok) {
533
+ let errorMessage = `Request failed with status ${response.status}`;
534
+ try {
535
+ const errorData = await response.json();
536
+ errorMessage = errorData.error || errorData.message || errorMessage;
537
+ } catch {
538
+ errorMessage = response.statusText || errorMessage;
539
+ }
540
+ throw new Error(errorMessage);
541
+ }
542
+
543
+ const sample = await response.json();
544
+ return JSON.stringify(sample, null, 2);
545
+ } catch (error) {
546
+ logger.error('Failed to generate sample JSON:', error);
547
+ throw error;
548
+ } finally {
549
+ setIsGeneratingSample(false);
550
+ }
551
+ };
552
+
360
553
  const handleAgentSelect = async (agentId: string) => {
361
554
  logger.debug('🔄 handleAgentSelect called with:', agentId);
362
555
  setSelectedAgent(agentId);
363
556
  // Save selection to localStorage for persistence across sessions
364
557
  saveSelectedAgent(agentId);
558
+ // Fetch state for the selected agent
559
+ await fetchAgentState(agentId);
365
560
  };
366
561
 
562
+ const clearAgentState = useCallback(
563
+ async (agentId: string) => {
564
+ if (!baseUrl) {
565
+ return;
566
+ }
567
+
568
+ try {
569
+ const headers: Record<string, string> = {};
570
+ if (apiKey) {
571
+ headers.Authorization = `Bearer ${apiKey}`;
572
+ }
573
+ applyThreadIdHeader(headers);
574
+
575
+ const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
576
+ const response = await fetch(url, {
577
+ method: 'DELETE',
578
+ headers,
579
+ credentials: 'include',
580
+ });
581
+ persistThreadIdFromResponse(response);
582
+
583
+ if (response.ok) {
584
+ setMessages([]);
585
+ logger.debug('✅ Cleared state for agent:', agentId);
586
+ } else {
587
+ logger.debug('⚠️ Failed to clear state');
588
+ }
589
+ } catch (error) {
590
+ logger.debug('⚠️ Error clearing state:', error);
591
+ }
592
+ },
593
+ [baseUrl, apiKey, logger, applyThreadIdHeader, persistThreadIdFromResponse]
594
+ );
595
+
367
596
  const contextValue: WorkbenchContextType = {
368
597
  config,
369
598
  agents: agents || {},
@@ -376,6 +605,9 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
376
605
  setInputMode,
377
606
  isLoading: isLoading || !!schemasLoading,
378
607
  submitMessage,
608
+ generateSample,
609
+ isGeneratingSample,
610
+ isAuthenticated,
379
611
  // Schema data from API
380
612
  schemas: schemaData,
381
613
  schemasLoading: !!schemasLoading,
@@ -383,6 +615,8 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
383
615
  refetchSchemas,
384
616
  // Connection status
385
617
  connectionStatus,
618
+ // Clear agent state
619
+ clearAgentState,
386
620
  };
387
621
 
388
622
  return <WorkbenchContext.Provider value={contextValue}>{children}</WorkbenchContext.Provider>;
package/src/lib/utils.ts CHANGED
@@ -40,12 +40,13 @@ export function getTotalTokens(tokens: Record<string, number>): number {
40
40
  }
41
41
 
42
42
  export const getProcessEnv = (key: string): string | undefined => {
43
- if (typeof process !== 'undefined' && process.env) {
44
- return process.env[key];
45
- }
43
+ // Prioritize import.meta.env for browser/Vite environments
46
44
  if (typeof import.meta.env !== 'undefined') {
47
45
  return import.meta.env[key];
48
46
  }
47
+ if (typeof process !== 'undefined' && process.env) {
48
+ return process.env[key];
49
+ }
49
50
  return undefined;
50
51
  };
51
52
 
@@ -24,6 +24,9 @@ export interface WorkbenchContextType {
24
24
  setInputMode: (mode: 'text' | 'form') => void;
25
25
  isLoading: boolean;
26
26
  submitMessage: (value: string, mode?: 'text' | 'form') => Promise<void>;
27
+ generateSample: (agentId: string) => Promise<string>;
28
+ isGeneratingSample: boolean;
29
+ isAuthenticated: boolean;
27
30
  // Schema data from API
28
31
  schemas: AgentSchemasResponse | null;
29
32
  schemasLoading: boolean;
@@ -31,4 +34,6 @@ export interface WorkbenchContextType {
31
34
  refetchSchemas: () => void;
32
35
  // Connection status
33
36
  connectionStatus: ConnectionStatus;
37
+ // Clear agent state
38
+ clearAgentState: (agentId: string) => Promise<void>;
34
39
  }