242 lines
6.4 KiB
Go
242 lines
6.4 KiB
Go
package acp
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
"xworkmate-bridge/internal/shared"
|
|
)
|
|
|
|
type syncedProvider struct {
|
|
ProviderID string
|
|
Label string
|
|
Endpoint string
|
|
AuthorizationHeader string
|
|
Enabled bool
|
|
}
|
|
|
|
type BridgeConfig struct {
|
|
Upstream struct {
|
|
GatewayURL string `yaml:"gateway_url"`
|
|
CodexURL string `yaml:"codex_url"`
|
|
OpenCodeURL string `yaml:"opencode_url"`
|
|
GeminiURL string `yaml:"gemini_url"`
|
|
HermesURL string `yaml:"hermes_url"`
|
|
} `yaml:"upstream"`
|
|
Distributed DistributedConfig `yaml:"distributed"`
|
|
OpenClawGateway OpenClawGatewayConfig `yaml:"openclaw_gateway"`
|
|
}
|
|
|
|
type DistributedConfig struct {
|
|
TaskForwardToken string `yaml:"task_forward_token"`
|
|
Topology string `yaml:"topology"`
|
|
LocalNodeID string `yaml:"local_node_id"`
|
|
TaskForwardPeerID string `yaml:"task_forward_peer_id"`
|
|
Nodes []DistributedNodeConfig `yaml:"nodes"`
|
|
Forwarding DistributedForwardingConfig `yaml:"forwarding"`
|
|
}
|
|
|
|
type DistributedNodeConfig struct {
|
|
ID string `yaml:"id"`
|
|
Role string `yaml:"role"`
|
|
Zone string `yaml:"zone"`
|
|
PublicBaseURL string `yaml:"public_base_url"`
|
|
BridgeEndpoint string `yaml:"bridge_endpoint"`
|
|
Capabilities []string `yaml:"capabilities"`
|
|
}
|
|
|
|
type DistributedForwardingConfig struct {
|
|
HopLimit int `yaml:"hop_limit"`
|
|
DefaultAction string `yaml:"default_action"`
|
|
Rules []DistributedForwardRuleConfig `yaml:"rules"`
|
|
Routes []DistributedRouteConfig `yaml:"routes"`
|
|
}
|
|
|
|
type DistributedForwardRuleConfig struct {
|
|
Methods []string `yaml:"methods"`
|
|
Target DistributedForwardTargetConfig `yaml:"target"`
|
|
}
|
|
|
|
type DistributedForwardTargetConfig struct {
|
|
NodeID string `yaml:"node_id"`
|
|
Selector DistributedForwardSelectorConfig `yaml:"selector"`
|
|
Strategy string `yaml:"strategy"`
|
|
}
|
|
|
|
type DistributedForwardSelectorConfig struct {
|
|
Role string `yaml:"role"`
|
|
Zone string `yaml:"zone"`
|
|
Capability string `yaml:"capability"`
|
|
}
|
|
|
|
type DistributedRouteConfig struct {
|
|
TargetNodeID string `yaml:"target_node_id"`
|
|
NextHopNodeID string `yaml:"next_hop_node_id"`
|
|
}
|
|
|
|
type OpenClawGatewayConfig struct {
|
|
MaxActive *int `yaml:"max_active"`
|
|
MaxQueued *int `yaml:"max_queued"`
|
|
QueueTimeout string `yaml:"queue_timeout"`
|
|
MaxAllowedSilentDuration string `yaml:"max_allowed_silent_duration"`
|
|
}
|
|
|
|
func loadBridgeConfig() *BridgeConfig {
|
|
config := &BridgeConfig{}
|
|
configPath := shared.EnvOrDefault("BRIDGE_CONFIG_PATH", "/app/config.yaml")
|
|
|
|
if _, err := os.Stat(configPath); err == nil {
|
|
data, err := os.ReadFile(configPath)
|
|
if err == nil {
|
|
_ = yaml.Unmarshal(data, config)
|
|
}
|
|
}
|
|
return config
|
|
}
|
|
|
|
func resolveURL(yamlVal string, envKeys ...string) string {
|
|
val := strings.TrimSpace(yamlVal)
|
|
if val != "" {
|
|
return val
|
|
}
|
|
for _, key := range envKeys {
|
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func bridgeUpstreamAuthorizationHeader() string {
|
|
token := bridgePublicAuthToken()
|
|
if token != "" && !strings.HasPrefix(strings.ToLower(token), "bearer ") {
|
|
return "Bearer " + token
|
|
}
|
|
return token
|
|
}
|
|
|
|
func bridgePublicAuthToken() string {
|
|
if token := strings.TrimSpace(os.Getenv("AI_WORKSPACE_AUTH_TOKEN")); token != "" {
|
|
return token
|
|
}
|
|
return strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""))
|
|
}
|
|
|
|
func bridgeSharedAuthToken() string {
|
|
return bridgePublicAuthToken()
|
|
}
|
|
|
|
func bridgeInboundAuthTokens() []string {
|
|
var tokens []string
|
|
seen := map[string]struct{}{}
|
|
for _, token := range []string{
|
|
os.Getenv("AI_WORKSPACE_AUTH_TOKEN"),
|
|
os.Getenv("BRIDGE_AUTH_TOKEN"),
|
|
os.Getenv("BRIDGE_REVIEW_AUTH_TOKEN"),
|
|
} {
|
|
trimmed := strings.TrimSpace(token)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[trimmed]; ok {
|
|
continue
|
|
}
|
|
seen[trimmed] = struct{}{}
|
|
tokens = append(tokens, trimmed)
|
|
}
|
|
return tokens
|
|
}
|
|
|
|
func resolveDistributedTaskForwardToken(config *BridgeConfig) string {
|
|
if token := strings.TrimSpace(os.Getenv("XWORKMATE_BRIDGE_TASK_FORWARD_TOKEN")); token != "" {
|
|
return token
|
|
}
|
|
if token := strings.TrimSpace(os.Getenv("BRIDGE_TASK_FORWARD_TOKEN")); token != "" {
|
|
return token
|
|
}
|
|
if config != nil {
|
|
if token := strings.TrimSpace(config.Distributed.TaskForwardToken); token != "" {
|
|
return token
|
|
}
|
|
}
|
|
return bridgeSharedAuthToken()
|
|
}
|
|
|
|
func defaultDistributedNodes() []DistributedNodeConfig {
|
|
return []DistributedNodeConfig{
|
|
{
|
|
ID: "xworkmate-bridge",
|
|
Role: "primary",
|
|
PublicBaseURL: "https://xworkmate-bridge.svc.plus",
|
|
BridgeEndpoint: "http://172.29.10.1:8787",
|
|
},
|
|
{
|
|
ID: "cn-xworkmate-bridge",
|
|
Role: "edge",
|
|
PublicBaseURL: "https://cn-xworkmate-bridge.svc.plus",
|
|
BridgeEndpoint: "http://172.29.10.2:8787",
|
|
},
|
|
}
|
|
}
|
|
|
|
func newProductionProviderCatalog() (*BridgeConfig, map[string]syncedProvider, []string) {
|
|
return newProductionProviderCatalogFromConfig(loadBridgeConfig())
|
|
}
|
|
|
|
func newProductionProviderCatalogFromConfig(config *BridgeConfig) (*BridgeConfig, map[string]syncedProvider, []string) {
|
|
if config == nil {
|
|
config = &BridgeConfig{}
|
|
}
|
|
authorizationHeader := bridgeUpstreamAuthorizationHeader()
|
|
|
|
providers := []struct {
|
|
id string
|
|
label string
|
|
yaml string
|
|
envKeys []string
|
|
}{
|
|
{
|
|
id: "codex",
|
|
label: "Codex",
|
|
yaml: config.Upstream.CodexURL,
|
|
envKeys: []string{"CODEX_RPC_URL"},
|
|
},
|
|
{
|
|
id: "opencode",
|
|
label: "OpenCode",
|
|
yaml: config.Upstream.OpenCodeURL,
|
|
envKeys: []string{"OPENCODE_RPC_URL"},
|
|
},
|
|
{
|
|
id: "gemini",
|
|
label: "Gemini",
|
|
yaml: config.Upstream.GeminiURL,
|
|
envKeys: []string{"GEMINI_RPC_URL"},
|
|
},
|
|
{
|
|
id: "hermes",
|
|
label: "Hermes",
|
|
yaml: config.Upstream.HermesURL,
|
|
envKeys: []string{"HERMES_RPC_URL"},
|
|
},
|
|
}
|
|
|
|
catalog := make(map[string]syncedProvider)
|
|
var order []string
|
|
|
|
for _, p := range providers {
|
|
endpoint := resolveURL(p.yaml, p.envKeys...)
|
|
catalog[p.id] = syncedProvider{
|
|
ProviderID: p.id,
|
|
Label: p.label,
|
|
Endpoint: endpoint,
|
|
AuthorizationHeader: authorizationHeader,
|
|
Enabled: endpoint != "",
|
|
}
|
|
order = append(order, p.id)
|
|
}
|
|
|
|
return config, catalog, order
|
|
}
|