@agentuity/workbench 0.0.79 → 0.0.84

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 (53) hide show
  1. package/dist/components/internal/Chat.d.ts.map +1 -1
  2. package/dist/components/internal/Chat.js +6 -1
  3. package/dist/components/internal/Chat.js.map +1 -1
  4. package/dist/components/internal/InputSection.d.ts.map +1 -1
  5. package/dist/components/internal/InputSection.js +29 -151
  6. package/dist/components/internal/InputSection.js.map +1 -1
  7. package/dist/components/internal/MonacoJsonEditor.d.ts +11 -0
  8. package/dist/components/internal/MonacoJsonEditor.d.ts.map +1 -0
  9. package/dist/components/internal/MonacoJsonEditor.js +270 -0
  10. package/dist/components/internal/MonacoJsonEditor.js.map +1 -0
  11. package/dist/components/internal/Schema.d.ts.map +1 -1
  12. package/dist/components/internal/Schema.js +1 -1
  13. package/dist/components/internal/Schema.js.map +1 -1
  14. package/dist/components/internal/WorkbenchProvider.d.ts.map +1 -1
  15. package/dist/components/internal/WorkbenchProvider.js +30 -9
  16. package/dist/components/internal/WorkbenchProvider.js.map +1 -1
  17. package/dist/components/ui/field.d.ts +1 -1
  18. package/dist/components.d.ts +1 -0
  19. package/dist/components.d.ts.map +1 -1
  20. package/dist/components.js +1 -0
  21. package/dist/components.js.map +1 -1
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.d.ts.map +1 -1
  24. package/dist/hooks/index.js +1 -0
  25. package/dist/hooks/index.js.map +1 -1
  26. package/dist/hooks/useAgentSchemas.d.ts +1 -0
  27. package/dist/hooks/useAgentSchemas.d.ts.map +1 -1
  28. package/dist/hooks/useAgentSchemas.js.map +1 -1
  29. package/dist/hooks/useLogger.d.ts +9 -0
  30. package/dist/hooks/useLogger.d.ts.map +1 -0
  31. package/dist/hooks/useLogger.js +41 -0
  32. package/dist/hooks/useLogger.js.map +1 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/lib/utils.d.ts +3 -0
  38. package/dist/lib/utils.d.ts.map +1 -1
  39. package/dist/lib/utils.js +31 -0
  40. package/dist/lib/utils.js.map +1 -1
  41. package/dist/styles.css +60 -0
  42. package/package.json +10 -5
  43. package/src/components/internal/Chat.tsx +8 -1
  44. package/src/components/internal/InputSection.tsx +71 -193
  45. package/src/components/internal/MonacoJsonEditor.tsx +359 -0
  46. package/src/components/internal/Schema.tsx +2 -1
  47. package/src/components/internal/WorkbenchProvider.tsx +35 -9
  48. package/src/components.tsx +1 -0
  49. package/src/hooks/index.ts +1 -0
  50. package/src/hooks/useAgentSchemas.ts +1 -0
  51. package/src/hooks/useLogger.ts +57 -0
  52. package/src/index.ts +1 -0
  53. package/src/lib/utils.ts +41 -0
@@ -0,0 +1,359 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import Editor, { type Monaco, type OnMount } from '@monaco-editor/react';
3
+ import { useTheme } from '../ui/theme-provider';
4
+ import { bundledThemes } from 'shiki';
5
+ import type { JSONSchema7 } from 'ai';
6
+
7
+ interface MonacoJsonEditorProps {
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ schema?: JSONSchema7;
11
+ schemaUri?: string;
12
+ className?: string;
13
+ }
14
+
15
+ // Convert color value to valid hex for Monaco
16
+ function normalizeColorForMonaco(color: string | undefined, isDark: boolean): string {
17
+ if (!color) return isDark ? 'abb2bf' : '383a42'; // Default foreground colors
18
+
19
+ // Remove # prefix if present
20
+ let normalized = color.replace('#', '');
21
+
22
+ // Handle common color names that might appear in themes
23
+ const colorMap: Record<string, string> = {
24
+ white: isDark ? 'ffffff' : '383a42',
25
+ black: isDark ? '000000' : 'abb2bf',
26
+ red: 'e45649',
27
+ green: '50a14f',
28
+ blue: '4078f2',
29
+ yellow: '986801',
30
+ cyan: '0184bc',
31
+ magenta: 'a626a4',
32
+ };
33
+
34
+ if (colorMap[normalized.toLowerCase()]) {
35
+ normalized = colorMap[normalized.toLowerCase()];
36
+ }
37
+
38
+ // Validate it's a proper hex color (3 or 6 characters)
39
+ if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$/.test(normalized)) {
40
+ return isDark ? 'abb2bf' : '383a42'; // Fallback to default
41
+ }
42
+
43
+ return normalized;
44
+ }
45
+
46
+ // Convert Shiki theme to Monaco theme
47
+ function convertShikiToMonaco(
48
+ shikiTheme: {
49
+ colors?: Record<string, string>;
50
+ tokenColors?: Array<{
51
+ scope?: string | string[];
52
+ settings?: { foreground?: string; fontStyle?: string };
53
+ }>;
54
+ },
55
+ themeName: string
56
+ ) {
57
+ const colors = shikiTheme.colors || {};
58
+ const tokenColors = shikiTheme.tokenColors || [];
59
+ const isDark = themeName.includes('dark');
60
+
61
+ // Convert token colors to Monaco rules
62
+ const rules: Array<{ token: string; foreground: string; fontStyle?: string }> = [];
63
+ tokenColors.forEach((tokenColor) => {
64
+ if (tokenColor.scope && tokenColor.settings?.foreground) {
65
+ const scopes = Array.isArray(tokenColor.scope) ? tokenColor.scope : [tokenColor.scope];
66
+ scopes.forEach((scope: string) => {
67
+ // Map common scopes to Monaco tokens
68
+ let token = scope;
69
+ if (scope.includes('string.quoted.double.json')) token = 'string.value.json';
70
+ if (scope.includes('support.type.property-name.json')) token = 'string.key.json';
71
+ if (scope.includes('constant.numeric.json')) token = 'number.json';
72
+ if (scope.includes('constant.language.json')) token = 'keyword.json';
73
+ if (scope.includes('punctuation.definition.string.json'))
74
+ token = 'delimiter.bracket.json';
75
+
76
+ const normalizedColor = normalizeColorForMonaco(
77
+ tokenColor.settings?.foreground,
78
+ isDark
79
+ );
80
+
81
+ rules.push({
82
+ token,
83
+ foreground: normalizedColor,
84
+ fontStyle: tokenColor.settings?.fontStyle || undefined,
85
+ });
86
+ });
87
+ }
88
+ });
89
+
90
+ return {
91
+ base: isDark ? 'vs-dark' : 'vs',
92
+ inherit: true,
93
+ rules,
94
+ colors: {
95
+ 'editor.background': '#00000000', // Always transparent
96
+ 'editor.foreground': normalizeColorForMonaco(colors['editor.foreground'], isDark),
97
+ },
98
+ };
99
+ }
100
+
101
+ export function MonacoJsonEditor({
102
+ value,
103
+ onChange,
104
+ schema,
105
+ schemaUri = 'agentuity://schema/default',
106
+ className = '',
107
+ }: MonacoJsonEditorProps) {
108
+ const { theme } = useTheme();
109
+ const [editorInstance, setEditorInstance] = useState<Parameters<OnMount>[0] | null>(null);
110
+ const [monacoInstance, setMonacoInstance] = useState<Monaco | null>(null);
111
+ const [editorHeight, setEditorHeight] = useState(120);
112
+
113
+ // Get resolved theme (similar to useTheme's resolvedTheme from next-themes)
114
+ const resolvedTheme = React.useMemo(() => {
115
+ if (theme === 'system') {
116
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
117
+ }
118
+ return theme;
119
+ }, [theme]);
120
+
121
+ // Configure JSON schema when schema or monacoInstance changes
122
+ useEffect(() => {
123
+ if (!monacoInstance || !schema) return;
124
+
125
+ const schemaObject = typeof schema === 'string' ? JSON.parse(schema) : schema;
126
+
127
+ // Configure Monaco JSON language support for schema validation
128
+ monacoInstance.languages.json.jsonDefaults.setDiagnosticsOptions({
129
+ validate: true,
130
+ schemas: [
131
+ {
132
+ uri: schemaUri,
133
+ fileMatch: ['*'],
134
+ schema: schemaObject,
135
+ },
136
+ ],
137
+ });
138
+ }, [monacoInstance, schema, schemaUri]);
139
+
140
+ // Handle theme changes for existing editor instance
141
+ useEffect(() => {
142
+ if (editorInstance && monacoInstance) {
143
+ editorInstance.updateOptions({
144
+ theme: resolvedTheme === 'light' ? 'custom-light' : 'custom-dark',
145
+ });
146
+ }
147
+ }, [resolvedTheme, editorInstance, monacoInstance]);
148
+
149
+ return (
150
+ <div
151
+ className={`w-full pl-3 pb-3 [&_.monaco-editor]:!bg-transparent [&_.monaco-editor-background]:!bg-transparent [&_.view-lines]:!bg-transparent [&_.monaco-editor]:!shadow-none [&_.monaco-scrollable-element]:!shadow-none [&_.overflow-guard]:!shadow-none [&_.monaco-scrollable-element>.shadow.top]:!hidden [&_.monaco-editor_.scroll-decoration]:!hidden [&_.shadow.top]:!hidden [&_.scroll-decoration]:!hidden ${className}`}
152
+ style={{ minHeight: '64px', maxHeight: '192px', height: `${editorHeight}px` }}
153
+ >
154
+ <Editor
155
+ value={value || '{}'}
156
+ onChange={(newValue) => onChange(newValue || '')}
157
+ language="json"
158
+ theme={resolvedTheme === 'light' ? 'custom-light' : 'custom-dark'}
159
+ height="100%"
160
+ options={{
161
+ minimap: { enabled: false },
162
+ lineNumbers: 'off',
163
+ folding: false,
164
+ scrollBeyondLastLine: false,
165
+ wordWrap: 'on',
166
+ renderLineHighlight: 'none',
167
+ overviewRulerBorder: false,
168
+ overviewRulerLanes: 0,
169
+ hideCursorInOverviewRuler: true,
170
+ fixedOverflowWidgets: true,
171
+ roundedSelection: false,
172
+ occurrencesHighlight: 'off',
173
+ selectionHighlight: false,
174
+ renderWhitespace: 'none',
175
+ fontSize: 14,
176
+ fontWeight: '400',
177
+ formatOnPaste: true,
178
+ formatOnType: true,
179
+ autoIndent: 'full',
180
+ glyphMargin: false,
181
+ lineDecorationsWidth: 0,
182
+ lineNumbersMinChars: 0,
183
+ automaticLayout: true,
184
+ scrollbar: {
185
+ vertical: 'auto',
186
+ horizontal: 'auto',
187
+ verticalScrollbarSize: 10,
188
+ horizontalScrollbarSize: 10,
189
+ // Disable scroll shadows
190
+ verticalHasArrows: false,
191
+ horizontalHasArrows: false,
192
+ },
193
+ padding: { top: 12, bottom: 12 },
194
+ // Additional background transparency options
195
+ renderValidationDecorations: 'off',
196
+ guides: {
197
+ indentation: false,
198
+ highlightActiveIndentation: false,
199
+ },
200
+ // Disable sticky scroll feature
201
+ stickyScroll: { enabled: false },
202
+ // Disable scroll decorations/shadows
203
+ scrollBeyondLastColumn: 0,
204
+ renderLineHighlightOnlyWhenFocus: true,
205
+ }}
206
+ onMount={(editor, monaco) => {
207
+ setEditorInstance(editor);
208
+ setMonacoInstance(monaco);
209
+ editor.focus();
210
+
211
+ // Auto-resize based on content
212
+ const updateHeight = () => {
213
+ const contentHeight = editor.getContentHeight();
214
+ const maxHeight = 192; // max-h-48 = 12rem = 192px
215
+ const minHeight = 64; // min-h-16 = 4rem = 64px
216
+ const newHeight = Math.min(Math.max(contentHeight + 24, minHeight), maxHeight);
217
+ setEditorHeight(newHeight);
218
+
219
+ // Layout after height change
220
+ setTimeout(() => editor.layout(), 0);
221
+ };
222
+
223
+ // Update height on content changes
224
+ editor.onDidChangeModelContent(updateHeight);
225
+
226
+ // Initial height update
227
+ setTimeout(updateHeight, 0);
228
+
229
+ // Ensure background transparency and remove shadows
230
+ setTimeout(() => {
231
+ const editorElement = editor.getDomNode();
232
+ if (editorElement) {
233
+ // Set transparent backgrounds on all relevant elements
234
+ const elementsToMakeTransparent = [
235
+ '.monaco-editor',
236
+ '.monaco-editor .monaco-editor-background',
237
+ '.monaco-editor .view-lines',
238
+ '.monaco-editor .margin',
239
+ '.monaco-editor .monaco-scrollable-element',
240
+ '.monaco-editor .overflow-guard',
241
+ '.view-overlays',
242
+ '.decorationsOverviewRuler',
243
+ ];
244
+
245
+ elementsToMakeTransparent.forEach((selector) => {
246
+ const element = editorElement.querySelector(selector);
247
+ if (element) {
248
+ (element as HTMLElement).style.backgroundColor = 'transparent';
249
+ (element as HTMLElement).style.boxShadow = 'none';
250
+ }
251
+ });
252
+
253
+ // Remove scroll shadows specifically - target the exact classes
254
+ const shadowTop = editorElement.querySelector(
255
+ '.monaco-scrollable-element > .shadow.top'
256
+ );
257
+ if (shadowTop) {
258
+ (shadowTop as HTMLElement).style.display = 'none';
259
+ }
260
+
261
+ const scrollDecorations = editorElement.querySelectorAll(
262
+ '.monaco-editor .scroll-decoration, .scroll-decoration'
263
+ );
264
+ scrollDecorations.forEach((decoration) => {
265
+ (decoration as HTMLElement).style.display = 'none';
266
+ });
267
+
268
+ const scrollableElement = editorElement.querySelector(
269
+ '.monaco-scrollable-element'
270
+ );
271
+ if (scrollableElement) {
272
+ (scrollableElement as HTMLElement).style.setProperty(
273
+ '--scroll-shadow',
274
+ 'none'
275
+ );
276
+ (scrollableElement as HTMLElement).style.setProperty(
277
+ 'box-shadow',
278
+ 'none',
279
+ 'important'
280
+ );
281
+ }
282
+
283
+ // Also set transparent and remove shadow on the editor element itself
284
+ (editorElement as HTMLElement).style.backgroundColor = 'transparent';
285
+ (editorElement as HTMLElement).style.boxShadow = 'none';
286
+ }
287
+ }, 0);
288
+ }}
289
+ beforeMount={async (monaco) => {
290
+ setMonacoInstance(monaco);
291
+
292
+ try {
293
+ // Try to use actual Shiki themes
294
+ const oneLightThemeModule = await bundledThemes['one-light']();
295
+ const oneDarkProThemeModule = await bundledThemes['one-dark-pro']();
296
+
297
+ if (oneLightThemeModule?.default) {
298
+ const lightMonacoTheme = convertShikiToMonaco(
299
+ oneLightThemeModule.default,
300
+ 'one-light'
301
+ );
302
+ monaco.editor.defineTheme('custom-light', lightMonacoTheme);
303
+ }
304
+
305
+ if (oneDarkProThemeModule?.default) {
306
+ const darkMonacoTheme = convertShikiToMonaco(
307
+ oneDarkProThemeModule.default,
308
+ 'one-dark-pro'
309
+ );
310
+ monaco.editor.defineTheme('custom-dark', darkMonacoTheme);
311
+ }
312
+ } catch (error) {
313
+ console.warn(
314
+ 'Failed to load Shiki themes, falling back to manual themes:',
315
+ error
316
+ );
317
+
318
+ // Fallback to manual theme definitions
319
+ monaco.editor.defineTheme('custom-light', {
320
+ base: 'vs',
321
+ inherit: true,
322
+ rules: [
323
+ { token: 'string.key.json', foreground: 'e45649' },
324
+ { token: 'string.value.json', foreground: '50a14f' },
325
+ { token: 'number.json', foreground: '986801' },
326
+ { token: 'keyword.json', foreground: '986801' },
327
+ { token: 'string', foreground: '50a14f' },
328
+ { token: 'number', foreground: '986801' },
329
+ { token: 'keyword', foreground: '986801' },
330
+ ],
331
+ colors: {
332
+ 'editor.background': '#00000000',
333
+ 'editor.foreground': '#383a42',
334
+ },
335
+ });
336
+
337
+ monaco.editor.defineTheme('custom-dark', {
338
+ base: 'vs-dark',
339
+ inherit: true,
340
+ rules: [
341
+ { token: 'string.key.json', foreground: 'e06c75' },
342
+ { token: 'string.value.json', foreground: '98c379' },
343
+ { token: 'number.json', foreground: 'd19a66' },
344
+ { token: 'keyword.json', foreground: 'c678dd' },
345
+ { token: 'string', foreground: '98c379' },
346
+ { token: 'number', foreground: 'd19a66' },
347
+ { token: 'keyword', foreground: 'c678dd' },
348
+ ],
349
+ colors: {
350
+ 'editor.background': '#00000000',
351
+ 'editor.foreground': '#abb2bf',
352
+ },
353
+ });
354
+ }
355
+ }}
356
+ />
357
+ </div>
358
+ );
359
+ }
@@ -14,7 +14,8 @@ export interface SchemaProps {
14
14
  export function Schema({ open, onOpenChange }: SchemaProps) {
15
15
  const { agents, selectedAgent, schemasLoading, schemasError } = useWorkbench();
16
16
 
17
- const selectedAgentData = agents[selectedAgent] || null;
17
+ const selectedAgentData =
18
+ Object.values(agents).find((agent) => agent.metadata.agentId === selectedAgent) || null;
18
19
 
19
20
  return (
20
21
  <>
@@ -4,7 +4,8 @@ import type { WorkbenchConfig } from '@agentuity/core/workbench';
4
4
  import type { WorkbenchContextType, ConnectionStatus } from '../../types/config';
5
5
  import { useAgentSchemas } from '../../hooks/useAgentSchemas';
6
6
  import { useWorkbenchWebsocket } from '../../hooks/useWorkbenchWebsocket';
7
- import { getTotalTokens, parseTokensHeader } from '../../lib/utils';
7
+ import { useLogger } from '../../hooks/useLogger';
8
+ import { getTotalTokens, parseTokensHeader, defaultBaseUrl } from '../../lib/utils';
8
9
 
9
10
  const WorkbenchContext = createContext<WorkbenchContextType | null>(null);
10
11
 
@@ -22,6 +23,8 @@ interface WorkbenchProviderProps {
22
23
  }
23
24
 
24
25
  export function WorkbenchProvider({ config, children }: WorkbenchProviderProps) {
26
+ const logger = useLogger('WorkbenchProvider');
27
+
25
28
  const [messages, setMessages] = useState<UIMessage[]>([]);
26
29
  const [selectedAgent, setSelectedAgent] = useState<string>('');
27
30
  const [inputMode, setInputMode] = useState<'text' | 'form'>('text');
@@ -29,7 +32,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
29
32
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('disconnected');
30
33
 
31
34
  // Config values
32
- const baseUrl = config.port ? `http://localhost:${config.port}` : undefined;
35
+ const baseUrl = defaultBaseUrl;
33
36
  const apiKey = config.apiKey;
34
37
  const shouldUseSchemas = true;
35
38
 
@@ -99,7 +102,14 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
99
102
  // Set initial agent selection
100
103
  useEffect(() => {
101
104
  if (agents && Object.keys(agents).length > 0 && !selectedAgent) {
102
- setSelectedAgent(Object.keys(agents)[0]);
105
+ logger.debug('🔍 Available agents:', agents);
106
+ const sortedAgents = Object.values(agents).sort((a, b) =>
107
+ a.metadata.name.localeCompare(b.metadata.name)
108
+ );
109
+ const firstAgent = sortedAgents[0];
110
+ logger.debug('🎯 First agent (alphabetically):', firstAgent);
111
+ logger.debug('🆔 Setting selectedAgent to:', firstAgent.metadata.agentId);
112
+ setSelectedAgent(firstAgent.metadata.agentId);
103
113
  }
104
114
  }, [agents, selectedAgent]);
105
115
 
@@ -115,11 +125,21 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
115
125
  const submitMessage = async (value: string, _mode: 'text' | 'form' = 'text') => {
116
126
  if (!selectedAgent) return;
117
127
 
118
- const selectedAgentData = agents?.[selectedAgent];
128
+ logger.debug('🚀 Submitting message with selectedAgent:', selectedAgent);
129
+ const selectedAgentData = agents
130
+ ? Object.values(agents).find((agent) => agent.metadata.agentId === selectedAgent)
131
+ : undefined;
132
+ logger.debug('📊 Found selectedAgentData:', selectedAgentData);
119
133
  const hasInputSchema = selectedAgentData?.schema?.input?.json;
134
+ logger.debug('📝 hasInputSchema:', hasInputSchema, 'value:', value);
120
135
 
121
136
  // Only require value for agents with input schemas
122
- if (hasInputSchema && !value.trim()) return;
137
+ if (hasInputSchema && !value.trim()) {
138
+ logger.debug('❌ Returning early - hasInputSchema but no value');
139
+ return;
140
+ }
141
+
142
+ logger.debug('✅ Validation passed, continuing with message submission...');
123
143
 
124
144
  // Add user message
125
145
  const displayText = hasInputSchema
@@ -134,7 +154,9 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
134
154
  setMessages((prev) => [...prev, userMessage]);
135
155
  setIsLoading(true);
136
156
 
157
+ logger.debug('🔗 baseUrl:', baseUrl);
137
158
  if (!baseUrl) {
159
+ logger.debug('❌ No baseUrl configured!');
138
160
  const errorMessage: UIMessage = {
139
161
  id: (Date.now() + 1).toString(),
140
162
  role: 'assistant',
@@ -164,6 +186,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
164
186
  }
165
187
  }
166
188
 
189
+ logger.debug('🌐 About to make API call...');
167
190
  // Call execution endpoint with timeout
168
191
  const headers: Record<string, string> = {
169
192
  'Content-Type': 'application/json',
@@ -178,13 +201,15 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
178
201
  const startTime = performance.now();
179
202
 
180
203
  try {
204
+ const requestPayload = {
205
+ agentId: selectedAgent,
206
+ input: parsedInput,
207
+ };
208
+ logger.debug('📤 API Request payload:', requestPayload);
181
209
  const response = await fetch(`${baseUrl}/_agentuity/workbench/execute`, {
182
210
  method: 'POST',
183
211
  headers,
184
- body: JSON.stringify({
185
- agentId: selectedAgent,
186
- input: parsedInput,
187
- }),
212
+ body: JSON.stringify(requestPayload),
188
213
  signal: controller.signal,
189
214
  });
190
215
  clearTimeout(timeoutId);
@@ -252,6 +277,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
252
277
  };
253
278
 
254
279
  const handleAgentSelect = async (agentId: string) => {
280
+ logger.debug('🔄 handleAgentSelect called with:', agentId);
255
281
  setSelectedAgent(agentId);
256
282
  // No handlers configured for now
257
283
  };
@@ -15,6 +15,7 @@ export {
15
15
  useWorkbenchAgentSchema,
16
16
  useWorkbenchAllAgentSchemas,
17
17
  } from './hooks/useWorkbenchSchemas';
18
+ export { useLogger } from './hooks/useLogger';
18
19
 
19
20
  export type {
20
21
  AgentSchema,
@@ -5,6 +5,7 @@ export {
5
5
  useWorkbenchAllAgentSchemas,
6
6
  } from './useWorkbenchSchemas';
7
7
  export { useWorkbenchWebsocket } from './useWorkbenchWebsocket';
8
+ export { useLogger } from './useLogger';
8
9
  export type {
9
10
  AgentSchema,
10
11
  AgentMetadata,
@@ -19,6 +19,7 @@ export interface AgentMetadata {
19
19
  version?: string;
20
20
  filename?: string;
21
21
  identifier?: string;
22
+ agentId: string;
22
23
  }
23
24
 
24
25
  export interface AgentSchemaData {
@@ -0,0 +1,57 @@
1
+ import { useCallback } from 'react';
2
+
3
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
4
+
5
+ interface Logger {
6
+ debug: (...args: unknown[]) => void;
7
+ info: (...args: unknown[]) => void;
8
+ warn: (...args: unknown[]) => void;
9
+ error: (...args: unknown[]) => void;
10
+ }
11
+
12
+ const getLogLevel = (): LogLevel | null => {
13
+ try {
14
+ const level = localStorage.getItem('AGENTUITY_LOG_LEVEL');
15
+ if (level && ['debug', 'info', 'warn', 'error'].includes(level)) {
16
+ return level as LogLevel;
17
+ }
18
+ return null;
19
+ } catch {
20
+ return null;
21
+ }
22
+ };
23
+
24
+ const shouldLog = (messageLevel: LogLevel): boolean => {
25
+ const currentLevel = getLogLevel();
26
+ if (!currentLevel) return false;
27
+
28
+ const levels: Record<LogLevel, number> = {
29
+ debug: 0,
30
+ info: 1,
31
+ warn: 2,
32
+ error: 3,
33
+ };
34
+
35
+ return levels[messageLevel] >= levels[currentLevel];
36
+ };
37
+
38
+ export function useLogger(component?: string): Logger {
39
+ const createLogFunction = useCallback(
40
+ (level: LogLevel) =>
41
+ (...args: unknown[]) => {
42
+ if (!shouldLog(level)) return;
43
+
44
+ const prefix = component ? `[${component}]` : '[Workbench]';
45
+ const consoleFn = console[level] || console.log;
46
+ consoleFn(prefix, ...args);
47
+ },
48
+ [component]
49
+ );
50
+
51
+ return {
52
+ debug: createLogFunction('debug'),
53
+ info: createLogFunction('info'),
54
+ warn: createLogFunction('warn'),
55
+ error: createLogFunction('error'),
56
+ };
57
+ }
package/src/index.ts CHANGED
@@ -16,4 +16,5 @@ export { Input } from './components/ui/input';
16
16
  // Export components
17
17
  export { default as App } from './components/App';
18
18
  export { default as Inline } from './components/Inline';
19
+ export { MonacoJsonEditor } from './components/internal/MonacoJsonEditor';
19
20
  export { createWorkbench } from './workbench';
package/src/lib/utils.ts CHANGED
@@ -38,3 +38,44 @@ export function parseTokensHeader(header: string): Record<string, number> {
38
38
  export function getTotalTokens(tokens: Record<string, number>): number {
39
39
  return Object.keys(tokens).reduce((sum, key) => sum + tokens[key], 0);
40
40
  }
41
+
42
+ export const getProcessEnv = (key: string): string | undefined => {
43
+ if (typeof process !== 'undefined' && process.env) {
44
+ return process.env[key];
45
+ }
46
+ if (typeof import.meta.env !== 'undefined') {
47
+ return import.meta.env[key];
48
+ }
49
+ return undefined;
50
+ };
51
+
52
+ export const buildUrl = (
53
+ base: string,
54
+ path: string,
55
+ subpath?: string,
56
+ query?: URLSearchParams
57
+ ): string => {
58
+ path = path.startsWith('/') ? path : `/${path}`;
59
+ let url = base.replace(/\/$/, '') + path;
60
+ if (subpath) {
61
+ subpath = subpath.startsWith('/') ? subpath : `/${subpath}`;
62
+ url += subpath;
63
+ }
64
+ if (query) {
65
+ url += `?${query.toString()}`;
66
+ }
67
+ return url;
68
+ };
69
+
70
+ const tryOrigin = () => {
71
+ if (typeof window !== 'undefined') {
72
+ return window.location.origin;
73
+ }
74
+ };
75
+
76
+ export const defaultBaseUrl: string =
77
+ getProcessEnv('NEXT_PUBLIC_AGENTUITY_URL') ||
78
+ getProcessEnv('VITE_AGENTUITY_URL') ||
79
+ getProcessEnv('AGENTUITY_URL') ||
80
+ tryOrigin() ||
81
+ 'http://localhost:3500';