Skip to content

Commit 4ec09cb

Browse files
Copilotsawka
andauthored
Centralize proxy HTTP client creation in aiutil and remove redundant backend tests (#2961)
`makeHTTPClient(proxyURL)` had been duplicated across AI backends with equivalent behavior. This change consolidates the logic into a single helper in `aiutil` and updates backends to consume it, then removes backend-local tests that only re-verified that shared utility behavior. - **Shared client construction** - Added `aiutil.MakeHTTPClient(proxyURL string) (*http.Client, error)` in `pkg/aiusechat/aiutil/aiutil.go`. - Standardizes proxy parsing and `http.Transport.Proxy` setup in one place. - Keeps streaming-safe client semantics (`Timeout: 0`) and existing invalid proxy URL error behavior. - **Backend refactor** - Removed duplicated client/proxy setup blocks from: - `pkg/aiusechat/openaichat/openaichat-backend.go` - `pkg/aiusechat/gemini/gemini-backend.go` - `pkg/aiusechat/openai/openai-backend.go` - `pkg/aiusechat/anthropic/anthropic-backend.go` - Replaced with direct calls to the shared helper. - **Test cleanup** - Deleted backend tests that only covered basic proxy client creation and no backend-specific behavior: - `pkg/aiusechat/openaichat/openaichat-backend_test.go` - `pkg/aiusechat/gemini/gemini-backend_test.go` ```go httpClient, err := aiutil.MakeHTTPClient(chatOpts.Config.ProxyURL) if err != nil { return nil, nil, nil, err } resp, err := httpClient.Do(req) ``` <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: sawka <[email protected]>
1 parent 95a97ca commit 4ec09cb

File tree

11 files changed

+55
-54
lines changed

11 files changed

+55
-54
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/aiusechat/aiutil/aiutil.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"encoding/hex"
1212
"encoding/json"
1313
"fmt"
14+
"net/http"
15+
"net/url"
1416
"strconv"
1517
"strings"
1618
"time"
@@ -185,6 +187,24 @@ func JsonEncodeRequestBody(reqBody any) (bytes.Buffer, error) {
185187
return buf, nil
186188
}
187189

190+
func MakeHTTPClient(proxyURL string) (*http.Client, error) {
191+
client := &http.Client{
192+
Timeout: 0, // rely on ctx; streaming can be long
193+
}
194+
if proxyURL == "" {
195+
return client, nil
196+
}
197+
198+
pURL, err := url.Parse(proxyURL)
199+
if err != nil {
200+
return nil, fmt.Errorf("invalid proxy URL: %w", err)
201+
}
202+
client.Transport = &http.Transport{
203+
Proxy: http.ProxyURL(pURL),
204+
}
205+
return client, nil
206+
}
207+
188208
func IsOpenAIReasoningModel(model string) bool {
189209
m := strings.ToLower(model)
190210
return CheckModelPrefix(m, "o1") ||

pkg/aiusechat/anthropic/anthropic-backend.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import (
1212
"io"
1313
"log"
1414
"net/http"
15-
"net/url"
1615
"strings"
1716
"time"
1817

1918
"github.com/google/uuid"
2019
"github.com/launchdarkly/eventsource"
20+
"github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
2121
"github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
2222
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
2323
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
@@ -454,18 +454,9 @@ func RunAnthropicChatStep(
454454
return nil, nil, nil, err
455455
}
456456

457-
httpClient := &http.Client{
458-
Timeout: 0, // rely on ctx; streaming can be long
459-
}
460-
// Proxy support
461-
if chatOpts.Config.ProxyURL != "" {
462-
pURL, perr := url.Parse(chatOpts.Config.ProxyURL)
463-
if perr != nil {
464-
return nil, nil, nil, fmt.Errorf("invalid proxy URL: %w", perr)
465-
}
466-
httpClient.Transport = &http.Transport{
467-
Proxy: http.ProxyURL(pURL),
468-
}
457+
httpClient, err := aiutil.MakeHTTPClient(chatOpts.Config.ProxyURL)
458+
if err != nil {
459+
return nil, nil, nil, err
469460
}
470461

471462
resp, err := httpClient.Do(req)

pkg/aiusechat/gemini/gemini-backend.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,9 @@ func RunGeminiChatStep(
231231
return nil, nil, nil, err
232232
}
233233

234-
httpClient := &http.Client{
235-
Timeout: 0, // rely on ctx; streaming can be long
234+
httpClient, err := aiutil.MakeHTTPClient(chatOpts.Config.ProxyURL)
235+
if err != nil {
236+
return nil, nil, nil, err
236237
}
237238

238239
resp, err := httpClient.Do(req)

pkg/aiusechat/openai/openai-backend.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -528,18 +528,9 @@ func RunOpenAIChatStep(
528528
return nil, nil, nil, err
529529
}
530530

531-
httpClient := &http.Client{
532-
Timeout: 0, // rely on ctx; streaming can be long
533-
}
534-
// Proxy support
535-
if chatOpts.Config.ProxyURL != "" {
536-
pURL, perr := url.Parse(chatOpts.Config.ProxyURL)
537-
if perr != nil {
538-
return nil, nil, nil, fmt.Errorf("invalid proxy URL: %w", perr)
539-
}
540-
httpClient.Transport = &http.Transport{
541-
Proxy: http.ProxyURL(pURL),
542-
}
531+
httpClient, err := aiutil.MakeHTTPClient(chatOpts.Config.ProxyURL)
532+
if err != nil {
533+
return nil, nil, nil, err
543534
}
544535

545536
resp, err := httpClient.Do(req)

pkg/aiusechat/openaichat/openaichat-backend.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/google/uuid"
1818
"github.com/launchdarkly/eventsource"
19+
"github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
1920
"github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
2021
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
2122
"github.com/wavetermdev/waveterm/pkg/web/sse"
@@ -60,7 +61,10 @@ func RunChatStep(
6061
return nil, nil, nil, err
6162
}
6263

63-
client := &http.Client{}
64+
client, err := aiutil.MakeHTTPClient(chatOpts.Config.ProxyURL)
65+
if err != nil {
66+
return nil, nil, nil, err
67+
}
6468
resp, err := client.Do(req)
6569
if err != nil {
6670
return nil, nil, nil, fmt.Errorf("request failed: %w", err)

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"`

0 commit comments

Comments
 (0)