-
Notifications
You must be signed in to change notification settings - Fork 0
[DX-790] Add support for Chat message updates and deletes #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
71d0a20
Add support for Chat message updates and deletes
umair-ably 27f571d
Address PR feedback + remove redundant subscribes
umair-ably b03fd6c
align formatting between json and non json outputs + adds serial outp…
umair-ably 1229599
update skills
umair-ably 06aae1c
fix formatting and set msgpack to false for chat (not supported)
umair-ably File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ Every command in this CLI falls into one of these patterns. Pick the right one b | |
| | **Get** | One-shot query for current state | `AblyBaseCommand` | REST | `channels occupancy get`, `rooms occupancy get` | | ||
| | **List** | Enumerate resources via REST API | `AblyBaseCommand` | REST | `channels list`, `rooms list` | | ||
| | **Enter** | Join presence/space then optionally listen | `AblyBaseCommand` | Realtime | `channels presence enter`, `spaces members enter` | | ||
| | **REST Mutation** | One-shot REST mutation (no subscription) | `AblyBaseCommand` | REST | `rooms messages update`, `rooms messages delete` | | ||
| | **CRUD** | Create/read/update/delete via Control API | `ControlBaseCommand` | Control API (HTTP) | `integrations create`, `queues delete` | | ||
|
|
||
| **Specialized base classes** — some command groups have dedicated base classes that handle common setup (client lifecycle, cleanup, shared flags): | ||
|
|
@@ -31,6 +32,25 @@ Every command in this CLI falls into one of these patterns. Pick the right one b | |
|
|
||
| If your command falls into one of these groups, extend the specialized base class instead of `AblyBaseCommand` or `ControlBaseCommand` directly. **Exception:** if your command only needs a REST client (e.g., history queries that don't enter a space or join a room), you may use `AblyBaseCommand` directly — the specialized base class is most valuable when the command needs realtime connections, cleanup lifecycle, or shared setup like `room.attach()` / `space.enter()`. | ||
|
|
||
| ### When to call `room.attach()` / `space.enter()` | ||
|
|
||
| **Not every Chat/Spaces command needs `room.attach()` or `space.enter()`.** Before adding attachment, check whether the SDK method you're calling requires an active realtime connection or is a pure REST call: | ||
|
|
||
| | Needs `room.attach()` | Does NOT need `room.attach()` | | ||
| |------------------------|-------------------------------| | ||
| | Subscribe (realtime listener) | Send (REST via `chatApi.sendMessage`) | | ||
| | Presence enter/get/update/leave | Update (REST via `chatApi.updateMessage`) | | ||
| | Occupancy subscribe | Delete (REST via `chatApi.deleteMessage`) | | ||
| | Typing keystroke/stop | Annotate/append (REST mutation) | | ||
| | Reactions send (realtime publish) | History queries (REST via `chatApi.history`) | | ||
| | Reactions subscribe | Occupancy get (REST via `chatApi.getOccupancy`) | | ||
|
|
||
| **How it works in the SDK:** Methods that go through `this._chatApi.*` are REST calls and don't need attachment. Methods that use `this._channel.publish()`, `this._channel.presence.*`, or subscribe to channel events require the realtime channel to be attached. Room-level reactions use `this._channel.publish()` (realtime), but messages send/update/delete use `this._chatApi.*` (REST). | ||
|
|
||
| **Rule of thumb:** If the SDK method is a one-shot REST call (returns a Promise with a result, no callback/listener), you do NOT need `room.attach()`. Just call `chatClient.rooms.get(roomId)` to get the room handle and invoke the method directly. Attaching unnecessarily adds latency and creates a realtime connection that isn't needed. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, you can publish without being attached - it creates a transient attachment. But as a general rule of thumb this is fine |
||
|
|
||
| **How to verify:** Check the Chat SDK source or typedoc — methods that are REST-based don't require the room to be in an `attached` state. When in doubt, test without `room.attach()` — if the SDK method works, attachment isn't needed. | ||
|
|
||
| ## Step 2: Create the command file | ||
|
|
||
| ### File location | ||
|
|
@@ -176,14 +196,23 @@ if (!this.shouldOutputJson(flags)) { | |
| this.log(formatListening("Listening for messages.")); | ||
| } | ||
|
|
||
| // JSON output — use logJsonResult for one-shot results: | ||
| // JSON output — nest data under a domain key, not spread at top level. | ||
| // Envelope provides top-level: type, command, success. | ||
| // One-shot single result — singular domain key: | ||
| if (this.shouldOutputJson(flags)) { | ||
| this.logJsonResult({ message: messageData }, flags); | ||
| // → {"type":"result","command":"...","success":true,"message":{...}} | ||
| } | ||
|
|
||
| // One-shot collection result — plural domain key + optional metadata: | ||
| if (this.shouldOutputJson(flags)) { | ||
| this.logJsonResult({ channel: args.channel, message }, flags); | ||
| this.logJsonResult({ messages: items, total: items.length }, flags); | ||
| } | ||
|
|
||
| // Streaming events — use logJsonEvent: | ||
| // Streaming events — singular domain key: | ||
| if (this.shouldOutputJson(flags)) { | ||
| this.logJsonEvent({ eventType: event.type, message, channel: channelName }, flags); | ||
| this.logJsonEvent({ message: messageData }, flags); | ||
| // → {"type":"event","command":"...","message":{...}} | ||
| } | ||
| ``` | ||
|
|
||
|
|
@@ -389,8 +418,15 @@ See the "Keeping Skills Up to Date" section in `CLAUDE.md` for the full list of | |
| - [ ] All human output wrapped in `if (!this.shouldOutputJson(flags))` | ||
| - [ ] Output helpers used correctly (`formatProgress`, `formatSuccess`, `formatWarning`, `formatListening`, `formatResource`, `formatTimestamp`, `formatClientId`, `formatEventType`, `formatLabel`, `formatHeading`, `formatIndex`) | ||
| - [ ] `formatSuccess()` messages end with `.` (period) | ||
| - [ ] Non-JSON data output uses multi-line labeled blocks (see `patterns.md` "Human-Readable Output Format"), not tables or custom grids | ||
| - [ ] Non-JSON output exposes all available SDK fields (same data as JSON mode, omitting only null/empty values) | ||
| - [ ] SDK types imported directly (`import type { CursorUpdate } from "@ably/spaces"`) — no local interface redefinitions of SDK types (display interfaces in `src/utils/` are fine) | ||
| - [ ] Field coverage checked against SDK type definitions (`node_modules/ably/ably.d.ts`, `node_modules/@ably/spaces/dist/mjs/types.d.ts`) | ||
| - [ ] Subscribe commands do NOT fetch initial state — they only listen for new events (use `get-all` for current state) | ||
| - [ ] Resource names use `formatResource(name)`, never quoted | ||
| - [ ] JSON output uses `logJsonResult()` (one-shot) or `logJsonEvent()` (streaming), not direct `formatJsonRecord()` | ||
| - [ ] JSON data nested under a domain key (singular for events/single items, plural for collections) — not spread at top level (see `patterns.md` "JSON Data Nesting Convention") | ||
| - [ ] `room.attach()` / `space.enter()` only called when the SDK method requires a realtime connection (subscribe, send, presence) — NOT for REST mutations (update, delete, annotate, history) | ||
| - [ ] Subscribe/enter commands use `this.waitAndTrackCleanup(flags, component, flags.duration)` (not `waitUntilInterruptedOrTimeout`) | ||
| - [ ] Error handling uses `this.fail()` exclusively, not `this.error()` or `this.log(chalk.red(...))` | ||
| - [ ] Component strings are camelCase: single-word lowercase (`"room"`, `"auth"`), multi-word camelCase (`"channelPublish"`, `"roomPresenceSubscribe"`) | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This raises a question... if we're having to describe chat SDK internals in the CLI repo for an LLM to make the right decision, shouldn't we be solving the problem at source and updating the docs / docblocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's a fair question but a tricky one to answer properly... the LLM struggled to make the right decision the first time because it just ignorantly used existing patterns in the CLI codebase which were mostly just PubSub patterns that did need attachment. I'll check if this was something that came up in the llm-eval work we did, if-not we can recreate a short experiment to see if this is a common issue post CLI GA
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://ably.atlassian.net/browse/DX-980