Документация
Создание клиента потокового помощника
Откройте ход, визуализируйте его реальные события, ответьте на ожидающие запросы и восстановите разговор после разрыва соединения.
Автоматически переведено с английского. Смотреть оригинал на английском
Подключитесь к работающей сайдкаре
Клиент-помощник по потоковой передаче подключается к той же локальной сайдкаре, что и рабочий стол. Получите назначенный URL-адрес обратной связи и текущий токен носителя приложения из соединения, с которым вы интегрируетесь. Порт, сохраненный со вчерашнего запуска, не является постоянным адресом: сайдкар запрашивает временный порт при каждом запуске. Токен сеанса учетной записи, токен сопряжения компаньона и токен Action-Bridge с ограниченной областью действия имеют разные цели и не могут заменить носитель приложения на этом маршруте чата.
Прежде чем отправлять ход, прочтите GET /api/ai/status, /api/ai/providersи необязательно /api/ai/models?configuredOnly=true. Настроенные учетные данные означают, что ключ присутствует; это не доказывает, что поставщик примет запрос или что конечная точка поддерживает выбранную модель. Хост по умолчанию — встроенный nikcli. Прямой режим может передавать в потоковом режиме ответ модели, но не обеспечивает постоянный инструментальный цикл встроенного хоста или специальный планировщик.
В этом руководстве используются CADCAMFUN_URL и CADCAMFUN_TOKEN как переменные оболочки клиента. Предоставьте их через существующую обработку соединений. См. Справочник по API ИИ точные схемы и Сайдкар для поведения при запуске. В примерах не устанавливаются учетные данные и не выбирают модель от вашего имени.
Выберите проект, чертеж и идентификатор сеанса
Клиент может предоставить sessionId в первый ход и повторно использовать его в последующих ходах. Служба сопоставляет этот идентификатор клиента с фактическим сеансом хоста и проверяет привязку перед каждым запросом. Сохраняйте идентификатор события/сеанса как состояние разговора, вместо того, чтобы создавать новый идентификатор каждый раз при повторном подключении пользовательского интерфейса.
Настоящий projectId дает очередь доступ к разрешенным ресурсам этого проекта. Дополнительный drawingId должен принадлежать этому проекту; он привязывает инструменты поворота к этому рисунку. Чертеж не может быть назван без его проекта. Сеанс, уже принадлежащий другой учетной записи или проекту, отклоняется. Это проверки допуска, прежде чем модель сможет использовать инструменты проекта, а не подсказки в системной подсказке.
Для общей объяснительной беседы проект и рисунок опускаем. Приложение использует контекст по умолчанию. Для работы с геометрией передавайте фактически выбранные идентификаторы проекта/чертежа через чат, восстановление состояния, очередь, отмену и запросы управления. Переключение учетной записи не должно автоматически перенимать активный сеанс предыдущей учетной записи.
Отправить потоковую передачу
POST /api/ai/chat/stream возвращает тело SSE. Для этого требуется непустое сообщение, хотя транспортная схема делает несколько полей необязательными, чтобы обработчики могли возвращать читаемые сообщения проверки. Следующий общий вопрос отключает внедрение сохраненного контекста, что делает его пригодным для проверки транспорта независимо от геометрии проекта.
curl --no-buffer --fail-with-body \
-H "Authorization: Bearer $CADCAMFUN_TOKEN" \
-H "Content-Type: application/json" \
--data '{"message":"Explain the difference between G0 and G1.","sessionId":"inspection-demo","includeContext":false,"mode":"gcode"}' \
"$CADCAMFUN_URL/api/ai/chat/stream" Добавлять projectId и drawingId работать над конкретным рисунком. mode принимает общие значения, CAD, CAM, Gcode или траекторию инструмента. А model селектор может содержать поставщика, модель, уровень и вариант; уровни быстрые, сбалансированные или лучшие. Найдите идентификаторы поставщиков/моделей из текущего каталога вместо того, чтобы жестко запрограммировать предположение о том, какой поставщик доступен.
permissionMode принимает «Одобрить», «Спросить» или «Авто». Группы деструктивных инструментов сохраняют одобрение людей в любом режиме. agent поле выбирает настроенную роль помощника; читать GET /api/ai/agents вместо того, чтобы угадывать идентификаторы ролей.
Чтение полных кадров данных SSE
В маршруте используется режим данных SSE Effect: каждая полезная нагрузка данных SSE представляет собой запись события JSON с type дискриминатор. Сетевой фрагмент не является границей события. JSON, окончания строк и символы UTF-8 можно разделить между операциями чтения. Накапливайте декодированный текст и анализируйте только полные кадры, объединяя несколько строк данных, если они есть.
async function* events(response: Response): AsyncGenerator<Record<string, unknown>> {
if (!response.ok) throw new Error(await response.text())
if (!response.body) throw new Error("The response has no stream")
const reader = response.body.getReader()
const decoder = new TextDecoder()
let pending = ""
try {
while (true) {
const next = await reader.read()
pending += decoder.decode(next.value, { stream: !next.done })
let boundary: RegExpExecArray | null
while ((boundary = /\r?\n\r?\n/.exec(pending)) !== null) {
const frame = pending.slice(0, boundary.index)
pending = pending.slice(boundary.index + boundary[0].length)
const data = frame.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).replace(/^ /, "")).join("\n")
if (!data) continue
const event: unknown = JSON.parse(data)
if (!event || typeof event !== "object" || !("type" in event)) {
throw new Error("Invalid assistant event")
}
yield event as Record<string, unknown>
}
if (next.done) break
}
} finally {
await reader.cancel().catch(() => {})
reader.releaseLock()
}
} Этот читатель демонстрирует кадрирование; производственный клиент также должен проверять каждое событие на соответствие общему контракту или использовать типизированный сгенерированный потоковый клиент. Он намеренно игнорирует поля комментариев/управления SSE, поскольку состояние приложения этой конечной точки находится в записях данных JSON. Освобождение клиентского потока — это очистка транспорта, а не явный запрос на прекращение работы на стороне сервера.
Рендеринг прогресса без дублирования контента
Запустите выборку с тем же носителем и телом JSON, показанными выше, затем выполните итерацию средства чтения. Добавить delta.text к видимому тексту ответа и маршруту reasoning.text на свой взгляд. А done событие содержит окончательный текст ответа; замените или согласуйте накопленный ответ с этим окончательным значением вместо того, чтобы снова добавлять весь ответ.
| События | Государство для поддержания |
|---|---|
turn, session_status, step | Состояние занятости/повторной попытки и текущий шаг; за завершением шага может последовать еще один шаг в том же ходу. |
tool_call, tool_progress, tool_result | Запись по callId с именем, вводом, ходом аргумента, выводом и успехом; агент идентифицирует специалиста при его присутствии. |
tool_diagnostic | Выводы аргумента/выполнения/частичной проверки/проверки, серьезность, текст восстановления и необязательная мутацияApplied. |
todo, goal | Замените текущий снимок плана/цели, а не добавляйте повторяющиеся копии. |
retry, compaction, compacted | Объясните продолжение работы, сохраняя при этом текущий текст разговора и идентификатор сеанса. |
heartbeat | Нет видимого контента; сервер отправляет один примерно каждые пять секунд, пока исходный поток остается активным. |
Сопоставьте вызовы инструментов и результаты по callId, а не их позиция в массиве. Неисправный инструмент может возвращать полезную диагностику, пока помощник продолжает работу. Ошибка поставщика после успешного использования инструмента редактирования не отменяет изменение геометрии.
Отвечать на разрешения и вопросы
A permission_request включает sessionId, requestId, разрешение и шаблоны. Предъявите запрос человеку и отправьте выбранное решение на POST /api/ai/permission/reply. Эквивалентное действие по управлению сеансом: reply_permission. Сервер проверяет, что запрос все еще находится на рассмотрении в диалоге или в разрешенном дочернем сеансе.
{
"sessionId": "inspection-demo",
"requestId": "request-id-from-the-event",
"decision": "once"
} Решение — один раз, всегда или отклонить. Это решение о разрешении сервера, отличное от аргумента действия, такого как подтверждение. Ожидаемый question_request содержит упорядоченный массив вопросов. Ответить через /api/ai/sessions/control с действием answer_question, идентификатор запроса и один массив ответов на вопрос; отклонить это с reject_question.
{
"sessionId": "inspection-demo",
"action": "answer_question",
"requestId": "question-id-from-the-event",
"answers": [["The selected option label"]]
} Каждый ответ должен быть непустым. Вопрос с одним выбором отклоняет несколько ответов, а параметр custom=false допускает только перечисленные метки. Сохраняйте точные метки и порядок вопросов. Если сервер сообщает, что запрос больше не находится на рассмотрении, обновите элементы управления вместо повторной публикации старого ответа.
Ставьте в очередь, управляйте и явно останавливайте работу
Использовать POST /api/ai/chat/queue за сообщение, отправленное во время работы очереди. Он принимает идентификатор сеанса, сообщение, необязательный идентификатор проекта и доставку. queue используется по умолчанию и ожидает следующего безопасного шага; steer просит хоста включить его как можно скорее. А user_message событие сигнализирует о том, что сообщение вошло в разговор.
Использовать POST /api/ai/chat/abort с идентификацией разговора, когда человек нажимает «Стоп». Это останавливает ход и присоединяет связанных с ним рабочих. Не реализуйте Stop исключительно путем отмены выборки: потеря HTTP-потока — это не та же операция. Затем сеанс может принять еще один ход без создания нового идентификатора проекта.
POST /api/ai/sessions/remove удаляет сеанс хоста после отмены и присоединения к его дочерним элементам. Он возвращает удаленный флаг. Удаление беседы не является откатом изменений проекта, внесенных ее инструментами; при необходимости просмотрите или восстановите их через API проекта/истории.
Восстановление после потери потока
Когда программа чтения завершается без финального события, считайте завершение неизвестным. Вызов POST /api/ai/sessions/state с тем же идентификатором сеанса и идентификатором проекта. Ответ содержит занято и текст, а также дополнительные восстановленные события инструмента/задачи/диагностики, использование и задачи. Восстановите пользовательский интерфейс из этого снимка; воспроизведение восстановленных событий инструмента не приводит к их повторному выполнению.
Затем запросите /api/ai/sessions/control со статусом действия для проверки ожидающих разрешений/вопросов, рабочих задач, целей и контекстной политики. Занятая очередь в ожидании вопроса без ответа требует ответа, а не второй копии исходного запроса. Сеанс, о котором сообщается, что он простаивает, с накопленным текстом, может отображаться как восстановленный вывод, даже если исходное TCP-соединение исчезло.
Следует различать сбой сети, событие типизированной ошибки поставщика/конфигурации, явное прерывание и удаление сеанса. Повторите запрос модели только после проверки того, что на самом деле сделал первый ход. В частности, читать текущий чертеж после частичной диагностики мутации или прерванного редактирования рабочим.
Внутри сервера разорванное соединение модели возобновляется до пяти раз с задержкой в 2 секунды, которая удваивается до 30 секунд. Клиент, повторно подключающийся к потоку, все равно должен читать состояние сеанса, поскольку эти возобновления могут завершить работу, которую клиент не видел в потоке.
Контекст, вложения и сжатие
includeContext: false отключает получение сохраненного контекста; явный контекстный текст все еще может быть предоставлен для этого хода. Обычная сборка заказывает постоянные требования, текущие наблюдения, снимки пользовательского интерфейса, память и вспомогательные ссылки, дедуплицирует их и применяет 24,000-символьный совокупный бюджет. Сообщается о пропущенных/усеченных блоках; этот бюджет не является окном токена провайдера.
Вложения включают имя, необязательный MIME, данные и необязательную текстовую/базовую64 кодировку. Их не более пяти на запрос. Изображения ограничены 5 МБ каждое, PDF-файлы — 10 МБ, а текст — 2,000,000 символов; короткий текст длиной до 6,000 символов также отображается внутри строки. Текст, сохраняемый для инструментов, хранится в памяти проекта, поэтому повторное подключение отличается от перезапуска службы. Неподдерживаемые файлы отклоняются с указанием причин.
Контекст управляющего действия запрашивает подробную разбивку токенов; обычный опрос статуса позволяет избежать работы токенизатора. Action Compact просит ведущего обобщить старый материал разговора. Консолидация памяти — это отдельная операция хоста. Сохраняйте исходные запросы на чертежи и производство через собственные API, а не полагайтесь на сжатый диалог для воспроизведения точной геометрии.
Отделяйте транспортные ошибки от результатов домена
HTTP 401 означает, что запрос не прошел авторизацию носителя. 400 перед потоковой передачей обычно означает пустое сообщение, недоступный идентификатор сеанса, недопустимую комбинацию проекта/чертежа или неправильно сформированное тело. После установления потока обработчик может выдать событие ошибки с конфигурацией, поставщиком, прерванной или внутренней классификацией; обработать это событие, даже если статус ответа был 200.
Результат инструмента с ok=false — это результат инструмента, а не обязательно неудачный поток. Покажите его выходные данные и диагностику, чтобы помощник или человек мог восстановиться. Рабочая очередь также может быть полной: планировщик допускает четыре активных и шестнадцать ожидающих задач. Прерванное чтение может быть восстановлено за ограниченное количество попыток; работа по редактированию рассматривается как неопределенная, а не воспроизводится автоматически.
Прочтите исполняемые контракты в packages/api/src/groups/ai.ts и ai-events.ts; прием и проводное поведение в apps/sidecar/src/http/ai.ts; отображение сеансов в packages/ai/src/service.ts; проверка вопроса в nikcli/session-controls.ts; время сердцебиения в heartbeat.ts. Эти источники определяют, какой результат должен представить клиент, независимо от прозы ассистента.
Ссылка на событие
Каждый SSE data: фрейм — это один объект JSON с type и sessionId. Разбирать только полные кадры; кадр может прийти разделенным на сетевые фрагменты.
| тип | Ключевые поля | Как справиться |
|---|---|---|
turn | phase: start, recovering, continuing, finish; необязательный reason | Показать состояние прогресса. recovering означает, что соединение с моделью возобновляется. |
session_status | status: busy, idle, retry | Включите или отключите кнопку отправки и очередь. |
user_message | сохраненное сообщение пользователя | Согласуйте оптимистичный интерфейс. |
delta | text | Добавьте к видимому ответу. |
reasoning | текст рассуждения | Необязательный; показать в складной области. |
step, todo, goal | планировать прогресс | Отобразите список задач или план. |
tool_call | callId, инструмент, аргументы, необязательно agent | Покажите, какой инструмент работает и для какого специалиста. |
tool_progress | callId, прогресс | Обновите долго работающий инструмент. |
tool_result | callId, результат | Показать результат; сопоставить это с callId. |
tool_diagnostic | детали, включая mutationApplied | Решите, изменилась ли модель. Прочтите его, прежде чем повторять редактирование. |
permission_request / permission_resolved / permission_blocked | requestId; отвечать once, always, reject | Спросите пользователя, затем POST /api/ai/permission/reply. |
question_request / question_resolved | данные вопроса | Задайте вопрос и отправьте ответ. |
context_snapshot, compaction, compacted | состояние контекста | Сообщите пользователю, что была обобщена старая история. |
retry | attempt, message | Показать временное уведомление; не отправляйте приглашение повторно. |
done | text, необязательный usage, необязательный recovered | Итоговый текст хода. Замените им накопившиеся дельты. |
error | code: provider, config, aborted, internal; message | Завершите ход в пользовательском интерфейсе сообщением; config обычно означает отсутствие учетных данных. |
heartbeat | никто | Поддержание активности примерно каждые 5 секунд; игнорировать. |
Полноценный потоковый клиент
const base = process.env.CADCAMFUN_URL! // e.g. http://127.0.0.1:53211
const token = process.env.CADCAMFUN_TOKEN!
const response = await fetch(`${base}/api/ai/chat/stream`, {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
body: JSON.stringify({ projectId: "p1", message: "Summarise this project", mode: "general" }),
})
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`)
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
let buffer = "", answer = ""
for (;;) {
const { value, done } = await reader.read()
if (done) break
buffer += value
let end
while ((end = buffer.indexOf("\n\n")) >= 0) { // one SSE frame
const frame = buffer.slice(0, end); buffer = buffer.slice(end + 2)
const data = frame.split("\n").filter((l) => l.startsWith("data:"))
.map((l) => l.slice(5).trimStart()).join("\n")
if (!data) continue
const event = JSON.parse(data)
switch (event.type) {
case "delta": answer += event.text; break
case "permission_request": /* ask, then POST /api/ai/permission/reply */ break
case "done": answer = event.text; console.log(answer); break
case "error": throw new Error(`${event.code}: ${event.message}`)
}
}
} Если соединение прервется, не отправляйте сообщение снова. Прочитайте сессию с POST /api/ai/sessions/state, поскольку сервер мог возобновить соединение модели и завершить ход без вас. Использовать /api/ai/chat/abort намеренно остановить поворот и /api/ai/chat/queue чтобы добавить сообщение, пока сеанс занят.
Как выглядит поток без провайдера
Если поставщик искусственного интеллекта не настроен, поток по-прежнему открывается и отвечает типизированной ошибкой, за которой следует контрольный сигнал. Снято со свежей сайдкара:
data: {"type":"error","sessionId":"","code":"config","message":"No AI provider is configured. Add an API key in the AI settings."}
data: {"type":"heartbeat"} - Статус HTTP по-прежнему 200, а тип контента
text/event-stream: проблемы конфигурации — это результаты предметной области, представленные в виде событий. sessionIdпуст, поскольку сеанс не был создан.heartbeatфреймы поддерживают соединение; игнорируйте их при рендеринге и не считайте прогрессом.- Каждый кадр заканчивается пустой строкой. Разбор путем разделения на пустые строки и объединения
data:строки кадра, как это делает итератор SDK.
Протестируйте свой клиент без модели
Направьте клиента на вспомогательную машину без учетных данных: он бесплатно проверяет полный путь SSE и ветвь ошибок.