Провайдеры агента и мост провайдера
Через провайдер агента плагин владеет агентом. Провайдер состоит из двух частей в одном артефакте: декларации на сервере и моста на машине. Ещё здесь описаны диалекты ACP и режим записи моста.
| Часть | Импорт | Главные имена |
|---|---|---|
| декларация (сервер) | @get-bb/plugin-sdk | bb.providers.register(declaration) → { dispose() } · PluginProviderDeclaration |
| мост (хост) | @get-bb/plugin-sdk/provider-bridge | experimental_defineProviderBridge · PROVIDER_BRIDGE_EXPORT_NAME · BRIDGE_REQUEST_METHODS · BRIDGE_JSON_RPC_ERRORS · PROVIDER_BRIDGE_PROTOCOL_VERSION · THREAD_DELTA_GRAMMAR_V2/V3 · runBridgeRequest · createBridgeIo · addTokenUsage |
| набор ACP | …/provider-bridge/acp | experimental_acpProviderBridge · experimental_acpLaunchSpecSchema · experimental_probeAcpAgent |
| соответствие | …/provider-bridge/testing | experimental_runBridgeConformance · experimental_captureBridgeJsonRpcOutput · experimental_createDeltaAssembler · experimental_replayRecording · experimental_compareParity |
// examples/plugins/echo-provider/src/provider-bridge.ts (abridged) — a minimal bridgeconst handlers: Record<string, RequestHandler> = { [BRIDGE_REQUEST_METHODS.initialize]: (id, params) => { io.sendResult(id, { protocolVersion: PROVIDER_BRIDGE_PROTOCOL_VERSION, // exactly 2 capabilities: { grammarVersions: [THREAD_DELTA_GRAMMAR_V3, THREAD_DELTA_GRAMMAR_V3], sessionRestore: true, threadArchive: false, threadRename: false, threadGoalClear: false, fork: "none", approvalEnforcedBy: "runtime", steerMode: "queue", }, }); }, [BRIDGE_REQUEST_METHODS.threadStart]: (id, params) => { /* … io.sendResult(id, { providerThreadId, sessionRestorable: true }) … */ },};export const experimental_providerBridge = experimental_defineProviderBridge({ handleLine, start });Транспорт. JSON-RPC 2.0. Работает в обе стороны через stdin/stdout процесса моста, сообщения разделяются переносом строки. Запросы и ответы различаются по наличию поля method, «never by the shape of the result». Пространства идентификаторов у этих направлений независимы. Вывод в stdout вне протокола игнорируется; мост обязан защищать свой stdout.
Грамматика. Ассемблер говорит только на v3 ([3, 3]). Мост, чей диапазон не включает 3 (даже если написан до появления этого поля), отклоняется при запуске с понятной ошибкой. Факт рукопожатия может только сузить то, что объявила декларация, но не расширить это.
Кто чем владеет. Сервер генерирует threadId, провайдер — providerThreadId, а ассемблер в демоне — идентификаторы ходов и элементов. «The bridge performs no id translation.»
Минимальный корректный ход. input.accepted { clientRequestId } → turn.open → дельты элементов → turn.boundary { status }. Даже промпт, который ничего не делает, обязан выдать пару open+boundary: «zero-delta acceptance is hung-thread class #1431». После thread/stop мост ничего не удерживает: все долги, и прежде всего терминальная граница прерванного хода, должны уйти в канал до ответа.
recovery. Строгая типизация, ошибки не сопоставляются по тексту: sessionArchived, authRequired, restartRecommended, staleTurn, rateLimited. Пэйлоад один, носителя два: подсказка об отклонённом запросе уходит в error.data.recovery, а незапрошенная — как уведомление provider/recovery. «Never send both for one event.»
Ловушки. Держите SDK в dependencies (задокументированное исключение); приватные пакеты @bb/* запрещены. Мост не должен запускать себя сам: bootstrap демона владеет argv, dataDir/tempDir, фреймингом stdin и сигналами. Не генерируйте providerThreadId из счётчика процесса. Если мост порождает дочерние процессы, он обязан вызывать sanitizeInheritedChildProcessEnv.
Кто поставляет. provider-claude-code, provider-codex («the largest working example»), provider-pi, provider-acp и examples/echo-provider («the smallest»).
Справка: docs/provider-bridge-protocol.md · docs/provider-plugin-api.md.
Диалекты и агенты ACP
Заголовок раздела «Диалекты и агенты ACP»Пять зарегистрированных диалектов, пять агентов из поставки bb.
| id провайдера | displayName | launch spec |
|---|---|---|
acp-cursor | Cursor | cursor-agent acp |
acp-opencode | opencode | opencode acp |
acp-omp | omp | omp acp |
acp-grok | Grok Build | grok agent stdio |
acp-hermes-agent | Hermes Agent | hermes acp |
В коде есть диалекты acp (универсальный no-op), grok, cursor, omp и opencode. Их пять, хотя документация описывает три. Код считается истиной, но расхождение не проверено по changelog.
Диалект выбирается в два этапа: побеждает явный providerOptions.acpDialect; иначе basename(launch.command) сопоставляется с DIALECT_IDS_BY_COMMAND, а всё неизвестное откатывается к универсальному диалекту. Пользовательские агенты берутся из настройки customAgents, получают префикс acp- и глиф "Toolbox". Все регистрации ACP используют общую family: "acp". Реестр диалектов намеренно скрыт: «no plugin has needed it, and its shape — process-global, unversioned hooks — is still open».
Режим записи моста
Заголовок раздела «Режим записи моста»Диагностика провайдера без догадок.
BB_PROVIDER_BRIDGE_RECORD_DIR=<dir>// every bridge process mirrors BOTH boundaries into// <dir>/<providerId>/<threadId>/<direction>.ndjson — four directions,// one line of { ts, run, seq, dir, line } each, with seq a single counter across all tracks.// A bridge that spawns a CLI calls experimental_recordProviderChildIo(child, { threadId })// right after spawn(); one whose pipe belongs to the SDK checks// experimental_isProviderBridgeRecording() and takes the spawn over itself.// "A recording is never overwritten": a deliberate bridge change writes// bridge→runtime.current.ndjson alongside it.