@stina/extension-api 1.3.1 → 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.
- package/dist/{chunk-ZB7GJUPS.js → chunk-3Q3YXWOH.js} +1 -1
- package/dist/{chunk-ZB7GJUPS.js.map → chunk-3Q3YXWOH.js.map} +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +1 -1
- package/dist/runtime.cjs +34 -5
- 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 +35 -6
- package/dist/runtime.js.map +1 -1
- package/dist/schemas/index.cjs +9 -1
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +4 -4
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +9 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/{types.tools-C0GqXQlu.d.cts → types.tools-R0xGhiBa.d.cts} +119 -17
- package/dist/{types.tools-C0GqXQlu.d.ts → types.tools-R0xGhiBa.d.ts} +119 -17
- package/package.json +1 -1
- package/schema/extension-manifest.schema.json +1 -0
- package/src/index.ts +3 -0
- package/src/messages.ts +2 -0
- package/src/runtime/executionContext.test.ts +71 -0
- package/src/runtime/executionContext.ts +22 -2
- package/src/runtime.ts +26 -3
- package/src/schemas/permissions.schema.ts +9 -1
- package/src/types.context.ts +43 -0
- package/src/types.permissions.ts +7 -0
- package/src/types.provider.ts +51 -1
- package/src/types.tools.ts +29 -15
- package/src/types.ts +3 -0
|
@@ -1178,9 +1178,22 @@ interface ModelCapabilities {
|
|
|
1178
1178
|
* Report this per model *and* per auth mode, like `voiceDuplex`: the same
|
|
1179
1179
|
* provider often serves both a vision model and a text-only one, and a picture
|
|
1180
1180
|
* sent to the latter is at best ignored and at worst an error mid-conversation.
|
|
1181
|
-
* Stina uses it to decide whether the paperclip is offered at all.
|
|
1182
1181
|
*/
|
|
1183
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;
|
|
1184
1197
|
}
|
|
1185
1198
|
/**
|
|
1186
1199
|
* How the client wants to connect to the voice session.
|
|
@@ -1280,6 +1293,18 @@ interface ChatMessage {
|
|
|
1280
1293
|
* Only ever `image/jpeg` or `image/png` — see `ChatAttachmentDTO` for why.
|
|
1281
1294
|
*/
|
|
1282
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[];
|
|
1283
1308
|
/** For assistant messages: tool calls made by the model */
|
|
1284
1309
|
tool_calls?: ToolCall[];
|
|
1285
1310
|
/** For tool messages: the ID of the tool call this is a response to */
|
|
@@ -1299,6 +1324,29 @@ interface ChatImage {
|
|
|
1299
1324
|
/** The image itself, base64 with no data-URI prefix. */
|
|
1300
1325
|
data: string;
|
|
1301
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
|
+
}
|
|
1302
1350
|
/**
|
|
1303
1351
|
* A tool call made by the model
|
|
1304
1352
|
*/
|
|
@@ -1714,6 +1762,46 @@ interface ExecutionContext {
|
|
|
1714
1762
|
readonly secrets: SecretsAPI;
|
|
1715
1763
|
/** User-scoped secrets */
|
|
1716
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;
|
|
1717
1805
|
}
|
|
1718
1806
|
/**
|
|
1719
1807
|
* Context provided to extension's activate function.
|
|
@@ -2353,30 +2441,44 @@ interface ToolResult {
|
|
|
2353
2441
|
*/
|
|
2354
2442
|
cardSuggestion?: string;
|
|
2355
2443
|
/**
|
|
2356
|
-
*
|
|
2444
|
+
* Files to put in front of the user, in the conversation where the tool ran.
|
|
2357
2445
|
*
|
|
2358
|
-
* For what a card cannot hold and the model cannot reproduce: a generated
|
|
2359
|
-
*
|
|
2360
|
-
* {@link ToolResult.display}, this is for the user
|
|
2361
|
-
*
|
|
2362
|
-
*
|
|
2363
|
-
*
|
|
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.
|
|
2364
2452
|
*
|
|
2365
|
-
* The host stores each one as an attachment of the conversation,
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
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.
|
|
2370
2465
|
*/
|
|
2371
2466
|
attachments?: ToolAttachment[];
|
|
2372
2467
|
}
|
|
2373
2468
|
/**
|
|
2374
|
-
* One
|
|
2469
|
+
* One file a tool wants to put in the conversation. See {@link ToolResult.attachments}.
|
|
2375
2470
|
*/
|
|
2376
2471
|
interface ToolAttachment {
|
|
2377
|
-
/** The
|
|
2472
|
+
/** The bytes, base64 encoded. What they are is read from them, not from `name`. */
|
|
2378
2473
|
data: string;
|
|
2379
|
-
/**
|
|
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
|
+
*/
|
|
2380
2482
|
name?: string;
|
|
2381
2483
|
}
|
|
2382
2484
|
/**
|
|
@@ -2405,4 +2507,4 @@ interface ActionResult {
|
|
|
2405
2507
|
error?: string;
|
|
2406
2508
|
}
|
|
2407
2509
|
|
|
2408
|
-
export { type
|
|
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 };
|
|
@@ -1178,9 +1178,22 @@ interface ModelCapabilities {
|
|
|
1178
1178
|
* Report this per model *and* per auth mode, like `voiceDuplex`: the same
|
|
1179
1179
|
* provider often serves both a vision model and a text-only one, and a picture
|
|
1180
1180
|
* sent to the latter is at best ignored and at worst an error mid-conversation.
|
|
1181
|
-
* Stina uses it to decide whether the paperclip is offered at all.
|
|
1182
1181
|
*/
|
|
1183
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;
|
|
1184
1197
|
}
|
|
1185
1198
|
/**
|
|
1186
1199
|
* How the client wants to connect to the voice session.
|
|
@@ -1280,6 +1293,18 @@ interface ChatMessage {
|
|
|
1280
1293
|
* Only ever `image/jpeg` or `image/png` — see `ChatAttachmentDTO` for why.
|
|
1281
1294
|
*/
|
|
1282
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[];
|
|
1283
1308
|
/** For assistant messages: tool calls made by the model */
|
|
1284
1309
|
tool_calls?: ToolCall[];
|
|
1285
1310
|
/** For tool messages: the ID of the tool call this is a response to */
|
|
@@ -1299,6 +1324,29 @@ interface ChatImage {
|
|
|
1299
1324
|
/** The image itself, base64 with no data-URI prefix. */
|
|
1300
1325
|
data: string;
|
|
1301
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
|
+
}
|
|
1302
1350
|
/**
|
|
1303
1351
|
* A tool call made by the model
|
|
1304
1352
|
*/
|
|
@@ -1714,6 +1762,46 @@ interface ExecutionContext {
|
|
|
1714
1762
|
readonly secrets: SecretsAPI;
|
|
1715
1763
|
/** User-scoped secrets */
|
|
1716
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;
|
|
1717
1805
|
}
|
|
1718
1806
|
/**
|
|
1719
1807
|
* Context provided to extension's activate function.
|
|
@@ -2353,30 +2441,44 @@ interface ToolResult {
|
|
|
2353
2441
|
*/
|
|
2354
2442
|
cardSuggestion?: string;
|
|
2355
2443
|
/**
|
|
2356
|
-
*
|
|
2444
|
+
* Files to put in front of the user, in the conversation where the tool ran.
|
|
2357
2445
|
*
|
|
2358
|
-
* For what a card cannot hold and the model cannot reproduce: a generated
|
|
2359
|
-
*
|
|
2360
|
-
* {@link ToolResult.display}, this is for the user
|
|
2361
|
-
*
|
|
2362
|
-
*
|
|
2363
|
-
*
|
|
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.
|
|
2364
2452
|
*
|
|
2365
|
-
* The host stores each one as an attachment of the conversation,
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
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.
|
|
2370
2465
|
*/
|
|
2371
2466
|
attachments?: ToolAttachment[];
|
|
2372
2467
|
}
|
|
2373
2468
|
/**
|
|
2374
|
-
* One
|
|
2469
|
+
* One file a tool wants to put in the conversation. See {@link ToolResult.attachments}.
|
|
2375
2470
|
*/
|
|
2376
2471
|
interface ToolAttachment {
|
|
2377
|
-
/** The
|
|
2472
|
+
/** The bytes, base64 encoded. What they are is read from them, not from `name`. */
|
|
2378
2473
|
data: string;
|
|
2379
|
-
/**
|
|
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
|
+
*/
|
|
2380
2482
|
name?: string;
|
|
2381
2483
|
}
|
|
2382
2484
|
/**
|
|
@@ -2405,4 +2507,4 @@ interface ActionResult {
|
|
|
2405
2507
|
error?: string;
|
|
2406
2508
|
}
|
|
2407
2509
|
|
|
2408
|
-
export { type
|
|
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
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,
|
package/src/messages.ts
CHANGED
|
@@ -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
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -105,6 +105,13 @@ const messagePort = getMessagePort()
|
|
|
105
105
|
let extensionModule: ExtensionModule | null = null
|
|
106
106
|
let extensionDisposable: Disposable | null = null
|
|
107
107
|
let extensionContext: ExtensionContext | null = null
|
|
108
|
+
/**
|
|
109
|
+
* What this extension was granted, kept from activation.
|
|
110
|
+
*
|
|
111
|
+
* A request-scoped context is built long after `handleActivate` has returned, and
|
|
112
|
+
* has to know the same things it did.
|
|
113
|
+
*/
|
|
114
|
+
let grantedPermissions: string[] = []
|
|
108
115
|
let backgroundTaskManager: WorkerBackgroundTaskManager | null = null
|
|
109
116
|
|
|
110
117
|
const pendingRequests = new Map<string, PendingRequest>()
|
|
@@ -284,6 +291,7 @@ async function handleActivate(payload: {
|
|
|
284
291
|
settings: Record<string, unknown>
|
|
285
292
|
}): Promise<void> {
|
|
286
293
|
const { extensionId, extensionVersion, storagePath, permissions } = payload
|
|
294
|
+
grantedPermissions = permissions
|
|
287
295
|
|
|
288
296
|
// Build the context based on permissions
|
|
289
297
|
extensionContext = buildContext(extensionId, extensionVersion, storagePath, permissions)
|
|
@@ -346,7 +354,12 @@ function handleSettingsChanged(key: string, value: unknown): void {
|
|
|
346
354
|
// ExecutionContext builder is in runtime/executionContext.ts
|
|
347
355
|
|
|
348
356
|
async function handleSchedulerFire(payload: SchedulerFirePayload): Promise<void> {
|
|
349
|
-
const execContext = createExecutionContext(
|
|
357
|
+
const execContext = createExecutionContext(
|
|
358
|
+
sendRequest,
|
|
359
|
+
extensionContext!,
|
|
360
|
+
payload.userId,
|
|
361
|
+
grantedPermissions.includes('attachments.read')
|
|
362
|
+
)
|
|
350
363
|
|
|
351
364
|
// Run callbacks concurrently to avoid blocking
|
|
352
365
|
const results = await Promise.allSettled(
|
|
@@ -543,7 +556,12 @@ async function handleToolExecuteRequest(
|
|
|
543
556
|
}
|
|
544
557
|
|
|
545
558
|
try {
|
|
546
|
-
const execContext = createExecutionContext(
|
|
559
|
+
const execContext = createExecutionContext(
|
|
560
|
+
sendRequest,
|
|
561
|
+
extensionContext!,
|
|
562
|
+
payload.userId,
|
|
563
|
+
grantedPermissions.includes('attachments.read')
|
|
564
|
+
)
|
|
547
565
|
|
|
548
566
|
const result = await tool.execute(payload.params, execContext)
|
|
549
567
|
|
|
@@ -586,7 +604,12 @@ async function handleActionExecuteRequest(
|
|
|
586
604
|
}
|
|
587
605
|
|
|
588
606
|
try {
|
|
589
|
-
const execContext = createExecutionContext(
|
|
607
|
+
const execContext = createExecutionContext(
|
|
608
|
+
sendRequest,
|
|
609
|
+
extensionContext!,
|
|
610
|
+
payload.userId,
|
|
611
|
+
grantedPermissions.includes('attachments.read')
|
|
612
|
+
)
|
|
590
613
|
|
|
591
614
|
const result = await action.execute(payload.params, execContext)
|
|
592
615
|
|
|
@@ -20,6 +20,7 @@ export const VALID_PERMISSIONS = [
|
|
|
20
20
|
'user.location.read',
|
|
21
21
|
'chat.history.read',
|
|
22
22
|
'chat.current.read',
|
|
23
|
+
'attachments.read',
|
|
23
24
|
'chat.message.write',
|
|
24
25
|
'provider.register',
|
|
25
26
|
'tools.register',
|
|
@@ -75,7 +76,14 @@ const StoragePermissionSchema = z.enum(['storage.collections', 'secrets.manage']
|
|
|
75
76
|
* User data permission schema
|
|
76
77
|
*/
|
|
77
78
|
const UserDataPermissionSchema = z
|
|
78
|
-
.enum([
|
|
79
|
+
.enum([
|
|
80
|
+
'user.profile.read',
|
|
81
|
+
'user.list',
|
|
82
|
+
'user.location.read',
|
|
83
|
+
'chat.history.read',
|
|
84
|
+
'chat.current.read',
|
|
85
|
+
'attachments.read',
|
|
86
|
+
])
|
|
79
87
|
.describe('User data access permission')
|
|
80
88
|
|
|
81
89
|
/**
|