Перейти к основному содержанию
gproxy-sdk — крейт, на котором построен сам шлюз GPROXY. Подключайте его напрямую, если программе на Rust нужно отправлять запросы через пул учётных данных с автоматическим обновлением, переключением между провайдерами и учётом расходов, но без HTTP-сервера. Дескриптору нужна база данных: состояние учётных данных нельзя хранить только в памяти. Например, Claude меняет refresh token при каждом обновлении, и дескриптор, державший учётные данные в памяти, потерял бы рабочие учётные данные при первом обновлении после перезапуска.

Что входит в крейт

sdk содержит сборку, запись конфигурации, вход, разрешение моделей, вызовы, запросы к данным и синхронизацию. В нём нет:
  • Идентификации. Пользователи, API-ключи, организации, команды, права, подписки, лимиты запросов и OAuth issuer принадлежат gproxy-app, который читает ту же устойчивую ревизию из того же пакета.
  • Аутентификации нижестоящих клиентов. sdk не решает, кто вызывающий; его права передаются извне.
  • Сервера: нет слушающего сокета, маршрутизатора, middleware и CLI.
  • Консоли. Крейт генерирует типы TypeScript, по которым пишется UI; сам UI остаётся за приложением.
  • Выполнения. Попытки, преобразование, перезапись, наблюдение, бюджеты и расчёт выполняет движок (gproxy-core); sdk решает только, что ему передать.
Место sdk между движком и app описано в разделе Архитектура.

Подключение зависимости

Подключите gproxy-sdk через Git-тег или зависимость по пути:
В примере закреплён тег v4.0.0; укажите тег того выпуска, с которым работаете. Workspace использует Rust edition 2024. Rust API и API управления в v4 отличаются от v3. gproxy-sdk распространяется под AGPL-3.0-or-later и на crates.io не публикуется. Библиотеки под MIT (gproxy-protocol, gproxy-client, gproxy-cache, gproxy-seaorm, gproxy-tokenizer, gproxy-file, gproxy-client-sdk, gproxy-store и другие) публикуются там отдельно.

Сборка дескриптора

Построитель создаётся через GproxyBuilder::sqlite(path), GproxyBuilder::sqlite_memory(), GproxyBuilder::database_url(url) или GproxyBuilder::connection(connection) для соединения, открытого вами. build() не открывает ничего, что ему не передали. Он синхронизирует схему сущностей (если не задано .sync_schema(false)), создаёт строку настроек, собирает движок, загружает первый снимок (если не задано .initial_reload(false)) и в фоновом режиме запускает циклы подписки и опроса. build_unsynced() собирает дескриптор без работы со схемой — для реплики или хоста, который уже синхронизировал схему; соответствие схемы при этом не проверяется. Способ хранения секретов нужно выбрать явно. .master_key(key) шифрует секреты учётных данных AES-256-GCM, .plaintext_secrets() хранит их открытым текстом. Сборка без одного из двух отклоняется, так что дескриптор никогда не хранит секреты открытым текстом по умолчанию. Другие методы построителя:

Cargo features

Набор features по умолчанию компилируется под wasm32-unknown-unknown, как и libsql. Нативная сборка использует транспорт reqwest. Каналы, которым нужен другой бэкенд, включают его сами: claudecode, kiro, antigravity и другие включают wreq для эмуляции TLS, а codex — нативный стек reqwest, совпадающий по отпечатку с Codex CLI. Хост также может включить feature бэкенда в gproxy-client напрямую или передать собственный пул клиентов через .client_pool.

Вызовы

call принимает ключ операции и WireRequest и возвращает построитель; send разрешает имя модели и перебирает полученный план, переключаясь между учётными данными и провайдерами, как описано в разделе Архитектура. connect — тот же построитель для рукопожатия WebSocket. .scope(...) обязателен и не имеет значения по умолчанию. Scope — граница изоляции, внутри которой движок хранит привязку вызывающего к учётным данным и его продолжения. Scope по умолчанию означал бы, что посторонние вызывающие делят их между собой. Остальные методы построителя передают то, что прикладной слой уже решил:
  • .attribution(...): на кого записывается использование.
  • .budgets(...): цепочка владельцев бюджетов, с которых списывается расход.
  • .providers(...) и .credentials(...): идентификаторы провайдеров и учётных данных, доступные вызывающему. None означает отсутствие ограничений, пустой набор — что не разрешено ничего.
  • .session(...), .session_id(...), .agent_session_id(...): идентификатор сессии для привязки.
  • .model(...): имя модели, если его нет в теле запроса.
  • .max_attempts(...), .deadline(...), .cancellation(...), .request_id(...).
.channel("claudecode") сужает кандидатов до одного канала. Используйте его, когда канал важен для вызывающего: claudecode расходует лимит OAuth-подписки, а claudeapi — API-ключ с потокенной оплатой, и у них раздельные пулы квот и биллинг. Провайдер же — строка конфигурации, о которой вызывающим вне экземпляра знать не нужно. Даже с закреплённым каналом дескриптор сам выбирает среди провайдеров этого канала и их учётных данных и переключается внутри канала при отказе.

Политика загрузки ресурсов

Если запрос ссылается на ресурс по URL, движок загружает его согласно FetchPolicy. Политика по умолчанию, DefaultFetchPolicy, разрешает только http и https к публичным адресам; прямые нативные соединения закрепляют проверенные публичные IP-адреса. URL с доменным именем отклоняются, если прокси разрешал бы имя самостоятельно. Встраивающий хост может явно задать AllowlistFetchPolicy через .fetch_policy(...) для подконтрольных ему имён хостов; тогда контроль адресов назначения ложится на его прокси. В wasm назначения по имени хоста по-прежнему зависят от ограничений исходящего трафика платформы.

Вход

gproxy.login() превращает OAuth-вход в апстрим в строку учётных данных. Поддерживаются два потока, каждый из пары вызовов:
  • Код авторизации: authcode_start возвращает URL для открытия; authcode_complete принимает обратный вызов и создаёт учётные данные.
  • Код устройства: device_start возвращает код, который вводит пользователь; device_poll выполняет один опрос и возвращает ответ апстрима, в том числе время ожидания до следующего опроса. sdk не спит и не крутит цикл — частотой опроса управляет вызывающий.
Верификатор PKCE и CSRF state создаёт и проверяет sdk. Обратный вызов с несовпадающим state отклоняется, а сессия уничтожается. Незавершённая сессия входа хранится в общем кеше с TTL входа, поэтому любой экземпляр развёртывания может завершить вход, начатый другим. До успешного обмена ничего не сохраняется, а секрет шифруется настроенным кодеком перед вставкой.

Управление

gproxy.manage() — сторона записи. У каждого семейства конфигурации свой метод доступа, и все они пишут через один примитив фиксации (см. Запись конфигурации и перезагрузка). Семейства CRUD предоставляют list, get, create, update, delete и batch. Переданные идентификаторы используются как есть, отсутствующие генерируются; метки времени — миллисекунды Unix; десятичные числа передаются строками; секрет учётных данных никогда не попадает в DTO — для его чтения есть reveal_secret. Списки принимают ListQuery и возвращают Page. Страницы нумеруются с 1; page_size по умолчанию 50 и ограничен диапазоном 1–500. Запись перечисляет свои scope, и от них зависит последующая перезагрузка. Scope состояния учётных данных ведёт по дешёвому пути перезагрузки и допустим только для записи, затрагивающей секрет, срок его действия и статус жизненного цикла. Всё остальное в учётных данных фиксируется в снимке выполнения при сборке и требует полной перезагрузки; с дешёвым scope такое изменение попало бы в базу, но оставалось бы невидимым для этого экземпляра до следующей посторонней перезагрузки. history() к конфигурации не относится. Удаление истории не увеличивает ревизию и не вызывает перезагрузку, а рассчитанные счётчики квот, записи биллинга и окна подписок остаются нетронутыми. Захват, который ещё записывается, никогда не удаляется. Одно удаление указывает не более 500 идентификаторов.

Запросы к данным

gproxy.query() — сторона чтения. Здесь ничего не записывается, поэтому ревизия не меняется.

Агрегаты использования

Фиксированные поля использования хранятся в отдельных столбцах, по одной строке на каждый физический вызов апстрима, без сводных строк нижестоящего уровня. Фильтры по провайдеру и учётным данным выполняются в SQL; затем summary, group и trend сворачивают подходящие строки и пользовательские величины в Rust, порциями в порядке ключа. Без max_scan_rows агрегат читает весь подходящий диапазон порциями. С ним сканирование останавливается после указанного числа строк (ограниченного диапазоном 1–50 000), а результат возвращается с truncated: true и числом просмотренных строк scanned, чтобы частичный итог не выдавался за полный. Тренд дополнительно ограничен числом корзин: нулевая или отрицательная ширина корзины, диапазон, заканчивающийся раньше начала, и диапазон, дающий больше 5 000 корзин, отклоняются. Стоимость читается из отдельных столбцов в USD. currency равен None, если ни одна запись не была тарифицирована. quantities содержит счётчики медиа, инструментов и пользовательские счётчики в виде десятичных строк.

Постраничный вывод

Записи использования, окна квот и списки управления листаются по смещению. Журнал запросов листается по курсору: это поток, в который только добавляются записи, и его начало сдвигается, пока его читают, поэтому страницы по смещению повторяли бы или пропускали строки. Курсор состоит из двух частей, next_cursor (метка времени) и next_cursor_id (идентификатор строки), и обе нужно возвращать без изменений. Одной метки времени недостаточно, потому что два запроса могут начаться в одну миллисекунду.

Маскирование

logs() ничего не маскирует. Наблюдатель применил политику захвата развёртывания при записи строк, поэтому сохранённое — это то, что можно показывать. Хосту не стоит рассчитывать на повторную фильтрацию при чтении: если секрет оказался в базе, значит, политика это разрешила.

Синхронизация

Экземпляры одного развёртывания держат одну конфигурацию с помощью уведомлений об инвалидации в общем кеше и опроса settings.config_revision (по умолчанию каждые 30 секунд). Механизм описан в разделе Архитектура. Методы синхронизации дескриптора:
  • reload(): перечитать конфигурацию и опубликовать новый снимок.
  • reload_credentials(ids): перечитать только данные учётных данных.
  • sync_now(): один опрос ревизии; перезагрузка, если устойчивая ревизия новее.
  • drain_notifications(): применить уже пришедшие уведомления, не обращаясь к базе. В SyncMode::Background ничего не делает.
  • tick(): drain_notifications(), затем sync_now().
В режиме SyncMode::Background дескриптор сам запускает оба цикла; для этого нужен рантайм Tokio и процесс, живущий дольше запроса. В режиме SyncMode::Manual ничего не происходит, пока хост не вызовет перечисленные методы. В wasm кеш по умолчанию — StoreCache поверх базы данных, а синхронизация всегда ручная, потому что изолят не переживает свой запрос. Вызывайте tick() в начале каждого запроса:
Забирать уведомления достаточно дёшево, чтобы делать это на каждом запросе; опрос дороже, поэтому хост, которому нужна только дешёвая половина, может вызывать drain_notifications() отдельно. В ручном режиме подписка создаётся во время build(), а её начальная подсказка о ресинхронизации отбрасывается (первая загрузка и есть эта ресинхронизация). Поэтому запись, пришедшая между сборкой и первым tick(), не теряется.

Экспорт типов TypeScript

С feature ts sdk экспортирует объявления TypeScript для каждого DTO и для дескрипторов каналов:
Без GPROXY_TS_OUT тест сразу завершается и ничего не пишет, поэтому cargo test --all-features не имеет побочных эффектов. С переменной выходной каталог сначала удаляется, чтобы не оставались объявления удалённых DTO, а в конце записывается index.ts, реэкспортирующий всё. Дескрипторы каналов экспортируются потому, что консоль строит формы провайдеров по дескриптору, который возвращает сам канал. Типизация формы по этому дескриптору сохраняет её актуальной, когда канал добавляет ключ конфигурации. Список экспорта ведётся вручную, поскольку Rust не умеет перечислять типы модуля во время выполнения. В v3 этот список расходился с кодом: DTO добавляли, не внося в список, и консоль оставалась без их типов. Второй тест, every_exported_dto_is_in_the_export_list, читает исходный код модуля dto и падает, если реэкспортируемого типа нет в списке.

Слои над sdk

Приложение, которому нужна ещё и идентификация, подключает gproxy-app. Он добавляет аутентификацию, допуск, семейства операций администрирования и портала и OAuth issuer, но не содержит транспорта. gproxy-host-axum предоставляет всё это по HTTP, gproxy-host-edge монтирует тот же маршрутизатор в Cloudflare Worker, а gproxy-host-tauri обслуживает административные части через IPC. Каждый из этих крейтов можно встраивать на своём уровне.