Conversation
Add support for xAI's Grok API as a new provider. Grok uses an OpenAI-compatible API, so the adapter reuses OpenAI's message/tool converters and stream adapter, with Grok-specific client and model mapping. New files: - src/services/api/grok/client.ts — xAI client (default: api.x.ai/v1) - src/services/api/grok/modelMapping.ts — sonnet/opus→grok-3, haiku→grok-3-mini - src/services/api/grok/index.ts — queryModelGrok() entry point Activation: - env: CLAUDE_CODE_USE_GROK=1 + GROK_API_KEY - settings.json: modelType: "grok"
📝 WalkthroughWalkthroughSupport for the Grok (xAI) API provider is added through new client factory, query handler, and model mapping modules. Integration occurs via a new dispatch path in Changes
Sequence DiagramsequenceDiagram
participant User
participant QueryModel as queryModel<br/>(claude.ts)
participant Dispatcher as Grok Dispatch
participant GrokQueryModel as queryModelGrok<br/>(grok/index.ts)
participant GrokClient as Grok Client<br/>(grok/client.ts)
participant GrokAPI as Grok API<br/>(xAI)
participant EventAdapter as Event Adapter
User->>QueryModel: call queryModel(messages, tools, options)
QueryModel->>Dispatcher: check getAPIProvider()
Dispatcher-->>QueryModel: returns 'grok'
QueryModel->>GrokQueryModel: yield* queryModelGrok(...)
GrokQueryModel->>GrokQueryModel: resolveGrokModel(options.model)
GrokQueryModel->>GrokQueryModel: normalize messages & tools
GrokQueryModel->>GrokClient: getGrokClient()
GrokClient-->>GrokQueryModel: return OpenAI client (cached)
GrokQueryModel->>GrokAPI: client.chat.completions.create(streaming)
GrokAPI-->>GrokQueryModel: stream events (OpenAI format)
GrokQueryModel->>EventAdapter: adapt to Anthropic format
EventAdapter-->>GrokQueryModel: adapted StreamEvent
GrokQueryModel->>GrokQueryModel: reconstruct AssistantMessage blocks
GrokQueryModel->>GrokQueryModel: track token usage & USD cost
GrokQueryModel-->>User: yield StreamEvent/AssistantMessage
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/utils/model/providers.ts (1)
5-12:⚠️ Potential issue | 🔴 CriticalAdd
grokentries to the provider-indexed model configs in the same change.
APIProvidernow makesgroka valid lookup key, but the providedsrc/utils/model/configs.ts:1-16snippet still has nogrokmember inModelConfig. That means Grok mode can fall back toundefinedfor default model selection beforeresolveGrokModel()ever gets a chance to translate anything. Mirroring the OpenAI/Gemini logical model IDs is enough there, since the Grok adapter does the provider-specific mapping later.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/model/providers.ts` around lines 5 - 12, ModelConfig is missing a 'grok' entry even though APIProvider now includes 'grok', causing default model lookups to return undefined before resolveGrokModel() runs; add a 'grok' key to the provider-indexed model configs (the same place ModelConfig is defined) and populate it with the same logical model IDs used for 'openai'/'gemini' (e.g., mirror their default identifiers) so that default selection never yields undefined and resolveGrokModel() can perform provider-specific mapping later.
🧹 Nitpick comments (2)
src/services/api/claude.ts (1)
1353-1355: Use thesrc/*alias for this dynamic import.This new branch introduces a relative internal import in a TypeScript file. Please switch it to the project alias so Grok is loaded through the same path style as the rest of
src/services/api/*.As per coding guidelines, `**/*.{ts,tsx}`: Use `src/*` path alias for imports in TypeScript files.♻️ Suggested change
- const { queryModelGrok } = await import('./grok/index.js') + const { queryModelGrok } = await import('src/services/api/grok/index.js')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/api/claude.ts` around lines 1353 - 1355, The dynamic import currently uses a relative path import('./grok/index.js'); change it to use the project alias so Grok is loaded via the src alias (e.g., import('src/services/api/grok/index.js') or import('src/services/api/grok')) where the import is invoked inside the getAPIProvider() === 'grok' branch that calls queryModelGrok, ensuring the imported symbol queryModelGrok is resolved from the aliased module.src/services/api/grok/index.ts (1)
1-20: Usesrc/*aliases throughout the new Grok adapter.This file introduces several
../and../../../imports for internal modules. Please convert them to the project alias before landing this.As per coding guidelines,
**/*.{ts,tsx}: Usesrc/*path alias for imports in TypeScript files.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/api/grok/index.ts` around lines 1 - 20, The file uses relative internal imports (e.g., SystemPrompt, Message types, Tools, getGrokClient, anthropicMessagesToOpenAI, anthropicToolsToOpenAI, anthropicToolChoiceToOpenAI, adaptOpenAIStreamToAnthropic, resolveGrokModel, normalizeMessagesForAPI, toolToAPISchema, logForDebugging, addToTotalSessionCost, calculateUSDCost, Options, createAssistantAPIErrorMessage, normalizeContentFromAPI) instead of the project alias; update each internal import path to use the src/* TypeScript path alias (e.g., import { SystemPrompt } from 'src/utils/systemPromptType' etc.), leave third‑party imports like 'crypto' and external package types unchanged, and verify tsconfig/paths supports these aliases and run a quick type check to ensure no broken imports remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/services/api/grok/client.ts`:
- Around line 15-20: The cachedClient short-circuits in getGrokClient and
ignores per-call options (maxRetries, fetchOverride, source) so subsequent calls
can reuse a client with the wrong transport/config; update getGrokClient to
either (a) key the cache by the options tuple (maxRetries, fetchOverride,
source) and return/create a client per-key, or (b) detect when non-default
options are passed and construct a fresh OpenAI client instead of returning
cachedClient; ensure you also apply the same logic where
maxRetries/fetchOverride are set later (see the code around the existing
cachedClient usage and the retry/transport setup) so per-call overrides always
take effect.
In `@src/services/api/grok/index.ts`:
- Around line 151-165: The emitted AssistantMessage m is built from
partialMessage captured at message_start but message_delta updates only the
local usage and stop_reason later, so the yielded message contains stale
usage/stop_reason; update partialMessage (or construct the final message) after
processing message_delta/stop events so that normalizeContentFromAPI and the
created AssistantMessage (uuid/timestamp aside) include the latest usage and
stop_reason values; apply the same fix to the other occurrence around the block
referenced (the similar yield at the 168–173 region) so both emitted messages
reflect updated usage/stop_reason from message_delta.
- Around line 71-84: The Grok request payload in client.chat.completions.create
(inside src/services/api/grok/index.ts) never sends a max_tokens field so
options.maxOutputTokensOverride and the CLI ceiling are ignored; resolve the
output-token limit using the same precedence as claude.ts (first any
retry/context override, then options.maxOutputTokensOverride, then the model
default for grokModel) and add that resolved value as max_tokens in the payload
sent to client.chat.completions.create (alongside model, messages, tools, etc.),
ensuring you reference grokModel, openaiMessages, openaiTools and
openaiToolChoice when constructing the final request object.
In `@src/utils/settings/types.ts`:
- Around line 379-380: The docs string in src/utils/settings/types.ts for
modelType "grok" misses mentioning the family-level overrides used by the Grok
mapper; update that description to include the ANTHROPIC_DEFAULT_SONNET_MODEL,
ANTHROPIC_DEFAULT_OPUS_MODEL, and ANTHROPIC_DEFAULT_HAIKU_MODEL environment
variables (in addition to GROK_MODEL and GROK_API_KEY/GROK_BASE_URL) so it
matches the behavior implemented in src/services/api/grok/modelMapping.ts;
locate the description text (the long string mentioning "grok" in
settings/types.ts) and append or modify the sentence to list those
ANTHROPIC_DEFAULT_*_MODEL variables as valid Grok model overrides.
---
Outside diff comments:
In `@src/utils/model/providers.ts`:
- Around line 5-12: ModelConfig is missing a 'grok' entry even though
APIProvider now includes 'grok', causing default model lookups to return
undefined before resolveGrokModel() runs; add a 'grok' key to the
provider-indexed model configs (the same place ModelConfig is defined) and
populate it with the same logical model IDs used for 'openai'/'gemini' (e.g.,
mirror their default identifiers) so that default selection never yields
undefined and resolveGrokModel() can perform provider-specific mapping later.
---
Nitpick comments:
In `@src/services/api/claude.ts`:
- Around line 1353-1355: The dynamic import currently uses a relative path
import('./grok/index.js'); change it to use the project alias so Grok is loaded
via the src alias (e.g., import('src/services/api/grok/index.js') or
import('src/services/api/grok')) where the import is invoked inside the
getAPIProvider() === 'grok' branch that calls queryModelGrok, ensuring the
imported symbol queryModelGrok is resolved from the aliased module.
In `@src/services/api/grok/index.ts`:
- Around line 1-20: The file uses relative internal imports (e.g., SystemPrompt,
Message types, Tools, getGrokClient, anthropicMessagesToOpenAI,
anthropicToolsToOpenAI, anthropicToolChoiceToOpenAI,
adaptOpenAIStreamToAnthropic, resolveGrokModel, normalizeMessagesForAPI,
toolToAPISchema, logForDebugging, addToTotalSessionCost, calculateUSDCost,
Options, createAssistantAPIErrorMessage, normalizeContentFromAPI) instead of the
project alias; update each internal import path to use the src/* TypeScript path
alias (e.g., import { SystemPrompt } from 'src/utils/systemPromptType' etc.),
leave third‑party imports like 'crypto' and external package types unchanged,
and verify tsconfig/paths supports these aliases and run a quick type check to
ensure no broken imports remain.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8c99c534-c617-44c8-a3a5-87e05a2f951f
📒 Files selected for processing (8)
src/services/api/claude.tssrc/services/api/grok/__tests__/client.test.tssrc/services/api/grok/__tests__/modelMapping.test.tssrc/services/api/grok/client.tssrc/services/api/grok/index.tssrc/services/api/grok/modelMapping.tssrc/utils/model/providers.tssrc/utils/settings/types.ts
| export function getGrokClient(options?: { | ||
| maxRetries?: number | ||
| fetchOverride?: typeof fetch | ||
| source?: string | ||
| }): OpenAI { | ||
| if (cachedClient) return cachedClient |
There was a problem hiding this comment.
The cache short-circuit drops per-call client options.
Once cachedClient exists, Line 20 returns it before fetchOverride or a non-default maxRetries can take effect. A later Grok call can therefore reuse the wrong transport configuration.
🐛 Suggested fix
export function getGrokClient(options?: {
maxRetries?: number
fetchOverride?: typeof fetch
source?: string
}): OpenAI {
- if (cachedClient) return cachedClient
+ const useCachedClient =
+ !options?.fetchOverride &&
+ (options?.maxRetries === undefined || options.maxRetries === 0)
+
+ if (useCachedClient && cachedClient) return cachedClient
const apiKey = process.env.GROK_API_KEY || process.env.XAI_API_KEY || ''
const baseURL = process.env.GROK_BASE_URL || DEFAULT_BASE_URL
const client = new OpenAI({
@@
- if (!options?.fetchOverride) {
+ if (useCachedClient) {
cachedClient = client
}Also applies to: 35-37
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/services/api/grok/client.ts` around lines 15 - 20, The cachedClient
short-circuits in getGrokClient and ignores per-call options (maxRetries,
fetchOverride, source) so subsequent calls can reuse a client with the wrong
transport/config; update getGrokClient to either (a) key the cache by the
options tuple (maxRetries, fetchOverride, source) and return/create a client
per-key, or (b) detect when non-default options are passed and construct a fresh
OpenAI client instead of returning cachedClient; ensure you also apply the same
logic where maxRetries/fetchOverride are set later (see the code around the
existing cachedClient usage and the retry/transport setup) so per-call overrides
always take effect.
| const stream = await client.chat.completions.create( | ||
| { | ||
| model: grokModel, | ||
| messages: openaiMessages, | ||
| ...(openaiTools.length > 0 && { | ||
| tools: openaiTools, | ||
| ...(openaiToolChoice && { tool_choice: openaiToolChoice }), | ||
| }), | ||
| stream: true, | ||
| stream_options: { include_usage: true }, | ||
| ...(options.temperatureOverride !== undefined && { | ||
| temperature: options.temperatureOverride, | ||
| }), | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/services/api/grok/index.ts | head -100Repository: claude-code-best/claude-code
Length of output: 4447
🏁 Script executed:
rg "maxOutputTokensOverride" --type ts -B 3 -A 3Repository: claude-code-best/claude-code
Length of output: 8548
🏁 Script executed:
rg "max_tokens|maxTokens" src/services/api --type ts -B 2 -A 2Repository: claude-code-best/claude-code
Length of output: 9296
🏁 Script executed:
rg "queryModel" src/services/api --type ts | head -20Repository: claude-code-best/claude-code
Length of output: 1330
🏁 Script executed:
grep -r "max_tokens" src/services/api/openai --type ts -A 1 -B 1 | head -30Repository: claude-code-best/claude-code
Length of output: 189
🏁 Script executed:
cat -n src/services/api/gemini/index.ts | grep -A 50 "const stream = await"Repository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
rg "max_tokens" src/services/api/openai/index.ts -B 2 -A 2Repository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
rg "max_tokens" src/services/api/gemini/index.ts -B 2 -A 2Repository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
cat -n src/services/api/gemini/index.ts | head -120Repository: claude-code-best/claude-code
Length of output: 4898
🏁 Script executed:
rg "maxOutputTokensOverride" src/services/api --type ts -B 5 -A 5Repository: claude-code-best/claude-code
Length of output: 1474
🏁 Script executed:
cat -n src/services/api/openai/index.ts | head -150Repository: claude-code-best/claude-code
Length of output: 6772
🏁 Script executed:
rg "maxOutputTokens|max_output_tokens" src/services/api/gemini -B 3 -A 3Repository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
cat -n src/services/api/claude.ts | sed -n '325,345p'Repository: claude-code-best/claude-code
Length of output: 853
🏁 Script executed:
cat -n src/services/api/claude.ts | sed -n '220,290p'Repository: claude-code-best/claude-code
Length of output: 3092
🏁 Script executed:
grep -n "const maxOutputTokens" src/services/api/claude.ts -A 5Repository: claude-code-best/claude-code
Length of output: 664
🏁 Script executed:
cat -n src/services/api/grok/index.ts | sed -n '60,95p'Repository: claude-code-best/claude-code
Length of output: 1454
🏁 Script executed:
cat -n src/services/api/openai/index.ts | sed -n '76,100p'Repository: claude-code-best/claude-code
Length of output: 1014
🏁 Script executed:
rg "getMaxOutputTokensForModel" src/services/api --type ts -B 2 -A 2 | head -40Repository: claude-code-best/claude-code
Length of output: 628
🏁 Script executed:
rg "max_tokens|maxOutputTokens" src/services/api/openai/index.ts -B 3 -A 3Repository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
grep -n "getMaxOutputTokensForModel" src/services/api/grok/index.tsRepository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
cat -n src/services/api/claude.ts | sed -n '1630,1670p'Repository: claude-code-best/claude-code
Length of output: 2123
🏁 Script executed:
rg "max_tokens" --type ts src/services/api -lRepository: claude-code-best/claude-code
Length of output: 266
🏁 Script executed:
cat -n src/services/api/openai/index.ts | sed -n '1,50p'Repository: claude-code-best/claude-code
Length of output: 2551
🏁 Script executed:
grep -n "maxOutputTokens\|max_tokens" src/services/api/openai/index.tsRepository: claude-code-best/claude-code
Length of output: 54
🏁 Script executed:
cat -n src/services/api/openai/index.ts | tail -100Repository: claude-code-best/claude-code
Length of output: 4297
🏁 Script executed:
cat -n src/services/api/claude.ts | sed -n '1670,1700p'Repository: claude-code-best/claude-code
Length of output: 1383
🏁 Script executed:
cat -n src/services/api/claude.ts | sed -n '1700,1750p'Repository: claude-code-best/claude-code
Length of output: 2156
🏁 Script executed:
cat -n src/services/api/claude.ts | sed -n '1740,1770p'Repository: claude-code-best/claude-code
Length of output: 1229
Thread the resolved output-token limit into the Grok request payload.
The Grok adapter ignores options.maxOutputTokensOverride and the normal CLI ceiling because it never sends a max_tokens field. Add logic to resolve the token limit (following the pattern in claude.ts: retry context override → maxOutputTokensOverride → model default) and include it in the request via the max_tokens parameter, which Grok's OpenAI-compatible API supports.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/services/api/grok/index.ts` around lines 71 - 84, The Grok request
payload in client.chat.completions.create (inside
src/services/api/grok/index.ts) never sends a max_tokens field so
options.maxOutputTokensOverride and the CLI ceiling are ignored; resolve the
output-token limit using the same precedence as claude.ts (first any
retry/context override, then options.maxOutputTokensOverride, then the model
default for grokModel) and add that resolved value as max_tokens in the payload
sent to client.chat.completions.create (alongside model, messages, tools, etc.),
ensuring you reference grokModel, openaiMessages, openaiTools and
openaiToolChoice when constructing the final request object.
| const m: AssistantMessage = { | ||
| message: { | ||
| ...partialMessage, | ||
| content: normalizeContentFromAPI( | ||
| [block], | ||
| tools, | ||
| options.agentId, | ||
| ), | ||
| }, | ||
| requestId: undefined, | ||
| type: 'assistant', | ||
| uuid: randomUUID(), | ||
| timestamp: new Date().toISOString(), | ||
| } | ||
| yield m |
There was a problem hiding this comment.
Patch the yielded assistant message after message_delta.
Each AssistantMessage is emitted from partialMessage captured at message_start, but message_delta only updates the local usage variable. The last emitted message therefore keeps stale usage and stop_reason, unlike the Anthropic path.
🐛 Suggested fix
- const contentBlocks: Record<number, any> = {}
+ const contentBlocks: Record<number, any> = {}
+ const emittedMessages: AssistantMessage[] = []
@@
const m: AssistantMessage = {
message: {
...partialMessage,
content: normalizeContentFromAPI(
[block],
@@
type: 'assistant',
uuid: randomUUID(),
timestamp: new Date().toISOString(),
}
+ emittedMessages.push(m)
yield m
break
}
case 'message_delta': {
const deltaUsage = (event as any).usage
if (deltaUsage) {
usage = { ...usage, ...deltaUsage }
}
+ const lastMessage = emittedMessages.at(-1)
+ if (lastMessage) {
+ lastMessage.message.usage = usage as any
+ lastMessage.message.stop_reason =
+ (event as any).delta?.stop_reason ??
+ lastMessage.message.stop_reason
+ }
break
}Also applies to: 168-173
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/services/api/grok/index.ts` around lines 151 - 165, The emitted
AssistantMessage m is built from partialMessage captured at message_start but
message_delta updates only the local usage and stop_reason later, so the yielded
message contains stale usage/stop_reason; update partialMessage (or construct
the final message) after processing message_delta/stop events so that
normalizeContentFromAPI and the created AssistantMessage (uuid/timestamp aside)
include the latest usage and stop_reason values; apply the same fix to the other
occurrence around the block referenced (the similar yield at the 168–173 region)
so both emitted messages reflect updated usage/stop_reason from message_delta.
| 'API provider type. "anthropic" uses the Anthropic API (default), "openai" uses the OpenAI Chat Completions API (/v1/chat/completions), "gemini" uses the Gemini Generate Content API, and "grok" uses the xAI Grok API (OpenAI-compatible). ' + | ||
| 'When set to "openai", configure OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL in env. When set to "gemini", configure GEMINI_API_KEY, optional GEMINI_BASE_URL, and either GEMINI_MODEL or ANTHROPIC_DEFAULT_*_MODEL family env vars. When set to "grok", configure GROK_API_KEY (or XAI_API_KEY), optional GROK_BASE_URL, and optional GROK_MODEL.', |
There was a problem hiding this comment.
Document the family-level Grok overrides here too.
src/services/api/grok/modelMapping.ts also honors ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, and ANTHROPIC_DEFAULT_HAIKU_MODEL. Listing only GROK_MODEL makes the new modelType: "grok" guidance incomplete.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/settings/types.ts` around lines 379 - 380, The docs string in
src/utils/settings/types.ts for modelType "grok" misses mentioning the
family-level overrides used by the Grok mapper; update that description to
include the ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, and
ANTHROPIC_DEFAULT_HAIKU_MODEL environment variables (in addition to GROK_MODEL
and GROK_API_KEY/GROK_BASE_URL) so it matches the behavior implemented in
src/services/api/grok/modelMapping.ts; locate the description text (the long
string mentioning "grok" in settings/types.ts) and append or modify the sentence
to list those ANTHROPIC_DEFAULT_*_MODEL variables as valid Grok model overrides.
|
它有加面板吗, 就 /login 那边的 |
|
我刚刚合并了一个 /provider 指令, 麻烦重新适配一下 |
API Key 的模式 不需要 login 哇。 |
|
我重新适配一下吧,看起来改了架构。 |

Summary
api.x.ai/v1) and model mapping (sonnet/opus →grok-3, haiku →grok-3-mini)GROK_API_KEY/XAI_API_KEY, optionalGROK_BASE_URLandGROK_MODELoverridesCLAUDE_CODE_USE_GROK=1env var ormodelType: "grok"in settings.jsonFiles changed
src/services/api/grok/— client, modelMapping, index (+ 2 test files)providers.ts(add'grok'type),types.ts(add to modelType enum),claude.ts(add dispatch)Test plan
bun run src/entrypoints/cli.tsx --versionsmoke test passesSummary by CodeRabbit
Release Notes