@stina/extension-api 0.20.0 → 0.22.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.
@@ -104,6 +104,9 @@ export interface ExtensionContext {
104
104
  /** Local storage (if permitted) */
105
105
  readonly storage?: StorageAPI
106
106
 
107
+ /** Background workers (if permitted) */
108
+ readonly backgroundWorkers?: BackgroundWorkersAPI
109
+
107
110
  /** Logging (always available) */
108
111
  readonly log: LogAPI
109
112
  }
@@ -397,3 +400,217 @@ export interface ExtensionModule {
397
400
  */
398
401
  deactivate?(): void | Promise<void>
399
402
  }
403
+
404
+ // ============================================================================
405
+ // Background Workers API
406
+ // ============================================================================
407
+
408
+ /**
409
+ * Restart policy for background tasks.
410
+ * Controls how tasks are restarted after failures.
411
+ */
412
+ export interface BackgroundRestartPolicy {
413
+ /**
414
+ * When to restart the task:
415
+ * - 'always': Always restart, regardless of exit reason
416
+ * - 'on-failure': Only restart if the task threw an error
417
+ * - 'never': Never restart automatically
418
+ */
419
+ type: 'always' | 'on-failure' | 'never'
420
+
421
+ /**
422
+ * Maximum number of restarts before giving up.
423
+ * 0 means unlimited restarts.
424
+ * @default 0
425
+ */
426
+ maxRestarts?: number
427
+
428
+ /**
429
+ * Initial delay in milliseconds before first restart.
430
+ * @default 1000
431
+ */
432
+ initialDelayMs?: number
433
+
434
+ /**
435
+ * Maximum delay in milliseconds between restarts.
436
+ * @default 60000
437
+ */
438
+ maxDelayMs?: number
439
+
440
+ /**
441
+ * Multiplier for exponential backoff.
442
+ * @default 2
443
+ */
444
+ backoffMultiplier?: number
445
+ }
446
+
447
+ /**
448
+ * Configuration for a background task.
449
+ */
450
+ export interface BackgroundTaskConfig {
451
+ /**
452
+ * Unique identifier for the task within the extension.
453
+ * Used to reference the task for stopping or checking status.
454
+ */
455
+ id: string
456
+
457
+ /**
458
+ * Human-readable name for observability and logging.
459
+ */
460
+ name: string
461
+
462
+ /**
463
+ * User ID that owns this task.
464
+ * Background tasks are always user-scoped.
465
+ */
466
+ userId: string
467
+
468
+ /**
469
+ * Policy for restarting the task after failures.
470
+ */
471
+ restartPolicy: BackgroundRestartPolicy
472
+
473
+ /**
474
+ * Optional payload data passed to the task callback.
475
+ */
476
+ payload?: Record<string, unknown>
477
+ }
478
+
479
+ /**
480
+ * Context provided to background task callbacks.
481
+ * Extends ExecutionContext with task-specific functionality.
482
+ */
483
+ export interface BackgroundTaskContext extends ExecutionContext {
484
+ /**
485
+ * AbortSignal that is triggered when the task should stop.
486
+ * Check this signal regularly and exit gracefully when aborted.
487
+ */
488
+ readonly signal: AbortSignal
489
+
490
+ /**
491
+ * Report the current health status of the task.
492
+ * Use this to provide observability into what the task is doing.
493
+ *
494
+ * @param status Human-readable status message
495
+ */
496
+ reportHealth(status: string): void
497
+
498
+ /**
499
+ * Task-specific logging API.
500
+ * Messages are tagged with the task ID for easier debugging.
501
+ */
502
+ readonly log: LogAPI
503
+ }
504
+
505
+ /**
506
+ * Callback function for background tasks.
507
+ * The function should run until the signal is aborted, then clean up and return.
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * const callback: BackgroundTaskCallback = async (ctx) => {
512
+ * const connection = await createConnection()
513
+ * try {
514
+ * while (!ctx.signal.aborted) {
515
+ * ctx.reportHealth('Waiting for messages...')
516
+ * const message = await connection.receive({ signal: ctx.signal })
517
+ * await processMessage(message)
518
+ * }
519
+ * } finally {
520
+ * await connection.close()
521
+ * }
522
+ * }
523
+ * ```
524
+ */
525
+ export type BackgroundTaskCallback = (context: BackgroundTaskContext) => Promise<void>
526
+
527
+ /**
528
+ * Health status of a background task.
529
+ */
530
+ export interface BackgroundTaskHealth {
531
+ /**
532
+ * Unique task identifier within the extension.
533
+ */
534
+ taskId: string
535
+
536
+ /**
537
+ * Human-readable task name.
538
+ */
539
+ name: string
540
+
541
+ /**
542
+ * User ID that owns this task.
543
+ */
544
+ userId: string
545
+
546
+ /**
547
+ * Current task status.
548
+ */
549
+ status: 'pending' | 'running' | 'stopped' | 'failed' | 'restarting'
550
+
551
+ /**
552
+ * Number of times the task has been restarted.
553
+ */
554
+ restartCount: number
555
+
556
+ /**
557
+ * Last health status message reported by the task.
558
+ */
559
+ lastHealthStatus?: string
560
+
561
+ /**
562
+ * Timestamp of the last health report.
563
+ */
564
+ lastHealthTime?: string
565
+
566
+ /**
567
+ * Error message if the task failed.
568
+ */
569
+ error?: string
570
+ }
571
+
572
+ /**
573
+ * API for managing background workers.
574
+ * Background workers are long-running tasks that can be automatically restarted.
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * const task = await context.backgroundWorkers.start({
579
+ * id: 'my-task',
580
+ * name: 'My Background Task',
581
+ * userId: 'user-123',
582
+ * restartPolicy: { type: 'always', maxRestarts: 0 }
583
+ * }, async (ctx) => {
584
+ * while (!ctx.signal.aborted) {
585
+ * // Do work...
586
+ * }
587
+ * })
588
+ *
589
+ * // Later, to stop the task:
590
+ * task.dispose()
591
+ * ```
592
+ */
593
+ export interface BackgroundWorkersAPI {
594
+ /**
595
+ * Start a new background task.
596
+ *
597
+ * @param config Task configuration
598
+ * @param callback Function to execute as the background task
599
+ * @returns Disposable that stops the task when disposed
600
+ */
601
+ start(config: BackgroundTaskConfig, callback: BackgroundTaskCallback): Promise<Disposable>
602
+
603
+ /**
604
+ * Stop a running background task.
605
+ *
606
+ * @param taskId The task ID to stop
607
+ */
608
+ stop(taskId: string): Promise<void>
609
+
610
+ /**
611
+ * Get the health status of all background tasks for this extension.
612
+ *
613
+ * @returns Array of task health statuses
614
+ */
615
+ getStatus(): Promise<BackgroundTaskHealth[]>
616
+ }
@@ -39,6 +39,7 @@ export type CapabilityPermission =
39
39
  | 'events.emit'
40
40
  | 'scheduler.register'
41
41
  | 'chat.message.write'
42
+ | 'background.workers'
42
43
 
43
44
  /** System permissions */
44
45
  export type SystemPermission =
package/src/types.ts CHANGED
@@ -95,4 +95,11 @@ export type {
95
95
  StorageAPI,
96
96
  LogAPI,
97
97
  ExtensionModule,
98
+ // Background workers
99
+ BackgroundRestartPolicy,
100
+ BackgroundTaskConfig,
101
+ BackgroundTaskContext,
102
+ BackgroundTaskCallback,
103
+ BackgroundTaskHealth,
104
+ BackgroundWorkersAPI,
98
105
  } from './types.context.js'
@@ -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 | StreamingFetchChunkMessage\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 /** User ID if the tool is executed in a user context */\n userId?: string\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 /** User ID if the action is executed in a user context */\n userId?: string\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 * Message sent from host to worker with streaming fetch data chunks.\n * Used for streaming network responses (e.g., NDJSON streams from Ollama).\n */\nexport interface StreamingFetchChunkMessage {\n type: 'streaming-fetch-chunk'\n id: string\n payload: {\n requestId: string\n chunk: string\n done: boolean\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 | StreamingFetchAckMessage\n\nexport interface ReadyMessage {\n type: 'ready'\n}\n\n/**\n * Message sent from worker to host to acknowledge receipt of a streaming fetch chunk.\n * This enables backpressure control to prevent unbounded memory growth.\n */\nexport interface StreamingFetchAckMessage {\n type: 'streaming-fetch-ack'\n payload: {\n requestId: string\n }\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 | 'network.fetch-stream'\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 | 'storage.getForUser'\n | 'storage.setForUser'\n | 'storage.deleteForUser'\n | 'storage.keysForUser'\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":";;;;;;;;AAgRO,SAAS,oBAA4B;AAC1C,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACjE;","names":[]}