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.)
|
- `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.
|
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.
|
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"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -61,9 +62,24 @@ func internalKey(k string) bool {
|
|||||||
return false
|
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
|
// llmTransport is a shared HTTP transport reused across all LLM calls so that
|
||||||
// connections are pooled instead of recreated per request.
|
// connections are pooled instead of recreated per request.
|
||||||
var llmTransport = &http.Transport{
|
var llmTransport = &http.Transport{
|
||||||
|
Proxy: proxyFromEnv,
|
||||||
DialContext: (&net.Dialer{Timeout: 300 * time.Second}).DialContext,
|
DialContext: (&net.Dialer{Timeout: 300 * time.Second}).DialContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +93,13 @@ func atoiD(s string, d int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func queryModelsContextWindow(cfg *Cfg) 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)
|
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
||||||
if err != nil { return 0 }
|
if err != nil { return 0 }
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Bantam/1.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) {
|
func listModels(cfg *Cfg) (string, error) {
|
||||||
t := cfg.Timeout
|
t := cfg.Timeout
|
||||||
if t < 10 { t = 10 }
|
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)
|
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
||||||
if err != nil { return "", err }
|
if err != nil { return "", err }
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Bantam/1.0)")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Bantam/1.0)")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@@ -2199,3 +2200,82 @@ func TestFilterTextPrintableOnly(t *testing.T) {
|
|||||||
t.Errorf("filterText = %q, want %q", got, "ok\t\nab")
|
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