@agentuity/workbench 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.
- 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 +18 -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 +15 -5
- package/src/components/internal/input-section.tsx +17 -14
- package/src/components/internal/workbench-provider.tsx +133 -55
- package/src/hooks/useAgentSchemas.ts +23 -4
- package/src/hooks/useLogger.ts +15 -13
- package/src/hooks/useWorkbenchWebsocket.ts +16 -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,35 @@ 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 = typeof url === 'string' ? btoa(encodeURIComponent(url)).slice(0, 16) : 'local';
|
|
104
|
+
return getStorageKey(`thread-id-${urlHash}`);
|
|
105
|
+
}, [getStorageKey, config.baseUrl]);
|
|
106
|
+
|
|
80
107
|
const saveThreadId = useCallback(
|
|
81
108
|
(threadId: string) => {
|
|
82
109
|
try {
|
|
83
|
-
localStorage.setItem(
|
|
110
|
+
localStorage.setItem(getThreadStorageKey(), threadId);
|
|
84
111
|
} catch (error) {
|
|
85
112
|
logger.warn('Failed to save thread id to localStorage:', error);
|
|
86
113
|
}
|
|
87
114
|
},
|
|
88
|
-
[
|
|
115
|
+
[getThreadStorageKey]
|
|
89
116
|
);
|
|
90
117
|
|
|
91
118
|
const loadThreadId = useCallback((): string | null => {
|
|
92
119
|
try {
|
|
93
|
-
return localStorage.getItem(
|
|
120
|
+
return localStorage.getItem(getThreadStorageKey());
|
|
94
121
|
} catch (error) {
|
|
95
122
|
logger.warn('Failed to load thread id from localStorage:', error);
|
|
96
123
|
|
|
97
124
|
return null;
|
|
98
125
|
}
|
|
99
|
-
}, [
|
|
126
|
+
}, [getThreadStorageKey]);
|
|
100
127
|
|
|
101
128
|
const applyThreadIdHeader = useCallback(
|
|
102
129
|
(headers: Record<string, string>) => {
|
|
@@ -128,8 +155,51 @@ export function WorkbenchProvider({
|
|
|
128
155
|
// Config values
|
|
129
156
|
const baseUrl = config.baseUrl === undefined ? defaultBaseUrl : config.baseUrl;
|
|
130
157
|
const apiKey = config.apiKey;
|
|
158
|
+
const configHeaders = config.headers;
|
|
131
159
|
const isBaseUrlNull = config.baseUrl === null;
|
|
132
160
|
|
|
161
|
+
// Helper to build request headers with config headers, auth, and thread ID
|
|
162
|
+
const buildRequestHeaders = useCallback(
|
|
163
|
+
(additionalHeaders?: Record<string, string>): Record<string, string> => {
|
|
164
|
+
const headers: Record<string, string> = {
|
|
165
|
+
...(configHeaders || {}),
|
|
166
|
+
...(additionalHeaders || {}),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (apiKey) {
|
|
170
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
applyThreadIdHeader(headers);
|
|
174
|
+
|
|
175
|
+
return headers;
|
|
176
|
+
},
|
|
177
|
+
[configHeaders, apiKey, applyThreadIdHeader]
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Async helper to get request headers including auth headers from callback
|
|
181
|
+
const getRequestHeaders = useCallback(
|
|
182
|
+
async (
|
|
183
|
+
body: string,
|
|
184
|
+
additionalHeaders?: Record<string, string>
|
|
185
|
+
): Promise<Record<string, string>> => {
|
|
186
|
+
const headers = buildRequestHeaders(additionalHeaders);
|
|
187
|
+
|
|
188
|
+
// Call getAuthHeaders callback if provided (use ref to avoid re-render loops)
|
|
189
|
+
if (getAuthHeadersRef.current) {
|
|
190
|
+
try {
|
|
191
|
+
const authHeaders = await getAuthHeadersRef.current(body);
|
|
192
|
+
Object.assign(headers, authHeaders);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
logger.warn('Failed to get auth headers:', error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return headers;
|
|
199
|
+
},
|
|
200
|
+
[buildRequestHeaders, logger]
|
|
201
|
+
);
|
|
202
|
+
|
|
133
203
|
// Log baseUrl state
|
|
134
204
|
useEffect(() => {
|
|
135
205
|
if (isBaseUrlNull) {
|
|
@@ -139,14 +209,19 @@ export function WorkbenchProvider({
|
|
|
139
209
|
}
|
|
140
210
|
}, [isBaseUrlNull, baseUrl, logger]);
|
|
141
211
|
|
|
142
|
-
// Set
|
|
212
|
+
// Set connection status based on baseUrl availability
|
|
213
|
+
// In cloud mode, we don't have websocket so we set connected when baseUrl is available
|
|
143
214
|
useEffect(() => {
|
|
144
215
|
if (isBaseUrlNull) {
|
|
145
216
|
logger.debug('🔌 Setting connection status to disconnected (baseUrl is null)');
|
|
146
|
-
|
|
147
217
|
setConnectionStatus('disconnected');
|
|
218
|
+
} else if (env.cloud) {
|
|
219
|
+
// In cloud mode, we're "connected" as soon as we have a baseUrl
|
|
220
|
+
// (no websocket to wait for)
|
|
221
|
+
logger.debug('🔌 Setting connection status to connected (cloud mode with baseUrl)');
|
|
222
|
+
setConnectionStatus('connected');
|
|
148
223
|
}
|
|
149
|
-
}, [isBaseUrlNull, logger]);
|
|
224
|
+
}, [isBaseUrlNull, env.cloud, logger]);
|
|
150
225
|
|
|
151
226
|
useEffect(() => {
|
|
152
227
|
if (isBaseUrlNull) {
|
|
@@ -160,24 +235,31 @@ export function WorkbenchProvider({
|
|
|
160
235
|
error: schemasError,
|
|
161
236
|
refetch: refetchSchemas,
|
|
162
237
|
} = useAgentSchemas({
|
|
163
|
-
baseUrl,
|
|
238
|
+
baseUrl: baseUrl ?? undefined,
|
|
164
239
|
apiKey,
|
|
240
|
+
headers: configHeaders,
|
|
165
241
|
enabled: !isBaseUrlNull,
|
|
242
|
+
getAuthHeaders,
|
|
166
243
|
});
|
|
167
244
|
|
|
168
245
|
// WebSocket connection for dev server restart detection
|
|
169
|
-
|
|
246
|
+
// Only enable for local dev - deployed agents don't have websocket endpoints
|
|
247
|
+
const wsEnabled = !isBaseUrlNull && !env.cloud;
|
|
248
|
+
const wsBaseUrl = wsEnabled && baseUrl ? baseUrl : undefined;
|
|
170
249
|
|
|
171
250
|
useEffect(() => {
|
|
172
251
|
if (isBaseUrlNull) {
|
|
173
252
|
logger.debug('🔌 WebSocket connection disabled (baseUrl is null)');
|
|
253
|
+
} else if (env.cloud) {
|
|
254
|
+
logger.debug('🔌 WebSocket connection disabled (cloud mode)');
|
|
174
255
|
}
|
|
175
|
-
}, [isBaseUrlNull, logger]);
|
|
256
|
+
}, [isBaseUrlNull, env.cloud, logger]);
|
|
176
257
|
|
|
177
258
|
const { connected } = useWorkbenchWebsocket({
|
|
178
|
-
enabled:
|
|
259
|
+
enabled: wsEnabled,
|
|
179
260
|
baseUrl: wsBaseUrl,
|
|
180
261
|
apiKey,
|
|
262
|
+
headers: configHeaders,
|
|
181
263
|
onConnect: () => {
|
|
182
264
|
setConnectionStatus('connected');
|
|
183
265
|
refetchSchemas();
|
|
@@ -196,10 +278,12 @@ export function WorkbenchProvider({
|
|
|
196
278
|
});
|
|
197
279
|
|
|
198
280
|
useEffect(() => {
|
|
199
|
-
|
|
281
|
+
// In cloud mode, websocket is disabled so we stay 'connected' (no live connection tracking)
|
|
282
|
+
// In local mode, track the websocket connection status
|
|
283
|
+
if (!isBaseUrlNull && !env.cloud && !connected && connectionStatus !== 'restarting') {
|
|
200
284
|
setConnectionStatus('disconnected');
|
|
201
285
|
}
|
|
202
|
-
}, [connected, connectionStatus, isBaseUrlNull]);
|
|
286
|
+
}, [connected, connectionStatus, isBaseUrlNull, env.cloud]);
|
|
203
287
|
|
|
204
288
|
// Convert schema data to Agent format, no fallback
|
|
205
289
|
const agents = schemaData?.agents;
|
|
@@ -228,18 +312,11 @@ export function WorkbenchProvider({
|
|
|
228
312
|
}
|
|
229
313
|
|
|
230
314
|
try {
|
|
231
|
-
const headers: Record<string, string> = {};
|
|
232
|
-
|
|
233
|
-
if (apiKey) {
|
|
234
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
applyThreadIdHeader(headers);
|
|
238
|
-
|
|
239
315
|
const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
|
|
240
316
|
|
|
241
317
|
logger.debug('📡 Fetching state for agent:', agentId);
|
|
242
318
|
|
|
319
|
+
const headers = await getRequestHeaders('');
|
|
243
320
|
const response = await fetch(url, {
|
|
244
321
|
method: 'GET',
|
|
245
322
|
headers,
|
|
@@ -295,7 +372,7 @@ export function WorkbenchProvider({
|
|
|
295
372
|
setMessages([]);
|
|
296
373
|
}
|
|
297
374
|
},
|
|
298
|
-
[baseUrl,
|
|
375
|
+
[baseUrl, logger, getRequestHeaders, persistThreadIdFromResponse]
|
|
299
376
|
);
|
|
300
377
|
|
|
301
378
|
// Set initial agent selection
|
|
@@ -369,6 +446,31 @@ export function WorkbenchProvider({
|
|
|
369
446
|
}
|
|
370
447
|
}, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger, fetchAgentState]);
|
|
371
448
|
|
|
449
|
+
// Validate selected agent still exists when agents list changes (e.g., switching local ↔ cloud)
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
if (!agents || Object.keys(agents).length === 0 || !selectedAgent) return;
|
|
452
|
+
|
|
453
|
+
const agentExists = Object.values(agents).some(
|
|
454
|
+
(agent) => agent.metadata.agentId === selectedAgent
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
if (!agentExists) {
|
|
458
|
+
logger.debug('⚠️ Selected agent no longer exists, falling back to first agent');
|
|
459
|
+
|
|
460
|
+
const sortedAgents = Object.values(agents).sort((a, b) =>
|
|
461
|
+
a.metadata.name.localeCompare(b.metadata.name)
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
const firstAgent = sortedAgents[0];
|
|
465
|
+
|
|
466
|
+
if (firstAgent) {
|
|
467
|
+
setSelectedAgent(firstAgent.metadata.agentId);
|
|
468
|
+
saveSelectedAgent(firstAgent.metadata.agentId);
|
|
469
|
+
fetchAgentState(firstAgent.metadata.agentId);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}, [agents, selectedAgent, logger, saveSelectedAgent, fetchAgentState]);
|
|
473
|
+
|
|
372
474
|
const submitMessage = async (value: string, _mode: 'text' | 'form' = 'text') => {
|
|
373
475
|
if (!selectedAgent) return;
|
|
374
476
|
|
|
@@ -456,16 +558,6 @@ export function WorkbenchProvider({
|
|
|
456
558
|
|
|
457
559
|
logger.debug('🌐 About to make API call...');
|
|
458
560
|
|
|
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
561
|
const startTime = performance.now();
|
|
470
562
|
|
|
471
563
|
try {
|
|
@@ -473,13 +565,17 @@ export function WorkbenchProvider({
|
|
|
473
565
|
agentId: selectedAgent,
|
|
474
566
|
input: parsedInput,
|
|
475
567
|
};
|
|
568
|
+
const requestBody = JSON.stringify(requestPayload);
|
|
476
569
|
|
|
477
570
|
logger.debug('📤 API Request payload:', requestPayload);
|
|
478
571
|
|
|
572
|
+
const headers = await getRequestHeaders(requestBody, {
|
|
573
|
+
'Content-Type': 'application/json',
|
|
574
|
+
});
|
|
479
575
|
const response = await fetch(`${baseUrl}/_agentuity/workbench/execute`, {
|
|
480
576
|
method: 'POST',
|
|
481
577
|
headers,
|
|
482
|
-
body:
|
|
578
|
+
body: requestBody,
|
|
483
579
|
credentials: 'include',
|
|
484
580
|
});
|
|
485
581
|
|
|
@@ -641,19 +737,8 @@ export function WorkbenchProvider({
|
|
|
641
737
|
|
|
642
738
|
try {
|
|
643
739
|
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
740
|
|
|
741
|
+
const headers = await getRequestHeaders('', { 'Content-Type': 'application/json' });
|
|
657
742
|
const response = await fetch(url, {
|
|
658
743
|
method: 'GET',
|
|
659
744
|
headers,
|
|
@@ -713,15 +798,8 @@ export function WorkbenchProvider({
|
|
|
713
798
|
}
|
|
714
799
|
|
|
715
800
|
try {
|
|
716
|
-
const headers: Record<string, string> = {};
|
|
717
|
-
|
|
718
|
-
if (apiKey) {
|
|
719
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
applyThreadIdHeader(headers);
|
|
723
|
-
|
|
724
801
|
const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
|
|
802
|
+
const headers = await getRequestHeaders('');
|
|
725
803
|
const response = await fetch(url, {
|
|
726
804
|
method: 'DELETE',
|
|
727
805
|
headers,
|
|
@@ -741,7 +819,7 @@ export function WorkbenchProvider({
|
|
|
741
819
|
logger.debug('⚠️ Error clearing state:', error);
|
|
742
820
|
}
|
|
743
821
|
},
|
|
744
|
-
[baseUrl,
|
|
822
|
+
[baseUrl, logger, getRequestHeaders, persistThreadIdFromResponse]
|
|
745
823
|
);
|
|
746
824
|
|
|
747
825
|
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,8 @@ export function useAgentSchemas(options: UseAgentSchemasOptions = {}): UseAgentS
|
|
|
131
149
|
} finally {
|
|
132
150
|
setIsLoading(false);
|
|
133
151
|
}
|
|
134
|
-
|
|
152
|
+
// eslint-disable-next-line -- getAuthHeadersRef is a ref
|
|
153
|
+
}, [baseUrl, apiKey, enabled, configHeaders, logger]);
|
|
135
154
|
|
|
136
155
|
const refetch = useCallback(() => {
|
|
137
156
|
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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
interface ReconnectOptions {
|
|
4
5
|
baseDelay?: number;
|
|
5
6
|
enabled?: () => boolean;
|
|
@@ -88,6 +89,7 @@ export interface UseWorkbenchWebsocketOptions {
|
|
|
88
89
|
apiKey?: string;
|
|
89
90
|
baseUrl?: string;
|
|
90
91
|
enabled?: boolean;
|
|
92
|
+
headers?: Record<string, string>;
|
|
91
93
|
onAlive?: () => void;
|
|
92
94
|
onConnect?: () => void;
|
|
93
95
|
onReconnect?: () => void;
|
|
@@ -102,7 +104,7 @@ export interface UseWorkbenchWebsocketResult {
|
|
|
102
104
|
export function useWorkbenchWebsocket(
|
|
103
105
|
options: UseWorkbenchWebsocketOptions = {}
|
|
104
106
|
): UseWorkbenchWebsocketResult {
|
|
105
|
-
const { baseUrl, apiKey, onConnect, onReconnect, onAlive, onRestarting } = options;
|
|
107
|
+
const { baseUrl, apiKey, headers, onConnect, onReconnect, onAlive, onRestarting } = options;
|
|
106
108
|
|
|
107
109
|
const [connected, setConnected] = useState(false);
|
|
108
110
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -138,8 +140,20 @@ export function useWorkbenchWebsocket(
|
|
|
138
140
|
url.searchParams.set('apiKey', apiKey);
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
// Pass any manual headers as query params (WebSocket can't use custom headers in browser)
|
|
144
|
+
if (headers) {
|
|
145
|
+
const signature = headers['X-Agentuity-Workbench-Signature'];
|
|
146
|
+
const timestamp = headers['X-Agentuity-Workbench-Timestamp'];
|
|
147
|
+
if (signature) {
|
|
148
|
+
url.searchParams.set('signature', signature);
|
|
149
|
+
}
|
|
150
|
+
if (timestamp) {
|
|
151
|
+
url.searchParams.set('timestamp', timestamp);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
return url.toString();
|
|
142
|
-
}, [baseUrl, apiKey]);
|
|
156
|
+
}, [baseUrl, apiKey, headers]);
|
|
143
157
|
|
|
144
158
|
const connect = useCallback(() => {
|
|
145
159
|
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';
|