@stina/extension-api 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{chunk-3Q3YXWOH.js → chunk-S3YP4QPF.js} +1 -1
  2. package/dist/{chunk-3Q3YXWOH.js.map → chunk-S3YP4QPF.js.map} +1 -1
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +12 -4
  5. package/dist/index.d.ts +12 -4
  6. package/dist/index.js +1 -1
  7. package/dist/runtime.cjs +24 -6
  8. package/dist/runtime.cjs.map +1 -1
  9. package/dist/runtime.d.cts +2 -2
  10. package/dist/runtime.d.ts +2 -2
  11. package/dist/runtime.js +25 -7
  12. package/dist/runtime.js.map +1 -1
  13. package/dist/schemas/index.cjs +26 -2
  14. package/dist/schemas/index.cjs.map +1 -1
  15. package/dist/schemas/index.d.cts +91 -5
  16. package/dist/schemas/index.d.ts +91 -5
  17. package/dist/schemas/index.js +22 -2
  18. package/dist/schemas/index.js.map +1 -1
  19. package/dist/{types.tools-R0xGhiBa.d.cts → types.tools-DcFBsfRV.d.cts} +105 -1
  20. package/dist/{types.tools-R0xGhiBa.d.ts → types.tools-DcFBsfRV.d.ts} +105 -1
  21. package/package.json +1 -1
  22. package/schema/extension-manifest.schema.json +39 -0
  23. package/src/background.test.ts +33 -0
  24. package/src/background.ts +9 -0
  25. package/src/index.ts +5 -0
  26. package/src/messages.ts +3 -0
  27. package/src/runtime/accountsApi.ts +26 -0
  28. package/src/runtime/executionContext.test.ts +46 -4
  29. package/src/runtime/executionContext.ts +10 -4
  30. package/src/runtime/index.ts +1 -0
  31. package/src/runtime.ts +15 -3
  32. package/src/schemas/accounts.schema.test.ts +58 -0
  33. package/src/schemas/contributions.schema.ts +48 -0
  34. package/src/schemas/index.ts +6 -0
  35. package/src/schemas/permissions.schema.ts +2 -0
  36. package/src/types.context.ts +68 -0
  37. package/src/types.contributions.ts +47 -0
  38. package/src/types.permissions.ts +8 -0
  39. package/src/types.ts +6 -0
@@ -789,6 +789,8 @@ interface ExtensionContributions {
789
789
  commands?: CommandDefinition[];
790
790
  /** Prompt contributions for the system prompt */
791
791
  prompts?: PromptContribution[];
792
+ /** External accounts the extension works through, signed in once in Stina's settings */
793
+ accounts?: AccountContribution[];
792
794
  /** Storage collection declarations */
793
795
  storage?: {
794
796
  collections: {
@@ -799,6 +801,45 @@ interface ExtensionContributions {
799
801
  };
800
802
  };
801
803
  }
804
+ /**
805
+ * An external account provider the host can sign in to on the user's behalf.
806
+ *
807
+ * Only Microsoft for now, and only through Microsoft Graph: mail and calendar
808
+ * then share one sign-in and one token, instead of an IMAP token and a Graph
809
+ * token that an admin has to approve separately.
810
+ */
811
+ type AccountProvider = 'microsoft';
812
+ /**
813
+ * What an extension needs from an account the user connects to Stina.
814
+ *
815
+ * Stina asks the provider for every scope that the enabled extensions declare,
816
+ * all at once, so that the user - and, at work, their administrator - approves
817
+ * one request instead of one per extension. Declaring a scope here is therefore
818
+ * also what makes it show up in that request, and in the list of what is asked
819
+ * for in the settings.
820
+ *
821
+ * @example
822
+ * ```json
823
+ * "accounts": [
824
+ * {
825
+ * "provider": "microsoft",
826
+ * "scopes": ["Calendars.ReadWrite"],
827
+ * "reason": { "en": "Read and book events in your calendar", "sv": "Läsa och boka i din kalender" }
828
+ * }
829
+ * ]
830
+ * ```
831
+ */
832
+ interface AccountContribution {
833
+ provider: AccountProvider;
834
+ /**
835
+ * Microsoft Graph delegated permissions, by name: `Mail.Read`,
836
+ * `Calendars.ReadWrite`. The host adds `offline_access` and `User.Read`
837
+ * itself, so they are not declared here.
838
+ */
839
+ scopes: string[];
840
+ /** Why the extension needs them, shown next to the scopes in the settings. */
841
+ reason?: LocalizedString;
842
+ }
802
843
  /**
803
844
  * Tool settings view definition (UI schema)
804
845
  */
@@ -1770,6 +1811,69 @@ interface ExecutionContext {
1770
1811
  * so in its manifest and check before reaching for them.
1771
1812
  */
1772
1813
  readonly attachments?: AttachmentsAPI;
1814
+ /**
1815
+ * The external accounts this user has connected to Stina.
1816
+ *
1817
+ * Present only for an extension holding `accounts.use`, and only on a request
1818
+ * that knows whose it is - an account belongs to somebody, like an attachment.
1819
+ */
1820
+ readonly accounts?: ConnectedAccountsAPI;
1821
+ }
1822
+ /**
1823
+ * Working through an account the user signed in to in Stina's settings.
1824
+ *
1825
+ * The user connects a Microsoft account once, and every extension that declares
1826
+ * Microsoft under `contributes.accounts` can use it. The extension never sees the
1827
+ * refresh token; it asks for an access token when it needs one and gets a fresh
1828
+ * one back, refreshed by the host when the old one has run out.
1829
+ *
1830
+ * One thing the host cannot narrow: Microsoft issues a Graph token carrying every
1831
+ * permission the user has granted Stina, not only the ones this extension
1832
+ * declared. The declaration decides whether an extension gets a token at all,
1833
+ * and what the user is asked to approve - not what the token can do.
1834
+ */
1835
+ interface ConnectedAccountsAPI {
1836
+ /**
1837
+ * The accounts this user has connected for a provider, newest first.
1838
+ *
1839
+ * An empty list is the normal answer for a user who has not connected one yet:
1840
+ * the extension should point them at Stina's settings rather than start a
1841
+ * sign-in of its own.
1842
+ */
1843
+ list(provider: 'microsoft'): Promise<ConnectedAccount[]>;
1844
+ /**
1845
+ * An access token for one of those accounts, for the scopes this extension
1846
+ * declared.
1847
+ *
1848
+ * Rejects when the account is gone, when the user has not granted every
1849
+ * declared scope yet, or when the provider has revoked the sign-in. The
1850
+ * message says which, in words the user can act on - it is fine to show it.
1851
+ */
1852
+ getAccessToken(accountId: string): Promise<AccountAccessToken>;
1853
+ }
1854
+ /** An account the user has connected, as an extension sees it. */
1855
+ interface ConnectedAccount {
1856
+ /** Stable id, to store alongside whatever the extension keeps for this account. */
1857
+ id: string;
1858
+ provider: 'microsoft';
1859
+ /** The address the user signs in with. */
1860
+ email: string;
1861
+ displayName?: string;
1862
+ /**
1863
+ * Whether the user has granted every scope this extension declared. False when
1864
+ * the extension was installed after the account was connected, and the user has
1865
+ * yet to reconnect it in the settings.
1866
+ */
1867
+ hasRequiredScopes: boolean;
1868
+ /** Set when the provider has refused the stored sign-in and it has to be redone. */
1869
+ needsReconnect: boolean;
1870
+ }
1871
+ /** An access token, and when it stops working. */
1872
+ interface AccountAccessToken {
1873
+ /** Bearer token for Microsoft Graph. */
1874
+ token: string;
1875
+ /** ISO timestamp. Ask again after this; the host refreshes. */
1876
+ expiresAt: string;
1773
1877
  }
1774
1878
  /**
1775
1879
  * Reading a file that is already in a conversation.
@@ -2507,4 +2611,4 @@ interface ActionResult {
2507
2611
  error?: string;
2508
2612
  }
2509
2613
 
2510
- export { type BackgroundTaskConfig as $, type ActionResult as A, type ActionsAPI as B, type ChatMessage as C, type Disposable as D, type ExtensionContributions as E, type EventsAPI as F, type GetModelsOptions as G, type HugeIconName as H, type SchedulerAPI as I, type SchedulerJobRequest as J, type SchedulerSchedule as K, type LocalizedString as L, type ModelInfo as M, type NetworkAPI as N, type UserProfile as O, type PanelDefinition as P, type ChatAPI as Q, type ChatInstructionMessage as R, type SchedulerFirePayload as S, type ToolResult as T, type UserAPI as U, type VoiceSessionOptions as V, type ConversationPresentation as W, type AttachmentsAPI as X, type AttachmentContent as Y, type LogAPI as Z, type BackgroundWorkersAPI as _, type ChatOptions as a, type WeatherWind as a$, type BackgroundTaskCallback as a0, type BackgroundTaskContext as a1, type BackgroundTaskHealth as a2, type BackgroundRestartPolicy as a3, type Query as a4, type QueryOptions as a5, type StorageAPI as a6, type SecretsAPI as a7, type StorageCollectionConfig as a8, type StorageContributions as a9, type NumberInputProps as aA, type TextAreaProps as aB, type DateTimeInputProps as aC, type SelectProps as aD, type IconPickerProps as aE, type VerticalStackProps as aF, type HorizontalStackProps as aG, type GridProps as aH, type DividerProps as aI, type IconProps as aJ, type IconButtonType as aK, type IconButtonProps as aL, type PanelAction as aM, type PanelProps as aN, type ToggleProps as aO, type CollapsibleProps as aP, type FrameVariant as aQ, type FrameProps as aR, type ListProps as aS, type PillVariant as aT, type PillProps as aU, type CheckboxProps as aV, type MarkdownProps as aW, type TextPreviewProps as aX, type ModalProps as aY, type ConditionalGroupProps as aZ, type WeatherCondition as a_, type AIProvider as aa, type ModelCapabilities as ab, type ChatImage as ac, type ChatFile as ad, type ToolCall as ae, type VoiceTransportRequest as af, type Tool as ag, type ToolAttachment as ah, type Action as ai, type ExtensionModule as aj, type AllowedCSSProperty as ak, type ExtensionComponentStyle as al, type ExtensionComponentData as am, type ExtensionComponentIterator as an, type ExtensionComponentChildren as ao, type ExtensionActionCall as ap, type ExtensionActionRef as aq, type ExtensionDataSource as ar, type ExtensionPanelDefinition as as, type HeaderProps as at, type LabelProps as au, type ClockProps as av, type ParagraphProps as aw, type ButtonProps as ax, type TextInputProps as ay, type PasswordInputProps as az, type StreamEvent as b, type WeatherNowProps as b0, type WeatherForecastStep as b1, type WeatherForecastProps as b2, type ChartKind as b3, type ChartSeries as b4, type ChartProps as b5, type StatTrend as b6, type StatTileProps as b7, type ProgressShape as b8, type ProgressColor as b9, type ProgressBarProps as ba, type KeyValueRow as bb, type KeyValueListProps as bc, type TimelineVariant as bd, type TimelineEntry as be, type TimelineProps as bf, type NoteVariant as bg, type NoteProps as bh, type CalendarEventStatus as bi, type CalendarEventProps as bj, type ExecutionContext as bk, type VoiceSessionDescriptor as c, type ToolSettingsViewDefinition as d, type ToolSettingsView as e, type ToolSettingsListView as f, type ToolSettingsListMapping as g, type ToolSettingsComponentView as h, type ToolSettingsActionDataSource as i, type StatusCardDefinition as j, type PanelView as k, type PanelComponentView as l, type PanelActionDataSource as m, type PanelUnknownView as n, type ProviderDefinition as o, type ProviderConfigView as p, type PromptContribution as q, resolveLocalizedString as r, type PromptSection as s, type ToolDefinition as t, type ToolConfirmationConfig as u, type CommandDefinition as v, type ExtensionContext as w, type SettingsAPI as x, type ProvidersAPI as y, type ToolsAPI as z };
2614
+ export { type ConnectedAccountsAPI as $, type ActionResult as A, type ProvidersAPI as B, type ChatMessage as C, type Disposable as D, type ExtensionContributions as E, type ToolsAPI as F, type GetModelsOptions as G, type HugeIconName as H, type ActionsAPI as I, type EventsAPI as J, type SchedulerAPI as K, type LocalizedString as L, type ModelInfo as M, type NetworkAPI as N, type SchedulerJobRequest as O, type PanelDefinition as P, type SchedulerSchedule as Q, type UserProfile as R, type SchedulerFirePayload as S, type ToolResult as T, type UserAPI as U, type VoiceSessionOptions as V, type ChatAPI as W, type ChatInstructionMessage as X, type ConversationPresentation as Y, type AttachmentsAPI as Z, type AttachmentContent as _, type ChatOptions as a, type MarkdownProps as a$, type ConnectedAccount as a0, type AccountAccessToken as a1, type LogAPI as a2, type BackgroundWorkersAPI as a3, type BackgroundTaskConfig as a4, type BackgroundTaskCallback as a5, type BackgroundTaskContext as a6, type BackgroundTaskHealth as a7, type BackgroundRestartPolicy as a8, type Query as a9, type ClockProps as aA, type ParagraphProps as aB, type ButtonProps as aC, type TextInputProps as aD, type PasswordInputProps as aE, type NumberInputProps as aF, type TextAreaProps as aG, type DateTimeInputProps as aH, type SelectProps as aI, type IconPickerProps as aJ, type VerticalStackProps as aK, type HorizontalStackProps as aL, type GridProps as aM, type DividerProps as aN, type IconProps as aO, type IconButtonType as aP, type IconButtonProps as aQ, type PanelAction as aR, type PanelProps as aS, type ToggleProps as aT, type CollapsibleProps as aU, type FrameVariant as aV, type FrameProps as aW, type ListProps as aX, type PillVariant as aY, type PillProps as aZ, type CheckboxProps as a_, type QueryOptions as aa, type StorageAPI as ab, type SecretsAPI as ac, type StorageCollectionConfig as ad, type StorageContributions as ae, type AIProvider as af, type ModelCapabilities as ag, type ChatImage as ah, type ChatFile as ai, type ToolCall as aj, type VoiceTransportRequest as ak, type Tool as al, type ToolAttachment as am, type Action as an, type ExtensionModule as ao, type AllowedCSSProperty as ap, type ExtensionComponentStyle as aq, type ExtensionComponentData as ar, type ExtensionComponentIterator as as, type ExtensionComponentChildren as at, type ExtensionActionCall as au, type ExtensionActionRef as av, type ExtensionDataSource as aw, type ExtensionPanelDefinition as ax, type HeaderProps as ay, type LabelProps as az, type StreamEvent as b, type TextPreviewProps as b0, type ModalProps as b1, type ConditionalGroupProps as b2, type WeatherCondition as b3, type WeatherWind as b4, type WeatherNowProps as b5, type WeatherForecastStep as b6, type WeatherForecastProps as b7, type ChartKind as b8, type ChartSeries as b9, type ChartProps as ba, type StatTrend as bb, type StatTileProps as bc, type ProgressShape as bd, type ProgressColor as be, type ProgressBarProps as bf, type KeyValueRow as bg, type KeyValueListProps as bh, type TimelineVariant as bi, type TimelineEntry as bj, type TimelineProps as bk, type NoteVariant as bl, type NoteProps as bm, type CalendarEventStatus as bn, type CalendarEventProps as bo, type ExecutionContext as bp, type VoiceSessionDescriptor as c, type ToolSettingsViewDefinition as d, type ToolSettingsView as e, type ToolSettingsListView as f, type ToolSettingsListMapping as g, type ToolSettingsComponentView as h, type ToolSettingsActionDataSource as i, type StatusCardDefinition as j, type PanelView as k, type PanelComponentView as l, type PanelActionDataSource as m, type PanelUnknownView as n, type ProviderDefinition as o, type ProviderConfigView as p, type PromptContribution as q, resolveLocalizedString as r, type PromptSection as s, type ToolDefinition as t, type ToolConfirmationConfig as u, type CommandDefinition as v, type AccountProvider as w, type AccountContribution as x, type ExtensionContext as y, type SettingsAPI as z };
@@ -789,6 +789,8 @@ interface ExtensionContributions {
789
789
  commands?: CommandDefinition[];
790
790
  /** Prompt contributions for the system prompt */
791
791
  prompts?: PromptContribution[];
792
+ /** External accounts the extension works through, signed in once in Stina's settings */
793
+ accounts?: AccountContribution[];
792
794
  /** Storage collection declarations */
793
795
  storage?: {
794
796
  collections: {
@@ -799,6 +801,45 @@ interface ExtensionContributions {
799
801
  };
800
802
  };
801
803
  }
804
+ /**
805
+ * An external account provider the host can sign in to on the user's behalf.
806
+ *
807
+ * Only Microsoft for now, and only through Microsoft Graph: mail and calendar
808
+ * then share one sign-in and one token, instead of an IMAP token and a Graph
809
+ * token that an admin has to approve separately.
810
+ */
811
+ type AccountProvider = 'microsoft';
812
+ /**
813
+ * What an extension needs from an account the user connects to Stina.
814
+ *
815
+ * Stina asks the provider for every scope that the enabled extensions declare,
816
+ * all at once, so that the user - and, at work, their administrator - approves
817
+ * one request instead of one per extension. Declaring a scope here is therefore
818
+ * also what makes it show up in that request, and in the list of what is asked
819
+ * for in the settings.
820
+ *
821
+ * @example
822
+ * ```json
823
+ * "accounts": [
824
+ * {
825
+ * "provider": "microsoft",
826
+ * "scopes": ["Calendars.ReadWrite"],
827
+ * "reason": { "en": "Read and book events in your calendar", "sv": "Läsa och boka i din kalender" }
828
+ * }
829
+ * ]
830
+ * ```
831
+ */
832
+ interface AccountContribution {
833
+ provider: AccountProvider;
834
+ /**
835
+ * Microsoft Graph delegated permissions, by name: `Mail.Read`,
836
+ * `Calendars.ReadWrite`. The host adds `offline_access` and `User.Read`
837
+ * itself, so they are not declared here.
838
+ */
839
+ scopes: string[];
840
+ /** Why the extension needs them, shown next to the scopes in the settings. */
841
+ reason?: LocalizedString;
842
+ }
802
843
  /**
803
844
  * Tool settings view definition (UI schema)
804
845
  */
@@ -1770,6 +1811,69 @@ interface ExecutionContext {
1770
1811
  * so in its manifest and check before reaching for them.
1771
1812
  */
1772
1813
  readonly attachments?: AttachmentsAPI;
1814
+ /**
1815
+ * The external accounts this user has connected to Stina.
1816
+ *
1817
+ * Present only for an extension holding `accounts.use`, and only on a request
1818
+ * that knows whose it is - an account belongs to somebody, like an attachment.
1819
+ */
1820
+ readonly accounts?: ConnectedAccountsAPI;
1821
+ }
1822
+ /**
1823
+ * Working through an account the user signed in to in Stina's settings.
1824
+ *
1825
+ * The user connects a Microsoft account once, and every extension that declares
1826
+ * Microsoft under `contributes.accounts` can use it. The extension never sees the
1827
+ * refresh token; it asks for an access token when it needs one and gets a fresh
1828
+ * one back, refreshed by the host when the old one has run out.
1829
+ *
1830
+ * One thing the host cannot narrow: Microsoft issues a Graph token carrying every
1831
+ * permission the user has granted Stina, not only the ones this extension
1832
+ * declared. The declaration decides whether an extension gets a token at all,
1833
+ * and what the user is asked to approve - not what the token can do.
1834
+ */
1835
+ interface ConnectedAccountsAPI {
1836
+ /**
1837
+ * The accounts this user has connected for a provider, newest first.
1838
+ *
1839
+ * An empty list is the normal answer for a user who has not connected one yet:
1840
+ * the extension should point them at Stina's settings rather than start a
1841
+ * sign-in of its own.
1842
+ */
1843
+ list(provider: 'microsoft'): Promise<ConnectedAccount[]>;
1844
+ /**
1845
+ * An access token for one of those accounts, for the scopes this extension
1846
+ * declared.
1847
+ *
1848
+ * Rejects when the account is gone, when the user has not granted every
1849
+ * declared scope yet, or when the provider has revoked the sign-in. The
1850
+ * message says which, in words the user can act on - it is fine to show it.
1851
+ */
1852
+ getAccessToken(accountId: string): Promise<AccountAccessToken>;
1853
+ }
1854
+ /** An account the user has connected, as an extension sees it. */
1855
+ interface ConnectedAccount {
1856
+ /** Stable id, to store alongside whatever the extension keeps for this account. */
1857
+ id: string;
1858
+ provider: 'microsoft';
1859
+ /** The address the user signs in with. */
1860
+ email: string;
1861
+ displayName?: string;
1862
+ /**
1863
+ * Whether the user has granted every scope this extension declared. False when
1864
+ * the extension was installed after the account was connected, and the user has
1865
+ * yet to reconnect it in the settings.
1866
+ */
1867
+ hasRequiredScopes: boolean;
1868
+ /** Set when the provider has refused the stored sign-in and it has to be redone. */
1869
+ needsReconnect: boolean;
1870
+ }
1871
+ /** An access token, and when it stops working. */
1872
+ interface AccountAccessToken {
1873
+ /** Bearer token for Microsoft Graph. */
1874
+ token: string;
1875
+ /** ISO timestamp. Ask again after this; the host refreshes. */
1876
+ expiresAt: string;
1773
1877
  }
1774
1878
  /**
1775
1879
  * Reading a file that is already in a conversation.
@@ -2507,4 +2611,4 @@ interface ActionResult {
2507
2611
  error?: string;
2508
2612
  }
2509
2613
 
2510
- export { type BackgroundTaskConfig as $, type ActionResult as A, type ActionsAPI as B, type ChatMessage as C, type Disposable as D, type ExtensionContributions as E, type EventsAPI as F, type GetModelsOptions as G, type HugeIconName as H, type SchedulerAPI as I, type SchedulerJobRequest as J, type SchedulerSchedule as K, type LocalizedString as L, type ModelInfo as M, type NetworkAPI as N, type UserProfile as O, type PanelDefinition as P, type ChatAPI as Q, type ChatInstructionMessage as R, type SchedulerFirePayload as S, type ToolResult as T, type UserAPI as U, type VoiceSessionOptions as V, type ConversationPresentation as W, type AttachmentsAPI as X, type AttachmentContent as Y, type LogAPI as Z, type BackgroundWorkersAPI as _, type ChatOptions as a, type WeatherWind as a$, type BackgroundTaskCallback as a0, type BackgroundTaskContext as a1, type BackgroundTaskHealth as a2, type BackgroundRestartPolicy as a3, type Query as a4, type QueryOptions as a5, type StorageAPI as a6, type SecretsAPI as a7, type StorageCollectionConfig as a8, type StorageContributions as a9, type NumberInputProps as aA, type TextAreaProps as aB, type DateTimeInputProps as aC, type SelectProps as aD, type IconPickerProps as aE, type VerticalStackProps as aF, type HorizontalStackProps as aG, type GridProps as aH, type DividerProps as aI, type IconProps as aJ, type IconButtonType as aK, type IconButtonProps as aL, type PanelAction as aM, type PanelProps as aN, type ToggleProps as aO, type CollapsibleProps as aP, type FrameVariant as aQ, type FrameProps as aR, type ListProps as aS, type PillVariant as aT, type PillProps as aU, type CheckboxProps as aV, type MarkdownProps as aW, type TextPreviewProps as aX, type ModalProps as aY, type ConditionalGroupProps as aZ, type WeatherCondition as a_, type AIProvider as aa, type ModelCapabilities as ab, type ChatImage as ac, type ChatFile as ad, type ToolCall as ae, type VoiceTransportRequest as af, type Tool as ag, type ToolAttachment as ah, type Action as ai, type ExtensionModule as aj, type AllowedCSSProperty as ak, type ExtensionComponentStyle as al, type ExtensionComponentData as am, type ExtensionComponentIterator as an, type ExtensionComponentChildren as ao, type ExtensionActionCall as ap, type ExtensionActionRef as aq, type ExtensionDataSource as ar, type ExtensionPanelDefinition as as, type HeaderProps as at, type LabelProps as au, type ClockProps as av, type ParagraphProps as aw, type ButtonProps as ax, type TextInputProps as ay, type PasswordInputProps as az, type StreamEvent as b, type WeatherNowProps as b0, type WeatherForecastStep as b1, type WeatherForecastProps as b2, type ChartKind as b3, type ChartSeries as b4, type ChartProps as b5, type StatTrend as b6, type StatTileProps as b7, type ProgressShape as b8, type ProgressColor as b9, type ProgressBarProps as ba, type KeyValueRow as bb, type KeyValueListProps as bc, type TimelineVariant as bd, type TimelineEntry as be, type TimelineProps as bf, type NoteVariant as bg, type NoteProps as bh, type CalendarEventStatus as bi, type CalendarEventProps as bj, type ExecutionContext as bk, type VoiceSessionDescriptor as c, type ToolSettingsViewDefinition as d, type ToolSettingsView as e, type ToolSettingsListView as f, type ToolSettingsListMapping as g, type ToolSettingsComponentView as h, type ToolSettingsActionDataSource as i, type StatusCardDefinition as j, type PanelView as k, type PanelComponentView as l, type PanelActionDataSource as m, type PanelUnknownView as n, type ProviderDefinition as o, type ProviderConfigView as p, type PromptContribution as q, resolveLocalizedString as r, type PromptSection as s, type ToolDefinition as t, type ToolConfirmationConfig as u, type CommandDefinition as v, type ExtensionContext as w, type SettingsAPI as x, type ProvidersAPI as y, type ToolsAPI as z };
2614
+ export { type ConnectedAccountsAPI as $, type ActionResult as A, type ProvidersAPI as B, type ChatMessage as C, type Disposable as D, type ExtensionContributions as E, type ToolsAPI as F, type GetModelsOptions as G, type HugeIconName as H, type ActionsAPI as I, type EventsAPI as J, type SchedulerAPI as K, type LocalizedString as L, type ModelInfo as M, type NetworkAPI as N, type SchedulerJobRequest as O, type PanelDefinition as P, type SchedulerSchedule as Q, type UserProfile as R, type SchedulerFirePayload as S, type ToolResult as T, type UserAPI as U, type VoiceSessionOptions as V, type ChatAPI as W, type ChatInstructionMessage as X, type ConversationPresentation as Y, type AttachmentsAPI as Z, type AttachmentContent as _, type ChatOptions as a, type MarkdownProps as a$, type ConnectedAccount as a0, type AccountAccessToken as a1, type LogAPI as a2, type BackgroundWorkersAPI as a3, type BackgroundTaskConfig as a4, type BackgroundTaskCallback as a5, type BackgroundTaskContext as a6, type BackgroundTaskHealth as a7, type BackgroundRestartPolicy as a8, type Query as a9, type ClockProps as aA, type ParagraphProps as aB, type ButtonProps as aC, type TextInputProps as aD, type PasswordInputProps as aE, type NumberInputProps as aF, type TextAreaProps as aG, type DateTimeInputProps as aH, type SelectProps as aI, type IconPickerProps as aJ, type VerticalStackProps as aK, type HorizontalStackProps as aL, type GridProps as aM, type DividerProps as aN, type IconProps as aO, type IconButtonType as aP, type IconButtonProps as aQ, type PanelAction as aR, type PanelProps as aS, type ToggleProps as aT, type CollapsibleProps as aU, type FrameVariant as aV, type FrameProps as aW, type ListProps as aX, type PillVariant as aY, type PillProps as aZ, type CheckboxProps as a_, type QueryOptions as aa, type StorageAPI as ab, type SecretsAPI as ac, type StorageCollectionConfig as ad, type StorageContributions as ae, type AIProvider as af, type ModelCapabilities as ag, type ChatImage as ah, type ChatFile as ai, type ToolCall as aj, type VoiceTransportRequest as ak, type Tool as al, type ToolAttachment as am, type Action as an, type ExtensionModule as ao, type AllowedCSSProperty as ap, type ExtensionComponentStyle as aq, type ExtensionComponentData as ar, type ExtensionComponentIterator as as, type ExtensionComponentChildren as at, type ExtensionActionCall as au, type ExtensionActionRef as av, type ExtensionDataSource as aw, type ExtensionPanelDefinition as ax, type HeaderProps as ay, type LabelProps as az, type StreamEvent as b, type TextPreviewProps as b0, type ModalProps as b1, type ConditionalGroupProps as b2, type WeatherCondition as b3, type WeatherWind as b4, type WeatherNowProps as b5, type WeatherForecastStep as b6, type WeatherForecastProps as b7, type ChartKind as b8, type ChartSeries as b9, type ChartProps as ba, type StatTrend as bb, type StatTileProps as bc, type ProgressShape as bd, type ProgressColor as be, type ProgressBarProps as bf, type KeyValueRow as bg, type KeyValueListProps as bh, type TimelineVariant as bi, type TimelineEntry as bj, type TimelineProps as bk, type NoteVariant as bl, type NoteProps as bm, type CalendarEventStatus as bn, type CalendarEventProps as bo, type ExecutionContext as bp, type VoiceSessionDescriptor as c, type ToolSettingsViewDefinition as d, type ToolSettingsView as e, type ToolSettingsListView as f, type ToolSettingsListMapping as g, type ToolSettingsComponentView as h, type ToolSettingsActionDataSource as i, type StatusCardDefinition as j, type PanelView as k, type PanelComponentView as l, type PanelActionDataSource as m, type PanelUnknownView as n, type ProviderDefinition as o, type ProviderConfigView as p, type PromptContribution as q, resolveLocalizedString as r, type PromptSection as s, type ToolDefinition as t, type ToolConfirmationConfig as u, type CommandDefinition as v, type AccountProvider as w, type AccountContribution as x, type ExtensionContext as y, type SettingsAPI as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stina/extension-api",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -111,6 +111,7 @@
111
111
  "chat.history.read",
112
112
  "chat.current.read",
113
113
  "attachments.read",
114
+ "accounts.use",
114
115
  "chat.message.write",
115
116
  "provider.register",
116
117
  "tools.register",
@@ -853,6 +854,44 @@
853
854
  ],
854
855
  "additionalProperties": false,
855
856
  "description": "Storage collection declarations"
857
+ },
858
+ "accounts": {
859
+ "type": "array",
860
+ "items": {
861
+ "type": "object",
862
+ "properties": {
863
+ "provider": {
864
+ "type": "string",
865
+ "enum": [
866
+ "microsoft"
867
+ ],
868
+ "description": "External account provider"
869
+ },
870
+ "scopes": {
871
+ "type": "array",
872
+ "items": {
873
+ "type": "string",
874
+ "pattern": "^[A-Za-z]+(\\.[A-Za-z]+)+$",
875
+ "errorMessage": {
876
+ "pattern": "Must be a Microsoft Graph permission name, such as \"Mail.Read\""
877
+ }
878
+ },
879
+ "minItems": 1,
880
+ "description": "Microsoft Graph delegated permissions"
881
+ },
882
+ "reason": {
883
+ "$ref": "#/definitions/ExtensionManifest/properties/contributes/properties/tools/items/properties/confirmationPrompt",
884
+ "description": "Why the extension needs them"
885
+ }
886
+ },
887
+ "required": [
888
+ "provider",
889
+ "scopes"
890
+ ],
891
+ "additionalProperties": false,
892
+ "description": "An external account the extension works through"
893
+ },
894
+ "description": "External accounts the extension works through, signed in once in Stina's settings"
856
895
  }
857
896
  },
858
897
  "additionalProperties": false,
@@ -318,6 +318,39 @@ describe('WorkerBackgroundTaskManager', () => {
318
318
  expect(receivedContext!.signal).toBeDefined()
319
319
  expect(receivedContext!.reportHealth).toBeDefined()
320
320
  expect(receivedContext!.log).toBeDefined()
321
+ // Not granted in this setup, so a task cannot reach for an account.
322
+ expect(receivedContext!.accounts).toBeUndefined()
323
+ })
324
+
325
+ it('hands a task the accounts of the user it runs for, when the extension may use them', async () => {
326
+ const accountsApi = { list: vi.fn(), getAccessToken: vi.fn() }
327
+ const createUserAccountsAPI = vi.fn(() => accountsApi)
328
+ const withAccounts = new WorkerBackgroundTaskManager({
329
+ extensionId: 'test-extension',
330
+ extensionVersion: '1.0.0',
331
+ storagePath: '/fake/path',
332
+ sendTaskRegistered,
333
+ sendTaskStatus,
334
+ sendHealthReport,
335
+ createLogAPI,
336
+ createStorageAPI,
337
+ createUserStorageAPI,
338
+ createSecretsAPI,
339
+ createUserSecretsAPI,
340
+ createUserAccountsAPI,
341
+ })
342
+
343
+ let receivedContext: BackgroundTaskContext | null = null
344
+ await withAccounts.start(
345
+ { id: 'task-2', name: 'Mail watch', userId: 'user-7', restartPolicy: { type: 'never' } },
346
+ vi.fn(async (context) => {
347
+ receivedContext = context
348
+ })
349
+ )
350
+ await withAccounts.handleStart('task-2')
351
+
352
+ expect(createUserAccountsAPI).toHaveBeenCalledWith('user-7')
353
+ expect(receivedContext!.accounts).toBe(accountsApi)
321
354
  })
322
355
  })
323
356
 
package/src/background.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  LogAPI,
15
15
  StorageAPI,
16
16
  SecretsAPI,
17
+ ConnectedAccountsAPI,
17
18
  } from './types.js'
18
19
 
19
20
  // ============================================================================
@@ -66,6 +67,11 @@ export interface WorkerBackgroundTaskManagerOptions {
66
67
  createSecretsAPI: () => SecretsAPI
67
68
  /** Create user-scoped secrets API */
68
69
  createUserSecretsAPI: (userId: string) => SecretsAPI
70
+ /**
71
+ * Create the connected accounts API for a user. Absent unless the extension
72
+ * holds `accounts.use`, which leaves `accounts` off the task's context.
73
+ */
74
+ createUserAccountsAPI?: (userId: string) => ConnectedAccountsAPI
69
75
  }
70
76
 
71
77
  // ============================================================================
@@ -235,6 +241,9 @@ export class WorkerBackgroundTaskManager {
235
241
  userStorage,
236
242
  secrets,
237
243
  userSecrets,
244
+ ...(config.userId && this.options.createUserAccountsAPI
245
+ ? { accounts: this.options.createUserAccountsAPI(config.userId) }
246
+ : {}),
238
247
  signal,
239
248
  reportHealth: (status: string) => {
240
249
  const timestamp = new Date().toISOString()
package/src/index.ts CHANGED
@@ -36,6 +36,8 @@ export type {
36
36
  ToolDefinition,
37
37
  ToolConfirmationConfig,
38
38
  CommandDefinition,
39
+ AccountProvider,
40
+ AccountContribution,
39
41
 
40
42
  // Permissions
41
43
  Permission,
@@ -65,6 +67,9 @@ export type {
65
67
  ConversationPresentation,
66
68
  AttachmentsAPI,
67
69
  AttachmentContent,
70
+ ConnectedAccountsAPI,
71
+ ConnectedAccount,
72
+ AccountAccessToken,
68
73
  LogAPI,
69
74
 
70
75
  // Background workers
package/src/messages.ts CHANGED
@@ -266,6 +266,9 @@ export type RequestMethod =
266
266
  | 'tools.execute'
267
267
  // Attachments
268
268
  | 'attachments.read'
269
+ // Connected accounts
270
+ | 'accounts.list'
271
+ | 'accounts.getAccessToken'
269
272
 
270
273
  export interface ProviderRegisteredMessage {
271
274
  type: 'provider-registered'
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Worker-side connected accounts API.
3
+ */
4
+
5
+ import type { AccountAccessToken, ConnectedAccount, ConnectedAccountsAPI } from '../types.js'
6
+ import type { RequestMessage } from '../messages.js'
7
+
8
+ type SendRequest = <T>(method: RequestMessage['method'], payload: unknown) => Promise<T>
9
+
10
+ /**
11
+ * Build the accounts API for one user.
12
+ *
13
+ * The user id is bound here, from the request that is being served, and not
14
+ * taken from the extension's own arguments: an extension cannot reach another
15
+ * user's account by passing a different id, because there is no id to pass.
16
+ */
17
+ export function buildUserAccountsAPI(sendRequest: SendRequest, userId: string): ConnectedAccountsAPI {
18
+ return {
19
+ async list(provider: 'microsoft'): Promise<ConnectedAccount[]> {
20
+ return sendRequest<ConnectedAccount[]>('accounts.list', { provider, userId })
21
+ },
22
+ async getAccessToken(accountId: string): Promise<AccountAccessToken> {
23
+ return sendRequest<AccountAccessToken>('accounts.getAccessToken', { accountId, userId })
24
+ },
25
+ }
26
+ }
@@ -15,7 +15,7 @@ const extensionContext = {
15
15
  */
16
16
  describe('createExecutionContext', () => {
17
17
  it('leaves attachments out without the permission', () => {
18
- const context = createExecutionContext(vi.fn(), extensionContext, 'user-1', false)
18
+ const context = createExecutionContext(vi.fn(), extensionContext, 'user-1', [])
19
19
 
20
20
  expect(context.attachments).toBeUndefined()
21
21
  })
@@ -23,7 +23,7 @@ describe('createExecutionContext', () => {
23
23
  it('leaves attachments out without a user, whatever the permission says', () => {
24
24
  // An attachment belongs to somebody. A request that cannot say whose work it is
25
25
  // doing has no business reading one.
26
- const context = createExecutionContext(vi.fn(), extensionContext, undefined, true)
26
+ const context = createExecutionContext(vi.fn(), extensionContext, undefined, ['attachments.read'])
27
27
 
28
28
  expect(context.attachments).toBeUndefined()
29
29
  })
@@ -36,7 +36,7 @@ describe('createExecutionContext', () => {
36
36
  byteSize: 4,
37
37
  })
38
38
 
39
- const context = createExecutionContext(sendRequest, extensionContext, 'user-1', true)
39
+ const context = createExecutionContext(sendRequest, extensionContext, 'user-1', ['attachments.read'])
40
40
  const content = await context.attachments!.read('att-7')
41
41
 
42
42
  // The user id is the runtime's to supply, not the tool's: it comes from the
@@ -53,7 +53,7 @@ describe('createExecutionContext', () => {
53
53
  vi.fn().mockResolvedValue(null),
54
54
  extensionContext,
55
55
  'user-1',
56
- true
56
+ ['attachments.read']
57
57
  )
58
58
 
59
59
  expect(await context.attachments!.read('gone')).toBeNull()
@@ -68,4 +68,46 @@ describe('createExecutionContext', () => {
68
68
  expect(context.secrets).toBeDefined()
69
69
  expect(context.userSecrets).toBeDefined()
70
70
  })
71
+
72
+ describe('connected accounts', () => {
73
+ it('leaves them out without the permission', () => {
74
+ const context = createExecutionContext(vi.fn(), extensionContext, 'user-1', ['attachments.read'])
75
+
76
+ expect(context.accounts).toBeUndefined()
77
+ })
78
+
79
+ it('leaves them out without a user, whatever the permission says', () => {
80
+ const context = createExecutionContext(vi.fn(), extensionContext, undefined, ['accounts.use'])
81
+
82
+ expect(context.accounts).toBeUndefined()
83
+ })
84
+
85
+ it('asks for the user the request belongs to, not one the extension names', async () => {
86
+ const sendRequest = vi
87
+ .fn()
88
+ .mockResolvedValueOnce([{ id: 'ms-1', provider: 'microsoft', email: 'a@b.se' }])
89
+ .mockResolvedValueOnce({ token: 'tok', expiresAt: '2026-09-16T12:00:00.000Z' })
90
+
91
+ const context = createExecutionContext(sendRequest, extensionContext, 'user-1', ['accounts.use'])
92
+ await context.accounts!.list('microsoft')
93
+ const token = await context.accounts!.getAccessToken('ms-1')
94
+
95
+ expect(sendRequest).toHaveBeenNthCalledWith(1, 'accounts.list', {
96
+ provider: 'microsoft',
97
+ userId: 'user-1',
98
+ })
99
+ expect(sendRequest).toHaveBeenNthCalledWith(2, 'accounts.getAccessToken', {
100
+ accountId: 'ms-1',
101
+ userId: 'user-1',
102
+ })
103
+ expect(token.token).toBe('tok')
104
+ })
105
+
106
+ it('leaves attachments alone when only accounts were granted', () => {
107
+ const context = createExecutionContext(vi.fn(), extensionContext, 'user-1', ['accounts.use'])
108
+
109
+ expect(context.accounts).toBeDefined()
110
+ expect(context.attachments).toBeUndefined()
111
+ })
112
+ })
71
113
  })
@@ -6,6 +6,7 @@ import type { AttachmentContent, ExecutionContext, ExtensionContext } from '../t
6
6
  import type { RequestMessage } from '../messages.js'
7
7
  import { buildExtensionStorageAPI, buildUserStorageAPI } from './storageApi.js'
8
8
  import { buildExtensionSecretsAPI, buildUserSecretsAPI } from './secretsApi.js'
9
+ import { buildUserAccountsAPI } from './accountsApi.js'
9
10
 
10
11
  type SendRequest = <T>(method: RequestMessage['method'], payload: unknown) => Promise<T>
11
12
 
@@ -18,12 +19,15 @@ export function createExecutionContext(
18
19
  extensionContext: ExtensionContext,
19
20
  userId?: string,
20
21
  /**
21
- * Whether the extension declared `attachments.read`. Checked here rather than in
22
- * the host alone so a tool can see whether the capability is there at all, the
23
- * way it can for storage — the host refuses it regardless.
22
+ * What the extension was granted. The permission-gated APIs are left out here
23
+ * rather than only refused by the host, so a tool can see whether a capability
24
+ * is there at all, the way it can for storage — the host refuses them regardless.
24
25
  */
25
- canReadAttachments = false
26
+ granted: readonly string[] = []
26
27
  ): ExecutionContext {
28
+ const canReadAttachments = granted.includes('attachments.read')
29
+ const canUseAccounts = granted.includes('accounts.use')
30
+
27
31
  return {
28
32
  userId,
29
33
  extension: {
@@ -53,5 +57,7 @@ export function createExecutionContext(
53
57
  },
54
58
  }
55
59
  : {}),
60
+ // Scoped to a user for the same reason: a connected account is somebody's.
61
+ ...(canUseAccounts && userId ? { accounts: buildUserAccountsAPI(sendRequest, userId) } : {}),
56
62
  }
57
63
  }
@@ -6,3 +6,4 @@
6
6
  export { buildExtensionStorageAPI, buildUserStorageAPI } from './storageApi.js'
7
7
  export { buildExtensionSecretsAPI, buildUserSecretsAPI } from './secretsApi.js'
8
8
  export { createExecutionContext } from './executionContext.js'
9
+ export { buildUserAccountsAPI } from './accountsApi.js'