@agentuity/workbench 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/internal/chat.d.ts.map +1 -1
- package/dist/components/internal/chat.js +1 -1
- package/dist/components/internal/chat.js.map +1 -1
- package/dist/components/internal/input-section.d.ts.map +1 -1
- package/dist/components/internal/input-section.js +10 -10
- package/dist/components/internal/input-section.js.map +1 -1
- package/dist/components/internal/workbench-provider.d.ts +13 -1
- package/dist/components/internal/workbench-provider.d.ts.map +1 -1
- package/dist/components/internal/workbench-provider.js +96 -44
- package/dist/components/internal/workbench-provider.js.map +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/hooks/useAgentSchemas.d.ts +3 -0
- package/dist/hooks/useAgentSchemas.d.ts.map +1 -1
- package/dist/hooks/useAgentSchemas.js +17 -3
- package/dist/hooks/useAgentSchemas.js.map +1 -1
- package/dist/hooks/useLogger.d.ts.map +1 -1
- package/dist/hooks/useLogger.js +18 -14
- package/dist/hooks/useLogger.js.map +1 -1
- package/dist/hooks/useWorkbenchWebsocket.d.ts +1 -0
- package/dist/hooks/useWorkbenchWebsocket.d.ts.map +1 -1
- package/dist/hooks/useWorkbenchWebsocket.js +13 -2
- package/dist/hooks/useWorkbenchWebsocket.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/standalone.css +11 -0
- package/package.json +4 -4
- package/src/components/internal/chat.tsx +10 -0
- package/src/components/internal/input-section.tsx +62 -59
- package/src/components/internal/workbench-provider.tsx +134 -55
- package/src/hooks/useAgentSchemas.ts +22 -4
- package/src/hooks/useLogger.ts +15 -13
- package/src/hooks/useWorkbenchWebsocket.ts +15 -2
- package/src/index.ts +5 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WorkbenchConfig } from '@agentuity/core/workbench';
|
|
2
2
|
import type React from 'react';
|
|
3
|
-
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useAgentSchemas } from '../../hooks/useAgentSchemas';
|
|
5
5
|
import { useLogger } from '../../hooks/useLogger';
|
|
6
6
|
import { useWorkbenchWebsocket } from '../../hooks/useWorkbenchWebsocket';
|
|
@@ -19,6 +19,13 @@ export function useWorkbench() {
|
|
|
19
19
|
return context;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Callback to get authentication headers for workbench requests.
|
|
24
|
+
* Called before each API request with the request body.
|
|
25
|
+
* Should return headers like X-Agentuity-Workbench-Signature and X-Agentuity-Workbench-Timestamp.
|
|
26
|
+
*/
|
|
27
|
+
export type GetAuthHeaders = (body: string) => Promise<Record<string, string>>;
|
|
28
|
+
|
|
22
29
|
interface WorkbenchProviderProps {
|
|
23
30
|
config: Omit<WorkbenchConfig, 'route'> & {
|
|
24
31
|
baseUrl?: string | null;
|
|
@@ -36,6 +43,12 @@ interface WorkbenchProviderProps {
|
|
|
36
43
|
post?: React.ReactNode;
|
|
37
44
|
};
|
|
38
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Optional callback to get authentication headers for requests to deployed agents.
|
|
48
|
+
* Called before each API request with the request body (empty string for GET/DELETE).
|
|
49
|
+
* Useful for signature-based authentication in production deployments.
|
|
50
|
+
*/
|
|
51
|
+
getAuthHeaders?: GetAuthHeaders;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
export function WorkbenchProvider({
|
|
@@ -47,9 +60,14 @@ export function WorkbenchProvider({
|
|
|
47
60
|
},
|
|
48
61
|
children,
|
|
49
62
|
portals,
|
|
63
|
+
getAuthHeaders,
|
|
50
64
|
}: WorkbenchProviderProps) {
|
|
51
65
|
const logger = useLogger('WorkbenchProvider');
|
|
52
66
|
|
|
67
|
+
// Use ref for getAuthHeaders to prevent re-render loops when the callback changes identity
|
|
68
|
+
const getAuthHeadersRef = useRef(getAuthHeaders);
|
|
69
|
+
getAuthHeadersRef.current = getAuthHeaders;
|
|
70
|
+
|
|
53
71
|
// localStorage utilities scoped by project
|
|
54
72
|
const getStorageKey = useCallback(
|
|
55
73
|
(key: string) =>
|
|
@@ -77,26 +95,36 @@ export function WorkbenchProvider({
|
|
|
77
95
|
}
|
|
78
96
|
}, [getStorageKey]);
|
|
79
97
|
|
|
98
|
+
// Thread IDs are stored per baseUrl to avoid signature mismatch between environments
|
|
99
|
+
// (local signs with 'agentuity', cloud signs with AGENTUITY_SDK_KEY)
|
|
100
|
+
const getThreadStorageKey = useCallback(() => {
|
|
101
|
+
// Use a hash of the baseUrl to create unique storage per endpoint
|
|
102
|
+
const url = config.baseUrl ?? 'local';
|
|
103
|
+
const urlHash =
|
|
104
|
+
typeof url === 'string' ? btoa(encodeURIComponent(url)).slice(0, 16) : 'local';
|
|
105
|
+
return getStorageKey(`thread-id-${urlHash}`);
|
|
106
|
+
}, [getStorageKey, config.baseUrl]);
|
|
107
|
+
|
|
80
108
|
const saveThreadId = useCallback(
|
|
81
109
|
(threadId: string) => {
|
|
82
110
|
try {
|
|
83
|
-
localStorage.setItem(
|
|
111
|
+
localStorage.setItem(getThreadStorageKey(), threadId);
|
|
84
112
|
} catch (error) {
|
|
85
113
|
logger.warn('Failed to save thread id to localStorage:', error);
|
|
86
114
|
}
|
|
87
115
|
},
|
|
88
|
-
[
|
|
116
|
+
[getThreadStorageKey]
|
|
89
117
|
);
|
|
90
118
|
|
|
91
119
|
const loadThreadId = useCallback((): string | null => {
|
|
92
120
|
try {
|
|
93
|
-
return localStorage.getItem(
|
|
121
|
+
return localStorage.getItem(getThreadStorageKey());
|
|
94
122
|
} catch (error) {
|
|
95
123
|
logger.warn('Failed to load thread id from localStorage:', error);
|
|
96
124
|
|
|
97
125
|
return null;
|
|
98
126
|
}
|
|
99
|
-
}, [
|
|
127
|
+
}, [getThreadStorageKey]);
|
|
100
128
|
|
|
101
129
|
const applyThreadIdHeader = useCallback(
|
|
102
130
|
(headers: Record<string, string>) => {
|
|
@@ -128,8 +156,51 @@ export function WorkbenchProvider({
|
|
|
128
156
|
// Config values
|
|
129
157
|
const baseUrl = config.baseUrl === undefined ? defaultBaseUrl : config.baseUrl;
|
|
130
158
|
const apiKey = config.apiKey;
|
|
159
|
+
const configHeaders = config.headers;
|
|
131
160
|
const isBaseUrlNull = config.baseUrl === null;
|
|
132
161
|
|
|
162
|
+
// Helper to build request headers with config headers, auth, and thread ID
|
|
163
|
+
const buildRequestHeaders = useCallback(
|
|
164
|
+
(additionalHeaders?: Record<string, string>): Record<string, string> => {
|
|
165
|
+
const headers: Record<string, string> = {
|
|
166
|
+
...(configHeaders || {}),
|
|
167
|
+
...(additionalHeaders || {}),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (apiKey) {
|
|
171
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
applyThreadIdHeader(headers);
|
|
175
|
+
|
|
176
|
+
return headers;
|
|
177
|
+
},
|
|
178
|
+
[configHeaders, apiKey, applyThreadIdHeader]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Async helper to get request headers including auth headers from callback
|
|
182
|
+
const getRequestHeaders = useCallback(
|
|
183
|
+
async (
|
|
184
|
+
body: string,
|
|
185
|
+
additionalHeaders?: Record<string, string>
|
|
186
|
+
): Promise<Record<string, string>> => {
|
|
187
|
+
const headers = buildRequestHeaders(additionalHeaders);
|
|
188
|
+
|
|
189
|
+
// Call getAuthHeaders callback if provided (use ref to avoid re-render loops)
|
|
190
|
+
if (getAuthHeadersRef.current) {
|
|
191
|
+
try {
|
|
192
|
+
const authHeaders = await getAuthHeadersRef.current(body);
|
|
193
|
+
Object.assign(headers, authHeaders);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logger.warn('Failed to get auth headers:', error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return headers;
|
|
200
|
+
},
|
|
201
|
+
[buildRequestHeaders, logger]
|
|
202
|
+
);
|
|
203
|
+
|
|
133
204
|
// Log baseUrl state
|
|
134
205
|
useEffect(() => {
|
|
135
206
|
if (isBaseUrlNull) {
|
|
@@ -139,14 +210,19 @@ export function WorkbenchProvider({
|
|
|
139
210
|
}
|
|
140
211
|
}, [isBaseUrlNull, baseUrl, logger]);
|
|
141
212
|
|
|
142
|
-
// Set
|
|
213
|
+
// Set connection status based on baseUrl availability
|
|
214
|
+
// In cloud mode, we don't have websocket so we set connected when baseUrl is available
|
|
143
215
|
useEffect(() => {
|
|
144
216
|
if (isBaseUrlNull) {
|
|
145
217
|
logger.debug('🔌 Setting connection status to disconnected (baseUrl is null)');
|
|
146
|
-
|
|
147
218
|
setConnectionStatus('disconnected');
|
|
219
|
+
} else if (env.cloud) {
|
|
220
|
+
// In cloud mode, we're "connected" as soon as we have a baseUrl
|
|
221
|
+
// (no websocket to wait for)
|
|
222
|
+
logger.debug('🔌 Setting connection status to connected (cloud mode with baseUrl)');
|
|
223
|
+
setConnectionStatus('connected');
|
|
148
224
|
}
|
|
149
|
-
}, [isBaseUrlNull, logger]);
|
|
225
|
+
}, [isBaseUrlNull, env.cloud, logger]);
|
|
150
226
|
|
|
151
227
|
useEffect(() => {
|
|
152
228
|
if (isBaseUrlNull) {
|
|
@@ -160,24 +236,31 @@ export function WorkbenchProvider({
|
|
|
160
236
|
error: schemasError,
|
|
161
237
|
refetch: refetchSchemas,
|
|
162
238
|
} = useAgentSchemas({
|
|
163
|
-
baseUrl,
|
|
239
|
+
baseUrl: baseUrl ?? undefined,
|
|
164
240
|
apiKey,
|
|
241
|
+
headers: configHeaders,
|
|
165
242
|
enabled: !isBaseUrlNull,
|
|
243
|
+
getAuthHeaders,
|
|
166
244
|
});
|
|
167
245
|
|
|
168
246
|
// WebSocket connection for dev server restart detection
|
|
169
|
-
|
|
247
|
+
// Only enable for local dev - deployed agents don't have websocket endpoints
|
|
248
|
+
const wsEnabled = !isBaseUrlNull && !env.cloud;
|
|
249
|
+
const wsBaseUrl = wsEnabled && baseUrl ? baseUrl : undefined;
|
|
170
250
|
|
|
171
251
|
useEffect(() => {
|
|
172
252
|
if (isBaseUrlNull) {
|
|
173
253
|
logger.debug('🔌 WebSocket connection disabled (baseUrl is null)');
|
|
254
|
+
} else if (env.cloud) {
|
|
255
|
+
logger.debug('🔌 WebSocket connection disabled (cloud mode)');
|
|
174
256
|
}
|
|
175
|
-
}, [isBaseUrlNull, logger]);
|
|
257
|
+
}, [isBaseUrlNull, env.cloud, logger]);
|
|
176
258
|
|
|
177
259
|
const { connected } = useWorkbenchWebsocket({
|
|
178
|
-
enabled:
|
|
260
|
+
enabled: wsEnabled,
|
|
179
261
|
baseUrl: wsBaseUrl,
|
|
180
262
|
apiKey,
|
|
263
|
+
headers: configHeaders,
|
|
181
264
|
onConnect: () => {
|
|
182
265
|
setConnectionStatus('connected');
|
|
183
266
|
refetchSchemas();
|
|
@@ -196,10 +279,12 @@ export function WorkbenchProvider({
|
|
|
196
279
|
});
|
|
197
280
|
|
|
198
281
|
useEffect(() => {
|
|
199
|
-
|
|
282
|
+
// In cloud mode, websocket is disabled so we stay 'connected' (no live connection tracking)
|
|
283
|
+
// In local mode, track the websocket connection status
|
|
284
|
+
if (!isBaseUrlNull && !env.cloud && !connected && connectionStatus !== 'restarting') {
|
|
200
285
|
setConnectionStatus('disconnected');
|
|
201
286
|
}
|
|
202
|
-
}, [connected, connectionStatus, isBaseUrlNull]);
|
|
287
|
+
}, [connected, connectionStatus, isBaseUrlNull, env.cloud]);
|
|
203
288
|
|
|
204
289
|
// Convert schema data to Agent format, no fallback
|
|
205
290
|
const agents = schemaData?.agents;
|
|
@@ -228,18 +313,11 @@ export function WorkbenchProvider({
|
|
|
228
313
|
}
|
|
229
314
|
|
|
230
315
|
try {
|
|
231
|
-
const headers: Record<string, string> = {};
|
|
232
|
-
|
|
233
|
-
if (apiKey) {
|
|
234
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
applyThreadIdHeader(headers);
|
|
238
|
-
|
|
239
316
|
const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
|
|
240
317
|
|
|
241
318
|
logger.debug('📡 Fetching state for agent:', agentId);
|
|
242
319
|
|
|
320
|
+
const headers = await getRequestHeaders('');
|
|
243
321
|
const response = await fetch(url, {
|
|
244
322
|
method: 'GET',
|
|
245
323
|
headers,
|
|
@@ -295,7 +373,7 @@ export function WorkbenchProvider({
|
|
|
295
373
|
setMessages([]);
|
|
296
374
|
}
|
|
297
375
|
},
|
|
298
|
-
[baseUrl,
|
|
376
|
+
[baseUrl, logger, getRequestHeaders, persistThreadIdFromResponse]
|
|
299
377
|
);
|
|
300
378
|
|
|
301
379
|
// Set initial agent selection
|
|
@@ -369,6 +447,31 @@ export function WorkbenchProvider({
|
|
|
369
447
|
}
|
|
370
448
|
}, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger, fetchAgentState]);
|
|
371
449
|
|
|
450
|
+
// Validate selected agent still exists when agents list changes (e.g., switching local ↔ cloud)
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (!agents || Object.keys(agents).length === 0 || !selectedAgent) return;
|
|
453
|
+
|
|
454
|
+
const agentExists = Object.values(agents).some(
|
|
455
|
+
(agent) => agent.metadata.agentId === selectedAgent
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
if (!agentExists) {
|
|
459
|
+
logger.debug('⚠️ Selected agent no longer exists, falling back to first agent');
|
|
460
|
+
|
|
461
|
+
const sortedAgents = Object.values(agents).sort((a, b) =>
|
|
462
|
+
a.metadata.name.localeCompare(b.metadata.name)
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const firstAgent = sortedAgents[0];
|
|
466
|
+
|
|
467
|
+
if (firstAgent) {
|
|
468
|
+
setSelectedAgent(firstAgent.metadata.agentId);
|
|
469
|
+
saveSelectedAgent(firstAgent.metadata.agentId);
|
|
470
|
+
fetchAgentState(firstAgent.metadata.agentId);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}, [agents, selectedAgent, logger, saveSelectedAgent, fetchAgentState]);
|
|
474
|
+
|
|
372
475
|
const submitMessage = async (value: string, _mode: 'text' | 'form' = 'text') => {
|
|
373
476
|
if (!selectedAgent) return;
|
|
374
477
|
|
|
@@ -456,16 +559,6 @@ export function WorkbenchProvider({
|
|
|
456
559
|
|
|
457
560
|
logger.debug('🌐 About to make API call...');
|
|
458
561
|
|
|
459
|
-
const headers: Record<string, string> = {
|
|
460
|
-
'Content-Type': 'application/json',
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
if (apiKey) {
|
|
464
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
applyThreadIdHeader(headers);
|
|
468
|
-
|
|
469
562
|
const startTime = performance.now();
|
|
470
563
|
|
|
471
564
|
try {
|
|
@@ -473,13 +566,17 @@ export function WorkbenchProvider({
|
|
|
473
566
|
agentId: selectedAgent,
|
|
474
567
|
input: parsedInput,
|
|
475
568
|
};
|
|
569
|
+
const requestBody = JSON.stringify(requestPayload);
|
|
476
570
|
|
|
477
571
|
logger.debug('📤 API Request payload:', requestPayload);
|
|
478
572
|
|
|
573
|
+
const headers = await getRequestHeaders(requestBody, {
|
|
574
|
+
'Content-Type': 'application/json',
|
|
575
|
+
});
|
|
479
576
|
const response = await fetch(`${baseUrl}/_agentuity/workbench/execute`, {
|
|
480
577
|
method: 'POST',
|
|
481
578
|
headers,
|
|
482
|
-
body:
|
|
579
|
+
body: requestBody,
|
|
483
580
|
credentials: 'include',
|
|
484
581
|
});
|
|
485
582
|
|
|
@@ -641,19 +738,8 @@ export function WorkbenchProvider({
|
|
|
641
738
|
|
|
642
739
|
try {
|
|
643
740
|
const url = `${baseUrl}/_agentuity/workbench/sample?agentId=${encodeURIComponent(agentId)}`;
|
|
644
|
-
const headers: HeadersInit = {
|
|
645
|
-
'Content-Type': 'application/json',
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
if (apiKey) {
|
|
649
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Keep thread id stable across workbench endpoints.
|
|
653
|
-
if (typeof headers === 'object' && headers && !Array.isArray(headers)) {
|
|
654
|
-
applyThreadIdHeader(headers as Record<string, string>);
|
|
655
|
-
}
|
|
656
741
|
|
|
742
|
+
const headers = await getRequestHeaders('', { 'Content-Type': 'application/json' });
|
|
657
743
|
const response = await fetch(url, {
|
|
658
744
|
method: 'GET',
|
|
659
745
|
headers,
|
|
@@ -713,15 +799,8 @@ export function WorkbenchProvider({
|
|
|
713
799
|
}
|
|
714
800
|
|
|
715
801
|
try {
|
|
716
|
-
const headers: Record<string, string> = {};
|
|
717
|
-
|
|
718
|
-
if (apiKey) {
|
|
719
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
applyThreadIdHeader(headers);
|
|
723
|
-
|
|
724
802
|
const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
|
|
803
|
+
const headers = await getRequestHeaders('');
|
|
725
804
|
const response = await fetch(url, {
|
|
726
805
|
method: 'DELETE',
|
|
727
806
|
headers,
|
|
@@ -741,7 +820,7 @@ export function WorkbenchProvider({
|
|
|
741
820
|
logger.debug('⚠️ Error clearing state:', error);
|
|
742
821
|
}
|
|
743
822
|
},
|
|
744
|
-
[baseUrl,
|
|
823
|
+
[baseUrl, logger, getRequestHeaders, persistThreadIdFromResponse]
|
|
745
824
|
);
|
|
746
825
|
|
|
747
826
|
const contextValue: WorkbenchContextType = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSONSchema7 } from 'ai';
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import type { GetAuthHeaders } from '../components/internal/workbench-provider';
|
|
3
4
|
import { useLogger } from './useLogger';
|
|
4
5
|
|
|
5
6
|
export interface AgentSchema {
|
|
@@ -36,6 +37,8 @@ export interface UseAgentSchemasOptions {
|
|
|
36
37
|
apiKey?: string;
|
|
37
38
|
baseUrl?: string;
|
|
38
39
|
enabled?: boolean;
|
|
40
|
+
headers?: Record<string, string>;
|
|
41
|
+
getAuthHeaders?: GetAuthHeaders;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export interface UseAgentSchemasResult {
|
|
@@ -66,13 +69,17 @@ export interface UseAgentSchemasResult {
|
|
|
66
69
|
* ```
|
|
67
70
|
*/
|
|
68
71
|
export function useAgentSchemas(options: UseAgentSchemasOptions = {}): UseAgentSchemasResult {
|
|
69
|
-
const { baseUrl = '', apiKey, enabled = true } = options;
|
|
72
|
+
const { baseUrl = '', apiKey, enabled = true, headers: configHeaders, getAuthHeaders } = options;
|
|
70
73
|
|
|
71
74
|
const logger = useLogger('useAgentSchemas');
|
|
72
75
|
const [data, setData] = useState<AgentSchemasResponse | null>(null);
|
|
73
76
|
const [isLoading, setIsLoading] = useState(false);
|
|
74
77
|
const [error, setError] = useState<Error | null>(null);
|
|
75
78
|
|
|
79
|
+
// Use ref to avoid re-render loops when getAuthHeaders changes
|
|
80
|
+
const getAuthHeadersRef = useRef(getAuthHeaders);
|
|
81
|
+
getAuthHeadersRef.current = getAuthHeaders;
|
|
82
|
+
|
|
76
83
|
const fetchSchemas = useCallback(async () => {
|
|
77
84
|
if (!enabled) return;
|
|
78
85
|
|
|
@@ -81,7 +88,8 @@ export function useAgentSchemas(options: UseAgentSchemasOptions = {}): UseAgentS
|
|
|
81
88
|
|
|
82
89
|
try {
|
|
83
90
|
const url = `${baseUrl}/_agentuity/workbench/metadata.json`;
|
|
84
|
-
const headers:
|
|
91
|
+
const headers: Record<string, string> = {
|
|
92
|
+
...(configHeaders || {}),
|
|
85
93
|
'Content-Type': 'application/json',
|
|
86
94
|
};
|
|
87
95
|
|
|
@@ -89,6 +97,16 @@ export function useAgentSchemas(options: UseAgentSchemasOptions = {}): UseAgentS
|
|
|
89
97
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
// Get auth headers if callback is provided
|
|
101
|
+
if (getAuthHeadersRef.current) {
|
|
102
|
+
try {
|
|
103
|
+
const authHeaders = await getAuthHeadersRef.current('');
|
|
104
|
+
Object.assign(headers, authHeaders);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
logger.warn('Failed to get auth headers:', err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
92
110
|
const response = await fetch(url, {
|
|
93
111
|
method: 'GET',
|
|
94
112
|
headers,
|
|
@@ -131,7 +149,7 @@ export function useAgentSchemas(options: UseAgentSchemasOptions = {}): UseAgentS
|
|
|
131
149
|
} finally {
|
|
132
150
|
setIsLoading(false);
|
|
133
151
|
}
|
|
134
|
-
}, [baseUrl, apiKey, enabled]);
|
|
152
|
+
}, [baseUrl, apiKey, enabled, configHeaders, logger]);
|
|
135
153
|
|
|
136
154
|
const refetch = useCallback(() => {
|
|
137
155
|
void fetchSchemas();
|
package/src/hooks/useLogger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
4
4
|
|
|
@@ -39,8 +39,11 @@ const shouldLog = (messageLevel: LogLevel): boolean => {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
export function useLogger(component?: string): Logger {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Memoize the entire logger object to prevent re-render loops
|
|
43
|
+
// when logger is used as a dependency in useCallback/useEffect
|
|
44
|
+
return useMemo(() => {
|
|
45
|
+
const createLogFunction =
|
|
46
|
+
(level: LogLevel) =>
|
|
44
47
|
(...args: unknown[]) => {
|
|
45
48
|
if (!shouldLog(level)) {
|
|
46
49
|
return;
|
|
@@ -50,14 +53,13 @@ export function useLogger(component?: string): Logger {
|
|
|
50
53
|
const consoleFn = console[level] || console.log;
|
|
51
54
|
|
|
52
55
|
consoleFn(prefix, ...args);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
debug: createLogFunction('debug'),
|
|
60
|
+
info: createLogFunction('info'),
|
|
61
|
+
warn: createLogFunction('warn'),
|
|
62
|
+
error: createLogFunction('error'),
|
|
63
|
+
};
|
|
64
|
+
}, [component]);
|
|
63
65
|
}
|
|
@@ -88,6 +88,7 @@ export interface UseWorkbenchWebsocketOptions {
|
|
|
88
88
|
apiKey?: string;
|
|
89
89
|
baseUrl?: string;
|
|
90
90
|
enabled?: boolean;
|
|
91
|
+
headers?: Record<string, string>;
|
|
91
92
|
onAlive?: () => void;
|
|
92
93
|
onConnect?: () => void;
|
|
93
94
|
onReconnect?: () => void;
|
|
@@ -102,7 +103,7 @@ export interface UseWorkbenchWebsocketResult {
|
|
|
102
103
|
export function useWorkbenchWebsocket(
|
|
103
104
|
options: UseWorkbenchWebsocketOptions = {}
|
|
104
105
|
): UseWorkbenchWebsocketResult {
|
|
105
|
-
const { baseUrl, apiKey, onConnect, onReconnect, onAlive, onRestarting } = options;
|
|
106
|
+
const { baseUrl, apiKey, headers, onConnect, onReconnect, onAlive, onRestarting } = options;
|
|
106
107
|
|
|
107
108
|
const [connected, setConnected] = useState(false);
|
|
108
109
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -138,8 +139,20 @@ export function useWorkbenchWebsocket(
|
|
|
138
139
|
url.searchParams.set('apiKey', apiKey);
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
// Pass any manual headers as query params (WebSocket can't use custom headers in browser)
|
|
143
|
+
if (headers) {
|
|
144
|
+
const signature = headers['X-Agentuity-Workbench-Signature'];
|
|
145
|
+
const timestamp = headers['X-Agentuity-Workbench-Timestamp'];
|
|
146
|
+
if (signature) {
|
|
147
|
+
url.searchParams.set('signature', signature);
|
|
148
|
+
}
|
|
149
|
+
if (timestamp) {
|
|
150
|
+
url.searchParams.set('timestamp', timestamp);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
141
154
|
return url.toString();
|
|
142
|
-
}, [baseUrl, apiKey]);
|
|
155
|
+
}, [baseUrl, apiKey, headers]);
|
|
143
156
|
|
|
144
157
|
const connect = useCallback(() => {
|
|
145
158
|
if (manualClose.current || !options.enabled) {
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,11 @@ export { default as App } from './components/App';
|
|
|
2
2
|
export { Chat } from './components/internal/chat';
|
|
3
3
|
export { StatusIndicator } from './components/internal/header';
|
|
4
4
|
export { Schema } from './components/internal/schema';
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
useWorkbench,
|
|
7
|
+
WorkbenchProvider,
|
|
8
|
+
type GetAuthHeaders,
|
|
9
|
+
} from './components/internal/workbench-provider';
|
|
6
10
|
export type { WorkbenchInstance } from './types';
|
|
7
11
|
export type { ConnectionStatus, WorkbenchMessage } from './types/config';
|
|
8
12
|
export { createWorkbench } from './workbench';
|