@stina/extension-api 1.0.0 → 1.6.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.
@@ -171,6 +171,18 @@ interface LabelProps extends ExtensionComponentData {
171
171
  component: 'Label';
172
172
  text: string;
173
173
  }
174
+ /**
175
+ * The extension API properties for the Clock component.
176
+ *
177
+ * The odd one out among the display components: it takes no facts, because the
178
+ * fact it shows is what time it is, and that is not something an action can
179
+ * hand over once. It reads the clock and the timezone from the host and keeps
180
+ * itself current, so a card carrying one stays right while the window is left
181
+ * open.
182
+ */
183
+ interface ClockProps extends ExtensionComponentData {
184
+ component: 'Clock';
185
+ }
174
186
  /** The extension API properties for the paragraph component. */
175
187
  interface ParagraphProps extends ExtensionComponentData {
176
188
  component: 'Paragraph';
@@ -1166,9 +1178,22 @@ interface ModelCapabilities {
1166
1178
  * Report this per model *and* per auth mode, like `voiceDuplex`: the same
1167
1179
  * provider often serves both a vision model and a text-only one, and a picture
1168
1180
  * sent to the latter is at best ignored and at worst an error mid-conversation.
1169
- * Stina uses it to decide whether the paperclip is offered at all.
1170
1181
  */
1171
1182
  vision?: boolean;
1183
+ /**
1184
+ * The model can be given files to read alongside the text of a message, and
1185
+ * the provider folds {@link ChatMessage.files} into whatever its API calls them.
1186
+ *
1187
+ * Separate from `vision` because the two come apart in both directions: a model
1188
+ * that reads a PDF natively is not necessarily one that looks at a photograph,
1189
+ * and an OpenAI-compatible server with a vision model behind it may take images
1190
+ * and nothing else. Report it per model and per auth mode for the same reason
1191
+ * `vision` is reported that way.
1192
+ *
1193
+ * A provider that says nothing here keeps behaving exactly as before: it is
1194
+ * handed the text, and `files` is simply a field it does not read.
1195
+ */
1196
+ documents?: boolean;
1172
1197
  }
1173
1198
  /**
1174
1199
  * How the client wants to connect to the voice session.
@@ -1268,6 +1293,18 @@ interface ChatMessage {
1268
1293
  * Only ever `image/jpeg` or `image/png` — see `ChatAttachmentDTO` for why.
1269
1294
  */
1270
1295
  images?: ChatImage[];
1296
+ /**
1297
+ * Files the user attached for the model to read: PDFs and plain text.
1298
+ *
1299
+ * Additive in the same way `images` is, and split from it for the same reason
1300
+ * the two capabilities are separate — a provider folds a document into a
1301
+ * different content block than a picture, and many can do one and not the other.
1302
+ * A provider that ignores the field behaves exactly as it did before.
1303
+ *
1304
+ * Only present on user messages, because that is where both hosted providers
1305
+ * require a document to sit.
1306
+ */
1307
+ files?: ChatFile[];
1271
1308
  /** For assistant messages: tool calls made by the model */
1272
1309
  tool_calls?: ToolCall[];
1273
1310
  /** For tool messages: the ID of the tool call this is a response to */
@@ -1287,6 +1324,29 @@ interface ChatImage {
1287
1324
  /** The image itself, base64 with no data-URI prefix. */
1288
1325
  data: string;
1289
1326
  }
1327
+ /**
1328
+ * One file the user attached for the model to read, rather than to look at.
1329
+ *
1330
+ * `application/pdf` and `text/plain` are what the host stores, so those are what
1331
+ * arrive. Both hosted providers read a PDF natively and want it as its own content
1332
+ * block; plain text needs no such thing and can simply be put in the prompt, which
1333
+ * is why a provider with no document support at all can still do something useful
1334
+ * with a `text/plain` file if it chooses to.
1335
+ */
1336
+ interface ChatFile {
1337
+ /** `application/pdf` or `text/plain`. */
1338
+ mime: string;
1339
+ /** The file itself, base64 with no data-URI prefix. */
1340
+ data: string;
1341
+ /**
1342
+ * The name the file arrived under, when it had one.
1343
+ *
1344
+ * Worth passing on rather than dropping: `faktura-1042.pdf` is most of what is
1345
+ * known about a file before it is opened, and both providers have somewhere to
1346
+ * put it — a file name on the one, a document title on the other.
1347
+ */
1348
+ name?: string;
1349
+ }
1290
1350
  /**
1291
1351
  * A tool call made by the model
1292
1352
  */
@@ -1702,6 +1762,46 @@ interface ExecutionContext {
1702
1762
  readonly secrets: SecretsAPI;
1703
1763
  /** User-scoped secrets */
1704
1764
  readonly userSecrets: SecretsAPI;
1765
+ /**
1766
+ * The files attached to this user's conversations.
1767
+ *
1768
+ * Present only for an extension holding `attachments.read`, and only on a request
1769
+ * that knows whose it is. Absent otherwise, so a tool that wants files has to say
1770
+ * so in its manifest and check before reaching for them.
1771
+ */
1772
+ readonly attachments?: AttachmentsAPI;
1773
+ }
1774
+ /**
1775
+ * Reading a file that is already in a conversation.
1776
+ *
1777
+ * For handing one on: mailing back the PDF she was just shown, printing it, putting
1778
+ * it somewhere. Not for finding out what it says — `core_read_attachment` does that
1779
+ * without an extension, and getting the text is nearly always what she actually
1780
+ * wants.
1781
+ */
1782
+ interface AttachmentsAPI {
1783
+ /**
1784
+ * Read one attachment by id.
1785
+ *
1786
+ * The id comes from Stina, which is the whole point: she gets it from the message
1787
+ * a file arrived on, or from the result of the tool that handed it over, and
1788
+ * passes it to a tool as a parameter.
1789
+ *
1790
+ * Resolves `null` when no such attachment belongs to this user — deleted, or
1791
+ * never theirs. The two are the same answer on purpose.
1792
+ */
1793
+ read(attachmentId: string): Promise<AttachmentContent | null>;
1794
+ }
1795
+ /** One attachment's bytes, with what is known about them. */
1796
+ interface AttachmentContent {
1797
+ id: string;
1798
+ /** `image/jpeg`, `image/png`, `application/pdf` or `text/plain`, read from the bytes. */
1799
+ mime: string;
1800
+ /** base64, no data-URI prefix. */
1801
+ data: string;
1802
+ byteSize: number;
1803
+ /** The name it was stored under, when it had one. */
1804
+ name?: string;
1705
1805
  }
1706
1806
  /**
1707
1807
  * Context provided to extension's activate function.
@@ -2340,6 +2440,46 @@ interface ToolResult {
2340
2440
  * would send her after a component that does not exist.
2341
2441
  */
2342
2442
  cardSuggestion?: string;
2443
+ /**
2444
+ * Files to put in front of the user, in the conversation where the tool ran.
2445
+ *
2446
+ * For what a card cannot hold and the model cannot reproduce: a generated image,
2447
+ * a photo fetched on the user's behalf, the PDF that came attached to a mail.
2448
+ * Like {@link ToolResult.display}, this is for the user — it is lifted out before
2449
+ * the result reaches the model, which would have nothing to do with the bytes but
2450
+ * spend tokens on them. Say in `data` that a file was attached, so she can talk
2451
+ * about it without reciting it.
2452
+ *
2453
+ * The host stores each one as an attachment of the conversation, exactly as a
2454
+ * file the user sends is stored, so it is served to every client and can be saved
2455
+ * or shared from there. The store's own rules apply: JPEG and PNG, PDF, and plain
2456
+ * text, up to 20 MB (1 MB for text). What the file *is* is read from the bytes,
2457
+ * not from `name`, so a PDF mislabelled `.png` still lands as a PDF. One that
2458
+ * fails the rules is dropped with a warning rather than half-shown.
2459
+ *
2460
+ * Attaching a file does not read it to the model. What comes back in the result
2461
+ * the model sees is a reference apiece — `{ id, mime, name? }` under this same
2462
+ * key, in place of the bytes — and she reads one with `core_read_attachment` if
2463
+ * she decides to. That is the intended flow for a mail's attachment: hand it over
2464
+ * here, and let her choose whether to open it.
2465
+ */
2466
+ attachments?: ToolAttachment[];
2467
+ }
2468
+ /**
2469
+ * One file a tool wants to put in the conversation. See {@link ToolResult.attachments}.
2470
+ */
2471
+ interface ToolAttachment {
2472
+ /** The bytes, base64 encoded. What they are is read from them, not from `name`. */
2473
+ data: string;
2474
+ /**
2475
+ * A file name to offer when the user saves it, e.g. `friday.png` or
2476
+ * `faktura-1042.pdf`.
2477
+ *
2478
+ * Worth sending for a picture and close to required for a document: it is the
2479
+ * whole label the user sees in the conversation, and it is what Stina has to go
2480
+ * on when she decides whether to read it.
2481
+ */
2482
+ name?: string;
2343
2483
  }
2344
2484
  /**
2345
2485
  * Action implementation for UI interactions.
@@ -2367,4 +2507,4 @@ interface ActionResult {
2367
2507
  error?: string;
2368
2508
  }
2369
2509
 
2370
- export { type BackgroundTaskContext 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 LogAPI as X, type BackgroundWorkersAPI as Y, type BackgroundTaskConfig as Z, type BackgroundTaskCallback as _, type ChatOptions as a, type ChartSeries as a$, type BackgroundTaskHealth as a0, type BackgroundRestartPolicy as a1, type Query as a2, type QueryOptions as a3, type StorageAPI as a4, type SecretsAPI as a5, type StorageCollectionConfig as a6, type StorageContributions as a7, type AIProvider as a8, type ModelCapabilities as a9, type VerticalStackProps as aA, type HorizontalStackProps as aB, type GridProps as aC, type DividerProps as aD, type IconProps as aE, type IconButtonType as aF, type IconButtonProps as aG, type PanelAction as aH, type PanelProps as aI, type ToggleProps as aJ, type CollapsibleProps as aK, type FrameVariant as aL, type FrameProps as aM, type ListProps as aN, type PillVariant as aO, type PillProps as aP, type CheckboxProps as aQ, type MarkdownProps as aR, type TextPreviewProps as aS, type ModalProps as aT, type ConditionalGroupProps as aU, type WeatherCondition as aV, type WeatherWind as aW, type WeatherNowProps as aX, type WeatherForecastStep as aY, type WeatherForecastProps as aZ, type ChartKind as a_, type ChatImage as aa, type ToolCall as ab, type VoiceTransportRequest as ac, type Tool as ad, type Action as ae, type ExtensionModule as af, type AllowedCSSProperty as ag, type ExtensionComponentStyle as ah, type ExtensionComponentData as ai, type ExtensionComponentIterator as aj, type ExtensionComponentChildren as ak, type ExtensionActionCall as al, type ExtensionActionRef as am, type ExtensionDataSource as an, type ExtensionPanelDefinition as ao, type HeaderProps as ap, type LabelProps as aq, type ParagraphProps as ar, type ButtonProps as as, type TextInputProps as at, type PasswordInputProps as au, type NumberInputProps as av, type TextAreaProps as aw, type DateTimeInputProps as ax, type SelectProps as ay, type IconPickerProps as az, type StreamEvent as b, type ChartProps as b0, type StatTrend as b1, type StatTileProps as b2, type ProgressShape as b3, type ProgressColor as b4, type ProgressBarProps as b5, type KeyValueRow as b6, type KeyValueListProps as b7, type TimelineVariant as b8, type TimelineEntry as b9, type TimelineProps as ba, type NoteVariant as bb, type NoteProps as bc, type CalendarEventStatus as bd, type CalendarEventProps as be, type ExecutionContext as bf, 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 };
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 };
@@ -171,6 +171,18 @@ interface LabelProps extends ExtensionComponentData {
171
171
  component: 'Label';
172
172
  text: string;
173
173
  }
174
+ /**
175
+ * The extension API properties for the Clock component.
176
+ *
177
+ * The odd one out among the display components: it takes no facts, because the
178
+ * fact it shows is what time it is, and that is not something an action can
179
+ * hand over once. It reads the clock and the timezone from the host and keeps
180
+ * itself current, so a card carrying one stays right while the window is left
181
+ * open.
182
+ */
183
+ interface ClockProps extends ExtensionComponentData {
184
+ component: 'Clock';
185
+ }
174
186
  /** The extension API properties for the paragraph component. */
175
187
  interface ParagraphProps extends ExtensionComponentData {
176
188
  component: 'Paragraph';
@@ -1166,9 +1178,22 @@ interface ModelCapabilities {
1166
1178
  * Report this per model *and* per auth mode, like `voiceDuplex`: the same
1167
1179
  * provider often serves both a vision model and a text-only one, and a picture
1168
1180
  * sent to the latter is at best ignored and at worst an error mid-conversation.
1169
- * Stina uses it to decide whether the paperclip is offered at all.
1170
1181
  */
1171
1182
  vision?: boolean;
1183
+ /**
1184
+ * The model can be given files to read alongside the text of a message, and
1185
+ * the provider folds {@link ChatMessage.files} into whatever its API calls them.
1186
+ *
1187
+ * Separate from `vision` because the two come apart in both directions: a model
1188
+ * that reads a PDF natively is not necessarily one that looks at a photograph,
1189
+ * and an OpenAI-compatible server with a vision model behind it may take images
1190
+ * and nothing else. Report it per model and per auth mode for the same reason
1191
+ * `vision` is reported that way.
1192
+ *
1193
+ * A provider that says nothing here keeps behaving exactly as before: it is
1194
+ * handed the text, and `files` is simply a field it does not read.
1195
+ */
1196
+ documents?: boolean;
1172
1197
  }
1173
1198
  /**
1174
1199
  * How the client wants to connect to the voice session.
@@ -1268,6 +1293,18 @@ interface ChatMessage {
1268
1293
  * Only ever `image/jpeg` or `image/png` — see `ChatAttachmentDTO` for why.
1269
1294
  */
1270
1295
  images?: ChatImage[];
1296
+ /**
1297
+ * Files the user attached for the model to read: PDFs and plain text.
1298
+ *
1299
+ * Additive in the same way `images` is, and split from it for the same reason
1300
+ * the two capabilities are separate — a provider folds a document into a
1301
+ * different content block than a picture, and many can do one and not the other.
1302
+ * A provider that ignores the field behaves exactly as it did before.
1303
+ *
1304
+ * Only present on user messages, because that is where both hosted providers
1305
+ * require a document to sit.
1306
+ */
1307
+ files?: ChatFile[];
1271
1308
  /** For assistant messages: tool calls made by the model */
1272
1309
  tool_calls?: ToolCall[];
1273
1310
  /** For tool messages: the ID of the tool call this is a response to */
@@ -1287,6 +1324,29 @@ interface ChatImage {
1287
1324
  /** The image itself, base64 with no data-URI prefix. */
1288
1325
  data: string;
1289
1326
  }
1327
+ /**
1328
+ * One file the user attached for the model to read, rather than to look at.
1329
+ *
1330
+ * `application/pdf` and `text/plain` are what the host stores, so those are what
1331
+ * arrive. Both hosted providers read a PDF natively and want it as its own content
1332
+ * block; plain text needs no such thing and can simply be put in the prompt, which
1333
+ * is why a provider with no document support at all can still do something useful
1334
+ * with a `text/plain` file if it chooses to.
1335
+ */
1336
+ interface ChatFile {
1337
+ /** `application/pdf` or `text/plain`. */
1338
+ mime: string;
1339
+ /** The file itself, base64 with no data-URI prefix. */
1340
+ data: string;
1341
+ /**
1342
+ * The name the file arrived under, when it had one.
1343
+ *
1344
+ * Worth passing on rather than dropping: `faktura-1042.pdf` is most of what is
1345
+ * known about a file before it is opened, and both providers have somewhere to
1346
+ * put it — a file name on the one, a document title on the other.
1347
+ */
1348
+ name?: string;
1349
+ }
1290
1350
  /**
1291
1351
  * A tool call made by the model
1292
1352
  */
@@ -1702,6 +1762,46 @@ interface ExecutionContext {
1702
1762
  readonly secrets: SecretsAPI;
1703
1763
  /** User-scoped secrets */
1704
1764
  readonly userSecrets: SecretsAPI;
1765
+ /**
1766
+ * The files attached to this user's conversations.
1767
+ *
1768
+ * Present only for an extension holding `attachments.read`, and only on a request
1769
+ * that knows whose it is. Absent otherwise, so a tool that wants files has to say
1770
+ * so in its manifest and check before reaching for them.
1771
+ */
1772
+ readonly attachments?: AttachmentsAPI;
1773
+ }
1774
+ /**
1775
+ * Reading a file that is already in a conversation.
1776
+ *
1777
+ * For handing one on: mailing back the PDF she was just shown, printing it, putting
1778
+ * it somewhere. Not for finding out what it says — `core_read_attachment` does that
1779
+ * without an extension, and getting the text is nearly always what she actually
1780
+ * wants.
1781
+ */
1782
+ interface AttachmentsAPI {
1783
+ /**
1784
+ * Read one attachment by id.
1785
+ *
1786
+ * The id comes from Stina, which is the whole point: she gets it from the message
1787
+ * a file arrived on, or from the result of the tool that handed it over, and
1788
+ * passes it to a tool as a parameter.
1789
+ *
1790
+ * Resolves `null` when no such attachment belongs to this user — deleted, or
1791
+ * never theirs. The two are the same answer on purpose.
1792
+ */
1793
+ read(attachmentId: string): Promise<AttachmentContent | null>;
1794
+ }
1795
+ /** One attachment's bytes, with what is known about them. */
1796
+ interface AttachmentContent {
1797
+ id: string;
1798
+ /** `image/jpeg`, `image/png`, `application/pdf` or `text/plain`, read from the bytes. */
1799
+ mime: string;
1800
+ /** base64, no data-URI prefix. */
1801
+ data: string;
1802
+ byteSize: number;
1803
+ /** The name it was stored under, when it had one. */
1804
+ name?: string;
1705
1805
  }
1706
1806
  /**
1707
1807
  * Context provided to extension's activate function.
@@ -2340,6 +2440,46 @@ interface ToolResult {
2340
2440
  * would send her after a component that does not exist.
2341
2441
  */
2342
2442
  cardSuggestion?: string;
2443
+ /**
2444
+ * Files to put in front of the user, in the conversation where the tool ran.
2445
+ *
2446
+ * For what a card cannot hold and the model cannot reproduce: a generated image,
2447
+ * a photo fetched on the user's behalf, the PDF that came attached to a mail.
2448
+ * Like {@link ToolResult.display}, this is for the user — it is lifted out before
2449
+ * the result reaches the model, which would have nothing to do with the bytes but
2450
+ * spend tokens on them. Say in `data` that a file was attached, so she can talk
2451
+ * about it without reciting it.
2452
+ *
2453
+ * The host stores each one as an attachment of the conversation, exactly as a
2454
+ * file the user sends is stored, so it is served to every client and can be saved
2455
+ * or shared from there. The store's own rules apply: JPEG and PNG, PDF, and plain
2456
+ * text, up to 20 MB (1 MB for text). What the file *is* is read from the bytes,
2457
+ * not from `name`, so a PDF mislabelled `.png` still lands as a PDF. One that
2458
+ * fails the rules is dropped with a warning rather than half-shown.
2459
+ *
2460
+ * Attaching a file does not read it to the model. What comes back in the result
2461
+ * the model sees is a reference apiece — `{ id, mime, name? }` under this same
2462
+ * key, in place of the bytes — and she reads one with `core_read_attachment` if
2463
+ * she decides to. That is the intended flow for a mail's attachment: hand it over
2464
+ * here, and let her choose whether to open it.
2465
+ */
2466
+ attachments?: ToolAttachment[];
2467
+ }
2468
+ /**
2469
+ * One file a tool wants to put in the conversation. See {@link ToolResult.attachments}.
2470
+ */
2471
+ interface ToolAttachment {
2472
+ /** The bytes, base64 encoded. What they are is read from them, not from `name`. */
2473
+ data: string;
2474
+ /**
2475
+ * A file name to offer when the user saves it, e.g. `friday.png` or
2476
+ * `faktura-1042.pdf`.
2477
+ *
2478
+ * Worth sending for a picture and close to required for a document: it is the
2479
+ * whole label the user sees in the conversation, and it is what Stina has to go
2480
+ * on when she decides whether to read it.
2481
+ */
2482
+ name?: string;
2343
2483
  }
2344
2484
  /**
2345
2485
  * Action implementation for UI interactions.
@@ -2367,4 +2507,4 @@ interface ActionResult {
2367
2507
  error?: string;
2368
2508
  }
2369
2509
 
2370
- export { type BackgroundTaskContext 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 LogAPI as X, type BackgroundWorkersAPI as Y, type BackgroundTaskConfig as Z, type BackgroundTaskCallback as _, type ChatOptions as a, type ChartSeries as a$, type BackgroundTaskHealth as a0, type BackgroundRestartPolicy as a1, type Query as a2, type QueryOptions as a3, type StorageAPI as a4, type SecretsAPI as a5, type StorageCollectionConfig as a6, type StorageContributions as a7, type AIProvider as a8, type ModelCapabilities as a9, type VerticalStackProps as aA, type HorizontalStackProps as aB, type GridProps as aC, type DividerProps as aD, type IconProps as aE, type IconButtonType as aF, type IconButtonProps as aG, type PanelAction as aH, type PanelProps as aI, type ToggleProps as aJ, type CollapsibleProps as aK, type FrameVariant as aL, type FrameProps as aM, type ListProps as aN, type PillVariant as aO, type PillProps as aP, type CheckboxProps as aQ, type MarkdownProps as aR, type TextPreviewProps as aS, type ModalProps as aT, type ConditionalGroupProps as aU, type WeatherCondition as aV, type WeatherWind as aW, type WeatherNowProps as aX, type WeatherForecastStep as aY, type WeatherForecastProps as aZ, type ChartKind as a_, type ChatImage as aa, type ToolCall as ab, type VoiceTransportRequest as ac, type Tool as ad, type Action as ae, type ExtensionModule as af, type AllowedCSSProperty as ag, type ExtensionComponentStyle as ah, type ExtensionComponentData as ai, type ExtensionComponentIterator as aj, type ExtensionComponentChildren as ak, type ExtensionActionCall as al, type ExtensionActionRef as am, type ExtensionDataSource as an, type ExtensionPanelDefinition as ao, type HeaderProps as ap, type LabelProps as aq, type ParagraphProps as ar, type ButtonProps as as, type TextInputProps as at, type PasswordInputProps as au, type NumberInputProps as av, type TextAreaProps as aw, type DateTimeInputProps as ax, type SelectProps as ay, type IconPickerProps as az, type StreamEvent as b, type ChartProps as b0, type StatTrend as b1, type StatTileProps as b2, type ProgressShape as b3, type ProgressColor as b4, type ProgressBarProps as b5, type KeyValueRow as b6, type KeyValueListProps as b7, type TimelineVariant as b8, type TimelineEntry as b9, type TimelineProps as ba, type NoteVariant as bb, type NoteProps as bc, type CalendarEventStatus as bd, type CalendarEventProps as be, type ExecutionContext as bf, 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 };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stina/extension-api",
3
- "version": "1.0.0",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -110,6 +110,7 @@
110
110
  "user.location.read",
111
111
  "chat.history.read",
112
112
  "chat.current.read",
113
+ "attachments.read",
113
114
  "chat.message.write",
114
115
  "provider.register",
115
116
  "tools.register",
package/src/index.ts CHANGED
@@ -63,6 +63,8 @@ export type {
63
63
  ChatAPI,
64
64
  ChatInstructionMessage,
65
65
  ConversationPresentation,
66
+ AttachmentsAPI,
67
+ AttachmentContent,
66
68
  LogAPI,
67
69
 
68
70
  // Background workers
@@ -87,6 +89,7 @@ export type {
87
89
  ModelCapabilities,
88
90
  ChatMessage,
89
91
  ChatImage,
92
+ ChatFile,
90
93
  ChatOptions,
91
94
  GetModelsOptions,
92
95
  StreamEvent,
@@ -100,6 +103,7 @@ export type {
100
103
  // Tools
101
104
  Tool,
102
105
  ToolResult,
106
+ ToolAttachment,
103
107
 
104
108
  // Actions
105
109
  Action,
@@ -163,6 +167,7 @@ export type {
163
167
  // Component Props
164
168
  HeaderProps,
165
169
  LabelProps,
170
+ ClockProps,
166
171
  ParagraphProps,
167
172
  ButtonProps,
168
173
  TextInputProps,
package/src/messages.ts CHANGED
@@ -264,6 +264,8 @@ export type RequestMethod =
264
264
  // Tools cross-extension methods
265
265
  | 'tools.list'
266
266
  | 'tools.execute'
267
+ // Attachments
268
+ | 'attachments.read'
267
269
 
268
270
  export interface ProviderRegisteredMessage {
269
271
  type: 'provider-registered'
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { createExecutionContext } from './executionContext.js'
3
+ import type { ExtensionContext } from '../types.js'
4
+
5
+ const extensionContext = {
6
+ extension: { id: 'mail-reader', version: '1.0.0', storagePath: '/tmp/x' },
7
+ } as ExtensionContext
8
+
9
+ /**
10
+ * What a tool is handed when it runs.
11
+ *
12
+ * The attachments API is the part worth pinning: it is absent unless the extension
13
+ * asked for the permission *and* the request knows whose work it is, and an absent
14
+ * API is what makes a tool check before reaching for files it may not have.
15
+ */
16
+ describe('createExecutionContext', () => {
17
+ it('leaves attachments out without the permission', () => {
18
+ const context = createExecutionContext(vi.fn(), extensionContext, 'user-1', false)
19
+
20
+ expect(context.attachments).toBeUndefined()
21
+ })
22
+
23
+ it('leaves attachments out without a user, whatever the permission says', () => {
24
+ // An attachment belongs to somebody. A request that cannot say whose work it is
25
+ // doing has no business reading one.
26
+ const context = createExecutionContext(vi.fn(), extensionContext, undefined, true)
27
+
28
+ expect(context.attachments).toBeUndefined()
29
+ })
30
+
31
+ it('reads an attachment for the user the request belongs to', async () => {
32
+ const sendRequest = vi.fn().mockResolvedValue({
33
+ id: 'att-7',
34
+ mime: 'application/pdf',
35
+ data: 'JVBE',
36
+ byteSize: 4,
37
+ })
38
+
39
+ const context = createExecutionContext(sendRequest, extensionContext, 'user-1', true)
40
+ const content = await context.attachments!.read('att-7')
41
+
42
+ // The user id is the runtime's to supply, not the tool's: it comes from the
43
+ // request, so a tool cannot reach another user's file by passing a different one.
44
+ expect(sendRequest).toHaveBeenCalledWith('attachments.read', {
45
+ attachmentId: 'att-7',
46
+ userId: 'user-1',
47
+ })
48
+ expect(content?.mime).toBe('application/pdf')
49
+ })
50
+
51
+ it('passes a missing attachment through as nothing', async () => {
52
+ const context = createExecutionContext(
53
+ vi.fn().mockResolvedValue(null),
54
+ extensionContext,
55
+ 'user-1',
56
+ true
57
+ )
58
+
59
+ expect(await context.attachments!.read('gone')).toBeNull()
60
+ })
61
+
62
+ it('still builds the rest of the context without the permission', () => {
63
+ const context = createExecutionContext(vi.fn(), extensionContext, 'user-1')
64
+
65
+ expect(context.userId).toBe('user-1')
66
+ expect(context.storage).toBeDefined()
67
+ expect(context.userStorage).toBeDefined()
68
+ expect(context.secrets).toBeDefined()
69
+ expect(context.userSecrets).toBeDefined()
70
+ })
71
+ })
@@ -2,7 +2,7 @@
2
2
  * Shared execution context builder for tool, action, and scheduler operations.
3
3
  */
4
4
 
5
- import type { ExecutionContext, ExtensionContext } from '../types.js'
5
+ import type { AttachmentContent, ExecutionContext, ExtensionContext } from '../types.js'
6
6
  import type { RequestMessage } from '../messages.js'
7
7
  import { buildExtensionStorageAPI, buildUserStorageAPI } from './storageApi.js'
8
8
  import { buildExtensionSecretsAPI, buildUserSecretsAPI } from './secretsApi.js'
@@ -16,7 +16,13 @@ type SendRequest = <T>(method: RequestMessage['method'], payload: unknown) => Pr
16
16
  export function createExecutionContext(
17
17
  sendRequest: SendRequest,
18
18
  extensionContext: ExtensionContext,
19
- userId?: string
19
+ userId?: string,
20
+ /**
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.
24
+ */
25
+ canReadAttachments = false
20
26
  ): ExecutionContext {
21
27
  return {
22
28
  userId,
@@ -33,5 +39,19 @@ export function createExecutionContext(
33
39
  userSecrets: userId
34
40
  ? buildUserSecretsAPI(sendRequest, userId)
35
41
  : buildExtensionSecretsAPI(sendRequest),
42
+ // Only with a user to scope it to. An attachment belongs to somebody, and a
43
+ // request that cannot say whose work it is doing has no business reading one.
44
+ ...(canReadAttachments && userId
45
+ ? {
46
+ attachments: {
47
+ async read(attachmentId: string): Promise<AttachmentContent | null> {
48
+ return sendRequest<AttachmentContent | null>('attachments.read', {
49
+ attachmentId,
50
+ userId,
51
+ })
52
+ },
53
+ },
54
+ }
55
+ : {}),
36
56
  }
37
57
  }