From 62f456f42023b266d6a22f5d85a0b6b847418425 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:05:02 +0000 Subject: [PATCH 1/3] Initial plan From 1019c2ae934bcd37042f44fc35d3bf8778a8888b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:08:12 +0000 Subject: [PATCH 2/3] Plan: add AI mode proxy URL support Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fbe74e2ac8..c47b7dc114 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.14.0", + "version": "0.14.1-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.14.0", + "version": "0.14.1-beta.0", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ From 512691660a963284443caba652eaa3be4e1bb8db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:09:39 +0000 Subject: [PATCH 3/3] Add AI mode proxy URL plumbing and backend support Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/aiusechat/gemini/gemini-backend.go | 23 +++++++++++++++++-- pkg/aiusechat/gemini/gemini-backend_test.go | 23 +++++++++++++++++++ .../openaichat/openaichat-backend.go | 22 +++++++++++++++++- .../openaichat/openaichat-backend_test.go | 23 +++++++++++++++++++ pkg/aiusechat/uctypes/uctypes.go | 23 ------------------- pkg/aiusechat/usechat.go | 1 + pkg/aiusechat/usechat_mode_test.go | 12 ++++++++++ pkg/wconfig/settingsconfig.go | 1 + schema/waveai.json | 5 +++- 9 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 pkg/aiusechat/gemini/gemini-backend_test.go create mode 100644 pkg/aiusechat/openaichat/openaichat-backend_test.go diff --git a/pkg/aiusechat/gemini/gemini-backend.go b/pkg/aiusechat/gemini/gemini-backend.go index 23a331dedb..ea76e17887 100644 --- a/pkg/aiusechat/gemini/gemini-backend.go +++ b/pkg/aiusechat/gemini/gemini-backend.go @@ -54,6 +54,24 @@ func appendPartToLastUserMessage(contents []GeminiContent, text string) { } } +func makeHTTPClient(proxyURL string) (*http.Client, error) { + httpClient := &http.Client{ + Timeout: 0, // rely on ctx; streaming can be long + } + if proxyURL == "" { + return httpClient, nil + } + + pURL, err := url.Parse(proxyURL) + if err != nil { + return nil, fmt.Errorf("invalid proxy URL: %w", err) + } + httpClient.Transport = &http.Transport{ + Proxy: http.ProxyURL(pURL), + } + return httpClient, nil +} + // buildGeminiHTTPRequest creates an HTTP request for the Gemini API func buildGeminiHTTPRequest(ctx context.Context, contents []GeminiContent, chatOpts uctypes.WaveChatOpts) (*http.Request, error) { opts := chatOpts.Config @@ -231,8 +249,9 @@ func RunGeminiChatStep( return nil, nil, nil, err } - httpClient := &http.Client{ - Timeout: 0, // rely on ctx; streaming can be long + httpClient, err := makeHTTPClient(chatOpts.Config.ProxyURL) + if err != nil { + return nil, nil, nil, err } resp, err := httpClient.Do(req) diff --git a/pkg/aiusechat/gemini/gemini-backend_test.go b/pkg/aiusechat/gemini/gemini-backend_test.go new file mode 100644 index 0000000000..1225736029 --- /dev/null +++ b/pkg/aiusechat/gemini/gemini-backend_test.go @@ -0,0 +1,23 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package gemini + +import "testing" + +func TestMakeHTTPClientProxy(t *testing.T) { + client, err := makeHTTPClient("http://localhost:8080") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if client.Transport == nil { + t.Fatalf("expected proxy transport to be set") + } +} + +func TestMakeHTTPClientInvalidProxy(t *testing.T) { + _, err := makeHTTPClient("://bad-url") + if err == nil { + t.Fatalf("expected invalid proxy URL error") + } +} diff --git a/pkg/aiusechat/openaichat/openaichat-backend.go b/pkg/aiusechat/openaichat/openaichat-backend.go index 7b90aee674..8fd74dd0e7 100644 --- a/pkg/aiusechat/openaichat/openaichat-backend.go +++ b/pkg/aiusechat/openaichat/openaichat-backend.go @@ -11,6 +11,7 @@ import ( "io" "log" "net/http" + "net/url" "strings" "time" @@ -21,6 +22,22 @@ import ( "github.com/wavetermdev/waveterm/pkg/web/sse" ) +func makeHTTPClient(proxyURL string) (*http.Client, error) { + client := &http.Client{} + if proxyURL == "" { + return client, nil + } + + pURL, err := url.Parse(proxyURL) + if err != nil { + return nil, fmt.Errorf("invalid proxy URL: %w", err) + } + client.Transport = &http.Transport{ + Proxy: http.ProxyURL(pURL), + } + return client, nil +} + // RunChatStep executes a chat step using the chat completions API func RunChatStep( ctx context.Context, @@ -60,7 +77,10 @@ func RunChatStep( return nil, nil, nil, err } - client := &http.Client{} + client, err := makeHTTPClient(chatOpts.Config.ProxyURL) + if err != nil { + return nil, nil, nil, err + } resp, err := client.Do(req) if err != nil { return nil, nil, nil, fmt.Errorf("request failed: %w", err) diff --git a/pkg/aiusechat/openaichat/openaichat-backend_test.go b/pkg/aiusechat/openaichat/openaichat-backend_test.go new file mode 100644 index 0000000000..318e2ad287 --- /dev/null +++ b/pkg/aiusechat/openaichat/openaichat-backend_test.go @@ -0,0 +1,23 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package openaichat + +import "testing" + +func TestMakeHTTPClientProxy(t *testing.T) { + client, err := makeHTTPClient("http://localhost:8080") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if client.Transport == nil { + t.Fatalf("expected proxy transport to be set") + } +} + +func TestMakeHTTPClientInvalidProxy(t *testing.T) { + _, err := makeHTTPClient("://bad-url") + if err == nil { + t.Fatalf("expected invalid proxy URL error") + } +} diff --git a/pkg/aiusechat/uctypes/uctypes.go b/pkg/aiusechat/uctypes/uctypes.go index 05fe469fc5..d2b25bbc1b 100644 --- a/pkg/aiusechat/uctypes/uctypes.go +++ b/pkg/aiusechat/uctypes/uctypes.go @@ -189,29 +189,6 @@ const ( ApprovalCanceled = "canceled" ) -type AIModeConfig struct { - Mode string `json:"mode"` - DisplayName string `json:"display:name"` - DisplayOrder float64 `json:"display:order,omitempty"` - DisplayIcon string `json:"display:icon"` - Provider string `json:"provider,omitempty"` - APIType string `json:"apitype"` - Model string `json:"model"` - ThinkingLevel string `json:"thinkinglevel"` - BaseURL string `json:"baseurl,omitempty"` - WaveAICloud bool `json:"waveaicloud,omitempty"` - APIVersion string `json:"apiversion,omitempty"` - APIToken string `json:"apitoken,omitempty"` - APITokenSecretName string `json:"apitokensecretname,omitempty"` - Premium bool `json:"premium"` - Description string `json:"description"` - Capabilities []string `json:"capabilities,omitempty"` -} - -func (c *AIModeConfig) HasCapability(cap string) bool { - return slices.Contains(c.Capabilities, cap) -} - // when updating this struct, also modify frontend/app/aipanel/aitypes.ts WaveUIDataTypes.tooluse type UIMessageDataToolUse struct { ToolCallId string `json:"toolcallid"` diff --git a/pkg/aiusechat/usechat.go b/pkg/aiusechat/usechat.go index ca7587e339..a55a10060a 100644 --- a/pkg/aiusechat/usechat.go +++ b/pkg/aiusechat/usechat.go @@ -123,6 +123,7 @@ func getWaveAISettings(premium bool, builderMode bool, rtInfo waveobj.ObjRTInfo, Verbosity: verbosity, AIMode: aiMode, Endpoint: baseUrl, + ProxyURL: config.ProxyURL, Capabilities: config.Capabilities, WaveAIPremium: config.WaveAIPremium, } diff --git a/pkg/aiusechat/usechat_mode_test.go b/pkg/aiusechat/usechat_mode_test.go index 98ef4074c1..73959f9585 100644 --- a/pkg/aiusechat/usechat_mode_test.go +++ b/pkg/aiusechat/usechat_mode_test.go @@ -25,3 +25,15 @@ func TestApplyProviderDefaultsGroq(t *testing.T) { t.Fatalf("expected API token secret name %q, got %q", GroqAPITokenSecretName, config.APITokenSecretName) } } + +func TestApplyProviderDefaultsKeepsProxyURL(t *testing.T) { + config := wconfig.AIModeConfigType{ + Provider: uctypes.AIProvider_OpenAI, + Model: "gpt-5-mini", + ProxyURL: "http://localhost:8080", + } + applyProviderDefaults(&config) + if config.ProxyURL != "http://localhost:8080" { + t.Fatalf("expected proxy URL to be preserved, got %q", config.ProxyURL) + } +} diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 39da4ac60a..387598e899 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -288,6 +288,7 @@ type AIModeConfigType struct { ThinkingLevel string `json:"ai:thinkinglevel,omitempty" jsonschema:"enum=low,enum=medium,enum=high"` Verbosity string `json:"ai:verbosity,omitempty" jsonschema:"enum=low,enum=medium,enum=high,description=Text verbosity level (OpenAI Responses API only)"` Endpoint string `json:"ai:endpoint,omitempty"` + ProxyURL string `json:"ai:proxyurl,omitempty"` AzureAPIVersion string `json:"ai:azureapiversion,omitempty"` APIToken string `json:"ai:apitoken,omitempty"` APITokenSecretName string `json:"ai:apitokensecretname,omitempty"` diff --git a/schema/waveai.json b/schema/waveai.json index d917cdcdae..5d90b86abd 100644 --- a/schema/waveai.json +++ b/schema/waveai.json @@ -59,6 +59,9 @@ "ai:endpoint": { "type": "string" }, + "ai:proxyurl": { + "type": "string" + }, "ai:azureapiversion": { "type": "string" }, @@ -109,4 +112,4 @@ "$ref": "#/$defs/AIModeConfigType" }, "type": "object" -} \ No newline at end of file +}