feat(chat): add Claude Code as local chat provider#2668
feat(chat): add Claude Code as local chat provider#2668
Conversation
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsShould a new version be published when this PR is merged? React with an emoji to vote on the release type:
Current version: Deployment
|
There was a problem hiding this comment.
3 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/decopilot/routes.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/routes.ts:297">
P2: Validate Claude Code availability before starting the run/saving messages; currently an unavailable provider still creates a failed run and persists the user message.</violation>
</file>
<file name="apps/mesh/src/api/routes/decopilot/claude-code-provider.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/claude-code-provider.ts:33">
P1: Multi-turn conversation context is lost. `messagesToPrompt` strips the `role` from all messages and concatenates their text, so prior assistant replies are indistinguishable from user messages. Add role prefixes (e.g., `User:` / `Assistant:`) or only pass the last user message as the prompt.</violation>
<violation number="2" location="apps/mesh/src/api/routes/decopilot/claude-code-provider.ts:215">
P1: Text will be duplicated in the output. The `stream_event` handler already writes incremental text deltas, but the `assistant` handler then re-emits the full completed text from `message.message.content`. Remove the text extraction from the `assistant` case since it's already delivered via stream events, or gate it so it only fires when no stream events were received.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| /** | ||
| * Convert chat messages to a prompt string for the Claude Agent SDK. | ||
| */ | ||
| function messagesToPrompt(messages: ChatMessage[]): string { |
There was a problem hiding this comment.
P1: Multi-turn conversation context is lost. messagesToPrompt strips the role from all messages and concatenates their text, so prior assistant replies are indistinguishable from user messages. Add role prefixes (e.g., User: / Assistant:) or only pass the last user message as the prompt.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/decopilot/claude-code-provider.ts, line 33:
<comment>Multi-turn conversation context is lost. `messagesToPrompt` strips the `role` from all messages and concatenates their text, so prior assistant replies are indistinguishable from user messages. Add role prefixes (e.g., `User:` / `Assistant:`) or only pass the last user message as the prompt.</comment>
<file context>
@@ -0,0 +1,277 @@
+/**
+ * Convert chat messages to a prompt string for the Claude Agent SDK.
+ */
+function messagesToPrompt(messages: ChatMessage[]): string {
+ const parts: string[] = [];
+
</file context>
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/decopilot/routes.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/routes.ts:315">
P2: Guard the Claude Code sentinel model id before forwarding it to the SDK; otherwise restored sessions can pass `"claude-code"` as a concrete model value.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
9b795cb to
8002f8b
Compare
There was a problem hiding this comment.
2 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/components/chat/select-model.tsx">
<violation number="1" location="apps/mesh/src/web/components/chat/select-model.tsx:1070">
P1: Selecting a Claude Code model never tags the request with `connectionId: "claude-code"`, so the backend will not take the local Claude Code streaming path.</violation>
</file>
<file name="apps/mesh/src/web/components/chat/no-llm-binding-empty-state.tsx">
<violation number="1" location="apps/mesh/src/web/components/chat/no-llm-binding-empty-state.tsx:57">
P2: Also guard the in-flight state here so repeated clicks do not trigger duplicate connect requests.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
apps/mesh/src/web/components/chat/no-llm-binding-empty-state.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
4 issues found across 8 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/decopilot/routes.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/routes.ts:158">
P1: Don't trust `models.thinking.provider` to decide whether to skip model-permission checks.</violation>
<violation number="2" location="apps/mesh/src/api/routes/decopilot/routes.ts:321">
P2: This passes `credentialId` into a `connectionId` field, so Claude Code responses emit the wrong metadata shape for follow-up requests.</violation>
</file>
<file name="apps/mesh/src/web/components/settings-modal/pages/org-ai-providers.tsx">
<violation number="1" location="apps/mesh/src/web/components/settings-modal/pages/org-ai-providers.tsx:334">
P2: Gate the Claude Code connect path on actual CLI availability; right now it can create a unusable "connected" provider on servers where Claude Code is unavailable.</violation>
</file>
<file name="apps/mesh/src/ai-providers/factory.ts">
<violation number="1" location="apps/mesh/src/ai-providers/factory.ts:8">
P2: Keep the Claude Code model variants in one shared source of truth; this duplicate list can drift from the runtime SDK mapping.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| import { PROVIDERS } from "./registry"; | ||
|
|
||
| /** Static model list for the Claude Code local provider. */ | ||
| const CLAUDE_CODE_MODEL_LIST: ModelInfo[] = [ |
There was a problem hiding this comment.
P2: Keep the Claude Code model variants in one shared source of truth; this duplicate list can drift from the runtime SDK mapping.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/ai-providers/factory.ts, line 8:
<comment>Keep the Claude Code model variants in one shared source of truth; this duplicate list can drift from the runtime SDK mapping.</comment>
<file context>
@@ -4,6 +4,40 @@ import type { ModelListCache } from "./model-list-cache";
import { PROVIDERS } from "./registry";
+/** Static model list for the Claude Code local provider. */
+const CLAUDE_CODE_MODEL_LIST: ModelInfo[] = [
+ {
+ providerId: "claude-code",
</file context>
There was a problem hiding this comment.
2 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/decopilot/routes.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/routes.ts:875">
P2: Use `getUserId(ctx)` here instead of reading `ctx.auth.user` directly; otherwise API-key-authenticated requests are rejected by the new Connect Studio endpoints.
(Based on your team's feedback about using `getUserId` for mixed user/API-key auth.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/src/web/components/connect-studio-modal.tsx">
<violation number="1" location="apps/mesh/src/web/components/connect-studio-modal.tsx:83">
P1: This disconnect flow reports success without revoking the generated full-access API key, so Claude Code access persists after “Disconnect”.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| app.delete("/:org/decopilot/connect-studio", async (c) => { | ||
| const ctx = c.get("meshContext"); | ||
| if (!ctx.auth?.user?.id) { |
There was a problem hiding this comment.
P2: Use getUserId(ctx) here instead of reading ctx.auth.user directly; otherwise API-key-authenticated requests are rejected by the new Connect Studio endpoints.
(Based on your team's feedback about using getUserId for mixed user/API-key auth.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/decopilot/routes.ts, line 875:
<comment>Use `getUserId(ctx)` here instead of reading `ctx.auth.user` directly; otherwise API-key-authenticated requests are rejected by the new Connect Studio endpoints.
(Based on your team's feedback about using `getUserId` for mixed user/API-key auth.) </comment>
<file context>
@@ -799,61 +830,80 @@ export function createDecopilotRoutes(deps: DecopilotDeps) {
+
+ app.delete("/:org/decopilot/connect-studio", async (c) => {
+ const ctx = c.get("meshContext");
+ if (!ctx.auth?.user?.id) {
+ throw new HTTPException(401, { message: "Authentication required" });
+ }
</file context>
There was a problem hiding this comment.
3 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/decopilot/claude-code-provider.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/claude-code-provider.ts:372">
P2: This replays the full `thinking` content even after `thinking_delta` events were already streamed, so Claude Code reasoning can show up duplicated in the chat UI.</violation>
</file>
<file name="apps/mesh/src/api/routes/decopilot/routes.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/routes.ts:794">
P2: `gh auth status --json` is missing the required field list, so GitHub status detection will always fall back to disconnected.</violation>
<violation number="2" location="apps/mesh/src/api/routes/decopilot/routes.ts:843">
P1: This creates a permanent wildcard API key but the disconnect/failure paths never revoke it, leaving active internal credentials behind.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| if (target === "claude-code") { | ||
| // Create API key for the MCP endpoint | ||
| const apiKey = await ctx.boundAuth.apiKey.create({ |
There was a problem hiding this comment.
P1: This creates a permanent wildcard API key but the disconnect/failure paths never revoke it, leaving active internal credentials behind.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/decopilot/routes.ts, line 843:
<comment>This creates a permanent wildcard API key but the disconnect/failure paths never revoke it, leaving active internal credentials behind.</comment>
<file context>
@@ -811,63 +835,78 @@ export function createDecopilotRoutes(deps: DecopilotDeps) {
+
+ if (target === "claude-code") {
+ // Create API key for the MCP endpoint
+ const apiKey = await ctx.boundAuth.apiKey.create({
+ name: "studio-connect-claude-code",
+ permissions: { "*": ["*"] },
</file context>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/decopilot/routes.ts">
<violation number="1" location="apps/mesh/src/api/routes/decopilot/routes.ts:796">
P2: Don't return early on missing `gh` auth here; it hides an already-registered GitHub MCP and flips the modal back to `Connect`.
(Based on your team's feedback about checking MCP authentication state first.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
When running locally with Claude Code installed, offer it as a model option in the chat UI. Routes messages through the Claude Agent SDK subprocess instead of OpenRouter, using the user's local Claude auth. - Add `claudeCodeAvailable` to AuthConfig (detected via Bun.which) - New claude-code-provider adapter wrapping @anthropic-ai/claude-agent-sdk - Fork decopilot stream endpoint for claude-code connectionId sentinel - Add Claude Code entry in model selector with "Local" badge - Handle empty connections gracefully when only Claude Code is available Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract text from assistant message content blocks (SDK emits full messages, not stream_event deltas) - Add start-step/finish-step markers for proper AI SDK status tracking - Add Opus/Sonnet/Haiku model variants in the selector - Pass selected model to SDK query options - Fix selectedConnectionId initialization to skip claude-code sentinel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Connect Studio modal with one-click Claude Code / Cursor setup - Server endpoint generates API key and runs `claude mcp add-json` or writes Cursor config - Status endpoint checks if IDE is already connected - Auto-wire MCP endpoint when using Claude Code as local chat provider - Support mcpHeaders in Claude Agent SDK http transport Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…boarding - Fix missing closing brace in model selector JSX - Fix AiProviderModel type mismatches for Claude Code synthetic models - Remove stale useAllowedModels import - Add Claude Code card with "Local" badge to the AI provider onboarding screen - Card shows connection status and one-click connect via CLI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove custom Claude Code UI from model selector and onboarding. Claude Code now uses the same ConnectionModelList → groupByTier → ModelTierSection pipeline as every other provider. Models are returned from factory.ts via a static list, classified into tiers via prefixes, and server detection uses thinking.provider instead of broken connectionId field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove Cursor support (not working), show Claude Code auth info (email, org, subscription) when connected, add disconnect button, and fix refresh icon positioning. Add timeouts to all CLI spawns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Handle thinking_delta events from the Claude Agent SDK to stream reasoning content in real-time. Track content block types to route thinking vs text deltas correctly. Prevent duplicate text from assistant message fallback when stream events are available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor Connect Studio modal to show each connection as a self-contained card with its own connect/disconnect buttons. Add GitHub connection that uses the local `gh` CLI token to register GitHub's MCP in Claude Code. Also enable `includePartialMessages` in the Claude Agent SDK to get real-time thinking/text streaming events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GitHub status now uses `gh auth status` (plain) + `gh api user --jq .login` instead of `gh auth status --json` which requires explicit field names. Simplify ConnectionCard to single-row layout with inline toggle button. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The self MCP connection URL is persisted in the DB using BASE_URL (e.g. las-vegas.localhost) which is unresolvable from Node.js/Bun runtime. Parse the stored URL and replace hostname/port/protocol with getInternalUrl() (localhost:PORT) so loopback calls always succeed, regardless of what origin is stored in the database. Also fix dev-worktree.ts to not set BASE_URL in local mode, allowing the server to default to localhost:PORT. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fd36ce0 to
8366d56
Compare
Summary
Bun.which("claude")and offers it as a model option in the chat UIChanges
apps/mesh/src/api/routes/auth.ts—claudeCodeAvailablein AuthConfigapps/mesh/src/api/routes/decopilot/claude-code-provider.ts— New SDK adapter (stream_event + assistant message handling)apps/mesh/src/api/routes/decopilot/routes.ts— Fork stream handler forclaude-codesentinelapps/mesh/src/api/routes/decopilot/schemas.ts— Acceptclaude-codeproviderapps/mesh/src/web/components/chat/select-model.tsx— Claude Code UI entry + guardsapps/mesh/src/web/components/chat/context.tsx— Handleclaude-codeconnectionId in model stateTest plan
which claude)🤖 Generated with Claude Code
Summary by cubic
Adds Claude Code as a local chat provider (Opus/Sonnet/Haiku) streamed via
@anthropic-ai/claude-agent-sdk, plus a Connect Studio flow with per‑connection cards for one‑click setup of Mesh tools and GitHub MCP. Auto‑wires the MCP endpoint with a short‑lived API key during Claude Code chats and skips org model permissions for this local provider.New Features
claude-code:*models in the factory/registry; server routes bymodels.thinking.provider === "claude-code";/configexposesclaudeCodeAvailable.@anthropic-ai/claude-agent-sdkwith cancel, start/finish-step markers, real‑time thinking/text deltas (includePartialMessages), assistant text fallback de‑dupe, and usage/cost passthrough.deco-studioMCP; modal with per‑connection cards (Claude Code and GitHub via localghtoken), auth info, connect/disconnect, and a sidebar entry.Bug Fixes
claude-codemodels; handled empty connections and replacedconnectionIdsentinel with provider detection.claude-code; org settings now support one‑click Claude Code with better loading states.gh auth status+gh api user) and compacted cards; rewrote self‑connection MCP URL to localhost for reliable loopback.Written for commit 8366d56. Summary will update on new commits.