@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.
@@ -85,6 +85,49 @@ export interface ExecutionContext {
85
85
 
86
86
  /** User-scoped secrets */
87
87
  readonly userSecrets: SecretsAPI
88
+
89
+ /**
90
+ * The files attached to this user's conversations.
91
+ *
92
+ * Present only for an extension holding `attachments.read`, and only on a request
93
+ * that knows whose it is. Absent otherwise, so a tool that wants files has to say
94
+ * so in its manifest and check before reaching for them.
95
+ */
96
+ readonly attachments?: AttachmentsAPI
97
+ }
98
+
99
+ /**
100
+ * Reading a file that is already in a conversation.
101
+ *
102
+ * For handing one on: mailing back the PDF she was just shown, printing it, putting
103
+ * it somewhere. Not for finding out what it says — `core_read_attachment` does that
104
+ * without an extension, and getting the text is nearly always what she actually
105
+ * wants.
106
+ */
107
+ export interface AttachmentsAPI {
108
+ /**
109
+ * Read one attachment by id.
110
+ *
111
+ * The id comes from Stina, which is the whole point: she gets it from the message
112
+ * a file arrived on, or from the result of the tool that handed it over, and
113
+ * passes it to a tool as a parameter.
114
+ *
115
+ * Resolves `null` when no such attachment belongs to this user — deleted, or
116
+ * never theirs. The two are the same answer on purpose.
117
+ */
118
+ read(attachmentId: string): Promise<AttachmentContent | null>
119
+ }
120
+
121
+ /** One attachment's bytes, with what is known about them. */
122
+ export interface AttachmentContent {
123
+ id: string
124
+ /** `image/jpeg`, `image/png`, `application/pdf` or `text/plain`, read from the bytes. */
125
+ mime: string
126
+ /** base64, no data-URI prefix. */
127
+ data: string
128
+ byteSize: number
129
+ /** The name it was stored under, when it had one. */
130
+ name?: string
88
131
  }
89
132
 
90
133
  /**
@@ -28,6 +28,13 @@ export type UserDataPermission =
28
28
  | 'user.location.read'
29
29
  | 'chat.history.read'
30
30
  | 'chat.current.read'
31
+ /**
32
+ * Read the bytes of a file attached to one of this user's conversations, given
33
+ * its id. For handing a file on — mailing the PDF she was shown, printing it —
34
+ * not for reading what it says, which `core_read_attachment` does without an
35
+ * extension.
36
+ */
37
+ | 'attachments.read'
31
38
 
32
39
  /** Capability permissions */
33
40
  export type CapabilityPermission =
@@ -89,9 +89,23 @@ export interface ModelCapabilities {
89
89
  * Report this per model *and* per auth mode, like `voiceDuplex`: the same
90
90
  * provider often serves both a vision model and a text-only one, and a picture
91
91
  * sent to the latter is at best ignored and at worst an error mid-conversation.
92
- * Stina uses it to decide whether the paperclip is offered at all.
93
92
  */
94
93
  vision?: boolean
94
+
95
+ /**
96
+ * The model can be given files to read alongside the text of a message, and
97
+ * the provider folds {@link ChatMessage.files} into whatever its API calls them.
98
+ *
99
+ * Separate from `vision` because the two come apart in both directions: a model
100
+ * that reads a PDF natively is not necessarily one that looks at a photograph,
101
+ * and an OpenAI-compatible server with a vision model behind it may take images
102
+ * and nothing else. Report it per model and per auth mode for the same reason
103
+ * `vision` is reported that way.
104
+ *
105
+ * A provider that says nothing here keeps behaving exactly as before: it is
106
+ * handed the text, and `files` is simply a field it does not read.
107
+ */
108
+ documents?: boolean
95
109
  }
96
110
 
97
111
  /**
@@ -189,6 +203,18 @@ export interface ChatMessage {
189
203
  * Only ever `image/jpeg` or `image/png` — see `ChatAttachmentDTO` for why.
190
204
  */
191
205
  images?: ChatImage[]
206
+ /**
207
+ * Files the user attached for the model to read: PDFs and plain text.
208
+ *
209
+ * Additive in the same way `images` is, and split from it for the same reason
210
+ * the two capabilities are separate — a provider folds a document into a
211
+ * different content block than a picture, and many can do one and not the other.
212
+ * A provider that ignores the field behaves exactly as it did before.
213
+ *
214
+ * Only present on user messages, because that is where both hosted providers
215
+ * require a document to sit.
216
+ */
217
+ files?: ChatFile[]
192
218
  /** For assistant messages: tool calls made by the model */
193
219
  tool_calls?: ToolCall[]
194
220
  /** For tool messages: the ID of the tool call this is a response to */
@@ -210,6 +236,30 @@ export interface ChatImage {
210
236
  data: string
211
237
  }
212
238
 
239
+ /**
240
+ * One file the user attached for the model to read, rather than to look at.
241
+ *
242
+ * `application/pdf` and `text/plain` are what the host stores, so those are what
243
+ * arrive. Both hosted providers read a PDF natively and want it as its own content
244
+ * block; plain text needs no such thing and can simply be put in the prompt, which
245
+ * is why a provider with no document support at all can still do something useful
246
+ * with a `text/plain` file if it chooses to.
247
+ */
248
+ export interface ChatFile {
249
+ /** `application/pdf` or `text/plain`. */
250
+ mime: string
251
+ /** The file itself, base64 with no data-URI prefix. */
252
+ data: string
253
+ /**
254
+ * The name the file arrived under, when it had one.
255
+ *
256
+ * Worth passing on rather than dropping: `faktura-1042.pdf` is most of what is
257
+ * known about a file before it is opened, and both providers have somewhere to
258
+ * put it — a file name on the one, a document title on the other.
259
+ */
260
+ name?: string
261
+ }
262
+
213
263
  /**
214
264
  * A tool call made by the model
215
265
  */
@@ -75,31 +75,45 @@ export interface ToolResult {
75
75
  cardSuggestion?: string
76
76
 
77
77
  /**
78
- * Pictures to show the user, rendered in the conversation where the tool ran.
78
+ * Files to put in front of the user, in the conversation where the tool ran.
79
79
  *
80
- * For what a card cannot hold and the model cannot reproduce: a generated
81
- * image, a rendered document, a photo fetched on the user's behalf. Like
82
- * {@link ToolResult.display}, this is for the user only — it is lifted out
83
- * before the result reaches the model, which would have nothing to do with
84
- * the bytes but spend tokens on them. Say in `data` that a picture was shown,
85
- * so she can talk about it without describing it back.
80
+ * For what a card cannot hold and the model cannot reproduce: a generated image,
81
+ * a photo fetched on the user's behalf, the PDF that came attached to a mail.
82
+ * Like {@link ToolResult.display}, this is for the user — it is lifted out before
83
+ * the result reaches the model, which would have nothing to do with the bytes but
84
+ * spend tokens on them. Say in `data` that a file was attached, so she can talk
85
+ * about it without reciting it.
86
86
  *
87
- * The host stores each one as an attachment of the conversation, the same way
88
- * a picture the user sends is stored, so it is served to every client and can
89
- * be saved or shared from there. JPEG and PNG only, and 20 MB at most, since
90
- * those are the limits the attachment store already holds users to. One that
91
- * fails them is dropped with a warning rather than half-shown.
87
+ * The host stores each one as an attachment of the conversation, exactly as a
88
+ * file the user sends is stored, so it is served to every client and can be saved
89
+ * or shared from there. The store's own rules apply: JPEG and PNG, PDF, and plain
90
+ * text, up to 20 MB (1 MB for text). What the file *is* is read from the bytes,
91
+ * not from `name`, so a PDF mislabelled `.png` still lands as a PDF. One that
92
+ * fails the rules is dropped with a warning rather than half-shown.
93
+ *
94
+ * Attaching a file does not read it to the model. What comes back in the result
95
+ * the model sees is a reference apiece — `{ id, mime, name? }` under this same
96
+ * key, in place of the bytes — and she reads one with `core_read_attachment` if
97
+ * she decides to. That is the intended flow for a mail's attachment: hand it over
98
+ * here, and let her choose whether to open it.
92
99
  */
93
100
  attachments?: ToolAttachment[]
94
101
  }
95
102
 
96
103
  /**
97
- * One picture a tool wants shown. See {@link ToolResult.attachments}.
104
+ * One file a tool wants to put in the conversation. See {@link ToolResult.attachments}.
98
105
  */
99
106
  export interface ToolAttachment {
100
- /** The image bytes, base64 encoded. The format is read from the bytes. */
107
+ /** The bytes, base64 encoded. What they are is read from them, not from `name`. */
101
108
  data: string
102
- /** A file name to offer when the user saves it, e.g. `friday.png`. */
109
+ /**
110
+ * A file name to offer when the user saves it, e.g. `friday.png` or
111
+ * `faktura-1042.pdf`.
112
+ *
113
+ * Worth sending for a picture and close to required for a document: it is the
114
+ * whole label the user sees in the conversation, and it is what Stina has to go
115
+ * on when she decides whether to read it.
116
+ */
103
117
  name?: string
104
118
  }
105
119
 
package/src/types.ts CHANGED
@@ -59,6 +59,7 @@ export type {
59
59
  ModelCapabilities,
60
60
  ChatMessage,
61
61
  ChatImage,
62
+ ChatFile,
62
63
  ToolCall,
63
64
  ChatOptions,
64
65
  GetModelsOptions,
@@ -100,6 +101,8 @@ export type {
100
101
  BackgroundTaskCallback,
101
102
  BackgroundTaskHealth,
102
103
  BackgroundWorkersAPI,
104
+ AttachmentsAPI,
105
+ AttachmentContent,
103
106
  } from './types.context.js'
104
107
 
105
108
  // Storage and Secrets