refactor(gateway): migrate QQBot adapter to bundled plugin - #41065
Closed
kshitijk4poor wants to merge 2 commits into
Closed
kshitijk4poor wants to merge 2 commits into
kshitijk4poor wants to merge 2 commits into
Conversation
Relocate the QQBot adapter package from gateway/platforms/qqbot/ into a
self-contained bundled plugin under plugins/platforms/qqbot/, following the
Discord/Mattermost/Home Assistant/Yuanbao/Signal migration shape. QQBot was
already a package (adapter + constants + utils + crypto + onboard + keyboards
+ chunked_upload), so this is mostly a directory relocate plus the register()
block; intra-package imports switch from absolute to relative.
register() supplies the hooks that previously lived as per-platform wiring
in core:
- adapter_factory -> the elif in gateway/run.py::_create_adapter()
- check_fn -> check_qq_requirements guard there
- is_connected -> the Platform.QQBOT lambda in gateway/config.py
- setup_fn -> _setup_qqbot (QR scan-to-configure + manual entry)
+ its _PLATFORMS entry + _builtin_setup_fn mapping
+ the delegating wrapper in hermes_cli/setup.py
- standalone_sender_fn -> _send_qqbot in tools/send_message_tool.py
- cron_deliver_env_var -> QQBOT_HOME_CHANNEL
The REST sender (token exchange + channel/C2C/group endpoint fallback) moved
into the plugin as _standalone_send; tools/send_message_tool.py keeps a thin
_send_qqbot shim so the in-module dispatch is unchanged. QQBot is HTTP, so cron
delivery works out-of-process.
Deliberately left generic in core: the Platform.QQBOT enum literal, the
_apply_env_overrides QQ_*/QQBOT_* env block, and the _is_user_authorized
allowlist maps. QQBot has no load_gateway_config YAML block, so no
apply_yaml_config_fn. The gateway.platforms.QQAdapter re-export is repointed
to the plugin path for back-compat. Updated the connection-checker guard test
to exclude bundled-plugin platforms whose is_connected lives in the plugin.
28 tasks
qqbot is now a bundled plugin; its PlatformEntry already permits /update via allow_update_command (defaults True), honored by the registry fallback in _handle_update_command. The hardcoded Platform.QQBOT entry in _UPDATE_ALLOWED_PLATFORMS is therefore redundant. Same cleanup as NousResearch#32525 did for Discord/Mattermost.
Contributor
Author
|
Follow-up to #32525: now that the migrated-platform |
Contributor
|
hello ser~ Why was this PR closed? Was it because it hadn't been merged for a long time, or was there another reason? @kshitijk4poor |
k176060444-lgtm
added a commit
to k176060444-lgtm/hermes-agent
that referenced
this pull request
Sep 1, 2026
Move QQBot outbound REST protocol out of tools/send_message_tool.py and into the platform package (gateway/platforms/qqbot/), where the live adapter already owns authentication, target resolution, and chunked upload. The tool layer now delegates to _send_via_adapter which prefers the in-process QQAdapter (live) and falls back to the standalone_sender_fn registered on QQBot's PlatformEntry. Key changes: - NEW: gateway/platforms/qqbot/standalone.py Standalone QQBot sender sharing ChunkedUploader, constants, and token acquisition with the live adapter. Supports text, image, voice, video, document, multi-attachment, force_document, and explicit C2C/group/guild targets. No WebSocket — uses a temporary httpx client with try/finally. - gateway/platforms/qqbot/__init__.py Register QQBot in platform_registry with standalone_sender_fn so _send_via_adapter can find it when no live adapter is present. - tools/send_message_tool.py REMOVED: _QQBOT_* constants, 7 MB base64 limit, _resolve_qqbot_target, _send_qqbot, _send_qqbot_with_media, _is_endpoint_mismatch, _split_text (~400 lines). QQBot now dispatches through _send_via_adapter for both text and media, matching the pattern used by plugin platforms (Discord, Google Chat, IRC, etc.). - tests/tools/test_send_message_tool.py REMOVED: ~1330 lines of tests covering deleted tool-layer functions. - NEW: tests/tools/test_send_message_tool_qqbot_rectify.py 20 tests covering live-adapter priority, no-standalone-fallback, standalone invocation, force_document/[[as_document]] passthrough, target prefix resolution, and file-type classification. Test results (excluding 6 pre-existing TestSendViaAdapterStandaloneFallback failures unrelated to this change): 127 existing tests pass, 0 regressions 20 new tests pass Maintainer review: NousResearch#40457 (teknium1) Related: NousResearch#41112 (platform migration tracking), NousResearch#41065 (QQBot plugin)
k176060444-lgtm
added a commit
to k176060444-lgtm/hermes-agent
that referenced
this pull request
Sep 1, 2026
Round 2 of PR NousResearch#40457 rectification — addresses all four blocking items from the maintainer review. 1. Live adapter media dispatch (_send_via_adapter / _dispatch_live_media): - Text → adapter.send() - Image → adapter.send_image_file() - Voice → adapter.send_voice() - Video → adapter.send_video() - Other → adapter.send_document() - force_document=True → all via adapter.send_document() - Multi-attachment: text sent once, each media sent separately - On failure: error returned immediately — no standalone fallback 2. Shared QQ outbound component (gateway/platforms/qqbot/outbound.py): - NEW: QQApiClient — token acquisition, auth headers, API requests, file upload (ChunkedUploader), text/media message sending - Used by standalone sender; structured for future adapter adoption - classify_media_type(), resolve_target(), split_for_qq() — single source of truth for extension→file_type, prefix→target parsing, and text chunking 3. Standalone rewrite: - Uses QQApiClient instead of duplicating REST protocol - Resource cleanup: try/finally → aclose() on httpx client - Guild text supported; guild media explicitly rejected 4. Platform registration: - adapter_factory=QQAdapter (no longer lambda cfg: None) - check_fn=check_qq_requirements (real dependency check) - Registration raises on failure (no bare except: pass) Test results (--override-ini addopts): 127 existing + 36 new = 163 passed, 0 regressions 6 pre-existing TestSendViaAdapterStandaloneFallback failures (pytest-asyncio not installed — identical on baseline branch) Maintainer review: NousResearch#40457 (teknium1) Related: NousResearch#41112, NousResearch#41065
k176060444-lgtm
added a commit
to k176060444-lgtm/hermes-agent
that referenced
this pull request
Sep 1, 2026
…ation Round 3 of PR NousResearch#40457 rectification. 1. QQAdapter now creates and uses QQApiClient for token, REST requests, and file uploads — with a transparent fallback to the adapter's own logic during startup (before connect() wires up the client). This makes QQApiClient the single shared QQ outbound implementation used by both the live adapter and the standalone sender. 2. Added normalize_target() to QQAdapter — strips explicit prefixes (c2c:, user:, group:, guild:) before URL construction. Honors explicit prefixes regardless of _chat_type_map. Raw OpenIDs fall back to _guess_chat_type() for inbound compatibility. Updated all outbound methods (send(), _send_media(), _send_chunk()) to use it. 3. Unified media classification: _dispatch_live_media now imports classify_media_type from gateway.platforms.qqbot.outbound — live and standalone now classify files identically. No more duplicate extension lists. Deleted duplicate code: - _ensure_token HTTP logic (~30 lines) - _api_request HTTP logic (~30 lines) Both now delegate to QQApiClient, with legacy fallbacks preserved for pre-connect() startup paths. Test results (with pytest-timeout + pytest-asyncio installed): 330 passed, 0 failures, 0 deselected - 161 gateway QQBot tests - 133 send_message_tool tests (inc. 6 previously-failing TestSendViaAdapterStandaloneFallback) - 36 new rectification tests Related: NousResearch#40457 (teknium1 review), NousResearch#41112, NousResearch#41065
k176060444-lgtm
added a commit
to k176060444-lgtm/hermes-agent
that referenced
this pull request
Sep 1, 2026
Round 4 of PR NousResearch#40457 rectification. 1. QQApiClient is now the single token/API implementation: - asyncio.Lock singleflight with double-check caching - public invalidate_token() — adapter no longer touches private fields - Read-only access_token / token_expires_at compat properties - Adapter _ensure_token / _api_request now ONLY delegate to _api - No legacy HTTP fallback (deleted ~70 lines of duplicate protocol) - disconnect() clears _api after closing _http_client 2. Structured QQApiError with status_code: - 404 detection for raw-OpenID C2C→group fallback - 401/403/429/timeout/5xx never trigger fallback - resolve_target() returns 3-tuple (type, id, has_prefix) - normalize_target() honors has_prefix; raw OpenIDs delegate to _guess_chat_type() 3. is_voice parameter in classify_media_type(): - Passed through from tool-layer _is_voice flag - force_document is highest priority - Live (_dispatch_live_media) and standalone both use same function 4. 4004 handler calls self._api.invalidate_token() - Next reconnect forces fresh token acquisition 5. Tests: 318 passed, 0 failures - token singleflight: concurrent ensure_token → 1 POST - invalidate clears cache; readonly properties work - adapter delegation; no _api raises RuntimeError - cleanup clears _api - is_voice classification (5 tests) - target has_prefix semantics - QQApiError.status_code - >7 MB sparse file reaches mocked ChunkedUploader - guild text dispatch; force_document live+standalone - live failure no standalone fallback Related: NousResearch#40457 (teknium1), NousResearch#41112, NousResearch#41065
k176060444-lgtm
added a commit
to k176060444-lgtm/hermes-agent
that referenced
this pull request
Sep 1, 2026
…xclusivity Round 5 of PR NousResearch#40457 rectification. 1. Raw OpenID 404-only fallback (standalone.py): - resolve_target() returns has_prefix flag - Only raw OpenIDs (has_prefix=False) may fallback C2C→group on 404 - Explicit c2c:/user:/group:/guild: never fallback - 401/403/429/timeout/5xx/non-JSON errors never fallback - Text/upload not double-sent: retry happens before final send - Fallback lives in platform-owned outbound layer (standalone.py) 2. is_voice has real semantics: - classify_media_type(is_voice=True) ONLY gates to MEDIA_TYPE_VOICE when the extension is also in _VOICE_EXTS - Without [[audio_as_voice]], audio files (.mp3/.ogg/etc.) → MEDIA_TYPE_FILE - force_document always highest priority - Matches Telegram/Slack is_voice contract from BasePlatformAdapter 3. QQApiClient is single source of token truth: - Removed _fallback_token from adapter - _ensure_token() purely delegates to _api (no fallback path) - Adapter compat properties delegate to _api - Updated old TestIdentifyIntents test to mock QQApiClient 4. Strengthened QQApiClient error handling: - Non-JSON responses still produce QQApiError(status_code=X) - Error text never leaks token/secret (only path) - 5xx/timeout treated correctly Tests: 320 passed, 0 failures - is_voice behavioral (7 tests): True→voice, False→file, force_doc - 404 fallback (8 tests): raw→group, explicit→no-fallback, 403/429/5xx/timeout→no-fallback - Token singleflight + invalidate - Adapter delegation + cleanup - Large file → ChunkedUploader - Non-JSON error handling - force_document + live no-fallback Related: NousResearch#40457 (teknium1), NousResearch#41112, NousResearch#41065
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Relocates the QQBot adapter package from
gateway/platforms/qqbot/into aself-contained bundled plugin under
plugins/platforms/qqbot/, following theDiscord / Mattermost / Home Assistant / Yuanbao / Signal migration shape.
QQBot was already a package (adapter + constants + utils + crypto + onboard +
keyboards + chunked_upload), so this is mostly a directory relocate plus the
register()block; intra-package imports switch from absolute to relative.How
register()supplies the hooks that were per-platform wiring in core:adapter_factory, check_fn, is_connected, setup_fn (QR scan-to-configure +
manual entry), standalone_sender_fn, cron_deliver_env_var.
The REST sender (token exchange + channel/C2C/group endpoint fallback) moved
into the plugin as
_standalone_send;tools/send_message_tool.pykeeps athin
_send_qqbotshim so the in-module dispatch is unchanged. QQBot is HTTP,so cron delivery works out-of-process.
Out of scope (stays generic, same as every other platform)
Platform.QQBOTenum literal, the_apply_env_overridesQQ_/QQBOT_ envblock, the
_is_user_authorizedallowlist maps. QQBot has noload_gateway_configYAML block, so noapply_yaml_config_fn. Thegateway.platforms.QQAdapterre-export is repointed to the plugin path.Tests
8 clean renames (R091 adapter + R098–R100 on the package files). 357 focused
tests pass. Updated the connection-checker guard test to exclude bundled-plugin
platforms whose
is_connectedlives in the plugin. No new failures vs main(pre-existing matrix/telegram xdist flakes excluded).