feat(gateway): streaming & markdown support for Google Chat adapter#743
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mand Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Multi-chunk path now sends GatewayResponse (prevents core timeout on long messages) - Token failure sends failure GatewayResponse (parity with Feishu adapter) - edit_message uses PATCH instead of PUT (per Google Chat API docs) - Inject api_base for testability - Rewrite integration tests with wiremock (hermetic, no real API calls) - Update docs/google-chat.md: move markdown to supported, add streaming Addresses #2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
~~text~~ → ~text~ (Google Chat native strikethrough syntax) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR enhances the gateway’s Google Chat adapter to support the OpenAB gateway streaming protocol by returning a message_id for the initial send and handling subsequent edit_message updates, while also adding Markdown-to-Google-Chat formatting conversion.
Changes:
- Add streaming support via
edit_message(PATCH update) andGatewayResponse { message_id }acknowledgements for initial sends. - Add
markdown_to_gchat()conversion and apply it to both send and edit operations. - Update docs to reflect streaming/markdown support and update the gateway entrypoint to pass
event_txinto the adapter.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
gateway/src/main.rs |
Passes event_tx into the Google Chat adapter reply handler to enable streaming acknowledgements. |
gateway/src/adapters/googlechat.rs |
Implements edit-in-place streaming, returns GatewayResponse with message_id, adds Markdown conversion, and introduces injectable api_base for tests. |
docs/google-chat.md |
Documents streaming and markdown support for the Google Chat adapter. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let text = &reply.content.text; | ||
| let chunks = split_text(text, GOOGLE_CHAT_MESSAGE_LIMIT); | ||
|
|
||
| for chunk in chunks { | ||
| send_message( | ||
| if chunks.len() <= 1 { | ||
| let msg_name = send_message( |
| request_id: req_id.clone(), | ||
| success: msg_name.is_some(), | ||
| thread_id: None, | ||
| message_id: msg_name, | ||
| error: if chunks.is_empty() { |
| let resp = crate::schema::GatewayResponse { | ||
| schema: "openab.gateway.response.v1".into(), | ||
| request_id: req_id.clone(), | ||
| success: first_msg_name.is_some(), | ||
| thread_id: None, | ||
| message_id: first_msg_name, | ||
| error: None, |
| // Bold: **text** → *text* | ||
| if chars[i] == '*' && i + 1 < chars.len() && chars[i + 1] == '*' { | ||
| if let Some(end) = find_closing(&chars, i + 2, &['*', '*']) { | ||
| out.push('*'); | ||
| let inner: String = chars[i + 2..end].iter().collect(); |
| - **Bot message filtering** — bot messages (`user_type: "BOT"`) are filtered at the gateway level | ||
| - **Message splitting** — long replies (>4096 chars) are automatically split at newline/space boundaries | ||
| - **Token auto-refresh** — service account JWT tokens are refreshed automatically before expiry | ||
| - **Markdown formatting** — replies are converted via `markdown_to_gchat` to Google Chat's native formatting (`*bold*`, `_italic_`, `` `code` ``, code blocks, `<url|text>` links); unsupported syntax (strikethrough, headings beyond bold) is approximated |
OpenAB PR ScreeningThis is auto-generated by the OpenAB project-screening flow for context collection and reviewer handoff.
Screening report## IntentPR #743 is trying to make the Google Chat gateway adapter support in-place streaming replies and native Google Chat markdown formatting. The user-visible problem is that Google Chat users currently do not get the same streaming response experience available in other adapters, and markdown emitted by OpenAB/core does not render cleanly in Google Chat. The operator-visible problem is that the gateway needs to complete the existing FeatThis is a feature PR. It adds two main behaviors:
It also updates gateway wiring so the Google Chat adapter can send Who It ServesPrimary beneficiaries:
Rewritten PromptImplement Google Chat adapter support for OpenAB gateway streaming. When Google Chat receives a normal reply with a Also add a focused markdown conversion layer for Google Chat formatting: bold, italic, strikethrough, links, inline code, code blocks, and headings. Leave unsupported markdown unchanged. Requirements:
Merge PitchThis is worth advancing because it brings Google Chat closer to parity with other gateway adapters and implements a core protocol expectation rather than a cosmetic feature. Streaming support improves perceived latency for users, while Risk profile is moderate. The behavioral surface is adapter-local, but the PR touches message routing and ack semantics, which are easy to get subtly wrong. The likely reviewer concern is whether the chosen Best-Practice ComparisonRelevant OpenClaw principles:
Relevant Hermes Agent principles:
The strongest comparison point is routing correctness: OpenClaw’s explicit delivery routing principle supports making the Implementation OptionsOption 1: Conservative adapter-local merge Accept the current approach if tests pass and the implementation keeps all streaming and markdown logic isolated to Option 2: Balanced protocol-aligned cleanup Merge the feature after tightening the adapter contract: define helper functions for converting Google Chat API message names into gateway message ids, centralize Option 3: Ambitious cross-adapter streaming hardening Use this PR as the trigger to formalize gateway streaming behavior across adapters. Add shared helper types for Comparison Table
RecommendationAdvance PR #743 using the balanced option. The feature itself is valuable and scoped to a real parity gap, but the next review should focus on protocol correctness rather than just whether Google Chat streaming works in E2E testing. Before merge, tighten or confirm the message id mapping, Keep table rendering, file attachments, and space events out of this PR. If maintainers want broader streaming consistency work, split that into a follow-up after this adapter-specific feature lands. |
…bdev#743 5 review items from #3: 1. Empty message: short-circuit to skip API call, send failure ack 2. Single-chunk send failure: propagate error string (status + body) in GatewayResponse.error 3. Multi-chunk send failure: propagate first error string instead of error: None 4. Italic: convert *text* → _text_ (Google Chat italic syntax). Single _text_ passes through. 5. docs/google-chat.md: align with actual converter behavior (bold, italic, strikethrough, headings) Refactor: send_message now returns Result<String, String> so error context flows to core. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…bdev#743 5 review items from #3: 1. Empty message: short-circuit to skip API call, send failure ack 2. Single-chunk send failure: propagate error string (status + body) in GatewayResponse.error 3. Multi-chunk send failure: propagate first error string instead of error: None 4. Italic: convert *text* → _text_ (Google Chat italic syntax). Single _text_ passes through. 5. docs/google-chat.md: align with actual converter behavior (bold, italic, strikethrough, headings) Refactor: send_message now returns Result<String, String> so error context flows to core. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
46b9a0b to
b7abe0d
Compare
|
Addressed all 5 Copilot review comments in
New tests added (119 total, 0 failed):
E2E tested image available at |
|
CHANGES REQUESTED 🟡 — Solid implementation of the OAB streaming protocol for Google Chat, with well-tested markdown conversion. Address the NITs below, then ready to merge. 📋 Review — Four Questions1. What problem does this solve?Google Chat adapter lacked streaming (typewriter effect) and sent raw markdown that rendered poorly. This PR adds:
2. How does it solve it?
3. What alternatives were considered?
4. Is this the best approach?Yes. Clean implementation following the established OAB streaming protocol pattern (same as Feishu/Discord). 🟢 INFO — Well done:
🟡 NIT — Must fix:
🔍 Baseline Check (Step 0)
|
|
Quick question on the Google Chat adapter setup: is a Google Workspace (enterprise) account required to create a Google Chat bot and use the service account auth flow documented here? Specifically wondering whether a regular Google account (gmail.com) can create a Google Chat app/bot via Google Cloud Console, or if the bot publishing and service account JWT flow only works for Workspace-managed domains. This affects how accessible this integration is to individual developers vs. enterprise teams. |
- markdown_to_gchat: doc comment noting caller must pass raw markdown (called by both send_message and edit_message; double-conversion would happen if pre-converted text is passed) - convert_inline: TODO note for future byte-level iteration optimization (currently Vec<char> allocation per line, acceptable at current scale) Addresses chaodu-agent must-fix #1 and #2 from PR openabdev#743 review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addressing review feedback@chaodu-agent must-fix items (
|
| # | Action |
|---|---|
| 1 | Added doc comment to markdown_to_gchat noting caller must pass raw markdown (called from both send_message and edit_message). Double-conversion would happen if pre-converted text is passed; OAB core is expected to always emit raw markdown. |
| 2 | Added TODO(perf) note above convert_inline for future byte-level iteration optimization. Current Vec<char> allocation is acceptable at expected streaming scale. |
| 3 | No-op — already covered by find_single for single-char patterns. |
@Joseph19820124 — re Google Workspace requirement
A Google Workspace (Business or Enterprise) account is required. A regular @gmail.com account cannot create a Google Chat bot — Google blocks Chat API configuration for non-Workspace accounts.
Per Google's official setup docs, the prerequisite is verbatim:
"A Business or Enterprise Google Workspace account with access to Google Chat"
Specifics:
- A personal Gmail can create a GCP project and toggle the Chat API on, but configuration ultimately fails with "Google Chat API is only available to Google Workspace users"
- Service account creation works mechanically but Chat API calls are blocked at runtime/config since the developer account isn't part of a Workspace tenant
- Workaround: Workspace Individual / free trial / Business Starter — cheapest tier is sufficient
I can update docs/google-chat.md to call this out in the Prerequisites section if helpful.
Regular @gmail.com consumer accounts cannot create Google Chat apps — Google requires a Workspace (Business or Enterprise) account at API configuration. Cheapest qualifying tier is Workspace Individual or Business Starter. Per Joseph19820124 question on PR openabdev#743. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Updated |
🟢 LGTM — solid implementation following established patternsVerdict: No blocking issues. This PR correctly implements the OAB gateway streaming protocol for Google Chat, matching the Feishu adapter pattern exactly. What it does well
🟡 NITs (non-blocking)
Review methodology
|
🔴 One blocking issue found — four-monk collaborative reviewUpdated verdict: Request changesThe initial LGTM is revised after collaborative triage by 超渡·普渡·覺渡·擺渡. One logic bug found in the multi-chunk path. 🔴 SUGGESTED CHANGES — multi-chunk
|
chaodu-agent
left a comment
There was a problem hiding this comment.
Found one blocking issue after checking the PR merge ref against current main.
gateway/src/adapters/googlechat.rs adds several GatewayReply test fixtures that construct Content with only content_type and text, for example around handle_reply_sends_gateway_response_success. Current main has Content { content_type, text, attachments }, so the merge result does not compile: these struct literals are missing attachments: Vec::new().
This is easy to miss because the PR head branch still has the older Content shape, but refs/pull/743/merge includes main schema changes. Please add attachments: Vec::new() to the new Google Chat test fixtures, or use a small test helper to construct text content.
I could not run cargo test in this environment because cargo is not installed, but this is a static Rust compile error on the merge ref.
When chunk 1 succeeds but subsequent chunks fail, GatewayResponse was reporting success=true with error=Some(...) — a contradictory signal. Core would treat the message as delivered despite missing content. Now any chunk failure marks the overall operation as failed, while preserving message_id so core retains the reference for any follow-up. Adds handle_reply_multi_chunk_partial_failure_reports_failure test covering the mixed success/failure scenario (wiremock 200→500). Addresses chaodu-agent blocking review on PR openabdev#743. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Addressed blocking issue in // Before
success: first_msg_name.is_some(),
// After
success: first_msg_name.is_some() && first_error.is_none(),Any chunk failure now marks the overall operation as failed. New test Skipping the non-blocking NITs (italic edge case, JSON parse fallback log, Business Starter trial mention) to keep this PR focused on the streaming/markdown scope. |
🟢 LGTM — Streaming & Markdown for Google ChatVerdict: Approve-ready. No blocking issues. Well-structured PR that follows the established Feishu adapter streaming pattern. What this PR doesAdds streaming support ( Traffic Light
四問框架 (Four Questions)1. What problem does it solve? 2. How does it solve it?
3. What alternatives were considered?
4. Is this the best approach? Review notesStreaming protocol compliance:
Markdown converter coverage:
Test coverage:
Review by 超渡法師 (chaodu-agent) · Four-monk collaborative review in progress |
Summary
Adds streaming (edit_message) and markdown formatting to the Google Chat adapter, implementing the OAB gateway streaming protocol.
Changes
gateway/src/adapters/googlechat.rsedit_message(): PATCH message text in-place for streaming.handle_reply(): acceptsevent_tx, routesedit_messagecommand, sendsGatewayResponsewithmessage_id(single-chunk, multi-chunk, and token-failure paths).markdown_to_gchat(): converts markdown bold/italic/strikethrough/links/code/headings to Google Chat native formatting.api_basefield for test injection.gateway/src/main.rsevent_txtogc.handle_reply()for streaming supportdocs/google-chat.mdKey Design Decisions
OAB gateway streaming protocol — Implements the standard
request_id → GatewayResponse → edit_messageflow. Core sends initial reply withrequest_id, gateway returnsGatewayResponsewithmessage_id, core sends subsequentedit_messagecommands to update the message in-place.PATCH for edit_message — Google Chat REST API documents
PATCH /v1/{message.name}?updateMask=textfor message updates. Verified working in E2E testing.GatewayResponse on all paths — Single-chunk, multi-chunk, and token-failure paths all send
GatewayResponseback to core. Prevents core from waiting 5s for a response that never arrives (token-failure ack follows the same pattern as Feishu adapter).markdown_to_gchat conversion —
**bold**→*bold*,~~strike~~→~strike~,[text](url)→<url|text>,# Heading→*bold heading*, code blocks preserved. Unsupported syntax (tables) passes through unchanged. Table rendering is tracked separately in feat(markdown): per-channel table rendering mode (follow-up to #513) #514.Injectable api_base —
GoogleChatAdaptercarries anapi_basefield (defaults tohttps://chat.googleapis.com/v1). Tests inject a wiremock server URL so they can assert on both success and failure responses without hitting real APIs.Testing
E2E tested on k3s VPS with Helm chart deployment:
cargo test— 112 gateway tests passed (47 googlechat)New test cases (wiremock-based)
handle_reply_sends_gateway_response_successhandle_reply_sends_failure_response_on_api_errorhandle_reply_token_failure_sends_error_responsehandle_reply_edit_message_does_not_send_responsehandle_reply_multi_chunk_sends_gateway_responseNot Yet Supported
Breaking Changes
None. Existing non-streaming Google Chat messages continue to work.
Discord Discussion URL
https://discord.com/channels/1491295327620169908/1501153334042824764
🤖 Generated with Claude Code