@datalayer/agent-runtimes 0.0.7 → 0.0.9

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 (104) hide show
  1. package/README.md +9 -0
  2. package/lib/components/chat/components/AgentDetails.d.ts +14 -1
  3. package/lib/components/chat/components/AgentDetails.js +3 -2
  4. package/lib/components/chat/components/AgentIdentity.d.ts +92 -0
  5. package/lib/components/chat/components/AgentIdentity.js +318 -0
  6. package/lib/components/chat/components/Chat.d.ts +20 -1
  7. package/lib/components/chat/components/Chat.js +16 -3
  8. package/lib/components/chat/components/ChatFloating.d.ts +6 -1
  9. package/lib/components/chat/components/ChatFloating.js +12 -6
  10. package/lib/components/chat/components/base/ChatBase.d.ts +47 -1
  11. package/lib/components/chat/components/base/ChatBase.js +242 -63
  12. package/lib/components/chat/components/display/ToolCallDisplay.d.ts +16 -2
  13. package/lib/components/chat/components/display/ToolCallDisplay.js +148 -6
  14. package/lib/components/chat/components/display/index.d.ts +1 -1
  15. package/lib/components/chat/components/display/index.js +1 -1
  16. package/lib/components/chat/components/elements/ChatInputPrompt.d.ts +12 -1
  17. package/lib/components/chat/components/elements/ChatInputPrompt.js +8 -3
  18. package/lib/components/chat/components/index.d.ts +1 -0
  19. package/lib/components/chat/components/index.js +1 -0
  20. package/lib/components/chat/components/parts/ToolPart.d.ts +1 -1
  21. package/lib/components/chat/components/parts/ToolPart.js +142 -6
  22. package/lib/components/chat/index.d.ts +1 -1
  23. package/lib/components/chat/index.js +1 -1
  24. package/lib/components/chat/protocols/A2AAdapter.d.ts +9 -0
  25. package/lib/components/chat/protocols/A2AAdapter.js +13 -2
  26. package/lib/components/chat/protocols/ACPAdapter.d.ts +9 -0
  27. package/lib/components/chat/protocols/ACPAdapter.js +13 -2
  28. package/lib/components/chat/protocols/AGUIAdapter.d.ts +9 -0
  29. package/lib/components/chat/protocols/AGUIAdapter.js +19 -1
  30. package/lib/components/chat/protocols/VercelAIAdapter.d.ts +7 -0
  31. package/lib/components/chat/protocols/VercelAIAdapter.js +19 -0
  32. package/lib/components/chat/types/execution.d.ts +78 -0
  33. package/lib/components/chat/types/execution.js +64 -0
  34. package/lib/components/chat/types/index.d.ts +1 -0
  35. package/lib/components/chat/types/index.js +1 -0
  36. package/lib/components/chat/types/protocol.d.ts +9 -0
  37. package/lib/components/ui/pagination.d.ts +2 -2
  38. package/lib/components/ui/pagination.js +4 -4
  39. package/lib/components/ui/resizable.d.ts +4 -4
  40. package/lib/components/ui/resizable.js +4 -4
  41. package/lib/examples/A2UiRestaurantExample.js +2 -2
  42. package/lib/examples/AgUiAgenticExample.js +2 -2
  43. package/lib/examples/AgUiBackendToolRenderingExample.js +2 -2
  44. package/lib/examples/AgUiHaikuGenUIExample.js +2 -2
  45. package/lib/examples/AgUiHumanInTheLoopExample.js +2 -2
  46. package/lib/examples/AgUiSharedStateExample.js +2 -2
  47. package/lib/examples/AgUiToolsBasedGenUIExample.js +2 -2
  48. package/lib/examples/AgentRuntimeCustomExample.js +2 -2
  49. package/lib/examples/AgentRuntimeLexical2Example.js +2 -1
  50. package/lib/examples/AgentRuntimeLexicalExample.js +5 -2
  51. package/lib/examples/AgentRuntimeLexicalSidebarExample.js +4 -2
  52. package/lib/examples/AgentRuntimeNotebookExample.js +1 -1
  53. package/lib/examples/AgentRuntimeStandaloneExample.js +2 -2
  54. package/lib/examples/AgentSpaceFormExample.d.ts +70 -2
  55. package/lib/examples/AgentSpaceFormExample.js +177 -43
  56. package/lib/examples/CopilotKitLexicalExample.js +2 -1
  57. package/lib/examples/components/AgentConfiguration.d.ts +17 -2
  58. package/lib/examples/components/AgentConfiguration.js +220 -16
  59. package/lib/examples/components/LexicalEditor.js +2 -1
  60. package/lib/examples/components/MockFileBrowser.js +6 -2
  61. package/lib/examples/components/index.d.ts +0 -1
  62. package/lib/examples/components/index.js +0 -1
  63. package/lib/examples/example-selector.js +0 -1
  64. package/lib/examples/index.d.ts +0 -1
  65. package/lib/examples/index.js +0 -1
  66. package/lib/examples/lexical/editorConfig.d.ts +3 -2
  67. package/lib/examples/lexical/editorConfig.js +7 -1
  68. package/lib/examples/lexical/initial-content.json +2210 -0
  69. package/lib/examples/main.js +15 -1
  70. package/lib/identity/IdentityConnect.d.ts +90 -0
  71. package/lib/identity/IdentityConnect.js +316 -0
  72. package/lib/identity/OAuthCallback.d.ts +58 -0
  73. package/lib/identity/OAuthCallback.js +223 -0
  74. package/lib/identity/dcr.d.ts +257 -0
  75. package/lib/identity/dcr.js +282 -0
  76. package/lib/identity/identityStore.d.ts +72 -0
  77. package/lib/identity/identityStore.js +529 -0
  78. package/lib/identity/index.d.ts +46 -0
  79. package/lib/identity/index.js +17 -0
  80. package/lib/identity/pkce.d.ts +30 -0
  81. package/lib/identity/pkce.js +65 -0
  82. package/lib/identity/types.d.ts +293 -0
  83. package/lib/identity/types.js +73 -0
  84. package/lib/identity/useIdentity.d.ts +108 -0
  85. package/lib/identity/useIdentity.js +323 -0
  86. package/lib/index.d.ts +1 -0
  87. package/lib/index.js +1 -0
  88. package/lib/lib/utils.js +1 -1
  89. package/lib/renderers/a2ui/lib/utils.js +1 -1
  90. package/lib/test-setup.d.ts +1 -1
  91. package/lib/test-setup.js +1 -0
  92. package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.js +32 -1
  93. package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +6 -0
  94. package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +16 -17
  95. package/package.json +20 -7
  96. package/patches/@datalayer+jupyter-lexical+1.0.8.patch +11628 -0
  97. package/patches/@datalayer+jupyter-react+2.0.2.patch +5338 -0
  98. package/scripts/apply-patches.sh +32 -0
  99. package/lib/examples/AgentSpaceHomeExample.d.ts +0 -8
  100. package/lib/examples/AgentSpaceHomeExample.js +0 -171
  101. package/lib/examples/components/AgentsDataTable.d.ts +0 -13
  102. package/lib/examples/components/AgentsDataTable.js +0 -74
  103. package/lib/examples/components/Rating.d.ts +0 -14
  104. package/lib/examples/components/Rating.js +0 -12
@@ -0,0 +1,529 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Zustand store for identity management.
7
+ *
8
+ * Provides OAuth 2.1 identity management with PKCE support.
9
+ *
10
+ * @module identity/identityStore
11
+ */
12
+ import { create } from 'zustand';
13
+ import { devtools, subscribeWithSelector, persist } from 'zustand/middleware';
14
+ import { GITHUB_PROVIDER, GOOGLE_PROVIDER, KAGGLE_PROVIDER } from './types';
15
+ import { generatePKCEPair, generateState } from './pkce';
16
+ /**
17
+ * Storage key for persisted identity data
18
+ */
19
+ const STORAGE_KEY = 'datalayer-agent-identities';
20
+ /**
21
+ * Token exchange endpoint on the agent-runtimes server
22
+ */
23
+ const DEFAULT_TOKEN_EXCHANGE_ENDPOINT = '/api/v1/identity/oauth/token';
24
+ const DEFAULT_USER_INFO_ENDPOINT = '/api/v1/identity/oauth/userinfo';
25
+ const DEFAULT_REVOKE_ENDPOINT = '/api/v1/identity/oauth/revoke';
26
+ /**
27
+ * Initial state
28
+ */
29
+ const initialState = {
30
+ identities: new Map(),
31
+ pendingAuthorization: null,
32
+ providerConfigs: new Map(),
33
+ isLoading: false,
34
+ error: null,
35
+ };
36
+ /**
37
+ * Build OAuth authorization URL with PKCE
38
+ */
39
+ function buildAuthorizationUrl(config, scopes, state, codeChallenge) {
40
+ const params = new URLSearchParams({
41
+ client_id: config.clientId,
42
+ redirect_uri: config.redirectUri,
43
+ response_type: 'code',
44
+ scope: scopes.join(' '),
45
+ state: state,
46
+ code_challenge: codeChallenge,
47
+ code_challenge_method: 'S256',
48
+ ...config.additionalParams,
49
+ });
50
+ return `${config.authorizationUrl}?${params.toString()}`;
51
+ }
52
+ /**
53
+ * Create the identity store
54
+ */
55
+ export const useIdentityStore = create()(devtools(subscribeWithSelector(persist((set, get) => ({
56
+ ...initialState,
57
+ configureProvider: (config) => {
58
+ set(state => {
59
+ const newConfigs = new Map(state.providerConfigs);
60
+ newConfigs.set(config.provider, config);
61
+ return { providerConfigs: newConfigs };
62
+ });
63
+ },
64
+ startAuthorization: async (provider, scopes, options) => {
65
+ const state = get();
66
+ const config = state.providerConfigs.get(provider);
67
+ if (!config) {
68
+ throw new Error(`Provider ${provider} is not configured`);
69
+ }
70
+ // Generate PKCE pair
71
+ const { codeVerifier, codeChallenge } = await generatePKCEPair();
72
+ const stateParam = generateState();
73
+ const effectiveScopes = scopes || config.defaultScopes;
74
+ // Build authorization URL
75
+ const authUrl = buildAuthorizationUrl(config, effectiveScopes, stateParam, codeChallenge);
76
+ // Create pending authorization
77
+ const request = {
78
+ requestId: `auth-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
79
+ provider,
80
+ authUrl,
81
+ state: stateParam,
82
+ codeVerifier,
83
+ scopes: effectiveScopes,
84
+ requestedAt: Date.now(),
85
+ onComplete: options?.onComplete,
86
+ onError: options?.onError,
87
+ };
88
+ set({ pendingAuthorization: request, error: null });
89
+ return authUrl;
90
+ },
91
+ completeAuthorization: async (callback) => {
92
+ const state = get();
93
+ const pending = state.pendingAuthorization;
94
+ if (!pending) {
95
+ throw new Error('No pending authorization request');
96
+ }
97
+ // Verify state parameter (CSRF protection)
98
+ if (callback.state !== pending.state) {
99
+ const error = new Error('State mismatch - possible CSRF attack');
100
+ pending.onError?.(error);
101
+ set({ pendingAuthorization: null, error });
102
+ throw error;
103
+ }
104
+ // Check for OAuth error
105
+ if (callback.error) {
106
+ const error = new Error(callback.errorDescription || callback.error);
107
+ pending.onError?.(error);
108
+ set({ pendingAuthorization: null, error });
109
+ throw error;
110
+ }
111
+ set({ isLoading: true });
112
+ try {
113
+ const config = state.providerConfigs.get(pending.provider);
114
+ if (!config) {
115
+ throw new Error(`Provider ${pending.provider} is not configured`);
116
+ }
117
+ // Exchange code for token via backend proxy
118
+ // This keeps client_secret secure on the server
119
+ const baseUrl = config.redirectUri
120
+ .split('/')
121
+ .slice(0, 3)
122
+ .join('/');
123
+ const tokenResponse = await fetch(`${baseUrl}${DEFAULT_TOKEN_EXCHANGE_ENDPOINT}`, {
124
+ method: 'POST',
125
+ headers: { 'Content-Type': 'application/json' },
126
+ body: JSON.stringify({
127
+ provider: pending.provider,
128
+ code: callback.code,
129
+ code_verifier: pending.codeVerifier,
130
+ redirect_uri: config.redirectUri,
131
+ }),
132
+ });
133
+ if (!tokenResponse.ok) {
134
+ const errorData = await tokenResponse.json().catch(() => ({}));
135
+ throw new Error(errorData.detail ||
136
+ `Token exchange failed: ${tokenResponse.status}`);
137
+ }
138
+ const tokenData = await tokenResponse.json();
139
+ const token = {
140
+ accessToken: tokenData.access_token,
141
+ tokenType: tokenData.token_type || 'Bearer',
142
+ expiresAt: tokenData.expires_in
143
+ ? Date.now() + tokenData.expires_in * 1000
144
+ : undefined,
145
+ refreshToken: tokenData.refresh_token,
146
+ scopes: pending.scopes,
147
+ };
148
+ // Fetch user info
149
+ let userInfo;
150
+ if (config.userInfoUrl) {
151
+ try {
152
+ const userInfoResponse = await fetch(`${baseUrl}${DEFAULT_USER_INFO_ENDPOINT}`, {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({
156
+ provider: pending.provider,
157
+ access_token: token.accessToken,
158
+ }),
159
+ });
160
+ if (userInfoResponse.ok) {
161
+ const userInfoData = await userInfoResponse.json();
162
+ // Map provider-specific user info fields to common format
163
+ // - GitHub: login, avatar_url, html_url
164
+ // - Google: sub (id), picture, profile
165
+ // - Others: username, id, etc.
166
+ userInfo = {
167
+ id: userInfoData.id?.toString() || userInfoData.sub,
168
+ username: userInfoData.login || userInfoData.username, // GitHub uses 'login'
169
+ name: userInfoData.name,
170
+ email: userInfoData.email,
171
+ avatarUrl: userInfoData.avatar_url || userInfoData.picture, // GitHub: avatar_url, Google: picture
172
+ profileUrl: userInfoData.html_url || userInfoData.profile, // GitHub: html_url, Google: profile
173
+ raw: userInfoData,
174
+ };
175
+ }
176
+ }
177
+ catch (e) {
178
+ console.warn('Failed to fetch user info:', e);
179
+ }
180
+ }
181
+ // Create identity
182
+ const identity = {
183
+ provider: pending.provider,
184
+ displayName: config.displayName,
185
+ iconUrl: config.iconUrl,
186
+ scopes: pending.scopes,
187
+ isConnected: true,
188
+ connectedAt: Date.now(),
189
+ userInfo,
190
+ token,
191
+ };
192
+ // Update store
193
+ set(state => {
194
+ const newIdentities = new Map(state.identities);
195
+ newIdentities.set(pending.provider, identity);
196
+ return {
197
+ identities: newIdentities,
198
+ pendingAuthorization: null,
199
+ isLoading: false,
200
+ error: null,
201
+ };
202
+ });
203
+ // Call completion callback
204
+ pending.onComplete?.(identity);
205
+ return identity;
206
+ }
207
+ catch (error) {
208
+ const err = error instanceof Error ? error : new Error(String(error));
209
+ pending.onError?.(err);
210
+ set({ isLoading: false, error: err, pendingAuthorization: null });
211
+ throw err;
212
+ }
213
+ },
214
+ cancelAuthorization: () => {
215
+ const pending = get().pendingAuthorization;
216
+ if (pending?.onError) {
217
+ pending.onError(new Error('Authorization cancelled'));
218
+ }
219
+ set({ pendingAuthorization: null });
220
+ },
221
+ connectWithToken: async (provider, token, options) => {
222
+ set({ isLoading: true, error: null });
223
+ try {
224
+ // Create token object (token-based auth doesn't expire)
225
+ const oauthToken = {
226
+ accessToken: token,
227
+ tokenType: 'Bearer',
228
+ scopes: [], // Token-based auth doesn't have scopes
229
+ };
230
+ // Provider display config
231
+ const displayConfig = {
232
+ kaggle: {
233
+ displayName: 'Kaggle',
234
+ iconUrl: 'https://www.kaggle.com/static/images/favicon.ico',
235
+ },
236
+ }[provider] || { displayName: provider, iconUrl: undefined };
237
+ // Create identity
238
+ const identity = {
239
+ provider,
240
+ authType: 'token',
241
+ displayName: options?.displayName || displayConfig.displayName,
242
+ iconUrl: options?.iconUrl || displayConfig.iconUrl,
243
+ scopes: [],
244
+ isConnected: true,
245
+ connectedAt: Date.now(),
246
+ userInfo: options?.userInfo,
247
+ token: oauthToken,
248
+ };
249
+ // Update store
250
+ set(state => {
251
+ const newIdentities = new Map(state.identities);
252
+ newIdentities.set(provider, identity);
253
+ return {
254
+ identities: newIdentities,
255
+ isLoading: false,
256
+ error: null,
257
+ };
258
+ });
259
+ return identity;
260
+ }
261
+ catch (error) {
262
+ const err = error instanceof Error ? error : new Error(String(error));
263
+ set({ isLoading: false, error: err });
264
+ throw err;
265
+ }
266
+ },
267
+ disconnect: async (provider) => {
268
+ const state = get();
269
+ const identity = state.identities.get(provider);
270
+ const config = state.providerConfigs.get(provider);
271
+ if (!identity || !identity.token) {
272
+ // Already disconnected
273
+ set(state => {
274
+ const newIdentities = new Map(state.identities);
275
+ newIdentities.delete(provider);
276
+ return { identities: newIdentities };
277
+ });
278
+ return;
279
+ }
280
+ // For token-based auth, just remove from store (no revocation)
281
+ if (identity.authType === 'token') {
282
+ set(state => {
283
+ const newIdentities = new Map(state.identities);
284
+ newIdentities.delete(provider);
285
+ return { identities: newIdentities };
286
+ });
287
+ return;
288
+ }
289
+ // Try to revoke token (OAuth only)
290
+ if (config?.revocationUrl) {
291
+ try {
292
+ const baseUrl = config.redirectUri
293
+ .split('/')
294
+ .slice(0, 3)
295
+ .join('/');
296
+ await fetch(`${baseUrl}${DEFAULT_REVOKE_ENDPOINT}`, {
297
+ method: 'POST',
298
+ headers: { 'Content-Type': 'application/json' },
299
+ body: JSON.stringify({
300
+ provider,
301
+ access_token: identity.token.accessToken,
302
+ }),
303
+ });
304
+ }
305
+ catch (e) {
306
+ console.warn('Failed to revoke token:', e);
307
+ }
308
+ }
309
+ // Remove from store
310
+ set(state => {
311
+ const newIdentities = new Map(state.identities);
312
+ newIdentities.delete(provider);
313
+ return { identities: newIdentities };
314
+ });
315
+ },
316
+ refreshToken: async (provider) => {
317
+ const state = get();
318
+ const identity = state.identities.get(provider);
319
+ const config = state.providerConfigs.get(provider);
320
+ if (!identity || !identity.token?.refreshToken) {
321
+ throw new Error(`No refresh token available for ${provider}`);
322
+ }
323
+ if (!config) {
324
+ throw new Error(`Provider ${provider} is not configured`);
325
+ }
326
+ const baseUrl = config.redirectUri.split('/').slice(0, 3).join('/');
327
+ const response = await fetch(`${baseUrl}${DEFAULT_TOKEN_EXCHANGE_ENDPOINT}`, {
328
+ method: 'POST',
329
+ headers: { 'Content-Type': 'application/json' },
330
+ body: JSON.stringify({
331
+ provider,
332
+ grant_type: 'refresh_token',
333
+ refresh_token: identity.token.refreshToken,
334
+ }),
335
+ });
336
+ if (!response.ok) {
337
+ throw new Error(`Token refresh failed: ${response.status}`);
338
+ }
339
+ const tokenData = await response.json();
340
+ const newToken = {
341
+ accessToken: tokenData.access_token,
342
+ tokenType: tokenData.token_type || 'Bearer',
343
+ expiresAt: tokenData.expires_in
344
+ ? Date.now() + tokenData.expires_in * 1000
345
+ : undefined,
346
+ refreshToken: tokenData.refresh_token || identity.token.refreshToken,
347
+ scopes: identity.scopes,
348
+ };
349
+ // Update identity with new token
350
+ set(state => {
351
+ const newIdentities = new Map(state.identities);
352
+ newIdentities.set(provider, {
353
+ ...identity,
354
+ token: newToken,
355
+ });
356
+ return { identities: newIdentities };
357
+ });
358
+ return newToken;
359
+ },
360
+ getIdentity: (provider) => {
361
+ return get().identities.get(provider);
362
+ },
363
+ isConnected: (provider) => {
364
+ const identity = get().identities.get(provider);
365
+ return identity?.isConnected ?? false;
366
+ },
367
+ getToken: async (provider) => {
368
+ const state = get();
369
+ const identity = state.identities.get(provider);
370
+ if (!identity || !identity.token) {
371
+ return null;
372
+ }
373
+ // Check if token is expired or about to expire (5 min buffer)
374
+ const expiresAt = identity.token.expiresAt;
375
+ if (expiresAt && expiresAt - Date.now() < 5 * 60 * 1000) {
376
+ // Token is expired or expiring soon, try to refresh
377
+ if (identity.token.refreshToken) {
378
+ try {
379
+ return await get().refreshToken(provider);
380
+ }
381
+ catch (e) {
382
+ console.warn('Token refresh failed:', e);
383
+ return null;
384
+ }
385
+ }
386
+ return null;
387
+ }
388
+ return identity.token;
389
+ },
390
+ clearAll: () => {
391
+ set({
392
+ identities: new Map(),
393
+ pendingAuthorization: null,
394
+ error: null,
395
+ });
396
+ },
397
+ setError: (error) => {
398
+ set({ error });
399
+ },
400
+ }), {
401
+ name: STORAGE_KEY,
402
+ // Custom serialization for Maps
403
+ storage: {
404
+ getItem: name => {
405
+ const str = localStorage.getItem(name);
406
+ if (!str)
407
+ return null;
408
+ const data = JSON.parse(str);
409
+ // Check if we're in an OAuth callback (code and state in URL)
410
+ // If not, clear any stale pendingAuthorization from previous sessions
411
+ const urlParams = new URLSearchParams(window.location.search);
412
+ const isOAuthCallback = urlParams.has('code') && urlParams.has('state');
413
+ return {
414
+ state: {
415
+ ...data.state,
416
+ identities: new Map(data.state.identities || []),
417
+ providerConfigs: new Map(data.state.providerConfigs || []),
418
+ // Only restore pendingAuthorization if we're in an OAuth callback
419
+ // Otherwise, clear it to prevent stale "Working..." state
420
+ pendingAuthorization: isOAuthCallback
421
+ ? data.state.pendingAuthorization || null
422
+ : null,
423
+ isLoading: false,
424
+ error: null,
425
+ },
426
+ };
427
+ },
428
+ setItem: (name, value) => {
429
+ const data = {
430
+ state: {
431
+ ...value.state,
432
+ identities: Array.from(value.state.identities.entries()),
433
+ providerConfigs: Array.from(value.state.providerConfigs.entries()),
434
+ // Persist pending auth so callback can complete after redirect
435
+ pendingAuthorization: value.state.pendingAuthorization
436
+ ? {
437
+ ...value.state.pendingAuthorization,
438
+ // Don't persist callbacks (functions can't be serialized)
439
+ onComplete: undefined,
440
+ onError: undefined,
441
+ }
442
+ : null,
443
+ isLoading: false,
444
+ error: null,
445
+ },
446
+ };
447
+ localStorage.setItem(name, JSON.stringify(data));
448
+ },
449
+ removeItem: name => localStorage.removeItem(name),
450
+ },
451
+ partialize: state => ({
452
+ identities: state.identities,
453
+ providerConfigs: state.providerConfigs,
454
+ pendingAuthorization: state.pendingAuthorization,
455
+ }),
456
+ })), { name: 'IdentityStore' }));
457
+ // === Selector Hooks ===
458
+ /**
459
+ * Get all connected identities
460
+ */
461
+ export function useConnectedIdentities() {
462
+ return useIdentityStore(state => Array.from(state.identities.values()).filter(i => i.isConnected));
463
+ }
464
+ /**
465
+ * Get connected provider names
466
+ */
467
+ export function useConnectedProviders() {
468
+ return useIdentityStore(state => Array.from(state.identities.entries())
469
+ .filter(([_, identity]) => identity.isConnected)
470
+ .map(([provider]) => provider));
471
+ }
472
+ /**
473
+ * Get identity for a specific provider
474
+ */
475
+ export function useIdentity(provider) {
476
+ return useIdentityStore(state => state.identities.get(provider));
477
+ }
478
+ /**
479
+ * Check if a provider is connected
480
+ */
481
+ export function useIsProviderConnected(provider) {
482
+ return useIdentityStore(state => state.identities.get(provider)?.isConnected ?? false);
483
+ }
484
+ /**
485
+ * Get pending authorization
486
+ */
487
+ export function usePendingAuthorization() {
488
+ return useIdentityStore(state => state.pendingAuthorization);
489
+ }
490
+ /**
491
+ * Get identity loading state
492
+ */
493
+ export function useIdentityLoading() {
494
+ return useIdentityStore(state => state.isLoading);
495
+ }
496
+ /**
497
+ * Get identity error
498
+ */
499
+ export function useIdentityError() {
500
+ return useIdentityStore(state => state.error);
501
+ }
502
+ // === Initialization Helpers ===
503
+ /**
504
+ * Configure built-in providers with client IDs
505
+ */
506
+ export function configureBuiltinProviders(options) {
507
+ const store = useIdentityStore.getState();
508
+ if (options.github) {
509
+ store.configureProvider({
510
+ ...GITHUB_PROVIDER,
511
+ clientId: options.github.clientId,
512
+ redirectUri: options.github.redirectUri,
513
+ });
514
+ }
515
+ if (options.google) {
516
+ store.configureProvider({
517
+ ...GOOGLE_PROVIDER,
518
+ clientId: options.google.clientId,
519
+ redirectUri: options.google.redirectUri,
520
+ });
521
+ }
522
+ if (options.kaggle) {
523
+ store.configureProvider({
524
+ ...KAGGLE_PROVIDER,
525
+ clientId: options.kaggle.clientId,
526
+ redirectUri: options.kaggle.redirectUri,
527
+ });
528
+ }
529
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Identity module for OAuth 2.1 user-delegated access.
3
+ *
4
+ * Provides secure identity management for AI agents to access
5
+ * external services like GitHub, Google, Kaggle, etc.
6
+ *
7
+ * Built-in provider support:
8
+ * - **GitHub**: OAuth 2.1 with PKCE (requires client_secret server-side)
9
+ * - **Google**: OAuth 2.1 with PKCE and offline access
10
+ * - **Kaggle**: Standard OAuth 2.1 for MCP server access
11
+ *
12
+ * Provider-specific notes:
13
+ * - GitHub requires `client_secret` for token exchange even with PKCE
14
+ * - GitHub returns user info from `https://api.github.com/user` with `login`, `avatar_url` fields
15
+ * - Google supports pure PKCE without client_secret
16
+ * - All providers need backend proxy for token exchange due to CORS restrictions
17
+ *
18
+ * @module identity
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * import { useIdentity } from '@datalayer/agent-runtimes';
23
+ *
24
+ * function MyComponent() {
25
+ * const { connect, disconnect, isConnected, identities } = useIdentity({
26
+ * providers: {
27
+ * github: { clientId: 'your-github-client-id' },
28
+ * },
29
+ * });
30
+ *
31
+ * return (
32
+ * <button onClick={() => connect('github', ['repo'])}>
33
+ * {isConnected('github') ? 'Disconnect GitHub' : 'Connect GitHub'}
34
+ * </button>
35
+ * );
36
+ * }
37
+ * ```
38
+ */
39
+ export type { OAuthProvider, OAuthToken, Identity, ProviderUserInfo, OAuthProviderConfig, AuthorizationRequest, AuthorizationCallback, IdentityState, IdentityActions, IdentityStore, AuthType, TokenProviderConfig, OAuthIdentityProviderConfig, TokenIdentityProviderConfig, IdentityProviderConfig, IdentityProvidersConfig, } from './types';
40
+ export { GITHUB_PROVIDER, GOOGLE_PROVIDER, KAGGLE_PROVIDER, KAGGLE_TOKEN_PROVIDER, } from './types';
41
+ export { generateCodeVerifier, generateCodeChallenge, generateState, generatePKCEPair, } from './pkce';
42
+ export { discoverAuthorizationServer, supportsDCR, registerClient, updateClientRegistration, deleteClientRegistration, getOrCreateDynamicClient, loadDynamicClient, saveDynamicClient, removeDynamicClient, getAllDynamicClients, clearAllDynamicClients, dynamicClientToProviderConfig, type AuthorizationServerMetadata, type ClientRegistrationRequest, type ClientRegistrationResponse, type ClientRegistrationError, type DynamicClient, } from './dcr';
43
+ export { useIdentityStore, useConnectedIdentities, useConnectedProviders, useIdentity as useIdentitySelector, useIsProviderConnected, usePendingAuthorization, useIdentityLoading, useIdentityError, configureBuiltinProviders, } from './identityStore';
44
+ export { useIdentity, type UseIdentityOptions, type UseIdentityReturn, } from './useIdentity';
45
+ export { IdentityButton, IdentityConnect, IdentityMenu, type IdentityButtonProps, type IdentityConnectProps, type IdentityMenuProps, } from './IdentityConnect';
46
+ export { OAuthCallback, type OAuthCallbackProps } from './OAuthCallback';
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export { GITHUB_PROVIDER, GOOGLE_PROVIDER, KAGGLE_PROVIDER, KAGGLE_TOKEN_PROVIDER, } from './types';
6
+ // PKCE utilities
7
+ export { generateCodeVerifier, generateCodeChallenge, generateState, generatePKCEPair, } from './pkce';
8
+ // Dynamic Client Registration (DCR)
9
+ export { discoverAuthorizationServer, supportsDCR, registerClient, updateClientRegistration, deleteClientRegistration, getOrCreateDynamicClient, loadDynamicClient, saveDynamicClient, removeDynamicClient, getAllDynamicClients, clearAllDynamicClients, dynamicClientToProviderConfig, } from './dcr';
10
+ // Store
11
+ export { useIdentityStore, useConnectedIdentities, useConnectedProviders, useIdentity as useIdentitySelector, useIsProviderConnected, usePendingAuthorization, useIdentityLoading, useIdentityError, configureBuiltinProviders, } from './identityStore';
12
+ // Main hook
13
+ export { useIdentity, } from './useIdentity';
14
+ // UI Components
15
+ export { IdentityButton, IdentityConnect, IdentityMenu, } from './IdentityConnect';
16
+ // OAuth Callback
17
+ export { OAuthCallback } from './OAuthCallback';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.1.
3
+ *
4
+ * @module identity/pkce
5
+ */
6
+ /**
7
+ * Generate a cryptographically random code verifier for PKCE.
8
+ * @param length - Length of the verifier (default: 64, min: 43, max: 128)
9
+ * @returns Base64URL-encoded code verifier
10
+ */
11
+ export declare function generateCodeVerifier(length?: number): string;
12
+ /**
13
+ * Generate a code challenge from a code verifier using SHA-256.
14
+ * @param codeVerifier - The code verifier string
15
+ * @returns Base64URL-encoded SHA-256 hash of the verifier
16
+ */
17
+ export declare function generateCodeChallenge(codeVerifier: string): Promise<string>;
18
+ /**
19
+ * Generate a random state parameter for CSRF protection.
20
+ * @returns Random state string
21
+ */
22
+ export declare function generateState(): string;
23
+ /**
24
+ * Generate a PKCE pair (code verifier and challenge).
25
+ * @returns Object with codeVerifier and codeChallenge
26
+ */
27
+ export declare function generatePKCEPair(): Promise<{
28
+ codeVerifier: string;
29
+ codeChallenge: string;
30
+ }>;
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.1.
7
+ *
8
+ * @module identity/pkce
9
+ */
10
+ /**
11
+ * Generate a cryptographically random code verifier for PKCE.
12
+ * @param length - Length of the verifier (default: 64, min: 43, max: 128)
13
+ * @returns Base64URL-encoded code verifier
14
+ */
15
+ export function generateCodeVerifier(length = 64) {
16
+ const validLength = Math.max(43, Math.min(128, length));
17
+ const array = new Uint8Array(validLength);
18
+ crypto.getRandomValues(array);
19
+ return base64UrlEncode(array);
20
+ }
21
+ /**
22
+ * Generate a code challenge from a code verifier using SHA-256.
23
+ * @param codeVerifier - The code verifier string
24
+ * @returns Base64URL-encoded SHA-256 hash of the verifier
25
+ */
26
+ export async function generateCodeChallenge(codeVerifier) {
27
+ const encoder = new TextEncoder();
28
+ const data = encoder.encode(codeVerifier);
29
+ const digest = await crypto.subtle.digest('SHA-256', data);
30
+ return base64UrlEncode(new Uint8Array(digest));
31
+ }
32
+ /**
33
+ * Generate a random state parameter for CSRF protection.
34
+ * @returns Random state string
35
+ */
36
+ export function generateState() {
37
+ const array = new Uint8Array(32);
38
+ crypto.getRandomValues(array);
39
+ return base64UrlEncode(array);
40
+ }
41
+ /**
42
+ * Base64URL encode a Uint8Array (no padding, URL-safe).
43
+ * @param data - Data to encode
44
+ * @returns Base64URL-encoded string
45
+ */
46
+ function base64UrlEncode(data) {
47
+ // Convert to base64
48
+ let base64 = '';
49
+ const bytes = new Uint8Array(data);
50
+ for (let i = 0; i < bytes.length; i++) {
51
+ base64 += String.fromCharCode(bytes[i]);
52
+ }
53
+ base64 = btoa(base64);
54
+ // Convert to base64url (URL-safe, no padding)
55
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
56
+ }
57
+ /**
58
+ * Generate a PKCE pair (code verifier and challenge).
59
+ * @returns Object with codeVerifier and codeChallenge
60
+ */
61
+ export async function generatePKCEPair() {
62
+ const codeVerifier = generateCodeVerifier();
63
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
64
+ return { codeVerifier, codeChallenge };
65
+ }