pika-shared 1.4.4 → 1.4.5

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.
@@ -1,5 +1,5 @@
1
1
  import { UploadStatus } from '../upload-types.mjs';
2
- import { ShowToastFn, ChatUser, RecordOrUndef, UserAwsCredentials, WidgetRenderingContextType, ShareSessionState, UserPrefs, ChatAppMode, IUserWidgetDataStoreState, CustomDataUiRepresentation, ChatAppOverridableFeatures, TagDefinition, TagDefinitionWidget, ChatSession, UserDataOverrideSettings, ChatMessageForRendering, ChatApp, ChatAppActionMenu, ChatAppAction, InvokeAgentAsComponentOptions, WidgetMetadata, WidgetAction } from './chatbot-types.mjs';
2
+ import { ShowToastFn, ChatUser, RecordOrUndef, UserAwsCredentials, WidgetRenderingContextType, ShareSessionState, UserPrefs, ChatAppMode, IUserWidgetDataStoreState, CustomDataUiRepresentation, ChatAppOverridableFeatures, TagDefinition, TagDefinitionWidget, ChatSession, UserDataOverrideSettings, ChatMessageForRendering, ChatApp, ChatAppActionMenu, ChatAppAction, WidgetInstance, WidgetMetadata, SpotlightWidgetDefinition, InvokeAgentAsComponentOptions, WidgetAction } from './chatbot-types.mjs';
3
3
  import '@aws-sdk/client-bedrock-agent-runtime';
4
4
  import '@aws-sdk/client-bedrock-agentcore';
5
5
 
@@ -176,6 +176,7 @@ interface IChatAppState {
176
176
  readonly pageTitle: string | undefined;
177
177
  readonly customDataForChatApp: Record<string, unknown> | undefined;
178
178
  readonly customTitleBarActions: (ChatAppActionMenu | ChatAppAction)[];
179
+ readonly widgetInstances: Map<string, WidgetInstance>;
179
180
  setCurrentSessionById(sessionId: string): void;
180
181
  removeFile(s3Key: string): void;
181
182
  startNewChatSession(): void;
@@ -186,11 +187,126 @@ interface IChatAppState {
186
187
  getMessageByMessageId(messageId: string): ChatMessageForRendering | undefined;
187
188
  uploadFiles(files: File[]): Promise<void>;
188
189
  initializeData(): Promise<void>;
189
- renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas', data?: Record<string, any>): Promise<void>;
190
+ renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas', data?: Record<string, any>, metadata?: WidgetMetadata): Promise<void>;
190
191
  closeCanvas(): void;
191
192
  closeDialog(): void;
192
193
  setOrUpdateCustomTitleBarAction(action: ChatAppActionMenu | ChatAppAction): void;
193
194
  removeCustomTitleBarAction(actionId: string): void;
195
+ getWidgetInstance(instanceId: string): WidgetInstance | undefined;
196
+ /**
197
+ * Update context for a widget. Call this when your widget's context has changed.
198
+ *
199
+ * This method will re-check your widget's `getContextForLlm()` method and update
200
+ * the context accordingly. Use this when:
201
+ * - Your widget initially had no context, but now has context to share
202
+ * - Your widget's context data has changed (e.g., user selected different items)
203
+ * - You want to change whether context should be auto-added
204
+ *
205
+ * @param instanceId - Your widget's instance ID (from getPikaContext())
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * // In a Svelte widget component:
210
+ * let selectedCities = $state<string[]>([]);
211
+ *
212
+ * onMount(async () => {
213
+ * const context = await getPikaContext($host());
214
+ *
215
+ * // Update context whenever selection changes
216
+ * $effect(() => {
217
+ * if (selectedCities.length > 0) {
218
+ * // This triggers re-evaluation of getContextForLlm()
219
+ * context.chatAppState.updateWidgetContext(context.instanceId);
220
+ * }
221
+ * });
222
+ * });
223
+ *
224
+ * // Your getContextForLlm() method will be called again
225
+ * getContextForLlm() {
226
+ * if (selectedCities.length === 0) return undefined;
227
+ *
228
+ * return {
229
+ * origin: 'auto',
230
+ * title: 'Selected Cities',
231
+ * description: `User selected ${selectedCities.length} cities`,
232
+ * data: { cities: selectedCities },
233
+ * addAutomatically: true
234
+ * };
235
+ * }
236
+ * ```
237
+ */
238
+ updateWidgetContext(instanceId: string): void;
239
+ /**
240
+ * Manually register a web component as a spotlight widget. This allows components to
241
+ * dynamically add themselves to the spotlight area at runtime.
242
+ *
243
+ * Note: Registration is ephemeral and does not persist across page refreshes.
244
+ * Components must re-register themselves on each page load.
245
+ *
246
+ * @param definition - Simplified definition for the spotlight widget
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * chatAppState.manuallyRegisterSpotlightWidget({
251
+ * tag: 'my-widget',
252
+ * scope: 'my-app',
253
+ * tagTitle: 'My Widget',
254
+ * displayOrder: 0
255
+ * });
256
+ * ```
257
+ */
258
+ manuallyRegisterSpotlightWidget(definition: SpotlightWidgetDefinition): void;
259
+ /**
260
+ * Save a persistent spotlight instance using the Virtual Tags Pattern.
261
+ * Creates a new instance with its own UserWidgetDataStore (400KB limit per instance),
262
+ * saves the data, registers it as a spotlight widget, and renders it immediately.
263
+ *
264
+ * Use this when users save content (like charts, queries, etc.) to spotlight.
265
+ *
266
+ * @param scope - Widget scope (e.g., 'weather')
267
+ * @param baseTag - Base tag name (e.g., 'chart-saved')
268
+ * @param displayName - User-facing name for this instance
269
+ * @param customElementName - The custom element name (same for all instances)
270
+ * @param data - The data to pass to this instance
271
+ * @param dataKey - The key to store data under (default: 'data')
272
+ * @param metadata - Optional widget metadata (title, actions, icon, etc.)
273
+ * @returns The instance ID (UUID)
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const instanceId = await chatAppState.saveSpotlightInstance(
278
+ * 'weather',
279
+ * 'chart-saved',
280
+ * 'Q4 Revenue Chart',
281
+ * 'weather-chart-saved',
282
+ * { chartType: 'bar', data: [...] },
283
+ * 'chartData',
284
+ * {
285
+ * title: 'Q4 Revenue Chart',
286
+ * iconSvg: '<svg>...</svg>',
287
+ * actions: [...]
288
+ * }
289
+ * );
290
+ * ```
291
+ */
292
+ saveSpotlightInstance(scope: string, baseTag: string, displayName: string, customElementName: string, data: Record<string, any>, dataKey?: string, metadata?: WidgetMetadata): Promise<string>;
293
+ /**
294
+ * Delete a saved spotlight instance.
295
+ * Removes from spotlight, removes from registry, and unregisters from state.
296
+ *
297
+ * Note: Instance data is currently orphaned (not deleted) but could be recovered.
298
+ * Future: Will add deleteAll() method to UserWidgetDataStore.
299
+ *
300
+ * @param scope - Widget scope
301
+ * @param baseTag - Base tag name
302
+ * @param instanceId - Instance UUID
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * await chatAppState.deleteSpotlightInstance('weather', 'chart-saved', instanceId);
307
+ * ```
308
+ */
309
+ deleteSpotlightInstance(scope: string, baseTag: string, instanceId: string): Promise<void>;
194
310
  /**
195
311
  * Invoke the agent directly from a web component using the 'chat-app-component' invocation mode.
196
312
  * This allows components to make out-of-band requests to the LLM without creating user sessions.
@@ -324,6 +440,54 @@ interface IUploadInstance {
324
440
  error?: string;
325
441
  };
326
442
  }
443
+ /**
444
+ * Callback invoked when the web component has been created and is ready.
445
+ * Called after the element is created but before it's added to the DOM.
446
+ */
447
+ interface OnReadyCallback {
448
+ (params: {
449
+ /** The web component element that was created */
450
+ element: HTMLElement;
451
+ /** The unique instance ID assigned to this component */
452
+ instanceId: string;
453
+ /** The full Pika context with instanceId */
454
+ context: PikaWCContext;
455
+ }): void;
456
+ }
457
+ /**
458
+ * Structure for data passed to web components.
459
+ *
460
+ * Special fields that affect element initialization:
461
+ * - `attributes`: Set as HTML attributes (stringified) and also as properties if they exist
462
+ * - `properties`: Set as JavaScript properties only (not attributes)
463
+ * - `onReady`: Callback invoked when the component is created and ready
464
+ *
465
+ * All other fields are available through `context.dataForWidget` but not set on the element.
466
+ */
467
+ interface DataForWidget {
468
+ /**
469
+ * HTML attributes to set on the element.
470
+ * Values are stringified and set via `setAttributeNS()`.
471
+ * If a corresponding property exists on the element, it's also set with the original value.
472
+ */
473
+ attributes?: Record<string, any>;
474
+ /**
475
+ * JavaScript properties to set on the element (not as HTML attributes).
476
+ * Only properties that exist on the element will be set.
477
+ * Use this for complex objects, arrays, functions, etc.
478
+ */
479
+ properties?: Record<string, any>;
480
+ /**
481
+ * Callback invoked when the web component is created and ready.
482
+ * Called after element creation, property/attribute setting, and context setup,
483
+ * but before the element is added to the DOM.
484
+ *
485
+ * Use this to get notified when the component is ready and to access the element directly.
486
+ */
487
+ onReady?: OnReadyCallback;
488
+ /** Any other data available through context (not set on the element) */
489
+ [key: string]: any;
490
+ }
327
491
  /**
328
492
  * This is the context object that is passed to the web component when it is rendered.
329
493
  */
@@ -337,8 +501,8 @@ interface PikaWCContext {
337
501
  * Set by injectChatAppWebComponent() and used by getWidgetMetadataAPI().
338
502
  */
339
503
  instanceId: string;
340
- /** Arbitrary data to be passed to the widget. */
341
- dataForWidget: Record<string, any>;
504
+ /** Data passed to the widget, available through `context.dataForWidget`. */
505
+ dataForWidget: DataForWidget;
342
506
  }
343
507
  type PikaWCContextWithoutInstanceId = Omit<PikaWCContext, 'instanceId'>;
344
508
  type PikaWCContextRequestCallbackFn = (contextRequest: PikaWCContext) => void;
@@ -349,4 +513,4 @@ interface PikaWCContextRequestEvent extends CustomEvent<PikaWCContextRequestDeta
349
513
  detail: PikaWCContextRequestDetail;
350
514
  }
351
515
 
352
- export type { IAppState, IChatAppState, IIdentityState, IUploadInstance, IUserPrefsState, IWidgetMetadataAPI, PikaWCContext, PikaWCContextRequestCallbackFn, PikaWCContextRequestDetail, PikaWCContextRequestEvent, PikaWCContextWithoutInstanceId, SidebarState, Snippet };
516
+ export type { DataForWidget, IAppState, IChatAppState, IIdentityState, IUploadInstance, IUserPrefsState, IWidgetMetadataAPI, OnReadyCallback, PikaWCContext, PikaWCContextRequestCallbackFn, PikaWCContextRequestDetail, PikaWCContextRequestEvent, PikaWCContextWithoutInstanceId, SidebarState, Snippet };
@@ -1,5 +1,5 @@
1
1
  import { UploadStatus } from '../upload-types.js';
2
- import { ShowToastFn, ChatUser, RecordOrUndef, UserAwsCredentials, WidgetRenderingContextType, ShareSessionState, UserPrefs, ChatAppMode, IUserWidgetDataStoreState, CustomDataUiRepresentation, ChatAppOverridableFeatures, TagDefinition, TagDefinitionWidget, ChatSession, UserDataOverrideSettings, ChatMessageForRendering, ChatApp, ChatAppActionMenu, ChatAppAction, InvokeAgentAsComponentOptions, WidgetMetadata, WidgetAction } from './chatbot-types.js';
2
+ import { ShowToastFn, ChatUser, RecordOrUndef, UserAwsCredentials, WidgetRenderingContextType, ShareSessionState, UserPrefs, ChatAppMode, IUserWidgetDataStoreState, CustomDataUiRepresentation, ChatAppOverridableFeatures, TagDefinition, TagDefinitionWidget, ChatSession, UserDataOverrideSettings, ChatMessageForRendering, ChatApp, ChatAppActionMenu, ChatAppAction, WidgetInstance, WidgetMetadata, SpotlightWidgetDefinition, InvokeAgentAsComponentOptions, WidgetAction } from './chatbot-types.js';
3
3
  import '@aws-sdk/client-bedrock-agent-runtime';
4
4
  import '@aws-sdk/client-bedrock-agentcore';
5
5
 
@@ -176,6 +176,7 @@ interface IChatAppState {
176
176
  readonly pageTitle: string | undefined;
177
177
  readonly customDataForChatApp: Record<string, unknown> | undefined;
178
178
  readonly customTitleBarActions: (ChatAppActionMenu | ChatAppAction)[];
179
+ readonly widgetInstances: Map<string, WidgetInstance>;
179
180
  setCurrentSessionById(sessionId: string): void;
180
181
  removeFile(s3Key: string): void;
181
182
  startNewChatSession(): void;
@@ -186,11 +187,126 @@ interface IChatAppState {
186
187
  getMessageByMessageId(messageId: string): ChatMessageForRendering | undefined;
187
188
  uploadFiles(files: File[]): Promise<void>;
188
189
  initializeData(): Promise<void>;
189
- renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas', data?: Record<string, any>): Promise<void>;
190
+ renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas', data?: Record<string, any>, metadata?: WidgetMetadata): Promise<void>;
190
191
  closeCanvas(): void;
191
192
  closeDialog(): void;
192
193
  setOrUpdateCustomTitleBarAction(action: ChatAppActionMenu | ChatAppAction): void;
193
194
  removeCustomTitleBarAction(actionId: string): void;
195
+ getWidgetInstance(instanceId: string): WidgetInstance | undefined;
196
+ /**
197
+ * Update context for a widget. Call this when your widget's context has changed.
198
+ *
199
+ * This method will re-check your widget's `getContextForLlm()` method and update
200
+ * the context accordingly. Use this when:
201
+ * - Your widget initially had no context, but now has context to share
202
+ * - Your widget's context data has changed (e.g., user selected different items)
203
+ * - You want to change whether context should be auto-added
204
+ *
205
+ * @param instanceId - Your widget's instance ID (from getPikaContext())
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * // In a Svelte widget component:
210
+ * let selectedCities = $state<string[]>([]);
211
+ *
212
+ * onMount(async () => {
213
+ * const context = await getPikaContext($host());
214
+ *
215
+ * // Update context whenever selection changes
216
+ * $effect(() => {
217
+ * if (selectedCities.length > 0) {
218
+ * // This triggers re-evaluation of getContextForLlm()
219
+ * context.chatAppState.updateWidgetContext(context.instanceId);
220
+ * }
221
+ * });
222
+ * });
223
+ *
224
+ * // Your getContextForLlm() method will be called again
225
+ * getContextForLlm() {
226
+ * if (selectedCities.length === 0) return undefined;
227
+ *
228
+ * return {
229
+ * origin: 'auto',
230
+ * title: 'Selected Cities',
231
+ * description: `User selected ${selectedCities.length} cities`,
232
+ * data: { cities: selectedCities },
233
+ * addAutomatically: true
234
+ * };
235
+ * }
236
+ * ```
237
+ */
238
+ updateWidgetContext(instanceId: string): void;
239
+ /**
240
+ * Manually register a web component as a spotlight widget. This allows components to
241
+ * dynamically add themselves to the spotlight area at runtime.
242
+ *
243
+ * Note: Registration is ephemeral and does not persist across page refreshes.
244
+ * Components must re-register themselves on each page load.
245
+ *
246
+ * @param definition - Simplified definition for the spotlight widget
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * chatAppState.manuallyRegisterSpotlightWidget({
251
+ * tag: 'my-widget',
252
+ * scope: 'my-app',
253
+ * tagTitle: 'My Widget',
254
+ * displayOrder: 0
255
+ * });
256
+ * ```
257
+ */
258
+ manuallyRegisterSpotlightWidget(definition: SpotlightWidgetDefinition): void;
259
+ /**
260
+ * Save a persistent spotlight instance using the Virtual Tags Pattern.
261
+ * Creates a new instance with its own UserWidgetDataStore (400KB limit per instance),
262
+ * saves the data, registers it as a spotlight widget, and renders it immediately.
263
+ *
264
+ * Use this when users save content (like charts, queries, etc.) to spotlight.
265
+ *
266
+ * @param scope - Widget scope (e.g., 'weather')
267
+ * @param baseTag - Base tag name (e.g., 'chart-saved')
268
+ * @param displayName - User-facing name for this instance
269
+ * @param customElementName - The custom element name (same for all instances)
270
+ * @param data - The data to pass to this instance
271
+ * @param dataKey - The key to store data under (default: 'data')
272
+ * @param metadata - Optional widget metadata (title, actions, icon, etc.)
273
+ * @returns The instance ID (UUID)
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const instanceId = await chatAppState.saveSpotlightInstance(
278
+ * 'weather',
279
+ * 'chart-saved',
280
+ * 'Q4 Revenue Chart',
281
+ * 'weather-chart-saved',
282
+ * { chartType: 'bar', data: [...] },
283
+ * 'chartData',
284
+ * {
285
+ * title: 'Q4 Revenue Chart',
286
+ * iconSvg: '<svg>...</svg>',
287
+ * actions: [...]
288
+ * }
289
+ * );
290
+ * ```
291
+ */
292
+ saveSpotlightInstance(scope: string, baseTag: string, displayName: string, customElementName: string, data: Record<string, any>, dataKey?: string, metadata?: WidgetMetadata): Promise<string>;
293
+ /**
294
+ * Delete a saved spotlight instance.
295
+ * Removes from spotlight, removes from registry, and unregisters from state.
296
+ *
297
+ * Note: Instance data is currently orphaned (not deleted) but could be recovered.
298
+ * Future: Will add deleteAll() method to UserWidgetDataStore.
299
+ *
300
+ * @param scope - Widget scope
301
+ * @param baseTag - Base tag name
302
+ * @param instanceId - Instance UUID
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * await chatAppState.deleteSpotlightInstance('weather', 'chart-saved', instanceId);
307
+ * ```
308
+ */
309
+ deleteSpotlightInstance(scope: string, baseTag: string, instanceId: string): Promise<void>;
194
310
  /**
195
311
  * Invoke the agent directly from a web component using the 'chat-app-component' invocation mode.
196
312
  * This allows components to make out-of-band requests to the LLM without creating user sessions.
@@ -324,6 +440,54 @@ interface IUploadInstance {
324
440
  error?: string;
325
441
  };
326
442
  }
443
+ /**
444
+ * Callback invoked when the web component has been created and is ready.
445
+ * Called after the element is created but before it's added to the DOM.
446
+ */
447
+ interface OnReadyCallback {
448
+ (params: {
449
+ /** The web component element that was created */
450
+ element: HTMLElement;
451
+ /** The unique instance ID assigned to this component */
452
+ instanceId: string;
453
+ /** The full Pika context with instanceId */
454
+ context: PikaWCContext;
455
+ }): void;
456
+ }
457
+ /**
458
+ * Structure for data passed to web components.
459
+ *
460
+ * Special fields that affect element initialization:
461
+ * - `attributes`: Set as HTML attributes (stringified) and also as properties if they exist
462
+ * - `properties`: Set as JavaScript properties only (not attributes)
463
+ * - `onReady`: Callback invoked when the component is created and ready
464
+ *
465
+ * All other fields are available through `context.dataForWidget` but not set on the element.
466
+ */
467
+ interface DataForWidget {
468
+ /**
469
+ * HTML attributes to set on the element.
470
+ * Values are stringified and set via `setAttributeNS()`.
471
+ * If a corresponding property exists on the element, it's also set with the original value.
472
+ */
473
+ attributes?: Record<string, any>;
474
+ /**
475
+ * JavaScript properties to set on the element (not as HTML attributes).
476
+ * Only properties that exist on the element will be set.
477
+ * Use this for complex objects, arrays, functions, etc.
478
+ */
479
+ properties?: Record<string, any>;
480
+ /**
481
+ * Callback invoked when the web component is created and ready.
482
+ * Called after element creation, property/attribute setting, and context setup,
483
+ * but before the element is added to the DOM.
484
+ *
485
+ * Use this to get notified when the component is ready and to access the element directly.
486
+ */
487
+ onReady?: OnReadyCallback;
488
+ /** Any other data available through context (not set on the element) */
489
+ [key: string]: any;
490
+ }
327
491
  /**
328
492
  * This is the context object that is passed to the web component when it is rendered.
329
493
  */
@@ -337,8 +501,8 @@ interface PikaWCContext {
337
501
  * Set by injectChatAppWebComponent() and used by getWidgetMetadataAPI().
338
502
  */
339
503
  instanceId: string;
340
- /** Arbitrary data to be passed to the widget. */
341
- dataForWidget: Record<string, any>;
504
+ /** Data passed to the widget, available through `context.dataForWidget`. */
505
+ dataForWidget: DataForWidget;
342
506
  }
343
507
  type PikaWCContextWithoutInstanceId = Omit<PikaWCContext, 'instanceId'>;
344
508
  type PikaWCContextRequestCallbackFn = (contextRequest: PikaWCContext) => void;
@@ -349,4 +513,4 @@ interface PikaWCContextRequestEvent extends CustomEvent<PikaWCContextRequestDeta
349
513
  detail: PikaWCContextRequestDetail;
350
514
  }
351
515
 
352
- export type { IAppState, IChatAppState, IIdentityState, IUploadInstance, IUserPrefsState, IWidgetMetadataAPI, PikaWCContext, PikaWCContextRequestCallbackFn, PikaWCContextRequestDetail, PikaWCContextRequestEvent, PikaWCContextWithoutInstanceId, SidebarState, Snippet };
516
+ export type { DataForWidget, IAppState, IChatAppState, IIdentityState, IUploadInstance, IUserPrefsState, IWidgetMetadataAPI, OnReadyCallback, PikaWCContext, PikaWCContextRequestCallbackFn, PikaWCContextRequestDetail, PikaWCContextRequestEvent, PikaWCContextWithoutInstanceId, SidebarState, Snippet };
@@ -27,5 +27,9 @@ declare function parseScope(scope: string): {
27
27
  scopeType: string;
28
28
  scopeValue: string | number | Record<string, string | number>;
29
29
  };
30
+ /**
31
+ * Useful for simple hashing like to figure out if content has changed since last sent or something.
32
+ */
33
+ declare function getContentHashString(content: unknown): Promise<string>;
30
34
 
31
- export { constructScope, parseScope, redactData, redactValue };
35
+ export { constructScope, getContentHashString, parseScope, redactData, redactValue };
@@ -27,5 +27,9 @@ declare function parseScope(scope: string): {
27
27
  scopeType: string;
28
28
  scopeValue: string | number | Record<string, string | number>;
29
29
  };
30
+ /**
31
+ * Useful for simple hashing like to figure out if content has changed since last sent or something.
32
+ */
33
+ declare function getContentHashString(content: unknown): Promise<string>;
30
34
 
31
- export { constructScope, parseScope, redactData, redactValue };
35
+ export { constructScope, getContentHashString, parseScope, redactData, redactValue };
@@ -91,8 +91,17 @@ function parseScope(scope) {
91
91
  }
92
92
  throw new Error(`Unsupported scope format: ${scope}`);
93
93
  }
94
+ async function getContentHashString(content) {
95
+ const encoder = new TextEncoder();
96
+ const data = encoder.encode(JSON.stringify(content));
97
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
98
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
99
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
100
+ return hashHex;
101
+ }
94
102
 
95
103
  exports.constructScope = constructScope;
104
+ exports.getContentHashString = getContentHashString;
96
105
  exports.parseScope = parseScope;
97
106
  exports.redactData = redactData;
98
107
  exports.redactValue = redactValue;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/server-client-utils.ts"],"names":[],"mappings":"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","file":"server-client-utils.js","sourcesContent":["/*\n * These are utils that are safe to use both on the server and the client.\n */\n\n/**\n * Helper function to redact sensitive data from specified attributes\n * @param data - The object containing data to redact\n * @param attributesToRedact - Single attribute name or array of attribute names to redact\n * @returns A new object with specified attributes redacted\n */\nfunction redactData(data: any, attributesToRedact: string | string[]): any {\n if (!data || typeof data !== 'object') {\n return data;\n }\n\n const attributes = Array.isArray(attributesToRedact) ? attributesToRedact : [attributesToRedact];\n const redacted = { ...data };\n\n for (const attr of attributes) {\n if (attr in redacted) {\n redacted[attr] = redactValue(redacted[attr]);\n }\n }\n\n return redacted;\n}\n\n/**\n * Recursively redacts a value based on its type\n * @param value - The value to redact\n * @returns The redacted value\n */\nfunction redactValue(value: any): any {\n if (typeof value === 'string') {\n return '[REDACTED]';\n } else if (Array.isArray(value)) {\n return value.map((item) => redactValue(item));\n } else if (value && typeof value === 'object') {\n const redactedObj: any = {};\n for (const key in value) {\n if (value.hasOwnProperty(key)) {\n redactedObj[key] = redactValue(value[key]);\n }\n }\n return redactedObj;\n }\n return value; // Return as-is for other types (numbers, booleans, null, etc.)\n}\n\n/**\n * Validates that a scope value doesn't contain the '#' character which is reserved for scope construction\n */\nfunction validateScopeValue(scopeValue: string | number | Record<string, string | number>, scopeType: string): void {\n if (typeof scopeValue === 'string' && scopeValue.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType} scope value: ${scopeValue}`);\n }\n if (typeof scopeValue === 'object' && scopeValue !== null) {\n for (const [key, value] of Object.entries(scopeValue)) {\n if (typeof value === 'string' && value.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType}.${key}: ${value}`);\n }\n }\n }\n}\n\n/**\n * Constructs a scope string from scopeType and scopeValue\n * @param scopeType - The type of scope (chatapp, agent, tool, entity, agent-entity)\n * @param scopeValue - The value(s) for the scope\n * @returns The constructed scope string\n */\nfunction constructScope(scopeType: string, scopeValue: string | number | Record<string, string | number>): string {\n // Validate scope value doesn't contain '#'\n validateScopeValue(scopeValue, scopeType);\n\n switch (scopeType) {\n case 'chatapp':\n case 'agent':\n case 'tool':\n case 'entity':\n return `${scopeType}#${scopeValue}`;\n case 'agent-entity':\n if (typeof scopeValue !== 'object' || scopeValue === null) {\n throw new Error('agent-entity scopeType requires an object with agent and entity properties');\n }\n\n const agentEntityValue = scopeValue as Record<string, string | number>;\n if (!('agent' in agentEntityValue) || !('entity' in agentEntityValue)) {\n throw new Error('agent-entity scopeType requires an object with both agent and entity properties');\n }\n\n return `agent#${agentEntityValue.agent}#entity#${agentEntityValue.entity}`;\n\n default:\n throw new Error(`Unsupported scopeType: ${scopeType}`);\n }\n}\n\n/**\n * Parses a scope string back into scopeType and scopeValue\n * @param scope - The scope string to parse\n * @returns Object containing scopeType and scopeValue\n */\nfunction parseScope(scope: string): { scopeType: string; scopeValue: string | number | Record<string, string | number> } {\n if (!scope || typeof scope !== 'string') {\n throw new Error('Invalid scope: must be a non-empty string');\n }\n\n const parts = scope.split('#');\n if (parts.length < 2) {\n throw new Error(`Invalid scope format: ${scope}. Expected format: scopeType#scopeValue`);\n }\n\n const [firstType, firstValue, ...remaining] = parts;\n\n // Handle compound scopes (agent-entity)\n if (remaining.length >= 2 && firstType === 'agent' && remaining[0] === 'entity') {\n return {\n scopeType: 'agent-entity',\n scopeValue: {\n agent: firstValue,\n entity: remaining[1]\n }\n };\n }\n\n // Handle simple scopes\n if (remaining.length === 0) {\n // For simple scopes, try to convert to number if it's numeric\n const numericValue = Number(firstValue);\n const scopeValue = !isNaN(numericValue) && isFinite(numericValue) ? numericValue : firstValue;\n\n return {\n scopeType: firstType,\n scopeValue: scopeValue\n };\n }\n\n throw new Error(`Unsupported scope format: ${scope}`);\n}\n\n// Export both functions\nexport { redactData, redactValue, constructScope, parseScope };\n"]}
1
+ {"version":3,"sources":["../../src/util/server-client-utils.ts"],"names":[],"mappings":"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","file":"server-client-utils.js","sourcesContent":["/*\n * These are utils that are safe to use both on the server and the client.\n */\n\n/**\n * Helper function to redact sensitive data from specified attributes\n * @param data - The object containing data to redact\n * @param attributesToRedact - Single attribute name or array of attribute names to redact\n * @returns A new object with specified attributes redacted\n */\nfunction redactData(data: any, attributesToRedact: string | string[]): any {\n if (!data || typeof data !== 'object') {\n return data;\n }\n\n const attributes = Array.isArray(attributesToRedact) ? attributesToRedact : [attributesToRedact];\n const redacted = { ...data };\n\n for (const attr of attributes) {\n if (attr in redacted) {\n redacted[attr] = redactValue(redacted[attr]);\n }\n }\n\n return redacted;\n}\n\n/**\n * Recursively redacts a value based on its type\n * @param value - The value to redact\n * @returns The redacted value\n */\nfunction redactValue(value: any): any {\n if (typeof value === 'string') {\n return '[REDACTED]';\n } else if (Array.isArray(value)) {\n return value.map((item) => redactValue(item));\n } else if (value && typeof value === 'object') {\n const redactedObj: any = {};\n for (const key in value) {\n if (value.hasOwnProperty(key)) {\n redactedObj[key] = redactValue(value[key]);\n }\n }\n return redactedObj;\n }\n return value; // Return as-is for other types (numbers, booleans, null, etc.)\n}\n\n/**\n * Validates that a scope value doesn't contain the '#' character which is reserved for scope construction\n */\nfunction validateScopeValue(scopeValue: string | number | Record<string, string | number>, scopeType: string): void {\n if (typeof scopeValue === 'string' && scopeValue.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType} scope value: ${scopeValue}`);\n }\n if (typeof scopeValue === 'object' && scopeValue !== null) {\n for (const [key, value] of Object.entries(scopeValue)) {\n if (typeof value === 'string' && value.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType}.${key}: ${value}`);\n }\n }\n }\n}\n\n/**\n * Constructs a scope string from scopeType and scopeValue\n * @param scopeType - The type of scope (chatapp, agent, tool, entity, agent-entity)\n * @param scopeValue - The value(s) for the scope\n * @returns The constructed scope string\n */\nfunction constructScope(scopeType: string, scopeValue: string | number | Record<string, string | number>): string {\n // Validate scope value doesn't contain '#'\n validateScopeValue(scopeValue, scopeType);\n\n switch (scopeType) {\n case 'chatapp':\n case 'agent':\n case 'tool':\n case 'entity':\n return `${scopeType}#${scopeValue}`;\n case 'agent-entity':\n if (typeof scopeValue !== 'object' || scopeValue === null) {\n throw new Error('agent-entity scopeType requires an object with agent and entity properties');\n }\n\n const agentEntityValue = scopeValue as Record<string, string | number>;\n if (!('agent' in agentEntityValue) || !('entity' in agentEntityValue)) {\n throw new Error('agent-entity scopeType requires an object with both agent and entity properties');\n }\n\n return `agent#${agentEntityValue.agent}#entity#${agentEntityValue.entity}`;\n\n default:\n throw new Error(`Unsupported scopeType: ${scopeType}`);\n }\n}\n\n/**\n * Parses a scope string back into scopeType and scopeValue\n * @param scope - The scope string to parse\n * @returns Object containing scopeType and scopeValue\n */\nfunction parseScope(scope: string): { scopeType: string; scopeValue: string | number | Record<string, string | number> } {\n if (!scope || typeof scope !== 'string') {\n throw new Error('Invalid scope: must be a non-empty string');\n }\n\n const parts = scope.split('#');\n if (parts.length < 2) {\n throw new Error(`Invalid scope format: ${scope}. Expected format: scopeType#scopeValue`);\n }\n\n const [firstType, firstValue, ...remaining] = parts;\n\n // Handle compound scopes (agent-entity)\n if (remaining.length >= 2 && firstType === 'agent' && remaining[0] === 'entity') {\n return {\n scopeType: 'agent-entity',\n scopeValue: {\n agent: firstValue,\n entity: remaining[1]\n }\n };\n }\n\n // Handle simple scopes\n if (remaining.length === 0) {\n // For simple scopes, try to convert to number if it's numeric\n const numericValue = Number(firstValue);\n const scopeValue = !isNaN(numericValue) && isFinite(numericValue) ? numericValue : firstValue;\n\n return {\n scopeType: firstType,\n scopeValue: scopeValue\n };\n }\n\n throw new Error(`Unsupported scope format: ${scope}`);\n}\n\n/**\n * Useful for simple hashing like to figure out if content has changed since last sent or something.\n */\nasync function getContentHashString(content: unknown): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(JSON.stringify(content));\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n return hashHex;\n}\n\n// Export both functions\nexport { redactData, redactValue, constructScope, parseScope, getContentHashString };\n"]}
@@ -89,7 +89,15 @@ function parseScope(scope) {
89
89
  }
90
90
  throw new Error(`Unsupported scope format: ${scope}`);
91
91
  }
92
+ async function getContentHashString(content) {
93
+ const encoder = new TextEncoder();
94
+ const data = encoder.encode(JSON.stringify(content));
95
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
96
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
97
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
98
+ return hashHex;
99
+ }
92
100
 
93
- export { constructScope, parseScope, redactData, redactValue };
101
+ export { constructScope, getContentHashString, parseScope, redactData, redactValue };
94
102
  //# sourceMappingURL=server-client-utils.mjs.map
95
103
  //# sourceMappingURL=server-client-utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/server-client-utils.ts"],"names":[],"mappings":"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","file":"server-client-utils.mjs","sourcesContent":["/*\n * These are utils that are safe to use both on the server and the client.\n */\n\n/**\n * Helper function to redact sensitive data from specified attributes\n * @param data - The object containing data to redact\n * @param attributesToRedact - Single attribute name or array of attribute names to redact\n * @returns A new object with specified attributes redacted\n */\nfunction redactData(data: any, attributesToRedact: string | string[]): any {\n if (!data || typeof data !== 'object') {\n return data;\n }\n\n const attributes = Array.isArray(attributesToRedact) ? attributesToRedact : [attributesToRedact];\n const redacted = { ...data };\n\n for (const attr of attributes) {\n if (attr in redacted) {\n redacted[attr] = redactValue(redacted[attr]);\n }\n }\n\n return redacted;\n}\n\n/**\n * Recursively redacts a value based on its type\n * @param value - The value to redact\n * @returns The redacted value\n */\nfunction redactValue(value: any): any {\n if (typeof value === 'string') {\n return '[REDACTED]';\n } else if (Array.isArray(value)) {\n return value.map((item) => redactValue(item));\n } else if (value && typeof value === 'object') {\n const redactedObj: any = {};\n for (const key in value) {\n if (value.hasOwnProperty(key)) {\n redactedObj[key] = redactValue(value[key]);\n }\n }\n return redactedObj;\n }\n return value; // Return as-is for other types (numbers, booleans, null, etc.)\n}\n\n/**\n * Validates that a scope value doesn't contain the '#' character which is reserved for scope construction\n */\nfunction validateScopeValue(scopeValue: string | number | Record<string, string | number>, scopeType: string): void {\n if (typeof scopeValue === 'string' && scopeValue.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType} scope value: ${scopeValue}`);\n }\n if (typeof scopeValue === 'object' && scopeValue !== null) {\n for (const [key, value] of Object.entries(scopeValue)) {\n if (typeof value === 'string' && value.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType}.${key}: ${value}`);\n }\n }\n }\n}\n\n/**\n * Constructs a scope string from scopeType and scopeValue\n * @param scopeType - The type of scope (chatapp, agent, tool, entity, agent-entity)\n * @param scopeValue - The value(s) for the scope\n * @returns The constructed scope string\n */\nfunction constructScope(scopeType: string, scopeValue: string | number | Record<string, string | number>): string {\n // Validate scope value doesn't contain '#'\n validateScopeValue(scopeValue, scopeType);\n\n switch (scopeType) {\n case 'chatapp':\n case 'agent':\n case 'tool':\n case 'entity':\n return `${scopeType}#${scopeValue}`;\n case 'agent-entity':\n if (typeof scopeValue !== 'object' || scopeValue === null) {\n throw new Error('agent-entity scopeType requires an object with agent and entity properties');\n }\n\n const agentEntityValue = scopeValue as Record<string, string | number>;\n if (!('agent' in agentEntityValue) || !('entity' in agentEntityValue)) {\n throw new Error('agent-entity scopeType requires an object with both agent and entity properties');\n }\n\n return `agent#${agentEntityValue.agent}#entity#${agentEntityValue.entity}`;\n\n default:\n throw new Error(`Unsupported scopeType: ${scopeType}`);\n }\n}\n\n/**\n * Parses a scope string back into scopeType and scopeValue\n * @param scope - The scope string to parse\n * @returns Object containing scopeType and scopeValue\n */\nfunction parseScope(scope: string): { scopeType: string; scopeValue: string | number | Record<string, string | number> } {\n if (!scope || typeof scope !== 'string') {\n throw new Error('Invalid scope: must be a non-empty string');\n }\n\n const parts = scope.split('#');\n if (parts.length < 2) {\n throw new Error(`Invalid scope format: ${scope}. Expected format: scopeType#scopeValue`);\n }\n\n const [firstType, firstValue, ...remaining] = parts;\n\n // Handle compound scopes (agent-entity)\n if (remaining.length >= 2 && firstType === 'agent' && remaining[0] === 'entity') {\n return {\n scopeType: 'agent-entity',\n scopeValue: {\n agent: firstValue,\n entity: remaining[1]\n }\n };\n }\n\n // Handle simple scopes\n if (remaining.length === 0) {\n // For simple scopes, try to convert to number if it's numeric\n const numericValue = Number(firstValue);\n const scopeValue = !isNaN(numericValue) && isFinite(numericValue) ? numericValue : firstValue;\n\n return {\n scopeType: firstType,\n scopeValue: scopeValue\n };\n }\n\n throw new Error(`Unsupported scope format: ${scope}`);\n}\n\n// Export both functions\nexport { redactData, redactValue, constructScope, parseScope };\n"]}
1
+ {"version":3,"sources":["../../src/util/server-client-utils.ts"],"names":[],"mappings":"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","file":"server-client-utils.mjs","sourcesContent":["/*\n * These are utils that are safe to use both on the server and the client.\n */\n\n/**\n * Helper function to redact sensitive data from specified attributes\n * @param data - The object containing data to redact\n * @param attributesToRedact - Single attribute name or array of attribute names to redact\n * @returns A new object with specified attributes redacted\n */\nfunction redactData(data: any, attributesToRedact: string | string[]): any {\n if (!data || typeof data !== 'object') {\n return data;\n }\n\n const attributes = Array.isArray(attributesToRedact) ? attributesToRedact : [attributesToRedact];\n const redacted = { ...data };\n\n for (const attr of attributes) {\n if (attr in redacted) {\n redacted[attr] = redactValue(redacted[attr]);\n }\n }\n\n return redacted;\n}\n\n/**\n * Recursively redacts a value based on its type\n * @param value - The value to redact\n * @returns The redacted value\n */\nfunction redactValue(value: any): any {\n if (typeof value === 'string') {\n return '[REDACTED]';\n } else if (Array.isArray(value)) {\n return value.map((item) => redactValue(item));\n } else if (value && typeof value === 'object') {\n const redactedObj: any = {};\n for (const key in value) {\n if (value.hasOwnProperty(key)) {\n redactedObj[key] = redactValue(value[key]);\n }\n }\n return redactedObj;\n }\n return value; // Return as-is for other types (numbers, booleans, null, etc.)\n}\n\n/**\n * Validates that a scope value doesn't contain the '#' character which is reserved for scope construction\n */\nfunction validateScopeValue(scopeValue: string | number | Record<string, string | number>, scopeType: string): void {\n if (typeof scopeValue === 'string' && scopeValue.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType} scope value: ${scopeValue}`);\n }\n if (typeof scopeValue === 'object' && scopeValue !== null) {\n for (const [key, value] of Object.entries(scopeValue)) {\n if (typeof value === 'string' && value.includes('#')) {\n throw new Error(`Scope value cannot contain '#' character. Found in ${scopeType}.${key}: ${value}`);\n }\n }\n }\n}\n\n/**\n * Constructs a scope string from scopeType and scopeValue\n * @param scopeType - The type of scope (chatapp, agent, tool, entity, agent-entity)\n * @param scopeValue - The value(s) for the scope\n * @returns The constructed scope string\n */\nfunction constructScope(scopeType: string, scopeValue: string | number | Record<string, string | number>): string {\n // Validate scope value doesn't contain '#'\n validateScopeValue(scopeValue, scopeType);\n\n switch (scopeType) {\n case 'chatapp':\n case 'agent':\n case 'tool':\n case 'entity':\n return `${scopeType}#${scopeValue}`;\n case 'agent-entity':\n if (typeof scopeValue !== 'object' || scopeValue === null) {\n throw new Error('agent-entity scopeType requires an object with agent and entity properties');\n }\n\n const agentEntityValue = scopeValue as Record<string, string | number>;\n if (!('agent' in agentEntityValue) || !('entity' in agentEntityValue)) {\n throw new Error('agent-entity scopeType requires an object with both agent and entity properties');\n }\n\n return `agent#${agentEntityValue.agent}#entity#${agentEntityValue.entity}`;\n\n default:\n throw new Error(`Unsupported scopeType: ${scopeType}`);\n }\n}\n\n/**\n * Parses a scope string back into scopeType and scopeValue\n * @param scope - The scope string to parse\n * @returns Object containing scopeType and scopeValue\n */\nfunction parseScope(scope: string): { scopeType: string; scopeValue: string | number | Record<string, string | number> } {\n if (!scope || typeof scope !== 'string') {\n throw new Error('Invalid scope: must be a non-empty string');\n }\n\n const parts = scope.split('#');\n if (parts.length < 2) {\n throw new Error(`Invalid scope format: ${scope}. Expected format: scopeType#scopeValue`);\n }\n\n const [firstType, firstValue, ...remaining] = parts;\n\n // Handle compound scopes (agent-entity)\n if (remaining.length >= 2 && firstType === 'agent' && remaining[0] === 'entity') {\n return {\n scopeType: 'agent-entity',\n scopeValue: {\n agent: firstValue,\n entity: remaining[1]\n }\n };\n }\n\n // Handle simple scopes\n if (remaining.length === 0) {\n // For simple scopes, try to convert to number if it's numeric\n const numericValue = Number(firstValue);\n const scopeValue = !isNaN(numericValue) && isFinite(numericValue) ? numericValue : firstValue;\n\n return {\n scopeType: firstType,\n scopeValue: scopeValue\n };\n }\n\n throw new Error(`Unsupported scope format: ${scope}`);\n}\n\n/**\n * Useful for simple hashing like to figure out if content has changed since last sent or something.\n */\nasync function getContentHashString(content: unknown): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(JSON.stringify(content));\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n return hashHex;\n}\n\n// Export both functions\nexport { redactData, redactValue, constructScope, parseScope, getContentHashString };\n"]}