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
1 change: 1 addition & 0 deletions cmd/chat_config_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
configEntryOllamaURL = "ollama-url"
configEntryKeyView = "key-view"
configEntryXiaomiRegion = "xiaomi-region"
configEntryZAIRegion = "zai-region"
)

// Providers referenced by config UI flows.
Expand Down
3 changes: 3 additions & 0 deletions cmd/chat_config_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ func saveCredentialAsync(inference hawkconfig.CredentialInference, secret string
if inference.ProviderID == hawkconfig.ProviderXiaomiTokenPlan {
hawkconfig.ApplyXiaomiTokenPlanRegionEnv(ctx)
}
if inference.ProviderID == hawkconfig.ProviderZAICoding {
hawkconfig.ApplyZAIRegionEnv(ctx)
}
rtInf := config.InferenceFromOption(credentialOptionFromHawk(inference))
if err := runtime.SaveCredential(ctx, rtInf, secret); err != nil {
return configApplyCredentialsMsg{
Expand Down
25 changes: 25 additions & 0 deletions cmd/chat_config_gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func (m chatModel) configGatewayRows() []configGatewayRow {
display += " · region required"
}
}
if id == hawkconfig.ProviderZAICoding {
if reg := hawkconfig.ZAIRegionLabel(id); reg != "" {
display += " · " + reg
} else {
display += " · region"
}
}
rows = append(rows, configGatewayRow{
ID: id,
DisplayName: display,
Expand Down Expand Up @@ -113,6 +120,11 @@ func (m chatModel) refreshConfigGateway() (chatModel, tea.Cmd) {
m.configNotice = "Pick Token Plan region (cn / sgp / ams) before refresh"
return m.startConfigXiaomiTokenPlanRegion(), nil
}
if row.ID == hawkconfig.ProviderZAICoding && hawkconfig.NeedsZAIRegion(row.ID) {
m.configNotice = "Pick Coding Plan region (international / cn) before refresh"
return m.startConfigZAIRegion(row.ID), nil
}

if !row.HasKey {
m.configNotice = fmt.Sprintf("Select %s and press enter to paste an API key", row.DisplayName)
return m, nil
Expand Down Expand Up @@ -206,12 +218,20 @@ func (m chatModel) configGatewaysView() string {
if targetIdx >= 0 && targetIdx < len(rows) && rows[targetIdx].ID == hawkconfig.ProviderXiaomiTokenPlan {
hint = "Token Plan: enter pick region (cn/sgp/ams) then key · g change region"
}
if targetIdx >= 0 && targetIdx < len(rows) && rows[targetIdx].ID == hawkconfig.ProviderZAICoding {
hint = "Coding Plan: enter pick region (international/cn) then key · g change region"
}

b.WriteString("\n" + mutedStyle.Render(indent+hint))
} else {
hints := "enter use gateway · k view key · delete remove · r refresh"
if targetIdx >= 0 && targetIdx < len(rows) && rows[targetIdx].ID == hawkconfig.ProviderXiaomiTokenPlan {
hints = "enter · g region · k key · delete · r refresh"
}
if targetIdx >= 0 && targetIdx < len(rows) && rows[targetIdx].ID == hawkconfig.ProviderZAICoding {
hints = "enter · g region · k key · delete · r refresh"
}

b.WriteString("\n" + configTableSelectionFooter(len(rows), m.configScroll, end, mutedStyle, hints))
}
return m.configTabShellView(b.String())
Expand Down Expand Up @@ -258,6 +278,11 @@ func (m chatModel) handleConfigGatewaysSelect() (chatModel, tea.Cmd) {
return m.startConfigXiaomiTokenPlanRegion(), nil
}
}
if row.ID == hawkconfig.ProviderZAICoding && (!row.HasKey || hawkconfig.NeedsZAIRegion(row.ID)) {
m.configGatewayFocus = m.configSel
return m.startConfigZAIRegion(row.ID), nil
}

if !row.HasKey {
if row.ID == configProviderOllama {
return m.startConfigOllamaURL()
Expand Down
10 changes: 10 additions & 0 deletions cmd/chat_config_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ func (m chatModel) startConfigKeyForProvider(provider string) (chatModel, tea.Cm
return m.startConfigXiaomiTokenPlanRegion(), nil
}
}
if provider == hawkconfig.ProviderZAICoding && hawkconfig.NeedsZAIRegion(provider) {
m.configPostSaveKeysProvider = provider
return m.startConfigZAIRegion(provider), nil
}

name := hawkconfig.GatewayDisplayName(provider)
m.configNotice = "Paste API key for " + name
return m.startConfigEntry(configEntryAPIKeyPaste, provider)
Expand All @@ -85,6 +90,11 @@ func (m chatModel) startConfigKeyReplace(provider string) (chatModel, tea.Cmd) {
m.configPostSaveKeysProvider = provider
return m.startConfigXiaomiTokenPlanRegion(), nil
}
if provider == hawkconfig.ProviderZAICoding && hawkconfig.NeedsZAIRegion(provider) {
m.configPostSaveKeysProvider = provider
return m.startConfigZAIRegion(provider), nil
}

m.configReplaceProvider = provider
m.configEntry = configEntryNone
m.configNotice = "Paste replacement API key for " + hawkconfig.GatewayDisplayName(provider)
Expand Down
9 changes: 9 additions & 0 deletions cmd/chat_config_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ func (m chatModel) configPanelView() string {
if m.configEntry == configEntryXiaomiRegion {
return m.configXiaomiRegionView()
}
if m.configEntry == configEntryZAIRegion {
return m.configZAIRegionView()
}
switch m.configTab {
case configTabGateways:
return m.configGatewaysView()
Expand Down Expand Up @@ -583,6 +586,12 @@ func (m chatModel) handleConfigKey(msg tea.KeyMsg) (chatModel, tea.Cmd) {
}
return m.handleConfigXiaomiRegionKey(msg)
}
if m.configEntry == configEntryZAIRegion {
if m.configSaving {
return m, nil
}
return m.handleConfigZAIRegionKey(msg)
}
if m.configEntry != configEntryNone {
if m.configSaving {
return m, nil
Expand Down
124 changes: 124 additions & 0 deletions cmd/chat_config_zai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package cmd

import (
"context"
"strings"

tea "github.com/charmbracelet/bubbletea"

hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
)

var zaiRegions = []struct {
id string
label string
}{
{id: "international", label: "International (api.z.ai)"},
{id: "cn", label: "China (open.bigmodel.cn)"},
}

func zaiRegionIndex(region string) int {
region = strings.ToLower(strings.TrimSpace(region))
if region == "" {
return 0
}
for i, r := range zaiRegions {
if r.id == region {
return i
}
}
return 0
}

func (m chatModel) startConfigZAIRegion(providerID string) chatModel {
m.configEntry = configEntryZAIRegion
m.configProvider = providerID
if hawkconfig.NeedsZAIRegion(providerID) {
m.configZAIRegionSel = 0
} else {
m.configZAIRegionSel = zaiRegionIndex(hawkconfig.ZAIRegionLabel(providerID))
}
name := hawkconfig.GatewayDisplayName(providerID)
notice := "Select " + name + " region (↑↓ · enter · esc cancel)"
if saved := hawkconfig.ZAIRegionLabel(providerID); saved != "" {
notice = name + " region · current " + saved + " (↑↓ · enter · esc cancel)"
}
m.configNotice = notice
return m
}

func (m chatModel) configZAIRegionView() string {
mutedStyle := configMutedStyle()
accentStyle := configAccentStyle()
rowStyle := configRowStyle()
var b strings.Builder
prov := m.configProvider
name := hawkconfig.GatewayDisplayName(prov)
b.WriteString(renderConfigBreadcrumb(name+" region") + "\n\n")
for i, r := range zaiRegions {
prefix := " "
if i == m.configZAIRegionSel {
prefix = "> "
}
line := prefix + r.label
if i == m.configZAIRegionSel {
b.WriteString(accentStyle.Render(line) + "\n")
} else {
b.WriteString(rowStyle.Render(line) + "\n")
}
}
b.WriteString("\n" + mutedStyle.Render(" Coding Plan uses dedicated /coding/paas/v4 on the chosen region"))
return m.configTabShellView(b.String())
}

func (m chatModel) handleConfigZAIRegionKey(msg tea.KeyMsg) (chatModel, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
prov := m.configProvider
m.configEntry = configEntryNone
m.configProvider = ""
if idx := m.configGatewayRowIndex(prov); idx >= 0 {
m.configSel = idx
}
m.configNotice = ""
return m, nil
case tea.KeyUp:
if m.configZAIRegionSel > 0 {
m.configZAIRegionSel--
}
return m, nil
case tea.KeyDown:
if m.configZAIRegionSel < len(zaiRegions)-1 {
m.configZAIRegionSel++
}
return m, nil
case tea.KeyEnter:
if m.configZAIRegionSel < 0 || m.configZAIRegionSel >= len(zaiRegions) {
return m, nil
}
region := zaiRegions[m.configZAIRegionSel].id
prov := m.configProvider
if err := hawkconfig.SetZAIRegion(prov, region); err != nil {
m.configNotice = "Region: " + err.Error()
return m, nil
}
InvalidateModelCacheProvider(prov)
m.configEntry = configEntryNone
ctx := context.Background()
if post := strings.TrimSpace(m.configPostSaveKeysProvider); post == prov {
m.configPostSaveKeysProvider = ""
return m.startConfigKeyReplace(post)
}
if hawkconfig.HasStoredCredentialForProvider(ctx, prov) {
m.configNotice = "Region saved (" + region + ") — press r to refresh models"
if idx := m.configGatewayRowIndex(prov); idx >= 0 {
m.configSel = idx
}
return m, nil
}
m.configNotice = "Region saved (" + region + ") — paste Z.AI API key"
return m.startConfigKeyForProvider(prov)
default:
return m, nil
}
}
1 change: 1 addition & 0 deletions cmd/chat_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ type chatModel struct {
configSaving bool // blocks hub/list input while async credential work runs
configPendingOllamaURL string
configXiaomiRegionSel int // Token Plan region picker index
configZAIRegionSel int // Z.AI (general or coding) region picker index
pluginRuntime *plugin.Runtime
spinnerVerb string
// Per-turn token counters shown next to the spinner (↑ input, ↓ output).
Expand Down
5 changes: 3 additions & 2 deletions cmd/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func friendlyError(err error) string {
{[]string{"gemini_api_key", "google_api_key", "gemini api key"}, "GEMINI_API_KEY", "Gemini"},
{[]string{"openrouter_api_key", "openrouter api key"}, "OPENROUTER_API_KEY", "OpenRouter"},
{[]string{"canopywave_api_key", "canopywave api key"}, "CANOPYWAVE_API_KEY", "CanopyWave"},
{[]string{"zai_api_key", "z.ai api key", "z-ai api key"}, "ZAI_API_KEY", "Z.AI"},
{[]string{"zai_payg_api_key", "zai_api_key"}, "ZAI_API_KEY", "Z.AI"},
{[]string{"zai_coding_api_key", "zai_coding_api_key"}, "ZAI_CODING_API_KEY", "Z.AI Coding Plan"},
{[]string{"xai_api_key", "xai api key"}, "XAI_API_KEY", "xAI (Grok)"},
{[]string{"opencodego_api_key", "opencodego api key"}, "OPENCODEGO_API_KEY", "OpenCodeGo"},
{[]string{"moonshot_api_key", "moonshot api key"}, "MOONSHOT_API_KEY", "Kimi (Moonshot)"},
Expand Down Expand Up @@ -447,7 +448,7 @@ func providerDNSHost(provider string) string {
return "api.x.ai"
case "canopywave":
return "inference.canopywave.io"
case "z-ai", "zai":
case "zai_payg", "zai_coding":
return "api.z.ai"
case "kimi", "moonshotai":
return "api.moonshot.ai"
Expand Down
2 changes: 1 addition & 1 deletion cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func configureSession(sess *engine.Session, settings hawkconfig.Settings, maxTur
sess.Autonomy = lvl
}

// GLM/Z.ai extended reasoning toggle (applied in the stream loop for z-ai).
// GLM/Z.AI extended reasoning toggle (applied in the stream loop for zai_coding/zai_payg).
sess.GLMThinkingEnabled = settings.GLMThinkingEnabled

return nil
Expand Down
Loading
Loading