feat(discord) - Discord Provider - #129
Conversation
Move the client-agnostic helpers (toFormData multipart serializer and downloadAttachment CDN fetch) out of client.ts so the file is just the photon-client API surface. Update inbound/media.ts to import downloadAttachment from the new ./util module.
Handle MESSAGE_DELETE and MESSAGE_REACTION_REMOVE dispatches, mapping each to an unsend that retracts the original message or reaction. Reuse the reaction add handler's synthesized event id so a removal resolves to the same reaction it retracts. Resolve flattened group-item ids (`<snowflake>:<index>`) back to the underlying Discord snowflake in parseMessageId, so a reply/edit/reaction targeting any group part hits the real message. Add inbound and outbound tests covering the discord adapter.
Map `poll` content to a Discord PollCreateRequest (question + ordered answers), letting Discord's defaults apply (24h duration, single-select). Outbound `poll_option` stays unsupported since bots cannot cast votes.
A poll arriving on MESSAGE_CREATE surfaces as `poll` content. Poll votes (MESSAGE_POLL_VOTE_ADD/REMOVE) map to `poll_option` (selected true/false); a removal preserves which option was unvoted rather than collapsing to an unsend. Votes carry only an answer_id, so the poll is reconstructed from a store cache, falling back to a one-off message fetch (getChannelMessage) on a miss and caching the result.
…orm overload The fusor definePlatform overload never threaded the instance `_Actions` generic into PlatformDef (only the regular overload did), so fusor-based providers could not declare data-returning instance actions. The runtime (buildInstanceActions) already supported them — this was a types-only gap. Add the `_Actions` generic to the fusor overload's def param and return type.
Wrap @photon-ai/discord-ts createThreadFromMessage and createThread as
startThreadFromMessage (POST /channels/{id}/messages/{id}/threads) and
startChannelThread (POST /channels/{id}/threads), mirroring the existing
client helpers. Both return the created thread, whose id is a snowflake
addressable as a space.
…ctions Sending and receiving in an existing thread already worked (a thread id is a channel snowflake, so it is a valid space.id). This adds the missing ability to start one: instance actions startThread (from an existing message) and createThread (a standalone text thread) open a thread and return its id, which the caller passes to space.get(id) to post into it. Discord's client here is fusor (inbound-only), so the REST client is built from config, matching send.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughIntroduces a new ChangesDiscord Provider Implementation
Sequence Diagram(s)sequenceDiagram
participant Bot as Bot HTTP Server
participant verify as verify.ts
participant handleMessages as handleMessages
participant store as Platform Store
participant client as client.ts
participant Discord as Discord REST API
Bot->>verify: rawBody (Gateway relay)
verify-->>Bot: DiscordPayload {t, d}
Bot->>handleMessages: {payload, config, store}
alt MESSAGE_CREATE with poll
handleMessages->>store: get(pollCacheKey)
store-->>handleMessages: undefined (cache miss)
handleMessages->>handleMessages: reconstructPoll(d.poll)
handleMessages->>store: set(pollCacheKey, reconstructed)
end
alt INTERACTION_CREATE (component click)
handleMessages->>client: acknowledgeComponentInteraction(id, token)
client->>Discord: POST /interactions/{id}/{token}/callback
Discord-->>client: 200
client-->>handleMessages: void
end
alt MESSAGE_POLL_VOTE_ADD cache miss
handleMessages->>store: get(pollCacheKey)
store-->>handleMessages: undefined
handleMessages->>client: getChannelMessage(channelId, messageId)
client->>Discord: GET /channels/{id}/messages/{id}
Discord-->>client: MessageResponse
client-->>handleMessages: message
handleMessages->>handleMessages: reconstructPoll(message.poll)
handleMessages->>store: set(pollCacheKey, reconstructed)
end
handleMessages-->>Bot: ProviderMessageRecord | undefined
sequenceDiagram
participant App as Application
participant send as send.ts
participant buildSend as buildSend (message.ts)
participant client as client.ts
participant Discord as Discord REST API
App->>send: {space, content, config}
alt content.type = reaction
send->>client: addReaction(channelId, targetId, emoji)
client->>Discord: PUT /channels/{id}/messages/{id}/reactions/{emoji}/@me
Discord-->>client: 204
send-->>App: synthetic ProviderMessageRecord
end
alt content.type = edit
send->>client: editChannelMessage(channelId, messageId, body)
client->>Discord: PATCH /channels/{id}/messages/{id}
Discord-->>client: MessageResponse
send-->>App: undefined
end
alt content.type = text/embed/components/etc.
send->>buildSend: content
buildSend-->>send: DiscordSendSpec
send->>send: resolveAllowedMentions(config, existing)
send->>client: createChannelMessage(client, channelId, spec)
client->>Discord: POST /channels/{id}/messages
Discord-->>client: MessageResponse
client-->>send: MessageResponse
send-->>App: ProviderMessageRecord
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Add embed(...) as Discord-scoped content (__platform: "Discord", not part of the universal Content union): one message carrying up to 10 RichEmbeds plus optional leading text and attachment:// files. Validated up front against Discord's embed limits. buildSend narrows it via isEmbed so it also works as the inner content of a reply or an item of a group.
…eplied_user off Add an optional allowedMentions to the Discord config (Discord's allowed_mentions: parse/users/roles/replied_user, validated up front incl. the parse-vs-id-list mutual exclusivity) and apply it to every outbound message in send. Precedence: content-level allowed_mentions (custom) wins, then config.allowedMentions, else a safe default that parses all mention types but never pings the replied-to author (replied_user: false) — so a reply no longer pings its target unless opted back in.
Add pin/unpin instance actions mirroring startThread/createThread. Each takes a Message, resolves its channel and message snowflakes (unwrapping flattened group-item ids), and toggles the pinned state through new pinMessage/unpinMessage REST client helpers (createPin/deletePin).
Map an INTERACTION_CREATE of type MESSAGE_COMPONENT (a button click or select-menu submit) to a Spectrum message. Acknowledge it within Discord's 3-second window first — DEFERRED_UPDATE_MESSAGE acks silently, so a reply the handler sends is an ordinary channel message — then surface the click as `custom` content a handler narrows on (`raw.discord.type === "interaction"`), carrying custom_id, select values, the source message id, and the interaction id/token for a future explicit-response API. Slash commands, autocomplete and modal submits remain out of scope.
…ispatch matches
The embed() and components() content builders hardcoded __platform: "Discord",
but the dispatch guard in platform/build.ts compares the tag against the
provider's definePlatform name ("discord") with a strict, case-sensitive !==.
The capitalized tag silently failed that check, dropping every embed/components
send as "unsupported". Use the DISCORD_PLATFORM constant on both the schema and
the as* factories so the two can never drift, and pin the equality in tests.
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
The discord provider source existed but was missing from tsup.config.ts's entry list, so the manifest generator threw because no compiled output was produced. Add the entry so the build succeeds and discord is emitted to dist and included in the manifest.
There was a problem hiding this comment.
Pull request overview
Adds a first-class Discord bot provider to spectrum-ts, integrating Fusor-relayed Gateway dispatches for inbound events and direct Discord REST API (v10) calls for outbound operations, plus Discord-only rich features (embeds/components) and new instance actions (threads/pins).
Changes:
- Introduces the Discord provider implementation (config, verify, inbound mapping, outbound send/mapping, REST client helpers, space creation).
- Adds Discord-scoped content helpers (
embed(...),components(...)) with upfront limit validation and outbound mapping support. - Extends the fusor
definePlatformoverload to type instance actions, and adds docs/tests + build/entry/dependency wiring.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/spectrum-ts/tsup.config.ts | Adds a build entry for the Discord provider. |
| packages/spectrum-ts/test/providers/discord/outbound/thread.test.ts | Tests outbound thread instance actions via stubbed client helpers. |
| packages/spectrum-ts/test/providers/discord/outbound/pin.test.ts | Tests pin/unpin instance actions and message-id unwrapping. |
| packages/spectrum-ts/test/providers/discord/outbound/message.test.ts | Tests message-id parsing and poll spec mapping in buildSend. |
| packages/spectrum-ts/test/providers/discord/outbound/embed.test.ts | Tests Discord-scoped embed builder/validation and mapping to send spec. |
| packages/spectrum-ts/test/providers/discord/outbound/components.test.ts | Tests Discord-scoped components constructors/validation and mapping to send spec. |
| packages/spectrum-ts/test/providers/discord/outbound/allowed-mentions.test.ts | Tests allowed-mentions resolution precedence and schema constraints. |
| packages/spectrum-ts/test/providers/discord/inbound/messages.test.ts | Tests inbound dispatch → record mapping incl. polls/votes and interactions. |
| packages/spectrum-ts/src/providers/index.ts | Exposes discord from the providers barrel. |
| packages/spectrum-ts/src/providers/discord/verify.ts | Implements Fusor verify parsing for relayed Gateway dispatch frames. |
| packages/spectrum-ts/src/providers/discord/util.ts | Adds multipart form serialization and CDN attachment download helper. |
| packages/spectrum-ts/src/providers/discord/types.ts | Introduces inbound Gateway subset types and outbound DTO for sends. |
| packages/spectrum-ts/src/providers/discord/space.ts | Adds Discord space creation (DM channel open) and user resolution. |
| packages/spectrum-ts/src/providers/discord/outbound/thread.ts | Implements thread-start REST calls and option mapping. |
| packages/spectrum-ts/src/providers/discord/outbound/send.ts | Adds outbound dispatcher incl. reactions, edits, groups, mentions defaults. |
| packages/spectrum-ts/src/providers/discord/outbound/pin.ts | Implements pin/unpin instance actions. |
| packages/spectrum-ts/src/providers/discord/outbound/message.ts | Adds content→Discord send spec mapping (poll, reply, files, custom, etc.). |
| packages/spectrum-ts/src/providers/discord/index.ts | Wires the provider via definePlatform (fusor mode) + instance actions + exports. |
| packages/spectrum-ts/src/providers/discord/inbound/poll.ts | Adds poll reconstruction and answer-id resolution helpers. |
| packages/spectrum-ts/src/providers/discord/inbound/messages.ts | Maps dispatches to Spectrum records (messages/edits/deletes/reactions/votes/interactions). |
| packages/spectrum-ts/src/providers/discord/inbound/media.ts | Maps Discord attachments to lazily-downloaded Spectrum attachment content. |
| packages/spectrum-ts/src/providers/discord/content/embed.ts | Adds Discord-only embed content builder + limit validation + attachment refs. |
| packages/spectrum-ts/src/providers/discord/content/components.ts | Adds Discord-only components builder + constructors + limit/layout validation. |
| packages/spectrum-ts/src/providers/discord/config.ts | Adds provider config schema and allowed-mentions validation. |
| packages/spectrum-ts/src/providers/discord/client.ts | Adds REST client construction and helper calls (messages, reactions, threads, pins, interactions, DM). |
| packages/spectrum-ts/src/platform/define.ts | Extends fusor-mode definePlatform overload typing to include instance actions. |
| packages/spectrum-ts/package.json | Adds @photon-ai/discord-ts dependency. |
| docs/discord.md | Adds comprehensive Discord provider documentation. |
| bun.lock | Updates lockfile with the new dependency and workspace metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
docs/discord.md (1)
266-266: 💤 Low valueConsider rephrasing "exactly" for conciseness.
Line 266 uses the word "exactly," which LanguageTool flags as overused. The sentence:
"Each returned value is exactly the@photon-ai/discord-tsrequest type..."can be streamlined without loss of clarity.💡 Suggested rephrase
- Each returned value is exactly the `@photon-ai/discord-ts` request type, so an + Each returned value matches the `@photon-ai/discord-ts` request type, so anor
- Each returned value is exactly the `@photon-ai/discord-ts` request type, so an + Each returned value is the `@photon-ai/discord-ts` request type, so an🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/discord.md` at line 266, The word "exactly" in the sentence beginning with "Each returned value is exactly the `@photon-ai/discord-ts` request type..." is flagged as overused by LanguageTool. Rephrase this sentence to remove or replace "exactly" while maintaining the original meaning and clarity. Consider alternatives that convey the same information more concisely, such as removing "exactly" entirely or using a more specific verb or structure.packages/spectrum-ts/src/providers/discord/client.ts (2)
54-66: ⚡ Quick winConsider adding runtime validation for the multipart upload response.
The response from the raw
client.postis cast toMessageResponsewithout schema validation (line 66). WhilethrowOnError: trueprevents error responses from being incorrectly typed, an unexpected success response shape could propagate invalid data downstream. Consider using a Zod schema or the discord-ts response validator if available.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/spectrum-ts/src/providers/discord/client.ts` around lines 54 - 66, The return statement where res.data is cast to MessageResponse lacks runtime validation of the response shape. Instead of directly casting res.data to MessageResponse, validate the response object against a Zod schema or use the discord-ts response validator before returning it. This ensures that unexpected response shapes from the raw client.post call are caught at runtime rather than propagating invalid data downstream. Apply the validation before the type assertion to maintain type safety while catching structural issues.
234-238: ⚡ Quick winConsider validating the DM channel response shape.
Line 238 casts the response to
{ id: string }without runtime validation. If Discord returns an unexpected shape, this could cause issues downstream. Consider adding a runtime check or using a Zod schema.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/spectrum-ts/src/providers/discord/client.ts` around lines 234 - 238, The channel response from the createDm function is being cast to { id: string } without any runtime validation, which could cause issues if Discord returns an unexpected response shape. Add runtime validation to verify that the channel response contains the expected id property before casting and returning it. Consider using a Zod schema to validate the response structure or implement manual validation checks to ensure the response has the required id field before accessing it in the return statement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/discord.md`:
- Line 105: In the discord.md documentation file, there is a typo where "AP"
should be "API". Locate the phrase "explicit-response AP" and change "AP" to
"API" to correctly reference the API functionality in the context of future
explicit-response features.
In `@packages/spectrum-ts/src/providers/discord/inbound/messages.ts`:
- Around line 51-55: The senderRef function incorrectly sets the isMe field to
Boolean(user.bot), which marks all bots as "me" rather than just the
application's own bot. This is inconsistent with other message handlers like
reactions, votes, and reaction-removes which hardcode isMe to false. Since
self-filtering is already handled via config.applicationId checks before
messages reach senderRef, all remaining inbound senders are from OTHER users or
bots. Change the isMe field in the senderRef function to be a hardcoded false
value instead of Boolean(user.bot) to match the semantics used elsewhere and
prevent downstream bugs from false positives.
In `@packages/spectrum-ts/src/providers/discord/types.ts`:
- Around line 52-55: The JSDoc comment for the DiscordPollMedia interface is
inaccurate. Update the comment to reflect the actual interface definition, which
only contains a text field. Remove the reference to "optional emoji" and clarify
that the interface contains "text only" with a note that "emoji support
intentionally omitted" to match the actual implementation and align with the
design decision documented in message.ts.
---
Nitpick comments:
In `@docs/discord.md`:
- Line 266: The word "exactly" in the sentence beginning with "Each returned
value is exactly the `@photon-ai/discord-ts` request type..." is flagged as
overused by LanguageTool. Rephrase this sentence to remove or replace "exactly"
while maintaining the original meaning and clarity. Consider alternatives that
convey the same information more concisely, such as removing "exactly" entirely
or using a more specific verb or structure.
In `@packages/spectrum-ts/src/providers/discord/client.ts`:
- Around line 54-66: The return statement where res.data is cast to
MessageResponse lacks runtime validation of the response shape. Instead of
directly casting res.data to MessageResponse, validate the response object
against a Zod schema or use the discord-ts response validator before returning
it. This ensures that unexpected response shapes from the raw client.post call
are caught at runtime rather than propagating invalid data downstream. Apply the
validation before the type assertion to maintain type safety while catching
structural issues.
- Around line 234-238: The channel response from the createDm function is being
cast to { id: string } without any runtime validation, which could cause issues
if Discord returns an unexpected response shape. Add runtime validation to
verify that the channel response contains the expected id property before
casting and returning it. Consider using a Zod schema to validate the response
structure or implement manual validation checks to ensure the response has the
required id field before accessing it in the return statement.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2f1f8853-b326-40f5-961d-12a5fa25baff
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (28)
docs/discord.mdpackages/spectrum-ts/package.jsonpackages/spectrum-ts/src/platform/define.tspackages/spectrum-ts/src/providers/discord/client.tspackages/spectrum-ts/src/providers/discord/config.tspackages/spectrum-ts/src/providers/discord/content/components.tspackages/spectrum-ts/src/providers/discord/content/embed.tspackages/spectrum-ts/src/providers/discord/inbound/media.tspackages/spectrum-ts/src/providers/discord/inbound/messages.tspackages/spectrum-ts/src/providers/discord/inbound/poll.tspackages/spectrum-ts/src/providers/discord/index.tspackages/spectrum-ts/src/providers/discord/outbound/message.tspackages/spectrum-ts/src/providers/discord/outbound/pin.tspackages/spectrum-ts/src/providers/discord/outbound/send.tspackages/spectrum-ts/src/providers/discord/outbound/thread.tspackages/spectrum-ts/src/providers/discord/space.tspackages/spectrum-ts/src/providers/discord/types.tspackages/spectrum-ts/src/providers/discord/util.tspackages/spectrum-ts/src/providers/discord/verify.tspackages/spectrum-ts/src/providers/index.tspackages/spectrum-ts/test/providers/discord/inbound/messages.test.tspackages/spectrum-ts/test/providers/discord/outbound/allowed-mentions.test.tspackages/spectrum-ts/test/providers/discord/outbound/components.test.tspackages/spectrum-ts/test/providers/discord/outbound/embed.test.tspackages/spectrum-ts/test/providers/discord/outbound/message.test.tspackages/spectrum-ts/test/providers/discord/outbound/pin.test.tspackages/spectrum-ts/test/providers/discord/outbound/thread.test.tspackages/spectrum-ts/tsup.config.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity in TypeScript
Preferunknownoveranywhen the type is genuinely unknown in TypeScript
Use const assertions (as const) for immutable values and literal types in TypeScript
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/spectrum-ts/src/providers/discord/inbound/media.tspackages/spectrum-ts/src/providers/discord/util.tspackages/spectrum-ts/src/providers/index.tspackages/spectrum-ts/test/providers/discord/outbound/message.test.tspackages/spectrum-ts/src/providers/discord/inbound/poll.tspackages/spectrum-ts/tsup.config.tspackages/spectrum-ts/src/providers/discord/verify.tspackages/spectrum-ts/src/providers/discord/space.tspackages/spectrum-ts/test/providers/discord/outbound/allowed-mentions.test.tspackages/spectrum-ts/src/providers/discord/outbound/pin.tspackages/spectrum-ts/test/providers/discord/outbound/pin.test.tspackages/spectrum-ts/test/providers/discord/outbound/thread.test.tspackages/spectrum-ts/test/providers/discord/outbound/embed.test.tspackages/spectrum-ts/src/providers/discord/config.tspackages/spectrum-ts/src/providers/discord/outbound/send.tspackages/spectrum-ts/src/providers/discord/outbound/thread.tspackages/spectrum-ts/src/providers/discord/index.tspackages/spectrum-ts/src/platform/define.tspackages/spectrum-ts/test/providers/discord/outbound/components.test.tspackages/spectrum-ts/src/providers/discord/client.tspackages/spectrum-ts/src/providers/discord/outbound/message.tspackages/spectrum-ts/test/providers/discord/inbound/messages.test.tspackages/spectrum-ts/src/providers/discord/content/embed.tspackages/spectrum-ts/src/providers/discord/content/components.tspackages/spectrum-ts/src/providers/discord/types.tspackages/spectrum-ts/src/providers/discord/inbound/messages.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions in JavaScript/TypeScript
Preferfor...ofloops over.forEach()and indexedforloops in JavaScript/TypeScript
Use optional chaining (?.) and nullish coalescing (??) for safer property access in JavaScript/TypeScript
Prefer template literals over string concatenation in JavaScript/TypeScript
Use destructuring for object and array assignments in JavaScript/TypeScript
Useconstby default,letonly when reassignment is needed, nevervarin JavaScript/TypeScript
Alwaysawaitpromises in async functions - don't forget to use the return value in JavaScript/TypeScript
Useasync/awaitsyntax instead of promise chains for better readability in JavaScript/TypeScript
Handle errors appropriately in async code with try-catch blocks in JavaScript/TypeScript
Don't use async functions as Promise executors in JavaScript/TypeScript
Removeconsole.log,debugger, andalertstatements from production code in JavaScript/TypeScript
ThrowErrorobjects with descriptive messages, not strings or other values in JavaScript/TypeScript
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them in JavaScript/TypeScript
Prefer early returns over nested conditionals for error cases in JavaScript/TypeScript
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables in JavaScript/TypeScript
Use early returns to reduce nesting in JavaScript/TypeScript
Prefer simple conditionals over nested ternary operators in JavaScript/TypeScript
Group related code together and separate concerns in JavaScript/TypeScript
Don't useeval()or assign directly todocument.cookiein JavaScript/TypeScript
Validate and sanitize user input in JavaScript/TypeScript
Avoid spread syntax in accumulators within loops in JavaScript/Ty...
Files:
packages/spectrum-ts/src/providers/discord/inbound/media.tspackages/spectrum-ts/src/providers/discord/util.tspackages/spectrum-ts/src/providers/index.tspackages/spectrum-ts/test/providers/discord/outbound/message.test.tspackages/spectrum-ts/src/providers/discord/inbound/poll.tspackages/spectrum-ts/tsup.config.tspackages/spectrum-ts/src/providers/discord/verify.tspackages/spectrum-ts/src/providers/discord/space.tspackages/spectrum-ts/test/providers/discord/outbound/allowed-mentions.test.tspackages/spectrum-ts/src/providers/discord/outbound/pin.tspackages/spectrum-ts/test/providers/discord/outbound/pin.test.tspackages/spectrum-ts/test/providers/discord/outbound/thread.test.tspackages/spectrum-ts/test/providers/discord/outbound/embed.test.tspackages/spectrum-ts/src/providers/discord/config.tspackages/spectrum-ts/src/providers/discord/outbound/send.tspackages/spectrum-ts/src/providers/discord/outbound/thread.tspackages/spectrum-ts/src/providers/discord/index.tspackages/spectrum-ts/src/platform/define.tspackages/spectrum-ts/test/providers/discord/outbound/components.test.tspackages/spectrum-ts/src/providers/discord/client.tspackages/spectrum-ts/src/providers/discord/outbound/message.tspackages/spectrum-ts/test/providers/discord/inbound/messages.test.tspackages/spectrum-ts/src/providers/discord/content/embed.tspackages/spectrum-ts/src/providers/discord/content/components.tspackages/spectrum-ts/src/providers/discord/types.tspackages/spectrum-ts/src/providers/discord/inbound/messages.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks in test files
Avoid done callbacks in async tests - use async/await instead in test files
Don't use.onlyor.skipin committed code in test files
Keep test suites reasonably flat - avoid excessivedescribenesting in test files
Files:
packages/spectrum-ts/test/providers/discord/outbound/message.test.tspackages/spectrum-ts/test/providers/discord/outbound/allowed-mentions.test.tspackages/spectrum-ts/test/providers/discord/outbound/pin.test.tspackages/spectrum-ts/test/providers/discord/outbound/thread.test.tspackages/spectrum-ts/test/providers/discord/outbound/embed.test.tspackages/spectrum-ts/test/providers/discord/outbound/components.test.tspackages/spectrum-ts/test/providers/discord/inbound/messages.test.ts
🪛 LanguageTool
docs/discord.md
[grammar] ~105-~105: Use a hyphen to join words.
Context: ...teraction_id/token`. The id/token ride along so a future explicit-response AP...
(QB_NEW_EN_HYPHEN)
[style] ~266-~266: Consider an alternative for the overused word “exactly”.
Context: .../premium| Each returned value is exactly the@photon-ai/discord-ts` request typ...
(EXACTLY_PRECISELY)
🪛 OpenGrep (1.22.0)
packages/spectrum-ts/src/providers/discord/outbound/message.ts
[ERROR] 30-30: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🔇 Additional comments (29)
packages/spectrum-ts/src/providers/discord/verify.ts (1)
1-46: LGTM!packages/spectrum-ts/src/providers/discord/inbound/media.ts (1)
1-23: LGTM!packages/spectrum-ts/src/providers/discord/inbound/poll.ts (1)
1-66: LGTM!packages/spectrum-ts/src/providers/discord/inbound/messages.ts (2)
60-91: LGTM!
93-423: LGTM!packages/spectrum-ts/test/providers/discord/inbound/messages.test.ts (1)
1-336: LGTM!packages/spectrum-ts/package.json (1)
105-105: LGTM!packages/spectrum-ts/src/platform/define.ts (1)
435-435: LGTM!Also applies to: 458-460, 495-497
packages/spectrum-ts/src/providers/discord/types.ts (1)
14-23: LGTM!Also applies to: 31-33, 35-240
packages/spectrum-ts/src/providers/discord/config.ts (1)
1-133: LGTM!packages/spectrum-ts/tsup.config.ts (1)
11-11: LGTM!packages/spectrum-ts/src/providers/index.ts (1)
2-2: LGTM!packages/spectrum-ts/src/providers/discord/client.ts (2)
60-63: Excellent security handling for multipart uploads.The explicit
securityopt-in ensures the bot token is sent with ad-hoc POST requests. The comment clearly explains why this is necessary—well done.
33-34: LGTM!Also applies to: 76-86, 93-101, 107-117, 123-128, 136-146, 155-164, 171-180, 188-197, 214-224
packages/spectrum-ts/src/providers/discord/util.ts (1)
1-42: LGTM!packages/spectrum-ts/src/providers/discord/space.ts (1)
1-47: LGTM!packages/spectrum-ts/src/providers/discord/outbound/thread.ts (1)
1-91: LGTM!packages/spectrum-ts/src/providers/discord/outbound/pin.ts (1)
1-42: LGTM!packages/spectrum-ts/test/providers/discord/outbound/thread.test.ts (1)
1-122: LGTM!packages/spectrum-ts/test/providers/discord/outbound/pin.test.ts (1)
1-66: LGTM!packages/spectrum-ts/src/providers/discord/index.ts (1)
1-96: LGTM!packages/spectrum-ts/src/providers/discord/content/embed.ts (1)
1-253: LGTM!packages/spectrum-ts/src/providers/discord/content/components.ts (1)
1-377: LGTM!packages/spectrum-ts/test/providers/discord/outbound/embed.test.ts (1)
1-208: LGTM!packages/spectrum-ts/test/providers/discord/outbound/components.test.ts (1)
1-255: LGTM!packages/spectrum-ts/src/providers/discord/outbound/message.ts (1)
1-184: LGTM!packages/spectrum-ts/src/providers/discord/outbound/send.ts (1)
1-181: LGTM!packages/spectrum-ts/test/providers/discord/outbound/message.test.ts (1)
1-56: LGTM!packages/spectrum-ts/test/providers/discord/outbound/allowed-mentions.test.ts (1)
1-87: LGTM!
sometimes bots will send messages as well. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Reconcile the discord provider with the v5 monolith split (#124): move it into its own @spectrum-ts/discord package (mirroring telegram), rewrite its imports onto @spectrum-ts/core + /authoring, and leave a compat shim at spectrum-ts/providers/discord. - Add asEdit, asUnsend, asReply, attachmentSchema, BaseContent, createStore to @spectrum-ts/core/authoring (the inbound factories discord needs) - New packages/discord package: package.json, tsconfig, tsdown, turbo, README - Wire discord into the providers barrel, spectrum-ts deps, tsdown entries, providers-shims parity test, and the README platform table
@spectrum-ts/core
@spectrum-ts/discord
@spectrum-ts/imessage
@spectrum-ts/slack
spectrum-ts
@spectrum-ts/telegram
@spectrum-ts/terminal
@spectrum-ts/whatsapp-business
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/discord/package.json (1)
56-56: Avoidlatestfor@types/bunto keep installs reproducible.Using
latestcauses the dependency to float, potentially pulling different type definitions across installs and CI runs. Pin to a specific version range instead.Proposed change
- "`@types/bun`": "latest", + "`@types/bun`": "^1.2.0",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/discord/package.json` at line 56, The "`@types/bun`" dependency in packages/discord/package.json is pinned to "latest" which causes non-reproducible installs as different versions may be resolved in different environments and CI runs. Replace the "latest" version string with a specific version or version range (such as a pinned version like "1.0.0" or a ranged version like "^1.0.0") to ensure consistent and reproducible dependency resolution across all installs and CI environments.packages/spectrum-ts/test/providers-shims.test.ts (1)
36-42: ⚡ Quick winAssert exact export-key parity, not just pkg→shim forwarding.
This currently allows extra shim-only exports to slip through while still passing. Add a key-set equality assertion before identity checks.
Suggested patch
for (const [key, shim, pkg] of cases) { it(`${key}: forwards every export of the provider package`, () => { - for (const name of Object.keys(pkg)) { + const pkgKeys = Object.keys(pkg).sort(); + const shimKeys = Object.keys(shim).sort(); + expect(shimKeys).toEqual(pkgKeys); + + for (const name of pkgKeys) { expect(shim[name as keyof typeof shim]).toBe( pkg[name as keyof typeof pkg] ); } - expect(Object.keys(pkg).length).toBeGreaterThan(0); + expect(pkgKeys.length).toBeGreaterThan(0); }); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/spectrum-ts/test/providers-shims.test.ts` around lines 36 - 42, The test currently only verifies that all keys from pkg exist in the shim object, but does not verify that the shim object does not contain extra keys beyond what pkg exports. Add an assertion to check that the set of keys in the shim object exactly matches the set of keys in the pkg object, ensuring complete export-key parity. This assertion should compare Object.keys(shim) with Object.keys(pkg) to guarantee they have identical exports and no extra shim-only exports slip through.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/discord/package.json`:
- Line 56: The "`@types/bun`" dependency in packages/discord/package.json is
pinned to "latest" which causes non-reproducible installs as different versions
may be resolved in different environments and CI runs. Replace the "latest"
version string with a specific version or version range (such as a pinned
version like "1.0.0" or a ranged version like "^1.0.0") to ensure consistent and
reproducible dependency resolution across all installs and CI environments.
In `@packages/spectrum-ts/test/providers-shims.test.ts`:
- Around line 36-42: The test currently only verifies that all keys from pkg
exist in the shim object, but does not verify that the shim object does not
contain extra keys beyond what pkg exports. Add an assertion to check that the
set of keys in the shim object exactly matches the set of keys in the pkg
object, ensuring complete export-key parity. This assertion should compare
Object.keys(shim) with Object.keys(pkg) to guarantee they have identical exports
and no extra shim-only exports slip through.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d034f338-9e4e-4d02-8bf0-1f0ce9385975
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
README.mdpackages/core/src/authoring.tspackages/core/src/platform/define.tspackages/discord/README.mdpackages/discord/package.jsonpackages/discord/src/client.tspackages/discord/src/config.tspackages/discord/src/content/components.tspackages/discord/src/content/embed.tspackages/discord/src/inbound/media.tspackages/discord/src/inbound/messages.tspackages/discord/src/inbound/poll.tspackages/discord/src/index.tspackages/discord/src/outbound/message.tspackages/discord/src/outbound/pin.tspackages/discord/src/outbound/send.tspackages/discord/src/outbound/thread.tspackages/discord/src/space.tspackages/discord/src/types.tspackages/discord/src/util.tspackages/discord/src/verify.tspackages/discord/test/inbound/messages.test.tspackages/discord/test/outbound/allowed-mentions.test.tspackages/discord/test/outbound/components.test.tspackages/discord/test/outbound/embed.test.tspackages/discord/test/outbound/message.test.tspackages/discord/test/outbound/pin.test.tspackages/discord/test/outbound/thread.test.tspackages/discord/tsconfig.jsonpackages/discord/tsdown.config.tspackages/discord/turbo.jsonpackages/spectrum-ts/package.jsonpackages/spectrum-ts/src/providers/discord/index.tspackages/spectrum-ts/src/providers/index.tspackages/spectrum-ts/test/providers-shims.test.tspackages/spectrum-ts/tsdown.config.ts
💤 Files with no reviewable changes (5)
- packages/discord/src/space.ts
- packages/discord/src/util.ts
- packages/discord/src/types.ts
- packages/discord/src/client.ts
- packages/core/src/platform/define.ts
✅ Files skipped from review due to trivial changes (3)
- packages/discord/turbo.json
- packages/discord/README.md
- README.md
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/spectrum-ts/src/providers/index.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity in TypeScript
Preferunknownoveranywhen the type is genuinely unknown in TypeScript
Use const assertions (as const) for immutable values and literal types in TypeScript
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/spectrum-ts/tsdown.config.tspackages/discord/test/outbound/message.test.tspackages/discord/tsdown.config.tspackages/core/src/authoring.tspackages/discord/src/verify.tspackages/discord/src/inbound/poll.tspackages/spectrum-ts/test/providers-shims.test.tspackages/discord/test/outbound/thread.test.tspackages/discord/src/inbound/media.tspackages/discord/src/outbound/thread.tspackages/discord/test/inbound/messages.test.tspackages/discord/src/index.tspackages/discord/test/outbound/components.test.tspackages/discord/src/outbound/pin.tspackages/discord/test/outbound/allowed-mentions.test.tspackages/discord/src/content/embed.tspackages/discord/test/outbound/embed.test.tspackages/discord/src/config.tspackages/discord/test/outbound/pin.test.tspackages/discord/src/outbound/message.tspackages/discord/src/outbound/send.tspackages/discord/src/inbound/messages.tspackages/discord/src/content/components.tspackages/spectrum-ts/src/providers/discord/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions in JavaScript/TypeScript
Preferfor...ofloops over.forEach()and indexedforloops in JavaScript/TypeScript
Use optional chaining (?.) and nullish coalescing (??) for safer property access in JavaScript/TypeScript
Prefer template literals over string concatenation in JavaScript/TypeScript
Use destructuring for object and array assignments in JavaScript/TypeScript
Useconstby default,letonly when reassignment is needed, nevervarin JavaScript/TypeScript
Alwaysawaitpromises in async functions - don't forget to use the return value in JavaScript/TypeScript
Useasync/awaitsyntax instead of promise chains for better readability in JavaScript/TypeScript
Handle errors appropriately in async code with try-catch blocks in JavaScript/TypeScript
Don't use async functions as Promise executors in JavaScript/TypeScript
Removeconsole.log,debugger, andalertstatements from production code in JavaScript/TypeScript
ThrowErrorobjects with descriptive messages, not strings or other values in JavaScript/TypeScript
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them in JavaScript/TypeScript
Prefer early returns over nested conditionals for error cases in JavaScript/TypeScript
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables in JavaScript/TypeScript
Use early returns to reduce nesting in JavaScript/TypeScript
Prefer simple conditionals over nested ternary operators in JavaScript/TypeScript
Group related code together and separate concerns in JavaScript/TypeScript
Don't useeval()or assign directly todocument.cookiein JavaScript/TypeScript
Validate and sanitize user input in JavaScript/TypeScript
Avoid spread syntax in accumulators within loops in JavaScript/Ty...
Files:
packages/spectrum-ts/tsdown.config.tspackages/discord/test/outbound/message.test.tspackages/discord/tsdown.config.tspackages/core/src/authoring.tspackages/discord/src/verify.tspackages/discord/src/inbound/poll.tspackages/spectrum-ts/test/providers-shims.test.tspackages/discord/test/outbound/thread.test.tspackages/discord/src/inbound/media.tspackages/discord/src/outbound/thread.tspackages/discord/test/inbound/messages.test.tspackages/discord/src/index.tspackages/discord/test/outbound/components.test.tspackages/discord/src/outbound/pin.tspackages/discord/test/outbound/allowed-mentions.test.tspackages/discord/src/content/embed.tspackages/discord/test/outbound/embed.test.tspackages/discord/src/config.tspackages/discord/test/outbound/pin.test.tspackages/discord/src/outbound/message.tspackages/discord/src/outbound/send.tspackages/discord/src/inbound/messages.tspackages/discord/src/content/components.tspackages/spectrum-ts/src/providers/discord/index.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks in test files
Avoid done callbacks in async tests - use async/await instead in test files
Don't use.onlyor.skipin committed code in test files
Keep test suites reasonably flat - avoid excessivedescribenesting in test files
Files:
packages/discord/test/outbound/message.test.tspackages/spectrum-ts/test/providers-shims.test.tspackages/discord/test/outbound/thread.test.tspackages/discord/test/inbound/messages.test.tspackages/discord/test/outbound/components.test.tspackages/discord/test/outbound/allowed-mentions.test.tspackages/discord/test/outbound/embed.test.tspackages/discord/test/outbound/pin.test.ts
🔇 Additional comments (27)
packages/discord/src/verify.ts (1)
1-1: LGTM!packages/discord/src/inbound/media.ts (1)
1-2: LGTM!packages/discord/src/inbound/poll.ts (1)
1-2: LGTM!packages/discord/src/inbound/messages.ts (1)
1-17: LGTM!packages/discord/test/inbound/messages.test.ts (1)
16-29: LGTM!packages/discord/src/config.ts (1)
108-108: LGTM!Also applies to: 118-120
packages/discord/tsconfig.json (1)
1-10: LGTM!packages/discord/tsdown.config.ts (1)
1-10: LGTM!packages/discord/src/outbound/thread.ts (1)
2-2: LGTM!packages/discord/src/outbound/pin.ts (1)
1-1: LGTM!packages/discord/test/outbound/pin.test.ts (1)
16-24: LGTM!packages/discord/src/index.ts (1)
1-99: LGTM!packages/discord/package.json (1)
19-33: No issue:publishConfig.exportscorrectly overrides for npm publish.The workspace
exports.typesintentionally points to./src/index.tsfor development DX (buildless in-repo resolution). The build/publish toolchain (clean-publish) appliespublishConfig.exportsduring publishing, which correctly mapstypesto./dist/index.d.ts. This is validated in CI viapublintand@arethetypeswrong/cliagainst the cleaned artifact before npm publish, confirming downstream TypeScript resolution will not break.packages/core/src/authoring.ts (1)
1-54: LGTM!packages/discord/test/outbound/embed.test.ts (1)
2-6: LGTM!packages/discord/test/outbound/components.test.ts (1)
2-4: LGTM!Also applies to: 14-15
packages/discord/src/outbound/send.ts (1)
2-8: LGTM!packages/discord/test/outbound/message.test.ts (1)
2-3: LGTM!packages/discord/test/outbound/allowed-mentions.test.ts (1)
6-7: LGTM!packages/discord/test/outbound/thread.test.ts (1)
32-40: LGTM!packages/spectrum-ts/package.json (1)
3-4: LGTM!Also applies to: 60-60, 94-111
packages/spectrum-ts/src/providers/discord/index.ts (1)
1-10: LGTM!packages/spectrum-ts/tsdown.config.ts (1)
1-29: LGTM!packages/spectrum-ts/test/providers-shims.test.ts (1)
1-35: LGTM!Also applies to: 43-54
packages/discord/src/content/embed.ts (1)
2-3: LGTM!packages/discord/src/content/components.ts (1)
12-12: LGTM!packages/discord/src/outbound/message.ts (1)
1-6: LGTM!
# Conflicts: # bun.lock # packages/core/src/authoring.ts
…g URL main (#140) added the universal `app` content type to the base Content union. Discord has no mini-app surface (iMessage renders the card; others fall back to the URL), so map `app` to its resolved URL in buildSend — Discord auto-embeds it, exactly like `richlink`. Handling it in buildSend (not just send) lets `app` also be the inner content of a `reply` or an item of a `group`.
…nd client Discord was the one provider untouched by #139's structured-logging pass. Wire in the same createLogger/errorAttrs pattern: - verify: warn on rejected inbound events (bad JSON / missing `t`) - inbound: warn on interaction-ack failure, debug on poll cache-miss fetch, warn when a poll vote is dropped (fetched message has no poll) - outbound: warn on send failure (excludes by-design UnsupportedError) - client: warn on DM-channel open failure (typically a 403) Error paths log then re-throw — no behavior change. Attrs are PII-safe (ids and byte counts only).
Adds a full Discord bot provider to spectrum-ts. Inbound events arrive over Fusor; outbound sends call the Discord REST API (v10) directly via the generated
@photon-ai/discord-tsclient.Design
lifecycle.createClientreturns afusor(...)client (platform +verify).verifyjust parses the relayed Gateway frame and there are no webhooks to self-register.discordClient(config)makes no network call, sosendand the inbound poll-vote path construct one on demand fromconfig.Inbound (
verify→messages)Maps Gateway dispatches to
ProviderMessageRecords:MESSAGE_CREATE(text/attachments)text/attachment/voice; text + multiple attachments →groupMESSAGE_CREATE(poll)pollMESSAGE_UPDATEeditMESSAGE_DELETEunsendMESSAGE_REACTION_ADD/_REMOVEreaction/unsendMESSAGE_POLL_VOTE_ADD/_REMOVEpoll_option(selectedtrue/false)INTERACTION_CREATE(MESSAGE_COMPONENT)custom(raw.discord.type === "interaction")applicationIdare dropped so the bot never re-ingests its own activity; authorlessMESSAGE_UPDATE(Discord's own link-embed edits) are ignored.store(first vote fetches once; siblings skip the round trip).DEFERRED_UPDATE_MESSAGEwithin the 3s window), then surfaced ascustomcarryingcustom_id/values/component_type/message_id/interaction_id/token.Outbound (
send)text/markdown,richlink,attachment/voice/contact(vCard),poll(native),reply,reaction,edit,typing,read(no-op),group, andcustom(raw body passthrough). Every message resolvesallowed_mentionsviaresolveAllowedMentions(configurable throughconfig.allowedMentions,replied_useroff by default).Instance actions:
startThread/createThread(threaded through the fusordefinePlatformoverload) andpin/unpin.Discord-scoped content
Helpers exported from
@photon-ai/spectrum-ts/providers/discordthat stay out of the universalContentunion:embed(...)— up to 10RichEmbeds + optional leading text in one message; validated against Discord's embed limits up front. Supportsattachment://file references.components(...)— up to 5 action rows of buttons / select menus + optional text; button clicks and select submits return as inboundcustominteractions.Docs & tests
docs/discord.md— full provider documentation.Notes
@photon-ai/discord-ts ^10.0.0.platform/define.tsgains a fusordefinePlatformoverload to thread instance actions — scoped to enable provider thread/pin actions.providers/index.tsand thetsupentry list.Changes: 25 commits, +4064 across 29 files.
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by CodeRabbit
New Features
attachment://resolution.Documentation
Tests
Compatibility / Chores