fix(web/config): preserve and update builtin web provider API keys
This commit is contained in:
parent
935d5f8660
commit
9db32d996b
7 changed files with 181 additions and 14 deletions
|
|
@ -191,6 +191,9 @@ func (h *Handler) handlePatchConfig(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
type webSearchAPIKeyPatch struct {
|
||||
braveAPIKey *string
|
||||
tavilyAPIKey *string
|
||||
perplexityAPIKey *string
|
||||
glmSearchAPIKey *string
|
||||
baiduSearchAPIKey *string
|
||||
}
|
||||
|
|
@ -199,6 +202,15 @@ func parseWebSearchAPIKeyPatch(body []byte) webSearchAPIKeyPatch {
|
|||
var raw struct {
|
||||
Tools *struct {
|
||||
Web *struct {
|
||||
Brave *struct {
|
||||
APIKey *string `json:"api_key"`
|
||||
} `json:"brave"`
|
||||
Tavily *struct {
|
||||
APIKey *string `json:"api_key"`
|
||||
} `json:"tavily"`
|
||||
Perplexity *struct {
|
||||
APIKey *string `json:"api_key"`
|
||||
} `json:"perplexity"`
|
||||
GLMSearch *struct {
|
||||
APIKey *string `json:"api_key"`
|
||||
} `json:"glm_search"`
|
||||
|
|
@ -214,6 +226,15 @@ func parseWebSearchAPIKeyPatch(body []byte) webSearchAPIKeyPatch {
|
|||
|
||||
patch := webSearchAPIKeyPatch{}
|
||||
if raw.Tools != nil && raw.Tools.Web != nil {
|
||||
if raw.Tools.Web.Brave != nil && raw.Tools.Web.Brave.APIKey != nil {
|
||||
patch.braveAPIKey = raw.Tools.Web.Brave.APIKey
|
||||
}
|
||||
if raw.Tools.Web.Tavily != nil && raw.Tools.Web.Tavily.APIKey != nil {
|
||||
patch.tavilyAPIKey = raw.Tools.Web.Tavily.APIKey
|
||||
}
|
||||
if raw.Tools.Web.Perplexity != nil && raw.Tools.Web.Perplexity.APIKey != nil {
|
||||
patch.perplexityAPIKey = raw.Tools.Web.Perplexity.APIKey
|
||||
}
|
||||
if raw.Tools.Web.GLMSearch != nil && raw.Tools.Web.GLMSearch.APIKey != nil {
|
||||
patch.glmSearchAPIKey = raw.Tools.Web.GLMSearch.APIKey
|
||||
}
|
||||
|
|
@ -225,6 +246,15 @@ func parseWebSearchAPIKeyPatch(body []byte) webSearchAPIKeyPatch {
|
|||
}
|
||||
|
||||
func applyWebSearchAPIKeyPatch(cfg *config.Config, patch webSearchAPIKeyPatch) {
|
||||
if patch.braveAPIKey != nil {
|
||||
cfg.Tools.Web.Brave.SetAPIKey(strings.TrimSpace(*patch.braveAPIKey))
|
||||
}
|
||||
if patch.tavilyAPIKey != nil {
|
||||
cfg.Tools.Web.Tavily.SetAPIKey(strings.TrimSpace(*patch.tavilyAPIKey))
|
||||
}
|
||||
if patch.perplexityAPIKey != nil {
|
||||
cfg.Tools.Web.Perplexity.SetAPIKey(strings.TrimSpace(*patch.perplexityAPIKey))
|
||||
}
|
||||
if patch.glmSearchAPIKey != nil {
|
||||
cfg.Tools.Web.GLMSearch.SetAPIKey(strings.TrimSpace(*patch.glmSearchAPIKey))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,32 +160,61 @@ func runConfigRequest(t *testing.T, configPath, method, body string) {
|
|||
}
|
||||
}
|
||||
|
||||
func assertWebSearchAPIKeys(t *testing.T, configPath, wantGLMAPIKey, wantBaiduAPIKey string) {
|
||||
type webSearchAPIKeys struct {
|
||||
brave string
|
||||
tavily string
|
||||
perplexity string
|
||||
glm string
|
||||
baidu string
|
||||
}
|
||||
|
||||
func assertWebSearchAPIKeys(t *testing.T, configPath string, want webSearchAPIKeys) {
|
||||
t.Helper()
|
||||
|
||||
cfg, err := config.LoadConfig(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error = %v", err)
|
||||
}
|
||||
if got := cfg.Tools.Web.GLMSearch.APIKey(); got != wantGLMAPIKey {
|
||||
t.Fatalf("tools.web.glm_search.api_key = %q, want %q", got, wantGLMAPIKey)
|
||||
if got := cfg.Tools.Web.Brave.APIKey(); got != want.brave {
|
||||
t.Fatalf("tools.web.brave.api_key = %q, want %q", got, want.brave)
|
||||
}
|
||||
if got := cfg.Tools.Web.BaiduSearch.APIKey(); got != wantBaiduAPIKey {
|
||||
t.Fatalf("tools.web.baidu_search.api_key = %q, want %q", got, wantBaiduAPIKey)
|
||||
if got := cfg.Tools.Web.Tavily.APIKey(); got != want.tavily {
|
||||
t.Fatalf("tools.web.tavily.api_key = %q, want %q", got, want.tavily)
|
||||
}
|
||||
if got := cfg.Tools.Web.Perplexity.APIKey(); got != want.perplexity {
|
||||
t.Fatalf("tools.web.perplexity.api_key = %q, want %q", got, want.perplexity)
|
||||
}
|
||||
if got := cfg.Tools.Web.GLMSearch.APIKey(); got != want.glm {
|
||||
t.Fatalf("tools.web.glm_search.api_key = %q, want %q", got, want.glm)
|
||||
}
|
||||
if got := cfg.Tools.Web.BaiduSearch.APIKey(); got != want.baidu {
|
||||
t.Fatalf("tools.web.baidu_search.api_key = %q, want %q", got, want.baidu)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePatchConfig_PreservesWebSearchAPIKeysWhenOmitted(t *testing.T) {
|
||||
configPath, cleanup := setupOAuthTestEnv(t)
|
||||
defer cleanup()
|
||||
seedWebSearchAPIKeys(t, configPath, "glm-existing-key", "baidu-existing-key")
|
||||
seedWebSearchAPIKeys(t, configPath, webSearchAPIKeys{
|
||||
brave: "brave-existing-key",
|
||||
tavily: "tavily-existing-key",
|
||||
perplexity: "perplexity-existing-key",
|
||||
glm: "glm-existing-key",
|
||||
baidu: "baidu-existing-key",
|
||||
})
|
||||
|
||||
runConfigRequest(t, configPath, http.MethodPatch, `{
|
||||
"gateway": {
|
||||
"log_level": "info"
|
||||
}
|
||||
}`)
|
||||
assertWebSearchAPIKeys(t, configPath, "glm-existing-key", "baidu-existing-key")
|
||||
assertWebSearchAPIKeys(t, configPath, webSearchAPIKeys{
|
||||
brave: "brave-existing-key",
|
||||
tavily: "tavily-existing-key",
|
||||
perplexity: "perplexity-existing-key",
|
||||
glm: "glm-existing-key",
|
||||
baidu: "baidu-existing-key",
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandlePatchConfig_UpdatesWebSearchAPIKeys(t *testing.T) {
|
||||
|
|
@ -195,18 +224,33 @@ func TestHandlePatchConfig_UpdatesWebSearchAPIKeys(t *testing.T) {
|
|||
runConfigRequest(t, configPath, http.MethodPatch, `{
|
||||
"tools": {
|
||||
"web": {
|
||||
"brave": { "api_key": "brave-updated-key" },
|
||||
"tavily": { "api_key": "tavily-updated-key" },
|
||||
"perplexity": { "api_key": "perplexity-updated-key" },
|
||||
"glm_search": { "api_key": "glm-updated-key" },
|
||||
"baidu_search": { "api_key": "baidu-updated-key" }
|
||||
}
|
||||
}
|
||||
}`)
|
||||
assertWebSearchAPIKeys(t, configPath, "glm-updated-key", "baidu-updated-key")
|
||||
assertWebSearchAPIKeys(t, configPath, webSearchAPIKeys{
|
||||
brave: "brave-updated-key",
|
||||
tavily: "tavily-updated-key",
|
||||
perplexity: "perplexity-updated-key",
|
||||
glm: "glm-updated-key",
|
||||
baidu: "baidu-updated-key",
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleUpdateConfig_PreservesWebSearchAPIKeysWhenOmitted(t *testing.T) {
|
||||
configPath, cleanup := setupOAuthTestEnv(t)
|
||||
defer cleanup()
|
||||
seedWebSearchAPIKeys(t, configPath, "glm-existing-key-via-put", "baidu-existing-key-via-put")
|
||||
seedWebSearchAPIKeys(t, configPath, webSearchAPIKeys{
|
||||
brave: "brave-existing-key-via-put",
|
||||
tavily: "tavily-existing-key-via-put",
|
||||
perplexity: "perplexity-existing-key-via-put",
|
||||
glm: "glm-existing-key-via-put",
|
||||
baidu: "baidu-existing-key-via-put",
|
||||
})
|
||||
|
||||
runConfigRequest(t, configPath, http.MethodPut, `{
|
||||
"version": 1,
|
||||
|
|
@ -224,7 +268,13 @@ func TestHandleUpdateConfig_PreservesWebSearchAPIKeysWhenOmitted(t *testing.T) {
|
|||
}
|
||||
]
|
||||
}`)
|
||||
assertWebSearchAPIKeys(t, configPath, "glm-existing-key-via-put", "baidu-existing-key-via-put")
|
||||
assertWebSearchAPIKeys(t, configPath, webSearchAPIKeys{
|
||||
brave: "brave-existing-key-via-put",
|
||||
tavily: "tavily-existing-key-via-put",
|
||||
perplexity: "perplexity-existing-key-via-put",
|
||||
glm: "glm-existing-key-via-put",
|
||||
baidu: "baidu-existing-key-via-put",
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleUpdateConfig_UpdatesWebSearchAPIKeys(t *testing.T) {
|
||||
|
|
@ -248,15 +298,24 @@ func TestHandleUpdateConfig_UpdatesWebSearchAPIKeys(t *testing.T) {
|
|||
],
|
||||
"tools": {
|
||||
"web": {
|
||||
"brave": { "api_key": "brave-updated-key-via-put" },
|
||||
"tavily": { "api_key": "tavily-updated-key-via-put" },
|
||||
"perplexity": { "api_key": "perplexity-updated-key-via-put" },
|
||||
"glm_search": { "api_key": "glm-updated-key-via-put" },
|
||||
"baidu_search": { "api_key": "baidu-updated-key-via-put" }
|
||||
}
|
||||
}
|
||||
}`)
|
||||
assertWebSearchAPIKeys(t, configPath, "glm-updated-key-via-put", "baidu-updated-key-via-put")
|
||||
assertWebSearchAPIKeys(t, configPath, webSearchAPIKeys{
|
||||
brave: "brave-updated-key-via-put",
|
||||
tavily: "tavily-updated-key-via-put",
|
||||
perplexity: "perplexity-updated-key-via-put",
|
||||
glm: "glm-updated-key-via-put",
|
||||
baidu: "baidu-updated-key-via-put",
|
||||
})
|
||||
}
|
||||
|
||||
func seedWebSearchAPIKeys(t *testing.T, configPath, glmAPIKey, baiduAPIKey string) {
|
||||
func seedWebSearchAPIKeys(t *testing.T, configPath string, keys webSearchAPIKeys) {
|
||||
t.Helper()
|
||||
|
||||
cfg, err := config.LoadConfig(configPath)
|
||||
|
|
@ -264,8 +323,11 @@ func seedWebSearchAPIKeys(t *testing.T, configPath, glmAPIKey, baiduAPIKey strin
|
|||
t.Fatalf("LoadConfig() error = %v", err)
|
||||
}
|
||||
|
||||
cfg.Tools.Web.GLMSearch.SetAPIKey(glmAPIKey)
|
||||
cfg.Tools.Web.BaiduSearch.SetAPIKey(baiduAPIKey)
|
||||
cfg.Tools.Web.Brave.SetAPIKey(keys.brave)
|
||||
cfg.Tools.Web.Tavily.SetAPIKey(keys.tavily)
|
||||
cfg.Tools.Web.Perplexity.SetAPIKey(keys.perplexity)
|
||||
cfg.Tools.Web.GLMSearch.SetAPIKey(keys.glm)
|
||||
cfg.Tools.Web.BaiduSearch.SetAPIKey(keys.baidu)
|
||||
|
||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||
t.Fatalf("SaveConfig() error = %v", err)
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@ export function ConfigPage() {
|
|||
"Cron exec timeout",
|
||||
{ min: 0 },
|
||||
)
|
||||
const braveAPIKey = form.braveAPIKey.trim()
|
||||
const tavilyAPIKey = form.tavilyAPIKey.trim()
|
||||
const perplexityAPIKey = form.perplexityAPIKey.trim()
|
||||
const glmSearchAPIKey = form.glmSearchAPIKey.trim()
|
||||
const baiduSearchAPIKey = form.baiduSearchAPIKey.trim()
|
||||
const execConfigPatch: Record<string, unknown> = {
|
||||
|
|
@ -209,6 +212,15 @@ export function ConfigPage() {
|
|||
}
|
||||
}
|
||||
|
||||
if (braveAPIKey !== "") {
|
||||
webConfigPatch.brave = { api_key: braveAPIKey }
|
||||
}
|
||||
if (tavilyAPIKey !== "") {
|
||||
webConfigPatch.tavily = { api_key: tavilyAPIKey }
|
||||
}
|
||||
if (perplexityAPIKey !== "") {
|
||||
webConfigPatch.perplexity = { api_key: perplexityAPIKey }
|
||||
}
|
||||
if (glmSearchAPIKey !== "") {
|
||||
webConfigPatch.glm_search = { api_key: glmSearchAPIKey }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -521,6 +521,45 @@ export function WebSearchSection({
|
|||
title={t("pages.config.sections.web_search")}
|
||||
description={t("pages.config.web_search_section_hint")}
|
||||
>
|
||||
<Field
|
||||
label={t("pages.config.brave_api_key")}
|
||||
hint={t("pages.config.brave_api_key_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
value={form.braveAPIKey}
|
||||
placeholder={t("pages.config.secret_placeholder")}
|
||||
onChange={(e) => onFieldChange("braveAPIKey", e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.tavily_api_key")}
|
||||
hint={t("pages.config.tavily_api_key_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
value={form.tavilyAPIKey}
|
||||
placeholder={t("pages.config.secret_placeholder")}
|
||||
onChange={(e) => onFieldChange("tavilyAPIKey", e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.perplexity_api_key")}
|
||||
hint={t("pages.config.perplexity_api_key_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
value={form.perplexityAPIKey}
|
||||
placeholder={t("pages.config.secret_placeholder")}
|
||||
onChange={(e) => onFieldChange("perplexityAPIKey", e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.glm_search_api_key")}
|
||||
hint={t("pages.config.glm_search_api_key_hint")}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ export interface CoreConfigForm {
|
|||
execTimeoutSeconds: string
|
||||
allowCommand: boolean
|
||||
cronExecTimeoutMinutes: string
|
||||
braveAPIKey: string
|
||||
tavilyAPIKey: string
|
||||
perplexityAPIKey: string
|
||||
glmSearchAPIKey: string
|
||||
baiduSearchAPIKey: string
|
||||
maxTokens: string
|
||||
|
|
@ -79,6 +82,9 @@ export const EMPTY_FORM: CoreConfigForm = {
|
|||
execTimeoutSeconds: "0",
|
||||
allowCommand: true,
|
||||
cronExecTimeoutMinutes: "5",
|
||||
braveAPIKey: "",
|
||||
tavilyAPIKey: "",
|
||||
perplexityAPIKey: "",
|
||||
glmSearchAPIKey: "",
|
||||
baiduSearchAPIKey: "",
|
||||
maxTokens: "32768",
|
||||
|
|
@ -133,6 +139,9 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
|||
const devices = asRecord(root.devices)
|
||||
const tools = asRecord(root.tools)
|
||||
const web = asRecord(tools.web)
|
||||
const brave = asRecord(web.brave)
|
||||
const tavily = asRecord(web.tavily)
|
||||
const perplexity = asRecord(web.perplexity)
|
||||
const glmSearch = asRecord(web.glm_search)
|
||||
const baiduSearch = asRecord(web.baidu_search)
|
||||
const cron = asRecord(tools.cron)
|
||||
|
|
@ -191,6 +200,9 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
|||
cron.exec_timeout_minutes,
|
||||
EMPTY_FORM.cronExecTimeoutMinutes,
|
||||
),
|
||||
braveAPIKey: asString(brave.api_key),
|
||||
tavilyAPIKey: asString(tavily.api_key),
|
||||
perplexityAPIKey: asString(perplexity.api_key),
|
||||
glmSearchAPIKey: asString(glmSearch.api_key),
|
||||
baiduSearchAPIKey: asString(baiduSearch.api_key),
|
||||
maxTokens: asNumberString(defaults.max_tokens, EMPTY_FORM.maxTokens),
|
||||
|
|
|
|||
|
|
@ -479,6 +479,12 @@
|
|||
"cron_exec_timeout": "Scheduled Command Timeout (minutes)",
|
||||
"cron_exec_timeout_hint": "Maximum runtime for scheduled commands. Set to 0 to disable the timeout.",
|
||||
"web_search_section_hint": "Manage API keys for built-in web search providers. Leave a field blank to keep the current key unchanged.",
|
||||
"brave_api_key": "Brave Search API Key",
|
||||
"brave_api_key_hint": "Used by the built-in Brave web search provider.",
|
||||
"tavily_api_key": "Tavily Search API Key",
|
||||
"tavily_api_key_hint": "Used by the built-in Tavily web search provider.",
|
||||
"perplexity_api_key": "Perplexity Search API Key",
|
||||
"perplexity_api_key_hint": "Used by the built-in Perplexity web search provider.",
|
||||
"glm_search_api_key": "GLM Search API Key",
|
||||
"glm_search_api_key_hint": "Used by the built-in GLM web search provider.",
|
||||
"baidu_search_api_key": "Baidu Search API Key",
|
||||
|
|
|
|||
|
|
@ -479,6 +479,12 @@
|
|||
"cron_exec_timeout": "定时命令超时(分钟)",
|
||||
"cron_exec_timeout_hint": "定时任务中命令的最长运行时间。设置为 0 表示不限制超时。",
|
||||
"web_search_section_hint": "管理内置网页搜索服务的 API Key。留空表示保持当前 Key 不变。",
|
||||
"brave_api_key": "Brave 搜索 API Key",
|
||||
"brave_api_key_hint": "用于内置的 Brave 网页搜索服务。",
|
||||
"tavily_api_key": "Tavily 搜索 API Key",
|
||||
"tavily_api_key_hint": "用于内置的 Tavily 网页搜索服务。",
|
||||
"perplexity_api_key": "Perplexity 搜索 API Key",
|
||||
"perplexity_api_key_hint": "用于内置的 Perplexity 网页搜索服务。",
|
||||
"glm_search_api_key": "GLM 搜索 API Key",
|
||||
"glm_search_api_key_hint": "用于内置的 GLM 网页搜索服务。",
|
||||
"baidu_search_api_key": "百度搜索 API Key",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue