Под капотом AI Free: три разных способа работы с веб-провайдерами, единый агентный рантайм, память, skills и много кода восстановления после сбоев.
У веб-версий больших языковых моделей есть странное свойство: для человека они доступны в один клик, а для программы между «отправить вопрос» и «получить ответ» внезапно вырастает целая инфраструктура.
Одному сервису нужен proof-of-work. Другой подписывает каждый запрос кодом из своего фронтенда. Третий формирует служебные токены глубоко внутри React-приложения. Сессии истекают, вкладки закрываются, контекст страницы исчезает при навигации, а интерфейс может измениться без предупреждения.
При этом мне хотелось получить не ещё один агрегатор чатов, а локальный инструмент разработчика: чтобы модель могла читать файлы проекта, предлагать и вносить изменения, запускать разрешённые команды, работать в браузере и помнить результаты прошлых задач.
Так появился AI Free — open-source приложение, в котором веб-сессия становится транспортом для модели, а поверх неё работает общий агентный рантайм.
Проект я разрабатываю один: от интеграций с провайдерами и агентного рантайма до desktop-интерфейса, расширения VS Code, тестов и релизов. Поэтому здесь особенно важны простые границы между модулями — без них соло-разработка такого количества интеграций быстро стала бы неуправляемой.
В этой статье я разберу не интерфейс приложения, а главные инженерные решения проекта: почему для трёх провайдеров понадобились три разных транспорта, где проходит граница между провайдером и агентом и почему восстановление после ошибок оказалось важнее самого первого успешного запроса.
Важное уточнение. Проект использует пользовательские сессии веб-сервисов и зависит от их текущих интерфейсов. Это не официальный API и не обещание вечной совместимости. Пользователь сам отвечает за соблюдение правил выбранного сервиса, а для коммерческих и критичных сценариев разумнее использовать официальные API.
Почему нельзя сделать один универсальный клиент
В начале кажется, что задача сводится к обычному HTTP:
-
авторизоваться;
-
отправить текст;
-
прочитать поток ответа;
-
сохранить идентификатор диалога.
Но браузерные приложения давно не являются тонкими оболочками над публичным endpoint. Часть протокола живёт в JavaScript и WASM, часть привязана к cookies и состоянию вкладки, а часть вообще запускается только штатным обработчиком интерфейса.
В AI Free три браузерных провайдера, и у каждого свой транспорт:
|
Провайдер |
Что мешает обычному HTTP-клиенту |
Как работает интеграция |
|---|---|---|
|
DeepSeek |
proof-of-work challenge |
локальный WASM-солвер и прямой запрос |
|
Qwen |
динамическая подпись запроса |
|
|
ChatGPT |
служебные токены формирует штатный фронтенд |
отправка через реальный UI и чтение результата |
Это важное архитектурное решение: я не пытался спрятать различия под одним «умным» сетевым модулем. Наоборот, каждый провайдер получил собственную реализацию транспорта, а унификация начинается уровнем выше.
DeepSeek: proof-of-work без постоянного браузера
DeepSeek перед выполнением запроса выдаёт challenge. Клиент решает вычислительную задачу и прикладывает результат к запросу — иначе completion не начинается.
Сам алгоритм реализован в WASM, который использует веб-приложение DeepSeek. В клиенте я загружаю этот бинарный модуль через WebAssembly.instantiate и вызываю его через небольшую обёртку. На вход она передаёт параметры challenge, на выходе получает решение в формате, ожидаемом сервером.
Упрощённо последовательность выглядит так:
получить challenge ↓загрузить актуальный WASM ↓вычислить proof-of-work локально ↓отправить completion с результатом ↓прочитать SSE-поток
Браузер здесь нужен прежде всего для первоначальной авторизации и обновления сессии. Сам рабочий запрос после этого можно выполнять из Node.js. Из трёх интеграций эта ближе всего к традиционному API-клиенту.
У подхода есть понятная цена: WASM-бинарник может измениться. Пока сохраняется вызываемый контракт, клиент подхватывает актуальную версию. Если провайдер поменяет сигнатуру или весь протокол, адаптер придётся обновлять.
Qwen: авторизованная страница как транспорт
У Qwen другая модель. Запрос снабжается динамической подписью, зависящей от его параметров и состояния сессии. Сохранить один удачный заголовок и повторно использовать его не получится.
Можно было бы воспроизвести алгоритм подписи в Node.js, но это означало бы постоянно догонять изменения чужого фронтенда. Я выбрал более устойчивую границу ответственности: пусть запрос формирует тот код, который уже умеет это делать, — сам веб-клиент Qwen.
AI Free держит persistent-контекст Chromium с авторизованной страницей chat.qwen.ai. Запрос выполняется через page.evaluate(() => fetch(...)). Для страницы это обычный вызов из её собственного окружения: работают cookies, origin и штатная логика подготовки запроса.
В результате браузер играет необычную роль. Он не рисует интерфейс для пользователя и не управляется кликами — он служит сессионным транспортом.
AI Free │ url + body ▼page.evaluate(fetch) │ cookies + логика веб-клиента ▼Qwen
Одна страница при этом не должна получать несколько конфликтующих операций одновременно. Поэтому поверх транспорта появился небольшой пул воркеров:
-
несколько страниц внутри одного persistent-контекста;
-
закрепление
chatIdза воркером через хеш; -
последовательная очередь запросов для каждой страницы;
-
ленивый запуск при первом обращении;
-
корректное закрытие вместе с приложением.
Хеширование по идентификатору чата сохраняет порядок сообщений внутри диалога, но позволяет разным диалогам работать параллельно.
ChatGPT: когда API надёжнее не трогать
С ChatGPT подход Qwen не сработал. Ручной fetch из контекста страницы не проходил тот же путь, что штатная отправка сообщения: нужные служебные значения формировались внутри логики приложения.
После нескольких попыток воспроизвести сетевой запрос я поменял уровень интеграции. Вместо обращения к внутреннему endpoint клиент работает с реальным интерфейсом:
-
открывает авторизованную сессию
chatgpt.com; -
находит composer;
-
вводит сообщение и запускает штатную отправку;
-
ждёт завершения ответа;
-
получает markdown из сохранённого диалога или, при необходимости, читает DOM.
На первый взгляд UI-автоматизация выглядит менее изящно, чем HTTP. Но в данном случае она лучше соответствует реальной границе системы. Штатный фронтенд сам выполняет все внутренние шаги, а клиенту не нужно копировать постоянно меняющийся протокол.
Конечно, появляется другая хрупкость: селекторы и структура страницы могут измениться. Поэтому поиск элементов строится с несколькими вариантами, а ошибки интерфейса отделены от транспортных. Это не делает интеграцию неуязвимой, но позволяет понять, что именно сломалось, и восстановить только нужный слой.
Если сервис запрашивает дополнительное подтверждение входа, AI Free не пытается полностью исключить человека из процесса: приложение может открыть видимое окно и дать пользователю завершить проверку вручную. После этого сессия продолжает работать локально.
Почему Patchright, но с запасным вариантом
Для браузерного слоя проект использует Patchright — совместимую с Playwright реализацию, уменьшающую часть характерных следов автоматизации. Это полезно для фоновых сессий, однако я не считаю такой инструмент гарантией прохождения любой защиты: антибот-системы меняются независимо от клиента.
Поэтому в архитектуре есть fallback на обычный Playwright. Он может потребовать видимого окна или ручного подтверждения, зато приложение не становится полностью неработоспособным из-за проблем одного движка.
Здесь проявился общий принцип проекта: деградация функции лучше, чем падение всего приложения.
Где заканчивается провайдер и начинается агент
После подключения моделей появилась следующая проблема: их клиенты говорят на разных языках даже на уровне внутренних объектов.
Условно один ожидает:
{ sessionId, parentMessageId, modelType, thinkingEnabled, searchEnabled, prompt}
Другой работает с chatId, parentId, thinking и search, третий — с conversationId и изображениями.
Агентный цикл не должен знать обо всех этих вариантах. Иначе каждое добавление провайдера размазывает условные операторы по рантайму и усложняет тестирование.
Я оставил провайдерские клиенты независимыми и добавил тонкие адаптеры. Их задача скучна и поэтому полезна: переименовать поля, привести ответ к общей форме и передать управление агенту.
Поверх адаптеров работает Agent Orchestrator. Перед запуском задачи он:
-
классифицирует задачу;
-
подбирает подходящий skill;
-
извлекает релевантные записи из локальной памяти;
-
формирует список разрешённых инструментов;
-
собирает системный контекст;
-
запускает общий цикл выполнения.
В итоге агентный рантайм не знает, был ли ответ получен через прямой запрос, браузерный fetch или форму на странице. Для него провайдер — источник следующего сообщения модели.
Браузер уже не транспорт, а инструмент
До этого браузер помогал общаться с самой моделью. Но агенту он нужен и для другой работы: открыть документацию, собрать данные со страницы или заполнить форму.
Эти роли важно не смешивать. Фоновая вкладка провайдера отвечает за сессию модели. Отдельный браузерный сервис отвечает за действия по задаче пользователя.
Браузерному агенту доступны операции вроде:
-
browser_snapshot; -
browser_navigate; -
browser_click; -
browser_type; -
browser_scroll; -
browser_key.
Вместо передачи модели полного HTML сервис строит компактный снимок: дерево элементов, видимый текст и стабильные ссылки вида e1, e2, e3. Агент выбирает действие по ссылке, выполняет его и получает новый снимок.
Такой цикл проще контролировать:
снимок страницы → решение → действие → новый снимок → проверка
Пользователь при этом может наблюдать за работой через live-панель. Это особенно полезно для задач, где автоматизация дошла до неожиданного состояния: вместо «агент завис» видно, какую страницу он открыл и чего ждёт.
Память и skills: контекст должен собираться до запуска
Даже хороший агент мало полезен, если каждую задачу начинает как первую. В AI Free память хранится локально и объединяет несколько способов поиска: полнотекстовый индекс, ключевые слова, векторное сходство и граф связей между задачами, файлами, ошибками и исправлениями.
После выполнения из журнала инструментов можно извлечь практический опыт: какие файлы менялись, что сработало, на какой ошибке сломалась первая попытка. При похожем запросе оркестратор добавляет найденные фрагменты в контекст.
Skills решают соседнюю задачу. Это набор инструкций и разрешённых инструментов для определённого типа работы — например, исправления ошибки или ревью кода. Skill можно выбрать явно либо подобрать по смыслу задачи.
Главное здесь не формат файлов, а момент применения. Память, skill и permissions собираются до запуска агентного цикла. Благодаря этому модель сразу понимает цель и границы действий, а не узнаёт их по ходу работы.
Первый успешный запрос — только начало
Самая обманчивая стадия такого проекта — демо. Один запрос проходит, ответ появляется на экране, и кажется, что основная работа закончена.
На практике браузерный транспорт регулярно сталкивается с ожидаемыми сбоями:
-
навигация уничтожила execution context;
-
вкладка или весь браузер закрылись;
-
сеть оборвалась во время потока;
-
cookies истекли;
-
страница ещё не готова принимать ввод;
-
провайдер вернул изменившийся формат ошибки.
Я разделил восстановление по уровням.
На уровне страницы транзиентная ошибка приводит к пересозданию воркера и ограниченному повтору операции.
На уровне браузера закрытый процесс сбрасывает singleton, чтобы следующий запрос поднял новый экземпляр.
На уровне провайдера транспортная ошибка может инициировать полную переинициализацию сессии.
На уровне авторизации истёкшая сессия переводит пользователя в повторный вход, после которого обновлённые cookies загружаются до навигации.
Повторы всегда ограничены. Бесконечный retry превращает понятную ошибку в вечное зависание и усложняет диагностику.
Из этого опыта я вынес простой критерий готовности: интеграция работает не тогда, когда она однажды получила ответ, а тогда, когда после закрытой вкладки, сетевого сбоя и истёкшей сессии пользователь понимает, что произошло и что делать дальше.
Что получилось
Сейчас AI Free объединяет несколько интерфейсов в одном локальном приложении:
-
desktop-окно и CLI;
-
OpenAI- и Anthropic-совместимые локальные API;
-
расширение для VS Code и IDE-интеграции;
-
код-агент с контролируемым доступом к workspace;
-
браузерный агент;
-
локальную память и skills.
Расширение AI Free Chat & Agent для VS Code на момент подготовки статьи установили 938 раз. Это именно число установок из Visual Studio Marketplace, а не количество активных пользователей, но для независимого проекта, который я развиваю один, первые сотни установок стали важным подтверждением, что инструмент нужен не только мне.
DeepSeek, Qwen и ChatGPT используют описанные выше браузерные интеграции. В проекте также есть EconomyOS, но это отдельный случай: он подключается через официальный OpenAI-совместимый API с ключом самого пользователя и не участвует в браузерной схеме.
Код проекта открыт: AI Free на GitHub.
Быстрый старт:
git clone https://github.com/Staks-sor/ai-free.gitcd ai-freenpm installnpm start
Понадобятся Node.js 18+ и Chromium; браузерные бинарники устанавливаются вместе с зависимостями. Авторизация каждого провайдера выполняется локально, а сессии хранятся на машине пользователя.
За обновлениями проекта и заметками о разработке можно следить в Telegram-канале «Будни программиста».
Вместо вывода
В начале проекта мне казалось, что самая интересная часть — научиться получать ответы без отдельного API-ключа. Оказалось, это лишь транспортная задача.
Настоящая архитектура начинается после неё: когда три несовместимых механизма нужно привести к одному интерфейсу, отделить сессию модели от браузера-инструмента, ограничить действия агента, добавить память и пережить неизбежные сбои внешних систем.
Главная идея AI Free в итоге сформулировалась так: браузер — не обязательно окно, в которое смотрит человек. Он может быть сессионным транспортом, средой выполнения и руками агента. Но полезным этот подход становится только тогда, когда поверх эффектного трюка построены адаптеры, наблюдаемость и восстановление.
Именно на это в проекте ушло больше всего времени.
ссылка на оригинал статьи https://habr.com/ru/articles/1061372/