@stina/extension-api 0.11.2 → 0.14.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.
package/src/types.ts CHANGED
@@ -435,7 +435,28 @@ export interface Disposable {
435
435
  }
436
436
 
437
437
  /**
438
- * Context provided to extension's activate function
438
+ * Context provided to extension's activate function.
439
+ *
440
+ * ## User ID Context
441
+ *
442
+ * The `userId` field provides the current user context for extensions. It is set when:
443
+ * - A tool is executed by a user
444
+ * - An action is executed by a user
445
+ * - A scheduled job fires (if the job was created with a userId)
446
+ *
447
+ * For extension activation, `userId` is undefined since activation happens at system level.
448
+ * Extensions should check for `userId` in their tool/action handlers to access user-specific data.
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * // In a tool execute handler:
453
+ * execute: async (params, context) => {
454
+ * if (context.userId) {
455
+ * // User-specific logic
456
+ * const userData = await storage.getForUser(context.userId, 'preferences')
457
+ * }
458
+ * }
459
+ * ```
439
460
  */
440
461
  export interface ExtensionContext {
441
462
  /** Extension metadata */
@@ -445,6 +466,12 @@ export interface ExtensionContext {
445
466
  readonly storagePath: string
446
467
  }
447
468
 
469
+ /**
470
+ * Current user ID if in a user context, undefined for global/system operations.
471
+ * Set when tools or actions are executed by a user, or when a user-scoped job fires.
472
+ */
473
+ readonly userId?: string
474
+
448
475
  /** Network access (if permitted) */
449
476
  readonly network?: NetworkAPI
450
477
 
@@ -573,6 +600,12 @@ export interface SchedulerJobRequest {
573
600
  schedule: SchedulerSchedule
574
601
  payload?: Record<string, unknown>
575
602
  misfire?: 'run_once' | 'skip'
603
+ /**
604
+ * Optional user ID for user-scoped jobs.
605
+ * If set, the job is associated with a specific user and the userId
606
+ * will be passed to the extension when the job fires.
607
+ */
608
+ userId?: string
576
609
  }
577
610
 
578
611
  /**
@@ -584,6 +617,8 @@ export interface SchedulerFirePayload {
584
617
  scheduledFor: string
585
618
  firedAt: string
586
619
  delayMs: number
620
+ /** User ID if this is a user-scoped job, undefined if global */
621
+ userId?: string
587
622
  }
588
623
 
589
624
  /**
@@ -638,28 +673,78 @@ export interface DatabaseAPI {
638
673
  }
639
674
 
640
675
  /**
641
- * Simple key-value storage API
676
+ * Simple key-value storage API with support for user-scoped storage.
677
+ *
678
+ * ## Global vs User-Scoped Storage
679
+ *
680
+ * Extensions have access to two types of storage:
681
+ * - **Global storage**: Shared across all users, accessed via `get()`, `set()`, etc.
682
+ * - **User-scoped storage**: Isolated per user, accessed via `getForUser()`, `setForUser()`, etc.
683
+ *
684
+ * Use global storage for extension-wide settings and user-scoped storage for
685
+ * user preferences, session data, or any data that should be private to a user.
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * // Global storage (extension-wide)
690
+ * await storage.set('apiEndpoint', 'https://api.example.com')
691
+ * const endpoint = await storage.get<string>('apiEndpoint')
692
+ *
693
+ * // User-scoped storage (per-user)
694
+ * if (context.userId) {
695
+ * await storage.setForUser(context.userId, 'preferences', { theme: 'dark' })
696
+ * const prefs = await storage.getForUser<Preferences>(context.userId, 'preferences')
697
+ * }
698
+ * ```
642
699
  */
643
700
  export interface StorageAPI {
644
701
  /**
645
- * Get a value by key
702
+ * Get a value by key (global/extension-scoped)
646
703
  */
647
704
  get<T>(key: string): Promise<T | undefined>
648
705
 
649
706
  /**
650
- * Set a value
707
+ * Set a value (global/extension-scoped)
651
708
  */
652
709
  set(key: string, value: unknown): Promise<void>
653
710
 
654
711
  /**
655
- * Delete a key
712
+ * Delete a key (global/extension-scoped)
656
713
  */
657
714
  delete(key: string): Promise<void>
658
715
 
659
716
  /**
660
- * Get all keys
717
+ * Get all keys (global/extension-scoped)
661
718
  */
662
719
  keys(): Promise<string[]>
720
+
721
+ /**
722
+ * Get a value by key for a specific user (user-scoped)
723
+ * @param userId The user ID
724
+ * @param key The storage key
725
+ */
726
+ getForUser<T>(userId: string, key: string): Promise<T | undefined>
727
+
728
+ /**
729
+ * Set a value for a specific user (user-scoped)
730
+ * @param userId The user ID
731
+ * @param key The storage key
732
+ * @param value The value to store
733
+ */
734
+ setForUser(userId: string, key: string, value: unknown): Promise<void>
735
+
736
+ /**
737
+ * Delete a key for a specific user (user-scoped)
738
+ * @param userId The user ID
739
+ * @param key The storage key
740
+ */
741
+ deleteForUser(userId: string, key: string): Promise<void>
742
+
743
+ /**
744
+ * Get all keys for a specific user (user-scoped)
745
+ * @param userId The user ID
746
+ */
747
+ keysForUser(userId: string): Promise<string[]>
663
748
  }
664
749
 
665
750
  /**
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/messages.ts"],"sourcesContent":["/**\n * Message protocol between Extension Host and Extension Workers\n */\n\nimport type {\n ChatMessage,\n ChatOptions,\n GetModelsOptions,\n StreamEvent,\n ToolResult,\n ActionResult,\n ModelInfo,\n SchedulerFirePayload,\n} from './types.js'\n\n// ============================================================================\n// Host → Worker Messages\n// ============================================================================\n\nexport type HostToWorkerMessage =\n | ActivateMessage\n | DeactivateMessage\n | SettingsChangedMessage\n | SchedulerFireMessage\n | ProviderChatRequestMessage\n | ProviderModelsRequestMessage\n | ToolExecuteRequestMessage\n | ActionExecuteRequestMessage\n | ResponseMessage\n\nexport interface ActivateMessage {\n type: 'activate'\n id: string\n payload: {\n extensionId: string\n extensionVersion: string\n storagePath: string\n permissions: string[]\n settings: Record<string, unknown>\n }\n}\n\nexport interface DeactivateMessage {\n type: 'deactivate'\n id: string\n}\n\nexport interface SettingsChangedMessage {\n type: 'settings-changed'\n id: string\n payload: {\n key: string\n value: unknown\n }\n}\n\nexport interface SchedulerFireMessage {\n type: 'scheduler-fire'\n id: string\n payload: SchedulerFirePayload\n}\n\nexport interface ProviderChatRequestMessage {\n type: 'provider-chat-request'\n id: string\n payload: {\n providerId: string\n messages: ChatMessage[]\n options: ChatOptions\n }\n}\n\nexport interface ProviderModelsRequestMessage {\n type: 'provider-models-request'\n id: string\n payload: {\n providerId: string\n options?: GetModelsOptions\n }\n}\n\nexport interface ToolExecuteRequestMessage {\n type: 'tool-execute-request'\n id: string\n payload: {\n toolId: string\n params: Record<string, unknown>\n }\n}\n\nexport interface ActionExecuteRequestMessage {\n type: 'action-execute-request'\n id: string\n payload: {\n actionId: string\n params: Record<string, unknown>\n }\n}\n\nexport interface ResponseMessage {\n type: 'response'\n id: string\n payload: {\n requestId: string\n success: boolean\n data?: unknown\n error?: string\n }\n}\n\n// ============================================================================\n// Worker → Host Messages\n// ============================================================================\n\nexport type WorkerToHostMessage =\n | ReadyMessage\n | RequestMessage\n | ProviderRegisteredMessage\n | ToolRegisteredMessage\n | ActionRegisteredMessage\n | StreamEventMessage\n | LogMessage\n | ProviderModelsResponseMessage\n | ToolExecuteResponseMessage\n | ActionExecuteResponseMessage\n\nexport interface ReadyMessage {\n type: 'ready'\n}\n\nexport interface RequestMessage {\n type: 'request'\n id: string\n method: RequestMethod\n payload: unknown\n}\n\nexport type RequestMethod =\n | 'network.fetch'\n | 'settings.getAll'\n | 'settings.get'\n | 'settings.set'\n | 'user.getProfile'\n | 'events.emit'\n | 'scheduler.schedule'\n | 'scheduler.cancel'\n | 'chat.appendInstruction'\n | 'database.execute'\n | 'storage.get'\n | 'storage.set'\n | 'storage.delete'\n | 'storage.keys'\n\nexport interface ProviderRegisteredMessage {\n type: 'provider-registered'\n payload: {\n id: string\n name: string\n }\n}\n\nexport interface ToolRegisteredMessage {\n type: 'tool-registered'\n payload: {\n id: string\n name: string\n description: string\n parameters?: Record<string, unknown>\n }\n}\n\nexport interface ActionRegisteredMessage {\n type: 'action-registered'\n payload: {\n id: string\n }\n}\n\nexport interface StreamEventMessage {\n type: 'stream-event'\n payload: {\n requestId: string\n event: StreamEvent\n }\n}\n\nexport interface ProviderModelsResponseMessage {\n type: 'provider-models-response'\n payload: {\n requestId: string\n models: ModelInfo[]\n error?: string\n }\n}\n\nexport interface ToolExecuteResponseMessage {\n type: 'tool-execute-response'\n payload: {\n requestId: string\n result: ToolResult\n error?: string\n }\n}\n\nexport interface ActionExecuteResponseMessage {\n type: 'action-execute-response'\n payload: {\n requestId: string\n result: ActionResult\n error?: string\n }\n}\n\nexport interface LogMessage {\n type: 'log'\n payload: {\n level: 'debug' | 'info' | 'warn' | 'error'\n message: string\n data?: Record<string, unknown>\n }\n}\n\n// ============================================================================\n// Utility Types\n// ============================================================================\n\nexport interface PendingRequest<T = unknown> {\n resolve: (value: T) => void\n reject: (error: Error) => void\n timeout: ReturnType<typeof setTimeout>\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`\n}\n"],"mappings":";;;;;;;;AA2OO,SAAS,oBAA4B;AAC1C,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACjE;","names":[]}