# Аишка: внешнее приложение на OpenAI

Редакция 15 октября 2026 года. Эти примеры запускаются на сервере интегратора; сайт Аишки не запускает их автоматически. Реальные вызовы OpenAI здесь **не выполнялись**. Проверены локальные схемы, повторяемость команд, синтаксис и серверный MCP по официальному Go SDK 1.3.1. Подключение конкретного аккаунта ChatGPT и Responses API требует отдельной живой приёмки.

Документация: [remote MCP](https://developers.openai.com/api/docs/guides/tools-connectors-mcp), [function calling](https://developers.openai.com/api/docs/guides/function-calling), [Go SDK](https://github.com/modelcontextprotocol/go-sdk/tree/v1.3.1). Версия SDK закреплена ради совместимости с Go 1.24. Поддержка протокола согласуется SDK; приложение не подделывает номер. Проверенный локальный handshake — `2025-06-18`; более новые режимы требуют отдельной проверки.

## Запуск

Скачайте [готовый ZIP](https://eval.su/docs/examples/openai.zip), распакуйте и перейдите в `examples/integrations/openai`. Доступ к GitHub не нужен: адаптер, вспомогательные файлы, шаблон окружения, lockfile и тесты входят в комплект. Соседняя папка `media` содержит загрузчик фотографий.

Установите Node.js 22+ и выполните `npm ci`, либо используйте Python 3.10+ и `pip install -r requirements.txt` в отдельном окружении. Зависимости закреплены. Скопируйте переменные из `.env.example` в окружение своего процесса, не в Git. Файл `.env` сам по себе не загружается: при его использовании запускайте Node с `--env-file=.env`, например `node --env-file=.env remote-mcp.mjs`; Python получает уже экспортированные переменные окружения. Модель выбирается явно через `OPENAI_MODEL`; цены и модель не зашиты в коде. `OPENAI_API_KEY` отправляется только OpenAI, `AISHKA_ACCESS_TOKEN` — только Аишке через защищённый MCP/HTTP. Запуск примеров расходует лимиты настроенного OpenAI аккаунта.

Начните с `AISHKA_ALLOWED_TOOLS=listProfiles,getMyCapabilities`, затем `node remote-mcp.mjs` или `python remote_mcp.py`. Все remote MCP вызовы спрашивают `yes/no` перед передачей данных/действием. Токен Аишки передаётся заново при каждом продолжении; скрытое автоматическое обновление токена не выполняется. Обновляйте его своим OAuth-клиентом, сохраняя тот же grant.

Для черновика явно добавьте `portfolio_save,portfolio_get,aishka_commit_command,aishka_get_command` в `AISHKA_ALLOWED_TOOLS`. `portfolio_save` подготавливает команду; `aishka_commit_command` выполняет её с выданным сервером постоянным UUID. Повтор commit использует тот же UUID. Подготовка и OpenAI approval не заменяют модерацию/подтверждение Аишки. После записи вызовите `portfolio_get`; `pending` означает «отправлено на проверку», а не публикацию.

Значимые действия — отправка сообщения/отклика, принятие предложения, завершение сделки, отправка публикации на проверку, удаление и другие изменения публичных данных — возвращают `approvalRequired:true` и `approvalUrl`. Пользователь открывает ссылку в Аишке, проверяет неизменяемые аргументы и подтверждает их после свежего входа. Модель, OAuth-токен и ключ API не могут подтвердить действие за пользователя. Затем тот же инициатор вызывает commit; срок подтверждения 10 минут, срок команды 30 минут. Повтор успешной команды сохраняет прежний эффект; ревизия, права и удаление перепроверяются.

HTTP-интегратор использует `POST /api/v1/integration-commands/prepare` с `{operationId, arguments}` без собственного `requestUuid`, сохраняет полученный `commandUuid`, после подтверждения вызывает `POST /api/v1/integration-commands/{uuid}/commit`. `GET` того же адреса с bearer-токеном возвращает статус. Прямое значимое изменение через API без такого подтверждения возвращает `approval_required`.

Пример function calling: `AISHKA_ALLOWED_OPERATIONS=listProfiles,getMyCapabilities,portfolio.save,portfolio.get node function-calling.mjs 'Подготовь черновик портфолио по моему описанию'`. Схемы читаются из развернутого registry; пример включает только проверенный allowlist. Для `portfolio.save` предусмотрен узкий контракт создания текстового черновика. Полный редактор блоков, файлы и правки существующих записей используйте через HTTP/MCP. Произвольный URL, неизвестная операция, частичные аргументы и незавершённый ответ не исполняются.

Конвертер strict schemas не меняет исходный MCP/HTTP контракт. Необязательные поля становятся nullable; для PATCH автоматическое удаление null запрещено. Неограниченный объект `additionalProperties:true` требует отдельного проверенного адаптера и не выдаётся за строгую схему.

Для фотографий и оформленных публикаций используйте [исполняемый пример загрузки и черновика](https://eval.su/docs/examples/media.md). Он показывает всю цепочку `prepare → PUT bytes → complete → ready → content.assets → save → readback`, обложку, подписи, alt, галерею и форматирование. Байты выбранных файлов обрабатывает ваше приложение; модель получает готовые UUID и разрешённую структуру материала. Локальный JSON-просмотр не делает запросов; сохранение включается явно и не отправляет публикацию на проверку.

## Восстановление

Состояние записывается в файл с правами `0600`. Не передавайте его модели целиком: он может содержать приватный диалог. Новое намерение пользователя — новый файл. Повтор после ошибки — тот же файл. Function adapter сохраняет UUID **до** запроса Аишке, повторяет тот же запрос после неизвестного исхода и подтверждает результат повторным чтением. Он не создаёт новый UUID по новому `tool_call_id`.

Remote MCP пример использует сохранённые Responses для продолжения одобрений; `store:true` здесь сознательно. Если сам запрос OpenAI оборвался с неизвестным исходом, пример останавливается и оставляет `uncertain:true`: сначала проверьте известные UUID команд через Аишку. Не очищайте этот флаг для слепого повторного создания. Ответ модели без успешного результата Аишки не подтверждает запись. Отказ и лимит числа шагов не запускают дальнейшие действия.

## Webhook и фоновый обработчик

Внешний получатель сначала создаётся в Аишке в состоянии `pending_approval`. Владелец в браузере проверяет точный HTTPS адрес и ревизию, подтверждает его; worker посылает минимальный `webhook.test`. Только успешный 2xx активирует подписку. Подпись HMAC хранится зашифрованной отдельно от ключа API; новый секрет показывается владельцу один раз. Ротация допускает 24 часа перекрытия двух подписей.

`webhook-receiver.mjs`, `webhook_receiver.py`, `webhook_receiver.go` проверяют исходные байты Standard Webhooks и окно 5 минут. После проверки атомарно положите `webhook-id` в собственную долговременную очередь с уникальным ограничением; только после commit ответьте 2xx. Повтор ID подтверждает исходный приём, не создаёт новую генерацию. Обрабатывайте очередь отдельно, получайте актуальную revision и разрешённый контекст через API; не передавайте модели полный приватный объект по умолчанию.

Фоновые правила браузера сохраняют отдельное внутреннее разрешение и продолжаются после завершения браузерной сессии. Актуальные права пользователя и членство в компании проверяются при каждом исполнении. Правило OAuth связано с действующим grant: обновление токена не мешает работе, отзыв grant прекращает её. Правило ключа API прекращается при отзыве или истечении ключа. Само правило/вебхук можно отключить; внутреннее разрешение не имеет токена для внешнего входа.

Ограничения API/MCP: до 120 чтений и 30 изменений в минуту на ключ; одновременно действует общий лимит 240/60 на пользователя и профиль. Правила имеют отдельный суточный лимит до 1000, ограничение цепочки событий и запрет произвольного кода. Встроенное выполнение команд разрешено только для сохранения черновиков.

Не вызывайте OpenAI напрямую из HTTP webhook и не доверяйте порядку событий. Для каждого действия сохраняйте command UUID и бюджет/число попыток. Текст внутри публикации или файла не меняет адрес получателя, scopes, allowlist и политику подтверждений. Сбой OpenAI оставляет очередь для контролируемого продолжения. Сами примеры не отправляют сообщения пользователям и не запускают генерацию по входящим событиям.

## Проверки без внешних вызовов

`npm test` проверяет strict conversion, null/PATCH, постоянную идентичность команды и подписи. `python -m py_compile remote_mcp.py webhook_receiver.py` проверяет синтаксис. Тесты backend в `internal/integrations` и `internal/mcpbridge` используют отдельную PostgreSQL 18 базу и реальный HTTP transport SDK; адреса произвольных клиентов при тестах не вызываются.
