История диалога

Используйте историю для восстановления переписки в собственном интерфейсе. Идентификаторы секции, пользователя и диалога должны совпадать с исходными запросами генерации. Web-виджет 1.42 вызывает метод при включённом resumeDialog и при выборе разговора в списке dialogHistory; настройки виджета по умолчанию выключены.

Для гостевого ресурса добавьте к запросу Authorization: Bearer gst_.... Пример с page без заголовка ниже относится к прежнему режиму. Правила передачи токена и общие ошибки: Гостевые токены.

Для выбора разговора используйте список диалогов. Метод /shopping/history возвращает сообщения одного диалога и поддерживает два режима: страницы page/limit и загрузку с конца.

POST /shopping/history

Чтобы восстановить сообщения после перезагрузки страницы или повторного открытия чата, вызовите:

POST https://shopping-assistant-api.gravityfield.ai/shopping/history
Content-Type: application/json

Endpoint возвращает только сообщения покупателя и ответы Shopping Assistant. Внутренние system-сообщения, вызовы инструментов и служебные данные в ответ не попадают.

Запрос истории

Без paginationMode действует прежняя пагинация page/limit:

curl --request POST \
  --url 'https://shopping-assistant-api.gravityfield.ai/shopping/history' \
  --header 'Content-Type: application/json' \
  --data '{
    "tenant_id": "demo-section",
    "resourceId": "demo-user",
    "threadId": "thread-8f2b1c",
    "page": 1,
    "limit": 50
  }'
Поле Обязательное Назначение
tenant_id Да ID секции Gravity Field. Должен совпадать с runtimeContext.tenant_id, который использовался при создании диалога.
resourceId Да Тот же идентификатор, что в генерации: user.uid Gravity Field или используемый в интеграции user.custom.
threadId Да ID диалога, историю которого нужно получить.
page Нет Номер страницы, начиная с 1. По умолчанию 1. Только без paginationMode.
limit Нет Количество сообщений на странице: от 1 до 100. По умолчанию 50.
paginationMode Нет backward для загрузки последних сообщений и более ранних страниц. Без поля используется page/limit.
before Нет Курсор из pagination.nextCursor предыдущего ответа. Только при paginationMode: "backward".

История доступна только при точном совпадении tenant_id, resourceId и threadId. Для гостевого запроса диалог должен принадлежать сессии предъявленного токена. Другой токен с тем же resourceId не даёт доступ к прежнему диалогу. Если диалог не найден, скрыт через «Убрать из истории» или принадлежит другой секции, пользователю или сессии, API возвращает 404.

Храните threadId вместе с sessionId гостевой сессии. После повторной регистрации начинайте новый диалог: история и память прежней сессии не переносятся. В ответах API поле resourceId, включая сообщения в items, сохраняет внешний идентификатор Gravity Field.

Ответ

{
  "threadId": "thread-8f2b1c",
  "resourceId": "demo-user",
  "messageFormat": "plain_text",
  "items": [
    {
      "id": "shopping:user:4f8c...",
      "role": "user",
      "content": "Помогите выбрать ноутбук для учёбы",
      "createdAt": "2026-07-28T10:00:00.000Z"
    },
    {
      "id": "shopping:assistant:8a21...",
      "role": "assistant",
      "createdAt": "2026-07-28T10:00:01.250Z",
      "threadId": "thread-8f2b1c",
      "resourceId": "demo-user",
      "context": {
        "intent": "product-selection",
        "scenario": { "id": "laptop-selection", "version": 1 },
        "mode": "clarify"
      },
      "response": {
        "contents": [
          {
            "type": "message",
            "value": "Какой у вас бюджет и нужна ли большая диагональ экрана?"
          }
        ],
        "ui": {
          "suggests": [
            "До 60 000 ₽, компактный",
            "До 80 000 ₽, большой экран",
            "Помогите определиться с бюджетом"
          ]
        }
      },
      "feedback": {
        "reaction": "like"
      },
      "responseLatencyMs": 1250
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "hasMore": false
  }
}

Элементы в items отсортированы по времени создания от ранних к поздним в пределах страницы:

  • для role: "user" поле content имеет тот же формат, что в запросе /shopping/generate: строка для текста или массив блоков text и image;
  • для role: "assistant" поле response имеет тот же контракт, что ответ /shopping/generate; id сообщения используйте как messageId для /shopping/feedback;
  • у сообщения ассистента рядом с response находится context с контекстом сценария; у пользовательских сообщений этого поля нет;
  • поле feedback.reaction в сообщении ассистента содержит текущую оценку пользователя: like, dislike или null;
  • дополнительно в сообщении ассистента могут присутствовать responseLatencyMs и events;
  • при пагинации page/limit, если pagination.hasMore равно true, запросите следующую страницу, увеличив page на 1.

Поле messageFormat верхнего уровня задаёт формат текста ответов ассистента: plain_text или markdown. Применяйте к восстановленным ответам тот же способ отображения, что и к новым ответам генерации.

Для сообщения с изображением history endpoint возвращает блок image с Base64. Для отображения превью соберите data URL из source.mediaType и source.data, но не передавайте это сообщение повторно в /shopping/generate.

const previewUrl =
  `data:${image.source.mediaType};base64,${image.source.data}`;

Ответ содержит заголовок Cache-Control: no-store. Не сохраняйте историю в общем или публичном кеше на клиентской стороне.

Контекст и восстановление интерфейса

items[].context у ответа ассистента имеет тип ShoppingResponseContext | null, как в контракте генерации. Он относится к этому сообщению и сохраняет ID и версию выбранного тогда плейбука. Обновление или архивация плейбука не меняет контекст старого ответа. Значения сценария в примере условные.

Восстанавливайте UI из последнего сообщения ассистента выбранного диалога. Если его context или scenario равен null, ID неизвестен либо поле отсутствует в старой версии сервера, показывайте обычный чат. Не ищите более ранний ответ с непустым сценарием: так можно вернуть интерфейс уже завершившегося подбора. Для handoff и refuse используйте правила обработки режима.

У старых сообщений без полных корректных метаданных возвращается context: null. При подгрузке более ранних страниц не переключайте UI на их сценарии. Для сопоставления с новым ответом используйте items[].id — это assistantMessageId из генерации.

Загрузка с конца

Для открытия чата с последними сообщениями передайте paginationMode: "backward" без page и before. В примере GUEST_ACCESS_TOKEN содержит полный токен сохранённой сессии, а USER_UID — тот же user.uid или user.custom, с которым зарегистрирована сессия:

curl --request POST \
  --url 'https://shopping-assistant-api.gravityfield.ai/shopping/history' \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer $GUEST_ACCESS_TOKEN" \
  --data '{
    "tenant_id": "demo-section",
    "resourceId": "USER_UID",
    "threadId": "thread-8f2b1c",
    "paginationMode": "backward",
    "limit": 50
  }'

Первый запрос возвращает последние limit сообщений. Допустимы значения от 1 до 100, по умолчанию — 50. Формат items, товарных карточек, изображений, оценок и messageFormat остаётся тем же. Меняется только объект пагинации:

{
  "pagination": {
    "limit": 50,
    "hasMore": true,
    "nextCursor": "opaque-cursor"
  }
}

Это фрагмент ответа; opaque-cursor обозначает курсор из фактического ответа API.

Для загрузки более ранних сообщений отправьте тот же запрос, добавив before со значением pagination.nextCursor:

{
  "tenant_id": "demo-section",
  "resourceId": "USER_UID",
  "threadId": "thread-8f2b1c",
  "paginationMode": "backward",
  "before": "opaque-cursor",
  "limit": 50
}

Внутри каждой страницы сообщения идут от старых к новым. Добавляйте предыдущую страницу сверху, объединяя сообщения по id. Пагинация опирается на время и ID сообщения, поэтому новые сообщения не сдвигают границу уже полученной страницы.

Если более ранних сообщений нет, API возвращает hasMore: false и nextCursor: null. Для пустой страницы items равен []. Не отправляйте before: null — для первого запроса поле нужно пропустить.

Курсор привязан к секции, владельцу истории и диалогу; в гостевом режиме — к текущей сессии. Не разбирайте и не изменяйте его, не используйте курсор списка диалогов вместо курсора сообщений. Неверный курсор возвращает 400 INVALID_CURSOR. Поле page вместе с backward, before без backward или неизвестный paginationMode также приводят к 400.

Для продолжения разговора передайте в генерацию только новое сообщение с прежним threadId. Загруженную историю повторно отправлять не нужно.

Ошибки получения истории

HTTP-статус Причина Что делать клиенту
400 Ошибка в JSON, обязательных полях, диапазоне page/limit или сочетании параметров пагинации; INVALID_CURSOR — неверный курсор. Проверить payload. При ошибке курсора повторить загрузку с конца без before.
401 Для гостевого ресурса токен отсутствует или недействителен. Проверить заголовок и состояние сессии по правилам работы с токеном.
403 IP не разрешён для секции или токен относится к другой секции/ресурсу. Проверить идентификаторы и условия доступа с командой Gravity Field.
404 Диалог не найден, скрыт, принадлежит другой гостевой сессии или связка tenant_id, resourceId и threadId не совпадает. Возможные коды: SHOPPING_HISTORY_NOT_FOUND, DIALOG_NOT_FOUND. Проверить сохранённые идентификаторы и sessionId или обновить список диалогов. Не менять сессию автоматически. Скрытый диалог восстановить нельзя.
500 Историю не удалось загрузить. Показать ошибку и предложить повторить запрос.
503 AUTH_STORAGE_UNAVAILABLE — сервис не смог проверить доступ или загрузить страницу с курсором. Сохранить текущую сессию, показать ошибку и предложить повторить запрос позже.