Skip to content

feat: Add dual language selector for AI Transport guides#3176

Open
GregHolmes wants to merge 9 commits intomainfrom
AIT-108-Framework-language-feature-combos-investigate-and-design-navigation
Open

feat: Add dual language selector for AI Transport guides#3176
GregHolmes wants to merge 9 commits intomainfrom
AIT-108-Framework-language-feature-combos-investigate-and-design-navigation

Conversation

@GregHolmes
Copy link
Contributor

@GregHolmes GregHolmes commented Feb 2, 2026

Summary

Adds dual language selection for AI Transport guide pages, allowing users to independently choose Client (frontend) and Agent (backend) languages for code examples.

Changes

  • Dual language selector UI: Two dropdowns ("Client" and "Agent") replace the single language selector on AI Transport guide pages
  • URL param support: ?fe_lang=xxx&be_lang=yyy for bookmarking and sharing specific language combinations
  • Navigation preservation: Language params persist when navigating between guide pages
  • Code block prefixes: MDX files use fe_ and be_ prefixes (e.g., fe_javascript, be_python) to distinguish frontend vs backend code
  • aiTransport languages: javascript, python, swift, java with SDK versions

Supported languages

  • Client: JavaScript, Swift, Java
  • Agent: JavaScript, Python, Java

Dependencies

  • Requires @ably/ui@17.13.1-dev.c839343a (or later) with fe_/be_ prefix support in CodeSnippet
  • Also removes stale CookieMessage CSS import that was breaking builds with newer ably-ui versions

Summary by CodeRabbit

  • New Features

    • Dual-language selection supporting client and agent language preferences
    • Expanded multi-language code examples for Anthropic integration guides
    • Updated language support versions for AI Transport
  • Documentation

    • Enhanced guides with per-language setup instructions and implementations for JavaScript, Python, Java, and Swift

@GregHolmes GregHolmes self-assigned this Feb 2, 2026
@GregHolmes GregHolmes added the review-app Create a Heroku review app label Feb 2, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Walkthrough

This pull request implements dual-language support for Client and Agent code blocks across the documentation platform. Changes introduce language selection infrastructure, context providers, and conditional rendering logic, along with updated language data and comprehensive guide documentation with per-language implementations.

Changes

Cohort / File(s) Summary
Language Selection Infrastructure
src/components/Layout/LanguageSelector.tsx
Refactored to support dual-language mode with new DualLanguageSelector and DualLanguageDropdown components alongside existing SingleLanguageSelector. Adds logic to determine rendering mode based on activePage.isDualLanguage and updates URL query parameters for client_lang and agent_lang.
Context & Provider
src/contexts/layout-context.tsx
Added CLIENT_LANGUAGES and AGENT_LANGUAGES arrays, isDualLanguagePath detection function, and helpers for deriving clientLanguage, agentLanguage, and isDualLanguage. Extended LayoutContext to include these fields in activePage object.
Navigation Types & Utilities
src/components/Layout/utils/nav.ts
Extended ActivePage type with optional clientLanguage, agentLanguage, and isDualLanguage fields to support dual-language metadata.
Sidebar Navigation
src/components/Layout/LeftSidebar.tsx
Added buildLinkWithParams helper to construct links with appropriate language query parameters based on dual-language support. Updated link generation to preserve client_lang/agent_lang for dual-language targets.
MDX Code Block Handling
src/components/Layout/MDXWrapper.tsx
Enhanced code-block language detection to support client_ and agent_ prefixed languages and dual-language filtering. Extended SDKContextType to allow SDKType | undefined. Adds detection and conditional rendering for dual-language code blocks.
Conditional Content Rendering
src/components/Layout/mdx/If.tsx
Added three optional props (client_lang, agent_lang, client_or_agent_lang) to enable per-language content filtering. Extended activePage to expose clientLanguage and agentLanguage.
Language Selector Display
src/components/Layout/mdx/PageHeader.tsx
Updated showLanguageSelector logic to always display for dual-language pages via activePage.isDualLanguage.
Language & SDK Data
src/data/languages/languageData.ts
Updated JavaScript version in aiTransport from 2.11 to 2.16 and added Swift 1.2 entry.
Navigation Structure
src/data/nav/aitransport.ts
Restructured Guides section to group token-streaming entries by provider (OpenAI, Anthropic, LangGraph, Vercel AI SDK) with nested "Message per response" and "Message per token" items.
Guide Documentation
src/pages/docs/guides/ai-transport/anthropic-message-per-response.mdx, anthropic-message-per-token.mdx
Significantly expanded guides with multi-language conditional sections (JavaScript, Python, Java, Swift), separate agent and client code blocks, and step-by-step implementations with language-specific setup and event handling.
Dependencies
package.json
Updated @ably/ui from 17.13.2 to 17.13.2-dev.aa83caf92f.
Test Mocks
src/components/Layout/LeftSidebar.test.tsx
Added isDualLanguagePath to layout context mock.

Sequence Diagram

sequenceDiagram
    participant User
    participant LanguageSelector
    participant LayoutContext
    participant LeftSidebar
    participant MDXWrapper
    participant CodeBlock as If/CodeBlock
    
    User->>LanguageSelector: Select client language
    LanguageSelector->>LanguageSelector: Check activePage.isDualLanguage
    alt isDualLanguage true
        LanguageSelector->>LanguageSelector: Render DualLanguageSelector
        LanguageSelector->>LanguageSelector: Update client_lang/agent_lang params
    else isDualLanguage false
        LanguageSelector->>LanguageSelector: Render SingleLanguageSelector
        LanguageSelector->>LanguageSelector: Update lang param
    end
    
    LanguageSelector->>LayoutContext: Update URL query params
    LayoutContext->>LayoutContext: determineClientLanguage/determineAgentLanguage
    LayoutContext->>LayoutContext: Set clientLanguage & agentLanguage in activePage
    
    LeftSidebar->>LayoutContext: Read isDualLanguagePath
    LeftSidebar->>LeftSidebar: buildLinkWithParams preserves lang params
    
    MDXWrapper->>MDXWrapper: Detect client_/agent_ prefixed languages
    MDXWrapper->>MDXWrapper: Filter code blocks based on clientLanguage/agentLanguage
    MDXWrapper->>CodeBlock: Pass language selection context
    
    CodeBlock->>CodeBlock: Evaluate If conditions (client_lang, agent_lang, etc.)
    CodeBlock->>User: Render filtered content for selected language
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Dual tongues now dance in harmony,
Client whispers, Agent speaks free,
Language selectors bloom and split,
Context flows—dual-language knit!
Guides expand in every tongue, 🌍✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing a dual language selector feature for AI Transport guides, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR comprehensively addresses AIT-108 by implementing dual language selection (Client/Agent) for framework/language combinations in AI Transport guides with UI, routing, and content updates.
Out of Scope Changes check ✅ Passed All changes are within scope of AIT-108: dual language selector implementation, language-aware navigation, MDX updates for client/agent code blocks, and supporting infrastructure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch AIT-108-Framework-language-feature-combos-investigate-and-design-navigation

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ably-ci ably-ci temporarily deployed to ably-docs-ait-108-frame-bxs5nk February 2, 2026 16:56 Inactive
@ably-ci ably-ci temporarily deployed to ably-docs-ait-108-frame-gtap38 February 2, 2026 17:03 Inactive
@ably-ci ably-ci temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 4, 2026 14:34 Inactive
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 4, 2026 14:52 Inactive
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 6, 2026 12:28 Inactive
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 6, 2026 12:29 Inactive
@GregHolmes GregHolmes had a problem deploying to ably-docs-ait-108-frame-6jgsbd February 6, 2026 13:51 Failure
@GregHolmes GregHolmes had a problem deploying to ably-docs-ait-108-frame-6jgsbd February 6, 2026 13:55 Failure
@GregHolmes GregHolmes force-pushed the AIT-108-Framework-language-feature-combos-investigate-and-design-navigation branch from a5fe2ec to c5eb002 Compare February 6, 2026 13:55
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 6, 2026 13:56 Inactive
@mschristensen
Copy link
Contributor

I haven't reviewed the code in detail, I will leave that to someone in the docs/deved team. But from a UX perspective, I really like the approach and I am happy with this :)

@GregHolmes GregHolmes force-pushed the AIT-108-Framework-language-feature-combos-investigate-and-design-navigation branch from c5eb002 to 71caaea Compare February 11, 2026 09:59
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 11, 2026 09:59 Inactive
@GregHolmes GregHolmes had a problem deploying to ably-docs-ait-108-frame-6jgsbd February 11, 2026 17:01 Failure
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 11, 2026 17:02 Inactive
@GregHolmes GregHolmes requested a deployment to ably-docs-ait-108-frame-6jgsbd February 12, 2026 09:58 Abandoned
@GregHolmes
Copy link
Contributor Author

This branch is heavily dependant on ably/ably-ui#1060. Which I need the devex web team to review. I'm very sorry but you're not going to be able to view the deployment as it stands as I've made changes to the ably-ui.

Note: I've asked April to make me a early release candidate version for ably-ui that I can use in this branch. I'll update with a new comment when it happens.

I've added quite a few people to the reviews. I think I'd like to recommend what each person reviews!

@m-hulbert Could I please have you review this from the docs perspective?
@lawrence-forooghian and @ttypic Could you please review the JS/Swift/Java/Python code snippets that these two guides compile into? I think I've got them where they should be but would be happier with some experts in the languages checking them out.
@kennethkalmer and @matt423 Would it be possible for you to first review ably/ably-ui#1060 as this branch won't work without those changes. Then hopefully could you review the code in this PR following that?

Let me know if you have any questions.

@GregHolmes
Copy link
Contributor Author

I've now moved the ably-ui code over to the new package in website repo: https://github.com/ably/website/pull/7827

@lawrence-forooghian
Copy link
Contributor

I'm gonna see if the skill from #3192 can be helpful here

@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 13, 2026 13:39 Inactive
@GregHolmes GregHolmes force-pushed the AIT-108-Framework-language-feature-combos-investigate-and-design-navigation branch from 5911152 to de2bf59 Compare February 13, 2026 14:11
@GregHolmes GregHolmes temporarily deployed to ably-docs-ait-108-frame-6jgsbd February 13, 2026 14:12 Inactive
@lawrence-forooghian
Copy link
Contributor

I've put updated Swift translations on #3205 if you're interested

@lawrence-forooghian
Copy link
Contributor

Nice!

A few things that I noticed when viewing the Swift versions of the guides:

  • "The client code requires Xcode 15 or higher." — do you know where it took this data from?
  • anthropic-message-per-response and anthropic-message-per-token:
    • "Run the subscriber in a separate terminal" shouldn't be there
    • "With the subscriber running, run the publisher in another terminal" shouldn't have the "another terminal" part
    • "Run a subscriber in multiple separate terminals" shouldn't be there

- Add Client/Agent dropdowns for separate frontend/backend language selection
- Support fe_/be_ prefixed code blocks in MDX files
- Preserve language params when navigating between guide pages
- Add aiTransport languages (javascript, python, swift, java)
- Add Guides section to AI Transport nav
- Update anthropic-message-per-response guide with dual language code blocks
- Update @ably/ui to 17.13.1-dev.c839343a

URL params: ?fe_lang=xxx&be_lang=yyy
The test mock for layout-context was missing the isDualLanguagePath
function that LeftSidebar imports, causing TypeError during test runs.
Extends the If component to support dual-language page conditionals:
- fe_lang: show content when frontend language matches
- be_lang: show content when backend language matches
- fe_or_be_lang: show content when either matches (with deduplication)

Used in AI Transport guides for language-specific prerequisites and SDK links.
- Add directory references to file creation instructions (e.g., "In your
  ably-anthropic-agent directory, create publisher.mjs")
- Add cd commands to run instructions for publisher and subscriber
- Add directory context to Step 6 rewind instructions
- Fix Swift Package.swift code block to use fe_swift prefix
- Wrap URL code block in Code component with text language
Move "Message per response" before "Message per token" in navigation
for all AI providers (Anthropic, OpenAI, LangGraph, Vercel AI SDK).
…age-per-token guide

- Add multi-language support (Python, Java, Swift) to message-per-token
  guide, matching the structure of message-per-response
- Fix Python code in both guides: use AsyncAnthropic with async/await,
  use Message objects for publish with extras and append_message,
  use transport_params for echo suppression
- Scope "without await" asides to JavaScript only
@GregHolmes GregHolmes force-pushed the AIT-108-Framework-language-feature-combos-investigate-and-design-navigation branch from de2bf59 to 57f0404 Compare February 17, 2026 10:49
@matt423
Copy link
Member

matt423 commented Feb 17, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (6)
src/contexts/layout-context.tsx (1)

68-92: Minor: redundant URLSearchParams instantiation.

determineClientLanguage and determineAgentLanguage each create a new URLSearchParams from the same location.search string. Consider parsing once and passing the params object, similar to how buildLinkWithParams works in LeftSidebar.tsx.

♻️ Suggested refactor
-const determineClientLanguage = (location: string, _product: ProductKey | null): LanguageKey => {
-  const params = new URLSearchParams(location);
-  const clientLangParam = params.get('client_lang') as LanguageKey;
+const determineClientLanguage = (params: URLSearchParams): LanguageKey => {
+  const clientLangParam = params.get('client_lang') as LanguageKey;

   if (clientLangParam && CLIENT_LANGUAGES.includes(clientLangParam)) {
     return clientLangParam;
   }

   return DEFAULT_LANGUAGE;
 };

-const determineAgentLanguage = (location: string, _product: ProductKey | null): LanguageKey => {
-  const params = new URLSearchParams(location);
-  const agentLangParam = params.get('agent_lang') as LanguageKey;
+const determineAgentLanguage = (params: URLSearchParams): LanguageKey => {
+  const agentLangParam = params.get('agent_lang') as LanguageKey;

   if (agentLangParam && AGENT_LANGUAGES.includes(agentLangParam)) {
     return agentLangParam;
   }

   return DEFAULT_LANGUAGE;
 };

Then in the provider:

+    const searchParams = new URLSearchParams(location.search);
     const clientLanguage = isDualLanguage
-      ? determineClientLanguage(location.search, activePageData?.product ?? null)
+      ? determineClientLanguage(searchParams)
       : undefined;
     const agentLanguage = isDualLanguage
-      ? determineAgentLanguage(location.search, activePageData?.product ?? null)
+      ? determineAgentLanguage(searchParams)
       : undefined;

This also removes the unused _product parameters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/layout-context.tsx` around lines 68 - 92, Both
determineClientLanguage and determineAgentLanguage redundantly instantiate
URLSearchParams from the same location string and keep an unused _product
parameter; refactor them to accept a parsed URLSearchParams (or a shared params
object) instead of a location string and remove the unused _product argument,
then update the provider call site to parse new URLSearchParams once (following
the buildLinkWithParams pattern used in LeftSidebar.tsx) and pass that params
object into determineClientLanguage and determineAgentLanguage so each function
only reads params.get('client_lang') / params.get('agent_lang').
src/components/Layout/mdx/If.tsx (2)

42-48: client_or_agent_lang OR-check can produce a falsy non-boolean shouldShow.

When both clientLanguage and agentLanguage are undefined, clientMatches and agentMatches are both undefined. The expression shouldShow && (undefined || undefined) yields undefined rather than false. This works in practice (falsy → renders null), but it weakens the shouldShow invariant from boolean to boolean | undefined.

Consider explicit boolean coercion for clarity:

Suggested fix
   if (client_or_agent_lang !== undefined) {
     const splitLang = client_or_agent_lang.split(',');
-    const clientMatches = clientLanguage && splitLang.includes(clientLanguage);
-    const agentMatches = agentLanguage && splitLang.includes(agentLanguage);
-    shouldShow = shouldShow && (clientMatches || agentMatches);
+    const clientMatches = !!clientLanguage && splitLang.includes(clientLanguage);
+    const agentMatches = !!agentLanguage && splitLang.includes(agentLanguage);
+    shouldShow = shouldShow && (clientMatches || agentMatches);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Layout/mdx/If.tsx` around lines 42 - 48, The OR-check in
If.tsx can set shouldShow to undefined when both clientLanguage and
agentLanguage are undefined; update the client_or_agent_lang block to coerce
matches to booleans so shouldShow remains a boolean. Specifically, in the
client_or_agent_lang handling (variables: splitLang, clientMatches,
agentMatches, shouldShow), compute clientMatches and agentMatches as explicit
booleans (e.g., use !!clientLanguage && splitLang.includes(clientLanguage) or
Boolean(splitLang.includes(clientLanguage) && clientLanguage)) and then set
shouldShow = shouldShow && (clientMatches || agentMatches) so the right-hand
side is always true/false.

6-15: className and as props are declared in IfProps but never destructured or used.

className (Line 12) and as (Line 14) are part of the interface but are not destructured in the component signature on Line 17 and have no effect. If these are intended for future use, they silently accept and discard values. If unneeded, removing them tightens the public API.

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Layout/mdx/If.tsx` around lines 6 - 15, IfProps declares
className and as but the If component does not destructure or use them, so they
are silently ignored; either remove className and as from the IfProps interface
to tighten the API or update the If component signature (the component that
consumes IfProps) to destructure className and as and apply them (e.g., pass
className to the rendered wrapper element and use as to render a custom element
type) so those props take effect; reference IfProps, className, as and the If
component when making the change.
src/components/Layout/LanguageSelector.tsx (2)

300-310: aria-hidden is always false in the else branch (pre-existing).

On Line 309, aria-hidden={option.value === value} is inside the else branch of option.value === value ? ... : ... (Line 300), so the condition is always false. This is a no-op. The same pattern exists in SingleLanguageSelector at Line 154. Consider removing the attribute or replacing the spacer <div> with a proper approach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Layout/LanguageSelector.tsx` around lines 300 - 310, In
LanguageSelector (and the similar SingleLanguageSelector) the else-branch spacer
div renders aria-hidden={option.value === value} which will always be false
there; update the spacer to either omit the aria-hidden prop entirely or set
aria-hidden="true" so it correctly hides the decorative element from assistive
tech, and ensure the replacement element keeps the same sizing/classNames (the
spacer div that replaces Select.ItemIndicator should remain a non-interactive,
decorative element). Target the else branch next to Select.ItemIndicator and the
corresponding code in SingleLanguageSelector to apply the same change.

167-320: Substantial code duplication between SingleLanguageSelector and DualLanguageDropdown.

The dropdown rendering logic (trigger, portal, viewport, option items with icon/badge/check indicator, skeleton fallback) is nearly identical between the two components. Consider extracting a shared base dropdown component that both SingleLanguageSelector and DualLanguageDropdown compose, passing in label, options, value, and onValueChange.

This would reduce ~150 lines of duplicated JSX and make styling/behavior changes easier to maintain consistently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Layout/LanguageSelector.tsx` around lines 167 - 320, The
DualLanguageDropdown duplicates most of the JSX and behavior from
SingleLanguageSelector; extract a shared BaseLanguageDropdown component that
encapsulates the Select.Root/Trigger/Portal/Content/Viewport rendering, the
skeleton fallback, option mapping (Icon, Badge, ItemIndicator), and sizing
logic, and have both SingleLanguageSelector and DualLanguageDropdown call it
with props like label, options (LanguageSelectorOptionData[]), value,
onValueChange (handleValueChange), disabled flag, and a selectedLang lookup;
ensure BaseLanguageDropdown uses the same constants and utilities
(LANGUAGE_SELECTOR_HEIGHT, componentMaxHeight, Icon, Badge, Select.*) so both
components become thin wrappers that prepare options/selectedLanguage and
delegate rendering to the new BaseLanguageDropdown.
src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx (1)

470-514: Java publisher: channel and class-level fields referenced without full class context.

The Java processEvent snippet at Lines 470-514 references channel (Line 489) and declares responseId as a private static String (Line 475), but these are meant to be added to the Publisher class defined earlier. Since guides instruct users to progressively build the file, this is understandable — but a brief note like "Add these fields and update the method in your Publisher class" would reduce ambiguity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx` around
lines 470 - 514, The snippet references class-level state and a channel that
belong in your Publisher class; move the private static String responseId field
into the Publisher class and add a channel field (the same type used elsewhere
in Publisher) there, then update the processEvent method to be a member of
Publisher (or make it static if you prefer) so it uses Publisher's responseId
and channel fields; ensure the method signature and access modifiers match the
existing Publisher class conventions (e.g., processEvent(...) inside Publisher)
and that any imports/types used (MessageExtras, JsonObject,
RawMessageStreamEvent, ContentBlockDeltaEvent) are available in that class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 43: package.json currently pins the `@ably/ui` dependency to a dev
prerelease ("@ably/ui": "17.13.2-dev.aa83caf92f"); replace this prerelease
version with a stable release (e.g., the corresponding released semver like
"17.13.2" or a caret range such as "^17.13.2") before merging. Update the
"@ably/ui" entry in package.json to a published stable version and run your
package manager install and a quick build/test to ensure compatibility.

In `@src/components/Layout/MDXWrapper.tsx`:
- Around line 170-181: The handler handleLanguageChange references
location.pathname but location isn't in scope; import useLocation from
"@reach/router" and call const location = useLocation() in the component where
handleLanguageChange/WrappedCodeSnippet is defined (or in MDXWrapper and pass it
down) so the function uses the router-provided location instead of the global
object; update the code to remove reliance on the global location and use the
location from useLocation() when calling
navigate(`${location.pathname}?lang=${lang}`).

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-response.mdx`:
- Around line 945-990: The Java snippet for AblyRealtime/Channel setup and the
subscribe lambda (references: ClientOptions, AblyRealtime, ChannelOptions,
Channel, channel.subscribe, responses map) is shown at top-level and will not
compile; wrap these statements inside a proper class and method (e.g., add them
into the existing Subscriber class or a new method like subscribeToChannel()) or
add a clear comment indicating they belong inside Subscriber.java’s
main/mainSubscribe method so the field declarations (responses) and the
subscribe call are within a valid class/method scope.
- Around line 631-670: The Swift example uses the wrong ARTMessageAction enum
case `.messageAppend`; update the switch in the channel.subscribe closure to use
the correct ably-cocoa enum case `.append` (the switch on message.action where
`.messageAppend` appears should be renamed to `.append`) so the handler for
appending tokens matches the ARTMessageAction API; leave the other cases
(`.create`, `.update`) and the responses dictionary handling unchanged.
- Around line 485-527: The code in processEvent reads message.serial
synchronously after calling channel.publish(message, listener), causing a race
because the server-assigned serial is provided asynchronously; move the
assignment of msgSerial into the PublishResult callback by implementing
CompletionListener.onSuccess to extract the serial (PublishResult.serials[0])
and set msgSerial there, and keep channel.appendMessage using msgSerial as
before; update the CompletionListener implementation passed to channel.publish
inside the message start branch to capture the serial instead of reading
message.serial directly.
- Around line 449-483: The code calls channel.append_message (not available in
ably-python); replace the append_message branch in process_event to publish
incremental token chunks via channel.publish instead of append_message (e.g.,
use channel.publish('response', event.delta.text)), remove or stop relying on
Message and msg_serial for appending, and add a brief comment noting that true
appendMessage behavior is a future SDK feature if you want to preserve intent;
update imports/usages (Message, msg_serial) accordingly.

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx`:
- Around line 340-349: The Python example incorrectly passes
transport_params={'echo': 'false'} to AblyRealtime (AblyRealtime) which the
Python SDK does not support for disabling echo; remove the transport_params
argument from the AblyRealtime(...) call in this snippet and either (a) document
that echo control via echoMessages is not available in the Python SDK (mention
echoMessages and ClientOptions.echoMessages for JS/Java) or (b) add a short
application-level workaround suggestion in the doc (e.g., filter incoming
messages by client_id on subscribe) so the example and Aside reflect accurate
Python behavior.

---

Nitpick comments:
In `@src/components/Layout/LanguageSelector.tsx`:
- Around line 300-310: In LanguageSelector (and the similar
SingleLanguageSelector) the else-branch spacer div renders
aria-hidden={option.value === value} which will always be false there; update
the spacer to either omit the aria-hidden prop entirely or set
aria-hidden="true" so it correctly hides the decorative element from assistive
tech, and ensure the replacement element keeps the same sizing/classNames (the
spacer div that replaces Select.ItemIndicator should remain a non-interactive,
decorative element). Target the else branch next to Select.ItemIndicator and the
corresponding code in SingleLanguageSelector to apply the same change.
- Around line 167-320: The DualLanguageDropdown duplicates most of the JSX and
behavior from SingleLanguageSelector; extract a shared BaseLanguageDropdown
component that encapsulates the Select.Root/Trigger/Portal/Content/Viewport
rendering, the skeleton fallback, option mapping (Icon, Badge, ItemIndicator),
and sizing logic, and have both SingleLanguageSelector and DualLanguageDropdown
call it with props like label, options (LanguageSelectorOptionData[]), value,
onValueChange (handleValueChange), disabled flag, and a selectedLang lookup;
ensure BaseLanguageDropdown uses the same constants and utilities
(LANGUAGE_SELECTOR_HEIGHT, componentMaxHeight, Icon, Badge, Select.*) so both
components become thin wrappers that prepare options/selectedLanguage and
delegate rendering to the new BaseLanguageDropdown.

In `@src/components/Layout/mdx/If.tsx`:
- Around line 42-48: The OR-check in If.tsx can set shouldShow to undefined when
both clientLanguage and agentLanguage are undefined; update the
client_or_agent_lang block to coerce matches to booleans so shouldShow remains a
boolean. Specifically, in the client_or_agent_lang handling (variables:
splitLang, clientMatches, agentMatches, shouldShow), compute clientMatches and
agentMatches as explicit booleans (e.g., use !!clientLanguage &&
splitLang.includes(clientLanguage) or Boolean(splitLang.includes(clientLanguage)
&& clientLanguage)) and then set shouldShow = shouldShow && (clientMatches ||
agentMatches) so the right-hand side is always true/false.
- Around line 6-15: IfProps declares className and as but the If component does
not destructure or use them, so they are silently ignored; either remove
className and as from the IfProps interface to tighten the API or update the If
component signature (the component that consumes IfProps) to destructure
className and as and apply them (e.g., pass className to the rendered wrapper
element and use as to render a custom element type) so those props take effect;
reference IfProps, className, as and the If component when making the change.

In `@src/contexts/layout-context.tsx`:
- Around line 68-92: Both determineClientLanguage and determineAgentLanguage
redundantly instantiate URLSearchParams from the same location string and keep
an unused _product parameter; refactor them to accept a parsed URLSearchParams
(or a shared params object) instead of a location string and remove the unused
_product argument, then update the provider call site to parse new
URLSearchParams once (following the buildLinkWithParams pattern used in
LeftSidebar.tsx) and pass that params object into determineClientLanguage and
determineAgentLanguage so each function only reads params.get('client_lang') /
params.get('agent_lang').

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx`:
- Around line 470-514: The snippet references class-level state and a channel
that belong in your Publisher class; move the private static String responseId
field into the Publisher class and add a channel field (the same type used
elsewhere in Publisher) there, then update the processEvent method to be a
member of Publisher (or make it static if you prefer) so it uses Publisher's
responseId and channel fields; ensure the method signature and access modifiers
match the existing Publisher class conventions (e.g., processEvent(...) inside
Publisher) and that any imports/types used (MessageExtras, JsonObject,
RawMessageStreamEvent, ContentBlockDeltaEvent) are available in that class.

Comment on lines +170 to +181
// For client/agent blocks, the page-level selector controls language, so disable internal onChange
const handleLanguageChange = (lang: string, newSdk: SDKType | undefined) => {
// Don't navigate for client/agent blocks - page-level selector handles this
if (detectedSdkType === 'client' || detectedSdkType === 'agent') {
return;
}

// If it's a utility language, return the language to use as override
return lang && UTILITY_LANGUAGES.includes(lang) ? lang : null;
}, [processedChildren]);
if (!detectedSdkType) {
setSdk(newSdk ?? undefined);
}
navigate(`${location.pathname}?lang=${lang}`);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

wc -l src/components/Layout/MDXWrapper.tsx

Repository: ably/docs

Length of output: 93


🏁 Script executed:

cat -n src/components/Layout/MDXWrapper.tsx | head -250

Repository: ably/docs

Length of output: 10803


🏁 Script executed:

# Check the exact context around lines 170-181 and where handleLanguageChange is called
cat -n src/components/Layout/MDXWrapper.tsx | sed -n '150,220p'

Repository: ably/docs

Length of output: 3727


🏁 Script executed:

# Search for all references to handleLanguageChange to understand its scope
rg -n 'handleLanguageChange' src/components/Layout/MDXWrapper.tsx

Repository: ably/docs

Length of output: 178


🏁 Script executed:

# Check how `@reach/router` or useLocation is used in the codebase
rg -n 'useLocation|@reach/router' src/

Repository: ably/docs

Length of output: 5625


handleLanguageChange uses location without it being in scope.

Line 180 references location.pathname inside WrappedCodeSnippet, but the component doesn't receive location as a prop (line 67 component signature). The parent MDXWrapper receives location from Gatsby (line 208) but doesn't pass it down. This relies on the global location object, which is unavailable during SSR or in test environments.

Use useLocation() from @reach/router (already a pattern in this codebase—see HiddenLanguageLinks, LanguageSelector, Header, etc.) inside handleLanguageChange:

+import { useLocation } from '@reach/router';

const WrappedCodeSnippet: React.FC<{ activePage: ActivePage } & CodeSnippetProps> = ({
  activePage,
  apiKeys,
  children,
  ...props
}) => {
  const { sdk, setSdk } = useSDK();
+  const location = useLocation();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Layout/MDXWrapper.tsx` around lines 170 - 181, The handler
handleLanguageChange references location.pathname but location isn't in scope;
import useLocation from "@reach/router" and call const location = useLocation()
in the component where handleLanguageChange/WrappedCodeSnippet is defined (or in
MDXWrapper and pass it down) so the function uses the router-provided location
instead of the global object; update the code to remove reliance on the global
location and use the location from useLocation() when calling
navigate(`${location.pathname}?lang=${lang}`).

Comment on lines +449 to +483
```agent_python
from ably.types.message import Message

# Track state across events
msg_serial = None
text_block_index = None

# Process each streaming event and publish to Ably
async def process_event(event):
global msg_serial, text_block_index

if event.type == 'message_start':
# Publish initial empty message when response starts
result = await channel.publish('response', '')

# Capture the message serial for appending tokens
msg_serial = result.serials[0]

elif event.type == 'content_block_start':
# Capture text block index when a text content block is added
if event.content_block.type == 'text':
text_block_index = event.index

elif event.type == 'content_block_delta':
# Append tokens from text deltas only
if (event.index == text_block_index and
hasattr(event.delta, 'text') and
msg_serial):
await channel.append_message(
Message(serial=msg_serial, data=event.delta.text)
)

elif event.type == 'message_stop':
print('Stream completed!')
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

ably-python SDK channel.publish return type serials

💡 Result:

In the current ably-python SDK, Channel.publish(...) is an async method, and when you await it, it returns a PublishResult object (not None). (raw.githubusercontent.com)

Example:

result = await channel.publish("event", {"x": 1})
serials = result.serials
``` ([raw.githubusercontent.com](https://raw.githubusercontent.com/ably/ably-python/main/ably/rest/channel.py))

Citations:

- 1: https://raw.githubusercontent.com/ably/ably-python/main/ably/rest/channel.py
- 2: https://raw.githubusercontent.com/ably/ably-python/main/ably/rest/channel.py
- 3: https://raw.githubusercontent.com/ably/ably-python/main/ably/types/operations.py
- 4: https://raw.githubusercontent.com/ably/ably-python/main/ably/rest/channel.py

---

🌐 Web query:

`ably-python append_message API message history`

💡 Result:

In Ably, **`appendMessage()` (a.k.a. “append message”)** appends new `data` onto an *existing* message identified by its Ably-assigned **`serial`** (you get the serial from the `publish()` result, from a subscribed message, or from `history`). When Ably processes an append, it builds the latest full message and uses that full (non-incremental) form for **History** resultsso **history queries return the complete, latest message content**, not a list of individual appended chunks. [1]

For **message history retention**:

- By default, Ably retains messages **in memory for ~2 minutes** (useful for short disconnect recovery). [2]
- For retention beyond that, you must **enable message persistence** for the channel/namespace using a dashboard **channel rule** (history is disabled by default). [3]
- If you enable the **Message annotations, updates, deletes and appends** channel rule (required for appends), Ably notes that messages are **persisted regardless of whether persistence is otherwise enabled**, which can affect usage/billing. [1]

Notes onhistory vs versions”:

- **Updates/deletes** replace what you see in history (history shows the latest version in the original position) and you can retrieve a messages version history. [1]
- **Appends are designed for high-frequency incremental building**; subscribers may receive `message.append` events (possibly conflated), but history/rewind is intended to show the fully aggregated message. [1]

**Sources**  
[1] Ably docsUpdates, deletes and appends (incl. append behavior + history semantics + channel rule requirement) https://ably.com/docs/messages/updates-deletes  
[2] Ably docsGetting started in Python (history section; 2-minute default retention) https://ably.com/docs/getting-started/python  
[3] Ably FAQHistory is disabled by default; enable persistence via channel rules https://faqs.ably.com/how-do-i-enable-history-my-messages-are-not-being-stored-for-longer-than-a-few-minutes

---

🌐 Web query:

`ably-python Channel append_message method documentation`

💡 Result:

The Ably Pub/Sub **Python SDK (`ably-python`) does not currently document (or generally expose) a `Channel.append_message` method**. The SDK README notes it supports **Ably REST and basic realtime message subscriptions**, not the full realtime feature set where message update/append APIs typically live. [1]

Ablys platform feature for appending to an existing message is documented as **`appendMessage()`** (camelCase) on a REST or realtime channel, as part of **Updates, deletes and appends** (Public Preview). That page also notes the feature **may not yet be implemented in all SDKs**. [2]

If youre looking for the *conceptual/HTTP-level* behavior and requirements (serial, required capabilities, etc.), use AblysUpdates, deletes and appendsdocumentation. [2]

**Sources:**  
[1] Ably `ably-python` GitHub repository README (feature/support notes) ([github.com](https://github.com/ably/ably-python))  
[2] Ably docs: “Updates, deletes and appends” (includes `appendMessage()` and SDK-availability note) ([ably.com](https://ably.com/docs/messages/updates-deletes?utm_source=openai))

Citations:

- 1: https://github.com/ably/ably-python
- 2: https://ably.com/docs/messages/updates-deletes?utm_source=openai

---



</details>

**Remove or update `channel.append_message()` callAPI does not exist in ably-python SDK.**

Lines 462465 correctly use `channel.publish()` and access `.serials[0]` (verified against ably-python SDK). However, line 477 calls `channel.append_message()`, which is **not yet implemented** in the ably-python SDK. The `appendMessage()` feature exists in Ably as a Public Preview feature in some SDKs, but ably-python does not currently expose this method. Either replace with an alternative approach, document the limitation, or indicate this requires a future SDK version.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @src/pages/docs/guides/ai-transport/anthropic-message-per-response.mdx around
lines 449 - 483, The code calls channel.append_message (not available in
ably-python); replace the append_message branch in process_event to publish
incremental token chunks via channel.publish instead of append_message (e.g.,
use channel.publish('response', event.delta.text)), remove or stop relying on
Message and msg_serial for appending, and add a brief comment noting that true
appendMessage behavior is a future SDK feature if you want to preserve intent;
update imports/usages (Message, msg_serial) accordingly.


</details>

<!-- fingerprinting:phantom:medusa:phoenix -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +485 to +527
```agent_java
// Track state across events
private static String msgSerial = null;
private static Long textBlockIndex = null;

// Process each streaming event and publish to Ably
private static void processEvent(RawMessageStreamEvent event) throws AblyException {
if (event.isMessageStart()) {
// Publish initial empty message when response starts
io.ably.lib.types.Message message = new io.ably.lib.types.Message("response", "");
CompletionListener listener = new CompletionListener() {
@Override
public void onSuccess() {}
@Override
public void onError(ErrorInfo reason) {}
};
channel.publish(message, listener);

// Capture the message serial for appending tokens
// Note: In production, use the callback to get the serial
msgSerial = message.serial;

} else if (event.isContentBlockStart()) {
// Capture text block index when a text content block is added
ContentBlockStartEvent blockStart = event.asContentBlockStart();
if (blockStart.contentBlock().isText()) {
textBlockIndex = blockStart.index();
}

} else if (event.isContentBlockDelta()) {
// Append tokens from text deltas only
ContentBlockDeltaEvent delta = event.asContentBlockDelta();
if (delta.index().equals(textBlockIndex) &&
delta.delta().isTextDelta() &&
msgSerial != null) {
String text = delta.delta().asTextDelta().text();
channel.appendMessage(msgSerial, text);
}

} else if (event.isMessageStop()) {
System.out.println("Stream completed!");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Ably Java SDK channel.publish() message serial synchronous asynchronous

💡 Result:

Ably Java SDK channel.publish(...): sync/async + message serial

Realtime (io.ably.lib.realtime.Channel / ChannelBase)

  • channel.publish(...) is non-blocking (asynchronous) in the Java Realtime SDK (annotated @NonBlocking) and returns void. If you need to know when it’s actually been accepted/sent (or failed), use the overload that takes a Callback<PublishResult>. The callback is invoked on a background thread. [1]
  • To get the message serial for what you published, you must use the callback overloads, because on success the callback receives a PublishResult that includes the message serial(s). [1]

REST (io.ably.lib.rest.Channel)

  • channel.publish(name, data) is blocking (synchronous) in the Java REST SDK (annotated @Blocking). [2]
  • If you want a synchronous call and you need the serial(s) returned, use publishWithResult(...), which returns PublishResult containing the serial(s). [2]
  • REST also provides publishAsync(..., Callback<PublishResult>) for an explicit async REST publish. [2]

What is “message serial” and how does it relate to ordering?

  • Ably’s realtime system uses a unique incrementing message serial on messages sent over a realtime connection, enabling reliable ordering for persistently-connected realtime subscribers on a channel (they receive messages in the order published on that channel). [3]

Sources:
[1] Realtime ChannelBase.publish(...) Java API docs (callbacks return PublishResult with serial(s), non-blocking) https://ably.com/docs/sdk/java/v1.2/io/ably/lib/realtime/ChannelBase.html
[2] REST ChannelBase.publish(...) / publishWithResult(...) Java API docs (blocking vs async, PublishResult contains serial(s)) https://ably.com/docs/sdk/java/v1.2/io/ably/lib/rest/ChannelBase.html
[3] Ably FAQ on message ordering + incrementing message serial https://faqs.ably.com/reliable-message-ordering-for-connected-clients


🌐 Web query:

Ably Java SDK message.serial when is it set Realtime PublishResult callback

💡 Result:

In the Ably Java Realtime SDK, the Message.serial you’re thinking of is server-assigned and is primarily used to reference a message later (for annotations / update / delete / append). It is not something you can rely on being set on the Message instance you pass to channel.publish(...).

What you do get back in the Realtime publish callback is a PublishResult, whose serials field contains the server-assigned serial(s), 1:1 with the messages you published (and a serial can be null if the message was discarded due to conflation rules). That array is the point at which the serial is available to the publisher. [1]

Also note: Ably documents Message.serial as being set only when message annotations/updates/deletes/appends are enabled via channel rules (because that serial is what those features use to target a specific message). [2][3]

So:

  • The serial is set by Ably when the publish is accepted/processed server-side, and is surfaced to the publisher via PublishResult.serials in the publish callback. [1]
  • Subscribers may see message.serial on received messages when those message-mutation/annotation features are enabled. [2][3]

Sources:
[1] Ably Java SDK Javadoc: PublishResult.serials (and null if conflated) — https://ably.com/docs/sdk/java/v1.2/io/ably/lib/types/PublishResult.html
[2] Ably “Messages” docs: meaning/availability of Message.serialhttps://ably.com/docs/api/realtime-sdk/messages
[3] Ably “Message annotations” docs: annotations target message.serialhttps://ably.com/docs/messages/annotations


Java publisher: race condition reading message.serial before server assigns it.

Line 501 calls channel.publish(message, listener) which is asynchronous in the Realtime SDK, but Line 505 reads msgSerial = message.serial; immediately — before the server has processed the message. The serial is server-assigned and only available in the PublishResult passed to the callback's onSuccess() method, not on the Message object. Reading it synchronously will return null, causing subsequent appendMessage calls on Line 521 to fail.

The comment on Line 504 correctly identifies the solution: capture the serial inside the callback. Extract serial from PublishResult.serials[0] in the onSuccess() method instead of reading it from the message object.

Correct fix: extract serial from PublishResult in callback
     if (event.isMessageStart()) {
         // Publish initial empty message when response starts
         io.ably.lib.types.Message message = new io.ably.lib.types.Message("response", "");
         CompletionListener listener = new CompletionListener() {
             `@Override`
-            public void onSuccess() {}
+            public void onSuccess() {
+                // Capture the message serial from the server response
+                if (listener.result != null && listener.result.serials != null && listener.result.serials.length > 0) {
+                    msgSerial = listener.result.serials[0];
+                }
+            }
             `@Override`
             public void onError(ErrorInfo reason) {}
         };
         channel.publish(message, listener);
-
-        // Capture the message serial for appending tokens
-        // Note: In production, use the callback to get the serial
-        msgSerial = message.serial;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-response.mdx` around
lines 485 - 527, The code in processEvent reads message.serial synchronously
after calling channel.publish(message, listener), causing a race because the
server-assigned serial is provided asynchronously; move the assignment of
msgSerial into the PublishResult callback by implementing
CompletionListener.onSuccess to extract the serial (PublishResult.serials[0])
and set msgSerial there, and keep channel.appendMessage using msgSerial as
before; update the CompletionListener implementation passed to channel.publish
inside the message start branch to capture the serial instead of reading
message.serial directly.

Comment on lines +631 to +670
```client_swift
import Ably

// Initialize Ably Realtime client
let realtime = ARTRealtime(key: "{{API_KEY}}")

// Get the same channel used by the publisher
let channel = realtime.channels.get("ai:{{RANDOM_CHANNEL_NAME}}")

// Track responses by message serial
var responses: [String: String] = [:]

// Subscribe to receive messages
channel.subscribe { message in
guard let serial = message.serial else { return }

switch message.action {
case .create:
// New response started
print("\n[Response started] \(serial)")
responses[serial] = message.data as? String ?? ""

case .messageAppend:
// Append token to existing response
let current = responses[serial] ?? ""
let token = message.data as? String ?? ""
responses[serial] = current + token

// Display token as it arrives
print(token, terminator: "")

case .update:
// Replace entire response content
responses[serial] = message.data as? String ?? ""
print("\n[Response updated with full content]")

default:
break
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

ably-cocoa ARTMessageAction enum cases Swift SDK documentation

💡 Result:

In the Ably Cocoa (ably-cocoa) Swift SDK, ARTMessageAction has these enum cases (with raw values): create = 0, update = 1, delete = 2, meta = 3, messageSummary = 4, append = 5. [1]

Sources
[1] Ably Cocoa SDK API Reference — ARTMessageAction Enumeration Reference (ably.com)


Fix incorrect enum case name in Swift code example.

Line 653 uses .messageAppend, but the correct ARTMessageAction enum case in the ably-cocoa SDK is .append. Update this case name to match the actual SDK API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-response.mdx` around
lines 631 - 670, The Swift example uses the wrong ARTMessageAction enum case
`.messageAppend`; update the switch in the channel.subscribe closure to use the
correct ably-cocoa enum case `.append` (the switch on message.action where
`.messageAppend` appears should be renamed to `.append`) so the handler for
appending tokens matches the ARTMessageAction API; leave the other cases
(`.create`, `.update`) and the responses dictionary handling unchanged.

Comment on lines +945 to +990
```client_java
import io.ably.lib.realtime.AblyRealtime;
import io.ably.lib.realtime.Channel;
import io.ably.lib.types.ClientOptions;
import io.ably.lib.types.ChannelOptions;
import io.ably.lib.types.Param;
import java.util.HashMap;
import java.util.Map;

// Use rewind to receive recent historical messages
ClientOptions clientOptions = new ClientOptions("{{API_KEY}}");
AblyRealtime realtime = new AblyRealtime(clientOptions);

ChannelOptions channelOptions = new ChannelOptions();
channelOptions.params = new Param[] {
new Param("rewind", "2m") // Retrieve messages from the last 2 minutes
};

Channel channel = realtime.channels.get("ai:{{RANDOM_CHANNEL_NAME}}", channelOptions);

Map<String, String> responses = new HashMap<>();

channel.subscribe(message -> {
String serial = message.serial;
if (serial == null) return;

switch (message.action) {
case MESSAGE_CREATE:
responses.put(serial, message.data != null ? message.data.toString() : "");
break;

case MESSAGE_APPEND:
String current = responses.getOrDefault(serial, "");
String token = message.data != null ? message.data.toString() : "";
responses.put(serial, current + token);
System.out.print(token);
break;

case MESSAGE_UPDATE:
// Historical messages contain full concatenated response
responses.put(serial, message.data != null ? message.data.toString() : "");
System.out.println("\n[Historical response]: " + responses.get(serial));
break;
}
});
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Java rewind subscriber snippet appears outside a class context.

Lines 945-990 show Java code (variable declarations, channel setup, subscribe call) that is not wrapped in a class or method. Unlike the earlier Subscriber.java that has a proper class structure, this snippet would not compile as shown. Consider wrapping it in a method or adding a comment indicating where in the Subscriber class it should be placed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-response.mdx` around
lines 945 - 990, The Java snippet for AblyRealtime/Channel setup and the
subscribe lambda (references: ClientOptions, AblyRealtime, ChannelOptions,
Channel, channel.subscribe, responses map) is shown at top-level and will not
compile; wrap these statements inside a proper class and method (e.g., add them
into the existing Subscriber class or a new method like subscribeToChannel()) or
add a clear comment indicating they belong inside Subscriber.java’s
main/mainSubscribe method so the field declarations (responses) and the
subscribe call are within a valid class/method scope.

Comment on lines +340 to +349

```agent_python
from ably import AblyRealtime

# Initialize Ably Realtime client
realtime = AblyRealtime(key='{{API_KEY}}', transport_params={'echo': 'false'})

# Create a channel for publishing streamed AI responses
channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}')
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Ably Python SDK transport_params echo messages documentation

💡 Result:

echoMessages (echo / self-receive published messages)

  • Ably Realtime clients echo messages back to the publisher by default; you can disable that by setting the echoMessages client option to false. [1][2]

transportParams (connection / transport tuning)

  • Ably Realtime clients support a transportParams (key/value) client option used to pass transport-level parameters such as:
    • heartbeatInterval (milliseconds) to request a different heartbeat interval. [3]
    • remainPresentFor (milliseconds) to control how long a client stays in presence after an abrupt disconnect. [4]

Python SDK status / where to look

  • The official ably (ably-python) SDK exists, but its README notes full Realtime support is unavailable (it supports REST + basic realtime subscriptions; for full Realtime features they suggest using MQTT). That means some advanced Realtime client options you may see in other SDKs’ docs (like transportParams, echoMessages) may not be available/exposed in the Python SDK in the same way. [5]

Sources:
[1] https://ably.com/docs/pub-sub/advanced
[2] https://faqs.ably.com/is-it-possible-to-prevent-messages-published-being-echoed-back-to-the-publishing-client
[3] https://ably.com/docs/connect
[4] https://ably.com/docs/ai-transport/sessions-identity/online-status
[5] https://github.com/ably/ably-python


🌐 Web query:

Ably Python SDK AblyRealtime echoMessages parameter

💡 Result:

echoMessages is an Ably client option (default true) that, when set to false, prevents messages published on a connection from being echoed back to the same connection. [1][2]

In the Ably Python SDK (ably / AblyRealtime), there is no echoMessages / echo_messages option exposed in the client Options used by AblyRealtime, so you can’t set it (passing it will error because Python Options/AuthOptions don’t accept that keyword). [3][4][5]

Workaround (Python): do application-level filtering (e.g., set a client_id and ignore received messages where message.client_id == my_client_id).


🏁 Script executed:

# Search for Python SDK documentation or examples in the codebase
fd -t f "\.md" "\.mdx" | xargs rg -l "transport_params|python.*echo" | head -20

Repository: ably/docs

Length of output: 646


🏁 Script executed:

# Search for other Python examples in the Anthropic guide
rg -A 5 "agent_python" src/pages/docs/guides/ai-transport/ --type mdx

Repository: ably/docs

Length of output: 80


🏁 Script executed:

# Check if there are any other Python + Realtime examples
rg -B 2 -A 5 "AblyRealtime.*python|agent_python" src/pages/docs/ --type mdx | head -50

Repository: ably/docs

Length of output: 80


🏁 Script executed:

# Read the specific file to see the context around lines 340-349
cat -n src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx | sed -n '330,360p'

Repository: ably/docs

Length of output: 1111


🏁 Script executed:

# Check the surrounding context to understand what this code is trying to do
cat -n src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx | sed -n '320,370p'

Repository: ably/docs

Length of output: 2167


Python SDK does not support disabling echo via echoMessages or transport_params.

The JavaScript example (line 334) correctly uses echoMessages: false, and the Java example (line 358) uses ClientOptions.echoMessages = false. However, the Python SDK does not expose an echoMessages option, and transport_params is reserved for transport-level tuning (heartbeat intervals, presence timeouts) — not echo control. The current Python code (transport_params={'echo': 'false'}) will not achieve the documented behavior in the Aside note (line 369).

For the Python SDK, remove the transport_params parameter since echo disabling is not available, or provide an application-level workaround (e.g., filter messages by client_id). Alternatively, clarify in the documentation that this feature is only available in JavaScript, Java, and other SDKs with full Realtime support.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/docs/guides/ai-transport/anthropic-message-per-token.mdx` around
lines 340 - 349, The Python example incorrectly passes transport_params={'echo':
'false'} to AblyRealtime (AblyRealtime) which the Python SDK does not support
for disabling echo; remove the transport_params argument from the
AblyRealtime(...) call in this snippet and either (a) document that echo control
via echoMessages is not available in the Python SDK (mention echoMessages and
ClientOptions.echoMessages for JS/Java) or (b) add a short application-level
workaround suggestion in the doc (e.g., filter incoming messages by client_id on
subscribe) so the example and Aside reflect accurate Python behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-app Create a Heroku review app

Development

Successfully merging this pull request may close these issues.

5 participants