Skip to content

Conversation

@techwithanirudh
Copy link
Owner

@techwithanirudh techwithanirudh commented Jun 15, 2025

Summary by CodeRabbit

  • New Features

    • Added Discord slash commands for managing allowed text channels and voice channel actions (join/leave).
    • Introduced real-time voice channel support with AI-powered transcription and response playback.
    • Integrated weather and message reporting tools for enhanced AI interactions.
    • Expanded AI capabilities with new models, richer prompts, and improved context awareness.
    • Added configuration options for external services and improved environment variable handling.
  • Improvements

    • Enhanced rate limiting and channel restrictions for message handling.
    • Refined AI response generation, tool usage, and error handling.
    • Improved logging, modularity, and code organization for maintainability.
  • Chores

    • Updated dependencies and scripts, added trusted dependencies, and adjusted project configuration.
    • Switched project license from AGPLv3 to MIT for more permissive usage.
  • Documentation

    • Expanded TODO list with new tasks and clarifications.
    • Updated environment variable templates and added descriptive comments.

fellipeutaka and others added 30 commits August 8, 2022 21:58
refactor: update import paths for ApplicationCommand and Event templates

feat: implement deploy and undeploy message commands for slash commands

feat: create message-command and sub-command templates for better command structure

feat: add autocomplete functionality to sub-commands for enhanced user experience

chore: add base command structure for future command implementations
@coderabbitai
Copy link

coderabbitai bot commented Jun 15, 2025

Warning

Rate limit exceeded

@techwithanirudh has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 21 minutes and 6 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 4533ece and 29f4679.

📒 Files selected for processing (2)
  • TODO.md (1 hunks)
  • src/config.ts (2 hunks)

Walkthrough

This update introduces major new features, including voice channel support with real-time speech-to-text and AI-driven voice responses, channel access controls, and a modular AI tool system. It refactors prompts, providers, and utility modules, updates configuration and environment handling, and switches the software license from AGPLv3 to MIT.

Changes

File(s) / Path(s) Summary
LICENSE Replaced AGPLv3 with MIT License.
.env.example, src/env.ts Added new environment variables for Discord, Deepgram, Google AI, and updated validation logic.
package.json Added deploy script, updated dependencies, added trustedDependencies.
.vscode/settings.json Added cSpell custom dictionary for spelling.
TODO.md Expanded TODOs with new features and clarifications.
src/commands/channels.ts, src/commands/voice-channel/* Added new Discord slash commands for managing allowed channels and voice channel join/leave actions.
src/commands/index.ts Registered new commands (channels, vc).
src/config.ts Updated keywords, speed settings, and added voice config.
src/deploy-commands.ts Added global deployment support for commands.
src/events/message-create/index.ts Refactored event handler for modularity, logging, triggers, and channel restrictions.
src/events/message-create/utils/relevance.ts Switched to new relevance model and message type.
src/events/message-create/utils/respond.ts Replaced reply with generateResponse, supporting AI tools and structured results.
src/index.ts Updated event registration, added voice state intent.
src/lib/ai/prompts.ts Expanded and refactored prompts, added agent/tool/reply prompts, updated system prompt logic.
src/lib/ai/providers.ts Switched to multiple OpenAI models, added Google provider, refactored language model config.
src/lib/ai/tools/get-weather.ts, report.ts, discord.ts Added new AI tools: weather fetch, reporting, and Discord automation agent.
src/lib/constants.ts Removed: All bot constants now handled elsewhere.
src/lib/kv.ts Added environment-based Redis key prefix, allowed channels key, updated function signatures.
src/lib/validators/probability.ts Updated zod import and string delimiters.
src/utils/context.ts Added buildChatContext utility for assembling chat context.
src/utils/discord.ts Added embed builder and object scrubbing utilities.
src/utils/log.ts Added logging utilities for triggers, incoming messages, and replies.
src/utils/message-rate-limiter.ts Added Redis-backed message rate limiter utilities.
src/utils/messages.ts Replaced sync message conversion with async, added attachment handling.
src/utils/sandbox.ts Added async sandboxed code execution utility.
src/utils/triggers.ts Added trigger detection utility for pings, keywords, DMs.
src/utils/voice/helpers/* Added helpers for AI responses, audio playback, Deepgram TTS integration, and re-exports.
src/utils/voice/stream.ts Added real-time voice channel listening, transcription, and AI response playback.

Sequence Diagram(s)

Voice Channel AI Interaction

sequenceDiagram
    participant User
    participant DiscordBot
    participant Deepgram
    participant AIModel

    User->>DiscordBot: Joins voice channel & speaks
    DiscordBot->>Deepgram: Streams user audio for transcription
    Deepgram-->>DiscordBot: Sends transcript (final)
    DiscordBot->>AIModel: Sends transcript for response
    AIModel-->>DiscordBot: Returns AI-generated reply
    DiscordBot->>Deepgram: Sends reply text for TTS
    Deepgram-->>DiscordBot: Returns speech audio
    DiscordBot->>User: Plays AI speech in voice channel
Loading

Discord Automation Tool Agent Loop

sequenceDiagram
    participant AIModel
    participant DiscordBot
    participant DiscordAPI

    AIModel->>DiscordBot: Requests Discord action (via tool)
    loop Agent Loop (max 15 steps)
        DiscordBot->>DiscordAPI: Executes action (via sandbox)
        DiscordAPI-->>DiscordBot: Returns result
        DiscordBot->>AIModel: Returns output/status
        AIModel->>DiscordBot: Decides next step or finishes
    end
    DiscordBot->>AIModel: Returns final answer
Loading

Possibly related PRs

Poem

🐇
Hopping through code with a voice so clear,
Now I can listen and answer, right here!
Channels are guarded, my tools are so bright—
Weather or Discord, I’ll help day and night.
With new prompts and powers, my license is light,
This bunny bot’s ready—let’s chat and delight!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Commit Unit Tests in branch feat/agentic-ai
  • Post Copyable Unit Tests in Comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 57

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bce3f1b and 57e3d60.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (51)
  • .env.example (2 hunks)
  • .eslintrc.json (1 hunks)
  • .prettierrc.json (1 hunks)
  • .vscode/settings.json (1 hunks)
  • LICENSE (1 hunks)
  • README.md (1 hunks)
  • TODO.md (1 hunks)
  • package.json (2 hunks)
  • src/commands/channels.ts (1 hunks)
  • src/commands/index.ts (1 hunks)
  • src/commands/ping.ts (1 hunks)
  • src/commands/voice-channel/index.ts (1 hunks)
  • src/commands/voice-channel/join.ts (1 hunks)
  • src/commands/voice-channel/leave.ts (1 hunks)
  • src/config.ts (1 hunks)
  • src/deploy-commands.ts (1 hunks)
  • src/env.ts (1 hunks)
  • src/events/index.ts (1 hunks)
  • src/events/message-create/index.ts (1 hunks)
  • src/events/message-create/utils/relevance.ts (1 hunks)
  • src/events/message-create/utils/respond.ts (1 hunks)
  • src/index.ts (1 hunks)
  • src/lib/ai/prompts.ts (1 hunks)
  • src/lib/ai/providers.ts (1 hunks)
  • src/lib/ai/tools/discord.ts (1 hunks)
  • src/lib/ai/tools/get-weather.ts (1 hunks)
  • src/lib/ai/tools/report.ts (1 hunks)
  • src/lib/constants.ts (0 hunks)
  • src/lib/kv.ts (1 hunks)
  • src/lib/logger.ts (1 hunks)
  • src/lib/queries.ts (1 hunks)
  • src/lib/validators/index.ts (1 hunks)
  • src/lib/validators/probability.ts (1 hunks)
  • src/utils/context.ts (1 hunks)
  • src/utils/delay.ts (1 hunks)
  • src/utils/discord.ts (1 hunks)
  • src/utils/log.ts (1 hunks)
  • src/utils/message-rate-limiter.ts (1 hunks)
  • src/utils/messages.ts (1 hunks)
  • src/utils/sandbox.ts (1 hunks)
  • src/utils/status.ts (1 hunks)
  • src/utils/time.ts (1 hunks)
  • src/utils/tokenize-messages.ts (1 hunks)
  • src/utils/triggers.ts (1 hunks)
  • src/utils/unprompted-counter.ts (0 hunks)
  • src/utils/voice/helpers/ai.ts (1 hunks)
  • src/utils/voice/helpers/audio.ts (1 hunks)
  • src/utils/voice/helpers/deepgram.ts (1 hunks)
  • src/utils/voice/helpers/index.ts (1 hunks)
  • src/utils/voice/stream.ts (1 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (2)
  • src/lib/constants.ts
  • src/utils/unprompted-counter.ts
🧰 Additional context used
🧬 Code Graph Analysis (18)
src/lib/logger.ts (1)
src/env.ts (1)
  • env (4-56)
src/utils/time.ts (1)
src/config.ts (1)
  • timezone (6-6)
src/utils/delay.ts (1)
src/utils/tokenize-messages.ts (2)
  • normalize (10-17)
  • sentences (3-8)
src/index.ts (4)
src/deploy-commands.ts (1)
  • deployCommands (14-29)
src/commands/index.ts (1)
  • commands (5-9)
src/events/index.ts (1)
  • events (3-5)
src/env.ts (1)
  • env (4-56)
src/deploy-commands.ts (2)
src/commands/index.ts (1)
  • commands (5-9)
src/env.ts (1)
  • env (4-56)
src/commands/voice-channel/leave.ts (2)
src/commands/voice-channel/join.ts (2)
  • data (13-15)
  • execute (17-62)
src/commands/voice-channel/index.ts (2)
  • data (5-15)
  • execute (17-33)
src/utils/triggers.ts (1)
src/config.ts (1)
  • keywords (3-3)
src/lib/ai/tools/discord.ts (4)
src/utils/discord.ts (2)
  • makeEmbed (27-70)
  • scrub (72-76)
src/lib/ai/providers.ts (1)
  • myProvider (27-39)
src/lib/ai/prompts.ts (1)
  • agentPrompt (79-129)
src/utils/sandbox.ts (1)
  • runInSandbox (11-46)
src/commands/voice-channel/index.ts (2)
src/commands/voice-channel/leave.ts (2)
  • data (10-12)
  • execute (14-31)
src/commands/voice-channel/join.ts (2)
  • data (13-15)
  • execute (17-62)
src/commands/channels.ts (2)
src/events/message-create/index.ts (1)
  • execute (45-105)
src/lib/kv.ts (2)
  • redisKeys (19-24)
  • redis (7-10)
src/utils/context.ts (5)
src/lib/ai/prompts.ts (1)
  • RequestHints (3-12)
src/lib/queries.ts (2)
  • getMessagesByChannel (12-29)
  • getChannelName (31-43)
src/config.ts (2)
  • initialMessages (24-29)
  • timezone (6-6)
src/utils/messages.ts (1)
  • convertToModelMessages (9-27)
src/utils/time.ts (1)
  • getTimeInCity (4-11)
src/utils/log.ts (1)
src/utils/triggers.ts (1)
  • TriggerType (3-3)
src/utils/voice/helpers/deepgram.ts (1)
src/env.ts (1)
  • env (4-56)
src/lib/ai/providers.ts (1)
src/env.ts (1)
  • env (4-56)
src/utils/voice/stream.ts (4)
src/utils/voice/helpers/deepgram.ts (2)
  • deepgram (4-4)
  • speak (11-23)
src/utils/voice/helpers/ai.ts (1)
  • getAIResponse (8-18)
src/config.ts (1)
  • voice (31-33)
src/utils/voice/helpers/audio.ts (1)
  • playAudio (10-37)
src/lib/kv.ts (1)
src/env.ts (1)
  • env (4-56)
src/events/message-create/index.ts (8)
src/lib/kv.ts (3)
  • ratelimit (12-17)
  • redisKeys (19-24)
  • redis (7-10)
src/utils/log.ts (3)
  • logIncoming (16-18)
  • logTrigger (4-14)
  • logReply (20-39)
src/utils/triggers.ts (1)
  • getTrigger (5-27)
src/config.ts (1)
  • keywords (3-3)
src/utils/message-rate-limiter.ts (3)
  • clearUnprompted (11-13)
  • getUnprompted (4-9)
  • hasUnpromptedQuota (15-19)
src/utils/context.ts (1)
  • buildChatContext (10-48)
src/events/message-create/utils/respond.ts (1)
  • generateResponse (13-68)
src/events/message-create/utils/relevance.ts (1)
  • assessRelevance (7-32)
src/utils/message-rate-limiter.ts (2)
src/lib/kv.ts (2)
  • redisKeys (19-24)
  • redis (7-10)
src/config.ts (1)
  • messageThreshold (23-23)
🪛 LanguageTool
README.md

[locale-violation] ~29-~29: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...idpolice.com/how-to-make-discord-bot/). Afterwards, you will need [Git][git] and [Bun][bun...

(AFTERWARDS_US)

TODO.md

[uncategorized] ~6-~6: This expression is usually spelled with a hyphen.
Context: ...true? Agent Isolation for each server, role based access control mention that u need to i...

(BASED_HYPHEN)


[misspelling] ~7-~7: Did you mean “you”?
Context: ... role based access control mention that u need to install rust curl --proto '=htt...

(U_TO_YOU)


[grammar] ~10-~10: This expression is usually spelled with a hyphen.
Context: ...m back audio from ElevenLabs instead of non streaming Switch to AI SDK Voice leater seperate...

(NON_ANTI_JJ)

🪛 markdownlint-cli2 (0.17.2)
README.md

1-1: Inline HTML
Element: h1

(MD033, no-inline-html)


10-10: Inline HTML
Element: a

(MD033, no-inline-html)


14-14: Inline HTML
Element: a

(MD033, no-inline-html)


27-27: Inline HTML
Element: a

(MD033, no-inline-html)


49-49: Inline HTML
Element: a

(MD033, no-inline-html)


55-55: Link and image reference definitions should be needed
Unused link or image reference definition: "pr"

(MD053, link-image-reference-definitions)


57-57: Link and image reference definitions should be needed
Unused link or image reference definition: "node"

(MD053, link-image-reference-definitions)

TODO.md

7-7: Bare URL used
null

(MD034, no-bare-urls)

🪛 Biome (1.9.4)
src/utils/status.ts

[error] 10-10: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

src/utils/voice/helpers/ai.ts

[error] 11-12: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

src/lib/ai/providers.ts

[error] 20-20: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 24-24: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

src/utils/discord.ts

[error] 35-35: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 dotenv-linter (3.3.0)
.env.example

[warning] 10-10: [TrailingWhitespace] Trailing whitespace detected


[warning] 10-10: [UnorderedKey] The DISCORD_OWNER_ID key should go before the DISCORD_TOKEN key


[warning] 12-12: [UnorderedKey] The DISCORD_DEFAULT_GUILD_ID key should go before the DISCORD_OWNER_ID key


[warning] 53-53: [EndingBlankLine] No blank line at the end of the file

🪛 ESLint
src/utils/sandbox.ts

[error] 32-32: Unexpected empty async function.

(@typescript-eslint/no-empty-function)

🔇 Additional comments (29)
LICENSE (4)

1-2: Verify license header accuracy.
Ensure the header exactly matches the official MIT License title (“MIT License”) to avoid any ambiguity in licensing.


3-4: Confirm copyright year and owner.
Double-check that “2025” and “Anirudh Sriram” accurately reflect the project’s current ownership and date.


5-13: Validate permission grant text.
This block must match the standard MIT grant section without unintended modifications. Confirm it aligns verbatim with the approved template.


15-21: Validate warranty disclaimer.
Verify that the disclaimer wording is identical to the canonical MIT License to ensure full legal coverage.

README.md (2)

5-8: Correct Table of Contents numbering
The ToC now properly includes the "3. 📚 Getting Started" entry, ensuring a consecutive sequence.


49-50: Inconsistent license change in summary vs README
The AI summary notes a license change to MIT, but the README still references AGPL-v3. Please verify and update the license text if a change was intended.

Likely an incorrect or invalid review comment.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

49-49: Inline HTML
Element: a

(MD033, no-inline-html)

src/lib/validators/index.ts (1)

1-1: Consistent quote style in exports.

Switching to single quotes for the module path aligns with the project-wide string delimiter convention.

tsconfig.json (1)

1-32: Formatting-only changes.

This update is purely whitespace and line-break adjustments; there are no functional or configuration modifications.

.eslintrc.json (1)

1-26: Formatting-only changes.

Only reflowed whitespace and line breaks to match Prettier rules; ESLint rules remain untouched.

src/lib/validators/probability.ts (2)

1-1: Update zod import path.

Importing from 'zod/v4' ensures compatibility with the pinned major version and matches other parts of the codebase.


7-8: Consistent single quotes in .describe() calls.

Standardizing string delimiters to single quotes improves readability and enforces consistency across all validators.

Also applies to: 12-14

.vscode/settings.json (1)

2-9: Add custom cSpell words for AI/voice features.

Including “arcas”, “Deepgram”, “ELEVENLABS”, and related terms prevents the spell checker from flagging new feature–specific identifiers.

src/lib/queries.ts (1)

1-43: Stylistic formatting changes only; logic and functionality are unchanged and adhere to the .prettierrc.json standards.

src/lib/logger.ts (1)

1-41: All modifications are stylistic—replacing double quotes with single quotes and adjusting spacing per the new Prettier config. No functional changes detected.

.prettierrc.json (1)

1-10: Add consistent Prettier configuration.
The new .prettierrc.json establishes project-wide formatting rules (80-char line width, 2-space indent, single quotes, semicolons, trailing commas, LF endings) matching the style updates in this PR.

src/utils/status.ts (1)

1-8: Pure formatting changes (quote style, indentation) per .prettierrc.json. No behavioral modifications.

Also applies to: 12-36

src/commands/ping.ts (1)

1-9: Only quote style changed; logic and functionality remain correct.

src/events/index.ts (1)

1-5: No functional changes – looks good
Only quote-style & blank-line tweaks; nothing else to flag.

src/deploy-commands.ts (1)

31-43: Top-level await requires ES2022 or later – verify build target

The global deployment block relies on top-level await.
Ensure tsconfig.json is targeting at least ES2022 and that your runtime (e.g., Node ≥ 14.8 with --experimental-top-level-await or Node 16+) matches, otherwise this will be a syntax error in production.

If older environments must be supported, wrap the block in an immediately-invoked async function instead.

src/index.ts (1)

10-21: Verify privileged intents are enabled

GuildMembers and MessageContent require enabling Privileged Gateway Intents in the Discord developer portal. Deployment will silently fail otherwise.

src/utils/log.ts (1)

17-18:

❓ Verification inconclusive

Beware of sensitive-content logging.

content may hold private DMs or PII. If log retention or transport isn’t encrypted, consider redacting or hashing the message text, or toggling with a debug flag.


Review Sensitive Data Logging in src/utils/log.ts

The call to

logger.info({ user: username, content }, `[${ctxId}] Incoming message`);

may record private messages or PII in plain text. If your logging pipeline or storage isn’t fully encrypted and access-controlled, consider:

  • Redacting or hashing the content field before logging
  • Emitting this only at a debug level behind a feature flag
  • Verifying that all log transport and retention are end-to-end encrypted
src/config.ts (1)

8-13:

❌ Incorrect review comment

speedFactor astronomical — verify downstream math doesn’t underflow to 0 ms.

A factor of 6 000 000 000 with speedMethod: 'divide' will likely truncate delays to 0 when using 32-bit integer arithmetic. Confirm the calculation logic or switch to floats.


I’ll search for all usages of speedFactor and speedMethod (and any generic speed. references) to see how the values are applied:


🏁 Script executed:

#!/bin/bash
rg -n "speedFactor" -C3
rg -n "speedMethod" -C3
rg -n "speed\." -C3

Length of output: 2116


Ignore speedFactor underflow concern
In JS/TS the / operator returns a double-precision float, so length / 6000000000 yields a tiny non-zero value (not 0). After multiplying by 1000, Math.max(baseSeconds * 1000, speedConfig.minDelay) (where minDelay = 1) guarantees the delay is never below 1 ms. No changes required.

Likely an incorrect or invalid review comment.

src/utils/messages.ts (1)

9-27: LGTM!

The async conversion to ModelMessage format is well-implemented with proper role assignment and comprehensive author context.

src/lib/kv.ts (1)

1-24: Environment-aware prefixing looks good!

The implementation correctly separates development and production Redis namespaces, and the new allowedChannels key generator follows the established pattern.

src/env.ts (1)

2-2:

✅ Verification successful

Verify the zod import path.

The import from 'zod/v4' is unusual. Most projects import directly from 'zod'.


🌐 Web query:

Is 'zod/v4' a valid import path for the zod library, or should it be imported as 'zod'?

💡 Result:

The import path you should use for the Zod library depends on which major version of Zod you intend to target.

  • If you want to use Zod 4, you should import it using the subpath:

    import { z } from "zod/v4";

    This subpath is explicitly provided to allow projects to migrate from Zod 3 to Zod 4 incrementally. The maintainers have confirmed that Zod 4 is available at the "zod/v4" subpath and is considered stable and production-ready[6][9]. This approach will remain stable and supported, even after Zod 4 becomes the default export in a future release[9].

  • If you want to use Zod 3, or are working in an environment that still relies on Zod 3, you should import from the package root:

    import { z } from "zod";

    The root import will continue to export Zod 3 during the transition period but will eventually switch to Zod 4 after the migration window ends[9].

Summary Table:

Target Zod Version Import Path Notes
Zod 3 "zod" Root continues to export Zod 3 during migration[9].
Zod 4 "zod/v4" Subpath is stable and production-ready; recommended for Zod 4[6][9].

After the transition period, both subpaths ("zod/v3" and "zod/v4") are expected to remain available for compatibility, but the root path ("zod") will eventually switch to Zod 4 by default[9].

In summary:

  • "zod/v4" is a valid and recommended import path for Zod 4[6][9].
  • Use "zod" if you specifically want to target Zod 3 or until the package root switches to Zod 4[5][9].

Citations:


Import path 'zod/v4' is valid for Zod 4 – no changes required.

Verified that the "zod/v4" subpath is the officially supported, stable import for Zod 4 (the root "zod" currently targets Zod 3 during the migration period and will eventually switch to Zod 4). You can safely keep:

• src/env.ts (line 2):

import { z } from 'zod/v4';
src/commands/voice-channel/join.ts (1)

61-61: Use editReply instead of followUp after deferReply

You called deferReply() at the top. To comply with Discord’s interaction rules, prefer editReply() for the first response instead of an additional followUp(), otherwise you may hit “interaction already responded” errors in edge-cases.

src/utils/discord.ts (1)

53-55: Risk of exceeding Discord field length

Wrapping large values in code-blocks (```) inflates their size.
Remember Discord limits field value to 1024 characters – consider truncation or attachment for longer snippets.

src/events/message-create/index.ts (2)

21-27: LGTM!

Clean rate limiting implementation with appropriate logging.


29-43: LGTM!

Good implementation with sensible defaults - allows all channels when none are configured, and always allows DMs.

Comment on lines 55 to 57
[pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
[git]: https://git-scm.com
[node]: https://nodejs.org/
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove unused link reference definitions
The [pr] and [node] reference labels are not used elsewhere in the document. Cleaning them up will reduce clutter.

-[pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
-[node]: https://nodejs.org/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
[git]: https://git-scm.com
[node]: https://nodejs.org/
[git]: https://git-scm.com
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

55-55: Link and image reference definitions should be needed
Unused link or image reference definition: "pr"

(MD053, link-image-reference-definitions)


57-57: Link and image reference definitions should be needed
Unused link or image reference definition: "node"

(MD053, link-image-reference-definitions)

🤖 Prompt for AI Agents
In README.md around lines 55 to 57, the link reference definitions for [pr] and
[node] are not used anywhere else in the document. Remove these unused link
reference definitions to clean up and reduce clutter in the markdown file,
leaving only the [git] reference if it is still used.

[bun]: https://bun.sh/
[mem0]: https://mem0.ai/
[exa]: https://exa.ai/
<h1 align="center">AI Discord Bot</h4>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix mismatched HTML header tag
The opening <h1> tag is incorrectly closed with </h4>, causing an HTML structure error.
Apply this diff:

-<h1 align="center">AI Discord Bot</h4>
+<h1 align="center">AI Discord Bot</h1>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h1 align="center">AI Discord Bot</h4>
<h1 align="center">AI Discord Bot</h1>
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

1-1: Inline HTML
Element: h1

(MD033, no-inline-html)

🤖 Prompt for AI Agents
In README.md at line 1, the opening <h1> tag is incorrectly closed with a </h4>
tag, causing an HTML structure error. Fix this by replacing the closing </h4>
tag with the correct </h1> tag to properly close the header element.

Comment on lines 9 to 10
if (arr.length === 0) throw new Error('Array must not be empty');
return arr[Math.floor(Math.random() * arr.length)]!;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Address non-null assertion lint error.
The ! on the array access is safe due to the preceding empty check, but it violates the @typescript-eslint/no-non-null-assertion rule. Consider either:

  • Removing the ! and handling undefined explicitly, or
  • Suppressing the rule with an ESLint disable comment on this line.
🧰 Tools
🪛 Biome (1.9.4)

[error] 10-10: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

🤖 Prompt for AI Agents
In src/utils/status.ts around lines 9 to 10, the non-null assertion operator `!`
on the array access causes a lint error despite the preceding empty check. To
fix this, either remove the `!` and add explicit handling for the potential
undefined value returned by the array access, or keep the `!` and add an ESLint
disable comment on that line to suppress the no-non-null-assertion rule.

Comment on lines 1 to 2
import { format } from 'date-fns';
import { TZDate } from '@date-fns/tz';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

TZDate import is invalid – switch to date-fns-tz helpers instead
@date-fns/tz does not expose a constructable TZDate class. Attempting new TZDate(...) will throw at runtime.

-import { format } from 'date-fns';
-import { TZDate } from '@date-fns/tz';
+import { format } from 'date-fns';
+import { utcToZonedTime } from 'date-fns-tz';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { format } from 'date-fns';
import { TZDate } from '@date-fns/tz';
import { format } from 'date-fns';
import { utcToZonedTime } from 'date-fns-tz';
🤖 Prompt for AI Agents
In src/utils/time.ts at lines 1 to 2, the import of TZDate from '@date-fns/tz'
is invalid because that package does not export a constructable TZDate class.
Replace the TZDate import with appropriate helpers from the 'date-fns-tz'
package, such as using functions like zonedTimeToUtc or utcToZonedTime, and
update the code to use these functions instead of constructing a TZDate
instance.

Comment on lines 8 to 10
const now = new Date();
const zonedDate = new TZDate(now, timezone);
return format(zonedDate, formatStr);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Runtime crash: new TZDate() is not a function
The current implementation will throw TypeError: TZDate is not a constructor. Use utcToZonedTime (or formatInTimeZone) instead.

-  const now = new Date();
-  const zonedDate = new TZDate(now, timezone);
-  return format(zonedDate, formatStr);
+  const now = new Date();
+  const zonedDate = utcToZonedTime(now, timezone);
+  return format(zonedDate, formatStr, { timeZone: timezone });

This also eliminates the implicit dependency on a custom TZDate type and keeps the codebase aligned with the official date-fns-tz API.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const now = new Date();
const zonedDate = new TZDate(now, timezone);
return format(zonedDate, formatStr);
const now = new Date();
const zonedDate = utcToZonedTime(now, timezone);
return format(zonedDate, formatStr, { timeZone: timezone });
🤖 Prompt for AI Agents
In src/utils/time.ts around lines 8 to 10, replace the usage of the non-existent
TZDate constructor with the official date-fns-tz function utcToZonedTime or
formatInTimeZone. Import utcToZonedTime from date-fns-tz and use it to convert
the current date to the specified timezone before formatting, ensuring
compatibility with the library's API and preventing the runtime TypeError.

Comment on lines +23 to +26
const google = createGoogleGenerativeAI({
apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!,
});

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

google provider is instantiated but never used.

Dead code adds maintenance overhead; either remove it or wire it into imageModels / languageModels.

🧰 Tools
🪛 Biome (1.9.4)

[error] 24-24: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

🤖 Prompt for AI Agents
In src/lib/ai/providers.ts around lines 23 to 26, the 'google' provider is
created but not used anywhere, resulting in dead code. To fix this, either
remove the 'google' provider instantiation if it is unnecessary, or integrate it
properly by adding it to the 'imageModels' or 'languageModels' collections where
other providers are used, ensuring it is utilized in the application.

Comment on lines 19 to 25
const openrouter = createOpenRouter({
apiKey: env.OPENROUTER_API_KEY!,
});

const google = createGoogleGenerativeAI({
apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Non-null assertions hide missing API keys; implement explicit guards.

env.OPENROUTER_API_KEY! and env.GOOGLE_GENERATIVE_AI_API_KEY! were flagged by Biome.
At runtime an undefined key will cause the provider to 401 rather than fail fast.

-const openrouter = createOpenRouter({
-  apiKey: env.OPENROUTER_API_KEY!,
-});
+if (!env.OPENROUTER_API_KEY) {
+  throw new Error('OPENROUTER_API_KEY is not set');
+}
+const openrouter = createOpenRouter({ apiKey: env.OPENROUTER_API_KEY });

Apply similar handling for the Google provider.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const openrouter = createOpenRouter({
apiKey: env.OPENROUTER_API_KEY!,
});
const google = createGoogleGenerativeAI({
apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!,
});
if (!env.OPENROUTER_API_KEY) {
throw new Error('OPENROUTER_API_KEY is not set');
}
const openrouter = createOpenRouter({ apiKey: env.OPENROUTER_API_KEY });
const google = createGoogleGenerativeAI({
apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!,
});
🧰 Tools
🪛 Biome (1.9.4)

[error] 20-20: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 24-24: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

🤖 Prompt for AI Agents
In src/lib/ai/providers.ts around lines 19 to 25, the code uses non-null
assertions for API keys which can cause runtime 401 errors if keys are missing.
Replace the non-null assertions with explicit checks that verify the presence of
env.OPENROUTER_API_KEY and env.GOOGLE_GENERATIVE_AI_API_KEY before creating the
providers. If a key is missing, throw a clear error immediately to fail fast and
avoid runtime authentication issues.

Comment on lines 5 to 8
const key = redisKeys.messageCount(ctxId);
const n = await redis.incr(key);
if (n === 1) await redis.expire(key, 3600); // 1‑hour window
return n;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

n === 1 comparison might fail due to string vs number.

redis.incr from Upstash can return a string. Guard with Number(n) === 1 to ensure the expiry is always set.

-  const n = await redis.incr(key);
-  if (n === 1) await redis.expire(key, 3600); // 1-hour window
+  const n = Number(await redis.incr(key));
+  if (n === 1) await redis.expire(key, 3600); // 1-hour window
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const key = redisKeys.messageCount(ctxId);
const n = await redis.incr(key);
if (n === 1) await redis.expire(key, 3600); // 1‑hour window
return n;
const key = redisKeys.messageCount(ctxId);
- const n = await redis.incr(key);
- if (n === 1) await redis.expire(key, 3600); // 1-hour window
+ const n = Number(await redis.incr(key));
+ if (n === 1) await redis.expire(key, 3600); // 1-hour window
return n;
🤖 Prompt for AI Agents
In src/utils/message-rate-limiter.ts around lines 5 to 8, the comparison n === 1
may fail because redis.incr can return a string instead of a number. To fix
this, convert n to a number using Number(n) before the comparison, changing the
condition to Number(n) === 1 to ensure the expiry is always set correctly.

Comment on lines 29 to 59
export async function processAttachments(
attachments: Collection<string, DiscordAttachment>,
): Promise<Array<FilePart>> {
const validTypes = ['image/jpeg', 'image/png', 'application/pdf'];
const invalidAttachments = attachments.filter(
(attachment) => !validTypes.includes(attachment.contentType ?? ''),
);

if (invalidAttachments.size > 0) {
logger.warn(
`Ignoring attachments: ${Array.from(invalidAttachments.values())
.map((a) => a.name)
.join(', ')}`,
);
}

const results = await Promise.all(
attachments.map(async (attachment) => {
const response = await fetch(attachment.url);
const buffer = await response.arrayBuffer();
return {
type: 'file' as const,
data: buffer,
mediaType: attachment.contentType ?? 'application/octet-stream',
filename: attachment.name,
};
}),
);

return results;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix attachment filtering logic and add error handling.

The function filters for valid attachment types but then processes all attachments, including invalid ones. Additionally, the fetch operation lacks error handling.

Apply this diff to fix the issues:

 export async function processAttachments(
   attachments: Collection<string, DiscordAttachment>,
 ): Promise<Array<FilePart>> {
   const validTypes = ['image/jpeg', 'image/png', 'application/pdf'];
-  const invalidAttachments = attachments.filter(
-    (attachment) => !validTypes.includes(attachment.contentType ?? ''),
+  const validAttachments = attachments.filter(
+    (attachment) => validTypes.includes(attachment.contentType ?? ''),
   );
+  const invalidAttachments = attachments.filter(
+    (attachment) => !validTypes.includes(attachment.contentType ?? ''),
+  );

   if (invalidAttachments.size > 0) {
     logger.warn(
       `Ignoring attachments: ${Array.from(invalidAttachments.values())
         .map((a) => a.name)
         .join(', ')}`,
     );
   }

   const results = await Promise.all(
-    attachments.map(async (attachment) => {
+    validAttachments.map(async (attachment) => {
+      try {
         const response = await fetch(attachment.url);
+        if (!response.ok) {
+          throw new Error(`Failed to fetch attachment: ${response.statusText}`);
+        }
         const buffer = await response.arrayBuffer();
         return {
           type: 'file' as const,
           data: buffer,
           mediaType: attachment.contentType ?? 'application/octet-stream',
           filename: attachment.name,
         };
+      } catch (error) {
+        logger.error(`Failed to process attachment ${attachment.name}:`, error);
+        throw error;
+      }
     }),
   );

   return results;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function processAttachments(
attachments: Collection<string, DiscordAttachment>,
): Promise<Array<FilePart>> {
const validTypes = ['image/jpeg', 'image/png', 'application/pdf'];
const invalidAttachments = attachments.filter(
(attachment) => !validTypes.includes(attachment.contentType ?? ''),
);
if (invalidAttachments.size > 0) {
logger.warn(
`Ignoring attachments: ${Array.from(invalidAttachments.values())
.map((a) => a.name)
.join(', ')}`,
);
}
const results = await Promise.all(
attachments.map(async (attachment) => {
const response = await fetch(attachment.url);
const buffer = await response.arrayBuffer();
return {
type: 'file' as const,
data: buffer,
mediaType: attachment.contentType ?? 'application/octet-stream',
filename: attachment.name,
};
}),
);
return results;
}
export async function processAttachments(
attachments: Collection<string, DiscordAttachment>,
): Promise<Array<FilePart>> {
const validTypes = ['image/jpeg', 'image/png', 'application/pdf'];
const validAttachments = attachments.filter(
(attachment) => validTypes.includes(attachment.contentType ?? ''),
);
const invalidAttachments = attachments.filter(
(attachment) => !validTypes.includes(attachment.contentType ?? ''),
);
if (invalidAttachments.size > 0) {
logger.warn(
`Ignoring attachments: ${Array.from(invalidAttachments.values())
.map((a) => a.name)
.join(', ')}`,
);
}
const results = await Promise.all(
validAttachments.map(async (attachment) => {
try {
const response = await fetch(attachment.url);
if (!response.ok) {
throw new Error(`Failed to fetch attachment: ${response.statusText}`);
}
const buffer = await response.arrayBuffer();
return {
type: 'file' as const,
data: buffer,
mediaType: attachment.contentType ?? 'application/octet-stream',
filename: attachment.name,
};
} catch (error) {
logger.error(`Failed to process attachment ${attachment.name}:`, error);
throw error;
}
}),
);
return results;
}
🤖 Prompt for AI Agents
In src/utils/messages.ts around lines 29 to 59, the function filters invalid
attachments but still processes all attachments including invalid ones, and
lacks error handling for fetch operations. Fix this by changing the attachments
processed to only the valid ones filtered by contentType. Also, wrap the fetch
call in a try-catch block to handle potential errors gracefully, logging or
skipping failed fetches as appropriate.

Comment on lines +121 to +124
return interaction.reply({
content: 'Unknown subcommand. ',
flags: MessageFlags.Ephemeral,
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Fix trailing space in error message.

-    content: 'Unknown subcommand. ',
+    content: 'Unknown subcommand.',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return interaction.reply({
content: 'Unknown subcommand. ',
flags: MessageFlags.Ephemeral,
});
return interaction.reply({
content: 'Unknown subcommand.',
flags: MessageFlags.Ephemeral,
});
🤖 Prompt for AI Agents
In src/commands/channels.ts around lines 121 to 124, the error message string
'Unknown subcommand. ' has a trailing space at the end. Remove the trailing
space from the content string so it reads 'Unknown subcommand.' without any
extra spaces.

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.

Review continued from previous batch...

Comment on lines +60 to +95
if (sub === 'add' || sub === 'remove') {
const channel = getChannel();

if (channel.type !== ChannelType.GuildText) {
return interaction.reply({
content: 'Please pick a text channel.',
flags: MessageFlags.Ephemeral,
});
}

if (sub === 'add') {
const isMember = await redis.sismember(guildKey, channel.id);
if (isMember) {
return interaction.reply({
content: `${channel} is already allowed.`,
flags: MessageFlags.Ephemeral,
});
}
await redis.sadd(guildKey, channel.id);
return interaction.reply({
content: `done! thanks for letting me talk in ${channel}!`,
flags: MessageFlags.Ephemeral,
});
} else {
const removedCount = await redis.srem(guildKey, channel.id);
if (!removedCount) {
return interaction.reply({
content: `there's nothing to remove! ${channel} wasn't even on the list.`,
flags: MessageFlags.Ephemeral,
});
}
return interaction.reply({
content: `aw... ${channel} has been removed from the allowed list. i won't talk there anymore...`,
flags: MessageFlags.Ephemeral,
});
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove redundant channel type check.

The channel type validation is unnecessary since the option already restricts selection to text channels only.

   if (sub === 'add' || sub === 'remove') {
     const channel = getChannel();

-    if (channel.type !== ChannelType.GuildText) {
-      return interaction.reply({
-        content: 'Please pick a text channel.',
-        flags: MessageFlags.Ephemeral,
-      });
-    }
-
     if (sub === 'add') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (sub === 'add' || sub === 'remove') {
const channel = getChannel();
if (channel.type !== ChannelType.GuildText) {
return interaction.reply({
content: 'Please pick a text channel.',
flags: MessageFlags.Ephemeral,
});
}
if (sub === 'add') {
const isMember = await redis.sismember(guildKey, channel.id);
if (isMember) {
return interaction.reply({
content: `${channel} is already allowed.`,
flags: MessageFlags.Ephemeral,
});
}
await redis.sadd(guildKey, channel.id);
return interaction.reply({
content: `done! thanks for letting me talk in ${channel}!`,
flags: MessageFlags.Ephemeral,
});
} else {
const removedCount = await redis.srem(guildKey, channel.id);
if (!removedCount) {
return interaction.reply({
content: `there's nothing to remove! ${channel} wasn't even on the list.`,
flags: MessageFlags.Ephemeral,
});
}
return interaction.reply({
content: `aw... ${channel} has been removed from the allowed list. i won't talk there anymore...`,
flags: MessageFlags.Ephemeral,
});
}
if (sub === 'add' || sub === 'remove') {
const channel = getChannel();
if (sub === 'add') {
const isMember = await redis.sismember(guildKey, channel.id);
if (isMember) {
return interaction.reply({
content: `${channel} is already allowed.`,
flags: MessageFlags.Ephemeral,
});
}
await redis.sadd(guildKey, channel.id);
return interaction.reply({
content: `done! thanks for letting me talk in ${channel}!`,
flags: MessageFlags.Ephemeral,
});
} else {
const removedCount = await redis.srem(guildKey, channel.id);
if (!removedCount) {
return interaction.reply({
content: `there's nothing to remove! ${channel} wasn't even on the list.`,
flags: MessageFlags.Ephemeral,
});
}
return interaction.reply({
content: `aw... ${channel} has been removed from the allowed list. i won't talk there anymore...`,
flags: MessageFlags.Ephemeral,
});
}
🤖 Prompt for AI Agents
In src/commands/channels.ts between lines 60 and 95, remove the redundant check
for channel.type !== ChannelType.GuildText because the command option already
restricts the selection to text channels. Simply delete the entire if block that
returns an error for non-text channels to streamline the code.

Comment on lines 29 to 31
MEM0_API_KEY: z.string().min(1).startsWith('m0-'),
// AssemblyAI
DEEPGRAM_API_KEY: z.string().min(1),
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Fix comment to match the service name.

The comment says "AssemblyAI" but the key is for Deepgram.

-    // AssemblyAI
+    // Deepgram
     DEEPGRAM_API_KEY: z.string().min(1),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
MEM0_API_KEY: z.string().min(1).startsWith('m0-'),
// AssemblyAI
DEEPGRAM_API_KEY: z.string().min(1),
MEM0_API_KEY: z.string().min(1).startsWith('m0-'),
// Deepgram
DEEPGRAM_API_KEY: z.string().min(1),
🤖 Prompt for AI Agents
In src/env.ts around lines 29 to 31, the comment above DEEPGRAM_API_KEY
incorrectly says "AssemblyAI" instead of "Deepgram". Update the comment to
correctly reflect the service name as "Deepgram" to match the API key it
describes.

Comment on lines +88 to +93
const result = await runInSandbox({
code,
context: sharedState,
allowRequire: true,
allowedModules: ['discord.js'],
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider expanding allowed modules for sandbox execution.

The agent prompt mentions needing access to ChannelType enum and the sandbox code might require Node.js built-ins like util or stream.

               const result = await runInSandbox({
                 code,
                 context: sharedState,
                 allowRequire: true,
-                allowedModules: ['discord.js'],
+                allowedModules: ['discord.js', 'util', 'stream', 'events'],
               });

Additionally, consider injecting commonly used enums into the shared state:

import { ChannelType } from 'discord.js';

const sharedState: Record<string, any> = {
  state: {},
  last: undefined,
  client,
  message,
  ChannelType, // Add this
};
🤖 Prompt for AI Agents
In src/lib/ai/tools/discord.ts around lines 88 to 93, the sandbox execution
currently only allows the 'discord.js' module, but the code may also require
Node.js built-in modules like 'util' or 'stream'. Update the allowedModules
array to include these built-ins. Additionally, import the ChannelType enum from
'discord.js' and inject it into the sharedState object so it is accessible
within the sandbox environment.

Comment on lines +136 to +151
logger.info({ ...answer }, 'Agent completed');

await status.edit({
embeds: [
makeEmbed({
title: answer?.success ? 'Task Completed' : 'Task Failed',
color: answer?.success ? 0x00ff00 : 0xff0000,
fields: [
{ name: 'Answer', value: answer?.answer },
{ name: 'Reasoning', value: answer?.reasoning },
],
}),
],
allowedMentions: { repliedUser: false },
});

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for status message updates.

If the status message edit fails, the user won't receive the final result.

       logger.info({ ...answer }, 'Agent completed');

+      try {
         await status.edit({
           embeds: [
             makeEmbed({
               title: answer?.success ? 'Task Completed' : 'Task Failed',
               color: answer?.success ? 0x00ff00 : 0xff0000,
               fields: [
                 { name: 'Answer', value: answer?.answer },
                 { name: 'Reasoning', value: answer?.reasoning },
               ],
             }),
           ],
           allowedMentions: { repliedUser: false },
         });
+      } catch (error) {
+        logger.error('Failed to update status message:', error);
+        // Attempt to send a new message if edit fails
+        await message.channel.send({
+          content: `Task ${answer?.success ? 'completed' : 'failed'}: ${answer?.answer}`,
+        });
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.info({ ...answer }, 'Agent completed');
await status.edit({
embeds: [
makeEmbed({
title: answer?.success ? 'Task Completed' : 'Task Failed',
color: answer?.success ? 0x00ff00 : 0xff0000,
fields: [
{ name: 'Answer', value: answer?.answer },
{ name: 'Reasoning', value: answer?.reasoning },
],
}),
],
allowedMentions: { repliedUser: false },
});
logger.info({ ...answer }, 'Agent completed');
try {
await status.edit({
embeds: [
makeEmbed({
title: answer?.success ? 'Task Completed' : 'Task Failed',
color: answer?.success ? 0x00ff00 : 0xff0000,
fields: [
{ name: 'Answer', value: answer?.answer },
{ name: 'Reasoning', value: answer?.reasoning },
],
}),
],
allowedMentions: { repliedUser: false },
});
} catch (error) {
logger.error('Failed to update status message:', error);
// Attempt to send a new message if edit fails
await message.channel.send({
content: `Task ${answer?.success ? 'completed' : 'failed'}: ${answer?.answer}`,
});
}
🤖 Prompt for AI Agents
In src/lib/ai/tools/discord.ts between lines 136 and 151, the call to
status.edit lacks error handling, which risks failing silently and leaving the
user uninformed if the message update fails. Wrap the await status.edit call in
a try-catch block to catch any errors, and log or handle the error appropriately
to ensure failures are noticed and can be addressed.

Comment on lines 24 to 32
if (!connection) {
if (!interaction.member?.voice.channel) {
await interaction.followUp(
'okay, but you\'re not in vc'
);

return;
}

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Minor: user-facing copy & early return

The follow-up message "okay, but you're not in vc" is fine for testing, but reads a bit informal for production.
Consider a clearer copy such as “You need to be in a voice channel for me to join.”

🤖 Prompt for AI Agents
In src/commands/voice-channel/join.ts around lines 24 to 32, the user-facing
message when the user is not in a voice channel is too informal. Replace the
string "okay, but you're not in vc" with a clearer and more professional message
like "You need to be in a voice channel for me to join." to improve user
experience.

Comment on lines +47 to +50
if (!(await isChannelAllowed(message))) {
logger.info(`Channel ${message.channel.id} not in allowed channels list`);
return;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider using debug level for non-allowed channel logs.

This info-level log could be verbose in production if the bot is in many servers with restricted channels.

 if (!(await isChannelAllowed(message))) {
-  logger.info(`Channel ${message.channel.id} not in allowed channels list`);
+  logger.debug(`Channel ${message.channel.id} not in allowed channels list`);
   return;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!(await isChannelAllowed(message))) {
logger.info(`Channel ${message.channel.id} not in allowed channels list`);
return;
}
if (!(await isChannelAllowed(message))) {
logger.debug(`Channel ${message.channel.id} not in allowed channels list`);
return;
}
🤖 Prompt for AI Agents
In src/events/message-create/index.ts around lines 47 to 50, the log message for
channels not allowed is currently at info level, which can be too verbose in
production environments with many restricted channels. Change the logger call
from info to debug level to reduce log noise while still capturing the event for
troubleshooting when debug logs are enabled.

Comment on lines 93 to 96
if (probability <= 0.5) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider making the relevance threshold configurable.

The hardcoded 0.5 threshold reduces flexibility for tuning the bot's responsiveness.

Consider moving this to a configuration value:

-if (probability <= 0.5) {
+const RELEVANCE_THRESHOLD = 0.5; // Move to config
+if (probability <= RELEVANCE_THRESHOLD) {
   logger.debug(`[${ctxId}] Low relevance — ignoring`);
   return;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (probability <= 0.5) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}
const RELEVANCE_THRESHOLD = 0.5; // Move to config
if (probability <= RELEVANCE_THRESHOLD) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}
🤖 Prompt for AI Agents
In src/events/message-create/index.ts around lines 93 to 96, the relevance
threshold is hardcoded as 0.5, limiting flexibility. Refactor the code to
replace the hardcoded value with a configurable parameter, such as reading the
threshold from a configuration file or environment variable. This will allow
tuning the bot's responsiveness without code changes.

Comment on lines 45 to 105
export async function execute(message: Message) {
if (message.author.bot) return;
if (!(await isChannelAllowed(message))) {
logger.info(`Channel ${message.channel.id} not in allowed channels list`);
return;
}

const { content, client, guild, author } = message;
const isDM = !guild;
const ctxId = isDM ? `dm:${author.id}` : guild.id;

logIncoming(ctxId, author.username, content);

if (!(await canReply(ctxId))) return;

const botId = client.user?.id;
const trigger = getTrigger(message, keywords, botId);

if (trigger.type) {
await clearUnprompted(ctxId);
logTrigger(ctxId, trigger);

const { messages, hints, memories } = await buildChatContext(message);
const result = await generateResponse(message, messages, hints, memories);
logReply(ctxId, author.username, result, 'explicit trigger');
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
return;
}

const idleCount = await getUnprompted(ctxId);
logger.debug(`[${ctxId}] Idle counter: ${idleCount}`);

if (!(await hasUnpromptedQuota(ctxId))) {
logger.info(`[${ctxId}] Idle quota exhausted — staying silent`);
return;
}

const { messages, hints, memories } = await buildChatContext(message);
const { probability, reason } = await assessRelevance(
message,
messages,
hints,
memories,
);
logger.info({ reason, probability }, `[${ctxId}] Relevance check`);

if (probability <= 0.5) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}

await clearUnprompted(ctxId);
logger.info(`[${ctxId}] Replying; idle counter reset`);
const result = await generateResponse(message, messages, hints, memories);
logReply(ctxId, author.username, result, 'high relevance');
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for async operations.

The function performs multiple async operations without error handling, which could lead to unhandled promise rejections.

Wrap the function body in a try-catch block:

 export async function execute(message: Message) {
+  try {
   if (message.author.bot) return;
   if (!(await isChannelAllowed(message))) {
     logger.info(`Channel ${message.channel.id} not in allowed channels list`);
     return;
   }
   
   // ... rest of the function
   
   if (result.success && result.response) {
     await staggeredReply(message, result.response);
   }
+  } catch (error) {
+    logger.error({ error, messageId: message.id }, 'Error handling message');
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function execute(message: Message) {
if (message.author.bot) return;
if (!(await isChannelAllowed(message))) {
logger.info(`Channel ${message.channel.id} not in allowed channels list`);
return;
}
const { content, client, guild, author } = message;
const isDM = !guild;
const ctxId = isDM ? `dm:${author.id}` : guild.id;
logIncoming(ctxId, author.username, content);
if (!(await canReply(ctxId))) return;
const botId = client.user?.id;
const trigger = getTrigger(message, keywords, botId);
if (trigger.type) {
await clearUnprompted(ctxId);
logTrigger(ctxId, trigger);
const { messages, hints, memories } = await buildChatContext(message);
const result = await generateResponse(message, messages, hints, memories);
logReply(ctxId, author.username, result, 'explicit trigger');
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
return;
}
const idleCount = await getUnprompted(ctxId);
logger.debug(`[${ctxId}] Idle counter: ${idleCount}`);
if (!(await hasUnpromptedQuota(ctxId))) {
logger.info(`[${ctxId}] Idle quota exhausted — staying silent`);
return;
}
const { messages, hints, memories } = await buildChatContext(message);
const { probability, reason } = await assessRelevance(
message,
messages,
hints,
memories,
);
logger.info({ reason, probability }, `[${ctxId}] Relevance check`);
if (probability <= 0.5) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}
await clearUnprompted(ctxId);
logger.info(`[${ctxId}] Replying; idle counter reset`);
const result = await generateResponse(message, messages, hints, memories);
logReply(ctxId, author.username, result, 'high relevance');
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
}
export async function execute(message: Message) {
try {
if (message.author.bot) return;
if (!(await isChannelAllowed(message))) {
logger.info(`Channel ${message.channel.id} not in allowed channels list`);
return;
}
const { content, client, guild, author } = message;
const isDM = !guild;
const ctxId = isDM ? `dm:${author.id}` : guild.id;
logIncoming(ctxId, author.username, content);
if (!(await canReply(ctxId))) return;
const botId = client.user?.id;
const trigger = getTrigger(message, keywords, botId);
if (trigger.type) {
await clearUnprompted(ctxId);
logTrigger(ctxId, trigger);
const { messages, hints, memories } = await buildChatContext(message);
const result = await generateResponse(message, messages, hints, memories);
logReply(ctxId, author.username, result, 'explicit trigger');
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
return;
}
const idleCount = await getUnprompted(ctxId);
logger.debug(`[${ctxId}] Idle counter: ${idleCount}`);
if (!(await hasUnpromptedQuota(ctxId))) {
logger.info(`[${ctxId}] Idle quota exhausted — staying silent`);
return;
}
const { messages, hints, memories } = await buildChatContext(message);
const { probability, reason } = await assessRelevance(
message,
messages,
hints,
memories,
);
logger.info({ reason, probability }, `[${ctxId}] Relevance check`);
if (probability <= 0.5) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}
await clearUnprompted(ctxId);
logger.info(`[${ctxId}] Replying; idle counter reset`);
const result = await generateResponse(message, messages, hints, memories);
logReply(ctxId, author.username, result, 'high relevance');
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
} catch (error) {
logger.error({ error, messageId: message.id }, 'Error handling message');
}
}
🤖 Prompt for AI Agents
In src/events/message-create/index.ts from lines 45 to 105, the execute function
performs many asynchronous operations without error handling, risking unhandled
promise rejections. Wrap the entire function body in a try-catch block to catch
any errors thrown during async calls. In the catch block, log the error
appropriately to aid debugging and prevent the function from failing silently.

Comment on lines 160 to 176
export const systemPrompt = ({
selectedChatModel,
requestHints,
memories,
}: {
selectedChatModel: string;
requestHints: RequestHints;
memories: string;
}) => {
const requestPrompt = getRequestPromptFromHints(requestHints);

if (selectedChatModel === 'chat-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
} else if (selectedChatModel === 'relevance-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle unmatched model types.

The function implicitly returns undefined when selectedChatModel doesn't match either case, which could cause runtime errors.

Add a default case or throw an error:

 export const systemPrompt = ({
   selectedChatModel,
   requestHints,
   memories,
 }: {
   selectedChatModel: string;
   requestHints: RequestHints;
   memories: string;
 }) => {
   const requestPrompt = getRequestPromptFromHints(requestHints);

   if (selectedChatModel === 'chat-model') {
     return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
   } else if (selectedChatModel === 'relevance-model') {
     return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
+  } else {
+    throw new Error(`Unknown chat model: ${selectedChatModel}`);
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const systemPrompt = ({
selectedChatModel,
requestHints,
memories,
}: {
selectedChatModel: string;
requestHints: RequestHints;
memories: string;
}) => {
const requestPrompt = getRequestPromptFromHints(requestHints);
if (selectedChatModel === 'chat-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
} else if (selectedChatModel === 'relevance-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
}
};
export const systemPrompt = ({
selectedChatModel,
requestHints,
memories,
}: {
selectedChatModel: string;
requestHints: RequestHints;
memories: string;
}) => {
const requestPrompt = getRequestPromptFromHints(requestHints);
if (selectedChatModel === 'chat-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
} else if (selectedChatModel === 'relevance-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
} else {
throw new Error(`Unknown chat model: ${selectedChatModel}`);
}
};
🤖 Prompt for AI Agents
In src/lib/ai/prompts.ts around lines 160 to 176, the systemPrompt function does
not handle cases where selectedChatModel is neither 'chat-model' nor
'relevance-model', causing it to implicitly return undefined. Add a default case
at the end of the function to either throw a descriptive error or return a
fallback prompt string to ensure the function always returns a valid string and
avoid runtime errors.

Comment on lines 14 to 26
export const getRequestPromptFromHints = (requestHints: RequestHints) => `\
You live in ${requestHints.city}, ${requestHints.country}.
In ${requestHints.city} and the date and time is ${requestHints.time}.
You're in the ${requestHints.server} Discord Server, and in the ${
requestHints.channel
} channel.
You joined the server on ${new Date(
requestHints.joined,
).toLocaleDateString()}.
Your current status is ${requestHints.status} and your activity is ${
requestHints.activity
}.
`;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove trailing spaces.

Line 25 has trailing spaces after the period.

   Your current status is ${requestHints.status} and your activity is ${
   requestHints.activity
-}.    
+}.
   `;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getRequestPromptFromHints = (requestHints: RequestHints) => `\
You live in ${requestHints.city}, ${requestHints.country}.
In ${requestHints.city} and the date and time is ${requestHints.time}.
You're in the ${requestHints.server} Discord Server, and in the ${
requestHints.channel
} channel.
You joined the server on ${new Date(
requestHints.joined,
).toLocaleDateString()}.
Your current status is ${requestHints.status} and your activity is ${
requestHints.activity
}.
`;
export const getRequestPromptFromHints = (requestHints: RequestHints) => `\
You live in ${requestHints.city}, ${requestHints.country}.
In ${requestHints.city} and the date and time is ${requestHints.time}.
You're in the ${requestHints.server} Discord Server, and in the ${
requestHints.channel
} channel.
You joined the server on ${new Date(
requestHints.joined,
).toLocaleDateString()}.
Your current status is ${requestHints.status} and your activity is ${
requestHints.activity
}.
`;
🤖 Prompt for AI Agents
In src/lib/ai/prompts.ts around lines 14 to 26, remove the trailing spaces after
the period on line 25 in the template string to clean up the formatting and
avoid unnecessary whitespace.

@techwithanirudh techwithanirudh merged commit 026d5e3 into main Jun 15, 2025
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: 2

♻️ Duplicate comments (4)
TODO.md (1)

6-16: 🧹 Nitpick (assertive)

Improve wording, fix typos & apply Markdown hygiene

LanguageTool / markdown-lint are already complaining. A quick clean-up keeps the roadmap readable and CI happy.

-Agent Isolation for each server, role based access control
-mention that u need to install rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-Put commands like join and leave as a subcommand under VC, make an easierway to have subcommands rather clubbing in one file
-Stream back audio from ElevenLabs instead of non streaming
-Switch to AI SDK Voice leater
-seperate deepgram code into its seperate files
-probably switch to 11 labs
-Implement memory for the ai voic chat
+Agent isolation for each server – role-based access control
+Mention that **you** need to install Rust:  
+`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
+Move `join` / `leave` under a top-level `vc` command; provide a helper for sub-commands instead of stuffing everything into one file
+Stream back audio from ElevenLabs instead of non-streaming
+Switch to AI-SDK voice *later*
+Separate Deepgram code into its own files
+Consider switching to ElevenLabs
+Implement memory for the AI voice chat
 Add commit lint
🧰 Tools
🪛 LanguageTool

[uncategorized] ~6-~6: This expression is usually spelled with a hyphen.
Context: ...true? Agent Isolation for each server, role based access control mention that u need to i...

(BASED_HYPHEN)


[misspelling] ~7-~7: Did you mean “you”?
Context: ... role based access control mention that u need to install rust curl --proto '=htt...

(U_TO_YOU)


[grammar] ~10-~10: This expression is usually spelled with a hyphen.
Context: ...m back audio from ElevenLabs instead of non streaming Switch to AI SDK Voice leater seperate...

(NON_ANTI_JJ)

🪛 markdownlint-cli2 (0.17.2)

7-7: Bare URL used
null

(MD034, no-bare-urls)

src/commands/voice-channel/leave.ts (1)

1-9: Remove unused imports to satisfy noUnusedLocals
Same point raised in the previous review – they’re still unused.

-import { CommandInteraction, SlashCommandBuilder } from 'discord.js';
-import {
-  entersState,
-  getVoiceConnection,
-  joinVoiceChannel,
-  VoiceConnectionStatus,
-} from '@discordjs/voice';
+import { SlashCommandBuilder } from 'discord.js';
+import { getVoiceConnection } from '@discordjs/voice';
-import type { ChatInputCommandInteraction, Snowflake } from 'discord.js';
+import type { ChatInputCommandInteraction } from 'discord.js';
src/commands/voice-channel/join.ts (2)

25-28: Previous feedback on user-facing copy still applies

The message is still "okay, but you're not in vc".
Please replace with a more neutral sentence as suggested earlier.


47-50: Duplicate speaking listeners & players → memory leak

Running /vc join twice registers an additional speaking listener and AudioPlayer.
Guard with a per-guild map or listenerCount('start') before adding.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57e3d60 and 4533ece.

📒 Files selected for processing (3)
  • TODO.md (1 hunks)
  • src/commands/voice-channel/join.ts (1 hunks)
  • src/commands/voice-channel/leave.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/commands/voice-channel/leave.ts (2)
src/commands/voice-channel/join.ts (2)
  • data (13-15)
  • execute (17-60)
src/commands/voice-channel/index.ts (2)
  • data (5-15)
  • execute (17-33)
🪛 LanguageTool
TODO.md

[uncategorized] ~6-~6: This expression is usually spelled with a hyphen.
Context: ...true? Agent Isolation for each server, role based access control mention that u need to i...

(BASED_HYPHEN)


[misspelling] ~7-~7: Did you mean “you”?
Context: ... role based access control mention that u need to install rust curl --proto '=htt...

(U_TO_YOU)


[grammar] ~10-~10: This expression is usually spelled with a hyphen.
Context: ...m back audio from ElevenLabs instead of non streaming Switch to AI SDK Voice leater seperate...

(NON_ANTI_JJ)

🪛 markdownlint-cli2 (0.17.2)
TODO.md

7-7: Bare URL used
null

(MD034, no-bare-urls)

Comment on lines +20 to +23
await interaction.reply({
content: "wdym? i'm not in a voice channel",
ephemeral: true,
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Tone down informal copy

"wdym? i'm not in a voice channel" is fun, but may not fit a production bot’s voice.
Consider something clearer such as:

-      content: "wdym? i'm not in a voice channel",
+      content: "I'm not currently connected to any voice channel.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await interaction.reply({
content: "wdym? i'm not in a voice channel",
ephemeral: true,
});
await interaction.reply({
content: "I'm not currently connected to any voice channel.",
ephemeral: true,
});
🤖 Prompt for AI Agents
In src/commands/voice-channel/leave.ts around lines 20 to 23, the reply message
uses informal language "wdym? i'm not in a voice channel" which is not suitable
for a production environment. Replace this string with a clearer, more
professional message such as "You are not currently in a voice channel." to
maintain a consistent and appropriate tone.

Comment on lines +1 to +12
import { CommandInteraction, SlashCommandBuilder } from 'discord.js';
import {
createAudioPlayer,
entersState,
getVoiceConnection,
joinVoiceChannel,
VoiceConnectionStatus,
} from '@discordjs/voice';
import type { ChatInputCommandInteraction, Snowflake } from 'discord.js';
import { createListeningStream } from '@/utils/voice/stream';
import { playAudio } from '@/utils/voice/helpers';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused symbols (CommandInteraction, Snowflake, playAudio) break noUnusedLocals

-import { CommandInteraction, SlashCommandBuilder } from 'discord.js';
+import { SlashCommandBuilder } from 'discord.js';
@@
-import type { ChatInputCommandInteraction, Snowflake } from 'discord.js';
+import type { ChatInputCommandInteraction } from 'discord.js';
-import { playAudio } from '@/utils/voice/helpers';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { CommandInteraction, SlashCommandBuilder } from 'discord.js';
import {
createAudioPlayer,
entersState,
getVoiceConnection,
joinVoiceChannel,
VoiceConnectionStatus,
} from '@discordjs/voice';
import type { ChatInputCommandInteraction, Snowflake } from 'discord.js';
import { createListeningStream } from '@/utils/voice/stream';
import { playAudio } from '@/utils/voice/helpers';
import { SlashCommandBuilder } from 'discord.js';
import {
createAudioPlayer,
entersState,
getVoiceConnection,
joinVoiceChannel,
VoiceConnectionStatus,
} from '@discordjs/voice';
import type { ChatInputCommandInteraction } from 'discord.js';
import { createListeningStream } from '@/utils/voice/stream';
🤖 Prompt for AI Agents
In src/commands/voice-channel/join.ts lines 1 to 12, the imports
CommandInteraction, Snowflake, and playAudio are not used anywhere in the code,
causing noUnusedLocals errors. Remove these unused imports from the import
statements to fix the issue.

@coderabbitai coderabbitai bot mentioned this pull request Jun 24, 2025
8 tasks
This was referenced Jul 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants