pika-shared 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,8 @@ 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>;
180
+ readonly user: ChatUser;
179
181
  setCurrentSessionById(sessionId: string): void;
180
182
  removeFile(s3Key: string): void;
181
183
  startNewChatSession(): void;
@@ -186,11 +188,126 @@ interface IChatAppState {
186
188
  getMessageByMessageId(messageId: string): ChatMessageForRendering | undefined;
187
189
  uploadFiles(files: File[]): Promise<void>;
188
190
  initializeData(): Promise<void>;
189
- renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas', data?: Record<string, any>): Promise<void>;
191
+ renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas' | 'static', data?: Record<string, any>, metadata?: WidgetMetadata): Promise<void>;
190
192
  closeCanvas(): void;
191
193
  closeDialog(): void;
192
194
  setOrUpdateCustomTitleBarAction(action: ChatAppActionMenu | ChatAppAction): void;
193
195
  removeCustomTitleBarAction(actionId: string): void;
196
+ getWidgetInstance(instanceId: string): WidgetInstance | undefined;
197
+ /**
198
+ * Update context for a widget. Call this when your widget's context has changed.
199
+ *
200
+ * This method will re-check your widget's `getContextForLlm()` method and update
201
+ * the context accordingly. Use this when:
202
+ * - Your widget initially had no context, but now has context to share
203
+ * - Your widget's context data has changed (e.g., user selected different items)
204
+ * - You want to change whether context should be auto-added
205
+ *
206
+ * @param instanceId - Your widget's instance ID (from getPikaContext())
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * // In a Svelte widget component:
211
+ * let selectedCities = $state<string[]>([]);
212
+ *
213
+ * onMount(async () => {
214
+ * const context = await getPikaContext($host());
215
+ *
216
+ * // Update context whenever selection changes
217
+ * $effect(() => {
218
+ * if (selectedCities.length > 0) {
219
+ * // This triggers re-evaluation of getContextForLlm()
220
+ * context.chatAppState.updateWidgetContext(context.instanceId);
221
+ * }
222
+ * });
223
+ * });
224
+ *
225
+ * // Your getContextForLlm() method will be called again
226
+ * getContextForLlm() {
227
+ * if (selectedCities.length === 0) return undefined;
228
+ *
229
+ * return {
230
+ * origin: 'auto',
231
+ * title: 'Selected Cities',
232
+ * description: `User selected ${selectedCities.length} cities`,
233
+ * data: { cities: selectedCities },
234
+ * addAutomatically: true
235
+ * };
236
+ * }
237
+ * ```
238
+ */
239
+ updateWidgetContext(instanceId: string): void;
240
+ /**
241
+ * Manually register a web component as a spotlight widget. This allows components to
242
+ * dynamically add themselves to the spotlight area at runtime.
243
+ *
244
+ * Note: Registration is ephemeral and does not persist across page refreshes.
245
+ * Components must re-register themselves on each page load.
246
+ *
247
+ * @param definition - Simplified definition for the spotlight widget
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * chatAppState.manuallyRegisterSpotlightWidget({
252
+ * tag: 'my-widget',
253
+ * scope: 'my-app',
254
+ * tagTitle: 'My Widget',
255
+ * displayOrder: 0
256
+ * });
257
+ * ```
258
+ */
259
+ manuallyRegisterSpotlightWidget(definition: SpotlightWidgetDefinition): void;
260
+ /**
261
+ * Save a persistent spotlight instance using the Virtual Tags Pattern.
262
+ * Creates a new instance with its own UserWidgetDataStore (400KB limit per instance),
263
+ * saves the data, registers it as a spotlight widget, and renders it immediately.
264
+ *
265
+ * Use this when users save content (like charts, queries, etc.) to spotlight.
266
+ *
267
+ * @param scope - Widget scope (e.g., 'weather')
268
+ * @param baseTag - Base tag name (e.g., 'chart-saved')
269
+ * @param displayName - User-facing name for this instance
270
+ * @param customElementName - The custom element name (same for all instances)
271
+ * @param data - The data to pass to this instance
272
+ * @param dataKey - The key to store data under (default: 'data')
273
+ * @param metadata - Optional widget metadata (title, actions, icon, etc.)
274
+ * @returns The instance ID (UUID)
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const instanceId = await chatAppState.saveSpotlightInstance(
279
+ * 'weather',
280
+ * 'chart-saved',
281
+ * 'Q4 Revenue Chart',
282
+ * 'weather-chart-saved',
283
+ * { chartType: 'bar', data: [...] },
284
+ * 'chartData',
285
+ * {
286
+ * title: 'Q4 Revenue Chart',
287
+ * iconSvg: '<svg>...</svg>',
288
+ * actions: [...]
289
+ * }
290
+ * );
291
+ * ```
292
+ */
293
+ saveSpotlightInstance(scope: string, baseTag: string, displayName: string, customElementName: string, data: Record<string, any>, dataKey?: string, metadata?: WidgetMetadata): Promise<string>;
294
+ /**
295
+ * Delete a saved spotlight instance.
296
+ * Removes from spotlight, removes from registry, and unregisters from state.
297
+ *
298
+ * Note: Instance data is currently orphaned (not deleted) but could be recovered.
299
+ * Future: Will add deleteAll() method to UserWidgetDataStore.
300
+ *
301
+ * @param scope - Widget scope
302
+ * @param baseTag - Base tag name
303
+ * @param instanceId - Instance UUID
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * await chatAppState.deleteSpotlightInstance('weather', 'chart-saved', instanceId);
308
+ * ```
309
+ */
310
+ deleteSpotlightInstance(scope: string, baseTag: string, instanceId: string): Promise<void>;
194
311
  /**
195
312
  * Invoke the agent directly from a web component using the 'chat-app-component' invocation mode.
196
313
  * This allows components to make out-of-band requests to the LLM without creating user sessions.
@@ -324,6 +441,54 @@ interface IUploadInstance {
324
441
  error?: string;
325
442
  };
326
443
  }
444
+ /**
445
+ * Callback invoked when the web component has been created and is ready.
446
+ * Called after the element is created but before it's added to the DOM.
447
+ */
448
+ interface OnReadyCallback {
449
+ (params: {
450
+ /** The web component element that was created */
451
+ element: HTMLElement;
452
+ /** The unique instance ID assigned to this component */
453
+ instanceId: string;
454
+ /** The full Pika context with instanceId */
455
+ context: PikaWCContext;
456
+ }): void;
457
+ }
458
+ /**
459
+ * Structure for data passed to web components.
460
+ *
461
+ * Special fields that affect element initialization:
462
+ * - `attributes`: Set as HTML attributes (stringified) and also as properties if they exist
463
+ * - `properties`: Set as JavaScript properties only (not attributes)
464
+ * - `onReady`: Callback invoked when the component is created and ready
465
+ *
466
+ * All other fields are available through `context.dataForWidget` but not set on the element.
467
+ */
468
+ interface DataForWidget {
469
+ /**
470
+ * HTML attributes to set on the element.
471
+ * Values are stringified and set via `setAttributeNS()`.
472
+ * If a corresponding property exists on the element, it's also set with the original value.
473
+ */
474
+ attributes?: Record<string, any>;
475
+ /**
476
+ * JavaScript properties to set on the element (not as HTML attributes).
477
+ * Only properties that exist on the element will be set.
478
+ * Use this for complex objects, arrays, functions, etc.
479
+ */
480
+ properties?: Record<string, any>;
481
+ /**
482
+ * Callback invoked when the web component is created and ready.
483
+ * Called after element creation, property/attribute setting, and context setup,
484
+ * but before the element is added to the DOM.
485
+ *
486
+ * Use this to get notified when the component is ready and to access the element directly.
487
+ */
488
+ onReady?: OnReadyCallback;
489
+ /** Any other data available through context (not set on the element) */
490
+ [key: string]: any;
491
+ }
327
492
  /**
328
493
  * This is the context object that is passed to the web component when it is rendered.
329
494
  */
@@ -337,8 +502,8 @@ interface PikaWCContext {
337
502
  * Set by injectChatAppWebComponent() and used by getWidgetMetadataAPI().
338
503
  */
339
504
  instanceId: string;
340
- /** Arbitrary data to be passed to the widget. */
341
- dataForWidget: Record<string, any>;
505
+ /** Data passed to the widget, available through `context.dataForWidget`. */
506
+ dataForWidget: DataForWidget;
342
507
  }
343
508
  type PikaWCContextWithoutInstanceId = Omit<PikaWCContext, 'instanceId'>;
344
509
  type PikaWCContextRequestCallbackFn = (contextRequest: PikaWCContext) => void;
@@ -349,4 +514,4 @@ interface PikaWCContextRequestEvent extends CustomEvent<PikaWCContextRequestDeta
349
514
  detail: PikaWCContextRequestDetail;
350
515
  }
351
516
 
352
- export type { IAppState, IChatAppState, IIdentityState, IUploadInstance, IUserPrefsState, IWidgetMetadataAPI, PikaWCContext, PikaWCContextRequestCallbackFn, PikaWCContextRequestDetail, PikaWCContextRequestEvent, PikaWCContextWithoutInstanceId, SidebarState, Snippet };
517
+ 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,8 @@ 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>;
180
+ readonly user: ChatUser;
179
181
  setCurrentSessionById(sessionId: string): void;
180
182
  removeFile(s3Key: string): void;
181
183
  startNewChatSession(): void;
@@ -186,11 +188,126 @@ interface IChatAppState {
186
188
  getMessageByMessageId(messageId: string): ChatMessageForRendering | undefined;
187
189
  uploadFiles(files: File[]): Promise<void>;
188
190
  initializeData(): Promise<void>;
189
- renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas', data?: Record<string, any>): Promise<void>;
191
+ renderTag(tagId: string, context: 'spotlight' | 'inline' | 'dialog' | 'canvas' | 'static', data?: Record<string, any>, metadata?: WidgetMetadata): Promise<void>;
190
192
  closeCanvas(): void;
191
193
  closeDialog(): void;
192
194
  setOrUpdateCustomTitleBarAction(action: ChatAppActionMenu | ChatAppAction): void;
193
195
  removeCustomTitleBarAction(actionId: string): void;
196
+ getWidgetInstance(instanceId: string): WidgetInstance | undefined;
197
+ /**
198
+ * Update context for a widget. Call this when your widget's context has changed.
199
+ *
200
+ * This method will re-check your widget's `getContextForLlm()` method and update
201
+ * the context accordingly. Use this when:
202
+ * - Your widget initially had no context, but now has context to share
203
+ * - Your widget's context data has changed (e.g., user selected different items)
204
+ * - You want to change whether context should be auto-added
205
+ *
206
+ * @param instanceId - Your widget's instance ID (from getPikaContext())
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * // In a Svelte widget component:
211
+ * let selectedCities = $state<string[]>([]);
212
+ *
213
+ * onMount(async () => {
214
+ * const context = await getPikaContext($host());
215
+ *
216
+ * // Update context whenever selection changes
217
+ * $effect(() => {
218
+ * if (selectedCities.length > 0) {
219
+ * // This triggers re-evaluation of getContextForLlm()
220
+ * context.chatAppState.updateWidgetContext(context.instanceId);
221
+ * }
222
+ * });
223
+ * });
224
+ *
225
+ * // Your getContextForLlm() method will be called again
226
+ * getContextForLlm() {
227
+ * if (selectedCities.length === 0) return undefined;
228
+ *
229
+ * return {
230
+ * origin: 'auto',
231
+ * title: 'Selected Cities',
232
+ * description: `User selected ${selectedCities.length} cities`,
233
+ * data: { cities: selectedCities },
234
+ * addAutomatically: true
235
+ * };
236
+ * }
237
+ * ```
238
+ */
239
+ updateWidgetContext(instanceId: string): void;
240
+ /**
241
+ * Manually register a web component as a spotlight widget. This allows components to
242
+ * dynamically add themselves to the spotlight area at runtime.
243
+ *
244
+ * Note: Registration is ephemeral and does not persist across page refreshes.
245
+ * Components must re-register themselves on each page load.
246
+ *
247
+ * @param definition - Simplified definition for the spotlight widget
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * chatAppState.manuallyRegisterSpotlightWidget({
252
+ * tag: 'my-widget',
253
+ * scope: 'my-app',
254
+ * tagTitle: 'My Widget',
255
+ * displayOrder: 0
256
+ * });
257
+ * ```
258
+ */
259
+ manuallyRegisterSpotlightWidget(definition: SpotlightWidgetDefinition): void;
260
+ /**
261
+ * Save a persistent spotlight instance using the Virtual Tags Pattern.
262
+ * Creates a new instance with its own UserWidgetDataStore (400KB limit per instance),
263
+ * saves the data, registers it as a spotlight widget, and renders it immediately.
264
+ *
265
+ * Use this when users save content (like charts, queries, etc.) to spotlight.
266
+ *
267
+ * @param scope - Widget scope (e.g., 'weather')
268
+ * @param baseTag - Base tag name (e.g., 'chart-saved')
269
+ * @param displayName - User-facing name for this instance
270
+ * @param customElementName - The custom element name (same for all instances)
271
+ * @param data - The data to pass to this instance
272
+ * @param dataKey - The key to store data under (default: 'data')
273
+ * @param metadata - Optional widget metadata (title, actions, icon, etc.)
274
+ * @returns The instance ID (UUID)
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const instanceId = await chatAppState.saveSpotlightInstance(
279
+ * 'weather',
280
+ * 'chart-saved',
281
+ * 'Q4 Revenue Chart',
282
+ * 'weather-chart-saved',
283
+ * { chartType: 'bar', data: [...] },
284
+ * 'chartData',
285
+ * {
286
+ * title: 'Q4 Revenue Chart',
287
+ * iconSvg: '<svg>...</svg>',
288
+ * actions: [...]
289
+ * }
290
+ * );
291
+ * ```
292
+ */
293
+ saveSpotlightInstance(scope: string, baseTag: string, displayName: string, customElementName: string, data: Record<string, any>, dataKey?: string, metadata?: WidgetMetadata): Promise<string>;
294
+ /**
295
+ * Delete a saved spotlight instance.
296
+ * Removes from spotlight, removes from registry, and unregisters from state.
297
+ *
298
+ * Note: Instance data is currently orphaned (not deleted) but could be recovered.
299
+ * Future: Will add deleteAll() method to UserWidgetDataStore.
300
+ *
301
+ * @param scope - Widget scope
302
+ * @param baseTag - Base tag name
303
+ * @param instanceId - Instance UUID
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * await chatAppState.deleteSpotlightInstance('weather', 'chart-saved', instanceId);
308
+ * ```
309
+ */
310
+ deleteSpotlightInstance(scope: string, baseTag: string, instanceId: string): Promise<void>;
194
311
  /**
195
312
  * Invoke the agent directly from a web component using the 'chat-app-component' invocation mode.
196
313
  * This allows components to make out-of-band requests to the LLM without creating user sessions.
@@ -324,6 +441,54 @@ interface IUploadInstance {
324
441
  error?: string;
325
442
  };
326
443
  }
444
+ /**
445
+ * Callback invoked when the web component has been created and is ready.
446
+ * Called after the element is created but before it's added to the DOM.
447
+ */
448
+ interface OnReadyCallback {
449
+ (params: {
450
+ /** The web component element that was created */
451
+ element: HTMLElement;
452
+ /** The unique instance ID assigned to this component */
453
+ instanceId: string;
454
+ /** The full Pika context with instanceId */
455
+ context: PikaWCContext;
456
+ }): void;
457
+ }
458
+ /**
459
+ * Structure for data passed to web components.
460
+ *
461
+ * Special fields that affect element initialization:
462
+ * - `attributes`: Set as HTML attributes (stringified) and also as properties if they exist
463
+ * - `properties`: Set as JavaScript properties only (not attributes)
464
+ * - `onReady`: Callback invoked when the component is created and ready
465
+ *
466
+ * All other fields are available through `context.dataForWidget` but not set on the element.
467
+ */
468
+ interface DataForWidget {
469
+ /**
470
+ * HTML attributes to set on the element.
471
+ * Values are stringified and set via `setAttributeNS()`.
472
+ * If a corresponding property exists on the element, it's also set with the original value.
473
+ */
474
+ attributes?: Record<string, any>;
475
+ /**
476
+ * JavaScript properties to set on the element (not as HTML attributes).
477
+ * Only properties that exist on the element will be set.
478
+ * Use this for complex objects, arrays, functions, etc.
479
+ */
480
+ properties?: Record<string, any>;
481
+ /**
482
+ * Callback invoked when the web component is created and ready.
483
+ * Called after element creation, property/attribute setting, and context setup,
484
+ * but before the element is added to the DOM.
485
+ *
486
+ * Use this to get notified when the component is ready and to access the element directly.
487
+ */
488
+ onReady?: OnReadyCallback;
489
+ /** Any other data available through context (not set on the element) */
490
+ [key: string]: any;
491
+ }
327
492
  /**
328
493
  * This is the context object that is passed to the web component when it is rendered.
329
494
  */
@@ -337,8 +502,8 @@ interface PikaWCContext {
337
502
  * Set by injectChatAppWebComponent() and used by getWidgetMetadataAPI().
338
503
  */
339
504
  instanceId: string;
340
- /** Arbitrary data to be passed to the widget. */
341
- dataForWidget: Record<string, any>;
505
+ /** Data passed to the widget, available through `context.dataForWidget`. */
506
+ dataForWidget: DataForWidget;
342
507
  }
343
508
  type PikaWCContextWithoutInstanceId = Omit<PikaWCContext, 'instanceId'>;
344
509
  type PikaWCContextRequestCallbackFn = (contextRequest: PikaWCContext) => void;
@@ -349,4 +514,4 @@ interface PikaWCContextRequestEvent extends CustomEvent<PikaWCContextRequestDeta
349
514
  detail: PikaWCContextRequestDetail;
350
515
  }
351
516
 
352
- export type { IAppState, IChatAppState, IIdentityState, IUploadInstance, IUserPrefsState, IWidgetMetadataAPI, PikaWCContext, PikaWCContextRequestCallbackFn, PikaWCContextRequestDetail, PikaWCContextRequestEvent, PikaWCContextWithoutInstanceId, SidebarState, Snippet };
517
+ 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"]}