Skip to content

feat: add xAI Grok integration with its current features #307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ OpenCode is a Go-based CLI application that brings AI assistance to your termina
## Features

- **Interactive TUI**: Built with [Bubble Tea](https://github.com/charmbracelet/bubbletea) for a smooth terminal experience
- **Multiple AI Providers**: Support for OpenAI, Anthropic Claude, Google Gemini, AWS Bedrock, Groq, Azure OpenAI, and OpenRouter
- **Multiple AI Providers**: Support for OpenAI, Anthropic Claude, Google Gemini, AWS Bedrock, Groq, Azure OpenAI, xAI, and OpenRouter
- **Session Management**: Save and manage multiple conversation sessions
- **Tool Integration**: AI can execute commands, search files, and modify code
- **Image Recognition**: Support for analyzing images with vision-enabled models (xAI Grok)
- **Web Search**: Real-time web search capabilities with supported models
- **Vim-like Editor**: Integrated editor with text input capabilities
- **Persistent Storage**: SQLite database for storing conversations and sessions
- **LSP Integration**: Language Server Protocol support for code intelligence
Expand Down Expand Up @@ -105,6 +107,7 @@ You can configure OpenCode using environment variables:
| `VERTEXAI_PROJECT` | For Google Cloud VertexAI (Gemini) |
| `VERTEXAI_LOCATION` | For Google Cloud VertexAI (Gemini) |
| `GROQ_API_KEY` | For Groq models |
| `XAI_API_KEY` | For xAI Grok models |
| `AWS_ACCESS_KEY_ID` | For AWS Bedrock (Claude) |
| `AWS_SECRET_ACCESS_KEY` | For AWS Bedrock (Claude) |
| `AWS_REGION` | For AWS Bedrock (Claude) |
Expand Down Expand Up @@ -244,6 +247,26 @@ OpenCode supports a variety of AI models from different providers:
- Gemini 2.0 Flash
- Gemini 2.0 Flash Lite

### xAI

- Grok 4 (grok-4-0709) - Most capable, with internal reasoning
- Grok 3 (grok-3) - Advanced model (no reasoning support)
- Grok 3 Fast (grok-3-fast) - Optimized for speed (no reasoning support)
- Grok 3 Mini (grok-3-mini) - Smaller model with reasoning_effort support
- Grok 3 Mini Fast (grok-3-mini-fast) - Fastest with reasoning_effort support
- Grok 2 (grok-2-1212) - General purpose (no reasoning support)
- Grok 2 Vision (grok-2-vision-1212) - Vision understanding (no reasoning support)
- Grok 2 Image (grok-2-image-1212) - Image generation model

**Special Features:**
- **Web Search**: All xAI models support live web search for current information
- **Reasoning Support** (verified via API):
- Grok 4 (grok-4-0709): Has internal reasoning capabilities but does NOT expose reasoning_content or accept reasoningEffort parameter
- Grok 3 Mini models: Support `reasoningEffort` parameter (only "low" or "high", not "medium")
- Grok 2 models, Grok 3/3-fast: No reasoning support
- **Vision Support**: grok-2-vision-1212 supports image understanding
- **Image Generation**: grok-2-image-1212 supports image generation

### AWS Bedrock

- Claude 3.7 Sonnet
Expand Down Expand Up @@ -418,6 +441,7 @@ OpenCode's AI assistant has access to various tools to help with coding tasks:
| `fetch` | Fetch data from URLs | `url` (required), `format` (required), `timeout` (optional) |
| `sourcegraph` | Search code across public repositories | `query` (required), `count` (optional), `context_window` (optional), `timeout` (optional) |
| `agent` | Run sub-tasks with the AI agent | `prompt` (required) |
| `web_search` | Search the web (xAI models only) | `query` (required) - Note: Automatically used by xAI models when needed |

## Architecture

Expand Down
24 changes: 22 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ var rootCmd = &cobra.Command{
Short: "Terminal-based AI assistant for software development",
Long: `OpenCode is a powerful terminal-based AI assistant that helps with software development tasks.
It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
to assist developers in writing, debugging, and understanding code directly from the terminal.`,
to assist developers in writing, debugging, and understanding code directly from the terminal.

Key Features:
- Interactive AI chat with multiple model providers (OpenAI, Anthropic, xAI, etc.)
- Code analysis and editing capabilities
- LSP (Language Server Protocol) integration
- Web search support for current information (xAI models)
- File system operations and project navigation
- Multi-turn conversations with context retention`,
Example: `
# Run in interactive mode
opencode
Expand Down Expand Up @@ -63,6 +71,8 @@ to assist developers in writing, debugging, and understanding code directly from
prompt, _ := cmd.Flags().GetString("prompt")
outputFormat, _ := cmd.Flags().GetString("output-format")
quiet, _ := cmd.Flags().GetBool("quiet")
deferred, _ := cmd.Flags().GetBool("deferred")
deferredTimeout, _ := cmd.Flags().GetString("deferred-timeout")

// Validate format option
if !format.IsValid(outputFormat) {
Expand Down Expand Up @@ -97,7 +107,13 @@ to assist developers in writing, debugging, and understanding code directly from
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

app, err := app.New(ctx, conn)
// Create runtime options from CLI flags
runtimeOpts := app.RuntimeOptions{
DeferredEnabled: deferred,
DeferredTimeout: deferredTimeout,
}

app, err := app.New(ctx, conn, runtimeOpts)
if err != nil {
logging.Error("Failed to create app: %v", err)
return err
Expand Down Expand Up @@ -302,6 +318,10 @@ func init() {
// Add quiet flag to hide spinner in non-interactive mode
rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode")

// Deferred completion flags
rootCmd.Flags().Bool("deferred", false, "Enable deferred completions for xAI models (useful for long-running requests)")
rootCmd.Flags().String("deferred-timeout", "10m", "Timeout for deferred completions (e.g., '5m', '30s')")

// Register custom validation for the format flag
rootCmd.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return format.SupportedFormats, cobra.ShellCompDirectiveNoFileComp
Expand Down
16 changes: 15 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,18 @@ type App struct {
watcherCancelFuncs []context.CancelFunc
cancelFuncsMutex sync.Mutex
watcherWG sync.WaitGroup

// Runtime options (e.g., from CLI flags)
RuntimeOptions RuntimeOptions
}

// RuntimeOptions contains runtime configuration options (e.g., from CLI flags)
type RuntimeOptions struct {
DeferredEnabled bool
DeferredTimeout string
}

func New(ctx context.Context, conn *sql.DB) (*App, error) {
func New(ctx context.Context, conn *sql.DB, opts ...RuntimeOptions) (*App, error) {
q := db.New(conn)
sessions := session.NewService(q)
messages := message.NewService(q)
Expand All @@ -53,6 +62,11 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
LSPClients: make(map[string]*lsp.Client),
}

// Apply runtime options if provided
if len(opts) > 0 {
app.RuntimeOptions = opts[0]
}

// Initialize theme based on configuration
app.initTheme()

Expand Down
131 changes: 94 additions & 37 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,32 @@ const (

// Agent defines configuration for different LLM models and their token limits.
type Agent struct {
Model models.ModelID `json:"model"`
MaxTokens int64 `json:"maxTokens"`
ReasoningEffort string `json:"reasoningEffort"` // For openai models low,medium,heigh
Model models.ModelID `json:"model"`
MaxTokens int64 `json:"maxTokens"`
ReasoningEffort string `json:"reasoningEffort"` // For openai models low,medium,heigh
DeferredCompletion *bool `json:"deferredCompletion,omitempty"` // Override provider setting for this agent
}

// Provider defines configuration for an LLM provider.
type Provider struct {
APIKey string `json:"apiKey"`
Disabled bool `json:"disabled"`
APIKey string `json:"apiKey"`
Disabled bool `json:"disabled"`
MaxConcurrentRequests int64 `json:"maxConcurrentRequests,omitempty"` // For providers that support concurrent requests (e.g., xAI)
DeferredCompletion *DeferredCompletionConfig `json:"deferredCompletion,omitempty"` // For providers that support deferred completions (e.g., xAI)
}

// DeferredCompletionConfig defines settings for deferred completions
type DeferredCompletionConfig struct {
Enabled bool `json:"enabled"` // Enable deferred completions
Timeout string `json:"timeout,omitempty"` // Timeout duration (e.g., "10m")
PollInterval string `json:"pollInterval,omitempty"` // Poll interval duration (e.g., "10s")
AutoEnable *DeferredAutoEnableConfig `json:"autoEnable,omitempty"` // Smart activation rules
}

// DeferredAutoEnableConfig defines rules for automatically enabling deferred completions
type DeferredAutoEnableConfig struct {
ForModels []string `json:"forModels,omitempty"` // Enable for specific models
WhenTokensExceed int64 `json:"whenTokensExceed,omitempty"` // Enable when max tokens exceed this value
}

// Data defines storage configuration.
Expand Down Expand Up @@ -207,11 +224,10 @@ func Load(workingDir string, debug bool) (*Config, error) {
cfg.Agents = make(map[AgentName]Agent)
}

// Override the max tokens for title agent
cfg.Agents[AgentTitle] = Agent{
Model: cfg.Agents[AgentTitle].Model,
MaxTokens: 80,
}
// Override the max tokens for title agent to ensure concise titles
titleAgent := cfg.Agents[AgentTitle]
titleAgent.MaxTokens = 80
cfg.Agents[AgentTitle] = titleAgent
return cfg, nil
}

Expand Down Expand Up @@ -351,10 +367,10 @@ func setProviderDefaults() {

// XAI configuration
if key := viper.GetString("providers.xai.apiKey"); strings.TrimSpace(key) != "" {
viper.SetDefault("agents.coder.model", models.XAIGrok3Beta)
viper.SetDefault("agents.summarizer.model", models.XAIGrok3Beta)
viper.SetDefault("agents.task.model", models.XAIGrok3Beta)
viper.SetDefault("agents.title.model", models.XAiGrok3MiniFastBeta)
viper.SetDefault("agents.coder.model", models.XAIGrok4) // Most capable model with reasoning + vision
viper.SetDefault("agents.summarizer.model", models.XAIGrok3) // Good balance for summarization
viper.SetDefault("agents.task.model", models.XAIGrok3Mini) // Reasoning support for complex tasks
viper.SetDefault("agents.title.model", models.XAIGrok3MiniFast) // Fast + cheap for simple titles
return
}

Expand Down Expand Up @@ -471,6 +487,7 @@ func applyDefaultValues() {
}
}

// validateAgent ensures that the agent configuration is valid and supported.
// It validates model IDs and providers, ensuring they are supported.
func validateAgent(cfg *Config, name AgentName, agent Agent) error {
// Check if model exists
Expand Down Expand Up @@ -563,31 +580,12 @@ func validateAgent(cfg *Config, name AgentName, agent Agent) error {
}

// Validate reasoning effort for models that support reasoning
if model.CanReason && provider == models.ProviderOpenAI || provider == models.ProviderLocal {
if agent.ReasoningEffort == "" {
// Set default reasoning effort for models that support it
logging.Info("setting default reasoning effort for model that supports reasoning",
"agent", name,
"model", agent.Model)

// Update the agent with default reasoning effort
if model.CanReason && isReasoningProvider(provider) {
validatedEffort := validateReasoningEffort(agent.ReasoningEffort, provider, string(name), string(agent.Model))
if validatedEffort != agent.ReasoningEffort {
updatedAgent := cfg.Agents[name]
updatedAgent.ReasoningEffort = "medium"
updatedAgent.ReasoningEffort = validatedEffort
cfg.Agents[name] = updatedAgent
} else {
// Check if reasoning effort is valid (low, medium, high)
effort := strings.ToLower(agent.ReasoningEffort)
if effort != "low" && effort != "medium" && effort != "high" {
logging.Warn("invalid reasoning effort, setting to medium",
"agent", name,
"model", agent.Model,
"reasoning_effort", agent.ReasoningEffort)

// Update the agent with valid reasoning effort
updatedAgent := cfg.Agents[name]
updatedAgent.ReasoningEffort = "medium"
cfg.Agents[name] = updatedAgent
}
}
} else if !model.CanReason && agent.ReasoningEffort != "" {
// Model doesn't support reasoning but reasoning effort is set
Expand Down Expand Up @@ -929,6 +927,65 @@ func UpdateTheme(themeName string) error {
})
}

// isReasoningProvider checks if the provider supports reasoning effort configuration
func isReasoningProvider(provider models.ModelProvider) bool {
return provider == models.ProviderOpenAI ||
provider == models.ProviderLocal ||
provider == models.ProviderXAI
}

// validateReasoningEffort validates and potentially adjusts the reasoning effort
// based on provider-specific constraints
func validateReasoningEffort(effort string, provider models.ModelProvider, agentName, modelName string) string {
if effort == "" {
// Set default reasoning effort
defaultEffort := "medium"
if provider == models.ProviderXAI {
defaultEffort = "high" // xAI doesn't support "medium"
}
logging.Info("setting default reasoning effort",
"agent", agentName,
"model", modelName,
"default", defaultEffort)
return defaultEffort
}

// Normalize to lowercase
normalizedEffort := strings.ToLower(effort)

// Provider-specific validation
if provider == models.ProviderXAI {
// xAI only supports "low" and "high"
switch normalizedEffort {
case "low", "high":
return normalizedEffort
case "medium":
logging.Info("xAI only supports low/high reasoning effort, mapping medium to high",
"agent", agentName,
"model", modelName)
return "high"
default:
logging.Warn("invalid reasoning effort for xAI, using high",
"agent", agentName,
"model", modelName,
"provided", effort)
return "high"
}
}

// Standard validation for other providers
switch normalizedEffort {
case "low", "medium", "high":
return normalizedEffort
default:
logging.Warn("invalid reasoning effort, using medium",
"agent", agentName,
"model", modelName,
"provided", effort)
return "medium"
}
}

// Tries to load Github token from all possible locations
func LoadGitHubToken() (string, error) {
// First check environment variable
Expand Down
Loading