10/Documentation
Протокол durable (экспериментально)
Что передаётся между Collie и durable agent, руководство для разработчика: маршруты, hello и эпоха, жизненный цикл запроса и идентификаторы запросов, сокетный и токенный транспорты, лимиты и правила версионирования, со ссылкой на WIRE.md для точных правил
Экспериментальная. Сетевой протокол может измениться или функция может быть удалена в последующем минорном релизе. В Версии и информативное приветствие описано, как изменения доходят до вас.
Сетевой протокол durable — это способ взаимодействия Collie с durable-агентом. На этой странице он описан для разработчика, который создаёт durable-агента без SDK или хочет знать, что именно делает SDK. Точные правила приведены в WIRE.md, и если эта страница расходится с тем файлом, верным считается WIRE.md. Durable SDK берёт на себя всю работу с протоколом.
Что передаётся по сети
Collie подключается, а durable agent слушает; каждый обмен представляет собой один HTTP-запрос с телом в формате JSON.
Durable agent слушает приватный Unix-сокет или доступен через TLS по URL (Транспорты). Collie опрашивает его: здесь нет push-уведомлений, WebSocket и потоков. Каждый путь начинается с /session/v1 — это фиксированное имя, а не версия. Каждый вызов, кроме первого hello, передаёт версию в заголовке X-Collie-Session. Тело запроса представляет собой один JSON-объект размером не более 64 КиБ, а пустое тело POST читается как {}.
| метод и путь | действие | лимит ответа |
|---|---|---|
GET /hello | Имя durable agent, диапазон версий, эпоха, готовность и возможности | 16 КиБ |
GET /snapshot | Разметка: рабочие пространства, вкладки, панели с их состоянием, шаг диалога, открытые запросы и стартеры. Передаёт ETag и возвращает 304, если ничего не изменилось | 4 МиБ |
GET /pane/:id/events?after=N&epoch=E | События панели после курсора | 8 МиБ |
POST /pane/:id/answer | Ответ на запрос | 16 КиБ |
POST /pane/:id/send | Текст для панели, чтобы направить текущий шаг диалога или дождаться следующего | 16 КиБ |
POST /pane/:id/abort | Остановить текущий шаг диалога | 16 КиБ |
POST /pane/:id/action | Нажатие на одну из кнопок на панели инструментов | 16 КиБ |
POST /rename | Новый заголовок для рабочей области или вкладки | 16 КиБ |
POST /close | Закрыть панель, вкладку или рабочее пространство | 16 КиБ |
POST /starter/:id | Запустить стартер. В ответе возвращается имя новой панели | 16 КиБ |
GET /conversations?after=C&limit=N | Диалоги durable agent, открытые и закрытые, сначала новые | 1 МиБ |
GET /conversation/:id/items?after=C&limit=N | Элементы одного диалога, сначала старые | 8 МиБ |
POST /reopen | Снова открыть закрытый диалог | 16 КиБ |
Каждая ошибка представляет собой тело JSON с code, например 409 stale или 404 no-such-pane. Любой другой путь возвращает 404 not-found. Ответ, превышающий лимит, не читается, а результат POST-запроса с оборванным ответом считается неизвестным (Идентификаторы запросов).
Приветствие и эпоха
hello указывает, чем является durable agent, а эпоха указывает, с каким именно его запуском взаимодействует Collie.
поле в hello | что содержит |
|---|---|
service | Строго collie-durable. Без этого Collie считает durable agent другим сервером и больше ничего не вызывает |
protocol | Диапазон поддерживаемых durable agent версий протокола, { min, max }. На сегодня { min: 1, max: 1 } |
host.name | Собственное имя durable agent, только отображаемый текст, не более 64 байт |
epoch | Новое и случайное значение при каждом запуске durable agent, не менее 128 бит, никогда не используется повторно |
ready | Значение false, пока durable agent компонует свои панели после запуска |
caps | Возможности durable agent, каждая в виде логического значения. Отсутствующая возможность считается false |
Collie сначала вызывает hello без заголовка версии, выбирает наибольшую поддерживаемую обеими сторонами версию и передаёт её во всех последующих вызовах. Пока ready равно false, каждый маршрут, кроме hello, возвращает 503 starting, а на телефоне отображается, что durable agent запускается. Durable agent выставляет ready только после того, как все диалоги снова отображены и каждый открытый ask создан повторно, поэтому при перезапуске никогда не отображается незавершённая разметка.
ETag снимка равен "<epoch>-<rev>". Значение rev увеличивается при каждом изменении, видимом для читателя, поэтому ответ 304 почти ничего не стоит для durable agent. Новая эпоха сообщает Collie, что durable agent перезапустился. Collie продолжает показывать имеющиеся панели, пока не считает новый снимок. Затем он считывает каждую панель со старым курсором и получает всё текущее представление панели в одном ответе.
Одно неудачное чтение после успешного сохраняет последний снимок на время этого опроса. Два сбоя подряд показывают durable agent как недоступный. Путь к сокету, не прошедший проверку прав доступа, становится недоступным сразу.
Запросы и ответы
Ask представляет собой карточку с вопросом; он открыт от события ask в durable agent до события ask-closed.
| состояние | кто задаёт | что отображает телефон |
|---|---|---|
| Открыт | Durable agent создаёт ask. Панель показывает waiting, пока открыт хотя бы один ask | Карточка в разделе «Требуется ваш ответ» с именем durable agent и оставшимся временем, если у запроса задано expiresAt |
| Изменён | Durable agent изменяет ask на месте, и его revision увеличивается | Карточка перерисовывается. Нажатие на старую форму возвращает 409 stale, а на карточке появляется сообщение: «Этот запрос изменился. Прочитайте его снова, затем выберите ответ». |
| Принят | Телефон отправляет ответ, и durable agent закрывает ask со статусом by: "phone" | Карточка исчезает, а инструмент запускается или отклоняется в соответствии с выбранным вариантом |
| Решено в durable agent | Durable agent закрывает ask самостоятельно, decidedBy: "program". Остановка шага засчитывается | Строка в чате, начинающаяся со слов «Решено в durable agent», с заголовком запроса |
| Время истекло | Время превысило expiresAt, decidedBy: "timeout" | Строка в чате, начинающаяся со слов «Время истекло до получения ответа», с заголовком запроса |
Нажатие, совершённое после принятия решения, возвращает 409 decided с первым результатом, и ничего не запускается. Затем на карточке отображается, кто принял решение первым: «Решено в durable agent.», «Время истекло до получения ответа.» или после повторного нажатия «Ответ уже дан. Эта карточка исчезнет при следующем обновлении.». Запрос, о котором durable agent больше не знает, возвращает 200 too-late. Тогда на карточке отображается: «Не принят. Вопрос может появиться снова через минуту.», поскольку после перезапуска durable agent может создать его повторно с тем же идентификатором.
| правило | почему |
|---|---|
| Идентификатор запроса представляет собой хеш идентификатора панели и идентификатора вызова инструмента | Тот же вызов инструмента, созданный после перезапуска, имеет тот же идентификатор, поэтому отправленный после перезапуска ответ будет доставлен |
Карточка permission формируется из имени инструмента и его аргументов, но никогда из слов модели | Модель, желающая получить утвердительный ответ, описала бы вызов в выгодном свете |
Durable agent за TLS не должен предлагать allow-always | Токен служит единственной точкой контроля, поэтому один ответ от его владельца пропустит все последующие вызовы |
Телефон не предлагает allow-always на карточке, слова в которой были урезаны или удалены, либо если в ней не указан инструмент | Пользователь не видит, что именно покрывает разрешение |
expiresAt задаётся по стандарту ISO 8601 с указанием временной зоны | После него судьбу запроса решает timeout, и снимок больше не показывает его как открытый |
| Durable agent хранит результаты как минимум последних 500 разрешённых запросов на панель | Запоздалое нажатие возвращает decided и исходный результат, а не обычный too-late |
Запрос бывает одного из четырёх типов: permission, choice, text или form. У каждого своя структура ответа, описанная рядом с Вопросы в SDK.
Идентификаторы запросов
Каждое действие с телефона содержит идентификатор запроса, поэтому оно выполняется не более одного раза.
Телефон генерирует по одному идентификатору запроса на каждое намерение, а Collie записывает собственную квитанцию перед дальнейшей передачей идентификатора. Durable agent хранит как минимум 500 последних идентификаторов своей эпохи. Повтор завершённого идентификатора снова получает первый ответ с replayed: true и ничего не запускает. Тот же идентификатор с другим телом запроса возвращает 409 request-id-reused.
Collie никогда не повторяет POST самостоятельно. Запрос POST, завершившийся по тайм-ауту, потерявший соединение или получивший ответ 5xx, имеет неизвестный результат. В поле ответа на карточке появляется сообщение "Not sure the answer arrived. Try again sends it once more.", а кнопка Try again отправляет тот же идентификатор запроса, поэтому память durable agent отвечает на повтор.
Повторная попытка, которая застаёт запрос закрытым или ход переданным, не может определить, прошёл ли первый запрос. В этом случае Collie возвращает телефону ответ "maybe applied", карточка сообщает "May already be applied. Check the chat.", и действие больше никогда не отправляется в третий раз.
Транспорты
Durable agent использует один из двух транспортов: приватный сокет на машине Collie или TLS с токеном.
Байты в обоих случаях одинаковы: маршруты, тела запросов, заголовок версии, ETag и коды ошибок. Различается лишь то, кому разрешён доступ к durable agent.
| через сокет | правило |
|---|---|
| Путь | Абсолютный, не более 103 байт. Обе стороны считывают его из COLLIE_DURABLE_SOCKET |
| Режимы доступа | Сокет создаётся с режимом 0600 в папке с режимом 0700. Durable agent удаляет устаревший файл сокета перед началом прослушивания |
| Обход пути | Перед каждым подключением Collie проверяет каждую часть пути с помощью lstat. Ни одна из частей не является символической ссылкой. Папка сокета принадлежит вам и имеет режим 0700. Ни одна папка выше по дереву не доступна для записи группе или остальным пользователям, за исключением папок со sticky bit, таких как /tmp. Сокет принадлежит вам, без прав для группы или остальных пользователей |
| Ошибки обхода пути | Collie не подключается и помечает durable agent как недоступный, указывая socket-not-private и часть пути, вызвавшую сбой |
| Токен | Нет. Приватный сокет сам служит точкой контроля |
| Кто может действовать | Любой процесс вашего пользователя. Durable agent может отклонять собственные дочерние процессы с кодом 403 caller-refused |
| Тайм-ауты | 1000 мс для hello, snapshot и events. 3000 мс для answer, send, stop, action, rename или close. 30000 мс для starter |
| Как часто Collie отправляет запросы | Каждую 1 с, пока телефон активен, иначе каждые 3 с. Если durable agent недоступен, опрос выполняется один раз за интервал |
| Системы | Linux и macOS. В Windows нет сокетного транспорта |
| через TLS | правило |
|---|---|
| URL | https://, либо http://127.0.0.1:<port> или http://localhost:<port> для локального теста. Не более 2048 байт, без имени пользователя и пароля, без query и фрагмента. Collie добавляет к нему /session/v1 |
| Токен | Не менее 32 байт, печатные символы ASCII без пробелов. Collie отправляет Authorization: Bearer <token> при каждом запросе, включая hello |
| Проверка durable agent | Сначала токен, до версии, hello и ready, сравнение за константное время. При отсутствии или неверном токене возвращается только 401 { "code": "unauthorized" } и ничего больше |
| Перенаправления | Collie не переходит по перенаправлениям. Код 3xx передаётся наверх как статус, поэтому токен никогда не попадёт на другой хост |
| ETag | Прокси, сжимающий ответ, может ослабить тег до W/"<epoch>-<rev>". Обе стороны сравнивают его без префикса W/ |
| Отказ | Код 401 или 403 отмечает durable agent как недоступный с token-refused |
| Тайм-ауты | 5000 мс на каждое чтение и запись, так как уснувший durable agent может запускаться с нуля. 30000 мс для starter |
| Как часто Collie отправляет запросы | Каждые 2 с, пока открыто на телефоне, иначе каждые 10 с. Durable agent, который недоступен или вернул отказ, опрашивается не чаще одного раза в 10 с |
Каждый таймаут задаёт предельный срок на весь обмен целиком, от подключения до последнего байта. Через TLS каждый опрос считается запросом, за который хостинг может списывать средства, включая 304.
Ограничения
Collie обрезает или отклоняет всё, что выходит за эти пределы, поэтому durable agent в их границах ничего не теряет.
| параметр | лимит |
|---|---|
| Рабочих пространств на снимок | 64 |
| Вкладок на снимок | 256 |
| Панелей на снимок | 256 |
| Открытых запросов на панель | 50, сначала самые старые |
Вариантов для permission | 6 |
Вариантов для choice и для поля выбора | 8 |
Полей для form | 6 |
| Starter на снимок | 50 |
| Кнопок belt на панель | 4, метка из 16 символов |
| Идентификатор | От 1 до 128 байт, без управляющих символов, без пробелов, без /, ? и # |
| Тело запроса | 64 КиБ, иначе 413 too-large |
| Ответы | hello — 16 КиБ, snapshot — 4 МиБ, events — 8 МиБ, conversations — 1 МиБ, items — 8 МиБ, POST — 16 КиБ |
| Событий на ответ | 1000. Отставший читатель получает текущее состояние, не более 2000 самых свежих элементов |
Текст элемента user, reply, thinking или notice | 64 КиБ |
Текст элемента markdown или code | 32 КиБ |
| Текст, отправляемый телефоном | 32 КиБ |
Элемент link | Только https://, не более 2048 символов |
| Страница истории | 100 диалогов или 1000 элементов |
| Курсор истории | 512 байт печатных символов ASCII, без пробелов |
Строка, превышающая лимит, обрезается, а её управляющие и невидимые символы удаляются. Список, превышающий лимит, сохраняет первые записи. Страница истории, превышающая лимит, не читается вовсе.
Версии и информативное приветствие
Сейчас существует одна версия сетевого протокола, 1, и Collie использует 1..1.
Когда выйдет версия 2, Collie продолжит поддерживать версию 1 в течение одного минорного релиза, а следующий за ним минорный релиз прекратит её поддержку. Читатель игнорирует неизвестные ему поля, типы событий, разновидности элементов и коды. Поле, добавленное в рамках версии 1, такое как expiresAt или decidedBy, является необязательным. Новое обязательное поле, новое значение для старого поля или удалённое поле требуют новой версии.
Запрос неизвестного телефону типа отображается как запрос, на который нельзя ответить с телефона, без кнопок.
Если два диапазона не пересекаются, Collie больше ничего не читает от durable agent. Один раз за эпоху durable agent он отправляет ещё один hello, на этот раз со своей версией, чтобы durable agent мог зафиксировать несовпадение в своём журнале. Durable agent отвечает 409 incompatible с заголовком refusal, где указаны оба диапазона. Это информационное приветствие.
Collie записывает одну строку в журнал для каждого отклонённого диапазона, а collie doctor показывает оба диапазона. Домашний экран durable agent на телефоне выводит то же сообщение, например: «The durable agent speaks wire v2, this Collie speaks v1. Update Collie, or the agent's SDK.» Обновите устаревшую сторону.
Где найти точные правила
WIRE.md — это контракт со всеми структурами, кодами и правилами.
Решения, лежащие в его основе, зафиксированы в репозитории:
| запись | что оно решает |
|---|---|
| ADR 0099 | Долговечный агент — это мультиплексор без экрана |
| ADR 0100 | Запрос передается как данные и получает ответ по id |
| ADR 0101 | Send и stop указывают ход, который видел телефон |
| ADR 0102 | Шаблоны запуска — это строки на странице «Создать» |
| ADR 0103 | Два транспорта |
| ADR 0104 | Долговечный агент — это сессия и выход из нее |
| ADR 0105 | SDK представляет собой npm-пакет |
Редактировать эту страницу на GitHub