mirror of
https://github.com/alibaba/higress.git
synced 2026-02-06 23:21:08 +08:00
feat(wasm-plugin): add jsonrpc-converter plugin (#2805)
This commit is contained in:
35
plugins/wasm-go/extensions/jsonrpc-converter/go.mod
Normal file
35
plugins/wasm-go/extensions/jsonrpc-converter/go.mod
Normal file
@@ -0,0 +1,35 @@
|
||||
module jsonrpc-converter
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/resp v0.1.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
75
plugins/wasm-go/extensions/jsonrpc-converter/go.sum
Normal file
75
plugins/wasm-go/extensions/jsonrpc-converter/go.sum
Normal file
@@ -0,0 +1,75 @@
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b h1:rRI9+ThQbe+nw4jUiYEyOFaREkXCMMW9k1X2gy2d6pE=
|
||||
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b/go.mod h1:rU3M+Tq5VrQOo0dxpKHGb03Ty0sdWIZfAH+YCOACx/Y=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1:xqmtTZI0JQ2O+Lg9/CE6c+Tw9KD6FnvWw8EpLVuuvfg=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250724062140-49cd26dcf15a h1:WCnqxeHgvV5LZvPx37EBWi1vXQ3Aw5ldFezZlG4l5Zk=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250724062140-49cd26dcf15a/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 h1:2wlbNpFJCQNbPBFYgswz7Zvxo9O3L0PH0AJxwiCc5lk=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
|
||||
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
271
plugins/wasm-go/extensions/jsonrpc-converter/main.go
Normal file
271
plugins/wasm-go/extensions/jsonrpc-converter/main.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/higress-group/wasm-go/pkg/log"
|
||||
"github.com/higress-group/wasm-go/pkg/mcp"
|
||||
"github.com/higress-group/wasm-go/pkg/mcp/utils"
|
||||
"github.com/higress-group/wasm-go/pkg/wrapper"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
const (
|
||||
JsonRpcId = "x-envoy-jsonrpc-id"
|
||||
JsonRpcMethod = "x-envoy-jsonrpc-method"
|
||||
JsonRpcParams = "x-envoy-jsonrpc-params"
|
||||
JsonRpcResult = "x-envoy-jsonrpc-result"
|
||||
JsonRpcError = "x-envoy-jsonrpc-error"
|
||||
McpToolName = "x-envoy-mcp-tool-name"
|
||||
McpToolArguments = "x-envoy-mcp-tool-arguments"
|
||||
McpToolResponse = "x-envoy-mcp-tool-response"
|
||||
McpToolError = "x-envoy-mcp-tool-error"
|
||||
|
||||
DefaultMaxHeaderLength = 4000 // default max length for truncation
|
||||
MethodToolList = "tools/list" // default method for tool list
|
||||
MethodToolCall = "tools/call" // default method for tool call
|
||||
)
|
||||
|
||||
type ProcessStage string
|
||||
|
||||
const (
|
||||
ProcessRequest ProcessStage = "request"
|
||||
ProcessResponse ProcessStage = "response"
|
||||
)
|
||||
|
||||
type McpConverterConfig struct {
|
||||
Stage ProcessStage `json:"stage"`
|
||||
MaxHeaderLength int `json:"max_header_length,omitempty"`
|
||||
AllowedMethods []string `json:"allowed_methods,omitempty"` // optional, for future use
|
||||
}
|
||||
|
||||
func init() {
|
||||
mcp.LoadMCPFilter(
|
||||
mcp.FilterName("jsonrpc-converter"),
|
||||
mcp.SetConfigParser(parseConfig),
|
||||
mcp.SetJsonRpcRequestFilter(processJsonRpcRequest),
|
||||
mcp.SetJsonRpcResponseFilter(processJsonRpcResponse),
|
||||
mcp.SetToolListResponseFilter(processToolListResponse),
|
||||
mcp.SetToolCallRequestFilter(processToolCallRequest),
|
||||
mcp.SetToolCallResponseFilter(processToolCallResponse),
|
||||
)
|
||||
mcp.InitMCPFilter()
|
||||
}
|
||||
|
||||
func parseConfig(configBytes []byte, filterConfig *any) error {
|
||||
var config McpConverterConfig
|
||||
if err := json.Unmarshal(configBytes, &config); err != nil {
|
||||
return fmt.Errorf("failed to parse mcp-converter config: %v", err)
|
||||
}
|
||||
// validate stage
|
||||
if config.Stage != ProcessRequest && config.Stage != ProcessResponse {
|
||||
return fmt.Errorf("invalid mcp-converter stage: %s, must be 'request' or 'response'", config.Stage)
|
||||
}
|
||||
// validate length
|
||||
if config.MaxHeaderLength <= 0 {
|
||||
config.MaxHeaderLength = DefaultMaxHeaderLength
|
||||
}
|
||||
// validate allowed methods
|
||||
if len(config.AllowedMethods) == 0 {
|
||||
config.AllowedMethods = []string{MethodToolList, MethodToolCall}
|
||||
}
|
||||
log.Infof("MCP Converter config parsed successfully, stage: %s", config.Stage)
|
||||
*filterConfig = config
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPreRequestStage(config any) bool {
|
||||
return config.(McpConverterConfig).Stage == ProcessRequest
|
||||
}
|
||||
|
||||
func isPreResponseStage(config any) bool {
|
||||
return config.(McpConverterConfig).Stage == ProcessResponse
|
||||
}
|
||||
|
||||
func isMethodAllowed(config any, method string) bool {
|
||||
allowedMethods := config.(McpConverterConfig).AllowedMethods
|
||||
return slices.Contains(allowedMethods, method)
|
||||
}
|
||||
|
||||
// Remove jsonrpc headers
|
||||
func removeJsonRpcHeaders(isRequest bool) {
|
||||
headersToRemove := []string{
|
||||
JsonRpcId,
|
||||
JsonRpcMethod,
|
||||
JsonRpcParams,
|
||||
JsonRpcResult,
|
||||
McpToolName,
|
||||
McpToolArguments,
|
||||
McpToolResponse,
|
||||
McpToolError,
|
||||
}
|
||||
for _, header := range headersToRemove {
|
||||
var err error
|
||||
if isRequest {
|
||||
err = proxywasm.RemoveHttpRequestHeader(header)
|
||||
} else {
|
||||
err = proxywasm.RemoveHttpResponseHeader(header)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("failed to remove header %s: %v", header, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert jsonrpc headers
|
||||
func insertJsonRpcHeaders(isRequest bool, config any, name string, value string) {
|
||||
if value == "" {
|
||||
log.Debugf("Skipping insertion of empty header %s", name)
|
||||
return
|
||||
}
|
||||
truncatedValue := truncateString(value, config)
|
||||
var err error
|
||||
if isRequest {
|
||||
err = proxywasm.ReplaceHttpRequestHeader(name, truncatedValue)
|
||||
} else {
|
||||
err = proxywasm.ReplaceHttpResponseHeader(name, truncatedValue)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("failed to insert header %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func printHeaders(stage ProcessStage, s string) {
|
||||
var err error
|
||||
var headersNow any
|
||||
switch stage {
|
||||
case ProcessRequest:
|
||||
headersNow, err = proxywasm.GetHttpRequestHeaders()
|
||||
case ProcessResponse:
|
||||
headersNow, err = proxywasm.GetHttpResponseHeaders()
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("PrintHeaders %s: failed to get request headers: %v", s, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("PrintHeaders %s: %v", s, headersNow)
|
||||
}
|
||||
|
||||
// truncates a string to a maximum length of 4000 characters.
|
||||
func truncateString(s string, config any) string {
|
||||
length := config.(McpConverterConfig).MaxHeaderLength
|
||||
if len(s) <= length {
|
||||
return s
|
||||
}
|
||||
prefix := s[:length/2]
|
||||
suffix := s[len(s)-length/2:]
|
||||
|
||||
return fmt.Sprintf("%s...(truncated)...%s", prefix, suffix)
|
||||
}
|
||||
|
||||
func processJsonRpcRequest(context wrapper.HttpContext, config any, id utils.JsonRpcID, method string, params gjson.Result, rawBody []byte) types.Action {
|
||||
if isPreResponseStage(config) {
|
||||
// pre-response removes request headers, which are added by pre-request
|
||||
removeJsonRpcHeaders(true)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
if !isMethodAllowed(config, method) {
|
||||
log.Debugf("[JsonRpcRequest] Method %s is not allowed, skipping processing", method)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// Set common headers, JsonRpcId, JsonRpcMethod
|
||||
insertJsonRpcHeaders(true, config, JsonRpcId, id.StringValue)
|
||||
insertJsonRpcHeaders(true, config, JsonRpcMethod, method)
|
||||
|
||||
// Set other headers based on the method
|
||||
// For MethodToolCall, we set the params in processToolCallRequest
|
||||
if method != MethodToolCall {
|
||||
// JsonRpcParams
|
||||
insertJsonRpcHeaders(true, config, JsonRpcParams, params.String())
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func processJsonRpcResponse(context wrapper.HttpContext, config any, id utils.JsonRpcID, result, error gjson.Result, rawBody []byte) types.Action {
|
||||
if isPreRequestStage(config) {
|
||||
// pre-request removes response headers, which are added by pre-response
|
||||
removeJsonRpcHeaders(false)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
method := context.GetStringContext("JSONRPC_METHOD", "")
|
||||
if !isMethodAllowed(config, method) {
|
||||
log.Debugf("[JsonRpcResponse] Method %s is not allowed, skipping processing", method)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// Set common headers, JsonRpcId, JsonRpcMethod
|
||||
insertJsonRpcHeaders(false, config, JsonRpcId, id.StringValue)
|
||||
insertJsonRpcHeaders(false, config, JsonRpcMethod, method)
|
||||
|
||||
// Set other headers based on the method
|
||||
// For MethodToolList & MethodToolCall, we set the params in processToolCallResponse and processToolListResponse
|
||||
if method != MethodToolList && method != MethodToolCall {
|
||||
// JsonRpcResult
|
||||
insertJsonRpcHeaders(false, config, JsonRpcResult, result.String())
|
||||
// JsonRpcError
|
||||
insertJsonRpcHeaders(false, config, JsonRpcError, error.String())
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func processToolListResponse(ctx wrapper.HttpContext, config any, tools gjson.Result, rawBody []byte) types.Action {
|
||||
if isPreRequestStage(config) {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
if !isMethodAllowed(config, MethodToolList) {
|
||||
log.Debugf("[ToolListResponse] Method %s is not allowed, skipping processing", MethodToolList)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// JsonRpcResult
|
||||
insertJsonRpcHeaders(false, config, JsonRpcResult, tools.String())
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func processToolCallRequest(ctx wrapper.HttpContext, config any, toolName string, toolArgs gjson.Result, rawBody []byte) types.Action {
|
||||
if isPreResponseStage(config) {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
if !isMethodAllowed(config, MethodToolCall) {
|
||||
log.Debugf("[ToolCallRequest] Method %s is not allowed, skipping processing", MethodToolCall)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// McpToolName, McpToolArguments
|
||||
insertJsonRpcHeaders(true, config, McpToolName, toolName)
|
||||
insertJsonRpcHeaders(true, config, McpToolArguments, toolArgs.String())
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func processToolCallResponse(ctx wrapper.HttpContext, config any, isError bool, content gjson.Result, rawBody []byte) types.Action {
|
||||
if isPreRequestStage(config) {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
if !isMethodAllowed(config, MethodToolCall) {
|
||||
log.Debugf("[ToolCallResponse] Method %s is not allowed, skipping processing", MethodToolCall)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// McpToolResponse, McpToolError
|
||||
insertJsonRpcHeaders(false, config, McpToolResponse, content.String())
|
||||
insertJsonRpcHeaders(false, config, McpToolError, strconv.FormatBool(isError))
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
28
plugins/wasm-go/extensions/jsonrpc-converter/main_test.go
Normal file
28
plugins/wasm-go/extensions/jsonrpc-converter/main_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTruncateString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
maxLen int
|
||||
expected string
|
||||
}{
|
||||
{"Short String", "Higress Is an AI-Native API Gateway", 1000, "Higress Is an AI-Native API Gateway"},
|
||||
{"Exact Length", "Higress Is an AI-Native API Gateway", 35, "Higress Is an AI-Native API Gateway"},
|
||||
{"Truncated String", "Higress Is an AI-Native API Gateway", 20, "Higress Is...(truncated)...PI Gateway"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := McpConverterConfig{MaxHeaderLength: tt.maxLen}
|
||||
result := truncateString(tt.input, config)
|
||||
if result != tt.expected {
|
||||
t.Errorf("truncateString(%q, %d) = %q; want %q", tt.input, tt.maxLen, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250807064511-eb1cd98e1f57
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/tidwall/sjson v1.2.5
|
||||
)
|
||||
|
||||
@@ -24,6 +24,8 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250807064511-eb1cd98e1f57 h1:WhNdnKSDtAQrh4Yil8HAtbl7VW+WC85m7WS8kirnHAA=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250807064511-eb1cd98e1f57/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 h1:2wlbNpFJCQNbPBFYgswz7Zvxo9O3L0PH0AJxwiCc5lk=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
|
||||
@@ -100,7 +100,7 @@ func ProcessRequest(context wrapper.HttpContext, config any, toolName string, to
|
||||
if !routerConfig.enable {
|
||||
return types.ActionContinue
|
||||
}
|
||||
// Extract server name from tool name (format: "${serverName}HigressRouteTo${toolName}")
|
||||
// Extract server name from tool name (format: "${serverName}___${toolName}")
|
||||
parts := strings.SplitN(toolName, consts.ToolSetNameSplitter, 2)
|
||||
if len(parts) != 2 {
|
||||
log.Debugf("Tool name '%s' does not contain server prefix, continuing without routing", toolName)
|
||||
|
||||
@@ -8,7 +8,7 @@ replace amap-tools => ../amap-tools
|
||||
|
||||
require (
|
||||
amap-tools v0.0.0-00010101000000-000000000000
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819032348-2591c1c2476d
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2
|
||||
quark-search v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819032348-2591c1c2476d h1:9XEF+IifLNcEqRTPQc9o6zqVEO6NXzjHUWM6S3qpbss=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819032348-2591c1c2476d/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 h1:2wlbNpFJCQNbPBFYgswz7Zvxo9O3L0PH0AJxwiCc5lk=
|
||||
github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
|
||||
Reference in New Issue
Block a user