Skip to content
Merged
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ Additional environment variables:
| `CODEX_TOKEN` | Codex OAuth access token (recommended for Codex-only) |
| `COPILOT_TOKEN` | GitHub Copilot PAT with `copilot` scope (Beta) |
| `MINIMAX_API_KEY` | MiniMax Coding Plan API key |
| `MINIMAX_REGION` | MiniMax region: `global` (default) or `cn` |
| `GEMINI_ENABLED` | Enable Gemini CLI quota tracking (Beta, auto-detected) |
| `GEMINI_REFRESH_TOKEN` | Gemini OAuth refresh token (for Docker/headless) |
| `GEMINI_ACCESS_TOKEN` | Gemini OAuth access token (for Docker/headless) |
Expand Down Expand Up @@ -477,6 +478,7 @@ Copy `.env.docker.example` to `.env` and set provider keys as needed. onWatch ca
| `ANTHROPIC_TOKEN` | Anthropic token (auto-detected if not set) | -- |
| `CODEX_TOKEN` | Codex OAuth access token (recommended; required for Codex-only) | -- |
| `MINIMAX_API_KEY` | MiniMax Coding Plan API key | -- |
| `MINIMAX_REGION` | MiniMax region: `global` (default) or `cn` | `global` |
| `GEMINI_REFRESH_TOKEN` | Gemini OAuth refresh token (Beta) | -- |
| `ONWATCH_ADMIN_USER` | Dashboard username | `admin` |
| `ONWATCH_ADMIN_PASS` | Dashboard password | `changeme` |
Expand Down
8 changes: 8 additions & 0 deletions docs/MINIMAX_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Add this to your environment file (`~/.onwatch/.env` for local installs):
MINIMAX_API_KEY=sk-cp-your_key_here
```

**Region** (optional, default: `global`):

```env
MINIMAX_REGION=cn # Use MiniMax CN endpoint (www.minimaxi.com)
```

Set `MINIMAX_REGION=cn` if you're using MiniMax from China; otherwise omit for the global endpoint (`api.minimax.io`).

## 3. Reload Providers

You can apply the new key without full restart:
Expand Down
20 changes: 17 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type Config struct {

// Z.ai provider configuration
ZaiAPIKey string // ZAI_API_KEY
ZaiBaseURL string // ZAI_BASE_URL
ZaiBaseURL string // ZAI_BASE_URL (auto-selected based on ZaiRegion)
ZaiRegion string // ZAI_REGION ( "global" | "cn", default: "global" )

// Anthropic provider configuration
AnthropicToken string // ANTHROPIC_TOKEN or auto-detected
Expand All @@ -45,7 +46,8 @@ type Config struct {
AntigravityEnabled bool // true if auto-detection should be attempted

// MiniMax provider configuration
MiniMaxAPIKey string // MINIMAX_API_KEY
MiniMaxAPIKey string // MINIMAX_API_KEY
MiniMaxRegion string // MINIMAX_REGION ( "global" | "cn", default: "global" )

// Gemini provider configuration (auto-detected from ~/.gemini/oauth_creds.json or env vars)
GeminiEnabled bool // true if auto-detected or GEMINI_ENABLED=true
Expand Down Expand Up @@ -228,6 +230,10 @@ func loadFromEnvAndFlags(flags *flagValues) (*Config, error) {
// Z.ai provider
cfg.ZaiAPIKey = os.Getenv("ZAI_API_KEY")
cfg.ZaiBaseURL = os.Getenv("ZAI_BASE_URL")
cfg.ZaiRegion = strings.ToLower(strings.TrimSpace(os.Getenv("ZAI_REGION")))
if cfg.ZaiRegion == "" {
cfg.ZaiRegion = "global"
}

// Anthropic provider
cfg.AnthropicToken = os.Getenv("ANTHROPIC_TOKEN")
Expand All @@ -248,6 +254,10 @@ func loadFromEnvAndFlags(flags *flagValues) (*Config, error) {

// MiniMax provider
cfg.MiniMaxAPIKey = strings.TrimSpace(os.Getenv("MINIMAX_API_KEY"))
cfg.MiniMaxRegion = strings.ToLower(strings.TrimSpace(os.Getenv("MINIMAX_REGION")))
if cfg.MiniMaxRegion == "" {
cfg.MiniMaxRegion = "global"
}

// Gemini provider (auto-detected, env vars, or opt-out via GEMINI_ENABLED=false)
cfg.GeminiRefreshToken = strings.TrimSpace(os.Getenv("GEMINI_REFRESH_TOKEN"))
Expand Down Expand Up @@ -356,7 +366,11 @@ func (c *Config) applyDefaults() {
c.LogLevel = "info"
}
if c.ZaiBaseURL == "" {
c.ZaiBaseURL = "https://api.z.ai/api"
if c.ZaiRegion == "cn" {
c.ZaiBaseURL = "https://open.bigmodel.cn/api"
} else {
c.ZaiBaseURL = "https://api.z.ai/api"
}
}
if c.SessionIdleTimeout == 0 {
c.SessionIdleTimeout = 600 * time.Second
Expand Down
103 changes: 103 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,65 @@ func TestConfig_ZaiDefaults(t *testing.T) {
}
}

func TestConfig_ZaiRegion_LoadsFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("ZAI_API_KEY", "zai_test_key")
os.Setenv("ZAI_REGION", "cn")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.ZaiRegion != "cn" {
t.Errorf("ZaiRegion = %q, want %q", cfg.ZaiRegion, "cn")
}
}

func TestConfig_ZaiRegion_DefaultsToGlobal(t *testing.T) {
os.Clearenv()
os.Setenv("ZAI_API_KEY", "zai_test_key")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.ZaiRegion != "global" {
t.Errorf("ZaiRegion = %q, want %q (default)", cfg.ZaiRegion, "global")
}
}

func TestConfig_ZaiRegion_NormalizesToLowercase(t *testing.T) {
os.Clearenv()
os.Setenv("ZAI_API_KEY", "zai_test_key")
os.Setenv("ZAI_REGION", "CN")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.ZaiRegion != "cn" {
t.Errorf("ZaiRegion = %q, want %q (lowercase)", cfg.ZaiRegion, "cn")
}
}

func TestConfig_ZaiRegion_SelectsCNBaseURL(t *testing.T) {
os.Clearenv()
os.Setenv("ZAI_API_KEY", "zai_test_key")
os.Setenv("ZAI_REGION", "cn")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.ZaiBaseURL != "https://open.bigmodel.cn/api" {
t.Errorf("ZaiBaseURL = %q, want %q (CN endpoint)", cfg.ZaiBaseURL, "https://open.bigmodel.cn/api")
}
}

func TestConfig_DefaultValues(t *testing.T) {
os.Setenv("SYNTHETIC_API_KEY", "syn_test_key_123")
defer os.Clearenv()
Expand Down Expand Up @@ -204,6 +263,50 @@ func TestConfig_MiniMaxProvider(t *testing.T) {
}
}

func TestConfig_MiniMaxRegion_LoadsFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("MINIMAX_API_KEY", "sk-cp-test-key")
os.Setenv("MINIMAX_REGION", "cn")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.MiniMaxRegion != "cn" {
t.Errorf("MiniMaxRegion = %q, want %q", cfg.MiniMaxRegion, "cn")
}
}

func TestConfig_MiniMaxRegion_DefaultsToGlobal(t *testing.T) {
os.Clearenv()
os.Setenv("MINIMAX_API_KEY", "sk-cp-test-key")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.MiniMaxRegion != "global" {
t.Errorf("MiniMaxRegion = %q, want %q (default)", cfg.MiniMaxRegion, "global")
}
}

func TestConfig_MiniMaxRegion_NormalizesToLowercase(t *testing.T) {
os.Clearenv()
os.Setenv("MINIMAX_API_KEY", "sk-cp-test-key")
os.Setenv("MINIMAX_REGION", "CN")
defer os.Clearenv()

cfg, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if cfg.MiniMaxRegion != "cn" {
t.Errorf("MiniMaxRegion = %q, want %q (lowercase)", cfg.MiniMaxRegion, "cn")
}
}

func TestConfig_AllowsNoProvidersConfigured(t *testing.T) {
os.Clearenv()

Expand Down
15 changes: 11 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,9 @@ func run() error {
}

if cfg.HasProvider("zai") {
zaiClient = api.NewZaiClient(cfg.ZaiAPIKey, logger)
logger.Info("Z.ai API client configured", "base_url", cfg.ZaiBaseURL)
zaiBaseURL := cfg.ZaiBaseURL + "/monitor/usage/quota/limit"
zaiClient = api.NewZaiClient(cfg.ZaiAPIKey, logger, api.WithZaiBaseURL(zaiBaseURL))
logger.Info("Z.ai API client configured", "base_url", cfg.ZaiBaseURL, "region", cfg.ZaiRegion)
}

var anthropicClient *api.AnthropicClient
Expand Down Expand Up @@ -716,8 +717,12 @@ func run() error {

var minimaxClient *api.MiniMaxClient
if cfg.HasProvider("minimax") {
minimaxClient = api.NewMiniMaxClient(cfg.MiniMaxAPIKey, logger)
logger.Info("MiniMax API client configured")
baseURL := "https://api.minimax.io/v1/api/openplatform/coding_plan/remains"
if cfg.MiniMaxRegion == "cn" {
baseURL = "https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains"
}
minimaxClient = api.NewMiniMaxClient(cfg.MiniMaxAPIKey, logger, api.WithMiniMaxBaseURL(baseURL))
logger.Info("MiniMax API client configured", "region", cfg.MiniMaxRegion)
}

// Gemini provider - env vars or auto-detect from ~/.gemini/oauth_creds.json
Expand Down Expand Up @@ -1634,10 +1639,12 @@ func printHelp() {
fmt.Println(" SYNTHETIC_API_KEY Synthetic API key")
fmt.Println(" ZAI_API_KEY Z.ai API key")
fmt.Println(" ZAI_BASE_URL Z.ai base URL (default: https://api.z.ai/api)")
fmt.Println(" ZAI_REGION Z.ai region: global or cn (default: global)")
fmt.Println(" ANTHROPIC_TOKEN Anthropic token (auto-detected if not set)")
fmt.Println(" COPILOT_TOKEN GitHub Copilot token (PAT with copilot scope)")
fmt.Println(" CODEX_TOKEN Codex OAuth token (recommended; required for Codex-only)")
fmt.Println(" MINIMAX_API_KEY MiniMax API key")
fmt.Println(" MINIMAX_REGION MiniMax region: global or cn (default: global)")
fmt.Println(" CODEX_HOME Optional Codex auth directory (uses CODEX_HOME/auth.json)")
fmt.Println(" ONWATCH_POLL_INTERVAL Polling interval in seconds")
fmt.Println(" ONWATCH_PORT Dashboard HTTP port")
Expand Down
Loading