socks5 support
This commit is contained in:
@@ -153,6 +153,8 @@ If the API rejects the request with an `Invalid assistant message: content or to
|
||||
|
||||
- `bantam_tools_dir` (optional path to a directory of extra shell tools; the Go port appends `"Extra shell tools can be found at <dir>"` to the system prompt at startup when set. The `BANTAM_TOOLS_DIR` environment variable overrides this and is checked first; if neither is set, nothing is appended. Note: this is an agent-internal hint, not forwarded to the API.)
|
||||
|
||||
The Go port also supports SOCKS5 proxying via the `SOCKS_PROXY` (or `socks_proxy`) environment variable (e.g. `SOCKS_PROXY=socks5://127.0.0.1:1080` or `SOCKS_PROXY=127.0.0.1:1080`), falling back to standard `HTTP_PROXY` / `HTTPS_PROXY` environment variables.
|
||||
|
||||
The Go port's built-in editor tracks the cursor with its own column math (terminal auto-wrap aware) and redraws from the first line of the buffer, so wrapped input stays clean at any terminal width.
|
||||
|
||||
When enabled, the interactive console uses a subtle ANSI palette: the pending-request status `...requesting...` is darkened bold, reasoning markers are cyan, reasoning text is dim, `[tool call: ...]` traces are yellow, `[tool result: ...]` headers are green, tool result bodies are dim (red for tool errors/unknown tools), and errors/network retries are red. Tool result payloads fed back to the LLM are never colored.
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@@ -61,9 +62,24 @@ func internalKey(k string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func proxyFromEnv(req *http.Request) (*url.URL, error) {
|
||||
socks := strings.TrimSpace(os.Getenv("SOCKS_PROXY"))
|
||||
if socks == "" {
|
||||
socks = strings.TrimSpace(os.Getenv("socks_proxy"))
|
||||
}
|
||||
if socks != "" {
|
||||
if !strings.Contains(socks, "://") {
|
||||
socks = "socks5://" + socks
|
||||
}
|
||||
return url.Parse(socks)
|
||||
}
|
||||
return http.ProxyFromEnvironment(req)
|
||||
}
|
||||
|
||||
// llmTransport is a shared HTTP transport reused across all LLM calls so that
|
||||
// connections are pooled instead of recreated per request.
|
||||
var llmTransport = &http.Transport{
|
||||
Proxy: proxyFromEnv,
|
||||
DialContext: (&net.Dialer{Timeout: 300 * time.Second}).DialContext,
|
||||
}
|
||||
|
||||
@@ -77,7 +93,13 @@ func atoiD(s string, d int) int {
|
||||
}
|
||||
|
||||
func queryModelsContextWindow(cfg *Cfg) int {
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: proxyFromEnv,
|
||||
DialContext: (&net.Dialer{Timeout: 3 * time.Second}).DialContext,
|
||||
},
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
||||
if err != nil { return 0 }
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Bantam/1.0)")
|
||||
@@ -143,7 +165,13 @@ func fetchContextWindow(cfg *Cfg) int {
|
||||
func listModels(cfg *Cfg) (string, error) {
|
||||
t := cfg.Timeout
|
||||
if t < 10 { t = 10 }
|
||||
client := &http.Client{Timeout: time.Duration(t) * time.Second}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: proxyFromEnv,
|
||||
DialContext: (&net.Dialer{Timeout: time.Duration(t) * time.Second}).DialContext,
|
||||
},
|
||||
Timeout: time.Duration(t) * time.Second,
|
||||
}
|
||||
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
||||
if err != nil { return "", err }
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Bantam/1.0)")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -2199,3 +2200,82 @@ func TestFilterTextPrintableOnly(t *testing.T) {
|
||||
t.Errorf("filterText = %q, want %q", got, "ok\t\nab")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyFromEnv(t *testing.T) {
|
||||
os.Unsetenv("SOCKS_PROXY")
|
||||
os.Unsetenv("socks_proxy")
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
_, err := proxyFromEnv(req)
|
||||
if err != nil {
|
||||
t.Fatalf("proxyFromEnv err: %v", err)
|
||||
}
|
||||
|
||||
os.Setenv("SOCKS_PROXY", "127.0.0.1:1080")
|
||||
defer os.Unsetenv("SOCKS_PROXY")
|
||||
u, err := proxyFromEnv(req)
|
||||
if err != nil {
|
||||
t.Fatalf("proxyFromEnv with 127.0.0.1:1080: %v", err)
|
||||
}
|
||||
if u == nil || u.Scheme != "socks5" || u.Host != "127.0.0.1:1080" {
|
||||
t.Fatalf("unexpected url: %v", u)
|
||||
}
|
||||
|
||||
os.Setenv("SOCKS_PROXY", "socks5://localhost:9050")
|
||||
u, err = proxyFromEnv(req)
|
||||
if err != nil {
|
||||
t.Fatalf("proxyFromEnv with socks5://: %v", err)
|
||||
}
|
||||
if u == nil || u.Scheme != "socks5" || u.Host != "localhost:9050" {
|
||||
t.Fatalf("unexpected url: %v", u)
|
||||
}
|
||||
|
||||
os.Unsetenv("SOCKS_PROXY")
|
||||
os.Setenv("socks_proxy", "socks5h://user:pass@127.0.0.1:1080")
|
||||
defer os.Unsetenv("socks_proxy")
|
||||
u, err = proxyFromEnv(req)
|
||||
if err != nil {
|
||||
t.Fatalf("proxyFromEnv with socks_proxy: %v", err)
|
||||
}
|
||||
if u == nil || u.Scheme != "socks5h" || u.User.Username() != "user" {
|
||||
t.Fatalf("unexpected url: %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSOCKS5ProxySupport(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
handshakeDone := make(chan bool, 1)
|
||||
go func() {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
buf := make([]byte, 256)
|
||||
n, err := conn.Read(buf)
|
||||
if err == nil && n >= 2 && buf[0] == 0x05 {
|
||||
conn.Write([]byte{0x05, 0x00})
|
||||
handshakeDone <- true
|
||||
}
|
||||
}()
|
||||
|
||||
os.Setenv("SOCKS_PROXY", "socks5://"+l.Addr().String())
|
||||
defer os.Unsetenv("SOCKS_PROXY")
|
||||
|
||||
tr := &http.Transport{
|
||||
Proxy: proxyFromEnv,
|
||||
DialContext: (&net.Dialer{Timeout: 1 * time.Second}).DialContext,
|
||||
}
|
||||
client := &http.Client{Transport: tr, Timeout: 1 * time.Second}
|
||||
client.Get("http://example.com/test")
|
||||
|
||||
select {
|
||||
case <-handshakeDone:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatalf("timed out waiting for SOCKS5 handshake through proxy")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user