Skip to content

Add Claude Code provider support#13

Open
Ghvstcode wants to merge 13 commits intomeltylabs:mainfrom
Ghvstcode:Ghvstcode/add-claudecode-support
Open

Add Claude Code provider support#13
Ghvstcode wants to merge 13 commits intomeltylabs:mainfrom
Ghvstcode:Ghvstcode/add-claudecode-support

Conversation

@Ghvstcode
Copy link
Copy Markdown

Adds Claude Code as an AI model provider to Chorus.

Summary

  • Created ProviderClaudeCode.ts module to handle Claude Code model integration
  • Updated model management to include Claude Code in the model picker
  • Added necessary backend command support for Claude Code API calls
  • Updated database schema to store Claude Code specific configuration
  • Added proxy utility functions for Claude Code requests

Test Plan

  • Claude Code appears in the model picker
  • Can select Claude Code model for use in chat
  • Can send messages to Claude Code and receive responses
  • Claude Code responses appear correctly formatted in chat history
  • Existing Claude, Gemini, and OpenAI models still work as expected

Adds integration with Claude Code as an AI model provider, enabling users to chat with Claude Code alongside other AI models in Chorus.
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (3)

  1. src-tauri/src/command.rs, line 789-790 (link)

    syntax: HOME env var doesn't exist on Windows, use USERPROFILE instead

  2. src-tauri/src/command.rs, line 841-843 (link)

    syntax: HOME env var doesn't exist on Windows, use USERPROFILE instead

  3. src/core/chorus/ModelProviders/ProviderClaudeCode.ts, line 68-75 (link)

    syntax: The return type of streamResponse should be Promise<ModelDisabled | void> to match the IProvider interface, but this implementation only returns Promise<void>

8 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@Ghvstcode Ghvstcode force-pushed the Ghvstcode/add-claudecode-support branch from bab23f3 to c9c0f32 Compare December 15, 2025 08:19
Copy link
Copy Markdown
Collaborator

@bcongdon bcongdon left a comment

Choose a reason for hiding this comment

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

This is exciting!

Trying it out locally: It's very possible that I'm "holding it wrong", but even with a logged-in claude code instance I wasn't able to get the output to show up in the Chorus dev. I do get valid stream-json output from running claude -p --output-format stream-json --verbose --permission-mode bypassPermissions "hello" in the same terminal window though

Comment on lines +182 to +193
switch (modelPart) {
case "opus":
case "opus-4.5":
return "opus";
case "sonnet":
case "sonnet-4.5":
return "sonnet";
case "haiku":
return "haiku";
default:
// Let Claude CLI use its default
return undefined;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any way we can make this a bit more flexible to future-proof it against future model version releases?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point! I have simplified this and just pass the model part directly to the Claude CLI.

Comment thread src-tauri/src/command.rs Outdated

/// Check if Claude Code CLI is installed and authenticated
#[tauri::command]
pub fn check_claude_code_available() -> Result<serde_json::Value, String> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: When attempting this locally without claude code installed, it seems like I was able to select the model and try running it, and I didn't get an error.

$ which claude
claude not found
Screenshot 2025-12-15 at 7 25 59 PM

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Screenshot 2025-12-16 at 09 07 38

Fixed this - now you cant select the claude code option if you dont have claude code.

Comment thread src-tauri/src/command.rs Outdated
…el mapping

- Use `which claude` to check CLI availability instead of credentials.json
- Disable Claude Code in model picker when CLI not installed
- Surface errors from stderr, non-zero exit codes, and error results
- Simplify model name mapping to pass through directly to CLI
@Ghvstcode
Copy link
Copy Markdown
Author

Trying it out locally: It's very possible that I'm "holding it wrong", but even with a logged-in claude code instance I wasn't able to get the output to show up in the Chorus dev. I do get valid stream-json output from running claude -p --output-format stream-json --verbose --permission-mode bypassPermissions "hello" in the same terminal window though

Very strange! This isn't the case for me. Will take a closer look at this and get back.

Use spawn_blocking to run process checks on a separate thread pool,
preventing the sync command from blocking Tauri's async runtime
(which caused SQL queries to hang).
@Ghvstcode Ghvstcode requested a review from bcongdon December 19, 2025 13:55
@Ghvstcode
Copy link
Copy Markdown
Author

This is exciting!

Trying it out locally: It's very possible that I'm "holding it wrong", but even with a logged-in claude code instance I wasn't able to get the output to show up in the Chorus dev. I do get valid stream-json output from running claude -p --output-format stream-json --verbose --permission-mode bypassPermissions "hello" in the same terminal window though

@bcongdon could you give this another spin? Should be working fine now.

Copy link
Copy Markdown
Collaborator

@bcongdon bcongdon left a comment

Choose a reason for hiding this comment

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

This is pretty cool! And it's working for me now 😄

Is it expected for tool calls to show up in-band like this?

Screenshot 2025-12-20 at 7 01 42 AM

Comment thread pnpm-lock.yaml
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like this has regressed to lockfileVersion: '6.0'. Can you update your pnpm so we continue using version 9.0 (e.g. at main)

Comment thread src-tauri/src/migrations.rs Outdated
-- Add Claude (via Claude Code) model
-- This uses the local Claude Code CLI and subscription instead of an API key
INSERT OR REPLACE INTO models (id, display_name, is_enabled, supported_attachment_types) VALUES
('claude-code::default', 'Claude (via Claude Code)', 1, '["text", "webpage"]');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not sure how you plan on using this, but do you think it's worth having the different models (Sonnet, Opus) exposed here?

Comment thread src/core/chorus/ModelProviders/ProviderClaudeCode.ts Outdated
…output

- Remove mapModelName function since we only expose one model and always use CLI default
- Remove --verbose flag from Claude Code CLI invocation to prevent tool calls from showing in chat
Implements clean, collapsible UI for displaying Claude Code tool calls:
- Group consecutive tool calls with summary (e.g., "Used 19 tools · Read (10×), Bash (5×)")
- Individual tool blocks with JSON syntax highlighting via CodeBlock
- Shared helper for extracting summaries from tool parameters
- Base64-encoded data attributes for robust HTML parsing

Enable Claude Code tools by removing --tools="" restriction and adding --verbose flag to expose tool_use blocks in stream output.
The tool call components were parsing and processing children on every render,
causing severe performance issues during streaming when many tool calls were present.

Changes:
- Add useMemo to ToolCallGroup to cache parsed tool calls based on children prop
- Add useMemo to ToolCallGroup summary calculation
- Add useMemo to ToolCallBlock and ToolCallItem for extractToolSummary calls

This prevents expensive regex matching, base64 decoding, and child traversal
from running on every render, which was causing the app to freeze.
@rmichelena
Copy link
Copy Markdown

this is a great, wonderful idea - and we'll then need to add Codex CLI too; that way we can use our regular suscription quotas in Chorus... (similar to what Conductor does!)
keep in mind you should implement (if you haven't done so) support for "daily quota exceeded - resets at xxx.xx" messages from Claude Code, similar to what Conductor does...

@Ghvstcode
Copy link
Copy Markdown
Author

Ghvstcode commented Dec 21, 2025

@rmichelena - Thank you

this is a great, wonderful idea - and we'll then need to add Codex CLI too; that way we can use our regular suscription quotas in Chorus... (similar to what Conductor does!) keep in mind you should implement (if you haven't done so) support for "daily quota exceeded - resets at xxx.xx" messages from Claude Code, similar to what Conductor does...

Good point!

On quota exceeded handling - wouldn't that error just bubble up to the chat normally? Curious what special handling Conductor does that we'd want to replicate.

@Ghvstcode
Copy link
Copy Markdown
Author

Ghvstcode commented Dec 21, 2025

This is pretty cool! And it's working for me now 😄

Is it expected for tool calls to show up in-band like this?

Screenshot 2025-12-20 at 7 01 42 AM

@bcongdon Thanks for flagging this! Fixed now - tool calls are no longer rendered plainly.
Instead, they show as collapsible UI elements that can be expanded if you want to see the details. The implementation is fairly basic for now, but eventually I'd like to add things like a diff viewer for edit operations instead of just showing the raw tool call info.
Also snuck in another change: the tool box is now hidden when all selected models are Claude Code (since they bring their own tools).
Screenshot 2025-12-21 at 18 31 34

@bcongdon
Copy link
Copy Markdown
Collaborator

bcongdon commented Jan 4, 2026

Thanks for continuing to iterate on this! I haven't been able to determine why, but I seem to be getting a hanging issue again when I try to use this.

Repro steps:

  1. Select the Sonnet 4 Claude Code model.
  2. Ask a simple question ("Write a haiku about birds").
  3. The model spins for a bit, then Chorus hangs into "Application Not Responding" and needs to be force quit.

High-frequency Tauri event emission caused the app to freeze. Now we
collect all Claude CLI output and emit only the final assistant message,
avoiding the freeze entirely.
@Ghvstcode
Copy link
Copy Markdown
Author

Thanks for continuing to iterate on this! I haven't been able to determine why, but I seem to be getting a hanging issue again when I try to use this.

Repro steps:

  1. Select the Sonnet 4 Claude Code model.
  2. Ask a simple question ("Write a haiku about birds").
  3. The model spins for a bit, then Chorus hangs into "Application Not Responding" and needs to be force quit.

Thanks for the repro details — that helped a lot.

I’ve narrowed this down to an issue specifically around the Tauri listen() + invoke() + event emission flow, rather than the Claude CLI itself or the way its beeing invoked.

What I was able to confirm:

  • If Rust emits events immediately after an invoke() call (even hardcoded test events with no CLI involved), the app can freeze.
  • If the frontend bypasses Tauri events entirely and calls onComplete() directly, the hang does not occur.
  • Adding a small delay between listen() and invoke() avoids the freeze in some cases, which suggests the listener isn’t fully ready when early events arrive.

Claude Code is the only provider that goes through Rust + Tauri events due to spawning a local CLI. Other providers stream via Fetch/SSE directly in JavaScript and don’t hit this path, which explains why the issue only appears here and not with the other providers.

Current behavior after the change:

  • The implementation now emits a single event only after generation completes.
  • This has been stable in testing, likely because the event arrives well after the listener has fully initialized.

Attempts at real-time streaming (emitting events per line of CLI output) continued to reproduce hangs, even with a delay workaround.

@Ghvstcode Ghvstcode requested a review from bcongdon January 26, 2026 07:17
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