@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.
- package/dist/{chunk-3Q3YXWOH.js → chunk-S3YP4QPF.js} +1 -1
- package/dist/{chunk-3Q3YXWOH.js.map → chunk-S3YP4QPF.js.map} +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +1 -1
- package/dist/runtime.cjs +24 -6
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +25 -7
- package/dist/runtime.js.map +1 -1
- package/dist/schemas/index.cjs +26 -2
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +91 -5
- package/dist/schemas/index.d.ts +91 -5
- package/dist/schemas/index.js +22 -2
- package/dist/schemas/index.js.map +1 -1
- package/dist/{types.tools-R0xGhiBa.d.cts → types.tools-DcFBsfRV.d.cts} +105 -1
- package/dist/{types.tools-R0xGhiBa.d.ts → types.tools-DcFBsfRV.d.ts} +105 -1
- package/package.json +1 -1
- package/schema/extension-manifest.schema.json +39 -0
- package/src/background.test.ts +33 -0
- package/src/background.ts +9 -0
- package/src/index.ts +5 -0
- package/src/messages.ts +3 -0
- package/src/runtime/accountsApi.ts +26 -0
- package/src/runtime/executionContext.test.ts +46 -4
- package/src/runtime/executionContext.ts +10 -4
- package/src/runtime/index.ts +1 -0
- package/src/runtime.ts +15 -3
- package/src/schemas/accounts.schema.test.ts +58 -0
- package/src/schemas/contributions.schema.ts +48 -0
- package/src/schemas/index.ts +6 -0
- package/src/schemas/permissions.schema.ts +2 -0
- package/src/types.context.ts +68 -0
- package/src/types.contributions.ts +47 -0
- package/src/types.permissions.ts +8 -0
- 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
|
|
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
|
|
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
|
@@ -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,
|
package/src/background.test.ts
CHANGED
|
@@ -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',
|
|
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,
|
|
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',
|
|
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
|
-
|
|
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
|
-
*
|
|
22
|
-
* the host
|
|
23
|
-
* way it can for storage — the host refuses
|
|
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
|
-
|
|
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
|
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -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'
|