Skip to content

Commit 5126916

Browse files
Copilotsawka
andcommitted
Add AI mode proxy URL plumbing and backend support
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
1 parent 1019c2a commit 5126916

9 files changed

Lines changed: 106 additions & 27 deletions

File tree

pkg/aiusechat/gemini/gemini-backend.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ func appendPartToLastUserMessage(contents []GeminiContent, text string) {
5454
}
5555
}
5656

57+
func makeHTTPClient(proxyURL string) (*http.Client, error) {
58+
httpClient := &http.Client{
59+
Timeout: 0, // rely on ctx; streaming can be long
60+
}
61+
if proxyURL == "" {
62+
return httpClient, nil
63+
}
64+
65+
pURL, err := url.Parse(proxyURL)
66+
if err != nil {
67+
return nil, fmt.Errorf("invalid proxy URL: %w", err)
68+
}
69+
httpClient.Transport = &http.Transport{
70+
Proxy: http.ProxyURL(pURL),
71+
}
72+
return httpClient, nil
73+
}
74+
5775
// buildGeminiHTTPRequest creates an HTTP request for the Gemini API
5876
func buildGeminiHTTPRequest(ctx context.Context, contents []GeminiContent, chatOpts uctypes.WaveChatOpts) (*http.Request, error) {
5977
opts := chatOpts.Config
@@ -231,8 +249,9 @@ func RunGeminiChatStep(
231249
return nil, nil, nil, err
232250
}
233251

234-
httpClient := &http.Client{
235-
Timeout: 0, // rely on ctx; streaming can be long
252+
httpClient, err := makeHTTPClient(chatOpts.Config.ProxyURL)
253+
if err != nil {
254+
return nil, nil, nil, err
236255
}
237256

238257
resp, err := httpClient.Do(req)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package gemini
5+
6+
import "testing"
7+
8+
func TestMakeHTTPClientProxy(t *testing.T) {
9+
client, err := makeHTTPClient("http://localhost:8080")
10+
if err != nil {
11+
t.Fatalf("unexpected error: %v", err)
12+
}
13+
if client.Transport == nil {
14+
t.Fatalf("expected proxy transport to be set")
15+
}
16+
}
17+
18+
func TestMakeHTTPClientInvalidProxy(t *testing.T) {
19+
_, err := makeHTTPClient("://bad-url")
20+
if err == nil {
21+
t.Fatalf("expected invalid proxy URL error")
22+
}
23+
}

pkg/aiusechat/openaichat/openaichat-backend.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"io"
1212
"log"
1313
"net/http"
14+
"net/url"
1415
"strings"
1516
"time"
1617

@@ -21,6 +22,22 @@ import (
2122
"github.com/wavetermdev/waveterm/pkg/web/sse"
2223
)
2324

25+
func makeHTTPClient(proxyURL string) (*http.Client, error) {
26+
client := &http.Client{}
27+
if proxyURL == "" {
28+
return client, nil
29+
}
30+
31+
pURL, err := url.Parse(proxyURL)
32+
if err != nil {
33+
return nil, fmt.Errorf("invalid proxy URL: %w", err)
34+
}
35+
client.Transport = &http.Transport{
36+
Proxy: http.ProxyURL(pURL),
37+
}
38+
return client, nil
39+
}
40+
2441
// RunChatStep executes a chat step using the chat completions API
2542
func RunChatStep(
2643
ctx context.Context,
@@ -60,7 +77,10 @@ func RunChatStep(
6077
return nil, nil, nil, err
6178
}
6279

63-
client := &http.Client{}
80+
client, err := makeHTTPClient(chatOpts.Config.ProxyURL)
81+
if err != nil {
82+
return nil, nil, nil, err
83+
}
6484
resp, err := client.Do(req)
6585
if err != nil {
6686
return nil, nil, nil, fmt.Errorf("request failed: %w", err)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package openaichat
5+
6+
import "testing"
7+
8+
func TestMakeHTTPClientProxy(t *testing.T) {
9+
client, err := makeHTTPClient("http://localhost:8080")
10+
if err != nil {
11+
t.Fatalf("unexpected error: %v", err)
12+
}
13+
if client.Transport == nil {
14+
t.Fatalf("expected proxy transport to be set")
15+
}
16+
}
17+
18+
func TestMakeHTTPClientInvalidProxy(t *testing.T) {
19+
_, err := makeHTTPClient("://bad-url")
20+
if err == nil {
21+
t.Fatalf("expected invalid proxy URL error")
22+
}
23+
}

pkg/aiusechat/uctypes/uctypes.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -189,29 +189,6 @@ const (
189189
ApprovalCanceled = "canceled"
190190
)
191191

192-
type AIModeConfig struct {
193-
Mode string `json:"mode"`
194-
DisplayName string `json:"display:name"`
195-
DisplayOrder float64 `json:"display:order,omitempty"`
196-
DisplayIcon string `json:"display:icon"`
197-
Provider string `json:"provider,omitempty"`
198-
APIType string `json:"apitype"`
199-
Model string `json:"model"`
200-
ThinkingLevel string `json:"thinkinglevel"`
201-
BaseURL string `json:"baseurl,omitempty"`
202-
WaveAICloud bool `json:"waveaicloud,omitempty"`
203-
APIVersion string `json:"apiversion,omitempty"`
204-
APIToken string `json:"apitoken,omitempty"`
205-
APITokenSecretName string `json:"apitokensecretname,omitempty"`
206-
Premium bool `json:"premium"`
207-
Description string `json:"description"`
208-
Capabilities []string `json:"capabilities,omitempty"`
209-
}
210-
211-
func (c *AIModeConfig) HasCapability(cap string) bool {
212-
return slices.Contains(c.Capabilities, cap)
213-
}
214-
215192
// when updating this struct, also modify frontend/app/aipanel/aitypes.ts WaveUIDataTypes.tooluse
216193
type UIMessageDataToolUse struct {
217194
ToolCallId string `json:"toolcallid"`

pkg/aiusechat/usechat.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func getWaveAISettings(premium bool, builderMode bool, rtInfo waveobj.ObjRTInfo,
123123
Verbosity: verbosity,
124124
AIMode: aiMode,
125125
Endpoint: baseUrl,
126+
ProxyURL: config.ProxyURL,
126127
Capabilities: config.Capabilities,
127128
WaveAIPremium: config.WaveAIPremium,
128129
}

pkg/aiusechat/usechat_mode_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,15 @@ func TestApplyProviderDefaultsGroq(t *testing.T) {
2525
t.Fatalf("expected API token secret name %q, got %q", GroqAPITokenSecretName, config.APITokenSecretName)
2626
}
2727
}
28+
29+
func TestApplyProviderDefaultsKeepsProxyURL(t *testing.T) {
30+
config := wconfig.AIModeConfigType{
31+
Provider: uctypes.AIProvider_OpenAI,
32+
Model: "gpt-5-mini",
33+
ProxyURL: "http://localhost:8080",
34+
}
35+
applyProviderDefaults(&config)
36+
if config.ProxyURL != "http://localhost:8080" {
37+
t.Fatalf("expected proxy URL to be preserved, got %q", config.ProxyURL)
38+
}
39+
}

pkg/wconfig/settingsconfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ type AIModeConfigType struct {
288288
ThinkingLevel string `json:"ai:thinkinglevel,omitempty" jsonschema:"enum=low,enum=medium,enum=high"`
289289
Verbosity string `json:"ai:verbosity,omitempty" jsonschema:"enum=low,enum=medium,enum=high,description=Text verbosity level (OpenAI Responses API only)"`
290290
Endpoint string `json:"ai:endpoint,omitempty"`
291+
ProxyURL string `json:"ai:proxyurl,omitempty"`
291292
AzureAPIVersion string `json:"ai:azureapiversion,omitempty"`
292293
APIToken string `json:"ai:apitoken,omitempty"`
293294
APITokenSecretName string `json:"ai:apitokensecretname,omitempty"`

schema/waveai.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
"ai:endpoint": {
6060
"type": "string"
6161
},
62+
"ai:proxyurl": {
63+
"type": "string"
64+
},
6265
"ai:azureapiversion": {
6366
"type": "string"
6467
},
@@ -109,4 +112,4 @@
109112
"$ref": "#/$defs/AIModeConfigType"
110113
},
111114
"type": "object"
112-
}
115+
}

0 commit comments

Comments
 (0)