@ykurilov/agent-chat-attachments 0.0.0-stage → 0.56.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yury Kurilov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,113 @@
1
- # Temporary Holding Version
1
+ # @ykurilov/agent-chat-attachments
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Вложения к сообщению: картинки, PDF и текстовые файлы. Отдельное расширение: приложению, которому вложения не нужны,
4
+ они не нужны и в сборке. Без пакета чат остаётся текстовым, договор остальных пакетов не меняется.
5
+
6
+ Серверу без экрана React не нужен: `react` и `@ykurilov/agent-chat-react` — необязательные зависимости (peer). Для `@ykurilov/agent-chat-attachments/react`
7
+ оба должны стоять в приложении, как и для самого чата на экране.
8
+
9
+ ```ts
10
+ // сервер
11
+ import { createAttachments, createDiskAttachmentStorage } from "@ykurilov/agent-chat-attachments";
12
+
13
+ const attachments = createAttachments({ storage: createDiskAttachmentStorage({ directory: "./data/attachments" }) });
14
+
15
+ createChatHandler({
16
+ …,
17
+ attachments,
18
+ agent: { instructions: appInstructions },
19
+ });
20
+ ```
21
+
22
+ ```tsx
23
+ // браузер
24
+ import { attachmentsClient } from "@ykurilov/agent-chat-attachments/react";
25
+ <AgentChat api="/api/chat" chatId={chatId} skin={tandemSkin} attachments={attachmentsClient} />;
26
+ ```
27
+
28
+ ## Что умеет
29
+
30
+ - **Типы**: картинки (PNG, JPEG, WebP, GIF), PDF, текстовые файлы (`.txt`, `.md`, `.csv`, `.json` и подобные). Тип определяется по содержимому,
31
+ а не по имени и не по тому, что назвал браузер. Всё остальное отклоняется понятным отказом.
32
+ - **Типы можно сузить**: `createAttachments({ acceptedMediaTypes: ["image/png", "image/jpeg", "image/webp"] })`. Файл другого типа
33
+ отклоняется отказом 415 `unsupported_type` до ужатия, разбора и записи (и при загрузке, и в `ingestFile`, и при импорте). Файл,
34
+ загруженный раньше, при отправке сообщения тоже отклоняется: «подойдут только PNG, JPEG, WebP». Поле ввода получает список
35
+ из `GET …/attachments/settings` и файл чужого типа (в том числе вставленный из буфера) отклоняет сразу, без загрузки. Тип, которого
36
+ SDK не распознаёт (например `image/jpg`), — ошибка при запуске.
37
+ - **Картинки** уходят только моделям, которые их видят у выбранного провайдера (`modelAcceptsImages(profile, providerId)` из ядра:
38
+ `acceptsImagesByProvider` профиля, иначе `acceptsImages`). Если модель не видит, человек узнаёт об этом до отправки, а модель
39
+ получает в запросе пометку «картинка не показана».
40
+ - **Модель без зрения смотрит картинку инструментом** (`imagesForBlindModels`, по умолчанию выключено). Вместо картинки в сообщении
41
+ — постоянная плашка с номером вложения, а инструмент приложения читает картинку этого чата и отдаёт её зрячей модели:
42
+
43
+ ```ts
44
+ const attachments = createAttachments({
45
+ storage,
46
+ acceptedMediaTypes: ["image/png", "image/jpeg", "image/webp"],
47
+ imagesForBlindModels: { toolName: "look_at_attachment" },
48
+ // свой текст: marker: ({ id, name, mediaType, authorId }) => `[Картинка от Юрия «${name}», вложение ${id}. Посмотреть: look_at_attachment.]`
49
+ });
50
+
51
+ // инструмент приложения (agent.tools — функция от выбранной модели, в ней есть chatId):
52
+ const image = await attachments.readChatImage({ chatId: model.chatId, attachmentId });
53
+ if (!image) return { error: "В этом чате нет такой картинки" }; // чужой чат, не картинка, неверный номер
54
+ // image: { id, name, mediaType, authorId, bytes }
55
+ ```
56
+
57
+ Плашка по умолчанию: `[Картинка «shot.png», вложение <id>. Посмотреть: look_at_attachment.]`. Она собирается только из неизменной
58
+ записи вложения, поэтому одинакова байт в байт на каждом ходе и после смены модели: кэш модели не сбивается. Свой `marker`
59
+ тоже должен зависеть только от входа (без дат и счётчиков). Зрячей паре картинка уходит как раньше; `always: true` — плашка
60
+ всем моделям. К инструкции вложений дописывается постоянная строка про плашку и инструмент. Поле ввода в этом режиме не
61
+ предупреждает «модель не видит картинки». Сам инструмент (какая модель смотрит, сколько стоит) пишет приложение.
62
+ `readChatImage` проверяет только чат: картинку отдаст, кто бы в этом чате её ни загрузил. Если в чате несколько людей
63
+ и кому-то нельзя смотреть чужие картинки, проверку автора (`image.authorId`) делает приложение в своём инструменте.
64
+ Без `always` смена пары «модель + провайдер» между зрячей и слепой меняет историю (плашка ↔ картинка) и сбрасывает
65
+ кэш модели; нужен ровный кэш при смене моделей — ставьте `always: true`.
66
+ - **Только нужные типы** (`acceptedMediaTypes`). Решает тип, с которым файл пришёл: ужатый в JPEG большой PNG при
67
+ `["image/png"]` проходит. Он записан в поле `sourceMediaType` записи вложения — своё хранилище должно его сохранять.
68
+ Файл чужого типа, загруженный до сужения списка, при отправке получает отказ `unsupported_type` с текстом
69
+ «Такой файл прикрепить нельзя: подойдут только PNG, JPEG, WebP.» (список — по настройке). Импорт чата
70
+ (`importChat`) вместо картинки неподходящего типа пишет текст «[картинка: имя]».
71
+ - **`isAttachmentId(value)`** — номер вложения ли это (UUID): проверка до похода в хранилище.
72
+ - **Большие фото уменьшаются при загрузке**: картинка поворачивается по EXIF и ужимается до 2000×2000 пикселей и ~4,5 МБ в base64
73
+ (запас ниже 5 МБ строгих провайдеров). Хранится и показывается уменьшенная копия, оригинал не сохраняется. Картинки, загруженные
74
+ до этого, ужимаются при сборке запроса (последние 20 решений запоминаются, повторно не перекодируются). Нечитаемая картинка
75
+ и картинка больше 60 млн пикселей при загрузке — отказ 422 `unreadable`, в старой истории — пометка «картинка пропущена».
76
+ У большой анимации (GIF, WebP) остаётся первый кадр в PNG или JPEG; имя файла не меняется. Обработку делает [sharp](https://sharp.pixelplumbing.com): готовые сборки для macOS, Linux (glibc и Alpine/musl)
77
+ и Windows ставятся сами, компилятор не нужен. В Docker ставьте зависимости внутри образа (`pnpm install`), а не копируйте
78
+ `node_modules` с Mac: сборка sharp у каждой системы своя.
79
+ - **PDF и текст**: текст достаётся на сервере при загрузке один раз и идёт в запрос блоком `<attachment name="…" type="…">…</attachment>`
80
+ любой модели. PDF без текста (скан) отклоняется с объяснением.
81
+ - **Лимиты** (`createAttachments({ limits })`): 10 файлов на сообщение, 20 МБ на файл, 25 000 токенов текста на файл (лишнее обрезается
82
+ с пометкой), 100 000 токенов текста на сообщение. На человека: не больше 30 неотправленных файлов и 200 МБ (`maxUnsentFiles`,
83
+ `maxUnsentBytes`, ответ 429 `too_many_unsent`); одновременных загрузок не больше 8 на сервер и 3 на человека (`maxParallelUploads`,
84
+ `maxParallelUploadsPerUser`, ответ 429 `busy`). Файл с ошибкой в лимит не входит. Поле ввода грузит по два файла сразу, остальные ждут в очереди; на `busy` повторяет само. Значения по умолчанию — `defaultAttachmentLimits`.
85
+ - **Ужатие при загрузке идёт по очереди**: разом не больше половины ядер процессора (`maxParallelImageFits`), остальные ждут.
86
+ - **Старые большие картинки** (загруженные до того, как сервер стал их уменьшать) уменьшаются один раз и держатся в памяти: до 128 МБ уменьшенных копий (`imageCacheBytes`, `0` — не хранить), сверх — уходят давно не нужные.
87
+ - **Разбор PDF** идёт в отдельном потоке с потолком памяти и сроком: зависший или «раздутый» файл обрывается, сервер живёт дальше.
88
+ Сбор текста останавливается, как только набран потолок символов. Текстовые логи с цветовыми кодами терминала принимаются как текст.
89
+ - **Хранилище файлов** заменяемое (`AttachmentStorage`): из коробки `createDiskAttachmentStorage` и `createMemoryAttachmentStorage`.
90
+ Своё (например, S3) проверяется общим набором `describeAttachmentStorageContract` из `@ykurilov/agent-chat-attachments/storage-contract`.
91
+ Схема базы чата не меняется, миграции не нужны. Метод `removeAllOfChat(chatId)` удаляет все файлы чата: его вызывает удаление чата из списка чатов.
92
+ - **Безопасность**: файл привязан к чату и владельцу, чужой и несуществующий файл отвечают одинаково (404). Адрес в хранилище — серверный UUID,
93
+ имя файла в путь не попадает. Файлы отдаются с `nosniff` и `Content-Security-Policy: sandbox`. Ключи провайдеров в браузер не уходят.
94
+ - **Кэш модели**: вложения — часть сообщения человека, извлечённый текст не меняется от хода к ходу, поэтому начало запроса остаётся постоянным.
95
+ - **Сжатие истории**: сводка называет прикреплённые файлы по имени, оригиналы остаются в хранилище.
96
+ - Загруженные, но не отправленные файлы убираются сами через 24 часа (проверка раз в 15 минут), вместе с недописанными остатками на диске.
97
+ Файл, который пролежал дольше и не был отправлен, возвращается в поле с пометкой «загрузите снова». Файл считается прикреплённым
98
+ только после того, как сообщение записано; если отправка отклонена, текст и файлы остаются в поле.
99
+ - **Импорт старой переписки**: `attachments.importFile({ ownerId, chatId, bytes, mediaType, filename? })` сохраняет картинку из
100
+ старой системы; его зовёт `service.importChat` ядра сам, если вложения подключены к чату.
101
+ - **Инструкция агенту** про вложения (`attachmentsInstructions`) ядро добавляет само, вручную дописывать не нужно (дубль не появится).
102
+
103
+ ## Адреса
104
+
105
+ Под адресом чата (`/api/chat`): `GET /attachments/settings` (лимиты и допустимые типы для браузера),
106
+ `POST /:chatId/attachments` (поле формы `file`), `GET` и `DELETE /:chatId/attachments/:id` (удалить можно только неотправленный файл).
107
+
108
+ ## Со своим скином
109
+
110
+ `Composer` получает необязательное `attachments` (`ComposerAttachments`): скрепка, перетаскивание и вставка файлов, превью с удалением,
111
+ `notice` с готовой надписью. `UserMessage` получает необязательное `attachments` (`MessageAttachmentsView`): миниатюры картинок и плашки файлов.
112
+ Нет свойства — вложений нет, ничего про них не рисовать. Надписи пакета — `russianAttachmentsLabels`, английские — `englishAttachmentsLabels`, свои передаются в
113
+ `createAttachmentsClient(labels)`.
@@ -0,0 +1,44 @@
1
+ # Сторонний код
2
+
3
+ Часть кода Agent Chat SDK перенесена из проекта pi-mono (https://github.com/badlogic/pi-mono) по лицензии MIT.
4
+
5
+ ## Что перенесено
6
+
7
+ | Откуда в pi-mono | Куда в SDK |
8
+ |---|---|
9
+ | `packages/ai/src/utils/overflow.ts` — признаки переполнения окна модели | `packages/core/src/provider-errors/error-signs.ts` |
10
+ | `packages/ai/src/utils/retry.ts` — признаки ошибок, которые не повторяют и которые повторяют | `packages/core/src/provider-errors/error-signs.ts` |
11
+ | `packages/ai/src/utils/json-parse.ts` — `repairJson` | `packages/core/src/agent-run/json-repair.ts` |
12
+ | `packages/coding-agent/src/utils/image-resize-core.ts` — порядок ужатия картинки для модели | `packages/attachments/src/fit-image.ts` |
13
+
14
+ ## Лицензия pi-mono
15
+
16
+ MIT License
17
+
18
+ Copyright (c) 2025 Mario Zechner
19
+
20
+ Permission is hereby granted, free of charge, to any person obtaining a copy
21
+ of this software and associated documentation files (the "Software"), to deal
22
+ in the Software without restriction, including without limitation the rights
23
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24
+ copies of the Software, and to permit persons to whom the Software is
25
+ furnished to do so, subject to the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be included in all
28
+ copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36
+ SOFTWARE.
37
+
38
+ ## Сторонние библиотеки в зависимостях
39
+
40
+ Код этих библиотек не перенесён в SDK: пакеты ставятся из npm вместе со своей лицензией.
41
+
42
+ | Библиотека | Лицензия | Где нужна |
43
+ |---|---|---|
44
+ | `@xyflow/react` (https://github.com/xyflow/xyflow) | MIT, Copyright (c) 2019-2025 webkid GmbH | доска редактора сценария, вход `@ykurilov/agent-chat-flows/react` |