diff --git a/agentteams-controller/internal/initializer/initializer.go b/agentteams-controller/internal/initializer/initializer.go index c4fbd9dee..0ab0aea6a 100644 --- a/agentteams-controller/internal/initializer/initializer.go +++ b/agentteams-controller/internal/initializer/initializer.go @@ -362,7 +362,7 @@ func (i *Initializer) initGatewayRoutes(ctx context.Context) error { time.Sleep(2 * time.Second) raw := map[string]interface{}{ "hiclawMode": true, - "openaiCustomUrl": cfg.OpenAIBaseURL, + "openaiCustomUrl": customOpenAIURL(cfg.OpenAIBaseURL), "openaiCustomServiceName": "openai-compat.dns", "openaiCustomServicePort": port, } @@ -396,7 +396,7 @@ func (i *Initializer) initGatewayRoutes(ctx context.Context) error { time.Sleep(2 * time.Second) raw := map[string]interface{}{ "hiclawMode": true, - "openaiCustomUrl": cfg.OpenAIBaseURL, + "openaiCustomUrl": customOpenAIURL(cfg.OpenAIBaseURL), "openaiCustomServiceName": provider + ".dns", "openaiCustomServicePort": port, } @@ -448,6 +448,25 @@ func (i *Initializer) initGatewayRoutes(ctx context.Context) error { return nil } +// customOpenAIURL returns the base URL with any port stripped while preserving +// everything else (escaped path, query string, fragment). Higress derives the +// upstream host domain from openaiCustomUrl, so an embedded port would be +// treated as part of the host and break DNS resolution. The port is supplied +// separately via openaiCustomServicePort. +func customOpenAIURL(rawURL string) string { + u, err := url.Parse(rawURL) + if err != nil || u.Hostname() == "" { + return rawURL + } + host := u.Hostname() + if strings.Contains(host, ":") { + // Re-bracket IPv6 literals so the reconstructed URL stays valid. + host = "[" + host + "]" + } + u.Host = host + return u.String() +} + // parseHostPort extracts host and port from a URL like "http://host:port". func parseHostPort(rawURL string) (string, int, error) { u, err := url.Parse(rawURL) diff --git a/agentteams-controller/internal/initializer/initializer_test.go b/agentteams-controller/internal/initializer/initializer_test.go new file mode 100644 index 000000000..7e7b9fee4 --- /dev/null +++ b/agentteams-controller/internal/initializer/initializer_test.go @@ -0,0 +1,28 @@ +package initializer + +import "testing" + +func TestCustomOpenAIURL(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {"ip port with path", "http://10.43.46.12:3000/v1", "http://10.43.46.12/v1"}, + {"ip port no path", "http://10.43.46.12:3000", "http://10.43.46.12"}, + {"https port with path", "https://example.com:8443/v1", "https://example.com/v1"}, + {"no port keeps url", "https://api.openai.com/v1", "https://api.openai.com/v1"}, + {"query string preserved", "http://10.43.46.12:3000/v1?api-version=2024-05", "http://10.43.46.12/v1?api-version=2024-05"}, + {"escaped path preserved", "http://10.43.46.12:3000/v1/models%2Ffoo", "http://10.43.46.12/v1/models%2Ffoo"}, + {"ipv6 port with path", "http://[2001:db8::1]:3000/v1", "http://[2001:db8::1]/v1"}, + {"ipv6 no port keeps brackets", "http://[2001:db8::1]/v1", "http://[2001:db8::1]/v1"}, + {"unparseable returns input", "://bad", "://bad"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := customOpenAIURL(tc.in); got != tc.want { + t.Fatalf("customOpenAIURL(%q) = %q, want %q", tc.in, got, tc.want) + } + }) + } +} diff --git a/changelog/current.md b/changelog/current.md index 8c557fe82..8e87b43b8 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -7,6 +7,7 @@ Record image-affecting changes to `manager/`, `worker/`, `copaw/`, `hermes/`, `o **Bug Fixes** - **CoPaw Team coordination routing**: Route Team Leader worker assignments sent through the `message` tool from Leader DM to Team Room, matching the Matrix channel send path. ([92c8145](https://github.com/agentscope-ai/AgentTeams/commit/92c8145)) +- **openai-compat provider with IP:port base URL**: Strip the port from `openaiCustomUrl` in the controller initializer and `setup-higress.sh`, so Higress derives the host domain without the port. Previously a base URL like `http://10.43.46.12:3000/v1` produced host domain `10.43.46.12:3000`, breaking DNS resolution; the port is now supplied only via `openaiCustomServicePort`. (#1057) **Branding and Compatibility** diff --git a/manager/scripts/init/setup-higress.sh b/manager/scripts/init/setup-higress.sh index 3447d397b..07858a260 100755 --- a/manager/scripts/init/setup-higress.sh +++ b/manager/scripts/init/setup-higress.sh @@ -209,6 +209,15 @@ if [ -n "${AGENTTEAMS_LLM_API_KEY}" ]; then OC_DOMAIN="${OC_URL_STRIP%%/*}" echo "${OC_DOMAIN}" | grep -q ':' && { OC_PORT="${OC_DOMAIN##*:}"; OC_DOMAIN="${OC_DOMAIN%:*}"; } + # Build openaiCustomUrl WITHOUT the port: Higress derives the + # host domain from this URL, so an embedded port would break + # DNS resolution. The port is supplied via openaiCustomServicePort. + OC_PATH="" + case "${OC_URL_STRIP}" in + */*) OC_PATH="/${OC_URL_STRIP#*/}" ;; + esac + OC_CUSTOM_URL="${OC_PROTO}://${OC_DOMAIN}${OC_PATH}" + # Service source: GET → PUT if exists, POST if not existing_svc=$(higress_get /v1/service-sources/openai-compat) SVC_BODY='{"type":"dns","name":"openai-compat","port":'"${OC_PORT}"',"protocol":"'"${OC_PROTO}"'","proxyName":"","domain":"'"${OC_DOMAIN}"'"}' @@ -218,7 +227,7 @@ if [ -n "${AGENTTEAMS_LLM_API_KEY}" ]; then higress_api POST /v1/service-sources "Registering openai-compat DNS service source" "${SVC_BODY}" fi - PROVIDER_BODY='{"type":"openai","name":"openai-compat","tokens":["'"${AGENTTEAMS_LLM_API_KEY}"'"],"version":0,"protocol":"openai/v1","tokenFailoverConfig":{"enabled":false},"rawConfigs":{"openaiCustomUrl":"'"${OPENAI_BASE_URL}"'","openaiCustomServiceName":"openai-compat.dns","openaiCustomServicePort":'"${OC_PORT}"',"hiclawMode":true}}' + PROVIDER_BODY='{"type":"openai","name":"openai-compat","tokens":["'"${AGENTTEAMS_LLM_API_KEY}"'"],"version":0,"protocol":"openai/v1","tokenFailoverConfig":{"enabled":false},"rawConfigs":{"openaiCustomUrl":"'"${OC_CUSTOM_URL}"'","openaiCustomServiceName":"openai-compat.dns","openaiCustomServicePort":'"${OC_PORT}"',"hiclawMode":true}}' existing_provider=$(higress_get /v1/ai/providers/openai-compat) if [ -n "${existing_provider}" ]; then higress_api PUT /v1/ai/providers/openai-compat "Updating LLM provider (openai-compat)" "${PROVIDER_BODY}"