Compare commits

...

81 Commits

Author SHA1 Message Date
EricaLiu
020b5f3984 Add security schema for nacos mcp (#2847) 2025-09-01 15:23:04 +08:00
澄潭
9a12f0b593 rel: Release v2.1.7 (#2849) 2025-09-01 15:22:17 +08:00
韩贤涛
7e74eeb333 feat(wasm-plugin): add tests and docs for hmac-auth-apisix (#2842) 2025-09-01 14:07:57 +08:00
澄潭
fff5903007 feat: add MCP SSE stateful session load balancer support (#2818) 2025-08-28 20:52:07 +08:00
Jingze
a00b810be5 feat(wasm-go): add wasm go plugin unit test and ci workflow (#2809) 2025-08-28 20:02:03 +08:00
澄潭
3e0a5f02a7 feat(wasm-plugin): add jsonrpc-converter plugin (#2805) 2025-08-28 19:28:37 +08:00
澄潭
44c33617fa feat(ai-proxy): add OpenRouter provider support (#2823) 2025-08-28 19:26:21 +08:00
韩贤涛
b2ffeff7b8 feat(wasm-plugin): add hmac-auth-apisix plugin (#2815) 2025-08-26 21:10:43 +08:00
Asnowww
c0ddbccbfe chore: fix typos (#2816) 2025-08-26 14:41:05 +08:00
澄潭
16a18c6609 feat(ai-proxy): add auto protocol compatibility for OpenAI and Claude APIs (#2810) 2025-08-25 14:13:51 +08:00
Xijun Dai
72b98ab6cf feat(ai-proxy): add anthropic/v1/messages and openai/v1/models support for DeepSeek (#2808)
Signed-off-by: Xijun Dai <daixijun1990@gmail.com>
2025-08-21 17:40:13 +08:00
xingpiaoliang
df20472f7b fix(wasm-go-build): correct the build command (#2799) 2025-08-21 09:47:30 +08:00
co63oc
9186b5505d chore: fix typos (#2770) 2025-08-20 10:38:03 +08:00
co63oc
eaea782693 fix RegisteTickFunc (#2787) 2025-08-19 20:53:53 +08:00
rinfx
890a802481 feat(ai-proxy): bedrock support tool use (#2730) 2025-08-19 16:54:50 +08:00
github-actions[bot]
bb69a1d50b Update CRD file in the helm folder (#2769)
Co-authored-by: CH3CHO <2909796+CH3CHO@users.noreply.github.com>
2025-08-19 16:54:27 +08:00
zat366
5a023512fa feat(mcp-server): update the dependency github.com/higress-group/wasm-go to support MCP response images (#2788) 2025-08-19 16:52:14 +08:00
Kent Dong
47f0478ef5 fix: Remove "accept-encoding" header for mcp-sse upstreams (#2786) 2025-08-19 15:53:49 +08:00
Kent Dong
c9fa8d15db chore: Restructure the path-to-api-name mapping logic in ai-proxy (#2773) 2025-08-18 19:05:23 +08:00
Kent Dong
0f1afcdcca fix(ai-proxy): Do not change the configured components of Azure URL (#2782) 2025-08-18 16:27:25 +08:00
StarryNight
19d1548971 update ai-prompt-decorator to new plugin wrapper api (#2777) 2025-08-18 11:01:35 +08:00
Kent Dong
24dca0455e fix: Fix bugs in the bedrock model name escaping logic (#2663)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-08-15 17:40:13 +08:00
澄潭
be603af461 Update CODEOWNERS 2025-08-15 16:53:31 +08:00
澄潭
8796c6040f Update CODEOWNERS 2025-08-15 16:52:45 +08:00
Kent Dong
15edc79fb3 fix: Fix the malfunction of _match_service_ rules in C++ Wasm plugins (#2723) 2025-08-15 16:47:30 +08:00
Kent Dong
5822868f87 feat: Support adding a proxy server in between when forwarding requests to upstream (#2710)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-08-15 16:15:42 +08:00
澄潭
995bcc2168 feat(transformer): Add split and retain strategy for dedupe (#2761) 2025-08-15 15:21:13 +08:00
Kent Dong
a3310f1a3b fix: Allow duplicated items in the IP list of ip-restriction config (#2755)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-08-15 13:39:26 +08:00
Jingze
0bb934073a fix(golang-filter): fix mcp server contruct envoy filter unit test (#2757) 2025-08-13 17:43:15 +08:00
Jingze
247de6a349 fix(golang-filter): fix bug of stop and buffer in decode data (#2754) 2025-08-13 11:37:01 +08:00
co63oc
79b3b23aab Fix typos (#2628)
Signed-off-by: co63oc <co63oc@users.noreply.github.com>
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
Co-authored-by: xingpiaoliang <erasernoobx@outlook.com>
2025-08-12 15:58:32 +08:00
Jingze
b9d6343efa fix(ip-restriction): fix bug of set ip_source_type (#2743) 2025-08-11 18:47:48 +08:00
xingpiaoliang
0af00bef6b feat(ai-proxy): gemini model multimodal support (#2698) 2025-08-11 15:54:34 +08:00
Kent Dong
953b95cf92 chore: Update some log levels in ai-statistics (#2740) 2025-08-11 13:59:01 +08:00
aias00
a76808171f feat: improve ai statistic plugin (#2671) 2025-08-11 13:43:00 +08:00
rinfx
f7813df1d7 add value length limit for ai statistics, truncate when over limit (#2729) 2025-08-11 11:12:03 +08:00
WeixinX
33ce18df5a feat(wasm-go): add field reroute to disable route reselection (#2739) 2025-08-11 09:37:35 +08:00
aias00
a1bf1ff009 feat(provider): add support for Grok provider in AI proxy (#2713)
Co-authored-by: 韩贤涛 <601803023@qq.com>
2025-08-07 17:22:47 +08:00
澄潭
b69e3a8f30 Deprecate the use of slash as a concatenation character for mcp server and tool, to avoid non-compliance with function naming conventions. (#2711) 2025-08-07 15:18:31 +08:00
NOBODY
5ee878198c feat(ai-proxy): gemini model thinking support (#2712) 2025-08-06 06:50:46 +08:00
rinfx
943fda0a9c AI security streaming (#2696) 2025-08-04 20:47:18 +08:00
WeixinX
abc31169a2 fix(wasm-go): transformer performs an add op when the replace key does not exist (#2706) 2025-08-04 20:38:29 +08:00
韩贤涛
5f65b4f5b0 feat: Rust WASM supports Redis database configuration option (#2704) 2025-08-03 12:56:27 +08:00
澄潭
645646fe22 Fix the issue where AI route fallback does not work when using Bedrock. (#2653) 2025-07-31 20:16:16 +08:00
澄潭
4acb65cc67 Update README_ZH.md 2025-07-31 14:26:07 +08:00
github-actions[bot]
e63a2e0251 Add release notes (#2693)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-07-31 14:23:54 +08:00
澄潭
d98f8b8b21 release v2.1.6 (#2688) 2025-07-30 21:52:51 +08:00
aias00
bd19a5049b feat(rust): improve rust readme (#2668) 2025-07-30 21:22:50 +08:00
aias00
1070541f1d fix(doc): fix some dead link (#2675) 2025-07-30 21:22:15 +08:00
lvshui
32b5c89c17 fix: postgres to mcp-server tools: describeTable fail (#2687) 2025-07-30 21:18:56 +08:00
shaoyin.zj
bd1101d711 feat(mcp): Get ErrorResponseTemplate from nacos mcp registry (#2650) 2025-07-30 21:18:37 +08:00
xingpiaoliang
27680223b9 feat(ingress/annotation): support external service in the mirror annotations (#2679) 2025-07-30 21:15:35 +08:00
hongzhouzi
93ea5e7355 fix: Error compiling golang-filter.so on arm64 machine (#2494) (#2507)
Signed-off-by: hongzhouzi <weihongzhou.whz@alibaba-inc.com>
2025-07-30 13:42:31 +08:00
澄潭
ff9a29c5d9 opt: mcp endpoint parser improvements (#2673) 2025-07-29 12:41:29 +08:00
韩贤涛
6a1557f6ac feat: ai-token-ratelimit support setting global rate limit thresholds for routes​ (#2667) 2025-07-28 08:14:35 +08:00
johnlanni
e6e4193679 update envoy commit 2025-07-25 20:54:28 +08:00
澄潭
978d0afb63 envoy: fix proxy-wasm memleak&ppv2 port mapping (#2662) 2025-07-25 20:24:41 +08:00
澄潭
39dd4538c9 opt:relax dns service domain validation (#2661) 2025-07-25 20:15:54 +08:00
co63oc
f826d79109 fix typos (#2656)
Signed-off-by: co63oc <co63oc@users.noreply.github.com>
2025-07-25 16:18:39 +08:00
Kent Dong
7348c265b5 feat: Support model mapping and more URL configuration formats for Azure OpenAI (#2649) 2025-07-25 11:28:02 +08:00
OxalisCu
ea0bf7c1b7 feat(ai-proxy): support first byte timeout for llm streaming requests (#2652)
Signed-off-by: OxalisCu <2127298698@qq.com>
2025-07-23 21:49:42 +08:00
Xijun Dai
ba1bf353b8 feat(ai-proxy): add anthropic/v1/messages support for qwen (#2648)
Signed-off-by: Xijun Dai <daixijun1990@gmail.com>
2025-07-23 21:06:56 +08:00
澄潭
b56097e647 Update CODEOWNERS 2025-07-23 19:24:13 +08:00
GuoChenxu
5b97b849b5 Release note format (#2647)
Signed-off-by: guochenxu <guochenxu11@outlook.com>
2025-07-23 19:07:53 +08:00
github-actions[bot]
331fe57c70 Add release notes (#2635) 2025-07-22 14:48:45 +08:00
澄潭
4d32cc9468 Disable reroute in some plugins (#2639) 2025-07-22 14:44:26 +08:00
GuoChenxu
34b5a6feea Fix the special character translation issue in pr #2596 (#2623)
Signed-off-by: guochenxu <guochenxu11@outlook.com>
2025-07-21 18:54:08 +08:00
GuoChenxu
8736edaf61 CI: 基于higress-report-agent实现higress release note agent (#2596)
Signed-off-by: guochenxu <guochenxu11@outlook.com>
2025-07-18 17:53:58 +08:00
澄潭
30d5b4d32e upgrade wasm-go sdk of some wasm plugins (#2615) 2025-07-17 21:03:42 +08:00
Alexander Kolotov
c0133378a7 Add the Blockscout MCP server (#2585) 2025-07-17 09:42:28 +08:00
Xijun Dai
8346b4a4a2 feat: Support statistics images/audio/responses API Token usage (#2542)
Signed-off-by: Xijun Dai <daixijun1990@gmail.com>
2025-07-16 10:34:09 +08:00
xingpiaoliang
ce271849de docs(wasm-go): update README related to wasm-go (#2586)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-07-16 10:27:40 +08:00
澄潭
bdc3ecab71 update go to 1.24.4 in wasm builder image (#2600) 2025-07-16 10:24:29 +08:00
澄潭
9214dca078 update go to 1.24.4 in wasm builder image (#2598) 2025-07-15 19:36:04 +08:00
Xijun Dai
c3eb8d0447 feat(ai-proxy): add anthropic && gemini apiName (#2551)
Signed-off-by: Xijun Dai <daixijun1990@gmail.com>
2025-07-15 19:15:07 +08:00
xingpiaoliang
081ab6ee8d Migrate WASM Go Plugins to New SDK and Go 1.24 (#2532) 2025-07-11 10:43:00 +08:00
rinfx
9a45f07972 fix: [ai-load-balancer]move the logic of request count to HttpStreamDone phase (#2564) 2025-07-09 15:42:00 +08:00
mamba
da2ae4c7ee 增加 useManifestAsEntry 配置支持 || Increase the useManifestAsEntry configuration support (#2499)
Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
2025-07-09 11:53:56 +08:00
woody
ff068258a1 [ai-proxy]qwen text-rerank support (#2537) 2025-07-07 20:27:56 +08:00
woody
0996ad21b1 feat(ai-proxy): add basePathHandling option (#2535)
Co-authored-by: Se7en <chengzw258@163.com>
2025-07-03 20:34:14 +08:00
Se7en
45eb76d4cc feat: Add Higress API MCP server (#2517) 2025-07-03 10:27:28 +08:00
499 changed files with 49423 additions and 4121 deletions

View File

@@ -3,22 +3,22 @@ name: Build and Push Wasm Plugin Image
on:
push:
tags:
- "wasm-*-*-v*.*.*" # 匹配 wasm-{go|rust}-{pluginName}-vX.Y.Z 格式的标签
- "wasm-*-*-v*.*.*" # 匹配 wasm-{go|rust}-{pluginName}-vX.Y.Z 格式的标签
workflow_dispatch:
inputs:
plugin_type:
description: 'Type of the plugin'
description: "Type of the plugin"
required: true
type: choice
options:
- go
- rust
plugin_name:
description: 'Name of the plugin'
description: "Name of the plugin"
required: true
type: string
version:
description: 'Version of the plugin (optional, without leading v)'
description: "Version of the plugin (optional, without leading v)"
required: false
type: string
@@ -31,8 +31,7 @@ jobs:
IMAGE_REGISTRY_SERVICE: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
IMAGE_REPOSITORY: ${{ vars.PLUGIN_IMAGE_REPOSITORY || 'plugins' }}
RUST_VERSION: 1.82
GO_VERSION: 1.19
TINYGO_VERSION: 0.28.1
GO_VERSION: 1.24.0
ORAS_VERSION: 1.0.0
steps:
- name: Set plugin_type, plugin_name and version from inputs or ref_name
@@ -53,7 +52,7 @@ jobs:
if [[ "$plugin_type" == "rust" ]]; then
builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-rust-builder:rust${{ env.RUST_VERSION }}-oras${{ env.ORAS_VERSION }}"
else
builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }}"
builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-oras${{ env.ORAS_VERSION }}"
fi
echo "PLUGIN_TYPE=$plugin_type" >> $GITHUB_ENV
echo "PLUGIN_NAME=$plugin_name" >> $GITHUB_ENV
@@ -62,9 +61,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: File Check
run: |
run: |
workspace=${{ github.workspace }}/plugins/wasm-${PLUGIN_TYPE}/extensions/${PLUGIN_NAME}
push_command="./plugin.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip"
@@ -79,7 +78,7 @@ jobs:
echo "README.md exists"
push_command="./README.md:application/vnd.module.wasm.doc.v1+markdown $push_command "
fi
# 查找README_{lang}.md
for file in ${workspace}/README_*.md; do
if [ -f "$file" ]; then
@@ -91,9 +90,9 @@ jobs:
done
echo "PUSH_COMMAND=\"$push_command\"" >> $GITHUB_ENV
- name: Run a wasm-builder
env:
env:
PLUGIN_NAME: ${{ env.PLUGIN_NAME }}
BUILDER_IMAGE: ${{ env.BUILDER_IMAGE }}
run: |
@@ -104,7 +103,7 @@ jobs:
push_command=${{ env.PUSH_COMMAND }}
push_command=${push_command#\"}
push_command=${push_command%\"} # 删除PUSH_COMMAND中的双引号确保oras push正常解析
target_image="${{ env.IMAGE_REGISTRY_SERVICE }}/${{ env.IMAGE_REPOSITORY}}/${{ env.PLUGIN_NAME }}:${{ env.VERSION }}"
target_image_latest="${{ env.IMAGE_REGISTRY_SERVICE }}/${{ env.IMAGE_REPOSITORY}}/${{ env.PLUGIN_NAME }}:latest"
echo "TargetImage=${target_image}"
@@ -123,7 +122,7 @@ jobs:
set -e
cd /workspace/plugins/wasm-go/extensions/${PLUGIN_NAME}
go mod tidy
tinygo build -o ./plugin.wasm -scheduler=none -target=wasi -gc=custom -tags=\"custommalloc nottinygc_finalizer ${EXTRA_TAGS}\" .
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o plugin.wasm .
tar czvf plugin.tar.gz plugin.wasm
echo ${{ secrets.REGISTRY_PASSWORD }} | oras login -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin ${{ env.IMAGE_REGISTRY_SERVICE }}
oras push ${target_image} ${push_command}

View File

@@ -2,20 +2,20 @@ name: "Build and Test Plugins"
on:
push:
branches: [ main ]
branches: [main]
paths:
- 'plugins/**'
- 'test/**'
- 'helm/**'
- 'Makefile.core.mk'
- "plugins/**"
- "test/**"
- "helm/**"
- "Makefile.core.mk"
pull_request:
branches: [ "*" ]
branches: ["*"]
paths:
- 'plugins/**'
- 'test/**'
- 'helm/**'
- 'Makefile.core.mk'
workflow_dispatch: ~
- "plugins/**"
- "test/**"
- "helm/**"
- "Makefile.core.mk"
workflow_dispatch: ~
jobs:
lint:
@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.22
go-version: 1.24
# There are too many lint errors in current code bases
# uncomment when we decide what lint should be addressed or ignored.
# - run: make lint
@@ -34,7 +34,7 @@ jobs:
strategy:
matrix:
# TODO(Xunzhuo): Enable C WASM Filters in CI
wasmPluginType: [ GO, RUST ]
wasmPluginType: [GO, RUST]
steps:
- uses: actions/checkout@v4
@@ -46,12 +46,12 @@ jobs:
dotnet: true
haskell: true
large-packages: true
swap-storage: true
swap-storage: true
- name: "Setup Go"
uses: actions/setup-go@v5
with:
go-version: 1.22
go-version: 1.24
- name: Setup Rust
uses: actions-rs/toolchain@v1
@@ -80,6 +80,6 @@ jobs:
publish:
runs-on: ubuntu-latest
needs: [ higress-wasmplugin-test ]
needs: [higress-wasmplugin-test]
steps:
- uses: actions/checkout@v4

View File

@@ -29,7 +29,7 @@ jobs:
echo "Version=$VERSION"
# Step 3
- name: Upload to OSS
uses: doggycool/ossutil-github-action@master
uses: go-choppy/ossutil-github-action@master
with:
ossArgs: 'cp -r -u ./artifact/ oss://higress-website-cn-hongkong/standalone/'
accessKey: ${{ secrets.ACCESS_KEYID }}

View File

@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v4
# Step 2
- name: Download Helm Charts Index
uses: doggycool/ossutil-github-action@master
uses: go-choppy/ossutil-github-action@master
with:
ossArgs: 'cp oss://higress-website-cn-hongkong/helm-charts/index.yaml ./artifact/'
accessKey: ${{ secrets.ACCESS_KEYID }}
@@ -46,7 +46,7 @@ jobs:
sed -i 's/higress\.io/higress\.cn/g' ./artifact/cn-index.yaml
# Step 5
- name: Upload to OSS
uses: doggycool/ossutil-github-action@master
uses: go-choppy/ossutil-github-action@master
with:
ossArgs: 'cp -r -u ./artifact/ oss://higress-website-cn-hongkong/helm-charts/'
accessKey: ${{ secrets.ACCESS_KEYID }}

View File

@@ -0,0 +1,217 @@
name: Generate Release Notes
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
generate-release-notes:
runs-on: ubuntu-latest
env:
DASHSCOPE_API_KEY: ${{ secrets.HIGRESS_OPENAI_API_KEY }}
MODEL_NAME: ${{ secrets.HIGRESS_OPENAI_API_MODEL }}
MODEL_SERVER: ${{ secrets.MODEL_SERVER }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24
- name: Clone GitHub MCP Server
run: |
git clone https://github.com/github/github-mcp-server.git
cd github-mcp-server
go build -o ../github-mcp-serve ./cmd/github-mcp-server
cd ..
chmod u+x github-mcp-serve
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Clone Higress Report Agent
run: |
git clone https://github.com/higress-group/higress-report-agent.git
mv github-mcp-serve higress-report-agent/
- name: Clean up old release notes
run: |
RELEASE_VERSION=$(cat ${GITHUB_WORKSPACE}/VERSION)
CLEAN_VERSION=${RELEASE_VERSION#v}
if [ -d "release-notes/${CLEAN_VERSION}" ]; then
echo "Removing old release notes directory: release-notes/${CLEAN_VERSION}"
rm -rf release-notes/${CLEAN_VERSION}
else
echo "No old release notes directory found for version ${CLEAN_VERSION}."
fi
- name: Create Release Report Script
run: |
cat > generate_release_report.sh << 'EOF'
#!/bin/bash
# Script to generate release notes for Higress projects
echo "Fetching GitHub generated release notes for ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}..."
curl -L \
"https://github.com/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/releases/tag/${RELEASE_VERSION}" \
-o release_page.html
echo "Extracting PR numbers from ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME} release notes..."
PR_NUMS=$(cat release_page.html | grep -o "/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/pull/[0-9]*" | grep -o "[0-9]*$" | sort -n | uniq | tr '\n' ',')
PR_NUMS=${PR_NUMS%,}
if [ -z "${PR_NUMS}" ]; then
echo "No PR numbers found in release notes for ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME} tag=${RELEASE_VERSION}."
rm release_page.html
exit 0
fi
echo "Identifying important PRs..."
IMPORTANT_PR_NUMS=$(cat release_page.html | grep -o "<strong>.*/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/pull/[0-9]*.*</strong>" | grep -o "pull/[0-9]*" | grep -o "[0-9]*" | sort -n | uniq | tr '\n' ',')
IMPORTANT_PR_NUMS=${IMPORTANT_PR_NUMS%,}
rm release_page.html
echo "Extracted PR numbers for ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}: ${PR_NUMS}"
echo "Important PR numbers: ${IMPORTANT_PR_NUMS}"
echo "Generating detailed release notes for ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}..."
cd higress-report-agent
pip install uv
uv sync
if [ -n "${IMPORTANT_PR_NUMS}" ]; then
uv run report_main.py --mode 2 --choice 2 --pr_nums ${PR_NUMS} --important_prs ${IMPORTANT_PR_NUMS}
else
uv run report_main.py --mode 2 --choice 2 --pr_nums ${PR_NUMS}
fi
cp report.md ../
cp report.EN.md ../
cd ..
# 去除主库版本号前缀v以主库版本号为路径
CLEAN_VERSION=${MAIN_RELEASE_VERSION#v}
echo "Creating release notes directory for main version ${MAIN_RELEASE_VERSION}..."
mkdir -p release-notes/${CLEAN_VERSION}
echo "# ${REPORT_TITLE}" >>release-notes/${CLEAN_VERSION}/README_ZH.md
sed 's/# Release Notes//' report.md >>release-notes/${CLEAN_VERSION}/README_ZH.md
echo -e "\n" >>release-notes/${CLEAN_VERSION}/README_ZH.md
echo "# ${REPORT_TITLE}" >>release-notes/${CLEAN_VERSION}/README.md
sed 's/# Release Notes//' report.EN.md >>release-notes/${CLEAN_VERSION}/README.md
echo -e "\n" >>release-notes/${CLEAN_VERSION}/README.md
rm report.md
rm report.EN.md
echo "${REPORT_TITLE} release notes saved to release-notes/${CLEAN_VERSION}/"
EOF
chmod +x generate_release_report.sh
- name: Generate Release Notes for Higress
env:
GITHUB_REPO_OWNER: alibaba
GITHUB_REPO_NAME: higress
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPORT_TITLE: Higress
run: |
export MAIN_RELEASE_VERSION=$(cat ${GITHUB_WORKSPACE}/VERSION)
export RELEASE_VERSION=$(cat ${GITHUB_WORKSPACE}/VERSION)
bash generate_release_report.sh
- name: Generate Release Notes for Higress Console
env:
GITHUB_REPO_OWNER: higress-group
GITHUB_REPO_NAME: higress-console
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPORT_TITLE: Higress Console
run: |
export MAIN_RELEASE_VERSION=$(cat ${GITHUB_WORKSPACE}/VERSION)
export RELEASE_VERSION=$(grep "^higress-console:" ${GITHUB_WORKSPACE}/DEP_VERSION | head -n1 | sed 's/higress-console: //')
bash generate_release_report.sh
- name: Create Update Release Notes Script
run: |
cat > update_release_note.sh << 'EOF'
#!/bin/bash
CLEAN_VERSION=${RELEASE_VERSION#v}
RELEASE_INFO=$(curl -s -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/releases/tags/${RELEASE_VERSION})
RELEASE_ID=$(echo $RELEASE_INFO | jq -r .id)
RELEASE_BODY=$(echo $RELEASE_INFO | jq -r .body)
NEW_CONTRIBUTORS=$(echo "$RELEASE_BODY" | awk '/## New Contributors/{flag=1; next} /\*\*Full Changelog\*\*/{flag=0} flag' | sed 's/\\n/\n/g')
FULL_CHANGELOG=$(echo "$RELEASE_BODY" | awk '/\*\*Full Changelog\*\*:/{print $0}' | sed 's/\*\*Full Changelog\*\*: //g' | sed 's/\\n/\n/g')
RELEASE_NOTES=$(cat release-notes/${CLEAN_VERSION}/README.md | sed 's/# /## /g')
if [[ -n "$NEW_CONTRIBUTORS" ]]; then
RELEASE_NOTES="${RELEASE_NOTES}
## New Contributors
${NEW_CONTRIBUTORS}"
fi
if [[ -n "$FULL_CHANGELOG" ]]; then
RELEASE_NOTES="${RELEASE_NOTES}
**Full Changelog**: ${FULL_CHANGELOG}"
fi
JSON_DATA=$(jq -n \
--arg body "$RELEASE_NOTES" \
'{body: $body}')
curl -L \
-X PATCH \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/releases/${RELEASE_ID} \
-d "$JSON_DATA"
EOF
chmod +x update_release_note.sh
- name: Update Release Notes
env:
GITHUB_REPO_OWNER: alibaba
GITHUB_REPO_NAME: higress
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
export RELEASE_VERSION=$(cat ${GITHUB_WORKSPACE}/VERSION)
bash update_release_note.sh
- name: Clean
run: |
rm generate_release_report.sh
rm update_release_note.sh
rm -rf higress-report-agent
rm -rf github-mcp-server
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Add release notes"
branch: add-release-notes
title: "Add release notes"
body: |
This PR adds release notes.
- Automatically generated by GitHub Actions
labels: release notes, automated
base: main

View File

@@ -0,0 +1,378 @@
name: Wasm Plugin Unit Tests(GO)
on:
push:
branches: [ main ]
paths:
- 'plugins/wasm-go/extensions/**'
- '.github/workflows/wasm-plugin-unit-test.yml'
- 'go.mod'
- 'go.sum'
pull_request:
branches: [ "*" ]
paths:
- 'plugins/wasm-go/extensions/**'
- '.github/workflows/wasm-plugin-unit-test.yml'
- 'go.mod'
- 'go.sum'
env:
GO111MODULE: on
CGO_ENABLED: 0
GOOS: linux
GOARCH: amd64
jobs:
detect-changed-plugins:
name: Detect Changed Plugins
runs-on: ubuntu-latest
outputs:
changed-plugins: ${{ steps.detect.outputs.plugins }}
has-changes: ${{ steps.detect.outputs.has-changes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史用于比较
- name: Detect changed plugins
id: detect
run: |
# 获取变更的文件列表
if [ "${{ github.event_name }}" = "pull_request" ]; then
# PR模式比较目标分支和源分支
git fetch origin ${{ github.base_ref }}
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
else
# Push模式比较当前提交和上一个提交
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD)
fi
echo "Changed files:"
echo "$CHANGED_FILES"
# 提取变更的插件名称
CHANGED_PLUGINS=""
for file in $CHANGED_FILES; do
if [[ $file =~ ^plugins/wasm-go/extensions/([^/]+)/ ]]; then
PLUGIN_NAME="${BASH_REMATCH[1]}"
if [[ ! " $CHANGED_PLUGINS " =~ " $PLUGIN_NAME " ]]; then
# 修复:只在非空时添加空格
if [ -z "$CHANGED_PLUGINS" ]; then
CHANGED_PLUGINS="$PLUGIN_NAME"
else
CHANGED_PLUGINS="$CHANGED_PLUGINS $PLUGIN_NAME"
fi
fi
fi
done
# 如果没有插件变更,不触发测试
if [ -z "$CHANGED_PLUGINS" ]; then
echo "No plugin changes detected, skipping tests"
echo "has-changes=false" >> $GITHUB_OUTPUT
echo "plugins=[]" >> $GITHUB_OUTPUT
else
echo "Changed plugins: $CHANGED_PLUGINS"
echo "has-changes=true" >> $GITHUB_OUTPUT
# 将空格分隔转换为 JSON 数组格式
PLUGINS_JSON=$(echo "$CHANGED_PLUGINS" | sed 's/ /","/g' | sed 's/^/["/' | sed 's/$/"]/')
echo "PLUGINS_JSON: $PLUGINS_JSON"
echo "plugins=$PLUGINS_JSON" >> $GITHUB_OUTPUT
fi
test:
name: Test Changed Plugins
runs-on: ubuntu-latest
needs: detect-changed-plugins
if: needs.detect-changed-plugins.outputs.has-changes == 'true'
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJSON(needs.detect-changed-plugins.outputs.changed-plugins) }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go 1.24
uses: actions/setup-go@v4
with:
go-version: 1.24
cache: true
- name: Install test tools
run: |
go install gotest.tools/gotestsum@latest
# 移除gocov工具直接使用Codecov
- name: Build WASM for ${{ matrix.plugin }}
working-directory: plugins/wasm-go/extensions/${{ matrix.plugin }}
run: |
echo "Building WASM for ${{ matrix.plugin }}..."
# 检查是否存在main.go文件
export GOOS=wasip1
export GOARCH=wasm
# 构建WASM文件失败时直接退出
if ! go build -buildmode=c-shared -o main.wasm ./; then
echo "❌ WASM build failed for ${{ matrix.plugin }}"
exit 1
fi
# 验证WASM文件是否生成
if [ ! -f "main.wasm" ]; then
echo "❌ WASM file not generated for ${{ matrix.plugin }}"
exit 1
fi
echo "✅ WASM build successful for ${{ matrix.plugin }}"
- name: Set WASM_PATH environment variable
run: |
echo "WASM_PATH=$(pwd)/plugins/wasm-go/extensions/${{ matrix.plugin }}/main.wasm" >> $GITHUB_ENV
- name: Run tests with coverage for ${{ matrix.plugin }}
working-directory: plugins/wasm-go/extensions/${{ matrix.plugin }}
run: |
# 检查是否存在main_test.go文件
if [ -f "main_test.go" ]; then
echo "Running tests for ${{ matrix.plugin }}..."
# 运行测试并生成覆盖率报告
gotestsum --junitfile ../../../../test-results-${{ matrix.plugin }}.xml \
--format standard-verbose \
--jsonfile ../../../../test-output-${{ matrix.plugin }}.json \
-- -coverprofile=coverage-${{ matrix.plugin }}.out -covermode=atomic -coverpkg=./... ./...
echo "✅ Tests completed for ${{ matrix.plugin }}"
else
echo "No tests found for ${{ matrix.plugin }}, skipping..."
# 创建空的测试结果文件
echo '<?xml version="1.0" encoding="UTF-8"?><testsuites><testsuite name="no-tests" tests="0" failures="0" errors="0" time="0"></testsuite></testsuites>' > ../../../../test-results-${{ matrix.plugin }}.xml
fi
- name: Upload test results for ${{ matrix.plugin }}
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.plugin }}
path: |
test-results-${{ matrix.plugin }}.xml
test-output-${{ matrix.plugin }}.json
retention-days: 30
- name: Upload coverage report for ${{ matrix.plugin }}
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-${{ matrix.plugin }}
path: plugins/wasm-go/extensions/${{ matrix.plugin }}/coverage-${{ matrix.plugin }}.out
retention-days: 30
test-summary:
name: Test Summary & Coverage
runs-on: ubuntu-latest
needs: [detect-changed-plugins, test]
if: always() && needs.detect-changed-plugins.outputs.has-changes == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go 1.24
uses: actions/setup-go@v4
with:
go-version: 1.24
cache: true
- name: Install required tools
run: |
go install github.com/wadey/gocovmerge@latest
- name: Download all test results
uses: actions/download-artifact@v4
with:
pattern: test-results-*
merge-multiple: true
path: ${{ github.workspace }}
- name: Download all coverage files
uses: actions/download-artifact@v4
with:
pattern: coverage-*
merge-multiple: true
path: ${{ github.workspace }}
- name: Generate comprehensive test summary
run: |
echo "## 🧪 Go Plugin Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
total_plugins=0
passed_plugins=0
failed_plugins=0
total_tests=0
total_failures=0
total_errors=0
echo "### 📊 Test Results by Plugin" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
for result_file in test-results-*.xml; do
if [ -f "$result_file" ]; then
plugin_name=$(echo "$result_file" | sed 's/test-results-\(.*\)\.xml/\1/')
total_plugins=$((total_plugins + 1))
# 解析XML获取测试结果
if grep -q '<testsuite' "$result_file"; then
# 使用grep解析XML属性更稳定可靠
tests=$(grep -o 'tests="[0-9]*"' "$result_file" | head -1 | grep -o '[0-9]*' || echo "0")
failures=$(grep -o 'failures="[0-9]*"' "$result_file" | head -1 | grep -o '[0-9]*' || echo "0")
errors=$(grep -o 'errors="[0-9]*"' "$result_file" | head -1 | grep -o '[0-9]*' || echo "0")
time=$(grep -o 'time="[0-9.]*"' "$result_file" | head -1 | grep -o '[0-9.]*' || echo "0")
# 确保数值有效避免bash算术运算错误
tests=${tests:-0}
failures=${failures:-0}
errors=${errors:-0}
# 转换为整数进行算术运算
total_tests=$((total_tests + tests))
total_failures=$((total_failures + failures))
total_errors=$((total_errors + errors))
if [ "$failures" = "0" ] && [ "$errors" = "0" ]; then
echo "✅ **$plugin_name**: $tests tests passed in ${time}s" >> $GITHUB_STEP_SUMMARY
passed_plugins=$((passed_plugins + 1))
else
echo "❌ **$plugin_name**: $tests tests, $failures failures, $errors errors in ${time}s" >> $GITHUB_STEP_SUMMARY
failed_plugins=$((failed_plugins + 1))
fi
else
echo "⚠️ **$plugin_name**: No tests found" >> $GITHUB_STEP_SUMMARY
fi
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📈 Coverage Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📊 **Coverage reports are now available on Codecov**" >> $GITHUB_STEP_SUMMARY
echo "🔗 **This Commit Coverage**: https://codecov.io/gh/${{ github.repository }}/commit/${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🎯 Summary" >> $GITHUB_STEP_SUMMARY
echo "- **Total plugins**: $total_plugins" >> $GITHUB_STEP_SUMMARY
echo "- **Passed**: $passed_plugins ✅" >> $GITHUB_STEP_SUMMARY
echo "- **Failed**: $failed_plugins ❌" >> $GITHUB_STEP_SUMMARY
echo "- **Total tests**: $total_tests" >> $GITHUB_STEP_SUMMARY
echo "- **Total failures**: $total_failures" >> $GITHUB_STEP_SUMMARY
echo "- **Total errors**: $total_errors" >> $GITHUB_STEP_SUMMARY
# 如果有失败,显示详细信息
if [ $total_failures -gt 0 ] || [ $total_errors -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ❌ Failed Tests Details" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Failed plugins**: $failed_plugins" >> $GITHUB_STEP_SUMMARY
echo "**Total failures**: $total_failures" >> $GITHUB_STEP_SUMMARY
echo "**Total errors**: $total_errors" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📋 **View detailed logs**: [Click here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# 显示每个失败插件的详细信息
echo "#### 📊 Failed Plugin Details" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
for result_file in test-results-*.xml; do
if [ -f "$result_file" ]; then
plugin_name=$(echo "$result_file" | sed 's/test-results-\(.*\)\.xml/\1/')
# 检查是否有失败
failures=$(grep -o 'failures="[0-9]*"' "$result_file" | head -1 | grep -o '[0-9]*' || echo "0")
errors=$(grep -o 'errors="[0-9]*"' "$result_file" | head -1 | grep -o '[0-9]*' || echo "0")
# 确保数值有效
failures=${failures:-0}
errors=${errors:-0}
if [ "$failures" -gt 0 ] || [ "$errors" -gt 0 ]; then
echo "**$plugin_name**:" >> $GITHUB_STEP_SUMMARY
echo "- Failures: $failures" >> $GITHUB_STEP_SUMMARY
echo "- Errors: $errors" >> $GITHUB_STEP_SUMMARY
echo "- [View plugin logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
fi
done
fi
# - name: Merge coverage reports
# run: |
# echo "Merging coverage reports..."
#
# # 使用绝对路径查找,更可靠
# coverage_files=$(find ${{ github.workspace }} -name "coverage-*")
#
# if [ -n "$coverage_files" ]; then
# echo "Found coverage files:"
# echo "$coverage_files"
#
# # 使用gocovmerge顺序合并
# echo "Merging Go coverage files using gocovmerge sequential method..."
#
# # 将文件列表转换为数组
# readarray -t coverage_array <<< "$coverage_files"
# file_count=${#coverage_array[@]}
#
# echo "Total files to merge: $file_count"
#
# # 复制第一个文件作为基础
# cp "${coverage_array[0]}" ${{ github.workspace }}/merged_coverage.out
# echo "Starting with: ${coverage_array[0]}"
#
# # 如果有多个文件,逐个合并其他文件到最终目标
# if [ $file_count -gt 1 ]; then
# echo "Multiple files, merging sequentially with gocovmerge..."
#
# for ((i=1; i<file_count; i++)); do
# current_file="${coverage_array[i]}"
#
# echo "Merging file $((i+1))/$file_count: $current_file"
#
# # 使用gocovmerge合并到最终目标文件
# gocovmerge "${{ github.workspace }}/merged_coverage.out" "$current_file" > "${{ github.workspace }}/temp_merge.out"
# mv "${{ github.workspace }}/temp_merge.out" "${{ github.workspace }}/merged_coverage.out"
#
# echo "Successfully merged with $current_file"
# done
# fi
#
# echo "Coverage reports merged successfully using gocovmerge sequential method"
# echo "Merged file size: $(wc -c < ${{ github.workspace }}/merged_coverage.out) bytes"
# else
# echo "No coverage files found"
# # 创建空的覆盖率文件
# echo "mode: atomic" > ${{ github.workspace }}/merged_coverage.out
# fi
# - name: Upload merged coverage to Codecov
# uses: codecov/codecov-action@v4
# if: always()
# with:
# file: ${{ github.workspace }}/merged_coverage.out
# flags: wasm-go-plugins-tests
# name: codecov-wasm-go-plugins
# fail_ci_if_error: false
# verbose: true

View File

@@ -27,6 +27,7 @@ header:
- 'plugins/**'
- 'CODEOWNERS'
- 'VERSION'
- 'DEP_VERSION'
- 'tools/'
- 'test/README.md'
- 'test/README_CN.md'

View File

@@ -2,10 +2,10 @@
/envoy @gengleilei @johnlanni
/istio @SpecialYang @johnlanni
/pkg @SpecialYang @johnlanni @CH3CHO
/plugins @johnlanni @CH3CHO @rinfx
/plugins/wasm-go/extensions/ai-proxy @cr7258 @CH3CHO @rinfx @wydream
/plugins @johnlanni @CH3CHO @rinfx @erasernoob
/plugins/wasm-go/extensions/ai-proxy @rinfx @wydream @johnlanni
/plugins/wasm-rust @007gzs @jizhuozhi
/registry @NameHaibinZhang @2456868764 @johnlanni
/registry @Erica177 @2456868764 @johnlanni
/test @Xunzhuo @2456868764 @CH3CHO
/tools @johnlanni @Xunzhuo @2456868764

1
DEP_VERSION Normal file
View File

@@ -0,0 +1 @@
higress-console: v2.1.7

View File

@@ -137,6 +137,8 @@ endif
# for now docker is limited to Linux compiles - why ?
include docker/docker.mk
docker-build-amd64: docker.higress-amd64 ## Build and push amdd64 docker images to registry defined by $HUB and $TAG
docker-build: docker.higress ## Build and push docker images to registry defined by $HUB and $TAG
docker-buildx-push: clean-env docker.higress-buildx
@@ -144,7 +146,7 @@ docker-buildx-push: clean-env docker.higress-buildx
export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.7/envoy-symbol-ARCH.tar.gz
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.9/envoy-symbol-ARCH.tar.gz
build-envoy: prebuild
./tools/hack/build-envoy.sh
@@ -192,7 +194,7 @@ install: pre-install
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
HIGRESS_LATEST_IMAGE_TAG ?= latest
ENVOY_LATEST_IMAGE_TAG ?= latest
ENVOY_LATEST_IMAGE_TAG ?= 48da465cfd0dc5c9ac851bd2b9743780dc82dd8a
ISTIO_LATEST_IMAGE_TAG ?= latest
install-dev: pre-install

View File

@@ -1 +1 @@
v2.1.5
v2.1.7

View File

@@ -247,6 +247,23 @@ spec:
properties:
spec:
properties:
proxies:
items:
properties:
connectTimeout:
type: integer
listenerPort:
type: integer
name:
type: string
serverAddress:
type: string
serverPort:
type: integer
type:
type: string
type: object
type: array
registries:
items:
properties:
@@ -309,6 +326,8 @@ spec:
type: integer
protocol:
type: string
proxyName:
type: string
sni:
type: string
type:

View File

@@ -65,6 +65,7 @@ type McpBridge struct {
unknownFields protoimpl.UnknownFields
Registries []*RegistryConfig `protobuf:"bytes,1,rep,name=registries,proto3" json:"registries,omitempty"`
Proxies []*ProxyConfig `protobuf:"bytes,2,rep,name=proxies,proto3" json:"proxies,omitempty"`
}
func (x *McpBridge) Reset() {
@@ -106,6 +107,13 @@ func (x *McpBridge) GetRegistries() []*RegistryConfig {
return nil
}
func (x *McpBridge) GetProxies() []*ProxyConfig {
if x != nil {
return x.Proxies
}
return nil
}
type RegistryConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -136,6 +144,7 @@ type RegistryConfig struct {
EnableScopeMcpServers *wrappers.BoolValue `protobuf:"bytes,23,opt,name=enableScopeMcpServers,proto3" json:"enableScopeMcpServers,omitempty"`
AllowMcpServers []string `protobuf:"bytes,24,rep,name=allowMcpServers,proto3" json:"allowMcpServers,omitempty"`
Metadata map[string]*InnerMap `protobuf:"bytes,25,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ProxyName string `protobuf:"bytes,26,opt,name=proxyName,proto3" json:"proxyName,omitempty"`
}
func (x *RegistryConfig) Reset() {
@@ -345,6 +354,100 @@ func (x *RegistryConfig) GetMetadata() map[string]*InnerMap {
return nil
}
func (x *RegistryConfig) GetProxyName() string {
if x != nil {
return x.ProxyName
}
return ""
}
type ProxyConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
ServerAddress string `protobuf:"bytes,3,opt,name=serverAddress,proto3" json:"serverAddress,omitempty"`
ServerPort uint32 `protobuf:"varint,4,opt,name=serverPort,proto3" json:"serverPort,omitempty"`
ListenerPort uint32 `protobuf:"varint,5,opt,name=listenerPort,proto3" json:"listenerPort,omitempty"`
ConnectTimeout uint32 `protobuf:"varint,6,opt,name=connectTimeout,proto3" json:"connectTimeout,omitempty"`
}
func (x *ProxyConfig) Reset() {
*x = ProxyConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_networking_v1_mcp_bridge_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ProxyConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProxyConfig) ProtoMessage() {}
func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
mi := &file_networking_v1_mcp_bridge_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.
func (*ProxyConfig) Descriptor() ([]byte, []int) {
return file_networking_v1_mcp_bridge_proto_rawDescGZIP(), []int{2}
}
func (x *ProxyConfig) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProxyConfig) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ProxyConfig) GetServerAddress() string {
if x != nil {
return x.ServerAddress
}
return ""
}
func (x *ProxyConfig) GetServerPort() uint32 {
if x != nil {
return x.ServerPort
}
return 0
}
func (x *ProxyConfig) GetListenerPort() uint32 {
if x != nil {
return x.ListenerPort
}
return 0
}
func (x *ProxyConfig) GetConnectTimeout() uint32 {
if x != nil {
return x.ConnectTimeout
}
return 0
}
type InnerMap struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -356,7 +459,7 @@ type InnerMap struct {
func (x *InnerMap) Reset() {
*x = InnerMap{}
if protoimpl.UnsafeEnabled {
mi := &file_networking_v1_mcp_bridge_proto_msgTypes[2]
mi := &file_networking_v1_mcp_bridge_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -369,7 +472,7 @@ func (x *InnerMap) String() string {
func (*InnerMap) ProtoMessage() {}
func (x *InnerMap) ProtoReflect() protoreflect.Message {
mi := &file_networking_v1_mcp_bridge_proto_msgTypes[2]
mi := &file_networking_v1_mcp_bridge_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -382,7 +485,7 @@ func (x *InnerMap) ProtoReflect() protoreflect.Message {
// Deprecated: Use InnerMap.ProtoReflect.Descriptor instead.
func (*InnerMap) Descriptor() ([]byte, []int) {
return file_networking_v1_mcp_bridge_proto_rawDescGZIP(), []int{2}
return file_networking_v1_mcp_bridge_proto_rawDescGZIP(), []int{3}
}
func (x *InnerMap) GetInnerMap() map[string]string {
@@ -404,100 +507,119 @@ var file_networking_v1_mcp_bridge_proto_rawDesc = []byte{
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65,
0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x52, 0x0a, 0x09, 0x4d, 0x63, 0x70, 0x42, 0x72, 0x69,
0x64, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73,
0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a,
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xa8, 0x09, 0x0a, 0x0e, 0x52,
0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x17, 0x0a,
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02,
0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x6f,
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52,
0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74,
0x12, 0x2e, 0x0a, 0x12, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6e, 0x61,
0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b,
0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41,
0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f,
0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79,
0x12, 0x2a, 0x0a, 0x10, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
0x63, 0x65, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x61, 0x63, 0x6f,
0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0e,
0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x09,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73,
0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x47, 0x72, 0x6f,
0x75, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52,
0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f,
0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x0c, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73,
0x70, 0x61, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x7a, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x7a, 0x6b,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2a, 0x0a, 0x10,
0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61,
0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73,
0x75, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x61, 0x67, 0x18, 0x0f, 0x20, 0x01,
0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x54, 0x61, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20,
0x01, 0x28, 0x03, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x75,
0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x11, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x12,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x10,
0x0a, 0x03, 0x73, 0x6e, 0x69, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x6e, 0x69,
0x12, 0x36, 0x0a, 0x16, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09,
0x52, 0x16, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x70, 0x6f, 0x72,
0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x6d, 0x63, 0x70, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x18, 0x15, 0x20, 0x01,
0x28, 0x09, 0x52, 0x10, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x61, 0x73,
0x65, 0x55, 0x72, 0x6c, 0x12, 0x44, 0x0a, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x43,
0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x50, 0x0a, 0x15, 0x65, 0x6e,
0x61, 0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x4d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f,
0x70, 0x65, 0x4d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x0f,
0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18,
0x18, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4d, 0x63, 0x70, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x4f, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x01, 0x0a, 0x09, 0x4d, 0x63, 0x70, 0x42, 0x72,
0x69, 0x64, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65,
0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x5c, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x69, 0x67, 0x72,
0x65, 0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76,
0x31, 0x2e, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x93, 0x01, 0x0a, 0x08, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d,
0x61, 0x70, 0x12, 0x4a, 0x0a, 0x09, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x70, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e,
0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x2e, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x1a, 0x3b,
0x0a, 0x0d, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62,
0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x70,
0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68,
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e,
0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x52, 0x07, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x22, 0xc6, 0x09, 0x0a, 0x0e, 0x52, 0x65,
0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x17, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12,
0x2e, 0x0a, 0x12, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6e, 0x61, 0x63,
0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12,
0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65,
0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x63,
0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73,
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12,
0x2a, 0x0a, 0x10, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
0x65, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x61, 0x63, 0x6f, 0x73,
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x6e,
0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x47,
0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0b, 0x20,
0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,
0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e,
0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
0x61, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x7a, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x50, 0x61, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x7a, 0x6b, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2a, 0x0a, 0x10, 0x63,
0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x18,
0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61, 0x74,
0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75,
0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x61, 0x67, 0x18, 0x0f, 0x20, 0x01, 0x28,
0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x54, 0x61, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65, 0x66,
0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20, 0x01,
0x28, 0x03, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,
0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x75, 0x74,
0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x12, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x10, 0x0a,
0x03, 0x73, 0x6e, 0x69, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x6e, 0x69, 0x12,
0x36, 0x0a, 0x16, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x70, 0x6f,
0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52,
0x16, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74,
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x6d, 0x63, 0x70, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x42, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28,
0x09, 0x52, 0x10, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x61, 0x73, 0x65,
0x55, 0x72, 0x6c, 0x12, 0x44, 0x0a, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x43, 0x50,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42,
0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x50, 0x0a, 0x15, 0x65, 0x6e, 0x61,
0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x4d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65,
0x72, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x52, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70,
0x65, 0x4d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x61,
0x6c, 0x6c, 0x6f, 0x77, 0x4d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x18,
0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4d, 0x63, 0x70, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x4f, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73,
0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x4e,
0x61, 0x6d, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x5c, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73,
0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49,
0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0xdb, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x12, 0x17, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02,
0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
0x23, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x50, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72,
0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6c, 0x69, 0x73, 0x74,
0x65, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
0x22, 0x93, 0x01, 0x0a, 0x08, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x12, 0x4a, 0x0a,
0x09, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2d, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61,
0x70, 0x2e, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x08, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x61, 0x70, 0x1a, 0x3b, 0x0a, 0x0d, 0x49, 0x6e, 0x6e,
0x65, 0x72, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67,
0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x69, 0x6e, 0x67, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -512,27 +634,29 @@ func file_networking_v1_mcp_bridge_proto_rawDescGZIP() []byte {
return file_networking_v1_mcp_bridge_proto_rawDescData
}
var file_networking_v1_mcp_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_networking_v1_mcp_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_networking_v1_mcp_bridge_proto_goTypes = []interface{}{
(*McpBridge)(nil), // 0: higress.networking.v1.McpBridge
(*RegistryConfig)(nil), // 1: higress.networking.v1.RegistryConfig
(*InnerMap)(nil), // 2: higress.networking.v1.InnerMap
nil, // 3: higress.networking.v1.RegistryConfig.MetadataEntry
nil, // 4: higress.networking.v1.InnerMap.InnerMapEntry
(*wrappers.BoolValue)(nil), // 5: google.protobuf.BoolValue
(*ProxyConfig)(nil), // 2: higress.networking.v1.ProxyConfig
(*InnerMap)(nil), // 3: higress.networking.v1.InnerMap
nil, // 4: higress.networking.v1.RegistryConfig.MetadataEntry
nil, // 5: higress.networking.v1.InnerMap.InnerMapEntry
(*wrappers.BoolValue)(nil), // 6: google.protobuf.BoolValue
}
var file_networking_v1_mcp_bridge_proto_depIdxs = []int32{
1, // 0: higress.networking.v1.McpBridge.registries:type_name -> higress.networking.v1.RegistryConfig
5, // 1: higress.networking.v1.RegistryConfig.enableMCPServer:type_name -> google.protobuf.BoolValue
5, // 2: higress.networking.v1.RegistryConfig.enableScopeMcpServers:type_name -> google.protobuf.BoolValue
3, // 3: higress.networking.v1.RegistryConfig.metadata:type_name -> higress.networking.v1.RegistryConfig.MetadataEntry
4, // 4: higress.networking.v1.InnerMap.inner_map:type_name -> higress.networking.v1.InnerMap.InnerMapEntry
2, // 5: higress.networking.v1.RegistryConfig.MetadataEntry.value:type_name -> higress.networking.v1.InnerMap
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
2, // 1: higress.networking.v1.McpBridge.proxies:type_name -> higress.networking.v1.ProxyConfig
6, // 2: higress.networking.v1.RegistryConfig.enableMCPServer:type_name -> google.protobuf.BoolValue
6, // 3: higress.networking.v1.RegistryConfig.enableScopeMcpServers:type_name -> google.protobuf.BoolValue
4, // 4: higress.networking.v1.RegistryConfig.metadata:type_name -> higress.networking.v1.RegistryConfig.MetadataEntry
5, // 5: higress.networking.v1.InnerMap.inner_map:type_name -> higress.networking.v1.InnerMap.InnerMapEntry
3, // 6: higress.networking.v1.RegistryConfig.MetadataEntry.value:type_name -> higress.networking.v1.InnerMap
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_networking_v1_mcp_bridge_proto_init() }
@@ -566,6 +690,18 @@ func file_networking_v1_mcp_bridge_proto_init() {
}
}
file_networking_v1_mcp_bridge_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ProxyConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_networking_v1_mcp_bridge_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InnerMap); i {
case 0:
return &v.state
@@ -584,7 +720,7 @@ func file_networking_v1_mcp_bridge_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_networking_v1_mcp_bridge_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -46,6 +46,7 @@ option go_package = "github.com/alibaba/higress/api/networking/v1";
// -->
message McpBridge {
repeated RegistryConfig registries = 1;
repeated ProxyConfig proxies = 2;
}
message RegistryConfig {
@@ -74,6 +75,16 @@ message RegistryConfig {
google.protobuf.BoolValue enableScopeMcpServers = 23;
repeated string allowMcpServers = 24;
map<string, InnerMap> metadata = 25;
string proxyName = 26;
}
message ProxyConfig {
string type = 1 [(google.api.field_behavior) = REQUIRED];
string name = 2 [(google.api.field_behavior) = REQUIRED];
string serverAddress = 3 [(google.api.field_behavior) = REQUIRED];
uint32 serverPort = 4 [(google.api.field_behavior) = REQUIRED];
uint32 listenerPort = 5;
uint32 connectTimeout = 6;
}
message InnerMap {

View File

@@ -47,6 +47,27 @@ func (in *RegistryConfig) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using ProxyConfig within kubernetes types, where deepcopy-gen is used.
func (in *ProxyConfig) DeepCopyInto(out *ProxyConfig) {
p := proto.Clone(in).(*ProxyConfig)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfig. Required by controller-gen.
func (in *ProxyConfig) DeepCopy() *ProxyConfig {
if in == nil {
return nil
}
out := new(ProxyConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfig. Required by controller-gen.
func (in *ProxyConfig) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using InnerMap within kubernetes types, where deepcopy-gen is used.
func (in *InnerMap) DeepCopyInto(out *InnerMap) {
p := proto.Clone(in).(*InnerMap)

View File

@@ -28,6 +28,17 @@ func (this *RegistryConfig) UnmarshalJSON(b []byte) error {
return McpBridgeUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for ProxyConfig
func (this *ProxyConfig) MarshalJSON() ([]byte, error) {
str, err := McpBridgeMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for ProxyConfig
func (this *ProxyConfig) UnmarshalJSON(b []byte) error {
return McpBridgeUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for InnerMap
func (this *InnerMap) MarshalJSON() ([]byte, error) {
str, err := McpBridgeMarshaler.MarshalToString(this)

View File

@@ -95,6 +95,6 @@ generate-k8s-client:
.PHONY: clean-k8s-client
clean-k8s-cliennt:
clean-k8s-client:
# remove generated code
@rm -rf pkg/

View File

@@ -6,11 +6,11 @@ ARG BASE_VERSION=latest
ARG HUB
ARG TARGETARCH
# The following section is used as base image if BASE_DISTRIBUTION=debug
# This base image is provided by istio, see: https://github.com/istio/istio/blob/master/docker/Dockerfile.base
FROM ${HUB}/base:${BASE_VERSION}
ARG TARGETARCH
FROM ${HUB}/base:${BASE_VERSION}-${TARGETARCH}
COPY ${TARGETARCH:-amd64}/higress /usr/local/bin/higress

View File

@@ -17,6 +17,11 @@ docker.higress: $(OUT_LINUX)/higress
docker.higress: docker/Dockerfile.higress
$(HIGRESS_DOCKER_RULE)
docker.higress-amd64: BUILD_ARGS=--build-arg BASE_VERSION=${HIGRESS_BASE_VERSION} --build-arg HUB=${HUB}
docker.higress-amd64: $(AMD64_OUT_LINUX)/higress
docker.higress-amd64: docker/Dockerfile.higress
$(HIGRESS_DOCKER_AMD64_RULE)
docker.higress-buildx: BUILD_ARGS=--build-arg BASE_VERSION=${HIGRESS_BASE_VERSION} --build-arg HUB=${HUB}
docker.higress-buildx: $(AMD64_OUT_LINUX)/higress
docker.higress-buildx: $(ARM64_OUT_LINUX)/higress
@@ -40,3 +45,4 @@ IMG_URL ?= $(HUB)/$(IMG):$(TAG)
HIGRESS_DOCKER_BUILDX_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(HIGRESS_DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./docker/docker-copy.sh $^ $(HIGRESS_DOCKER_BUILD_TOP)/$@ && cd $(HIGRESS_DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker buildx create --name higress --node higress0 --platform linux/amd64,linux/arm64 --use && docker buildx build --no-cache --platform linux/amd64,linux/arm64 $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(IMG_URL)$(call variant-tag,$(VARIANT)) -f Dockerfile.higress . --push ); )
HIGRESS_DOCKER_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(HIGRESS_DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./docker/docker-copy.sh $^ $(HIGRESS_DOCKER_BUILD_TOP)/$@ && cd $(HIGRESS_DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker build $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(IMG_URL)$(call variant-tag,$(VARIANT)) -f Dockerfile.higress . ); )
HIGRESS_DOCKER_AMD64_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(HIGRESS_DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=amd64 ./docker/docker-copy.sh $^ $(HIGRESS_DOCKER_BUILD_TOP)/$@ && cd $(HIGRESS_DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker build $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) --build-arg TARGETARCH=amd64 -t $(IMG_URL)$(call variant-tag,$(VARIANT)) -f Dockerfile.higress . ); )

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.5
appVersion: 2.1.7
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -15,4 +15,4 @@ dependencies:
repository: "file://../redis"
version: 0.0.1
type: application
version: 2.1.5
version: 2.1.7

View File

@@ -247,6 +247,23 @@ spec:
properties:
spec:
properties:
proxies:
items:
properties:
connectTimeout:
type: integer
listenerPort:
type: integer
name:
type: string
serverAddress:
type: string
serverPort:
type: integer
type:
type: string
type: object
type: array
registries:
items:
properties:
@@ -309,6 +326,8 @@ spec:
type: integer
protocol:
type: string
proxyName:
type: string
sni:
type: string
type:

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 2.1.5
version: 2.1.7
- name: higress-console
repository: https://higress.io/helm-charts/
version: 2.1.5
digest: sha256:1c7c8003686b2df2c67427054006aef21c92ab1ff86d2e5f5587daf02ebc7d61
generated: "2025-07-02T17:38:10.089494+08:00"
version: 2.1.7
digest: sha256:c5bc8ddcc56c66751217aee5c7a40da0a906bfa9fc5c671cc4ae6e456db6bc21
generated: "2025-09-01T15:19:26.228634+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.5
appVersion: 2.1.7
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 2.1.5
version: 2.1.7
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 2.1.5
version: 2.1.7
type: application
version: 2.1.5
version: 2.1.7

View File

@@ -704,9 +704,9 @@ func TestK8sObject_ResolveK8sConflict(t *testing.T) {
t.Run(tt.desc, func(t *testing.T) {
newObj := tt.o1.ResolveK8sConflict()
if !newObj.Equal(tt.o2) {
newObjjson, _ := newObj.JSON()
wantedObjjson, _ := tt.o2.JSON()
t.Errorf("Got: %s, want: %s", string(newObjjson), string(wantedObjjson))
newObjJson, _ := newObj.JSON()
wantedObjJson, _ := tt.o2.JSON()
t.Errorf("Got: %s, want: %s", string(newObjJson), string(wantedObjJson))
}
})
}

View File

@@ -65,7 +65,7 @@ func (o *K8sInstaller) Install() error {
return err1
}
fmt.Fprintf(o.writer, "\n✔ Wrote Profile in kubernetes configmap: \"%s\" \n", profileName)
fmt.Fprintf(o.writer, "\n Use bellow kubectl command to edit profile for upgrade. \n")
fmt.Fprintf(o.writer, "\n Use below kubectl command to edit profile for upgrade. \n")
fmt.Fprintf(o.writer, " ================================================================================== \n")
names := strings.Split(profileName, "/")
fmt.Fprintf(o.writer, " kubectl edit configmap %s -n %s \n", names[1], names[0])

View File

@@ -33,7 +33,8 @@ import (
"github.com/tidwall/gjson"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/wrapper"
logs "github.com/higress-group/wasm-go/pkg/log"
)
func main() {
@@ -72,13 +73,13 @@ type PluginConfig struct {
secondField string ` + "`required:\"true\"`" + `
}
func parseConfig(json gjson.Result, config *PluginConfig, log wrapper.Log) error {
func parseConfig(json gjson.Result, config *PluginConfig, log logs.Log) error {
config.firstField = json.Get("firstField").String()
config.secondField = json.Get("secondField").String()
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log logs.Log) types.Action {
err := proxywasm.AddHttpRequestHeader(config.firstField, config.secondField)
if err != nil {
log.Critical("failed to set request header")
@@ -90,10 +91,10 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrap
module {{ .Name }}
go 1.19
go 1.24
require (
github.com/alibaba/higress/plugins/wasm-go main
github.com/higress-group/wasm-go main
github.com/higress-group/proxy-wasm-go-sdk main
github.com/tidwall/gjson v1.14.3
)

View File

@@ -93,6 +93,15 @@ func (p Protocol) IsUnsupported() bool {
return p == Unsupported
}
func (p Protocol) IsSupportedByProxy() bool {
switch p {
case HTTPS:
return true
default:
return false
}
}
func (p Protocol) String() string {
return string(p)
}

59
pkg/common/proxy.go Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package common
import (
"strings"
)
type ProxyType string
const (
ProxyType_Unknown ProxyType = "Unknown"
ProxyType_HTTP ProxyType = "HTTP"
ProxyType_HTTPS ProxyType = "HTTPS"
ProxyType_SOCKS4 ProxyType = "SOCKS4"
ProxyType_SOCKS5 ProxyType = "SOCKS5"
)
func ParseProxyType(s string) ProxyType {
switch strings.ToLower(s) {
case "http":
return ProxyType_HTTP
case "https":
return ProxyType_HTTPS
case "socks4":
return ProxyType_SOCKS4
case "socks5":
return ProxyType_SOCKS5
}
return ProxyType_Unknown
}
func (p ProxyType) GetTransportProtocol() Protocol {
switch p {
case ProxyType_HTTP:
return HTTP
case ProxyType_HTTPS:
return HTTPS
case ProxyType_SOCKS4, ProxyType_SOCKS5:
return TCP
}
return Unsupported
}
func (p ProxyType) String() string {
return string(p)
}

View File

@@ -69,7 +69,7 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/wasmplugin"
. "github.com/alibaba/higress/pkg/ingress/log"
"github.com/alibaba/higress/pkg/kube"
"github.com/alibaba/higress/registry/memory"
"github.com/alibaba/higress/registry"
"github.com/alibaba/higress/registry/reconcile"
)
@@ -340,10 +340,6 @@ func (m *IngressConfig) listFromIngressControllers(typ config.GroupVersionKind,
}
IngressLog.Infof("Append %d configmap EnvoyFilters", len(configmapEnvoyFilters))
}
if len(envoyFilters) == 0 {
IngressLog.Infof("resource type %s, configs number %d", typ, len(m.cachedEnvoyFilters))
return m.cachedEnvoyFilters
}
envoyFilters = append(envoyFilters, m.cachedEnvoyFilters...)
IngressLog.Infof("resource type %s, configs number %d", typ, len(envoyFilters))
return envoyFilters
@@ -490,6 +486,22 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
VirtualServices: map[string]*common.WrapperVirtualService{},
HTTPRoutes: map[string][]*common.WrapperHTTPRoute{},
Route2Ingress: map[string]*common.WrapperConfigWithRuleKey{},
ServiceWrappers: make(map[string]*common.ServiceWrapper),
ProxyWrappers: make(map[string]*common.ProxyWrapper),
}
if m.RegistryReconciler != nil {
for _, sew := range m.RegistryReconciler.GetAllServiceWrapper() {
hosts := sew.ServiceEntry.Hosts
if len(hosts) == 0 {
continue
}
for _, host := range hosts {
convertOptions.ServiceWrappers[host] = sew
}
}
for _, pw := range m.RegistryReconciler.GetAllProxyWrapper() {
convertOptions.ProxyWrappers[pw.ProxyName] = pw
}
}
// convert http route
@@ -616,6 +628,7 @@ func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions
mappings := map[string]*common.Rule{}
initHttp2RpcGlobalConfig := true
initMcpSseGlobalFilter := true
for _, routes := range convertOptions.HTTPRoutes {
for _, route := range routes {
if strings.HasSuffix(route.HTTPRoute.Name, "app-root") {
@@ -635,6 +648,19 @@ func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions
}
}
loadBalance := route.WrapperConfig.AnnotationsConfig.LoadBalance
if loadBalance != nil && loadBalance.McpSseStateful {
IngressLog.Infof("Found MCP SSE stateful session for route %s", route.HTTPRoute.Name)
envoyFilter, err := m.constructMcpSseStatefulSessionEnvoyFilter(route, m.namespace, initMcpSseGlobalFilter)
if err != nil {
IngressLog.Errorf("Construct MCP SSE stateful session EnvoyFilter error %v", err)
} else {
IngressLog.Infof("Append MCP SSE stateful session EnvoyFilter for route %s", route.HTTPRoute.Name)
envoyFilters = append(envoyFilters, *envoyFilter)
initMcpSseGlobalFilter = false
}
}
auth := route.WrapperConfig.AnnotationsConfig.Auth
if auth == nil {
continue
@@ -669,6 +695,12 @@ func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions
}
}
if proxyEnvoyFilters := constructProxyEnvoyFilters(convertOptions.ProxyWrappers, convertOptions.ServiceWrappers, m.namespace); len(proxyEnvoyFilters) != 0 {
for _, ef := range proxyEnvoyFilters {
envoyFilters = append(envoyFilters, *ef)
}
}
// TODO Support other envoy filters
IngressLog.Infof("Found %d number of envoyFilters", len(envoyFilters))
@@ -1113,7 +1145,7 @@ func (m *IngressConfig) AddOrUpdateWasmPlugin(clusterNamespacedName util.Cluster
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
}
for _, f := range m.wasmPluginHandlers {
IngressLog.Debug("WasmPlugin triggerd update")
IngressLog.Debug("WasmPlugin triggered update")
f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, istiomodel.EventUpdate)
}
istioWasmPlugin, err := m.convertIstioWasmPlugin(&wasmPlugin.Spec)
@@ -1155,7 +1187,7 @@ func (m *IngressConfig) DeleteWasmPlugin(clusterNamespacedName util.ClusterNames
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
}
for _, f := range m.wasmPluginHandlers {
IngressLog.Debug("WasmPlugin triggerd update")
IngressLog.Debug("WasmPlugin triggered update")
f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, istiomodel.EventDelete)
}
}
@@ -1211,23 +1243,23 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
}
for _, f := range m.serviceEntryHandlers {
IngressLog.Debug("McpBridge triggerd serviceEntry update")
IngressLog.Debug("McpBridge triggered serviceEntry update")
f(config.Config{Meta: seMetadata}, config.Config{Meta: seMetadata}, istiomodel.EventUpdate)
}
for _, f := range m.destinationRuleHandlers {
IngressLog.Debug("McpBridge triggerd destinationRule update")
IngressLog.Debug("McpBridge triggered destinationRule update")
f(config.Config{Meta: drMetadata}, config.Config{Meta: drMetadata}, istiomodel.EventUpdate)
}
for _, f := range m.virtualServiceHandlers {
IngressLog.Debug("McpBridge triggerd virtualservice update")
IngressLog.Debug("McpBridge triggered virtualservice update")
f(config.Config{Meta: vsMetadata}, config.Config{Meta: vsMetadata}, istiomodel.EventUpdate)
}
for _, f := range m.wasmPluginHandlers {
IngressLog.Debug("McpBridge triggerd wasmplugin update")
IngressLog.Debug("McpBridge triggered wasmplugin update")
f(config.Config{Meta: wasmMetadata}, config.Config{Meta: wasmMetadata}, istiomodel.EventUpdate)
}
for _, f := range m.envoyFilterHandlers {
IngressLog.Debug("McpBridge triggerd envoyfilter update")
IngressLog.Debug("McpBridge triggered envoyfilter update")
f(config.Config{Meta: efMetadata}, config.Config{Meta: efMetadata}, istiomodel.EventUpdate)
}
}, m.localKubeClient, m.namespace, m.clusterId.String())
@@ -1295,7 +1327,7 @@ func (m *IngressConfig) DeleteHttp2Rpc(clusterNamespacedName util.ClusterNamespa
}
m.mutex.Unlock()
if hit {
IngressLog.Infof("Http2Rpc triggerd deleted event executed %s", clusterNamespacedName.Name)
IngressLog.Infof("Http2Rpc triggered deleted event executed %s", clusterNamespacedName.Name)
push := func(gvk config.GroupVersionKind) {
m.XDSUpdater.ConfigUpdate(&istiomodel.PushRequest{
Full: true,
@@ -1493,7 +1525,7 @@ func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations
return &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, http2rpcConfig.Name),
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, "http2rpc", http2rpcConfig.Name, "route", common.ConvertToDNSLabelValid(httpRoute.Name)),
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
@@ -1675,28 +1707,150 @@ func constructBasicAuthEnvoyFilter(rules *common.BasicAuthRules, namespace strin
}, nil
}
func QueryByName(serviceEntries []*memory.ServiceWrapper, serviceName string) (*memory.ServiceWrapper, error) {
IngressLog.Infof("Found http2rpc serviceEntries %s", serviceEntries)
for _, se := range serviceEntries {
if se.ServiceName == serviceName {
return se, nil
func constructProxyEnvoyFilters(proxyWrappers map[string]*common.ProxyWrapper, serviceWrappers map[string]*common.ServiceWrapper, namespace string) []*config.Config {
var envoyFilters []*config.Config
for _, proxyWrapper := range proxyWrappers {
envoyFilters = append(envoyFilters, &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, "proxy", proxyWrapper.ProxyName),
Namespace: namespace,
},
Spec: proxyWrapper.EnvoyFilter,
})
}
// Create a cluster for each service that uses a proxy.
var serviceProxyPatches []*networking.EnvoyFilter_EnvoyConfigObjectPatch
for _, serviceWrapper := range serviceWrappers {
proxyConfig := serviceWrapper.ProxyConfig
if proxyConfig == nil || proxyConfig.ProxyName == "" {
continue
}
IngressLog.Debugf("Found service %s using proxy %s", serviceWrapper.ServiceName, proxyConfig.ProxyName)
if err := validateServiceWrapperForProxy(serviceWrapper); err != nil {
IngressLog.Warnf("Service wrapper validation failed for proxy: %v", err)
continue
}
proxyWrapper := proxyWrappers[proxyConfig.ProxyName]
if proxyWrapper == nil {
IngressLog.Warnf("Service %s has proxy config %s, but no corresponding proxy wrapper found", serviceWrapper.ServiceName, proxyConfig.ProxyName)
continue
}
if !proxyConfig.UpstreamProtocol.IsSupportedByProxy() {
IngressLog.Warnf("Proxy %s does not support upstream protocol %s, skipping EnvoyFilter construction for service %s")
continue
}
if proxyWrapper.EnvoyFilter == nil {
IngressLog.Warnf("Proxy %s has no EnvoyFilter generated, meaning not ready for use.", proxyConfig.ProxyName)
continue
}
se := serviceWrapper.ServiceEntry
if se == nil || len(se.Hosts) == 0 || len(se.Ports) == 0 {
continue
}
for _, host := range se.Hosts {
IngressLog.Debugf("Constructing EnvoyFilter for service %s using proxy %s", host, proxyConfig.ProxyName)
for _, port := range se.Ports {
if port == nil || port.Number <= 0 {
continue
}
clusterName := fmt.Sprintf("outbound|%d||%s", port.Number, host)
// We need to delete the original cluster and add a new one pointing to the local proxy listener.
serviceProxyPatches = append(serviceProxyPatches, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_CLUSTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
Cluster: &networking.EnvoyFilter_ClusterMatch{
Name: clusterName,
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_REMOVE,
},
})
patchObj := map[string]interface{}{
"name": clusterName,
"type": "STATIC",
"connect_timeout": "10s",
"load_assignment": map[string]interface{}{
"cluster_name": clusterName,
"endpoints": []map[string]interface{}{
{
"lb_endpoints": []map[string]interface{}{
{
"endpoint": map[string]interface{}{
"address": map[string]interface{}{
"socket_address": map[string]interface{}{
"address": "127.0.0.1",
"port_value": proxyWrapper.ListenerPort,
},
},
},
},
},
},
},
},
}
if proxyConfig.UpstreamProtocol.IsHTTPS() {
tlsTypedConfig := map[string]interface{}{
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
}
if proxyConfig.UpstreamSni != "" {
tlsTypedConfig["sni"] = proxyConfig.UpstreamSni
}
patchObj["transport_socket"] = map[string]interface{}{
"name": "envoy.transport_sockets.tls",
"typed_config": tlsTypedConfig,
}
}
patchJson, _ := json.Marshal(patchObj)
serviceProxyPatches = append(serviceProxyPatches, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_CLUSTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_ADD,
Value: util.BuildPatchStruct(string(patchJson)),
},
})
}
}
}
return nil, fmt.Errorf("can't find ServiceEntry by serviceName:%v", serviceName)
if len(serviceProxyPatches) != 0 {
envoyFilters = append(envoyFilters, &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, "service-proxy"),
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: serviceProxyPatches,
},
})
}
return envoyFilters
}
func QueryRpcServiceVersion(serviceEntry *memory.ServiceWrapper, serviceName string) (string, error) {
IngressLog.Infof("Found http2rpc serviceEntry %s", serviceEntry)
IngressLog.Infof("Found http2rpc ServiceEntry %s", serviceEntry.ServiceEntry)
IngressLog.Infof("Found http2rpc WorkloadSelector %s", serviceEntry.ServiceEntry.WorkloadSelector)
IngressLog.Infof("Found http2rpc Labels %s", serviceEntry.ServiceEntry.WorkloadSelector.Labels)
labels := (*serviceEntry).ServiceEntry.WorkloadSelector.Labels
for key, value := range labels {
if key == "version" {
return value, nil
}
func validateServiceWrapperForProxy(serviceWrapper *common.ServiceWrapper) error {
registryType := registry.ServiceRegistryType(serviceWrapper.RegistryType)
switch registryType {
case registry.DNS:
break
default:
return fmt.Errorf("service %s has proxy config %s, but registry type %s is not supported for proxying", serviceWrapper.ServiceName, serviceWrapper.ProxyConfig.ProxyName, registryType)
}
return "", fmt.Errorf("can't get RpcServiceVersion for serviceName:%v", serviceName)
if len(serviceWrapper.ServiceEntry.Endpoints) > 1 {
return fmt.Errorf("service %s has multiple endpoints, which is not supported for proxying with EnvoyFilter. Skipping EnvoyFilter construction", serviceWrapper.ServiceName)
}
return nil
}
func (m *IngressConfig) Run(stop <-chan struct{}) {
@@ -1800,6 +1954,99 @@ func (m *IngressConfig) Delete(config.GroupVersionKind, string, string, *string)
return common.ErrUnsupportedOp
}
func (m *IngressConfig) constructMcpSseStatefulSessionEnvoyFilter(route *common.WrapperHTTPRoute, namespace string, initGlobalFilter bool) (*config.Config, error) {
httpRoute := route.HTTPRoute
var configPatches []*networking.EnvoyFilter_EnvoyConfigObjectPatch
// Add global HTTP filter if this is the first route using MCP SSE stateful session
if initGlobalFilter {
configPatches = append(configPatches, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
Value: buildPatchStruct(`{
"name": "envoy.filters.http.mcp_sse_stateful_session",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSession"
}
}`),
},
})
}
// Add route-specific configuration
configPatches = append(configPatches, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{
RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{
Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{
Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{
Name: httpRoute.Name,
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: buildPatchStruct(`{
"typed_per_filter_config": {
"envoy.filters.http.mcp_sse_stateful_session": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSessionPerRoute",
"value": {
"mcp_sse_stateful_session": {
"session_state": {
"name": "envoy.http.mcp_sse_stateful_session.envelope",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.http.mcp_sse_stateful_session.envelope.v3alpha.EnvelopeSessionState",
"value": {
"param_name": "sessionId",
"chunk_end_patterns": ["\r\n\r\n", "\n\n", "\r\r"]
}
}
},
"strict": true
}
}
}
}
}`),
},
})
return &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, "mcp-lb-route", common.ConvertToDNSLabelValid(httpRoute.Name)),
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: configPatches,
},
}, nil
}
func (m *IngressConfig) notifyXDSFullUpdate(gvk config.GroupVersionKind, reason istiomodel.TriggerReason, updatedConfigName *util.ClusterNamespacedName) {
var configsUpdated map[istiomodel.ConfigKey]struct{}
if updatedConfigName != nil {

View File

@@ -66,9 +66,10 @@ type consistentHashByCookie struct {
}
type LoadBalanceConfig struct {
simple networking.LoadBalancerSettings_SimpleLB
other *consistentHashByOther
cookie *consistentHashByCookie
simple networking.LoadBalancerSettings_SimpleLB
other *consistentHashByOther
cookie *consistentHashByCookie
McpSseStateful bool
}
type loadBalance struct{}
@@ -129,7 +130,11 @@ func (l loadBalance) Parse(annotations Annotations, config *Ingress, _ *GlobalCo
} else {
if lb, err := annotations.ParseStringASAP(loadBalanceAnnotation); err == nil {
lb = strings.ToUpper(lb)
loadBalanceConfig.simple = networking.LoadBalancerSettings_SimpleLB(networking.LoadBalancerSettings_SimpleLB_value[lb])
if lb == "MCP-SSE" {
loadBalanceConfig.McpSseStateful = true
} else {
loadBalanceConfig.simple = networking.LoadBalancerSettings_SimpleLB(networking.LoadBalancerSettings_SimpleLB_value[lb])
}
}
}

View File

@@ -22,8 +22,10 @@ import (
)
const (
mirrorTargetService = "mirror-target-service"
mirrorPercentage = "mirror-percentage"
mirrorTargetService = "mirror-target-service"
mirrorPercentage = "mirror-percentage"
mirrorTargetFQDN = "mirror-target-fqdn"
mirrorTargetFQDNPort = "mirror-target-fqdn-port"
)
var (
@@ -34,6 +36,8 @@ var (
type MirrorConfig struct {
util.ServiceInfo
Percentage *wrappers.DoubleValue
FQDN string
FPort uint32 // Port for FQDN
}
type mirror struct{}
@@ -43,6 +47,24 @@ func (m mirror) Parse(annotations Annotations, config *Ingress, globalContext *G
return nil
}
// if FQDN is set, then parse FQDN
if fqdn, err := annotations.ParseStringASAP(mirrorTargetFQDN); err == nil {
// default is 80
var port uint32
port = 80
if p, err := annotations.ParseInt32ASAP(mirrorTargetFQDNPort); err == nil {
port = uint32(p)
}
config.Mirror = &MirrorConfig{
Percentage: parsePercentage(annotations),
FQDN: fqdn,
FPort: port,
}
return nil
}
target, err := annotations.ParseStringASAP(mirrorTargetService)
if err != nil {
IngressLog.Errorf("Get mirror target service fail, err: %v", err)
@@ -78,7 +100,16 @@ func (m mirror) Parse(annotations Annotations, config *Ingress, globalContext *G
serviceInfo.Port = uint32(service.Spec.Ports[0].Port)
}
config.Mirror = &MirrorConfig{
ServiceInfo: serviceInfo,
Percentage: parsePercentage(annotations),
}
return nil
}
func parsePercentage(annotations Annotations) *wrappers.DoubleValue {
var percentage *wrappers.DoubleValue
if value, err := annotations.ParseIntASAP(mirrorPercentage); err == nil {
if value < 100 {
percentage = &wrappers.DoubleValue{
@@ -86,12 +117,7 @@ func (m mirror) Parse(annotations Annotations, config *Ingress, globalContext *G
}
}
}
config.Mirror = &MirrorConfig{
ServiceInfo: serviceInfo,
Percentage: percentage,
}
return nil
return percentage
}
func (m mirror) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
@@ -99,10 +125,21 @@ func (m mirror) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
return
}
var mirrorHost string
var mirrorPort uint32
if config.Mirror.FQDN != "" {
mirrorHost = config.Mirror.FQDN
mirrorPort = config.Mirror.FPort
} else {
mirrorHost = util.CreateServiceFQDN(config.Mirror.Namespace, config.Mirror.Name)
mirrorPort = config.Mirror.Port
}
route.Mirror = &networking.Destination{
Host: util.CreateServiceFQDN(config.Mirror.Namespace, config.Mirror.Name),
Host: mirrorHost,
Port: &networking.PortSelector{
Number: config.Mirror.Port,
Number: mirrorPort,
},
}
@@ -114,5 +151,5 @@ func (m mirror) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
}
func needMirror(annotations Annotations) bool {
return annotations.HasASAP(mirrorTargetService)
return annotations.HasASAP(mirrorTargetService) || annotations.HasASAP(mirrorTargetFQDN)
}

View File

@@ -15,12 +15,13 @@
package annotations
import (
"reflect"
"testing"
"github.com/alibaba/higress/pkg/ingress/kube/util"
"github.com/golang/protobuf/proto"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/model"
"reflect"
"testing"
)
func TestParseMirror(t *testing.T) {
@@ -29,6 +30,28 @@ func TestParseMirror(t *testing.T) {
expect *MirrorConfig
}{
{},
{
input: []map[string]string{
{buildHigressAnnotationKey(mirrorTargetFQDN): "www.example.com"},
{buildNginxAnnotationKey(mirrorTargetFQDN): "www.example.com"},
},
expect: &MirrorConfig{
ServiceInfo: util.ServiceInfo{},
FQDN: "www.example.com",
FPort: 80,
},
},
{
input: []map[string]string{
{buildHigressAnnotationKey(mirrorTargetFQDN): "192.168.252.112", buildHigressAnnotationKey(mirrorTargetFQDNPort): "8080"},
{buildNginxAnnotationKey(mirrorTargetFQDN): "192.168.252.112", buildNginxAnnotationKey(mirrorTargetFQDNPort): "8080"},
},
expect: &MirrorConfig{
ServiceInfo: util.ServiceInfo{},
FQDN: "192.168.252.112",
FPort: 8080,
},
},
{
input: []map[string]string{
{buildHigressAnnotationKey(mirrorTargetService): "test/app"},
@@ -149,6 +172,42 @@ func TestMirror_ApplyRoute(t *testing.T) {
},
},
},
{
config: &Ingress{
Mirror: &MirrorConfig{
ServiceInfo: util.ServiceInfo{},
FQDN: "www.example.com",
FPort: 80,
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Mirror: &networking.Destination{
Host: "www.example.com",
Port: &networking.PortSelector{
Number: 80,
},
},
},
},
{
config: &Ingress{
Mirror: &MirrorConfig{
ServiceInfo: util.ServiceInfo{},
FQDN: "192.168.252.112",
FPort: 8080,
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Mirror: &networking.Destination{
Host: "192.168.252.112",
Port: &networking.PortSelector{
Number: 8080,
},
},
},
},
}
mirror := mirror{}

View File

@@ -16,9 +16,8 @@ package common
import (
"strings"
"time"
"github.com/alibaba/higress/pkg/cert"
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/cluster"
@@ -26,6 +25,10 @@ import (
gatewaytool "istio.io/istio/pkg/config/gateway"
listerv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"github.com/alibaba/higress/pkg/cert"
"github.com/alibaba/higress/pkg/common"
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
)
type ServiceKey struct {
@@ -120,6 +123,68 @@ type WrapperDestinationRule struct {
ServiceKey ServiceKey
}
type ServiceProxyConfig struct {
ProxyName string
UpstreamProtocol common.Protocol
UpstreamSni string
}
type ServiceWrapper struct {
ServiceName string
ServiceEntry *networking.ServiceEntry
DestinationRuleWrapper *WrapperDestinationRule
Suffix string
RegistryType string
RegistryName string
ProxyConfig *ServiceProxyConfig
createTime time.Time
}
func (sew *ServiceWrapper) DeepCopy() *ServiceWrapper {
res := &ServiceWrapper{}
*res = *sew
res.ServiceEntry = sew.ServiceEntry.DeepCopy()
if sew.DestinationRuleWrapper != nil {
res.DestinationRuleWrapper = sew.DestinationRuleWrapper
res.DestinationRuleWrapper.DestinationRule = sew.DestinationRuleWrapper.DestinationRule.DeepCopy()
}
return res
}
func (sew *ServiceWrapper) SetCreateTime(createTime time.Time) {
sew.createTime = createTime
}
func (sew *ServiceWrapper) GetCreateTime() time.Time {
return sew.createTime
}
type ProxyWrapper struct {
ProxyName string
ListenerPort uint32
EnvoyFilter *networking.EnvoyFilter
createTime time.Time
}
func (pw *ProxyWrapper) DeepCopy() *ProxyWrapper {
res := &ProxyWrapper{}
*res = *pw
if pw.EnvoyFilter != nil {
res.EnvoyFilter = pw.EnvoyFilter.DeepCopy()
}
return res
}
func (pw *ProxyWrapper) SetCreateTime(createTime time.Time) {
pw.createTime = createTime
}
func (pw *ProxyWrapper) GetCreateTime() time.Time {
return pw.createTime
}
type IngressController interface {
// RegisterEventHandler adds a handler to receive config update events for a
// configuration type

View File

@@ -169,6 +169,10 @@ type ConvertOptions struct {
Service2TrafficPolicy map[ServiceKey]*WrapperTrafficPolicy
ServiceWrappers map[string]*ServiceWrapper
ProxyWrappers map[string]*ProxyWrapper
HasDefaultBackend bool
}

View File

@@ -146,7 +146,7 @@ func GetHost(annotations map[string]string) string {
// Istio requires that the name of the gateway must conform to the DNS label.
// For details, you can view: https://github.com/istio/istio/blob/2d5c40ad5e9cceebe64106005aa38381097da2ba/pkg/config/validation/validation.go#L478
func convertToDNSLabelValid(input string) string {
func ConvertToDNSLabelValid(input string) string {
hasher := md5.New()
hasher.Write([]byte(input))
hash := hasher.Sum(nil)
@@ -156,7 +156,7 @@ func convertToDNSLabelValid(input string) string {
// CleanHost follow the format of mse-ops for host.
func CleanHost(host string) string {
return convertToDNSLabelValid(host)
return ConvertToDNSLabelValid(host)
}
func CreateConvertedName(items ...string) string {

View File

@@ -158,7 +158,7 @@ func (c *ConfigmapMgr) AddOrUpdateHigressConfig(name util.ClusterNamespacedName)
IngressLog.Infof("configmapMgr oldHigressConfig: %s", GetHigressConfigString(oldHigressConfig))
IngressLog.Infof("configmapMgr newHigressConfig: %s", GetHigressConfigString(newHigressConfig))
result, _ := c.CompareHigressConfig(oldHigressConfig, newHigressConfig)
IngressLog.Infof("configmapMgr CompareHigressConfig reuslt is %d", result)
IngressLog.Infof("configmapMgr CompareHigressConfig result is %d", result)
if result == ResultNothing {
return
@@ -177,7 +177,7 @@ func (c *ConfigmapMgr) AddOrUpdateHigressConfig(name util.ClusterNamespacedName)
}
}
c.SetHigressConfig(newHigressConfig)
IngressLog.Infof("configmapMgr higress config AddOrUpdate success, reuslt is %d", result)
IngressLog.Infof("configmapMgr higress config AddOrUpdate success, result is %d", result)
// Call updateConfig
}

View File

@@ -509,6 +509,11 @@ func (m *McpServerController) constructMcpSessionStruct(mcp *McpServer) string {
}
func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
// if no servers, return empty string
if mcp == nil || len(mcp.Servers) == 0 {
return ""
}
// Build servers configuration
servers := "[]"
if len(mcp.Servers) > 0 {

View File

@@ -566,7 +566,7 @@ func TestMcpServerController_ConstructEnvoyFilters(t *testing.T) {
MatchList: []*MatchRule{},
Servers: []*SSEServer{},
},
wantConfigs: 2, // Both session and server filters
wantConfigs: 1, // Only session filter when no servers configured
wantErr: nil,
},
}
@@ -744,24 +744,7 @@ func TestMcpServerController_constructMcpServerStruct(t *testing.T) {
mcp: &McpServer{
Servers: []*SSEServer{},
},
wantJSON: `{
"name": "envoy.filters.http.golang",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
"value": {
"library_id": "mcp-server",
"library_path": "/var/lib/istio/envoy/golang-filter.so",
"plugin_name": "mcp-server",
"plugin_config": {
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": {
"servers": []
}
}
}
}
}`,
wantJSON: "", // Return empty string when no servers configured
},
{
name: "with servers",

View File

@@ -286,7 +286,7 @@ func testConvertHTTPRoute(t *testing.T, c common.KIngressController) {
expectNoError: true,
},
{
description: "valid httpRoute convention, vaild ingress",
description: "valid httpRoute convention, valid ingress",
input: struct {
options *common.ConvertOptions
wrapperConfig *common.WrapperConfig

View File

@@ -17,7 +17,7 @@ RUN if [ "$GOARCH" = "arm64" ]; then \
else \
echo "Installing AMD64 toolchain" && \
apt-get update && \
apt-get install -y gcc binutils; \
apt-get install -y gcc-x86-64-linux-gnu binutils-x86-64-linux-gnu; \
fi
WORKDIR /workspace
@@ -30,7 +30,7 @@ RUN go mod tidy
RUN if [ "$GOARCH" = "arm64" ]; then \
CC=aarch64-linux-gnu-gcc AS=aarch64-linux-gnu-as go build -o /$GO_FILTER_NAME.so -buildmode=c-shared .; \
else \
go build -o /$GO_FILTER_NAME.so -buildmode=c-shared .; \
CC=x86_64-linux-gnu-gcc AS=x86_64-linux-gnu-as go build -o /$GO_FILTER_NAME.so -buildmode=c-shared .; \
fi
FROM scratch AS output

View File

@@ -54,8 +54,15 @@ http_filters:
## 快速构建
使用以下命令可以快速构建 golang filter 插件:
使用以下命令可以快速构建 golang filter 插件
```bash
make build
```
如果是 arm64 架构,请设置 `GOARCH=arm64`
```bash
make build GOARCH=arm64
```
你也可以直接在 Higress 项目的根目录下执行 `make build-gateway-local` 来构建 Higress Gateway 镜像,`golang-filter.so` 将会自动构建并复制到镜像中。

View File

@@ -58,4 +58,12 @@ Use the following command to quickly build the golang filter plugin:
```bash
make build
```
```
If you are on an arm64 architecture, please set `GOARCH=arm64`:
```bash
make build GOARCH=arm64
```
Alternatively, you can build the Higress Gateway image directly by running `make build-gateway-local` in the root directory of the Higress project. The `golang-filter.so` file will be automatically built and included in the image.

View File

@@ -5,6 +5,7 @@ import (
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/registry/nacos"
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/gorm"
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api"
mcp_session "github.com/alibaba/higress/plugins/golang-filter/mcp-session"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
xds "github.com/cncf/xds/go/xds/type/v3"
@@ -99,7 +100,7 @@ func (p *Parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (int
serverInstance, err := server.NewServer(serverName)
if err != nil {
return nil, fmt.Errorf("failed to initialize DBServer: %w", err)
return nil, fmt.Errorf("failed to initialize MCP Server: %w", err)
}
conf.servers = append(conf.servers, &SSEServerWrapper{

View File

@@ -57,12 +57,12 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.
}
func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
if !endStream {
return api.StopAndBuffer
}
if f.message {
for _, server := range f.config.servers {
if f.path == server.BaseServer.GetMessageEndpoint() {
if !endStream {
return api.StopAndBuffer
}
// Create a response recorder to capture the response
recorder := httptest.NewRecorder()
// Call the handleMessage method of SSEServer with complete body

View File

@@ -14,7 +14,7 @@ import (
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
type NacosMcpRegsitry struct {
type NacosMcpRegistry struct {
serviceMatcher map[string]string
configClient config_client.IConfigClient
namingClient naming_client.INamingClient
@@ -27,7 +27,7 @@ type NacosMcpRegsitry struct {
const DEFAULT_SERVICE_LIST_MAX_PGSIZXE = 10000
const MCP_TOOL_SUBFIX = "-mcp-tools.json"
func (n *NacosMcpRegsitry) ListToolsDesciption() []*registry.ToolDescription {
func (n *NacosMcpRegistry) ListToolsDescription() []*registry.ToolDescription {
if n.toolsDescription == nil {
n.refreshToolsList()
}
@@ -39,7 +39,7 @@ func (n *NacosMcpRegsitry) ListToolsDesciption() []*registry.ToolDescription {
return result
}
func (n *NacosMcpRegsitry) GetToolRpcContext(toolName string) (*registry.RpcContext, bool) {
func (n *NacosMcpRegistry) GetToolRpcContext(toolName string) (*registry.RpcContext, bool) {
if n.toolsRpcContext == nil {
n.refreshToolsList()
}
@@ -47,11 +47,11 @@ func (n *NacosMcpRegsitry) GetToolRpcContext(toolName string) (*registry.RpcCont
return tool, ok
}
func (n *NacosMcpRegsitry) RegisterToolChangeEventListener(listener registry.ToolChangeEventListener) {
func (n *NacosMcpRegistry) RegisterToolChangeEventListener(listener registry.ToolChangeEventListener) {
n.toolChangeEventListeners = append(n.toolChangeEventListeners, listener)
}
func (n *NacosMcpRegsitry) refreshToolsList() bool {
func (n *NacosMcpRegistry) refreshToolsList() bool {
changed := false
for group, serviceMatcher := range n.serviceMatcher {
if n.refreshToolsListForGroup(group, serviceMatcher) {
@@ -61,7 +61,7 @@ func (n *NacosMcpRegsitry) refreshToolsList() bool {
return changed
}
func (n *NacosMcpRegsitry) refreshToolsListForGroup(group string, serviceMatcher string) bool {
func (n *NacosMcpRegistry) refreshToolsListForGroup(group string, serviceMatcher string) bool {
services, err := n.namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
GroupName: group,
PageNo: 1,
@@ -77,7 +77,7 @@ func (n *NacosMcpRegsitry) refreshToolsListForGroup(group string, serviceMatcher
serviceList := services.Doms
pattern, err := regexp.Compile(serviceMatcher)
if err != nil {
api.LogErrorf("Match service error for patter %s", serviceMatcher)
api.LogErrorf("Match service error for pattern %s", serviceMatcher)
return false
}
@@ -134,7 +134,7 @@ func getFormatServiceName(group string, service string) string {
return fmt.Sprintf("%s_%s", group, service)
}
func (n *NacosMcpRegsitry) deleteToolForService(group string, service string) {
func (n *NacosMcpRegistry) deleteToolForService(group string, service string) {
toolsNeedReset := []string{}
formatServiceName := getFormatServiceName(group, service)
@@ -150,7 +150,7 @@ func (n *NacosMcpRegsitry) deleteToolForService(group string, service string) {
}
}
func (n *NacosMcpRegsitry) refreshToolsListForServiceWithContent(group string, service string, newConfig *string, instances *[]model.Instance) bool {
func (n *NacosMcpRegistry) refreshToolsListForServiceWithContent(group string, service string, newConfig *string, instances *[]model.Instance) bool {
if newConfig == nil {
dataId := makeToolsConfigId(service)
@@ -243,7 +243,7 @@ func (n *NacosMcpRegsitry) refreshToolsListForServiceWithContent(group string, s
return true
}
func (n *NacosMcpRegsitry) GetCredential(name string, group string) *registry.CredentialInfo {
func (n *NacosMcpRegistry) GetCredential(name string, group string) *registry.CredentialInfo {
dataId := makeCredentialDataId(name)
content, err := n.configClient.GetConfig(vo.ConfigParam{
DataId: dataId,
@@ -265,11 +265,11 @@ func (n *NacosMcpRegsitry) GetCredential(name string, group string) *registry.Cr
return &credential
}
func (n *NacosMcpRegsitry) refreshToolsListForService(group string, service string) bool {
func (n *NacosMcpRegistry) refreshToolsListForService(group string, service string) bool {
return n.refreshToolsListForServiceWithContent(group, service, nil, nil)
}
func (n *NacosMcpRegsitry) listenToService(group string, service string) {
func (n *NacosMcpRegistry) listenToService(group string, service string) {
// config changed, tools description may be changed
err := n.configClient.ListenConfig(vo.ConfigParam{

View File

@@ -35,7 +35,7 @@ func (l *McpServerToolsChangeListener) OnToolChanged(reg registry.McpServerRegis
resetToolsToMcpServer(l.mcpServer, reg)
}
func CreateNacosMcpRegsitry(config *NacosConfig) (*NacosMcpRegsitry, error) {
func CreateNacosMcpRegistry(config *NacosConfig) (*NacosMcpRegistry, error) {
sc := []constant.ServerConfig{
*constant.NewServerConfig(*config.ServerAddr, 8848, constant.WithContextPath("/nacos")),
}
@@ -90,7 +90,7 @@ func CreateNacosMcpRegsitry(config *NacosConfig) (*NacosMcpRegsitry, error) {
return nil, fmt.Errorf("failed to initial naming config client: %w", err)
}
return &NacosMcpRegsitry{
return &NacosMcpRegistry{
configClient: configClient,
namingClient: namingClient,
serviceMatcher: *config.ServiceMatcher,
@@ -143,7 +143,7 @@ func (c *NacosConfig) NewServer(serverName string) (*common.MCPServer, error) {
"1.0.0",
)
nacosRegistry, err := CreateNacosMcpRegsitry(c)
nacosRegistry, err := CreateNacosMcpRegistry(c)
if err != nil {
return nil, fmt.Errorf("failed to initialize NacosMcpRegistry: %w", err)
}
@@ -172,7 +172,7 @@ func (c *NacosConfig) NewServer(serverName string) (*common.MCPServer, error) {
func resetToolsToMcpServer(mcpServer *common.MCPServer, reg registry.McpServerRegistry) {
wrappedTools := []common.ServerTool{}
tools := reg.ListToolsDesciption()
tools := reg.ListToolsDescription()
for _, tool := range tools {
wrappedTools = append(wrappedTools, common.ServerTool{
Tool: mcp.NewToolWithRawSchema(tool.Name, tool.Description, tool.InputSchema),

View File

@@ -36,7 +36,7 @@ type ToolChangeEventListener interface {
}
type McpServerRegistry interface {
ListToolsDesciption() []*ToolDescription
ListToolsDescription() []*ToolDescription
GetToolRpcContext(toolname string) (*RpcContext, bool)
RegisterToolChangeEventListener(listener ToolChangeEventListener)
}

View File

@@ -177,7 +177,7 @@ func selectOneInstance(ctx *RpcContext) (*Instance, error) {
return &select_instance, nil
}
func getRemoteCallhandle(ctx *RpcContext) (RemoteCallHandle, error) {
func getRemoteCallHandle(ctx *RpcContext) (RemoteCallHandle, error) {
if ctx.Protocol == PROTOCOL_HTTP || ctx.Protocol == PROTOCOL_HTTPS {
return newHttpRemoteCallHandle(ctx)
} else {
@@ -192,7 +192,7 @@ func CommonRemoteCall(reg McpServerRegistry, toolName string, parameters map[str
return nil, fmt.Errorf("Unknown tool %s", toolName)
}
remoteHandle, err := getRemoteCallhandle(ctx)
remoteHandle, err := getRemoteCallHandle(ctx)
if remoteHandle == nil {
return nil, fmt.Errorf("Unknown backend protocol %s", ctx.Protocol)
}

View File

@@ -3,6 +3,7 @@ package gorm
import (
"context"
"fmt"
"strings"
"sync/atomic"
"time"
@@ -160,7 +161,6 @@ func (c *DBClient) handleSQLError(err error) error {
// DescribeTable Get the structure of a specific table.
func (c *DBClient) DescribeTable(table string) ([]map[string]interface{}, error) {
var sql string
var args []string
switch c.dbType {
case MYSQL:
sql = `
@@ -175,7 +175,7 @@ func (c *DBClient) DescribeTable(table string) ([]map[string]interface{}, error)
from information_schema.columns
where table_schema = database() and table_name = ?
`
args = []string{table}
return c.Query(sql, table)
case POSTGRES:
sql = `
@@ -184,20 +184,21 @@ func (c *DBClient) DescribeTable(table string) ([]map[string]interface{}, error)
data_type as column_type,
is_nullable,
case
when column_default like 'nextval%%' then 'auto_increment'
when column_default like 'nextval%' then 'auto_increment'
when column_default is not null then 'default'
else ''
end as column_key,
column_default,
case
when column_default like 'nextval%%' then 'auto_increment'
when column_default like 'nextval%' then 'auto_increment'
else ''
end as extra,
col_description((select oid from pg_class where relname = ?), ordinal_position) as column_comment
from information_schema.columns
where table_name = ?
`
args = []string{table, table}
lowerTable := strings.ToLower(table)
return c.Query(sql, lowerTable, lowerTable)
case CLICKHOUSE:
sql = `
@@ -212,7 +213,7 @@ func (c *DBClient) DescribeTable(table string) ([]map[string]interface{}, error)
from system.columns
where database = currentDatabase() and table = ?
`
args = []string{table}
return c.Query(sql, table)
case SQLITE:
sql = `
@@ -226,13 +227,11 @@ func (c *DBClient) DescribeTable(table string) ([]map[string]interface{}, error)
'' as column_comment
from pragma_table_info(?)
`
args = []string{table}
return c.Query(sql, table)
default:
return nil, fmt.Errorf("unsupported database type: %s", c.dbType)
}
return c.Query(sql, args)
}
// ListTables List all tables in the connected database.

View File

@@ -0,0 +1,95 @@
package higress
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
)
// HigressClient handles Higress Console API connections and operations
type HigressClient struct {
baseURL string
username string
password string
httpClient *http.Client
}
func NewHigressClient(baseURL, username, password string) *HigressClient {
client := &HigressClient{
baseURL: baseURL,
username: username,
password: password,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
api.LogInfof("Higress Console client initialized: %s", baseURL)
return client
}
func (c *HigressClient) Get(path string) ([]byte, error) {
return c.request("GET", path, nil)
}
func (c *HigressClient) Post(path string, data interface{}) ([]byte, error) {
return c.request("POST", path, data)
}
func (c *HigressClient) Put(path string, data interface{}) ([]byte, error) {
return c.request("PUT", path, data)
}
func (c *HigressClient) Delete(path string) ([]byte, error) {
return c.request("DELETE", path, nil)
}
func (c *HigressClient) request(method, path string, data interface{}) ([]byte, error) {
url := c.baseURL + path
var body io.Reader
if data != nil {
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal request data: %w", err)
}
body = bytes.NewBuffer(jsonData)
api.LogDebugf("Higress API %s %s: %s", method, url, string(jsonData))
} else {
api.LogDebugf("Higress API %s %s", method, url)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.SetBasicAuth(c.username, c.password)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("HTTP error %d", resp.StatusCode)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return respBody, nil
}

View File

@@ -0,0 +1,73 @@
# Higress API MCP Server
Higress API MCP Server 提供了 MCP 工具来管理 Higress 路由、服务来源和插件等资源。
## 功能特性
### 路由管理
- `list-routes`: 列出路由
- `get-route`: 获取路由
- `add-route`: 添加路由
- `update-route`: 更新路由
### 服务来源管理
- `list-service-sources`: 列出服务来源
- `get-service-source`: 获取服务来源
- `add-service-source`: 添加服务来源
- `update-service-source`: 更新服务来源
### 插件管理
- `get-plugin`: 获取插件配置
- `delete-plugin`: 删除插件
- `update-request-block-plugin`: 更新 request-block 插件配置
## 配置参数
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `higressURL` | string | 必填 | Higress Console 的 URL 地址 |
| `username` | string | 必填 | Higress Console 登录用户名 |
| `password` | string | 必填 | Higress Console 登录密码 |
| `description` | string | 可选 | 服务器描述信息 |
配置示例:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
annotations:
meta.helm.sh/release-name: higress
meta.helm.sh/release-namespace: higress-system
labels:
app: higress-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: higress-gateway
app.kubernetes.io/version: 2.1.4
helm.sh/chart: higress-core-2.1.4
higress: higress-system-higress-gateway
name: higress-config
namespace: higress-system
data:
higress: |-
mcpServer:
sse_path_suffix: /sse # SSE 连接的路径后缀
enable: true # 启用 MCP Server
redis:
address: redis-stack-server.higress-system.svc.cluster.local:6379 # Redis服务地址
username: "" # Redis用户名可选
password: "" # Redis密码可选
db: 0 # Redis数据库可选
match_list: # MCP Server 会话保持路由规则(当匹配下面路径时,将被识别为一个 MCP 会话,通过 SSE 等机制进行会话保持)
- match_rule_domain: "*"
match_rule_path: /higress-api
match_rule_type: "prefix"
servers:
- name: higress-api-mcp-server # MCP Server 名称
path: /higress-api # 访问路径,需要与 match_list 中的配置匹配
type: higress-api # 类型和 RegisterServer 一致
config:
higressURL: http://higress-console.higress-system.svc.cluster.local:8080
username: admin
password: admin
```

View File

@@ -0,0 +1,73 @@
# Higress API MCP Server
Higress API MCP Server provides MCP tools to manage Higress routes, service sources, plugins and other resources.
## Features
### Route Management
- `list-routes`: List routes
- `get-route`: Get route
- `add-route`: Add route
- `update-route`: Update route
### Service Source Management
- `list-service-sources`: List service sources
- `get-service-source`: Get service source
- `add-service-source`: Add service source
- `update-service-source`: Update service source
### Plugin Management
- `get-plugin`: Get plugin configuration
- `delete-plugin`: Delete plugin
- `update-request-block-plugin`: Update request block configuration
## Configuration Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `higressURL` | string | Required | Higress Console URL address |
| `username` | string | Required | Higress Console login username |
| `password` | string | Required | Higress Console login password |
| `description` | string | Optional | MCP Server description |
Configuration Example:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
annotations:
meta.helm.sh/release-name: higress
meta.helm.sh/release-namespace: higress-system
labels:
app: higress-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: higress-gateway
app.kubernetes.io/version: 2.1.4
helm.sh/chart: higress-core-2.1.4
higress: higress-system-higress-gateway
name: higress-config
namespace: higress-system
data:
higress: |-
mcpServer:
sse_path_suffix: /sse # SSE connection path suffix
enable: true # Enable MCP Server
redis:
address: redis-stack-server.higress-system.svc.cluster.local:6379 # Redis service address
username: "" # Redis username (optional)
password: "" # Redis password (optional)
db: 0 # Redis database (optional)
match_list: # MCP Server session persistence routing rules (when matching the following paths, it will be recognized as an MCP session and maintained through SSE)
- match_rule_domain: "*"
match_rule_path: /higress-api
match_rule_type: "prefix"
servers:
- name: higress-api-mcp-server # MCP Server name
path: /higress-api # Access path, needs to match the configuration in match_list
type: higress-api # Type defined in RegisterServer function
config:
higressURL: http://higress-console.higress-system.svc.cluster.local:8080
username: admin
password: admin
```

View File

@@ -0,0 +1,76 @@
package higress_ops
import (
"errors"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
)
const Version = "1.0.0"
func init() {
common.GlobalRegistry.RegisterServer("higress-api", &HigressConfig{})
}
type HigressConfig struct {
higressURL string
username string
password string
description string
}
func (c *HigressConfig) ParseConfig(config map[string]interface{}) error {
higressURL, ok := config["higressURL"].(string)
if !ok {
return errors.New("missing higressURL")
}
c.higressURL = higressURL
username, ok := config["username"].(string)
if !ok {
return errors.New("missing username")
}
c.username = username
password, ok := config["password"].(string)
if !ok {
return errors.New("missing password")
}
c.password = password
if desc, ok := config["description"].(string); ok {
c.description = desc
} else {
c.description = "Higress API MCP Server, which invokes Higress Console APIs to manage resources such as routes, services, and plugins."
}
api.LogDebugf("HigressConfig ParseConfig: higressURL=%s, username=%s, description=%s",
c.higressURL, c.username, c.description)
return nil
}
func (c *HigressConfig) NewServer(serverName string) (*common.MCPServer, error) {
mcpServer := common.NewMCPServer(
serverName,
Version,
common.WithInstructions("This is a Higress API MCP Server"),
)
// Initialize Higress API client
client := higress.NewHigressClient(c.higressURL, c.username, c.password)
// Register all tools
tools.RegisterRouteTools(mcpServer, client)
tools.RegisterServiceTools(mcpServer, client)
plugins.RegisterCommonPluginTools(mcpServer, client)
plugins.RegisterRequestBlockPluginTools(mcpServer, client)
api.LogInfof("Higress MCP Server initialized: %s", serverName)
return mcpServer, nil
}

View File

@@ -0,0 +1,141 @@
package plugins
import (
"context"
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/mark3labs/mcp-go/mcp"
)
// RegisterCommonPluginTools registers all common plugin management tools
func RegisterCommonPluginTools(mcpServer *common.MCPServer, client *higress.HigressClient) {
// Get plugin configuration
mcpServer.AddTool(
mcp.NewToolWithRawSchema("get-plugin", "Get configuration for a specific plugin", getPluginConfigSchema()),
handleGetPluginConfig(client),
)
// Delete plugin configuration
mcpServer.AddTool(
mcp.NewToolWithRawSchema("delete-plugin", "Delete configuration for a specific plugin", getPluginConfigSchema()),
handleDeletePluginConfig(client),
)
}
func handleGetPluginConfig(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
// Parse required parameters
pluginName, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
scope, ok := arguments["scope"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'scope' argument")
}
if !IsValidScope(scope) {
return nil, fmt.Errorf("invalid scope '%s', must be one of: %v", scope, ValidScopes)
}
// Parse resource_name (required for non-global scopes)
var resourceName string
if scope != ScopeGlobal {
resourceName, ok = arguments["resource_name"].(string)
if !ok || resourceName == "" {
return nil, fmt.Errorf("'resource_name' is required for scope '%s'", scope)
}
}
// Build API path and make request
path := BuildPluginPath(pluginName, scope, resourceName)
respBody, err := client.Get(path)
if err != nil {
return nil, fmt.Errorf("failed to get plugin config for '%s' at scope '%s': %w", pluginName, scope, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleDeletePluginConfig(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
// Parse required parameters
pluginName, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
scope, ok := arguments["scope"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'scope' argument")
}
if !IsValidScope(scope) {
return nil, fmt.Errorf("invalid scope '%s', must be one of: %v", scope, ValidScopes)
}
// Parse resource_name (required for non-global scopes)
var resourceName string
if scope != ScopeGlobal {
resourceName, ok = arguments["resource_name"].(string)
if !ok || resourceName == "" {
return nil, fmt.Errorf("'resource_name' is required for scope '%s'", scope)
}
}
// Build API path and make request
path := BuildPluginPath(pluginName, scope, resourceName)
respBody, err := client.Delete(path)
if err != nil {
return nil, fmt.Errorf("failed to delete plugin config for '%s' at scope '%s': %w", pluginName, scope, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func getPluginConfigSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the plugin"
},
"scope": {
"type": "string",
"enum": ["GLOBAL", "DOMAIN", "SERVICE", "ROUTE"],
"description": "The scope at which the plugin is applied"
},
"resource_name": {
"type": "string",
"description": "The name of the resource (required for DOMAIN, SERVICE, ROUTE scopes)"
}
},
"required": ["name", "scope"],
"additionalProperties": false
}`)
}

View File

@@ -0,0 +1,186 @@
package plugins
import (
"context"
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/mark3labs/mcp-go/mcp"
)
const RequestBlockPluginName = "request-block"
// RequestBlockConfig represents the configuration for request-block plugin
type RequestBlockConfig struct {
BlockBodies []string `json:"block_bodies,omitempty"`
BlockHeaders []string `json:"block_headers,omitempty"`
BlockUrls []string `json:"block_urls,omitempty"`
BlockedCode int `json:"blocked_code,omitempty"`
CaseSensitive bool `json:"case_sensitive,omitempty"`
}
// RequestBlockInstance represents a request-block plugin instance
type RequestBlockInstance = PluginInstance[RequestBlockConfig]
// RequestBlockResponse represents the API response for request-block plugin
type RequestBlockResponse = higress.APIResponse[RequestBlockInstance]
// RegisterRequestBlockPluginTools registers all request block plugin management tools
func RegisterRequestBlockPluginTools(mcpServer *common.MCPServer, client *higress.HigressClient) {
// Update request block configuration
mcpServer.AddTool(
mcp.NewToolWithRawSchema(fmt.Sprintf("update-%s-plugin", RequestBlockPluginName), "Update request block plugin configuration", getAddOrUpdateRequestBlockConfigSchema()),
handleAddOrUpdateRequestBlockConfig(client),
)
}
func handleAddOrUpdateRequestBlockConfig(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
// Parse required parameters
scope, ok := arguments["scope"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'scope' argument")
}
if !IsValidScope(scope) {
return nil, fmt.Errorf("invalid scope '%s', must be one of: %v", scope, ValidScopes)
}
enabled, ok := arguments["enabled"].(bool)
if !ok {
return nil, fmt.Errorf("missing or invalid 'enabled' argument")
}
configurations, ok := arguments["configurations"]
if !ok {
return nil, fmt.Errorf("missing 'configurations' argument")
}
// Parse resource_name for non-global scopes
var resourceName string
if scope != ScopeGlobal {
// Validate and get resource_name
resourceName, ok = arguments["resource_name"].(string)
if !ok || resourceName == "" {
return nil, fmt.Errorf("'resource_name' is required for scope '%s'", scope)
}
}
// Build API path
path := BuildPluginPath(RequestBlockPluginName, scope, resourceName)
// Get current request block configuration to merge with updates
currentBody, err := client.Get(path)
if err != nil {
return nil, fmt.Errorf("failed to get current request block configuration: %w", err)
}
var response RequestBlockResponse
if err := json.Unmarshal(currentBody, &response); err != nil {
return nil, fmt.Errorf("failed to parse current request block response: %w", err)
}
currentConfig := response.Data
currentConfig.Enabled = enabled
currentConfig.Scope = scope
// Convert the input configurations to RequestBlockConfig and merge
configBytes, err := json.Marshal(configurations)
if err != nil {
return nil, fmt.Errorf("failed to marshal configurations: %w", err)
}
var newConfig RequestBlockConfig
if err := json.Unmarshal(configBytes, &newConfig); err != nil {
return nil, fmt.Errorf("failed to parse request block configurations: %w", err)
}
// Update configurations (overwrite with new values where provided)
if newConfig.BlockBodies != nil {
currentConfig.Configurations.BlockBodies = newConfig.BlockBodies
}
if newConfig.BlockHeaders != nil {
currentConfig.Configurations.BlockHeaders = newConfig.BlockHeaders
}
if newConfig.BlockUrls != nil {
currentConfig.Configurations.BlockUrls = newConfig.BlockUrls
}
if newConfig.BlockedCode != 0 {
currentConfig.Configurations.BlockedCode = newConfig.BlockedCode
}
currentConfig.Configurations.CaseSensitive = newConfig.CaseSensitive
respBody, err := client.Put(path, currentConfig)
if err != nil {
return nil, fmt.Errorf("failed to update request block config at scope '%s': %w", scope, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func getAddOrUpdateRequestBlockConfigSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"scope": {
"type": "string",
"enum": ["GLOBAL", "DOMAIN", "SERVICE", "ROUTE"],
"description": "The scope at which the plugin is applied"
},
"resource_name": {
"type": "string",
"description": "The name of the resource (required for DOMAIN, SERVICE, ROUTE scopes)"
},
"enabled": {
"type": "boolean",
"description": "Whether the plugin is enabled"
},
"configurations": {
"type": "object",
"properties": {
"block_bodies": {
"type": "array",
"items": {"type": "string"},
"description": "List of patterns to match against request body content"
},
"block_headers": {
"type": "array",
"items": {"type": "string"},
"description": "List of patterns to match against request headers"
},
"block_urls": {
"type": "array",
"items": {"type": "string"},
"description": "List of patterns to match against request URLs"
},
"blocked_code": {
"type": "integer",
"minimum": 100,
"maximum": 599,
"description": "HTTP status code to return when a block is matched"
},
"case_sensitive": {
"type": "boolean",
"description": "Whether the block matching is case sensitive"
}
},
"additionalProperties": false
}
},
"required": ["scope", "enabled", "configurations"],
"additionalProperties": false
}`)
}

View File

@@ -0,0 +1,22 @@
package plugins
// PluginTargets represents the targets for different scopes
type PluginTargets struct {
Domain string `json:"DOMAIN,omitempty"`
Service string `json:"SERVICE,omitempty"`
Route string `json:"ROUTE,omitempty"`
}
// PluginInstance represents a plugin instance configuration
type PluginInstance[T any] struct {
Version string `json:"version,omitempty"`
Scope string `json:"scope"`
Target string `json:"target,omitempty"`
Targets PluginTargets `json:"targets,omitempty"`
PluginName string `json:"pluginName,omitempty"`
PluginVersion string `json:"pluginVersion,omitempty"`
Internal bool `json:"internal,omitempty"`
Enabled bool `json:"enabled"`
RawConfigurations string `json:"rawConfigurations,omitempty"`
Configurations T `json:"configurations,omitempty"`
}

View File

@@ -0,0 +1,39 @@
package plugins
import "fmt"
const (
ScopeGlobal = "GLOBAL"
ScopeDomain = "DOMAIN"
ScopeService = "SERVICE"
ScopeRoute = "ROUTE"
)
// ValidScopes contains all valid plugin scopes
var ValidScopes = []string{ScopeGlobal, ScopeDomain, ScopeService, ScopeRoute}
// IsValidScope checks if the given scope is valid
func IsValidScope(scope string) bool {
for _, validScope := range ValidScopes {
if scope == validScope {
return true
}
}
return false
}
// BuildPluginPath builds the API path for plugin operations based on scope and resource
func BuildPluginPath(pluginName, scope, resourceName string) string {
switch scope {
case ScopeGlobal:
return fmt.Sprintf("/v1/global/plugin-instances/%s", pluginName)
case ScopeDomain:
return fmt.Sprintf("/v1/domains/%s/plugin-instances/%s", resourceName, pluginName)
case ScopeService:
return fmt.Sprintf("/v1/services/%s/plugin-instances/%s", resourceName, pluginName)
case ScopeRoute:
return fmt.Sprintf("/v1/routes/%s/plugin-instances/%s", resourceName, pluginName)
default:
return fmt.Sprintf("/v1/global/plugin-instances/%s", pluginName)
}
}

View File

@@ -0,0 +1,456 @@
package tools
import (
"context"
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/mark3labs/mcp-go/mcp"
)
// Route represents a route configuration
type Route struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Domains []string `json:"domains,omitempty"`
Path *RoutePath `json:"path,omitempty"`
Methods []string `json:"methods,omitempty"`
Headers []RouteMatch `json:"headers,omitempty"`
URLParams []RouteMatch `json:"urlParams,omitempty"`
Services []RouteService `json:"services,omitempty"`
AuthConfig *RouteAuthConfig `json:"authConfig,omitempty"`
CustomConfigs map[string]interface{} `json:"customConfigs,omitempty"`
}
// RoutePath represents path matching configuration
type RoutePath struct {
MatchType string `json:"matchType"`
MatchValue string `json:"matchValue"`
CaseSensitive bool `json:"caseSensitive,omitempty"`
}
// RouteMatch represents header or URL parameter matching configuration
type RouteMatch struct {
Key string `json:"key"`
MatchType string `json:"matchType"`
MatchValue string `json:"matchValue"`
}
// RouteService represents a service in the route
type RouteService struct {
Name string `json:"name"`
Port int `json:"port"`
Weight int `json:"weight"`
}
// RouteAuthConfig represents authentication configuration for a route
type RouteAuthConfig struct {
Enabled bool `json:"enabled"`
AllowedConsumers []string `json:"allowedConsumers,omitempty"`
}
// RouteResponse represents the API response for route operations
type RouteResponse = higress.APIResponse[Route]
// RegisterRouteTools registers all route management tools
func RegisterRouteTools(mcpServer *common.MCPServer, client *higress.HigressClient) {
// List all routes
mcpServer.AddTool(
mcp.NewTool("list-routes", mcp.WithDescription("List all available routes")),
handleListRoutes(client),
)
// Get specific route
mcpServer.AddTool(
mcp.NewToolWithRawSchema("get-route", "Get detailed information about a specific route", getRouteSchema()),
handleGetRoute(client),
)
// Add new route
mcpServer.AddTool(
mcp.NewToolWithRawSchema("add-route", "Add a new route", getAddRouteSchema()),
handleAddRoute(client),
)
// Update existing route
mcpServer.AddTool(
mcp.NewToolWithRawSchema("update-route", "Update an existing route", getUpdateRouteSchema()),
handleUpdateRoute(client),
)
// Delete existing route
mcpServer.AddTool(
mcp.NewToolWithRawSchema("delete-route", "Delete an existing route", getRouteSchema()),
handleDeleteRoute(client),
)
}
func handleListRoutes(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
respBody, err := client.Get("/v1/routes")
if err != nil {
return nil, fmt.Errorf("failed to list routes: %w", err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleGetRoute(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
name, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
respBody, err := client.Get(fmt.Sprintf("/v1/routes/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to get route '%s': %w", name, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleAddRoute(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
configurations, ok := arguments["configurations"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("missing or invalid 'configurations' argument")
}
// Validate required fields
if _, ok := configurations["name"]; !ok {
return nil, fmt.Errorf("missing required field 'name' in configurations")
}
if _, ok := configurations["path"]; !ok {
return nil, fmt.Errorf("missing required field 'path' in configurations")
}
if _, ok := configurations["services"]; !ok {
return nil, fmt.Errorf("missing required field 'services' in configurations")
}
respBody, err := client.Post("/v1/routes", configurations)
if err != nil {
return nil, fmt.Errorf("failed to add route: %w", err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleUpdateRoute(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
name, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
configurations, ok := arguments["configurations"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("missing or invalid 'configurations' argument")
}
// Get current route configuration to merge with updates
currentBody, err := client.Get(fmt.Sprintf("/v1/routes/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to get current route configuration: %w", err)
}
var response RouteResponse
if err := json.Unmarshal(currentBody, &response); err != nil {
return nil, fmt.Errorf("failed to parse current route response: %w", err)
}
currentConfig := response.Data
// Update configurations using JSON marshal/unmarshal for type conversion
configBytes, err := json.Marshal(configurations)
if err != nil {
return nil, fmt.Errorf("failed to marshal configurations: %w", err)
}
var newConfig Route
if err := json.Unmarshal(configBytes, &newConfig); err != nil {
return nil, fmt.Errorf("failed to parse route configurations: %w", err)
}
// Merge configurations (overwrite with new values where provided)
if newConfig.Domains != nil {
currentConfig.Domains = newConfig.Domains
}
if newConfig.Path != nil {
currentConfig.Path = newConfig.Path
}
if newConfig.Methods != nil {
currentConfig.Methods = newConfig.Methods
}
if newConfig.Headers != nil {
currentConfig.Headers = newConfig.Headers
}
if newConfig.URLParams != nil {
currentConfig.URLParams = newConfig.URLParams
}
if newConfig.Services != nil {
currentConfig.Services = newConfig.Services
}
if newConfig.AuthConfig != nil {
currentConfig.AuthConfig = newConfig.AuthConfig
}
if newConfig.CustomConfigs != nil {
currentConfig.CustomConfigs = newConfig.CustomConfigs
}
respBody, err := client.Put(fmt.Sprintf("/v1/routes/%s", name), currentConfig)
if err != nil {
return nil, fmt.Errorf("failed to update route '%s': %w", name, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleDeleteRoute(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
name, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
respBody, err := client.Delete(fmt.Sprintf("/v1/routes/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to delete route '%s': %w", name, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func getRouteSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the route"
}
},
"required": ["name"],
"additionalProperties": false
}`)
}
func getAddRouteSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"configurations": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the route"
},
"domains": {
"type": "array",
"items": {"type": "string"},
"description": "List of domain names, but only one domain is allowed"
},
"path": {
"type": "object",
"properties": {
"matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of path"},
"matchValue": {"type": "string", "description": "Value to match"},
"caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"}
},
"required": ["matchType", "matchValue"],
"description": "List of path match conditions"
},
"methods": {
"type": "array",
"items": {"type": "string", "enum": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE", "CONNECT"]},
"description": "List of HTTP methods"
},
"headers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of header"},
"matchValue": {"type": "string", "description": "Value to match"},
"caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"},
"key": {"type": "string", "description": "Header key name"}
},
"required": ["matchType", "matchValue", "key"]
},
"description": "List of header match conditions"
},
"urlParams": {
"type": "array",
"items": {
"type": "object",
"properties": {
"matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of URL parameter"},
"matchValue": {"type": "string", "description": "Value to match"},
"caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"},
"key": {"type": "string", "description": "Parameter key name"}
},
"required": ["matchType", "matchValue", "key"]
},
"description": "List of URL parameter match conditions"
},
"services": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Service name"},
"port": {"type": "integer", "description": "Service port"},
"weight": {"type": "integer", "description": "Service weight"}
},
"required": ["name", "port", "weight"]
},
"description": "List of services for this route"
},
"customConfigs": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Dictionary of custom configurations"
}
},
"required": ["name", "path", "services"],
"additionalProperties": false
}
},
"required": ["configurations"],
"additionalProperties": false
}`)
}
func getUpdateRouteSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the route"
},
"configurations": {
"type": "object",
"properties": {
"domains": {
"type": "array",
"items": {"type": "string"},
"description": "List of domain names, but only one domain is allowed",
"maxItems": 1
},
"path": {
"type": "object",
"properties": {
"matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of path"},
"matchValue": {"type": "string", "description": "Value to match"},
"caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"}
},
"required": ["matchType", "matchValue"],
"description": "The path configuration"
},
"methods": {
"type": "array",
"items": {"type": "string", "enum": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE", "CONNECT"]},
"description": "List of HTTP methods"
},
"headers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of header"},
"matchValue": {"type": "string", "description": "Value to match"},
"caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"},
"key": {"type": "string", "description": "Header key name"}
},
"required": ["matchType", "matchValue", "key"]
},
"description": "List of header match conditions"
},
"urlParams": {
"type": "array",
"items": {
"type": "object",
"properties": {
"matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of URL parameter"},
"matchValue": {"type": "string", "description": "Value to match"},
"caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"},
"key": {"type": "string", "description": "Parameter key name"}
},
"required": ["matchType", "matchValue", "key"]
},
"description": "List of URL parameter match conditions"
},
"services": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Service name"},
"port": {"type": "integer", "description": "Service port"},
"weight": {"type": "integer", "description": "Service weight"}
},
"required": ["name", "port", "weight"]
},
"description": "List of services for this route"
},
"customConfigs": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Dictionary of custom configurations"
}
},
"additionalProperties": false
}
},
"required": ["name", "configurations"],
"additionalProperties": false
}`)
}

View File

@@ -0,0 +1,355 @@
package tools
import (
"context"
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/mark3labs/mcp-go/mcp"
)
// ServiceSource represents a service source configuration
type ServiceSource struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Type string `json:"type"`
Domain string `json:"domain"`
Port int `json:"port"`
Protocol string `json:"protocol,omitempty"`
SNI *string `json:"sni,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
AuthN *ServiceSourceAuthN `json:"authN,omitempty"`
Valid bool `json:"valid,omitempty"`
}
// ServiceSourceAuthN represents authentication configuration for service source
type ServiceSourceAuthN struct {
Enabled bool `json:"enabled"`
Properties map[string]interface{} `json:"properties,omitempty"`
}
// ServiceSourceResponse represents the API response for service source operations
type ServiceSourceResponse = higress.APIResponse[ServiceSource]
// RegisterServiceTools registers all service source management tools
func RegisterServiceTools(mcpServer *common.MCPServer, client *higress.HigressClient) {
// List all service sources
mcpServer.AddTool(
mcp.NewTool("list-service-sources", mcp.WithDescription("List all available service sources")),
handleListServiceSources(client),
)
// Get specific service source
mcpServer.AddTool(
mcp.NewToolWithRawSchema("get-service-source", "Get detailed information about a specific service source", getServiceSourceSchema()),
handleGetServiceSource(client),
)
// Add new service source
mcpServer.AddTool(
mcp.NewToolWithRawSchema("add-service-source", "Add a new service source", getAddServiceSourceSchema()),
handleAddServiceSource(client),
)
// Update existing service source
mcpServer.AddTool(
mcp.NewToolWithRawSchema("update-service-source", "Update an existing service source", getUpdateServiceSourceSchema()),
handleUpdateServiceSource(client),
)
// Delete existing service source
mcpServer.AddTool(
mcp.NewToolWithRawSchema("delete-service-source", "Delete an existing service source", getServiceSourceSchema()),
handleDeleteServiceSource(client),
)
}
func handleListServiceSources(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
respBody, err := client.Get("/v1/service-sources")
if err != nil {
return nil, fmt.Errorf("failed to list service sources: %w", err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleGetServiceSource(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
name, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
respBody, err := client.Get(fmt.Sprintf("/v1/service-sources/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to get service source '%s': %w", name, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleAddServiceSource(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
configurations, ok := arguments["configurations"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("missing or invalid 'configurations' argument")
}
// Validate required fields
if _, ok := configurations["name"]; !ok {
return nil, fmt.Errorf("missing required field 'name' in configurations")
}
if _, ok := configurations["type"]; !ok {
return nil, fmt.Errorf("missing required field 'type' in configurations")
}
if _, ok := configurations["domain"]; !ok {
return nil, fmt.Errorf("missing required field 'domain' in configurations")
}
if _, ok := configurations["port"]; !ok {
return nil, fmt.Errorf("missing required field 'port' in configurations")
}
respBody, err := client.Post("/v1/service-sources", configurations)
if err != nil {
return nil, fmt.Errorf("failed to add service source: %w", err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleUpdateServiceSource(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
name, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
configurations, ok := arguments["configurations"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("missing or invalid 'configurations' argument")
}
// Get current service source configuration to merge with updates
currentBody, err := client.Get(fmt.Sprintf("/v1/service-sources/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to get current service source configuration: %w", err)
}
var response ServiceSourceResponse
if err := json.Unmarshal(currentBody, &response); err != nil {
return nil, fmt.Errorf("failed to parse current service source response: %w", err)
}
currentConfig := response.Data
// Update configurations using JSON marshal/unmarshal for type conversion
configBytes, err := json.Marshal(configurations)
if err != nil {
return nil, fmt.Errorf("failed to marshal configurations: %w", err)
}
var newConfig ServiceSource
if err := json.Unmarshal(configBytes, &newConfig); err != nil {
return nil, fmt.Errorf("failed to parse service source configurations: %w", err)
}
// Merge configurations (overwrite with new values where provided)
if newConfig.Name != "" {
currentConfig.Name = newConfig.Name
}
if newConfig.Type != "" {
currentConfig.Type = newConfig.Type
}
if newConfig.Domain != "" {
currentConfig.Domain = newConfig.Domain
}
if newConfig.Port != 0 {
currentConfig.Port = newConfig.Port
}
if newConfig.Protocol != "" {
currentConfig.Protocol = newConfig.Protocol
}
if newConfig.SNI != nil {
currentConfig.SNI = newConfig.SNI
}
if newConfig.Properties != nil {
currentConfig.Properties = newConfig.Properties
}
if newConfig.AuthN != nil {
currentConfig.AuthN = newConfig.AuthN
}
respBody, err := client.Put(fmt.Sprintf("/v1/service-sources/%s", name), currentConfig)
if err != nil {
return nil, fmt.Errorf("failed to update service source '%s': %w", name, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func handleDeleteServiceSource(client *higress.HigressClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
name, ok := arguments["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' argument")
}
respBody, err := client.Delete(fmt.Sprintf("/v1/service-sources/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to delete service source '%s': %w", name, err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: string(respBody),
},
},
}, nil
}
}
func getServiceSourceSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the service source to retrieve"
}
},
"required": ["name"],
"additionalProperties": false
}`)
}
// TODO: extend other types of service sources, e.g., nacos, zookeeper, euraka.
func getAddServiceSourceSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"configurations": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the service source"
},
"type": {
"type": "string",
"enum": ["static", "dns"],
"description": "The type of service source: 'static' for static IPs, 'dns' for DNS resolution"
},
"domain": {
"type": "string",
"description": "The domain name or IP address (required)"
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "The port number (required)"
},
"protocol": {
"type": "string",
"enum": ["http", "https"],
"description": "The protocol to use (optional, defaults to http)"
},
"sni": {
"type": "string",
"description": "Server Name Indication for HTTPS connections (optional)"
}
},
"required": ["name", "type", "domain", "port"],
"additionalProperties": false
}
},
"required": ["configurations"],
"additionalProperties": false
}`)
}
// TODO: extend other types of service sources, e.g., nacos, zookeeper, euraka.
func getUpdateServiceSourceSchema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the service source to update"
},
"configurations": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["static", "dns"],
"description": "The type of service source: 'static' for static IPs, 'dns' for DNS resolution"
},
"domain": {
"type": "string",
"description": "The domain name or IP address"
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "The port number"
},
"protocol": {
"type": "string",
"enum": ["http", "https"],
"description": "The protocol to use (optional, defaults to http)"
},
"sni": {
"type": "string",
"description": "Server Name Indication for HTTPS connections"
}
},
"additionalProperties": false
}
},
"required": ["name", "configurations"],
"additionalProperties": false
}`)
}

View File

@@ -0,0 +1,8 @@
package higress
// APIResponse represents the standard Higress API response format
type APIResponse[T any] struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data T `json:"data,omitempty"`
}

View File

@@ -64,7 +64,7 @@ func (p *Parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (int
redisClient, err := common.NewRedisClient(redisConfig)
if err != nil {
api.LogErrorf("Failed to initialize Redis client: %w", err)
api.LogErrorf("Failed to initialize Redis client: %v", err)
} else {
api.LogDebug("Redis client initialized")
}

View File

@@ -149,6 +149,9 @@ func (f *filter) processMcpRequestHeadersForRestUpstream(header api.RequestHeade
func (f *filter) processMcpRequestHeadersForSSEUpstream(header api.RequestHeaderMap, endStream bool) api.StatusType {
// We don't need to process the request body for SSE upstream.
f.skipRequestBody = true
// Remove Accept-Encoding header to avoid gzip encoding,
// which our response body handling logic doesn't support.
header.Del("Accept-Encoding")
return api.Continue
}
@@ -397,48 +400,93 @@ func (f *filter) findNextLineBreak(bufferData string) (error, string) {
}
func (f *filter) findEndpointUrl(bufferData string) (error, string) {
eventIndex := strings.Index(bufferData, "event:")
if eventIndex == -1 {
return nil, ""
// Keep searching for events until we find an endpoint event or run out of data
for {
eventIndex := strings.Index(bufferData, "event:")
if eventIndex == -1 {
// No more events found
return nil, ""
}
// Move to the start of the event
bufferData = bufferData[eventIndex:]
// Find the end of the event line
err, lineBreak := f.findNextLineBreak(bufferData)
if err != nil {
return fmt.Errorf("failed to find endpoint URL in SSE data: %v", err), ""
}
if lineBreak == "" {
// No line break found, which means the data is not enough.
return nil, ""
}
api.LogDebugf("event line break sequence: %v", []byte(lineBreak))
eventEndIndex := strings.Index(bufferData, lineBreak)
if eventEndIndex == -1 {
return nil, ""
}
eventName := strings.TrimSpace(bufferData[len("event:"):eventEndIndex])
// Move past the event line
bufferData = bufferData[eventEndIndex+len(lineBreak):]
if eventName == "endpoint" {
// Found endpoint event, now look for the data field
err, lineBreak = f.findNextLineBreak(bufferData)
if err != nil {
return fmt.Errorf("failed to find endpoint URL in SSE data: %v", err), ""
}
if lineBreak == "" {
// No line break found, which means the data is not enough.
return nil, ""
}
api.LogDebugf("data line break sequence: %v", []byte(lineBreak))
dataEndIndex := strings.Index(bufferData, lineBreak)
if dataEndIndex == -1 {
// Data received not enough.
return nil, ""
}
eventData := bufferData[:dataEndIndex]
if !strings.HasPrefix(eventData, "data:") {
return fmt.Errorf("an unexpected non-data field found in the event. Skip processing. Field: %s", eventData), ""
}
return nil, strings.TrimSpace(eventData[len("data:"):])
} else {
// Not an endpoint event, skip to the next event
api.LogDebugf("Skipping non-endpoint event: %s", eventName)
// First, we need to skip the data field of this event
err, lineBreak = f.findNextLineBreak(bufferData)
if err != nil {
return fmt.Errorf("failed to find endpoint URL in SSE data: %v", err), ""
}
if lineBreak == "" {
// No line break found, which means the data is not enough.
return nil, ""
}
dataEndIndex := strings.Index(bufferData, lineBreak)
if dataEndIndex == -1 {
// Data received not enough.
return nil, ""
}
// Move past the data line
bufferData = bufferData[dataEndIndex+len(lineBreak):]
// Skip any additional empty lines that separate events
for strings.HasPrefix(bufferData, lineBreak) {
bufferData = bufferData[len(lineBreak):]
}
// Continue to look for the next event
}
}
bufferData = bufferData[eventIndex:]
err, lineBreak := f.findNextLineBreak(bufferData)
if err != nil {
return fmt.Errorf("failed to find endpoint URL in SSE data: %v", err), ""
}
if lineBreak == "" {
// No line break found, which means the data is not enough.
return nil, ""
}
api.LogDebugf("event line break sequence: %v", []byte(lineBreak))
eventEndIndex := strings.Index(bufferData, lineBreak)
if eventEndIndex == -1 {
return nil, ""
}
eventName := strings.TrimSpace(bufferData[len("event:"):eventEndIndex])
if eventName != "endpoint" {
return fmt.Errorf("the initial event [%s] is not an endpoint event. Skip processing", eventName), ""
}
bufferData = bufferData[eventEndIndex+len(lineBreak):]
err, lineBreak = f.findNextLineBreak(bufferData)
if err != nil {
return fmt.Errorf("failed to find endpoint URL in SSE data: %v", err), ""
}
if lineBreak == "" {
// No line break found, which means the data is not enough.
return nil, ""
}
api.LogDebugf("data line break sequence: %v", []byte(lineBreak))
dataEndIndex := strings.Index(bufferData, lineBreak)
if dataEndIndex == -1 {
// Data received not enough.
return nil, ""
}
eventData := bufferData[:dataEndIndex]
if !strings.HasPrefix(eventData, "data:") {
return fmt.Errorf("an unexpected non-data field found in the event. Skip processing. Field: %s", eventData), ""
}
return nil, strings.TrimSpace(eventData[len("data:"):])
}
// OnDestroy stops the goroutine

View File

@@ -0,0 +1,464 @@
package mcp_session
import (
"fmt"
"testing"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
)
// Mock implementation of CommonCAPI for testing
type mockCommonCAPI struct {
logs []string
}
func (m *mockCommonCAPI) Log(level api.LogType, message string) {
fmt.Printf("[%s] %s", level, message)
m.logs = append(m.logs, message)
}
func (m *mockCommonCAPI) LogLevel() api.LogType {
return api.Debug
}
// Test helper to create a filter instance for testing
func createTestFilter() *filter {
return &filter{}
}
// Test helper to create a match rule for testing
func createTestMatchRule() common.MatchRule {
return common.MatchRule{
UpstreamType: common.SSEUpstream,
EnablePathRewrite: true,
PathRewritePrefix: "/api/v1",
MatchRulePath: "/mcp",
MatchRuleType: common.PrefixMatch,
MatchRuleDomain: "example.com",
}
}
// TestFindEndpointUrl_ValidEndpointMessage tests the current behavior with a valid endpoint message
func TestFindEndpointUrl_ValidEndpointMessage(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with valid endpoint message
sseData := "event: endpoint\ndata: https://api.example.com/chat\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
expectedUrl := "https://api.example.com/chat"
if endpointUrl != expectedUrl {
t.Errorf("Expected endpoint URL '%s', got '%s'", expectedUrl, endpointUrl)
}
}
// TestFindEndpointUrl_NonEndpointFirstMessage tests improved behavior with non-endpoint first message
func TestFindEndpointUrl_NonEndpointFirstMessage(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with ping message first (this should now succeed with improved implementation)
sseData := "event: ping\ndata: alive\n\nevent: endpoint\ndata: https://api.example.com/chat\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
// Improved implementation should handle non-endpoint first message
if err != nil {
t.Errorf("Expected no error for non-endpoint first message, got: %v", err)
}
expectedUrl := "https://api.example.com/chat"
if endpointUrl != expectedUrl {
t.Errorf("Expected endpoint URL '%s', got '%s'", expectedUrl, endpointUrl)
}
// Check that the non-endpoint event was logged
found := false
for _, log := range mockAPI.logs {
if log == "Skipping non-endpoint event: ping" {
found = true
break
}
}
if !found {
t.Errorf("Expected log message about skipping ping event not found")
}
}
// TestFindEndpointUrl_MultipleNonEndpointMessages tests multiple non-endpoint messages before endpoint
func TestFindEndpointUrl_MultipleNonEndpointMessages(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with multiple non-endpoint messages before endpoint
sseData := "event: ping\ndata: alive\n\nevent: status\ndata: connecting\n\nevent: info\ndata: ready\n\nevent: endpoint\ndata: https://api.example.com/chat\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
expectedUrl := "https://api.example.com/chat"
if endpointUrl != expectedUrl {
t.Errorf("Expected endpoint URL '%s', got '%s'", expectedUrl, endpointUrl)
}
// Check that all non-endpoint events were logged
expectedLogs := []string{
"Skipping non-endpoint event: ping",
"Skipping non-endpoint event: status",
"Skipping non-endpoint event: info",
}
for _, expectedLog := range expectedLogs {
found := false
for _, log := range mockAPI.logs {
if log == expectedLog {
found = true
break
}
}
if !found {
t.Errorf("Expected log message '%s' not found", expectedLog)
}
}
}
// TestFindEndpointUrl_EndpointInMiddle tests endpoint message in the middle of other messages
func TestFindEndpointUrl_EndpointInMiddle(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with endpoint message in the middle
sseData := "event: ping\ndata: alive\n\nevent: endpoint\ndata: https://api.example.com/chat\n\nevent: status\ndata: ready\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
expectedUrl := "https://api.example.com/chat"
if endpointUrl != expectedUrl {
t.Errorf("Expected endpoint URL '%s', got '%s'", expectedUrl, endpointUrl)
}
// Check that the ping event was logged as skipped
found := false
for _, log := range mockAPI.logs {
if log == "Skipping non-endpoint event: ping" {
found = true
break
}
}
if !found {
t.Errorf("Expected log message about skipping ping event not found")
}
}
// TestFindEndpointUrl_NoEndpointMessage tests when no endpoint message is present
func TestFindEndpointUrl_NoEndpointMessage(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with no endpoint message
sseData := "event: ping\ndata: alive\n\nevent: status\ndata: connecting\n\nevent: info\ndata: ready\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error when no endpoint found, got: %v", err)
}
if endpointUrl != "" {
t.Errorf("Expected empty endpoint URL when no endpoint found, got '%s'", endpointUrl)
}
// Check that all non-endpoint events were logged
expectedLogs := []string{
"Skipping non-endpoint event: ping",
"Skipping non-endpoint event: status",
"Skipping non-endpoint event: info",
}
for _, expectedLog := range expectedLogs {
found := false
for _, log := range mockAPI.logs {
if log == expectedLog {
found = true
break
}
}
if !found {
t.Errorf("Expected log message '%s' not found", expectedLog)
}
}
}
// TestFindEndpointUrl_IncompleteEndpointMessage tests incomplete endpoint message
func TestFindEndpointUrl_IncompleteEndpointMessage(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with incomplete endpoint message (missing final line break)
sseData := "event: ping\ndata: alive\n\nevent: endpoint\ndata: https://api.example.com/chat"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error for incomplete endpoint message, got: %v", err)
}
if endpointUrl != "" {
t.Errorf("Expected empty endpoint URL for incomplete message, got '%s'", endpointUrl)
}
}
// TestFindEndpointUrl_IncompleteNonEndpointMessage tests incomplete non-endpoint message
func TestFindEndpointUrl_IncompleteNonEndpointMessage(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with incomplete non-endpoint message
sseData := "event: ping\ndata: alive"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error for incomplete non-endpoint message, got: %v", err)
}
if endpointUrl != "" {
t.Errorf("Expected empty endpoint URL for incomplete message, got '%s'", endpointUrl)
}
}
// TestFindEndpointUrl_MalformedEndpointData tests malformed endpoint data
func TestFindEndpointUrl_MalformedEndpointData(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with malformed endpoint data (missing data field)
sseData := "event: ping\ndata: alive\n\nevent: endpoint\nnotdata: https://api.example.com/chat\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
// Should return error for malformed endpoint data
if err == nil {
t.Errorf("Expected error for malformed endpoint data, but got none")
}
if endpointUrl != "" {
t.Errorf("Expected empty endpoint URL when error occurs, got '%s'", endpointUrl)
}
}
// TestFindEndpointUrl_DifferentLineBreaks tests different line break formats with improved version
func TestFindEndpointUrl_DifferentLineBreaks(t *testing.T) {
testCases := []struct {
name string
sseData string
expected string
}{
{
name: "CRLF line breaks with ping first",
sseData: "event: ping\r\ndata: alive\r\n\r\nevent: endpoint\r\ndata: https://api.example.com/chat\r\n\r\n",
expected: "https://api.example.com/chat",
},
{
name: "CR line breaks with status first",
sseData: "event: status\rdata: ready\r\revent: endpoint\rdata: https://api.example.com/chat\r\r",
expected: "https://api.example.com/chat",
},
{
name: "LF line breaks with info first",
sseData: "event: info\ndata: starting\n\nevent: endpoint\ndata: https://api.example.com/chat\n\n",
expected: "https://api.example.com/chat",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
err, endpointUrl := f.findEndpointUrl(tc.sseData)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
if endpointUrl != tc.expected {
t.Errorf("Expected endpoint URL '%s', got '%s'", tc.expected, endpointUrl)
}
})
}
}
// TestFindEndpointUrl_WithWhitespace tests improved version with whitespace
func TestFindEndpointUrl_WithWhitespace(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with whitespace around event names and data
sseData := "event: ping \ndata: alive \n\nevent: endpoint \ndata: https://api.example.com/chat \n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
expectedUrl := "https://api.example.com/chat"
if endpointUrl != expectedUrl {
t.Errorf("Expected endpoint URL '%s', got '%s'", expectedUrl, endpointUrl)
}
}
// TestFindEndpointUrl_NoEventFound tests behavior when no event is found
func TestFindEndpointUrl_NoEventFound(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with data that doesn't contain event
sseData := "some random data without event"
err, endpointUrl := f.findEndpointUrl(sseData)
if err != nil {
t.Errorf("Expected no error when no event found, got: %v", err)
}
if endpointUrl != "" {
t.Errorf("Expected empty endpoint URL when no event found, got '%s'", endpointUrl)
}
}
// TestFindEndpointUrl_MalformedData tests behavior with malformed SSE data
func TestFindEndpointUrl_MalformedData(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
// Test with malformed data (missing data field)
sseData := "event: endpoint\nnotdata: https://api.example.com/chat\n\n"
err, endpointUrl := f.findEndpointUrl(sseData)
// Should return error for malformed data
if err == nil {
t.Errorf("Expected error for malformed data, but got none")
}
if endpointUrl != "" {
t.Errorf("Expected empty endpoint URL when error occurs, got '%s'", endpointUrl)
}
}
// TestFindNextLineBreak tests the line break detection functionality
func TestFindNextLineBreak(t *testing.T) {
// Setup mock API
mockAPI := &mockCommonCAPI{}
api.SetCommonCAPI(mockAPI)
f := createTestFilter()
testCases := []struct {
name string
input string
expectedBreak string
expectedError bool
}{
{
name: "LF only",
input: "some text\nmore text",
expectedBreak: "\n",
expectedError: false,
},
{
name: "CR only",
input: "some text\rmore text",
expectedBreak: "\r",
expectedError: false,
},
{
name: "CRLF",
input: "some text\r\nmore text",
expectedBreak: "\r\n",
expectedError: false,
},
{
name: "No line break",
input: "some text without break",
expectedBreak: "",
expectedError: false,
},
{
name: "LF before CR (separate)",
input: "some text\n\rmore text",
expectedBreak: "",
expectedError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err, lineBreak := f.findNextLineBreak(tc.input)
if tc.expectedError && err == nil {
t.Errorf("Expected error, but got none")
}
if !tc.expectedError && err != nil {
t.Errorf("Expected no error, got: %v", err)
}
if lineBreak != tc.expectedBreak {
t.Errorf("Expected line break '%v', got '%v'", []byte(tc.expectedBreak), []byte(lineBreak))
}
})
}
}

View File

@@ -14,5 +14,5 @@ export {SetCtx,
ProcessRequestHeadersBy,
ProcessResponseBodyBy,
ProcessResponseHeadersBy,
Logger, RegisteTickFunc} from "./plugin_wrapper"
Logger, RegisterTickFunc} from "./plugin_wrapper"
export {ParseResult} from "./rule_matcher"

View File

@@ -156,7 +156,7 @@ class TickFuncEntry {
var globalOnTickFuncs = new Array<TickFuncEntry>();
export function RegisteTickFunc(tickPeriod: i64, tickFunc: () => void): void {
export function RegisterTickFunc(tickPeriod: i64, tickFunc: () => void): void {
globalOnTickFuncs.push(new TickFuncEntry(0, tickPeriod, tickFunc));
}

View File

@@ -1,5 +1,5 @@
export * from "@higress/proxy-wasm-assemblyscript-sdk/assembly/proxy";
import { SetCtx, HttpContext, ProcessRequestHeadersBy, Logger, ParseResult, ParseConfigBy, RegisteTickFunc, ProcessResponseHeadersBy } from "@higress/wasm-assemblyscript/assembly";
import { SetCtx, HttpContext, ProcessRequestHeadersBy, Logger, ParseResult, ParseConfigBy, RegisterTickFunc, ProcessResponseHeadersBy } from "@higress/wasm-assemblyscript/assembly";
import { FilterHeadersStatusValues, send_http_response, stream_context } from "@higress/proxy-wasm-assemblyscript-sdk/assembly"
import { JSON } from "assemblyscript-json/assembly";
class HelloWorldConfig {
@@ -12,10 +12,10 @@ SetCtx<HelloWorldConfig>("hello-world",
])
function parseConfig(json: JSON.Obj): ParseResult<HelloWorldConfig> {
RegisteTickFunc(2000, () => {
RegisterTickFunc(2000, () => {
Logger.Debug("tick 2s");
})
RegisteTickFunc(5000, () => {
RegisterTickFunc(5000, () => {
Logger.Debug("tick 5s");
})
return new ParseResult<HelloWorldConfig>(new HelloWorldConfig(), true);

View File

@@ -243,7 +243,7 @@ class RouteRuleMatcher {
std::string route_name;
getValue({"route_name"}, &route_name);
std::string service_name;
getValue({"service_name"}, &service_name);
getValue({"cluster_name"}, &service_name);
std::optional<std::reference_wrapper<PluginConfig>> match_config;
std::optional<std::reference_wrapper<std::unordered_set<std::string>>>
allow_set;

View File

@@ -1,13 +1,10 @@
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.20.14-tinygo0.29.0-oras1.0.0
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.24.0-oras1.0.0
FROM $BUILDER AS builder
ARG GOPROXY
ENV GOPROXY=${GOPROXY}
ARG EXTRA_TAGS=""
ENV EXTRA_TAGS=${EXTRA_TAGS}
ARG PLUGIN_NAME=hello-world
WORKDIR /workspace
@@ -18,13 +15,7 @@ WORKDIR /workspace/extensions/$PLUGIN_NAME
RUN go mod tidy
RUN \
if echo "$PLUGIN_NAME" | grep -Eq '^waf$'; then \
# Please use higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.28.1-oras1.0.0 as BUILDER
go run mage.go build && \
mv ./local/main.wasm /main.wasm ; \
else \
tinygo build -o /main.wasm -scheduler=none -gc=custom -tags="custommalloc nottinygc_finalizer $EXTRA_TAGS" -target=wasi ./ ; \
fi
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o /main.wasm .
FROM scratch AS output

View File

@@ -1,115 +1,35 @@
# The Dockerfile for wasm-go builder only support amd64 and arm64 yet.
# If you want to build on another architecture, the following information may be helpful.
#
# - arch: amd64
# base image: docker.io/ubuntu
# go_url: https://golang.google.cn/dl/go1.20.1.linux-amd64.tar.gz"
# tinygo_url: https://github.com/alibaba/higress/releases/download/v1.0.0-rc/higress-tinygo0.25.0.linux-amd64.tar.gz
# oras_url: https://github.com/oras-project/oras/releases/download/v1.0.0/oras_1.0.0_linux_amd64.tar.gz
#
# - arch: arm64
# base image: docker.io/ubuntu
# go_url: https://golang.google.cn/dl/go1.20.1.linux-arm64.tar.gz
# tinygo_url: https://github.com/alibaba/higress/releases/download/v1.0.0-rc/higress-tinygo0.25.0.linux-arm64.tar.gz
# oras_url: https://github.com/oras-project/oras/releases/download/v1.0.0/oras_1.0.0_linux_arm64.tar.gz
#
# - arch: armel
# base image: build yourself
# go_url: install from source code
# tinygo_url: build yourself
# oras_url: build your self
#
# - arch: i386
# base image: build yourself
# go_url: https://dl.google.com/go/go1.20.1.linux-386.tar.gz
# tinygo_url: build yourself
# oras_url: build your self
#
# - arch: mips64el
# base image: build your self
# go_url: https://dl.google.com/go/go1.20.1.linux-386.tar.gz
# tinygo_url: build your self
# oras_url: build your self
#
# - arch: ppc64el
# base image: build your self
# go_url: https://dl.google.com/go/go1.20.1.linux-ppc64le.tar.gz
# tinygo_url: build your self
# oras_url: build your self
#
# - arch: s390x
# base image: docker.io/ubuntu
# go_url: https://dl.google.com/go/go1.20.1.linux-s390x.tar.gz
# tinygo_url: build your self
# oras_url: build your self
#
# - arch: armhf
# base image: build your self
# go_url: https://golang.google.cn/dl/go1.20.1.linux-armv6l.tar.gz
# tinygo_url: https://github.com/tinygo-org/tinygo/releases/download/v0.25.0/tinygo_0.25.0_armhf.deb
# oras_url: build your self
ARG BASE_IMAGE=docker.io/ubuntu
FROM $BASE_IMAGE
ARG GO_VERSION
ARG TINYGO_VERSION
ARG ORAS_VERSION
ARG HIGRESS_VERSION
ARG USE_HIGRESS_TINYGO
ARG GO_VERSION=1.24.4
ARG ORAS_VERSION=1.0.0
ARG HIGRESS_VERSION=1.0.0-rc
LABEL go_version=$GO_VERSION tinygo_version=$TINYGO_VERSION oras_version=$ORAS_VERSION
LABEL go_version=$GO_VERSION oras_version=$ORAS_VERSION
RUN apt-get update \
&& apt-get install -y wget \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y wget tar && rm -rf /var/lib/apt/lists/*
RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
go_url=; \
tinygo_url=; \
go_version=${GO_VERSION:-1.19}; \
tinygo_version=${TINYGO_VERSION:-0.25.0}; \
oras_version=${ORAS_VERSION:-1.0.0}; \
higress_version=${HIGRESS_VERSION:-1.0.0-rc}; \
use_higress_tinygo=${USE_HIGRESS_TINYGO:-false}; \
echo "arch: '$arch'"; \
echo "go go_version: '$go_version'"; \
echo "tinygo_version: '$tinygo_version'"; \
echo "oras_version: '$oras_version'"; \
echo "higress_version: '$higress_version'"; \
echo "use_higress_tinygo: '$use_higress_tinygo'"; \
RUN set -e; \
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
echo "arch: $arch"; \
go_url=""; tinygo_url=""; \
case "$arch" in \
'amd64') \
go_url="https://golang.google.cn/dl/go$go_version.linux-amd64.tar.gz"; \
if [ "$use_higress_tinygo" = "true" ]; \
then \
tinygo_url="https://github.com/alibaba/higress/releases/download/v$higress_version/higress-tinygo${tinygo_version}.linux-amd64.tar.gz"; \
else \
tinygo_url="https://github.com/tinygo-org/tinygo/releases/download/v$tinygo_version/tinygo${tinygo_version}.linux-amd64.tar.gz"; \
fi; \
oras_url="https://github.com/oras-project/oras/releases/download/v$oras_version/oras_${oras_version}_linux_amd64.tar.gz"; \
;; \
'arm64') \
go_url="https://golang.google.cn/dl/go$go_version.linux-arm64.tar.gz"; \
if [ "$use_higress_tinygo" = "true" ]; \
then \
tinygo_url="https://github.com/alibaba/higress/releases/download/v$higress_version/higress-tinygo${tinygo_version}.linux-arm64.tar.gz"; \
else \
tinygo_url="https://github.com/tinygo-org/tinygo/releases/download/v$tinygo_version/tinygo${tinygo_version}.linux-arm64.tar.gz"; \
fi; \
oras_url="https://github.com/oras-project/oras/releases/download/v$oras_version/oras_${oras_version}_linux_arm64.tar.gz"; \
;; \
*) echo >&2 "error: unsupported architecture '$arch' "; exit 1 ;; \
'amd64') \
go_url="https://golang.google.cn/dl/go${GO_VERSION}.linux-amd64.tar.gz"; \
oras_url="https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz"; \
;; \
'arm64') \
go_url="https://golang.google.cn/dl/go${GO_VERSION}.linux-arm64.tar.gz"; \
oras_url="https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_arm64.tar.gz"; \
;; \
*) echo >&2 "unsupported architecture: $arch"; exit 1 ;; \
esac; \
echo "go_url: '$go_url'"; \
wget -O go.tgz "$go_url" --progress=dot:giga; \
rm -rf /usr/local/go && tar -C /usr/local -xzf go.tgz && rm -rf go.tgz; \
echo "tinygo_url: '$tinygo_url'"; \
wget -O tinygo.tgz "$tinygo_url" --progress=dot:giga; \
rm -rf /usr/local/tinygo && tar -C /usr/local -xzf tinygo.tgz && rm -rf tinygo.tgz; \
echo "oras_url: '$oras_url'"; \
wget -O oras.tgz "$oras_url" --progress=dot:giga; \
tar -C /usr/local/bin -xzf oras.tgz && rm -rf oras.tgz; \
echo "go_url: $go_url"; \
wget -O go.tgz "$go_url" --progress=dot:giga || (echo "Failed to download Go" && exit 1); \
tar -C /usr/local -xzf go.tgz && rm go.tgz; \
echo "oras_url: $oras_url"; \
wget -O oras.tgz "$oras_url" --progress=dot:giga || (echo "Failed to download ORAS" && exit 1); \
tar -C /usr/local/bin -xzf oras.tgz && rm oras.tgz; \
echo "done";
ENV PATH=$PATH:/usr/local/go/bin:/usr/local/tinygo/bin:/usr/local/bin
ENV PATH=$PATH:/usr/local/go/bin:/usr/local/bin

View File

@@ -1,25 +1,23 @@
PLUGIN_NAME ?= hello-world
BUILDER_REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
GO_VERSION ?= 1.20.14
GO_VERSION ?= 1.24.4
TINYGO_VERSION ?= 0.29.0
ORAS_VERSION ?= 1.0.0
HIGRESS_VERSION ?= 1.0.0-rc
USE_HIGRESS_TINYGO ?= false
BUILDER ?= ${BUILDER_REGISTRY}wasm-go-builder:go${GO_VERSION}-tinygo${TINYGO_VERSION}-oras${ORAS_VERSION}
BUILDER ?= ${BUILDER_REGISTRY}wasm-go-builder:go${GO_VERSION}-oras${ORAS_VERSION}
BUILD_TIME := $(shell date "+%Y%m%d-%H%M%S")
COMMIT_ID := $(shell git rev-parse --short HEAD 2>/dev/null)
IMAGE_TAG = $(if $(strip $(PLUGIN_VERSION)),${PLUGIN_VERSION},${BUILD_TIME}-${COMMIT_ID})
IMG ?= ${REGISTRY}${PLUGIN_NAME}:${IMAGE_TAG}
GOPROXY := $(shell go env GOPROXY)
EXTRA_TAGS := $(shell [ -f extensions/${PLUGIN_NAME}/.buildrc ] && . extensions/${PLUGIN_NAME}/.buildrc && echo $$EXTRA_TAGS || echo "")
.DEFAULT:
build:
DOCKER_BUILDKIT=1 docker build --build-arg PLUGIN_NAME=${PLUGIN_NAME} \
--build-arg BUILDER=${BUILDER} \
--build-arg GOPROXY=$(GOPROXY) \
--build-arg EXTRA_TAGS=${EXTRA_TAGS} \
-t ${IMG} \
--output extensions/${PLUGIN_NAME} \
.
@@ -30,7 +28,6 @@ build-image:
DOCKER_BUILDKIT=1 docker build --build-arg PLUGIN_NAME=${PLUGIN_NAME} \
--build-arg BUILDER=${BUILDER} \
--build-arg GOPROXY=$(GOPROXY) \
--build-arg EXTRA_TAGS=${EXTRA_TAGS} \
-t ${IMG} \
.
@echo ""
@@ -52,7 +49,6 @@ builder:
--platform linux/amd64,linux/arm64 \
--build-arg BASE_IMAGE=docker.io/ubuntu \
--build-arg GO_VERSION=$(GO_VERSION) \
--build-arg TINYGO_VERSION=$(TINYGO_VERSION) \
--build-arg ORAS_VERSION=$(ORAS_VERSION) \
--build-arg HIGRESS_VERSION=$(HIGRESS_VERSION) \
--build-arg USE_HIGRESS_TINYGO=$(USE_HIGRESS_TINYGO) \
@@ -64,9 +60,8 @@ builder:
@echo "image: ${BUILDER}"
local-build:
tinygo build -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' \
-o extensions/${PLUGIN_NAME}/main.wasm \
extensions/${PLUGIN_NAME}/main.go
cd extensions/${PLUGIN_NAME};GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ./main.wasm .
@echo ""
@echo "wasm: extensions/${PLUGIN_NAME}/main.wasm"

View File

@@ -45,16 +45,15 @@ output wasm file: extensions/request-block/plugin.wasm
编译环境要求如下:
- Go 版本: >= 1.18 (需要支持范型特性)
- TinyGo 版本: >= 0.28.1
- Go 版本: >= 1.24 (需要支持 wasm 构建特性)
下面是本地多步骤构建 [request-block](extensions/request-block) 的例子。
### step1. 编译 wasm
```bash
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./extensions/request-block/main.go
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ./extensions/request-block/main.wasm ./extensions/request-block
```
详细的编译说明,包括要使用更复杂的 Header 状态管理机制,请参考[ Go 开发插件的最佳实践](https://higress.io/docs/latest/user/wasm-go/#3-%E7%BC%96%E8%AF%91%E7%94%9F%E6%88%90-wasm-%E6%96%87%E4%BB%B6)。
@@ -70,8 +69,8 @@ COPY main.wasm plugin.wasm
```
```bash
docker build -t <your_registry_hub>/request-block:1.0.0 -f <your_dockerfile> .
docker push <your_registry_hub>/request-block:1.0.0
docker build -t <your_registry_hub>/request-block:2.0.0 -f <your_dockerfile> .
docker push <your_registry_hub>/request-block:2.0.0
```
## 创建 WasmPlugin 资源使插件生效
@@ -144,11 +143,54 @@ spec:
block_bodies:
- "foo"
- "bar"
url: oci://<your_registry_hub>/request-block:1.0.0
url: oci://<your_registry_hub>/request-block:2.0.0
```
所有规则会按上面配置的顺序一次执行匹配,当有一个规则匹配时,就停止匹配,并选择匹配的配置执行插件逻辑。
## 单元测试
在开发wasm插件时建议同时编写单元测试来验证插件功能。详细的单元测试编写指南请参考 [wasm plugin unit test](https://github.com/higress-group/wasm-go/blob/main/pkg/test/README.md)。
### 单元测试样例
```go
func TestMyPlugin(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// 1. 创建测试主机
config := json.RawMessage(`{"key": "value"}`)
host, status := test.NewTestHost(config)
require.Equal(t, types.OnPluginStartStatusOK, status)
defer host.Reset()
// 2. 设置请求头
headers := [][2]string{
{":method", "GET"},
{":path", "/test"},
{":authority", "test.com"},
}
// 3. 调用插件请求头处理方法
action := host.CallOnHttpRequestHeaders(headers)
require.Equal(t, types.ActionPause, action)
// 4. 模拟外部调用响应(如果需要)
// host.CallOnRedisCall(0, test.CreateRedisRespString("OK"))
// host.CallOnHttpCall([][2]string{{":status", "200"}}, []byte(`{"result": "success"}`))
// 5. 完成请求
host.CompleteHttp()
// 6. 验证结果(如果插件里返回了响应)
localResponse := host.GetLocalResponse()
require.NotNil(t, localResponse)
assert.Equal(t, uint32(200), localResponse.StatusCode)
})
}
```
## E2E测试
当你完成一个GO语言的插件功能时, 可以同时创建关联的e2e test cases, 并在本地对插件功能完成测试验证。

View File

@@ -41,16 +41,14 @@ You can also use `make build-push` to build and push the image at the same time.
You can also build wasm locally and copy it to a Docker image. This requires a local build environment:
Go version: >= 1.18
TinyGo version: >= 0.25.0
Go version: >= 1.24
The following is an example of building the plugin [request-block](extensions/request-block).
### step1. build wasm
```bash
tinygo build -o main.wasm -scheduler=none -target=wasi ./extensions/request-block/main.go
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ./extensions/request-block/main.wasm ./extensions/request-block
```
### step2. build and push docker image
@@ -63,8 +61,8 @@ COPY main.wasm plugin.wasm
```
```bash
docker build -t <your_registry_hub>/request-block:1.0.0 -f <your_dockerfile> .
docker push <your_registry_hub>/request-block:1.0.0
docker build -t <your_registry_hub>/request-block:2.0.0 -f <your_dockerfile> .
docker push <your_registry_hub>/request-block:2.0.0
```
## Apply WasmPlugin API
@@ -83,7 +81,7 @@ spec:
defaultConfig:
block_urls:
- "swagger.html"
url: oci://<your_registry_hub>/request-block:1.0.0
url: oci://<your_registry_hub>/request-block:2.0.0
```
When the resource is applied on the Kubernetes cluster with `kubectl apply -f <your-wasm-plugin-yaml>`,
@@ -141,6 +139,52 @@ spec:
The rules will be matched in the order of configuration. If one match is found, it will stop, and the matching configuration will take effect.
## Unit Testing
When developing wasm plugins, it's recommended to write unit tests to verify plugin functionality. For detailed unit testing guidelines, please refer to [wasm plugin unit test](https://github.com/higress-group/wasm-go/blob/main/pkg/test/README.md).
### Unit Test Structure Example
```go
func TestMyPlugin(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// 1. Create test host
config := json.RawMessage(`{"key": "value"}`)
host, status := test.NewTestHost(config)
require.Equal(t, types.OnPluginStartStatusOK, status)
defer host.Reset()
// 2. Set request headers
headers := [][2]string{
{":method", "GET"},
{":path", "/test"},
{":authority", "test.com"},
}
// 3. Call plugin request header processing method
action := host.CallOnHttpRequestHeaders(headers)
require.Equal(t, types.ActionPause, action)
// 4. Simulate external call responses (if needed)
// host.CallOnRedisCall(0, test.CreateRedisRespString("OK"))
// host.CallOnHttpCall([][2]string{{":status", "200"}}, []byte(`{"result": "success"}`))
// 5. Complete request
host.CompleteHttp()
// 6. Verify results (if the plugin returns a response)
localResponse := host.GetLocalResponse()
require.NotNil(t, localResponse)
assert.Equal(t, uint32(200), localResponse.StatusCode)
})
}
```
This example shows the basic test structure including configuration parsing, request processing flow, and result verification.
## E2E test
When you complete a GO plug-in function, you can create associated e2e test cases at the same time, and complete the test verification of the plug-in function locally.

View File

@@ -1,20 +1,18 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/custom-logs
go 1.18
go 1.24.1
replace github.com/alibaba/higress/plugins/wasm-go => ../..
toolchain go1.24.4
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v1.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80
github.com/higress-group/wasm-go v1.0.0
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)

View File

@@ -1,20 +1,23 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v1.0.0 h1:BZRNf4R7jr9hwRivg/E29nkVaKEak5MWjBDhWjuHijU=
github.com/higress-group/proxy-wasm-go-sdk v1.0.0/go.mod h1:iiSyFbo+rAtbtGt/bsefv8GU57h9CCLYGJA74/tF5/0=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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.0 h1:4Ik5n3FsJ5+r13KLQl2ky+8NuAE8dfWQwoKxXYD2KAw=
github.com/higress-group/wasm-go v1.0.0/go.mod h1:ODBV27sjmhIW8Cqv3R74EUcTnbdkE69bmXBQFuRkY1M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.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 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -19,10 +19,12 @@ import (
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/wrapper"
)
func main() {
func main() {}
func init() {
wrapper.SetCtx(
"custom-log",
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),

View File

@@ -1,20 +1,18 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/custom-logs
go 1.18
go 1.24.1
replace github.com/alibaba/higress/plugins/wasm-go => ../..
toolchain go1.24.4
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v1.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80
github.com/higress-group/wasm-go v1.0.0
github.com/tidwall/gjson v1.18.0
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)

View File

@@ -1,20 +1,23 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v1.0.0 h1:BZRNf4R7jr9hwRivg/E29nkVaKEak5MWjBDhWjuHijU=
github.com/higress-group/proxy-wasm-go-sdk v1.0.0/go.mod h1:iiSyFbo+rAtbtGt/bsefv8GU57h9CCLYGJA74/tF5/0=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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.0 h1:4Ik5n3FsJ5+r13KLQl2ky+8NuAE8dfWQwoKxXYD2KAw=
github.com/higress-group/wasm-go v1.0.0/go.mod h1:ODBV27sjmhIW8Cqv3R74EUcTnbdkE69bmXBQFuRkY1M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.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 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -22,10 +22,12 @@ import (
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/wrapper"
)
func main() {
func main() {}
func init() {
wrapper.SetCtx(
"custom-span-attribute",
wrapper.ParseConfigBy(parseConfig),

View File

@@ -293,7 +293,7 @@ apis:
本示例配置了三个服务演示了get与post两种类型的工具。其中get类型的工具包括高德地图与心知天气post类型的工具是deepl翻译。三个服务都需要现在Higress的服务中以DNS域名的方式配置好并确保健康。
高德地图提供了两个工具分别是获取指定地点的坐标以及搜索坐标附近的感兴趣的地点。文档https://lbs.amap.com/api/webservice/guide/api-advanced/newpoisearch
心知天气提供了一个工具用于获取指定城市的实时天气情况支持中文英文日语返回以及摄氏度和华氏度的表示。文档https://seniverse.yuque.com/hyper_data/api_v3/nyiu3t
deepl提供了一个工具用于翻译给定的句子支持多语言。文档https://developers.deepl.com/docs/v/zh/api-reference/translate?fallback=true
deepl提供了一个工具用于翻译给定的句子支持多语言。文档https://developers.deepl.com/api-reference/translate/request-translation
以下为测试用例为了效果的稳定性建议保持大模型版本的稳定本例子中使用的qwen-max-0403

View File

@@ -283,7 +283,7 @@ apis:
This example configures three services demonstrating both GET and POST types of tools. The GET type tools include Amap and XZWeather, while the POST type tool is the DeepL translation. All three services need to be properly configured in the Higress service with DNS domain names and should be healthy.
Amap provides two tools, one for obtaining the coordinates of a specified location and the other for searching for points of interest near the coordinates. Document: https://lbs.amap.com/api/webservice/guide/api-advanced/newpoisearch
XZWeather provides one tool to get real-time weather conditions for a specified city, supporting results in Chinese, English, and Japanese, as well as representations in Celsius and Fahrenheit. Document: https://seniverse.yuque.com/hyper_data/api_v3/nyiu3t
DeepL provides one tool for translating given sentences, supporting multiple languages. Document: https://developers.deepl.com/docs/v/zh/api-reference/translate?fallback=true
DeepL provides one tool for translating given sentences, supporting multiple languages. Document: https://developers.deepl.com/api-reference/translate/request-translation
Below are test cases. For stability, it is recommended to maintain a stable version of the large model. The example used here is qwen-max-0403:
**Request Example**

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
"gopkg.in/yaml.v2"
)

View File

@@ -1,19 +1,25 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/ai-agent
go 1.19
go 1.24.1
toolchain go1.24.4
require (
github.com/alibaba/higress/plugins/wasm-go v1.4.2
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.17.3
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250822030947-8345453fddd0
github.com/higress-group/wasm-go v1.0.2-0.20250821081215-b573359becf8
github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.18.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tetratelabs/wazero v1.7.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // 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
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,26 +1,32 @@
github.com/alibaba/higress/plugins/wasm-go v1.4.2 h1:gH7OIGXm4wtW5Vo7L2deMPqF7OVWNESDHv1CaaTGu6s=
github.com/alibaba/higress/plugins/wasm-go v1.4.2/go.mod h1:359don/ahMxpfeLMzr29Cjwcu8IywTTDUzWlBPRNLHw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/proxy-wasm-go-sdk v0.0.0-20250822030947-8345453fddd0 h1:YGdj8KBzVjabU3STUfwMZghB+VlX6YLfJtLbrsWaOD0=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250822030947-8345453fddd0/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/higress-group/wasm-go v1.0.2-0.20250821081215-b573359becf8 h1:rs+AH1wfZy4swzuAyiRXT7xPUm8gycXt9Gwy0tqOq0o=
github.com/higress-group/wasm-go v1.0.2-0.20250821081215-b573359becf8/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
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 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
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=
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -11,9 +11,10 @@ import (
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-agent/dashscope"
prompttpl "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-agent/promptTpl"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"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/wrapper"
"github.com/tidwall/gjson"
)
@@ -26,7 +27,9 @@ const ActionPattern = `Action:\s*(.*?)[.\n]`
const ActionInputPattern = `Action Input:\s*(.*)`
const FinalAnswerPattern = `Final Answer:(.*)`
func main() {
func main() {}
func init() {
wrapper.SetCtx(
"ai-agent",
wrapper.ParseConfigBy(parseConfig),
@@ -37,7 +40,7 @@ func main() {
)
}
func parseConfig(gjson gjson.Result, c *PluginConfig, log wrapper.Log) error {
func parseConfig(gjson gjson.Result, c *PluginConfig, log log.Log) error {
initResponsePromptTpl(gjson, c)
err := initAPIs(gjson, c)
@@ -54,11 +57,12 @@ func parseConfig(gjson gjson.Result, c *PluginConfig, log wrapper.Log) error {
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log log.Log) types.Action {
ctx.DisableReroute()
return types.ActionContinue
}
func firstReq(ctx wrapper.HttpContext, config PluginConfig, prompt string, rawRequest Request, log wrapper.Log) types.Action {
func firstReq(ctx wrapper.HttpContext, config PluginConfig, prompt string, rawRequest Request, log log.Log) types.Action {
log.Debugf("[onHttpRequestBody] firstreq:%s", prompt)
var userMessage Message
@@ -88,7 +92,7 @@ func firstReq(ctx wrapper.HttpContext, config PluginConfig, prompt string, rawRe
}
}
func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte, log wrapper.Log) types.Action {
func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte, log log.Log) types.Action {
log.Debug("onHttpRequestBody start")
defer log.Debug("onHttpRequestBody end")
@@ -172,7 +176,7 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte
return ret
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log log.Log) types.Action {
log.Debug("onHttpResponseHeaders start")
defer log.Debug("onHttpResponseHeaders end")
@@ -200,7 +204,7 @@ func extractJson(bodyStr string) (string, error) {
return jsonStr, nil
}
func jsonFormat(llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonSchema map[string]interface{}, assistantMessage Message, actionInput string, headers [][2]string, streamMode bool, rawResponse Response, log wrapper.Log) string {
func jsonFormat(llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonSchema map[string]interface{}, assistantMessage Message, actionInput string, headers [][2]string, streamMode bool, rawResponse Response, log log.Log) string {
prompt := fmt.Sprintf(prompttpl.Json_Resp_Template, jsonSchema, actionInput)
messages := []dashscope.Message{{Role: "user", Content: prompt}}
@@ -241,7 +245,7 @@ func jsonFormat(llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonSchema map[st
return content
}
func noneStream(assistantMessage Message, actionInput string, rawResponse Response, log wrapper.Log) {
func noneStream(assistantMessage Message, actionInput string, rawResponse Response, log log.Log) {
assistantMessage.Role = "assistant"
assistantMessage.Content = actionInput
rawResponse.Choices[0].Message = assistantMessage
@@ -257,7 +261,7 @@ func noneStream(assistantMessage Message, actionInput string, rawResponse Respon
}
}
func stream(actionInput string, rawResponse Response, log wrapper.Log) {
func stream(actionInput string, rawResponse Response, log log.Log) {
headers := [][2]string{{"content-type", "text/event-stream; charset=utf-8"}}
proxywasm.ReplaceHttpResponseHeaders(headers)
// Remove quotes from actionInput
@@ -271,7 +275,7 @@ func stream(actionInput string, rawResponse Response, log wrapper.Log) {
proxywasm.ResumeHttpResponse()
}
func toolsCallResult(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonResp JsonResp, aPIsParam []APIsParam, aPIClient []wrapper.HttpClient, content string, rawResponse Response, log wrapper.Log, statusCode int, responseBody []byte) {
func toolsCallResult(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonResp JsonResp, aPIsParam []APIsParam, aPIClient []wrapper.HttpClient, content string, rawResponse Response, log log.Log, statusCode int, responseBody []byte) {
if statusCode != http.StatusOK {
log.Debugf("statusCode: %d", statusCode)
}
@@ -332,7 +336,7 @@ func toolsCallResult(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmI
}
}
func outputParser(response string, log wrapper.Log) (string, string) {
func outputParser(response string, log log.Log) (string, string) {
log.Debugf("Raw response:%s", response)
start := strings.Index(response, "```")
@@ -379,7 +383,7 @@ func outputParser(response string, log wrapper.Log) (string, string) {
return "", ""
}
func toolsCall(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonResp JsonResp, aPIsParam []APIsParam, aPIClient []wrapper.HttpClient, content string, rawResponse Response, log wrapper.Log) (types.Action, string) {
func toolsCall(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonResp JsonResp, aPIsParam []APIsParam, aPIClient []wrapper.HttpClient, content string, rawResponse Response, log log.Log) (types.Action, string) {
dashscope.MessageStore.AddForAssistant(content)
action, actionInput := outputParser(content, log)
@@ -514,7 +518,7 @@ func toolsCall(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LL
}
// 从response接收到firstreq的大模型返回
func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, body []byte, log wrapper.Log) types.Action {
func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, body []byte, log log.Log) types.Action {
log.Debugf("onHttpResponseBody start")
defer log.Debugf("onHttpResponseBody end")

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
EXTRA_TAGS=proxy_wasm_version_0_2_100

View File

@@ -4,7 +4,8 @@ import (
"errors"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)
@@ -15,7 +16,7 @@ const (
type providerInitializer interface {
ValidateConfig(ProviderConfig) error
CreateProvider(ProviderConfig, wrapper.Log) (Provider, error)
CreateProvider(ProviderConfig, log.Log) (Provider, error)
}
var (
@@ -128,7 +129,7 @@ func (c *ProviderConfig) Validate() error {
return nil
}
func CreateProvider(pc ProviderConfig, log wrapper.Log) (Provider, error) {
func CreateProvider(pc ProviderConfig, log log.Log) (Provider, error) {
initializer, has := providerInitializers[pc.typ]
if !has {
return nil, errors.New("unknown provider type: " + pc.typ)

View File

@@ -3,7 +3,8 @@ package cache
import (
"errors"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
)
type redisProviderInitializer struct {
@@ -16,7 +17,7 @@ func (r *redisProviderInitializer) ValidateConfig(cf ProviderConfig) error {
return nil
}
func (r *redisProviderInitializer) CreateProvider(cf ProviderConfig, log wrapper.Log) (Provider, error) {
func (r *redisProviderInitializer) CreateProvider(cf ProviderConfig, log log.Log) (Provider, error) {
rp := redisProvider{
config: cf,
client: wrapper.NewRedisClusterClient(wrapper.FQDNCluster{
@@ -32,7 +33,7 @@ func (r *redisProviderInitializer) CreateProvider(cf ProviderConfig, log wrapper
type redisProvider struct {
config ProviderConfig
client wrapper.RedisClient
log wrapper.Log
log log.Log
}
func (rp *redisProvider) GetProviderType() string {

View File

@@ -6,7 +6,7 @@ import (
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/cache"
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/embedding"
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/vector"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/tidwall/gjson"
)
@@ -46,7 +46,7 @@ type PluginConfig struct {
CacheKeyStrategy string
}
func (c *PluginConfig) FromJson(json gjson.Result, log wrapper.Log) {
func (c *PluginConfig) FromJson(json gjson.Result, log log.Log) {
c.embeddingProviderConfig = &embedding.ProviderConfig{}
c.vectorProviderConfig = &vector.ProviderConfig{}
c.cacheProviderConfig = &cache.ProviderConfig{}
@@ -91,7 +91,7 @@ func (c *PluginConfig) FromJson(json gjson.Result, log wrapper.Log) {
if json.Get("enableSemanticCache").Exists() {
c.EnableSemanticCache = json.Get("enableSemanticCache").Bool()
} else if c.GetVectorProvider() == nil {
c.EnableSemanticCache = false // set value to false when no vector provider
c.EnableSemanticCache = false // set value to false when no vector provider
} else {
c.EnableSemanticCache = true // set default value to true
}
@@ -142,7 +142,7 @@ func (c *PluginConfig) Validate() error {
return nil
}
func (c *PluginConfig) Complete(log wrapper.Log) error {
func (c *PluginConfig) Complete(log log.Log) error {
var err error
if c.embeddingProviderConfig.GetProviderType() != "" {
log.Debugf("embedding provider is set to %s", c.embeddingProviderConfig.GetProviderType())
@@ -193,7 +193,7 @@ func (c *PluginConfig) GetCacheProvider() cache.Provider {
return c.cacheProvider
}
func convertLegacyMapFields(c *PluginConfig, json gjson.Result, log wrapper.Log) {
func convertLegacyMapFields(c *PluginConfig, json gjson.Result, log log.Log) {
keyMap := map[string]string{
"cacheKeyFrom.requestBody": "cacheKeyFrom",
"cacheValueFrom.requestBody": "cacheValueFrom",
@@ -212,7 +212,7 @@ func convertLegacyMapFields(c *PluginConfig, json gjson.Result, log wrapper.Log)
}
}
func setField(c *PluginConfig, fieldName string, value string, log wrapper.Log) {
func setField(c *PluginConfig, fieldName string, value string, log log.Log) {
switch fieldName {
case "cacheKeyFrom":
c.CacheKeyFrom = value

View File

@@ -8,13 +8,14 @@ import (
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/config"
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/vector"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
logs "github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/resp"
)
// CheckCacheForKey checks if the key is in the cache, or triggers similarity search if not found.
func CheckCacheForKey(key string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, stream bool, useSimilaritySearch bool) error {
func CheckCacheForKey(key string, ctx wrapper.HttpContext, c config.PluginConfig, log logs.Log, stream bool, useSimilaritySearch bool) error {
activeCacheProvider := c.GetCacheProvider()
if activeCacheProvider == nil {
log.Debugf("[%s] [CheckCacheForKey] no cache provider configured, performing similarity search", PLUGIN_NAME)
@@ -37,7 +38,7 @@ func CheckCacheForKey(key string, ctx wrapper.HttpContext, c config.PluginConfig
}
// handleCacheResponse processes cache response and handles cache hits and misses.
func handleCacheResponse(key string, response resp.Value, ctx wrapper.HttpContext, log wrapper.Log, stream bool, c config.PluginConfig, useSimilaritySearch bool) {
func handleCacheResponse(key string, response resp.Value, ctx wrapper.HttpContext, log logs.Log, stream bool, c config.PluginConfig, useSimilaritySearch bool) {
if err := response.Error(); err == nil && !response.IsNull() {
log.Infof("[%s] cache hit for key: %s", PLUGIN_NAME, key)
processCacheHit(key, response.String(), stream, ctx, c, log)
@@ -60,7 +61,7 @@ func handleCacheResponse(key string, response resp.Value, ctx wrapper.HttpContex
}
// processCacheHit handles a successful cache hit.
func processCacheHit(key string, response string, stream bool, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log) {
func processCacheHit(key string, response string, stream bool, ctx wrapper.HttpContext, c config.PluginConfig, log logs.Log) {
if strings.TrimSpace(response) == "" {
log.Warnf("[%s] [processCacheHit] cached response for key %s is empty", PLUGIN_NAME, key)
proxywasm.ResumeHttpRequest()
@@ -85,7 +86,7 @@ func processCacheHit(key string, response string, stream bool, ctx wrapper.HttpC
}
// performSimilaritySearch determines the appropriate similarity search method to use.
func performSimilaritySearch(key string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, queryString string, stream bool) error {
func performSimilaritySearch(key string, ctx wrapper.HttpContext, c config.PluginConfig, log logs.Log, queryString string, stream bool) error {
activeVectorProvider := c.GetVectorProvider()
if activeVectorProvider == nil {
return logAndReturnError(log, "[performSimilaritySearch] no vector provider configured for similarity search")
@@ -107,19 +108,19 @@ func performSimilaritySearch(key string, ctx wrapper.HttpContext, c config.Plugi
}
// performStringQuery executes the string-based similarity search.
func performStringQuery(key string, queryString string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, stream bool) error {
func performStringQuery(key string, queryString string, ctx wrapper.HttpContext, c config.PluginConfig, log logs.Log, stream bool) error {
stringQuerier, ok := c.GetVectorProvider().(vector.StringQuerier)
if !ok {
return logAndReturnError(log, "[performStringQuery] active vector provider does not implement StringQuerier interface")
}
return stringQuerier.QueryString(queryString, ctx, log, func(results []vector.QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error) {
return stringQuerier.QueryString(queryString, ctx, log, func(results []vector.QueryResult, ctx wrapper.HttpContext, log logs.Log, err error) {
handleQueryResults(key, results, ctx, log, stream, c, err)
})
}
// performEmbeddingQuery executes the embedding-based similarity search.
func performEmbeddingQuery(key string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, stream bool) error {
func performEmbeddingQuery(key string, ctx wrapper.HttpContext, c config.PluginConfig, log logs.Log, stream bool) error {
embeddingQuerier, ok := c.GetVectorProvider().(vector.EmbeddingQuerier)
if !ok {
return logAndReturnError(log, fmt.Sprintf("[performEmbeddingQuery] active vector provider does not implement EmbeddingQuerier interface"))
@@ -138,7 +139,7 @@ func performEmbeddingQuery(key string, ctx wrapper.HttpContext, c config.PluginC
}
ctx.SetContext(CACHE_KEY_EMBEDDING_KEY, textEmbedding)
err = embeddingQuerier.QueryEmbedding(textEmbedding, ctx, log, func(results []vector.QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error) {
err = embeddingQuerier.QueryEmbedding(textEmbedding, ctx, log, func(results []vector.QueryResult, ctx wrapper.HttpContext, log logs.Log, err error) {
handleQueryResults(key, results, ctx, log, stream, c, err)
})
if err != nil {
@@ -148,7 +149,7 @@ func performEmbeddingQuery(key string, ctx wrapper.HttpContext, c config.PluginC
}
// handleQueryResults processes the results of similarity search and determines next actions.
func handleQueryResults(key string, results []vector.QueryResult, ctx wrapper.HttpContext, log wrapper.Log, stream bool, c config.PluginConfig, err error) {
func handleQueryResults(key string, results []vector.QueryResult, ctx wrapper.HttpContext, log logs.Log, stream bool, c config.PluginConfig, err error) {
if err != nil {
handleInternalError(err, fmt.Sprintf("[%s] [handleQueryResults] error querying vector database for key: %s", PLUGIN_NAME, key), log)
return
@@ -186,14 +187,14 @@ func handleQueryResults(key string, results []vector.QueryResult, ctx wrapper.Ht
}
// logAndReturnError logs an error and returns it.
func logAndReturnError(log wrapper.Log, message string) error {
func logAndReturnError(log logs.Log, message string) error {
message = fmt.Sprintf("[%s] %s", PLUGIN_NAME, message)
log.Errorf(message)
return errors.New(message)
}
// handleInternalError logs an error and resumes the HTTP request.
func handleInternalError(err error, message string, log wrapper.Log) {
func handleInternalError(err error, message string, log logs.Log) {
if err != nil {
log.Errorf("[%s] [handleInternalError] %s: %v", PLUGIN_NAME, message, err)
} else {
@@ -204,7 +205,7 @@ func handleInternalError(err error, message string, log wrapper.Log) {
}
// Caches the response value
func cacheResponse(ctx wrapper.HttpContext, c config.PluginConfig, key string, value string, log wrapper.Log) {
func cacheResponse(ctx wrapper.HttpContext, c config.PluginConfig, key string, value string, log logs.Log) {
if strings.TrimSpace(value) == "" {
log.Warnf("[%s] [cacheResponse] cached value for key %s is empty", PLUGIN_NAME, key)
return
@@ -219,7 +220,7 @@ func cacheResponse(ctx wrapper.HttpContext, c config.PluginConfig, key string, v
}
// Handles embedding upload if available
func uploadEmbeddingAndAnswer(ctx wrapper.HttpContext, c config.PluginConfig, key string, value string, log wrapper.Log) {
func uploadEmbeddingAndAnswer(ctx wrapper.HttpContext, c config.PluginConfig, key string, value string, log logs.Log) {
embedding := ctx.GetContext(CACHE_KEY_EMBEDDING_KEY)
if embedding == nil {
return

View File

@@ -7,8 +7,8 @@ import (
"net/http"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)

View File

@@ -7,8 +7,8 @@ import (
"net/http"
"strconv"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)

View File

@@ -7,8 +7,8 @@ import (
"net/http"
"strconv"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)

View File

@@ -8,8 +8,8 @@ import (
"strconv"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)

View File

@@ -7,8 +7,8 @@ import (
"net/http"
"strconv"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)

Some files were not shown because too many files have changed in this diff Show More