@agentuity/workbench 0.0.93 → 0.0.95

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 (45) 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 +3 -1
  5. package/dist/components/internal/Chat.d.ts.map +1 -1
  6. package/dist/components/internal/Chat.js +5 -5
  7. package/dist/components/internal/Chat.js.map +1 -1
  8. package/dist/components/internal/Header.d.ts +4 -0
  9. package/dist/components/internal/Header.d.ts.map +1 -1
  10. package/dist/components/internal/Header.js +1 -1
  11. package/dist/components/internal/Header.js.map +1 -1
  12. package/dist/components/internal/InputSection.d.ts.map +1 -1
  13. package/dist/components/internal/InputSection.js +10 -10
  14. package/dist/components/internal/InputSection.js.map +1 -1
  15. package/dist/components/internal/MonacoJsonEditor.d.ts.map +1 -1
  16. package/dist/components/internal/MonacoJsonEditor.js +10 -1
  17. package/dist/components/internal/MonacoJsonEditor.js.map +1 -1
  18. package/dist/components/internal/WorkbenchProvider.d.ts +6 -2
  19. package/dist/components/internal/WorkbenchProvider.d.ts.map +1 -1
  20. package/dist/components/internal/WorkbenchProvider.js +117 -46
  21. package/dist/components/internal/WorkbenchProvider.js.map +1 -1
  22. package/dist/hooks/useAgentSchemas.d.ts.map +1 -1
  23. package/dist/hooks/useAgentSchemas.js +17 -4
  24. package/dist/hooks/useAgentSchemas.js.map +1 -1
  25. package/dist/hooks/useWorkbenchWebsocket.d.ts +1 -0
  26. package/dist/hooks/useWorkbenchWebsocket.d.ts.map +1 -1
  27. package/dist/hooks/useWorkbenchWebsocket.js +1 -1
  28. package/dist/hooks/useWorkbenchWebsocket.js.map +1 -1
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/types/config.d.ts +3 -0
  34. package/dist/types/config.d.ts.map +1 -1
  35. package/package.json +3 -4
  36. package/src/components/App.tsx +9 -4
  37. package/src/components/internal/Chat.tsx +100 -94
  38. package/src/components/internal/Header.tsx +1 -1
  39. package/src/components/internal/InputSection.tsx +107 -69
  40. package/src/components/internal/MonacoJsonEditor.tsx +8 -1
  41. package/src/components/internal/WorkbenchProvider.tsx +132 -50
  42. package/src/hooks/useAgentSchemas.ts +16 -4
  43. package/src/hooks/useWorkbenchWebsocket.ts +2 -1
  44. package/src/index.ts +2 -1
  45. package/src/types/config.ts +3 -0
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useState, useMemo, useCallback } from 'react';
1
+ import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
2
2
  import type { UIMessage } from 'ai';
3
3
  import type { WorkbenchConfig } from '@agentuity/core/workbench';
4
4
  import type { WorkbenchContextType, ConnectionStatus } from '../../types/config';
@@ -18,29 +18,22 @@ export function useWorkbench() {
18
18
  }
19
19
 
20
20
  interface WorkbenchProviderProps {
21
- config: WorkbenchConfig;
21
+ config: Omit<WorkbenchConfig, 'route'> & {
22
+ baseUrl?: string | null;
23
+ projectId?: string;
24
+ };
25
+ isAuthenticated: boolean;
22
26
  children: React.ReactNode;
23
27
  }
24
28
 
25
- export function WorkbenchProvider({ config, children }: WorkbenchProviderProps) {
29
+ export function WorkbenchProvider({ config, isAuthenticated, children }: WorkbenchProviderProps) {
26
30
  const logger = useLogger('WorkbenchProvider');
27
31
 
28
- // Generate project identifier from config for localStorage scoping
29
- const projectId = useMemo(() => {
30
- // Use a combination of baseUrl and apiKey hash to create unique project identifier
31
- const configHash = btoa(
32
- JSON.stringify({
33
- route: config.route,
34
- apiKey: config.apiKey?.substring(0, 8), // Only first 8 chars for uniqueness without exposing key
35
- })
36
- )
37
- .replace(/[^a-zA-Z0-9]/g, '')
38
- .substring(0, 16);
39
- return `project_${configHash}`;
40
- }, [config]);
41
-
42
32
  // localStorage utilities scoped by project
43
- const getStorageKey = useCallback((key: string) => `agentuity_${projectId}_${key}`, [projectId]);
33
+ const getStorageKey = useCallback(
34
+ (key: string) => `agentuity_workbench_${config.projectId}_${key}`,
35
+ [config.projectId]
36
+ );
44
37
 
45
38
  const saveSelectedAgent = useCallback(
46
39
  (agentId: string) => {
@@ -66,22 +59,36 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
66
59
  const [selectedAgent, setSelectedAgent] = useState<string>('');
67
60
  const [inputMode, setInputMode] = useState<'text' | 'form'>('text');
68
61
  const [isLoading, setIsLoading] = useState(false);
69
- const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('disconnected');
62
+ const [isGeneratingSample, setIsGeneratingSample] = useState(false);
63
+ const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('connected'); // Default to connected when websocket is disabled
70
64
 
71
65
  // Config values
72
- const baseUrl = config.baseUrl ?? defaultBaseUrl;
66
+ const baseUrl = config.baseUrl === undefined ? defaultBaseUrl : config.baseUrl;
73
67
  const apiKey = config.apiKey;
74
- const shouldUseSchemas = true;
68
+ const isBaseUrlNull = config.baseUrl === null;
75
69
 
76
- // Debug logging
70
+ // Log baseUrl state
77
71
  useEffect(() => {
78
- if (process.env.NODE_ENV === 'development') {
79
- console.log('WorkbenchProvider Debug:', {
80
- baseUrl,
81
- shouldUseSchemas,
82
- });
72
+ if (isBaseUrlNull) {
73
+ logger.debug('🚫 baseUrl is null - disabling API calls and websocket');
74
+ } else {
75
+ logger.debug('✅ baseUrl configured:', baseUrl);
76
+ }
77
+ }, [isBaseUrlNull, baseUrl, logger]);
78
+
79
+ // Set disconnected status if baseUrl is null
80
+ useEffect(() => {
81
+ if (isBaseUrlNull) {
82
+ logger.debug('🔌 Setting connection status to disconnected (baseUrl is null)');
83
+ setConnectionStatus('disconnected');
84
+ }
85
+ }, [isBaseUrlNull, logger]);
86
+
87
+ useEffect(() => {
88
+ if (isBaseUrlNull) {
89
+ logger.debug('📋 Schema fetching disabled (baseUrl is null)');
83
90
  }
84
- }, [baseUrl, shouldUseSchemas]);
91
+ }, [isBaseUrlNull, logger]);
85
92
 
86
93
  const {
87
94
  data: schemaData,
@@ -91,12 +98,20 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
91
98
  } = useAgentSchemas({
92
99
  baseUrl,
93
100
  apiKey,
94
- enabled: shouldUseSchemas,
101
+ enabled: !isBaseUrlNull,
95
102
  });
96
103
 
97
104
  // WebSocket connection for dev server restart detection
105
+ const wsBaseUrl = isBaseUrlNull ? undefined : baseUrl;
106
+ useEffect(() => {
107
+ if (isBaseUrlNull) {
108
+ logger.debug('🔌 WebSocket connection disabled (baseUrl is null)');
109
+ }
110
+ }, [isBaseUrlNull, logger]);
111
+
98
112
  const { connected } = useWorkbenchWebsocket({
99
- baseUrl,
113
+ enabled: !isBaseUrlNull,
114
+ baseUrl: wsBaseUrl,
100
115
  apiKey,
101
116
  onConnect: () => {
102
117
  setConnectionStatus('connected');
@@ -115,12 +130,11 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
115
130
  },
116
131
  });
117
132
 
118
- // Update connection status based on WebSocket connection state
119
133
  useEffect(() => {
120
- if (!connected && connectionStatus !== 'restarting') {
134
+ if (!isBaseUrlNull && !connected && connectionStatus !== 'restarting') {
121
135
  setConnectionStatus('disconnected');
122
136
  }
123
- }, [connected, connectionStatus]);
137
+ }, [connected, connectionStatus, isBaseUrlNull]);
124
138
 
125
139
  // Convert schema data to Agent format, no fallback
126
140
  const agents = schemaData?.agents;
@@ -200,23 +214,33 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
200
214
  logger.debug('✅ Validation passed, continuing with message submission...');
201
215
 
202
216
  // Add user message
217
+ // Note: We also add a placeholder assistant message so only the last message
218
+ // shows a loading state while the request is in-flight.
219
+ const now = Date.now();
203
220
  const displayText = hasInputSchema
204
221
  ? value
205
222
  : `Running ${selectedAgentData?.metadata.name || 'agent'}...`;
206
223
  const userMessage: UIMessage = {
207
- id: Date.now().toString(),
224
+ id: now.toString(),
208
225
  role: 'user',
209
226
  parts: [{ type: 'text', text: displayText }],
210
227
  };
211
228
 
212
- setMessages((prev) => [...prev, userMessage]);
229
+ const assistantMessageId = (now + 1).toString();
230
+ const placeholderAssistantMessage: UIMessage = {
231
+ id: assistantMessageId,
232
+ role: 'assistant',
233
+ parts: [{ type: 'text', text: '', state: 'streaming' }],
234
+ };
235
+
236
+ setMessages((prev) => [...prev, userMessage, placeholderAssistantMessage]);
213
237
  setIsLoading(true);
214
238
 
215
- logger.debug('🔗 baseUrl:', baseUrl);
216
- if (!baseUrl) {
217
- logger.debug('❌ No baseUrl configured!');
239
+ logger.debug('🔗 baseUrl:', baseUrl, 'isBaseUrlNull:', isBaseUrlNull);
240
+ if (!baseUrl || isBaseUrlNull) {
241
+ logger.debug('❌ Message submission blocked - baseUrl is null or missing');
218
242
  const errorMessage: UIMessage = {
219
- id: (Date.now() + 1).toString(),
243
+ id: assistantMessageId,
220
244
  role: 'assistant',
221
245
  parts: [
222
246
  {
@@ -225,7 +249,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
225
249
  },
226
250
  ],
227
251
  };
228
- setMessages((prev) => [...prev, errorMessage]);
252
+ setMessages((prev) => prev.map((m) => (m.id === assistantMessageId ? errorMessage : m)));
229
253
  setIsLoading(false);
230
254
  return;
231
255
  }
@@ -273,13 +297,24 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
273
297
  clearTimeout(timeoutId);
274
298
 
275
299
  if (!response.ok) {
276
- const errorData = await response
277
- .json()
278
- .catch(() => ({ error: response.statusText }));
279
- throw new Error(errorData.error || `Request failed with status ${response.status}`);
300
+ let errorMessage = `Request failed with status ${response.status}`;
301
+ try {
302
+ const errorData = await response.json();
303
+ errorMessage = errorData.error || errorData.message || errorMessage;
304
+ } catch {
305
+ // If JSON parsing fails, use status text
306
+ errorMessage = response.statusText || errorMessage;
307
+ }
308
+ throw new Error(errorMessage);
309
+ }
310
+
311
+ let result;
312
+ try {
313
+ result = await response.json();
314
+ } catch (jsonError) {
315
+ throw new Error(`Invalid JSON response from server: ${jsonError}`);
280
316
  }
281
317
 
282
- const result = await response.json();
283
318
  const endTime = performance.now();
284
319
  const clientDuration = ((endTime - startTime) / 1000).toFixed(1); // Duration in seconds
285
320
 
@@ -297,14 +332,16 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
297
332
  typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
298
333
 
299
334
  const assistantMessage: UIMessage & { tokens?: string; duration?: string } = {
300
- id: (Date.now() + 1).toString(),
335
+ id: assistantMessageId,
301
336
  role: 'assistant',
302
337
  parts: [{ type: 'text', text: resultText }],
303
338
  tokens: totalTokens?.toString(),
304
339
  duration,
305
340
  };
306
341
 
307
- setMessages((prev) => [...prev, assistantMessage]);
342
+ setMessages((prev) =>
343
+ prev.map((m) => (m.id === assistantMessageId ? assistantMessage : m))
344
+ );
308
345
  } catch (fetchError) {
309
346
  clearTimeout(timeoutId);
310
347
  throw fetchError;
@@ -319,7 +356,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
319
356
  : 'Sorry, I encountered an error processing your message.';
320
357
 
321
358
  const errorMessage: UIMessage = {
322
- id: (Date.now() + 1).toString(),
359
+ id: assistantMessageId,
323
360
  role: 'assistant',
324
361
  parts: [
325
362
  {
@@ -328,12 +365,54 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
328
365
  },
329
366
  ],
330
367
  };
331
- setMessages((prev) => [...prev, errorMessage]);
368
+ setMessages((prev) => prev.map((m) => (m.id === assistantMessageId ? errorMessage : m)));
332
369
  } finally {
333
370
  setIsLoading(false);
334
371
  }
335
372
  };
336
373
 
374
+ const generateSample = async (agentId: string): Promise<string> => {
375
+ if (!baseUrl || isBaseUrlNull) {
376
+ throw new Error('Base URL not configured');
377
+ }
378
+
379
+ setIsGeneratingSample(true);
380
+ try {
381
+ const url = `${baseUrl}/_agentuity/workbench/sample?agentId=${encodeURIComponent(agentId)}`;
382
+ const headers: HeadersInit = {
383
+ 'Content-Type': 'application/json',
384
+ };
385
+
386
+ if (apiKey) {
387
+ headers['Authorization'] = `Bearer ${apiKey}`;
388
+ }
389
+
390
+ const response = await fetch(url, {
391
+ method: 'GET',
392
+ headers,
393
+ });
394
+
395
+ if (!response.ok) {
396
+ let errorMessage = `Request failed with status ${response.status}`;
397
+ try {
398
+ const errorData = await response.json();
399
+ errorMessage = errorData.error || errorData.message || errorMessage;
400
+ } catch {
401
+ errorMessage = response.statusText || errorMessage;
402
+ }
403
+ throw new Error(errorMessage);
404
+ }
405
+
406
+ const sample = await response.json();
407
+ return JSON.stringify(sample, null, 2);
408
+ } catch (error) {
409
+ logger.error('Failed to generate sample JSON:', error);
410
+ throw error;
411
+ } finally {
412
+ setIsGeneratingSample(false);
413
+ }
414
+ };
415
+
337
416
  const handleAgentSelect = async (agentId: string) => {
338
417
  logger.debug('🔄 handleAgentSelect called with:', agentId);
339
418
  setSelectedAgent(agentId);
@@ -351,8 +430,11 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
351
430
  setSelectedAgent: handleAgentSelect,
352
431
  inputMode,
353
432
  setInputMode,
354
- isLoading: isLoading || (shouldUseSchemas && !!schemasLoading),
433
+ isLoading: isLoading || !!schemasLoading,
355
434
  submitMessage,
435
+ generateSample,
436
+ isGeneratingSample,
437
+ isAuthenticated,
356
438
  // Schema data from API
357
439
  schemas: schemaData,
358
440
  schemasLoading: !!schemasLoading,
@@ -93,14 +93,26 @@ export function useAgentSchemas(options: UseAgentSchemasOptions = {}): UseAgentS
93
93
  });
94
94
 
95
95
  if (!response.ok) {
96
+ // Handle 404/500 gracefully without throwing
96
97
  if (response.status === 401) {
97
- throw new Error('Unauthorized: Invalid or missing API key');
98
+ setError(new Error('Unauthorized: Invalid or missing API key'));
99
+ return;
98
100
  }
99
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
101
+ if (response.status === 404 || response.status >= 500) {
102
+ setError(new Error(`Server error: ${response.status} ${response.statusText}`));
103
+ return;
104
+ }
105
+ setError(new Error(`HTTP ${response.status}: ${response.statusText}`));
106
+ return;
100
107
  }
101
108
 
102
- const result = (await response.json()) as AgentSchemasResponse;
103
- setData(result);
109
+ try {
110
+ const result = (await response.json()) as AgentSchemasResponse;
111
+ setData(result);
112
+ } catch (jsonError) {
113
+ setError(new Error('Invalid JSON response from server'));
114
+ console.error('Failed to parse JSON response:', jsonError);
115
+ }
104
116
  } catch (err) {
105
117
  const error = err instanceof Error ? err : new Error('Unknown error occurred');
106
118
  setError(error);
@@ -77,6 +77,7 @@ function createReconnectManager(opts: ReconnectOptions): ReconnectManager {
77
77
  export interface UseWorkbenchWebsocketOptions {
78
78
  baseUrl?: string;
79
79
  apiKey?: string;
80
+ enabled?: boolean;
80
81
  onConnect?: () => void;
81
82
  onReconnect?: () => void;
82
83
  onAlive?: () => void;
@@ -126,7 +127,7 @@ export function useWorkbenchWebsocket(
126
127
  }, [baseUrl, apiKey]);
127
128
 
128
129
  const connect = useCallback(() => {
129
- if (manualClose.current) return;
130
+ if (manualClose.current || !options.enabled) return;
130
131
  const url = wsUrl();
131
132
  if (!url) return;
132
133
 
package/src/index.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  // Export types
2
2
  export type { WorkbenchInstance } from './types';
3
+ export type { ConnectionStatus } from './types/config';
3
4
 
4
5
  // Export components
5
6
  export { default as App } from './components/App';
6
7
  export { Chat } from './components/internal/Chat';
7
8
  export { Schema } from './components/internal/Schema';
9
+ export { StatusIndicator } from './components/internal/Header';
8
10
  export { WorkbenchProvider, useWorkbench } from './components/internal/WorkbenchProvider';
9
-
10
11
  // Export workbench functions
11
12
  export { createWorkbench } from './workbench';
@@ -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;