@portel/photon-core 1.5.0 → 2.1.0

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 (163) hide show
  1. package/dist/auto-ui.js +1 -1
  2. package/dist/auto-ui.js.map +1 -1
  3. package/dist/base.d.ts +1 -1
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +2 -2
  6. package/dist/base.js.map +1 -1
  7. package/dist/cli-ui-renderer.js +1 -1
  8. package/dist/cli-ui-renderer.js.map +1 -1
  9. package/dist/design-system/index.d.ts +21 -0
  10. package/dist/design-system/index.d.ts.map +1 -0
  11. package/dist/design-system/index.js +27 -0
  12. package/dist/design-system/index.js.map +1 -0
  13. package/dist/design-system/tokens.d.ts +149 -0
  14. package/dist/design-system/tokens.d.ts.map +1 -0
  15. package/dist/design-system/tokens.js +413 -0
  16. package/dist/design-system/tokens.js.map +1 -0
  17. package/dist/design-system/transaction-ui.d.ts +70 -0
  18. package/dist/design-system/transaction-ui.d.ts.map +1 -0
  19. package/dist/design-system/transaction-ui.js +982 -0
  20. package/dist/design-system/transaction-ui.js.map +1 -0
  21. package/dist/generator.d.ts +56 -6
  22. package/dist/generator.d.ts.map +1 -1
  23. package/dist/generator.js.map +1 -1
  24. package/dist/index.d.ts +6 -7
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +46 -56
  27. package/dist/index.js.map +1 -1
  28. package/dist/io.d.ts +103 -2
  29. package/dist/io.d.ts.map +1 -1
  30. package/dist/io.js +37 -1
  31. package/dist/io.js.map +1 -1
  32. package/dist/rendering/components.d.ts +29 -0
  33. package/dist/rendering/components.d.ts.map +1 -0
  34. package/dist/rendering/components.js +773 -0
  35. package/dist/rendering/components.js.map +1 -0
  36. package/dist/rendering/field-analyzer.d.ts +48 -0
  37. package/dist/rendering/field-analyzer.d.ts.map +1 -0
  38. package/dist/rendering/field-analyzer.js +270 -0
  39. package/dist/rendering/field-analyzer.js.map +1 -0
  40. package/dist/rendering/field-renderers.d.ts +64 -0
  41. package/dist/rendering/field-renderers.d.ts.map +1 -0
  42. package/dist/rendering/field-renderers.js +317 -0
  43. package/dist/rendering/field-renderers.js.map +1 -0
  44. package/dist/rendering/index.d.ts +28 -0
  45. package/dist/rendering/index.d.ts.map +1 -0
  46. package/dist/rendering/index.js +60 -0
  47. package/dist/rendering/index.js.map +1 -0
  48. package/dist/rendering/layout-selector.d.ts +48 -0
  49. package/dist/rendering/layout-selector.d.ts.map +1 -0
  50. package/dist/rendering/layout-selector.js +347 -0
  51. package/dist/rendering/layout-selector.js.map +1 -0
  52. package/dist/rendering/template-engine.d.ts +41 -0
  53. package/dist/rendering/template-engine.d.ts.map +1 -0
  54. package/dist/rendering/template-engine.js +236 -0
  55. package/dist/rendering/template-engine.js.map +1 -0
  56. package/dist/schema-extractor.d.ts +30 -0
  57. package/dist/schema-extractor.d.ts.map +1 -1
  58. package/dist/schema-extractor.js +205 -12
  59. package/dist/schema-extractor.js.map +1 -1
  60. package/dist/stateful.js +1 -1
  61. package/dist/stateful.js.map +1 -1
  62. package/dist/types.d.ts +9 -1
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js.map +1 -1
  65. package/dist/ucp/ap2/handlers.d.ts +242 -0
  66. package/dist/ucp/ap2/handlers.d.ts.map +1 -0
  67. package/dist/ucp/ap2/handlers.js +482 -0
  68. package/dist/ucp/ap2/handlers.js.map +1 -0
  69. package/dist/ucp/ap2/mandates.d.ts +95 -0
  70. package/dist/ucp/ap2/mandates.d.ts.map +1 -0
  71. package/dist/ucp/ap2/mandates.js +234 -0
  72. package/dist/ucp/ap2/mandates.js.map +1 -0
  73. package/dist/ucp/ap2/types.d.ts +305 -0
  74. package/dist/ucp/ap2/types.d.ts.map +1 -0
  75. package/dist/ucp/ap2/types.js +8 -0
  76. package/dist/ucp/ap2/types.js.map +1 -0
  77. package/dist/ucp/capabilities/checkout.d.ts +118 -0
  78. package/dist/ucp/capabilities/checkout.d.ts.map +1 -0
  79. package/dist/ucp/capabilities/checkout.js +344 -0
  80. package/dist/ucp/capabilities/checkout.js.map +1 -0
  81. package/dist/ucp/capabilities/identity.d.ts +130 -0
  82. package/dist/ucp/capabilities/identity.d.ts.map +1 -0
  83. package/dist/ucp/capabilities/identity.js +290 -0
  84. package/dist/ucp/capabilities/identity.js.map +1 -0
  85. package/dist/ucp/capabilities/order.d.ts +142 -0
  86. package/dist/ucp/capabilities/order.d.ts.map +1 -0
  87. package/dist/ucp/capabilities/order.js +383 -0
  88. package/dist/ucp/capabilities/order.js.map +1 -0
  89. package/dist/ucp/index.d.ts +18 -0
  90. package/dist/ucp/index.d.ts.map +1 -0
  91. package/dist/ucp/index.js +19 -0
  92. package/dist/ucp/index.js.map +1 -0
  93. package/dist/ucp/manifest.d.ts +62 -0
  94. package/dist/ucp/manifest.d.ts.map +1 -0
  95. package/dist/ucp/manifest.js +180 -0
  96. package/dist/ucp/manifest.js.map +1 -0
  97. package/dist/ucp/types.d.ts +327 -0
  98. package/dist/ucp/types.d.ts.map +1 -0
  99. package/dist/ucp/types.js +8 -0
  100. package/dist/ucp/types.js.map +1 -0
  101. package/package.json +3 -4
  102. package/src/auto-ui.ts +1 -1
  103. package/src/base.ts +2 -2
  104. package/src/cli-ui-renderer.ts +1 -1
  105. package/src/design-system/index.ts +30 -0
  106. package/src/design-system/tokens.ts +451 -0
  107. package/src/design-system/transaction-ui.ts +1038 -0
  108. package/src/generator.ts +58 -2
  109. package/src/index.ts +135 -124
  110. package/src/io.ts +108 -3
  111. package/src/rendering/components.ts +785 -0
  112. package/src/rendering/field-analyzer.ts +299 -0
  113. package/src/rendering/field-renderers.ts +356 -0
  114. package/src/rendering/index.ts +63 -0
  115. package/src/rendering/layout-selector.ts +390 -0
  116. package/src/rendering/template-engine.ts +254 -0
  117. package/src/schema-extractor.ts +225 -12
  118. package/src/stateful.ts +1 -1
  119. package/src/types.ts +10 -1
  120. package/src/ucp/ap2/handlers.ts +779 -0
  121. package/src/ucp/ap2/mandates.ts +354 -0
  122. package/src/ucp/ap2/types.ts +441 -0
  123. package/src/ucp/capabilities/checkout.ts +497 -0
  124. package/src/ucp/capabilities/identity.ts +425 -0
  125. package/src/ucp/capabilities/order.ts +549 -0
  126. package/src/ucp/index.ts +27 -0
  127. package/src/ucp/manifest.ts +257 -0
  128. package/src/ucp/types.ts +454 -0
  129. package/dist/cli-formatter.d.ts +0 -92
  130. package/dist/cli-formatter.d.ts.map +0 -1
  131. package/dist/cli-formatter.js +0 -486
  132. package/dist/cli-formatter.js.map +0 -1
  133. package/dist/context.d.ts +0 -6
  134. package/dist/context.d.ts.map +0 -1
  135. package/dist/context.js +0 -3
  136. package/dist/context.js.map +0 -1
  137. package/dist/elicit.d.ts +0 -93
  138. package/dist/elicit.d.ts.map +0 -1
  139. package/dist/elicit.js +0 -373
  140. package/dist/elicit.js.map +0 -1
  141. package/dist/mcp-client.d.ts +0 -218
  142. package/dist/mcp-client.d.ts.map +0 -1
  143. package/dist/mcp-client.js +0 -424
  144. package/dist/mcp-client.js.map +0 -1
  145. package/dist/mcp-sdk-transport.d.ts +0 -88
  146. package/dist/mcp-sdk-transport.d.ts.map +0 -1
  147. package/dist/mcp-sdk-transport.js +0 -360
  148. package/dist/mcp-sdk-transport.js.map +0 -1
  149. package/dist/photon-config.d.ts +0 -86
  150. package/dist/photon-config.d.ts.map +0 -1
  151. package/dist/photon-config.js +0 -156
  152. package/dist/photon-config.js.map +0 -1
  153. package/dist/progress.d.ts +0 -93
  154. package/dist/progress.d.ts.map +0 -1
  155. package/dist/progress.js +0 -195
  156. package/dist/progress.js.map +0 -1
  157. package/src/cli-formatter.ts +0 -579
  158. package/src/context.ts +0 -7
  159. package/src/elicit.ts +0 -438
  160. package/src/mcp-client.ts +0 -561
  161. package/src/mcp-sdk-transport.ts +0 -449
  162. package/src/photon-config.ts +0 -201
  163. package/src/progress.ts +0 -224
@@ -0,0 +1,425 @@
1
+ /**
2
+ * UCP Identity Linking Capability Implementation
3
+ *
4
+ * Provides OAuth 2.0 based identity linking for agent authorization.
5
+ */
6
+
7
+ import * as crypto from 'crypto';
8
+ import {
9
+ OAuthConfig,
10
+ TokenResponse,
11
+ IdentityLink
12
+ } from '../types.js';
13
+
14
+ // ============================================================================
15
+ // Token Storage Interface
16
+ // ============================================================================
17
+
18
+ export interface TokenStorage {
19
+ storeAuthCode(code: string, data: AuthCodeData): Promise<void>;
20
+ getAuthCode(code: string): Promise<AuthCodeData | null>;
21
+ deleteAuthCode(code: string): Promise<void>;
22
+
23
+ storeToken(token: string, data: TokenData): Promise<void>;
24
+ getToken(token: string): Promise<TokenData | null>;
25
+ deleteToken(token: string): Promise<void>;
26
+
27
+ storeRefreshToken(token: string, data: RefreshTokenData): Promise<void>;
28
+ getRefreshToken(token: string): Promise<RefreshTokenData | null>;
29
+ deleteRefreshToken(token: string): Promise<void>;
30
+
31
+ storeIdentityLink(link: IdentityLink): Promise<void>;
32
+ getIdentityLink(agentId: string): Promise<IdentityLink | null>;
33
+ deleteIdentityLink(agentId: string): Promise<void>;
34
+ }
35
+
36
+ interface AuthCodeData {
37
+ agentId: string;
38
+ scopes: string[];
39
+ redirectUri: string;
40
+ expiresAt: string;
41
+ state?: string;
42
+ }
43
+
44
+ interface TokenData {
45
+ agentId: string;
46
+ scopes: string[];
47
+ expiresAt: string;
48
+ }
49
+
50
+ interface RefreshTokenData {
51
+ agentId: string;
52
+ scopes: string[];
53
+ accessToken: string;
54
+ }
55
+
56
+ /**
57
+ * In-memory token storage (for development/testing)
58
+ */
59
+ export class MemoryTokenStorage implements TokenStorage {
60
+ private authCodes = new Map<string, AuthCodeData>();
61
+ private tokens = new Map<string, TokenData>();
62
+ private refreshTokens = new Map<string, RefreshTokenData>();
63
+ private identityLinks = new Map<string, IdentityLink>();
64
+
65
+ async storeAuthCode(code: string, data: AuthCodeData): Promise<void> {
66
+ this.authCodes.set(code, data);
67
+ }
68
+
69
+ async getAuthCode(code: string): Promise<AuthCodeData | null> {
70
+ const data = this.authCodes.get(code);
71
+ if (!data) return null;
72
+
73
+ if (new Date(data.expiresAt) < new Date()) {
74
+ this.authCodes.delete(code);
75
+ return null;
76
+ }
77
+
78
+ return data;
79
+ }
80
+
81
+ async deleteAuthCode(code: string): Promise<void> {
82
+ this.authCodes.delete(code);
83
+ }
84
+
85
+ async storeToken(token: string, data: TokenData): Promise<void> {
86
+ this.tokens.set(token, data);
87
+ }
88
+
89
+ async getToken(token: string): Promise<TokenData | null> {
90
+ const data = this.tokens.get(token);
91
+ if (!data) return null;
92
+
93
+ if (new Date(data.expiresAt) < new Date()) {
94
+ this.tokens.delete(token);
95
+ return null;
96
+ }
97
+
98
+ return data;
99
+ }
100
+
101
+ async deleteToken(token: string): Promise<void> {
102
+ this.tokens.delete(token);
103
+ }
104
+
105
+ async storeRefreshToken(token: string, data: RefreshTokenData): Promise<void> {
106
+ this.refreshTokens.set(token, data);
107
+ }
108
+
109
+ async getRefreshToken(token: string): Promise<RefreshTokenData | null> {
110
+ return this.refreshTokens.get(token) || null;
111
+ }
112
+
113
+ async deleteRefreshToken(token: string): Promise<void> {
114
+ this.refreshTokens.delete(token);
115
+ }
116
+
117
+ async storeIdentityLink(link: IdentityLink): Promise<void> {
118
+ this.identityLinks.set(link.agentId, link);
119
+ }
120
+
121
+ async getIdentityLink(agentId: string): Promise<IdentityLink | null> {
122
+ return this.identityLinks.get(agentId) || null;
123
+ }
124
+
125
+ async deleteIdentityLink(agentId: string): Promise<void> {
126
+ this.identityLinks.delete(agentId);
127
+ }
128
+ }
129
+
130
+ // ============================================================================
131
+ // Identity Service
132
+ // ============================================================================
133
+
134
+ export interface IdentityServiceConfig {
135
+ baseUrl: string;
136
+ allowedScopes: string[];
137
+ authCodeTTLSeconds: number;
138
+ accessTokenTTLSeconds: number;
139
+ refreshTokenTTLDays: number;
140
+ }
141
+
142
+ const DEFAULT_CONFIG: IdentityServiceConfig = {
143
+ baseUrl: '',
144
+ allowedScopes: ['checkout.read', 'checkout.write', 'order.read', 'order.write'],
145
+ authCodeTTLSeconds: 600, // 10 minutes
146
+ accessTokenTTLSeconds: 3600, // 1 hour
147
+ refreshTokenTTLDays: 30
148
+ };
149
+
150
+ export class IdentityService {
151
+ private storage: TokenStorage;
152
+ private config: IdentityServiceConfig;
153
+
154
+ constructor(config?: Partial<IdentityServiceConfig>, storage?: TokenStorage) {
155
+ this.config = { ...DEFAULT_CONFIG, ...config };
156
+ this.storage = storage || new MemoryTokenStorage();
157
+ }
158
+
159
+ // --------------------------------------------------------------------------
160
+ // OAuth Configuration
161
+ // --------------------------------------------------------------------------
162
+
163
+ /**
164
+ * Get OAuth configuration for discovery
165
+ */
166
+ async getOAuthConfig(): Promise<OAuthConfig> {
167
+ return {
168
+ authorizationEndpoint: `${this.config.baseUrl}/oauth/authorize`,
169
+ tokenEndpoint: `${this.config.baseUrl}/oauth/token`,
170
+ revocationEndpoint: `${this.config.baseUrl}/oauth/revoke`,
171
+ scopes: this.config.allowedScopes
172
+ };
173
+ }
174
+
175
+ // --------------------------------------------------------------------------
176
+ // Authorization Flow
177
+ // --------------------------------------------------------------------------
178
+
179
+ /**
180
+ * Generate authorization URL for agent
181
+ */
182
+ async requestAuthorization(params: {
183
+ agentId: string;
184
+ scopes: string[];
185
+ redirectUri: string;
186
+ state?: string;
187
+ }): Promise<{ authorizationUrl: string }> {
188
+ // Validate scopes
189
+ const invalidScopes = params.scopes.filter(s => !this.config.allowedScopes.includes(s));
190
+ if (invalidScopes.length > 0) {
191
+ throw new Error(`Invalid scopes: ${invalidScopes.join(', ')}`);
192
+ }
193
+
194
+ // Generate auth code
195
+ const code = this.generateCode();
196
+ const expiresAt = new Date(Date.now() + this.config.authCodeTTLSeconds * 1000);
197
+
198
+ await this.storage.storeAuthCode(code, {
199
+ agentId: params.agentId,
200
+ scopes: params.scopes,
201
+ redirectUri: params.redirectUri,
202
+ expiresAt: expiresAt.toISOString(),
203
+ state: params.state
204
+ });
205
+
206
+ // Build authorization URL
207
+ const url = new URL(`${this.config.baseUrl}/oauth/authorize`);
208
+ url.searchParams.set('code', code);
209
+ url.searchParams.set('agent_id', params.agentId);
210
+ url.searchParams.set('scope', params.scopes.join(' '));
211
+ url.searchParams.set('redirect_uri', params.redirectUri);
212
+ if (params.state) {
213
+ url.searchParams.set('state', params.state);
214
+ }
215
+
216
+ return { authorizationUrl: url.toString() };
217
+ }
218
+
219
+ /**
220
+ * Handle user approval (called after user consents)
221
+ */
222
+ async approveAuthorization(code: string, userId: string): Promise<{
223
+ redirectUrl: string;
224
+ }> {
225
+ const authData = await this.storage.getAuthCode(code);
226
+ if (!authData) {
227
+ throw new Error('Invalid or expired authorization code');
228
+ }
229
+
230
+ // Create identity link
231
+ const link: IdentityLink = {
232
+ id: `link_${crypto.randomUUID()}`,
233
+ agentId: authData.agentId,
234
+ userId,
235
+ scopes: authData.scopes,
236
+ createdAt: new Date().toISOString()
237
+ };
238
+
239
+ await this.storage.storeIdentityLink(link);
240
+
241
+ // Build redirect URL with code
242
+ const redirectUrl = new URL(authData.redirectUri);
243
+ redirectUrl.searchParams.set('code', code);
244
+ if (authData.state) {
245
+ redirectUrl.searchParams.set('state', authData.state);
246
+ }
247
+
248
+ return { redirectUrl: redirectUrl.toString() };
249
+ }
250
+
251
+ // --------------------------------------------------------------------------
252
+ // Token Exchange
253
+ // --------------------------------------------------------------------------
254
+
255
+ /**
256
+ * Exchange authorization code for tokens
257
+ */
258
+ async exchangeCode(params: {
259
+ code: string;
260
+ redirectUri: string;
261
+ }): Promise<TokenResponse> {
262
+ const authData = await this.storage.getAuthCode(params.code);
263
+
264
+ if (!authData) {
265
+ throw new Error('Invalid or expired authorization code');
266
+ }
267
+
268
+ if (authData.redirectUri !== params.redirectUri) {
269
+ throw new Error('Redirect URI mismatch');
270
+ }
271
+
272
+ // Delete used auth code
273
+ await this.storage.deleteAuthCode(params.code);
274
+
275
+ // Generate tokens
276
+ const accessToken = this.generateToken();
277
+ const refreshToken = this.generateToken();
278
+
279
+ const accessTokenExpires = new Date(Date.now() + this.config.accessTokenTTLSeconds * 1000);
280
+
281
+ // Store tokens
282
+ await this.storage.storeToken(accessToken, {
283
+ agentId: authData.agentId,
284
+ scopes: authData.scopes,
285
+ expiresAt: accessTokenExpires.toISOString()
286
+ });
287
+
288
+ await this.storage.storeRefreshToken(refreshToken, {
289
+ agentId: authData.agentId,
290
+ scopes: authData.scopes,
291
+ accessToken
292
+ });
293
+
294
+ return {
295
+ accessToken,
296
+ tokenType: 'Bearer',
297
+ expiresIn: this.config.accessTokenTTLSeconds,
298
+ refreshToken,
299
+ scope: authData.scopes.join(' ')
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Refresh access token
305
+ */
306
+ async refreshToken(refreshToken: string): Promise<TokenResponse> {
307
+ const refreshData = await this.storage.getRefreshToken(refreshToken);
308
+
309
+ if (!refreshData) {
310
+ throw new Error('Invalid refresh token');
311
+ }
312
+
313
+ // Delete old access token
314
+ await this.storage.deleteToken(refreshData.accessToken);
315
+
316
+ // Generate new access token
317
+ const newAccessToken = this.generateToken();
318
+ const accessTokenExpires = new Date(Date.now() + this.config.accessTokenTTLSeconds * 1000);
319
+
320
+ await this.storage.storeToken(newAccessToken, {
321
+ agentId: refreshData.agentId,
322
+ scopes: refreshData.scopes,
323
+ expiresAt: accessTokenExpires.toISOString()
324
+ });
325
+
326
+ // Update refresh token reference
327
+ await this.storage.storeRefreshToken(refreshToken, {
328
+ ...refreshData,
329
+ accessToken: newAccessToken
330
+ });
331
+
332
+ return {
333
+ accessToken: newAccessToken,
334
+ tokenType: 'Bearer',
335
+ expiresIn: this.config.accessTokenTTLSeconds,
336
+ scope: refreshData.scopes.join(' ')
337
+ };
338
+ }
339
+
340
+ // --------------------------------------------------------------------------
341
+ // Token Validation
342
+ // --------------------------------------------------------------------------
343
+
344
+ /**
345
+ * Validate access token
346
+ */
347
+ async validateToken(token: string): Promise<{
348
+ valid: boolean;
349
+ agentId?: string;
350
+ scopes?: string[];
351
+ }> {
352
+ const tokenData = await this.storage.getToken(token);
353
+
354
+ if (!tokenData) {
355
+ return { valid: false };
356
+ }
357
+
358
+ return {
359
+ valid: true,
360
+ agentId: tokenData.agentId,
361
+ scopes: tokenData.scopes
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Check if token has required scope
367
+ */
368
+ async hasScope(token: string, requiredScope: string): Promise<boolean> {
369
+ const validation = await this.validateToken(token);
370
+ return validation.valid && (validation.scopes?.includes(requiredScope) || false);
371
+ }
372
+
373
+ // --------------------------------------------------------------------------
374
+ // Revocation
375
+ // --------------------------------------------------------------------------
376
+
377
+ /**
378
+ * Revoke token
379
+ */
380
+ async revokeToken(token: string): Promise<void> {
381
+ // Try to find and delete as access token
382
+ const tokenData = await this.storage.getToken(token);
383
+ if (tokenData) {
384
+ await this.storage.deleteToken(token);
385
+ return;
386
+ }
387
+
388
+ // Try to find and delete as refresh token
389
+ const refreshData = await this.storage.getRefreshToken(token);
390
+ if (refreshData) {
391
+ await this.storage.deleteToken(refreshData.accessToken);
392
+ await this.storage.deleteRefreshToken(token);
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Revoke all tokens for an agent
398
+ */
399
+ async revokeAgent(agentId: string): Promise<void> {
400
+ await this.storage.deleteIdentityLink(agentId);
401
+ }
402
+
403
+ // --------------------------------------------------------------------------
404
+ // Identity Links
405
+ // --------------------------------------------------------------------------
406
+
407
+ /**
408
+ * Get identity link for agent
409
+ */
410
+ async getIdentityLink(agentId: string): Promise<IdentityLink | null> {
411
+ return this.storage.getIdentityLink(agentId);
412
+ }
413
+
414
+ // --------------------------------------------------------------------------
415
+ // Helpers
416
+ // --------------------------------------------------------------------------
417
+
418
+ private generateCode(): string {
419
+ return crypto.randomBytes(32).toString('base64url');
420
+ }
421
+
422
+ private generateToken(): string {
423
+ return crypto.randomBytes(48).toString('base64url');
424
+ }
425
+ }