@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.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/LICENSE +1 -2
- package/NOTICE +19 -0
- package/README.md +589 -2
- package/cordis.patch.yml +14 -0
- package/lib/access.d.ts +76 -0
- package/lib/access.d.ts.map +1 -0
- package/lib/access.js +137 -0
- package/lib/access.js.map +1 -0
- package/lib/client.js +835 -0
- package/lib/commands.d.ts +51 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +83 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +125 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +97 -0
- package/lib/config.js.map +1 -0
- package/lib/diagnostics.d.ts +58 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +59 -0
- package/lib/diagnostics.js.map +1 -0
- package/lib/failures.d.ts +71 -0
- package/lib/failures.d.ts.map +1 -0
- package/lib/failures.js +139 -0
- package/lib/failures.js.map +1 -0
- package/lib/harness/host.d.ts +106 -0
- package/lib/harness/host.d.ts.map +1 -0
- package/lib/harness/host.js +142 -0
- package/lib/harness/host.js.map +1 -0
- package/lib/harness/message.d.ts +45 -0
- package/lib/harness/message.d.ts.map +1 -0
- package/lib/harness/message.js +28 -0
- package/lib/harness/message.js.map +1 -0
- package/lib/harness/model-selection.d.ts +82 -0
- package/lib/harness/model-selection.d.ts.map +1 -0
- package/lib/harness/model-selection.js +103 -0
- package/lib/harness/model-selection.js.map +1 -0
- package/lib/harness/questions-seam.d.ts +34 -0
- package/lib/harness/questions-seam.d.ts.map +1 -0
- package/lib/harness/questions-seam.js +45 -0
- package/lib/harness/questions-seam.js.map +1 -0
- package/lib/harness/types.d.ts +108 -0
- package/lib/harness/types.d.ts.map +1 -0
- package/lib/harness/types.js +23 -0
- package/lib/harness/types.js.map +1 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +977 -0
- package/lib/index.js.map +1 -0
- package/lib/interact/approvals.d.ts +59 -0
- package/lib/interact/approvals.d.ts.map +1 -0
- package/lib/interact/approvals.js +132 -0
- package/lib/interact/approvals.js.map +1 -0
- package/lib/interact/pending.d.ts +56 -0
- package/lib/interact/pending.d.ts.map +1 -0
- package/lib/interact/pending.js +89 -0
- package/lib/interact/pending.js.map +1 -0
- package/lib/interact/questions.d.ts +100 -0
- package/lib/interact/questions.d.ts.map +1 -0
- package/lib/interact/questions.js +255 -0
- package/lib/interact/questions.js.map +1 -0
- package/lib/interact/surface.d.ts +33 -0
- package/lib/interact/surface.d.ts.map +1 -0
- package/lib/interact/surface.js +39 -0
- package/lib/interact/surface.js.map +1 -0
- package/lib/interact/text-capture.d.ts +38 -0
- package/lib/interact/text-capture.d.ts.map +1 -0
- package/lib/interact/text-capture.js +75 -0
- package/lib/interact/text-capture.js.map +1 -0
- package/lib/media/collect.d.ts +157 -0
- package/lib/media/collect.d.ts.map +1 -0
- package/lib/media/collect.js +235 -0
- package/lib/media/collect.js.map +1 -0
- package/lib/media/extractor.d.ts +117 -0
- package/lib/media/extractor.d.ts.map +1 -0
- package/lib/media/extractor.js +207 -0
- package/lib/media/extractor.js.map +1 -0
- package/lib/media/intake.d.ts +57 -0
- package/lib/media/intake.d.ts.map +1 -0
- package/lib/media/intake.js +115 -0
- package/lib/media/intake.js.map +1 -0
- package/lib/media/limits.d.ts +69 -0
- package/lib/media/limits.d.ts.map +1 -0
- package/lib/media/limits.js +97 -0
- package/lib/media/limits.js.map +1 -0
- package/lib/media/ocr.d.ts +66 -0
- package/lib/media/ocr.d.ts.map +1 -0
- package/lib/media/ocr.js +131 -0
- package/lib/media/ocr.js.map +1 -0
- package/lib/media/screenshot.d.ts +57 -0
- package/lib/media/screenshot.d.ts.map +1 -0
- package/lib/media/screenshot.js +101 -0
- package/lib/media/screenshot.js.map +1 -0
- package/lib/media/vision.d.ts +60 -0
- package/lib/media/vision.d.ts.map +1 -0
- package/lib/media/vision.js +74 -0
- package/lib/media/vision.js.map +1 -0
- package/lib/render/clamp.d.ts +34 -0
- package/lib/render/clamp.d.ts.map +1 -0
- package/lib/render/clamp.js +65 -0
- package/lib/render/clamp.js.map +1 -0
- package/lib/render/escape.d.ts +15 -0
- package/lib/render/escape.d.ts.map +1 -0
- package/lib/render/escape.js +17 -0
- package/lib/render/escape.js.map +1 -0
- package/lib/reply/activity.d.ts +31 -0
- package/lib/reply/activity.d.ts.map +1 -0
- package/lib/reply/activity.js +88 -0
- package/lib/reply/activity.js.map +1 -0
- package/lib/reply/rich-stream.d.ts +173 -0
- package/lib/reply/rich-stream.d.ts.map +1 -0
- package/lib/reply/rich-stream.js +286 -0
- package/lib/reply/rich-stream.js.map +1 -0
- package/lib/reply/split-markdown.d.ts +21 -0
- package/lib/reply/split-markdown.d.ts.map +1 -0
- package/lib/reply/split-markdown.js +87 -0
- package/lib/reply/split-markdown.js.map +1 -0
- package/lib/reply/turn-bridge.d.ts +143 -0
- package/lib/reply/turn-bridge.d.ts.map +1 -0
- package/lib/reply/turn-bridge.js +182 -0
- package/lib/reply/turn-bridge.js.map +1 -0
- package/lib/router.d.ts +415 -0
- package/lib/router.d.ts.map +1 -0
- package/lib/router.js +663 -0
- package/lib/router.js.map +1 -0
- package/lib/secrets.d.ts +30 -0
- package/lib/secrets.d.ts.map +1 -0
- package/lib/secrets.js +51 -0
- package/lib/secrets.js.map +1 -0
- package/lib/session/bindings.d.ts +81 -0
- package/lib/session/bindings.d.ts.map +1 -0
- package/lib/session/bindings.js +170 -0
- package/lib/session/bindings.js.map +1 -0
- package/lib/session/history.d.ts +50 -0
- package/lib/session/history.d.ts.map +1 -0
- package/lib/session/history.js +127 -0
- package/lib/session/history.js.map +1 -0
- package/lib/session/models.d.ts +99 -0
- package/lib/session/models.d.ts.map +1 -0
- package/lib/session/models.js +123 -0
- package/lib/session/models.js.map +1 -0
- package/lib/session/permission.d.ts +83 -0
- package/lib/session/permission.d.ts.map +1 -0
- package/lib/session/permission.js +107 -0
- package/lib/session/permission.js.map +1 -0
- package/lib/session/picker.d.ts +49 -0
- package/lib/session/picker.d.ts.map +1 -0
- package/lib/session/picker.js +107 -0
- package/lib/session/picker.js.map +1 -0
- package/lib/session/preferences.d.ts +55 -0
- package/lib/session/preferences.d.ts.map +1 -0
- package/lib/session/preferences.js +102 -0
- package/lib/session/preferences.js.map +1 -0
- package/lib/session/recovery.d.ts +66 -0
- package/lib/session/recovery.d.ts.map +1 -0
- package/lib/session/recovery.js +112 -0
- package/lib/session/recovery.js.map +1 -0
- package/lib/session/runner.d.ts +223 -0
- package/lib/session/runner.d.ts.map +1 -0
- package/lib/session/runner.js +260 -0
- package/lib/session/runner.js.map +1 -0
- package/lib/session/workspaces.d.ts +28 -0
- package/lib/session/workspaces.d.ts.map +1 -0
- package/lib/session/workspaces.js +53 -0
- package/lib/session/workspaces.js.map +1 -0
- package/lib/telegram/addressing.d.ts +39 -0
- package/lib/telegram/addressing.d.ts.map +1 -0
- package/lib/telegram/addressing.js +81 -0
- package/lib/telegram/addressing.js.map +1 -0
- package/lib/telegram/albums.d.ts +46 -0
- package/lib/telegram/albums.d.ts.map +1 -0
- package/lib/telegram/albums.js +104 -0
- package/lib/telegram/albums.js.map +1 -0
- package/lib/telegram/api.d.ts +238 -0
- package/lib/telegram/api.d.ts.map +1 -0
- package/lib/telegram/api.js +518 -0
- package/lib/telegram/api.js.map +1 -0
- package/lib/telegram/poller.d.ts +59 -0
- package/lib/telegram/poller.d.ts.map +1 -0
- package/lib/telegram/poller.js +94 -0
- package/lib/telegram/poller.js.map +1 -0
- package/lib/telegram/types.d.ts +130 -0
- package/lib/telegram/types.d.ts.map +1 -0
- package/lib/telegram/types.js +9 -0
- package/lib/telegram/types.js.map +1 -0
- package/lib/telegram/typing.d.ts +47 -0
- package/lib/telegram/typing.d.ts.map +1 -0
- package/lib/telegram/typing.js +91 -0
- package/lib/telegram/typing.js.map +1 -0
- package/lib/versions.d.ts +63 -0
- package/lib/versions.d.ts.map +1 -0
- package/lib/versions.js +135 -0
- package/lib/versions.js.map +1 -0
- package/package.json +94 -10
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetching a message's media and turning it into prompt content.
|
|
3
|
+
*
|
|
4
|
+
* Downloading is the part that can go wrong in ordinary ways — a file above
|
|
5
|
+
* the bot download limit, a network drop, an image the harness refuses — and
|
|
6
|
+
* none of them should cost the user their message. So every failure becomes a
|
|
7
|
+
* note in the prompt saying what could not be read, and the text the user
|
|
8
|
+
* typed alongside it still reaches the agent.
|
|
9
|
+
*/
|
|
10
|
+
import { SILENT_LOGGER } from '../harness/types.js';
|
|
11
|
+
import { describeMedia, isStorableImage } from './intake.js';
|
|
12
|
+
import { describeSize, isTooLarge, orderBySuitability } from './limits.js';
|
|
13
|
+
export class MediaCollector {
|
|
14
|
+
options;
|
|
15
|
+
logger;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.logger = options.logger ?? SILENT_LOGGER;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Turn one message into the parts a prompt is built from.
|
|
22
|
+
*
|
|
23
|
+
* @param message - the incoming message.
|
|
24
|
+
* @param caption - the text the user typed, if any.
|
|
25
|
+
* @returns text and image parts, in the order the model should read them.
|
|
26
|
+
*/
|
|
27
|
+
async collect(message, caption, context = {}) {
|
|
28
|
+
const item = describeMedia(message);
|
|
29
|
+
const said = caption?.trim();
|
|
30
|
+
if (!item) {
|
|
31
|
+
return { parts: said ? [{ type: 'text', text: said }] : [] };
|
|
32
|
+
}
|
|
33
|
+
if (item.kind === 'unsupported') {
|
|
34
|
+
const what = item.describedAs ?? 'a file';
|
|
35
|
+
return this.declined(said, `${what}, which cannot be read`, `I can't read ${what}.`);
|
|
36
|
+
}
|
|
37
|
+
if (item.size !== undefined && item.size > this.options.maxBytes) {
|
|
38
|
+
const limit = Math.floor(this.options.maxBytes / 1024 / 1024);
|
|
39
|
+
return this.declined(said, `a file of ${item.size} bytes, above the size limit`, `That file is too large — the limit is ${limit} MB.`);
|
|
40
|
+
}
|
|
41
|
+
if (item.kind === 'image') {
|
|
42
|
+
// Skipped when anything can read the picture: the conversation's own
|
|
43
|
+
// model, a vision model behind it, or OCR. The refusal exists to replace
|
|
44
|
+
// a failed turn with a useful sentence, and with any of those in place it
|
|
45
|
+
// would instead throw away an image that was going to be read.
|
|
46
|
+
const readable = context.modelSees === true || (await this.options.canReadWithoutModel?.()) === true;
|
|
47
|
+
const refusal = readable ? undefined : await this.imageRefusal();
|
|
48
|
+
if (refusal)
|
|
49
|
+
return this.declined(said, refusal.forAgent, refusal.forUser);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
if (item.kind === 'text') {
|
|
53
|
+
const bytes = await this.download(item.fileId);
|
|
54
|
+
const text = decodeText(bytes, this.options.maxTextChars);
|
|
55
|
+
const name = item.name ?? 'attachment';
|
|
56
|
+
return {
|
|
57
|
+
parts: [
|
|
58
|
+
{ type: 'text', text: `${said ? `${said}\n\n` : ''}File \`${name}\`:\n\n${text}` },
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (!this.options.attachments) {
|
|
63
|
+
return this.declined(said, 'an image, but this deployment stores none', 'This deployment cannot store images.');
|
|
64
|
+
}
|
|
65
|
+
if (!isStorableImage(item.mediaType)) {
|
|
66
|
+
const type = item.mediaType ?? 'unknown';
|
|
67
|
+
return this.declined(said, `an image of type ${type}, which cannot be stored`, `Images of type ${type} cannot be stored. PNG, JPEG, WebP and GIF work.`);
|
|
68
|
+
}
|
|
69
|
+
const stored = await this.storeImage(item);
|
|
70
|
+
if (!stored) {
|
|
71
|
+
// Reached only when every size Telegram offered was still refused,
|
|
72
|
+
// which in practice means a file sent uncompressed: there is one size
|
|
73
|
+
// and it is the full-resolution original.
|
|
74
|
+
const limits = this.options.attachments.imageLimits;
|
|
75
|
+
const explanation = describeSize(largest(item.candidates), limits) ??
|
|
76
|
+
'That image is larger than this harness stores.';
|
|
77
|
+
return this.declined(said, 'an image too large for this harness to store, so it was left out', `${explanation} Sending it as a photo rather than as a file lets Telegram offer a smaller copy.`);
|
|
78
|
+
}
|
|
79
|
+
// Text first: it is what the user asked, and the image is its subject.
|
|
80
|
+
const parts = [];
|
|
81
|
+
if (said)
|
|
82
|
+
parts.push({ type: 'text', text: said });
|
|
83
|
+
parts.push({ type: 'image', attachment: stored });
|
|
84
|
+
return { parts };
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
this.logger.warn('[dsh-telegram] could not read an attachment', error);
|
|
88
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
89
|
+
return this.declined(said, `a file that could not be read: ${reason}`, `That file could not be read: ${reason}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Turn a whole album into one prompt.
|
|
94
|
+
*
|
|
95
|
+
* Each part is collected on its own — the same path a single photo takes, so
|
|
96
|
+
* refusals and size limits behave identically — and the results are then
|
|
97
|
+
* laid out as one message would have been: what the user said, then every
|
|
98
|
+
* image, then anything that could not be read.
|
|
99
|
+
*
|
|
100
|
+
* @param messages - the album's parts, in the order they were sent.
|
|
101
|
+
* @param caption - the one caption the album carries.
|
|
102
|
+
*/
|
|
103
|
+
async collectAll(messages, caption, context = {}) {
|
|
104
|
+
if (messages.length === 0)
|
|
105
|
+
return { parts: [] };
|
|
106
|
+
if (messages.length === 1) {
|
|
107
|
+
return await this.collect(messages[0], caption, context);
|
|
108
|
+
}
|
|
109
|
+
// Sequential rather than parallel: each part downloads bytes and commits
|
|
110
|
+
// them, and a dozen at once is a burst of traffic and disk for no gain
|
|
111
|
+
// when the user is waiting on the whole set anyway.
|
|
112
|
+
const collected = [];
|
|
113
|
+
for (const message of messages) {
|
|
114
|
+
collected.push(await this.collect(message, undefined, context));
|
|
115
|
+
}
|
|
116
|
+
const said = caption?.trim();
|
|
117
|
+
const images = collected.flatMap((item) => item.parts.filter((part) => part.type === 'image'));
|
|
118
|
+
const notes = collected.flatMap((item) => item.parts.filter((part) => part.type === 'text'));
|
|
119
|
+
const notices = collected.map((item) => item.notice).filter((notice) => notice !== undefined);
|
|
120
|
+
const parts = [];
|
|
121
|
+
if (said)
|
|
122
|
+
parts.push({ type: 'text', text: said });
|
|
123
|
+
parts.push(...images);
|
|
124
|
+
// After the images, because a note explains what is missing from them.
|
|
125
|
+
parts.push(...notes);
|
|
126
|
+
return {
|
|
127
|
+
parts,
|
|
128
|
+
// One line however many parts failed the same way: three identical
|
|
129
|
+
// refusals in a row say nothing the first did not.
|
|
130
|
+
...(notices.length > 0 ? { notice: [...new Set(notices)].join('\n') } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Store an image, stepping down through Telegram's smaller renderings.
|
|
135
|
+
*
|
|
136
|
+
* The seam refuses anything over its per-side limit, and the largest size of
|
|
137
|
+
* a phone screenshot is always over it — 1179×2556 against a limit of 2000.
|
|
138
|
+
* The largest size that fits is tried first, and a rejection falls through
|
|
139
|
+
* to the next, because the limit belongs to the harness and may not be the
|
|
140
|
+
* number read here.
|
|
141
|
+
*
|
|
142
|
+
* @returns the stored reference, or undefined when no size was accepted.
|
|
143
|
+
*/
|
|
144
|
+
async storeImage(item) {
|
|
145
|
+
const store = this.options.attachments;
|
|
146
|
+
if (!store)
|
|
147
|
+
return undefined;
|
|
148
|
+
const candidates = item.candidates ?? [{ fileId: item.fileId }];
|
|
149
|
+
const ordered = orderBySuitability(candidates, store.imageLimits);
|
|
150
|
+
for (const candidate of ordered) {
|
|
151
|
+
try {
|
|
152
|
+
const bytes = await this.download(candidate.fileId);
|
|
153
|
+
return await store.saveImage({
|
|
154
|
+
data: bytes,
|
|
155
|
+
mediaType: item.mediaType,
|
|
156
|
+
...(item.name !== undefined ? { name: item.name } : {}),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
// Only a size refusal is worth another download; a malformed file or a
|
|
161
|
+
// failed write would fail identically however small the image was.
|
|
162
|
+
if (!isTooLarge(error))
|
|
163
|
+
throw error;
|
|
164
|
+
this.logger.debug('[dsh-telegram] image refused as too large; trying a smaller size', error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Exhausted rather than thrown: the seam's own wording — "Image exceeds
|
|
168
|
+
// the configured per-side pixel limit" — tells the user nothing they can
|
|
169
|
+
// act on, and the caller has something better to say.
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Why an image must not be sent on the current route, if it must not.
|
|
174
|
+
*
|
|
175
|
+
* A model with no image input rejects the entire request, so this refusal
|
|
176
|
+
* replaces a failed turn with a sentence naming what would have worked.
|
|
177
|
+
*/
|
|
178
|
+
async imageRefusal() {
|
|
179
|
+
const vision = this.options.vision;
|
|
180
|
+
if (!vision)
|
|
181
|
+
return undefined;
|
|
182
|
+
if ((await vision.verdict()) !== 'no')
|
|
183
|
+
return undefined;
|
|
184
|
+
const model = vision.currentModel() ?? 'the current model';
|
|
185
|
+
const alternatives = await vision.alternatives();
|
|
186
|
+
const suggestion = alternatives.length > 0
|
|
187
|
+
? ` These accept images: ${alternatives.join(', ')}.`
|
|
188
|
+
: ' No configured model accepts images.';
|
|
189
|
+
return {
|
|
190
|
+
forAgent: `an image, which the model ${model} cannot read, so it was left out`,
|
|
191
|
+
forUser: `${model} can't read images.${suggestion} Change it in Settings → Models.`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Keep the caption, tell the agent what was left out, and tell the user why. */
|
|
195
|
+
declined(said, forAgent, forUser) {
|
|
196
|
+
// Applied here rather than at each call site: every refusal in this class
|
|
197
|
+
// funnels through this method, so one guard covers all of them and a
|
|
198
|
+
// refusal added later cannot forget it.
|
|
199
|
+
const clean = this.options.redact ?? ((text) => text);
|
|
200
|
+
return {
|
|
201
|
+
parts: [{ type: 'text', text: clean(note(said, `The user sent ${forAgent}.`)) }],
|
|
202
|
+
notice: clean(forUser),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Resolve a file id and pull its bytes, refusing anything oversized. */
|
|
206
|
+
async download(fileId) {
|
|
207
|
+
const file = await this.options.source.getFile(fileId);
|
|
208
|
+
if (!file.file_path)
|
|
209
|
+
throw new Error('telegram returned no download path');
|
|
210
|
+
if (file.file_size !== undefined && file.file_size > this.options.maxBytes) {
|
|
211
|
+
throw new Error(`file is ${file.file_size} bytes, above the size limit`);
|
|
212
|
+
}
|
|
213
|
+
return await this.options.source.downloadFile(file.file_path);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/** The biggest size offered, for a refusal that can name what was sent. */
|
|
217
|
+
function largest(candidates) {
|
|
218
|
+
if (!candidates || candidates.length === 0)
|
|
219
|
+
return undefined;
|
|
220
|
+
return candidates.reduce((biggest, candidate) => (candidate.width ?? 0) * (candidate.height ?? 0) > (biggest.width ?? 0) * (biggest.height ?? 0)
|
|
221
|
+
? candidate
|
|
222
|
+
: biggest);
|
|
223
|
+
}
|
|
224
|
+
/** Keep what the user said, and add why their file did not come through. */
|
|
225
|
+
function note(said, explanation) {
|
|
226
|
+
return said ? `${said}\n\n(${explanation})` : `(${explanation})`;
|
|
227
|
+
}
|
|
228
|
+
/** Decode file bytes as text, replacing what is not valid UTF-8. */
|
|
229
|
+
function decodeText(bytes, maxChars) {
|
|
230
|
+
const text = new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
|
231
|
+
if (text.length <= maxChars)
|
|
232
|
+
return text;
|
|
233
|
+
return `${text.slice(0, maxChars)}\n\n… (truncated at ${maxChars} characters)`;
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/media/collect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading an image with one model so another can use what it says.
|
|
3
|
+
*
|
|
4
|
+
* A provider inspects the whole request history for images, so an image left
|
|
5
|
+
* in a conversation binds that conversation to a model that can see — for
|
|
6
|
+
* good. That costs more than the picture is usually worth: the conversation
|
|
7
|
+
* loses the model it was chosen for, and the tools configured around it.
|
|
8
|
+
*
|
|
9
|
+
* So the image never enters the conversation. It goes to a throwaway session
|
|
10
|
+
* on the vision model, whose reply — the transcription, the description — is
|
|
11
|
+
* what the conversation receives, as ordinary text. The conversation's history
|
|
12
|
+
* stays free of images, so it keeps its own model and never becomes stuck.
|
|
13
|
+
*
|
|
14
|
+
* The throwaway session is disposed either way. It exists for one turn.
|
|
15
|
+
*/
|
|
16
|
+
import type { PromptPart } from './collect.js';
|
|
17
|
+
import type { ModelRoute } from '../harness/model-selection.js';
|
|
18
|
+
import type { Logger } from '../harness/types.js';
|
|
19
|
+
/** A live agent driven for exactly one turn. */
|
|
20
|
+
export interface ExtractionAgent {
|
|
21
|
+
readonly sessionId: string;
|
|
22
|
+
followup(content: readonly PromptPart[]): void;
|
|
23
|
+
dispose(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/** Creating the throwaway agent the extraction runs on. */
|
|
26
|
+
export interface ExtractionHost {
|
|
27
|
+
create(sessionId: string, cwd: string, route: ModelRoute): Promise<ExtractionAgent>;
|
|
28
|
+
}
|
|
29
|
+
/** The session-event shapes an extraction watches for. */
|
|
30
|
+
export interface ExtractionEvent {
|
|
31
|
+
readonly type: string;
|
|
32
|
+
readonly data?: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** Reading an image's text without a model, when there is no model. */
|
|
35
|
+
export interface FallbackReader {
|
|
36
|
+
available(): Promise<boolean>;
|
|
37
|
+
read(data: Uint8Array): Promise<string | undefined>;
|
|
38
|
+
}
|
|
39
|
+
/** Fetching the bytes back for a stored attachment. */
|
|
40
|
+
export interface AttachmentReader {
|
|
41
|
+
readImage(ref: unknown): Promise<{
|
|
42
|
+
data: Uint8Array;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/** Construction options. */
|
|
46
|
+
export interface VisionExtractorOptions {
|
|
47
|
+
readonly host: ExtractionHost;
|
|
48
|
+
readonly cwd: string;
|
|
49
|
+
/**
|
|
50
|
+
* Whether any vision model is configured anywhere.
|
|
51
|
+
*
|
|
52
|
+
* Only decides {@link VisionExtractor.available}; which model a particular
|
|
53
|
+
* conversation reads with is passed to `resolve`, because `/vision` makes
|
|
54
|
+
* that a per-conversation answer.
|
|
55
|
+
*/
|
|
56
|
+
readonly visionModel: () => ModelRoute | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* OCR, for when no vision model is configured or the one configured could
|
|
59
|
+
* not be reached. Strictly a fallback: it reads text and does not see, so a
|
|
60
|
+
* model that can look is always preferred.
|
|
61
|
+
*/
|
|
62
|
+
readonly fallback?: FallbackReader;
|
|
63
|
+
/** Reads a stored image back, which the fallback needs and the model does not. */
|
|
64
|
+
readonly attachments?: AttachmentReader;
|
|
65
|
+
readonly newSessionId?: () => string;
|
|
66
|
+
readonly timeoutMs?: number;
|
|
67
|
+
readonly logger?: Logger;
|
|
68
|
+
}
|
|
69
|
+
export declare class VisionExtractor {
|
|
70
|
+
private readonly options;
|
|
71
|
+
private readonly pending;
|
|
72
|
+
private readonly logger;
|
|
73
|
+
private counter;
|
|
74
|
+
constructor(options: VisionExtractorOptions);
|
|
75
|
+
/**
|
|
76
|
+
* Whether an image can be read at all right now.
|
|
77
|
+
*
|
|
78
|
+
* Optimistic about the fallback: probing Tesseract spawns a process, and
|
|
79
|
+
* this is consulted on the path of every message. A fallback that turns out
|
|
80
|
+
* to be absent simply reads nothing, which is the same outcome as saying so
|
|
81
|
+
* here would have been.
|
|
82
|
+
*/
|
|
83
|
+
get available(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Replace image parts with what a vision model reads in them.
|
|
86
|
+
*
|
|
87
|
+
* @param content - the prompt as the user sent it.
|
|
88
|
+
* @returns the same prompt with each image replaced by its reading, or the
|
|
89
|
+
* original content when there is nothing to read or no model to read it.
|
|
90
|
+
*/
|
|
91
|
+
resolve(content: readonly PromptPart[], route?: ModelRoute | undefined): Promise<PromptPart[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Read every image's text with the fallback.
|
|
94
|
+
*
|
|
95
|
+
* The bytes come back from the attachment store rather than being kept
|
|
96
|
+
* around: the model path never needs them, and holding every image in memory
|
|
97
|
+
* against the chance that a model call fails would be a strange thing to pay
|
|
98
|
+
* for on every message.
|
|
99
|
+
*
|
|
100
|
+
* @returns the readings joined, or undefined when there were none.
|
|
101
|
+
*/
|
|
102
|
+
private scan;
|
|
103
|
+
/**
|
|
104
|
+
* Consume one session event.
|
|
105
|
+
*
|
|
106
|
+
* @returns whether it belonged to an extraction, so the caller knows not to
|
|
107
|
+
* treat it as a conversation of its own.
|
|
108
|
+
*/
|
|
109
|
+
handle(sessionId: string, event: ExtractionEvent): boolean;
|
|
110
|
+
/** Abandon every extraction in flight — the plugin is unloading. */
|
|
111
|
+
dispose(): void;
|
|
112
|
+
/** Run one throwaway turn and return what the model said. */
|
|
113
|
+
private read;
|
|
114
|
+
/** A session id no conversation can collide with. */
|
|
115
|
+
private nextSessionId;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/media/extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAcjD,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI,CAAA;IAC9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CACpF;AAED,0DAA0D;AAC1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACpD;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,CAAA;CACvD;AAED,4BAA4B;AAC5B,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,UAAU,GAAG,SAAS,CAAA;IAClD;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;IAClC,kFAAkF;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAA;IACvC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,MAAM,CAAA;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AASD,qBAAa,eAAe;IAKd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,OAAO,CAAI;gBAEU,OAAO,EAAE,sBAAsB;IAI5D;;;;;;;OAOG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;OAMG;IACG,OAAO,CACX,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,KAAK,GAAE,UAAU,GAAG,SAAsC,GACzD,OAAO,CAAC,UAAU,EAAE,CAAC;IA0CxB;;;;;;;;;OASG;YACW,IAAI;IA2BlB;;;;;OAKG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO;IAoC1D,oEAAoE;IACpE,OAAO,IAAI,IAAI;IAIf,6DAA6D;YAC/C,IAAI;IA2ClB,qDAAqD;IACrD,OAAO,CAAC,aAAa;CAKtB"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading an image with one model so another can use what it says.
|
|
3
|
+
*
|
|
4
|
+
* A provider inspects the whole request history for images, so an image left
|
|
5
|
+
* in a conversation binds that conversation to a model that can see — for
|
|
6
|
+
* good. That costs more than the picture is usually worth: the conversation
|
|
7
|
+
* loses the model it was chosen for, and the tools configured around it.
|
|
8
|
+
*
|
|
9
|
+
* So the image never enters the conversation. It goes to a throwaway session
|
|
10
|
+
* on the vision model, whose reply — the transcription, the description — is
|
|
11
|
+
* what the conversation receives, as ordinary text. The conversation's history
|
|
12
|
+
* stays free of images, so it keeps its own model and never becomes stuck.
|
|
13
|
+
*
|
|
14
|
+
* The throwaway session is disposed either way. It exists for one turn.
|
|
15
|
+
*/
|
|
16
|
+
import { SILENT_LOGGER } from '../harness/types.js';
|
|
17
|
+
import { labelOcr } from './ocr.js';
|
|
18
|
+
/** What the vision model is asked to do with the picture. */
|
|
19
|
+
const INSTRUCTION = 'Read this image for someone who cannot see it. Transcribe every piece of ' +
|
|
20
|
+
'text it contains, exactly, including numbers, dates, names and amounts. ' +
|
|
21
|
+
'Then describe briefly what the image is. Reply with only that — no ' +
|
|
22
|
+
'preamble, no offer of further help.';
|
|
23
|
+
/** How long one extraction may take before the conversation moves on without it. */
|
|
24
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
25
|
+
export class VisionExtractor {
|
|
26
|
+
options;
|
|
27
|
+
pending = new Map();
|
|
28
|
+
logger;
|
|
29
|
+
counter = 0;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.options = options;
|
|
32
|
+
this.logger = options.logger ?? SILENT_LOGGER;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether an image can be read at all right now.
|
|
36
|
+
*
|
|
37
|
+
* Optimistic about the fallback: probing Tesseract spawns a process, and
|
|
38
|
+
* this is consulted on the path of every message. A fallback that turns out
|
|
39
|
+
* to be absent simply reads nothing, which is the same outcome as saying so
|
|
40
|
+
* here would have been.
|
|
41
|
+
*/
|
|
42
|
+
get available() {
|
|
43
|
+
return this.options.visionModel() !== undefined || this.options.fallback !== undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Replace image parts with what a vision model reads in them.
|
|
47
|
+
*
|
|
48
|
+
* @param content - the prompt as the user sent it.
|
|
49
|
+
* @returns the same prompt with each image replaced by its reading, or the
|
|
50
|
+
* original content when there is nothing to read or no model to read it.
|
|
51
|
+
*/
|
|
52
|
+
async resolve(content, route = this.options.visionModel()) {
|
|
53
|
+
const images = content.filter((part) => part.type === 'image');
|
|
54
|
+
if (images.length === 0)
|
|
55
|
+
return [...content];
|
|
56
|
+
// A route is no longer required: without one there is still OCR, and
|
|
57
|
+
// without either the prompt says so rather than carrying an image that
|
|
58
|
+
// nothing downstream can use.
|
|
59
|
+
if (route === undefined && this.options.fallback === undefined)
|
|
60
|
+
return [...content];
|
|
61
|
+
const said = content
|
|
62
|
+
.filter((part) => part.type === 'text')
|
|
63
|
+
.map((part) => part.text);
|
|
64
|
+
const read = route === undefined ? undefined : await this.read(images, route);
|
|
65
|
+
if (read !== undefined) {
|
|
66
|
+
// The user's own words first: the image is what they are asking about.
|
|
67
|
+
return [
|
|
68
|
+
...said.map((text) => ({ type: 'text', text })),
|
|
69
|
+
{ type: 'text', text: `Contents of the image the user sent:\n\n${read}` },
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
// Reached either because nothing was configured to look, or because what
|
|
73
|
+
// was configured could not be reached. Both are cases where reading the
|
|
74
|
+
// text beats returning nothing at all.
|
|
75
|
+
const scanned = await this.scan(images);
|
|
76
|
+
if (scanned !== undefined) {
|
|
77
|
+
return [...said.map((text) => ({ type: 'text', text })), { type: 'text', text: scanned }];
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
...said.map((text) => ({ type: 'text', text })),
|
|
81
|
+
{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: route === undefined
|
|
84
|
+
? '(The user sent an image, but nothing here can read one. Configure a vision model in Settings → Telegram, or install tesseract.)'
|
|
85
|
+
: `(The user sent an image, but it could not be read by ${route.model}.)`,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read every image's text with the fallback.
|
|
91
|
+
*
|
|
92
|
+
* The bytes come back from the attachment store rather than being kept
|
|
93
|
+
* around: the model path never needs them, and holding every image in memory
|
|
94
|
+
* against the chance that a model call fails would be a strange thing to pay
|
|
95
|
+
* for on every message.
|
|
96
|
+
*
|
|
97
|
+
* @returns the readings joined, or undefined when there were none.
|
|
98
|
+
*/
|
|
99
|
+
async scan(images) {
|
|
100
|
+
const { fallback, attachments } = this.options;
|
|
101
|
+
if (!fallback || !attachments)
|
|
102
|
+
return undefined;
|
|
103
|
+
if (!(await fallback.available()))
|
|
104
|
+
return undefined;
|
|
105
|
+
const readings = [];
|
|
106
|
+
for (const image of images) {
|
|
107
|
+
if (image.type !== 'image')
|
|
108
|
+
continue;
|
|
109
|
+
try {
|
|
110
|
+
const stored = await attachments.readImage(image.attachment);
|
|
111
|
+
const text = await fallback.read(stored.data);
|
|
112
|
+
if (text !== undefined)
|
|
113
|
+
readings.push(text);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.logger.warn('[dsh-telegram] could not read a stored image back', error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (readings.length === 0)
|
|
120
|
+
return undefined;
|
|
121
|
+
return labelOcr(readings.length === 1
|
|
122
|
+
? readings[0]
|
|
123
|
+
: readings.map((text, index) => `--- image ${index + 1} ---\n${text}`).join('\n\n'));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Consume one session event.
|
|
127
|
+
*
|
|
128
|
+
* @returns whether it belonged to an extraction, so the caller knows not to
|
|
129
|
+
* treat it as a conversation of its own.
|
|
130
|
+
*/
|
|
131
|
+
handle(sessionId, event) {
|
|
132
|
+
const entry = this.pending.get(sessionId);
|
|
133
|
+
if (!entry)
|
|
134
|
+
return false;
|
|
135
|
+
if (event.type === 'assistant/message') {
|
|
136
|
+
const message = event.data?.message;
|
|
137
|
+
const text = (message?.content ?? [])
|
|
138
|
+
.filter((block) => block.type === 'text')
|
|
139
|
+
.map((block) => block.text ?? '')
|
|
140
|
+
.join('')
|
|
141
|
+
.trim();
|
|
142
|
+
if (text !== '')
|
|
143
|
+
entry.text = text;
|
|
144
|
+
}
|
|
145
|
+
if (event.type === 'turn/end') {
|
|
146
|
+
const reason = event.data?.reason;
|
|
147
|
+
if (reason?.kind === 'error') {
|
|
148
|
+
this.logger.warn(`[dsh-telegram] the vision model could not read the image: ${reason.error?.message ?? 'the turn failed'}`);
|
|
149
|
+
entry.settle(undefined);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
entry.settle(entry.text === '' ? undefined : entry.text);
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
/** Abandon every extraction in flight — the plugin is unloading. */
|
|
157
|
+
dispose() {
|
|
158
|
+
for (const entry of [...this.pending.values()])
|
|
159
|
+
entry.settle(undefined);
|
|
160
|
+
}
|
|
161
|
+
/** Run one throwaway turn and return what the model said. */
|
|
162
|
+
async read(images, route) {
|
|
163
|
+
const sessionId = this.nextSessionId();
|
|
164
|
+
let agent;
|
|
165
|
+
try {
|
|
166
|
+
agent = await this.options.host.create(sessionId, this.options.cwd, route);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.logger.warn('[dsh-telegram] could not open a session to read the image', error);
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const settled = new Promise((resolve) => {
|
|
174
|
+
const timer = setTimeout(() => {
|
|
175
|
+
this.pending.delete(sessionId);
|
|
176
|
+
this.logger.warn('[dsh-telegram] reading the image timed out');
|
|
177
|
+
resolve(undefined);
|
|
178
|
+
}, this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
179
|
+
this.pending.set(sessionId, {
|
|
180
|
+
text: '',
|
|
181
|
+
settle: (text) => {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
this.pending.delete(sessionId);
|
|
184
|
+
resolve(text);
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
agent.followup([{ type: 'text', text: INSTRUCTION }, ...images]);
|
|
189
|
+
return await settled;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
// One turn is its whole life; leaving it loaded would keep a second
|
|
193
|
+
// model warm for every picture anyone sends.
|
|
194
|
+
await agent.dispose().catch((error) => {
|
|
195
|
+
this.logger.warn('[dsh-telegram] could not dispose the reading session', error);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** A session id no conversation can collide with. */
|
|
200
|
+
nextSessionId() {
|
|
201
|
+
if (this.options.newSessionId)
|
|
202
|
+
return this.options.newSessionId();
|
|
203
|
+
this.counter += 1;
|
|
204
|
+
return `tg-vision-${Date.now()}-${this.counter}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../../src/media/extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Getting what the user sent into what the agent can read.
|
|
3
|
+
*
|
|
4
|
+
* Three kinds arrive and only two can go anywhere useful:
|
|
5
|
+
*
|
|
6
|
+
* - **Images** reach the model. The harness attachment seam accepts PNG,
|
|
7
|
+
* JPEG, WebP and GIF, commits the bytes durably, and hands back a reference
|
|
8
|
+
* the session log can carry — which is why a screenshot works at all.
|
|
9
|
+
* - **Text documents** — a log, a stack trace, a source file — have no seam,
|
|
10
|
+
* but they do not need one: their content is text, so it is read and placed
|
|
11
|
+
* in the prompt where the model already looks.
|
|
12
|
+
* - **Voice, audio and video** have neither. The harness explicitly defers
|
|
13
|
+
* them, so the honest answer is to say so rather than to accept the message
|
|
14
|
+
* and silently drop what it carried.
|
|
15
|
+
*
|
|
16
|
+
* Telegram sends an uncompressed image as a *document*, so kind is decided by
|
|
17
|
+
* media type rather than by which field it arrived in.
|
|
18
|
+
*/
|
|
19
|
+
import type { TelegramMessage } from '../telegram/types.js';
|
|
20
|
+
import type { ImageCandidate } from './limits.js';
|
|
21
|
+
/** One file a message carries, and what can be done with it. */
|
|
22
|
+
export interface MediaItem {
|
|
23
|
+
/** Telegram's handle for the bytes. */
|
|
24
|
+
readonly fileId: string;
|
|
25
|
+
/** What this plugin can do with it. */
|
|
26
|
+
readonly kind: 'image' | 'text' | 'unsupported';
|
|
27
|
+
/** Media type, where Telegram supplied one. */
|
|
28
|
+
readonly mediaType?: string;
|
|
29
|
+
/** File name, where one was sent. */
|
|
30
|
+
readonly name?: string;
|
|
31
|
+
/** Size in bytes, where Telegram reported it. */
|
|
32
|
+
readonly size?: number;
|
|
33
|
+
/** For `unsupported`, what it was — so the refusal can name it. */
|
|
34
|
+
readonly describedAs?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Every size this image is available in, for `image` items.
|
|
37
|
+
*
|
|
38
|
+
* Telegram renders a photo at several sizes and the largest routinely
|
|
39
|
+
* exceeds what the harness will store, so the choice is made against the
|
|
40
|
+
* seam's limits rather than here. A document arrives at one size only.
|
|
41
|
+
*/
|
|
42
|
+
readonly candidates?: readonly ImageCandidate[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Identify the media a message carries.
|
|
46
|
+
*
|
|
47
|
+
* @param message - the incoming Telegram message.
|
|
48
|
+
* @returns the item, or undefined for a message carrying none.
|
|
49
|
+
*/
|
|
50
|
+
export declare function describeMedia(message: TelegramMessage): MediaItem | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the harness can store this image.
|
|
53
|
+
*
|
|
54
|
+
* @param mediaType - the type Telegram reported.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isStorableImage(mediaType: string | undefined): boolean;
|
|
57
|
+
//# sourceMappingURL=intake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/media/intake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAyBjD,gEAAgE;AAChE,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,aAAa,CAAA;IAC/C,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,qCAAqC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;CAChD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,SAAS,CAoD7E;AAeD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEtE"}
|