Compare commits

..

11 Commits

25 changed files with 4914 additions and 200 deletions

View File

@@ -10,14 +10,14 @@ Deploy and configure Higress AI Gateway for Clawdbot/OpenClaw integration with o
## Prerequisites
- Docker installed and running
- Internet access to download the setup script
- Internet access to download setup script
- LLM provider API keys (at least one)
## Workflow
### Step 1: Download Setup Script
Download the official get-ai-gateway.sh script:
Download official get-ai-gateway.sh script:
```bash
curl -fsSL https://raw.githubusercontent.com/higress-group/higress-standalone/main/all-in-one/get-ai-gateway.sh -o get-ai-gateway.sh
@@ -26,15 +26,17 @@ chmod +x get-ai-gateway.sh
### Step 2: Gather Configuration
Ask the user for:
Ask user for:
1. **LLM Provider API Keys** (at least one required):
**Top Commonly Used Providers:**
- Aliyun Dashscope (Qwen): `--dashscope-key`
- DeepSeek: `--deepseek-key`
- Moonshot (Kimi): `--moonshot-key`
- Zhipu AI: `--zhipuai-key`
- Claude Code (OAuth mode): `--claude-code-key` (run `claude setup-token` to get token)
- Claude: `--claude-key`
- Minimax: `--minimax-key`
- Azure OpenAI: `--azure-key`
- AWS Bedrock: `--bedrock-key`
@@ -42,8 +44,8 @@ Ask the user for:
- OpenAI: `--openai-key`
- OpenRouter: `--openrouter-key`
- Grok: `--grok-key`
See CLI Parameters Reference for complete list with model pattern options.
To configure additional providers beyond the above, run `./get-ai-gateway.sh --help` to view the complete list of supported models and providers.
2. **Port Configuration** (optional):
- HTTP port: `--http-port` (default: 8080)
@@ -56,7 +58,7 @@ Ask the user for:
### Step 3: Run Setup Script
Run the script in non-interactive mode with gathered parameters:
Run script in non-interactive mode with gathered parameters:
```bash
./get-ai-gateway.sh start --non-interactive \
@@ -100,23 +102,23 @@ After script completion:
docker ps --filter "name=higress-ai-gateway"
```
2. Test the gateway endpoint:
2. Test gateway endpoint:
```bash
curl http://localhost:8080/v1/models
```
3. Access the console (optional):
3. Access console (optional):
```
http://localhost:8001
```
### Step 5: Configure Clawdbot/OpenClaw Plugin
If the user wants to use Higress with Clawdbot/OpenClaw, install the appropriate plugin:
If user wants to use Higress with Clawdbot/OpenClaw, install appropriate plugin:
#### Automatic Installation
Detect runtime and install the correct plugin version:
Detect runtime and install correct plugin version:
```bash
# Detect which runtime is installed
@@ -133,7 +135,7 @@ else
exit 1
fi
# Install the plugin
# Install plugin
PLUGIN_DEST="$RUNTIME_DIR/extensions/higress-ai-gateway"
echo "Installing Higress AI Gateway plugin for $RUNTIME..."
mkdir -p "$(dirname "$PLUGIN_DEST")"
@@ -142,7 +144,7 @@ cp -r "$PLUGIN_SRC" "$PLUGIN_DEST"
echo "✓ Plugin installed at: $PLUGIN_DEST"
# Configure provider
echo
echo ""
echo "Configuring provider..."
$RUNTIME models auth login --provider higress
```
@@ -161,10 +163,8 @@ After deployment, manage API keys without redeploying:
```bash
# View configured API keys
./get-ai-gateway.sh config list
# Add or update an API key (hot-reload, no restart needed)
./get-ai-gateway.sh config add --provider <provider> --key <api-key>
# Remove an API key (hot-reload, no restart needed)
./get-ai-gateway.sh config remove --provider <provider>
```
@@ -220,13 +220,15 @@ PLUGIN_REGISTRY="higress-registry.ap-southeast-7.cr.aliyuncs.com" \
| `--deepseek-key` | DeepSeek |
| `--moonshot-key` | Moonshot (Kimi) |
| `--zhipuai-key` | Zhipu AI |
| `--claude-code-key` | Claude Code (OAuth mode - run `claude setup-token` to get token) |
| `--claude-key` | Claude |
| `--openai-key` | OpenAI |
| `--openrouter-key` | OpenRouter |
| `--claude-key` | Claude |
| `--gemini-key` | Google Gemini |
| `--groq-key` | Groq |
**Additional Providers:**
`--doubao-key`, `--baichuan-key`, `--yi-key`, `--stepfun-key`, `--minimax-key`, `--cohere-key`, `--mistral-key`, `--github-key`, `--fireworks-key`, `--togetherai-key`, `--grok-key`, `--azure-key`, `--bedrock-key`, `--vertex-key`
## Managing Configuration
@@ -236,15 +238,14 @@ PLUGIN_REGISTRY="higress-registry.ap-southeast-7.cr.aliyuncs.com" \
```bash
# List all configured API keys
./get-ai-gateway.sh config list
# Add or update an API key (hot-reload)
./get-ai-gateway.sh config add --provider deepseek --key sk-xxx
# Remove an API key (hot-reload)
./get-ai-gateway.sh config remove --provider deepseek
```
**Supported provider aliases:**
`dashscope`/`qwen`, `moonshot`/`kimi`, `zhipuai`/`zhipu`, `togetherai`/`together`
### Routing Rules
@@ -252,10 +253,8 @@ PLUGIN_REGISTRY="higress-registry.ap-southeast-7.cr.aliyuncs.com" \
```bash
# Add a routing rule
./get-ai-gateway.sh route add --model claude-opus-4.5 --trigger "深入思考|deep thinking"
# List all rules
./get-ai-gateway.sh route list
# Remove a rule
./get-ai-gateway.sh route remove --rule-id 0
```
@@ -265,11 +264,12 @@ See [higress-auto-router](../higress-auto-router/SKILL.md) for detailed document
## Access Logs
Gateway access logs are available at:
```
$DATA_FOLDER/logs/access.log
```
These logs can be used with the **agent-session-monitor** skill for token tracking and conversation analysis.
These logs can be used with **agent-session-monitor** skill for token tracking and conversation analysis.
## Related Skills
@@ -295,6 +295,7 @@ These logs can be used with the **agent-session-monitor** skill for token tracki
```
**Response:**
```
Auto-detected timezone: Asia/Shanghai
Selected plugin registry: higress-registry.cn-hangzhou.cr.aliyuncs.com
@@ -325,6 +326,7 @@ curl 'http://localhost:8080/v1/chat/completions' \
4. Set up session monitoring
**Response:**
```
Auto-detected timezone: Asia/Shanghai
Selected plugin registry: higress-registry.cn-hangzhou.cr.aliyuncs.com
@@ -366,6 +368,7 @@ Selected plugin registry: higress-registry.cn-hangzhou.cr.aliyuncs.com
```
**Response:**
```
当前配置的API keys:
@@ -399,6 +402,7 @@ Configuration has been hot-reloaded (no restart needed).
```
**Response:**
```
Auto-detected timezone: America/Los_Angeles
Selected plugin registry: higress-registry.us-west-1.cr.aliyuncs.com

View File

@@ -182,6 +182,8 @@ Higress would not be possible without the valuable open-source work of projects
- Higress Console: https://github.com/higress-group/higress-console
- Higress Standalone: https://github.com/higress-group/higress-standalone
- Higress Plugin Serverhttps://github.com/higress-group/plugin-server
- Higress Wasm Plugin Golang SDKhttps://github.com/higress-group/wasm-go
### Contributors

View File

@@ -208,6 +208,8 @@ WeChat公式アカウント
- Higressコンソールhttps://github.com/higress-group/higress-console
- Higressスタンドアロン版https://github.com/higress-group/higress-standalone
- Higress Plugin Serverhttps://github.com/higress-group/plugin-server
- Higress Wasm Plugin Golang SDKhttps://github.com/higress-group/wasm-go
### 貢献者

View File

@@ -221,6 +221,8 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
- Higress 控制台https://github.com/higress-group/higress-console
- Higress独立运行版https://github.com/higress-group/higress-standalone
- Higress 插件服务器https://github.com/higress-group/plugin-server
- Higress Wasm 插件 Golang SDKhttps://github.com/higress-group/wasm-go
### 贡献者

45
go.mod
View File

@@ -20,7 +20,7 @@ require (
github.com/caddyserver/certmagic v0.21.3
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5
github.com/dubbogo/gost v1.13.1
github.com/envoyproxy/go-control-plane/envoy v1.35.0
github.com/envoyproxy/go-control-plane/envoy v1.36.0
github.com/go-errors/errors v1.5.1
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.4
@@ -38,10 +38,10 @@ require (
github.com/tidwall/gjson v1.17.0
go.uber.org/atomic v1.11.0
go.uber.org/zap v1.27.0
golang.org/x/net v0.44.0
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
golang.org/x/net v0.47.0
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda
google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.11
istio.io/api v1.27.1-0.20250820125923-f5a5d3a605a9
istio.io/client-go v1.27.1-0.20250820130622-12f6d11feb40
istio.io/istio v0.0.0
@@ -65,7 +65,7 @@ require (
cloud.google.com/go v0.120.0 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.8.4 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/logging v1.13.0 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
dario.cat/mergo v1.0.2 // indirect
@@ -103,11 +103,10 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -117,23 +116,23 @@ require (
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/envoyproxy/go-control-plane v0.13.4 // indirect
github.com/envoyproxy/go-control-plane v0.14.0 // indirect
github.com/envoyproxy/go-control-plane/contrib v0.0.0-20251016030003-90eca0228178 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
@@ -220,7 +219,7 @@ require (
github.com/yl2chen/cidranger v1.0.2 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
@@ -231,24 +230,24 @@ require (
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/tools v0.38.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.250.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/gcfg.v1 v1.2.3 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

1897
go.sum
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.9
appVersion: 2.2.0
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.9
version: 2.2.0

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 2.1.9
version: 2.2.0
- name: higress-console
repository: https://higress.io/helm-charts/
version: 2.1.9
digest: sha256:d696af6726b40219cc16e7cf8de7400101479dfbd8deb3101d7ee736415b9875
generated: "2025-11-13T16:33:49.721553+08:00"
version: 2.2.0
digest: sha256:2cb148fa6d52856344e1905d3fea018466c2feb52013e08997c2d5c7d50f2e5d
generated: "2026-02-11T17:45:59.187965929+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.9
appVersion: 2.2.0
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.9
version: 2.2.0
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 2.1.9
version: 2.2.0
type: application
version: 2.1.9
version: 2.2.0

View File

@@ -64,16 +64,16 @@ require (
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/envoyproxy/go-control-plane/contrib v0.0.0-20251016030003-90eca0228178 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
@@ -111,10 +111,9 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.3 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/continuity v0.4.4 // indirect
@@ -132,7 +131,7 @@ require (
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -152,7 +151,7 @@ require (
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.0 // indirect
@@ -162,7 +161,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
@@ -231,7 +229,6 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/prometheus/prometheus v0.307.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rubenv/sql-migrate v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -268,26 +265,26 @@ require (
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
istio.io/api v1.27.1-0.20250820125923-f5a5d3a605a9 // indirect

View File

File diff suppressed because it is too large Load Diff

View File

@@ -224,6 +224,17 @@ Anthropic Claude 所对应的 `type` 为 `claude`。它特有的配置字段如
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| --------------- | -------- | -------- | ------ | ----------------------------------------- |
| `claudeVersion` | string | 可选 | - | Claude 服务的 API 版本,默认为 2023-06-01 |
| `claudeCodeMode` | boolean | 可选 | false | 启用 Claude Code 模式,用于支持 Claude Code OAuth 令牌认证。启用后将伪装成 Claude Code 客户端发起请求 |
**Claude Code 模式说明**
启用 `claudeCodeMode: true` 时,插件将:
- 使用 Bearer Token 认证替代 x-api-key适配 Claude Code OAuth 令牌)
- 设置 Claude Code 特定的请求头user-agent、x-app、anthropic-beta
- 为请求 URL 添加 `?beta=true` 查询参数
- 自动注入 Claude Code 的系统提示词(如未提供)
这允许在 Higress 中直接使用 Claude Code 的 OAuth Token 进行身份验证。
#### Ollama
@@ -1211,6 +1222,44 @@ URL: `http://your-domain/v1/messages`
}
```
### 使用 Claude Code 模式
Claude Code 是 Anthropic 提供的官方 CLI 工具。通过启用 `claudeCodeMode`,可以使用 Claude Code 的 OAuth Token 进行身份验证:
**配置信息**
```yaml
provider:
type: claude
apiTokens:
- 'sk-ant-oat01-xxxxx' # Claude Code OAuth Token
claudeCodeMode: true # 启用 Claude Code 模式
```
启用此模式后,插件将自动:
- 使用 Bearer Token 认证(而非 x-api-key
- 设置 Claude Code 特定的请求头和查询参数
- 注入 Claude Code 的系统提示词(如未提供)
**请求示例**
```json
{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 8192,
"messages": [
{
"role": "user",
"content": "List files in current directory"
}
]
}
```
插件将自动转换为适合 Claude Code 的请求格式,包括:
- 添加系统提示词:`"You are Claude Code, Anthropic's official CLI for Claude."`
- 设置适当的认证和请求头
### 使用智能协议转换
当目标供应商不原生支持 Claude 协议时,插件会自动进行协议转换:

View File

@@ -185,11 +185,22 @@ For MiniMax, the corresponding `type` is `minimax`. Its unique configuration fie
#### Anthropic Claude
For Anthropic Claude, the corresponding `type` is `claude`. Its unique configuration field is:
For Anthropic Claude, the corresponding `type` is `claude`. Its unique configuration fields are:
| Name | Data Type | Filling Requirements | Default Value | Description |
|------------|-------------|----------------------|---------------|---------------------------------------------------------------------------------------------------------------|
| `claudeVersion` | string | Optional | - | The version of the Claude service's API, default is 2023-06-01. |
| `claudeCodeMode` | boolean | Optional | false | Enable Claude Code mode for OAuth token authentication. When enabled, requests will be formatted as Claude Code client requests. |
**Claude Code Mode**
When `claudeCodeMode: true` is enabled, the plugin will:
- Use Bearer Token authentication instead of x-api-key (compatible with Claude Code OAuth tokens)
- Set Claude Code-specific request headers (user-agent, x-app, anthropic-beta)
- Add `?beta=true` query parameter to request URLs
- Automatically inject Claude Code system prompt if not provided
This enables direct use of Claude Code OAuth tokens for authentication in Higress.
#### Ollama
@@ -1148,6 +1159,44 @@ Both protocol formats will return responses in their respective formats:
}
```
### Using Claude Code Mode
Claude Code is Anthropic's official CLI tool. By enabling `claudeCodeMode`, you can authenticate using Claude Code OAuth tokens:
**Configuration Information**
```yaml
provider:
type: claude
apiTokens:
- "sk-ant-oat01-xxxxx" # Claude Code OAuth Token
claudeCodeMode: true # Enable Claude Code mode
```
Once this mode is enabled, the plugin will automatically:
- Use Bearer Token authentication (instead of x-api-key)
- Set Claude Code-specific request headers and query parameters
- Inject Claude Code system prompt if not provided
**Request Example**
```json
{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 8192,
"messages": [
{
"role": "user",
"content": "List files in current directory"
}
]
}
```
The plugin will automatically transform the request into Claude Code format, including:
- Adding system prompt: `"You are Claude Code, Anthropic's official CLI for Claude."`
- Setting appropriate authentication and request headers
### Using Intelligent Protocol Conversion
When the target provider doesn't natively support Claude protocol, the plugin automatically performs protocol conversion:

View File

@@ -149,4 +149,11 @@ func TestBedrock(t *testing.T) {
test.RunBedrockOnHttpRequestBodyTests(t)
test.RunBedrockOnHttpResponseHeadersTests(t)
test.RunBedrockOnHttpResponseBodyTests(t)
test.RunBedrockToolCallTests(t)
}
func TestClaude(t *testing.T) {
test.RunClaudeParseConfigTests(t)
test.RunClaudeOnHttpRequestHeadersTests(t)
test.RunClaudeOnHttpRequestBodyTests(t)
}

View File

@@ -206,7 +206,16 @@ func (m *azureProvider) transformRequestPath(ctx wrapper.HttpContext, apiName Ap
path = strings.ReplaceAll(path, pathAzureModelPlaceholder, model)
log.Debugf("azureProvider: model replaced path: %s", path)
}
path = path + "?" + m.serviceUrl.RawQuery
if !strings.Contains(path, "?") {
// No query string yet
path = path + "?" + m.serviceUrl.RawQuery
} else if strings.HasSuffix(path, "?") {
// Ends with "?" and has no query parameter
path = path + m.serviceUrl.RawQuery
} else {
// Has other query parameters
path = path + "&" + m.serviceUrl.RawQuery
}
log.Debugf("azureProvider: final path: %s", path)
return path

View File

@@ -769,7 +769,15 @@ func (b *bedrockProvider) buildBedrockTextGenerationRequest(origRequest *chatCom
case roleSystem:
systemMessages = append(systemMessages, systemContentBlock{Text: msg.StringContent()})
case roleTool:
messages = append(messages, chatToolMessage2BedrockMessage(msg))
toolResultContent := chatToolMessage2BedrockToolResultContent(msg)
if len(messages) > 0 && messages[len(messages)-1].Role == roleUser && messages[len(messages)-1].Content[0].ToolResult != nil {
messages[len(messages)-1].Content = append(messages[len(messages)-1].Content, toolResultContent)
} else {
messages = append(messages, bedrockMessage{
Role: roleUser,
Content: []bedrockMessageContent{toolResultContent},
})
}
default:
messages = append(messages, chatMessage2BedrockMessage(msg))
}
@@ -1060,7 +1068,7 @@ type tokenUsage struct {
TotalTokens int `json:"totalTokens"`
}
func chatToolMessage2BedrockMessage(chatMessage chatMessage) bedrockMessage {
func chatToolMessage2BedrockToolResultContent(chatMessage chatMessage) bedrockMessageContent {
toolResultContent := &toolResultBlock{}
toolResultContent.ToolUseId = chatMessage.ToolCallId
if text, ok := chatMessage.Content.(string); ok {
@@ -1083,29 +1091,29 @@ func chatToolMessage2BedrockMessage(chatMessage chatMessage) bedrockMessage {
} else {
log.Warnf("the content type is not supported, current content is %v", chatMessage.Content)
}
return bedrockMessage{
Role: roleUser,
Content: []bedrockMessageContent{
{
ToolResult: toolResultContent,
},
},
return bedrockMessageContent{
ToolResult: toolResultContent,
}
}
func chatMessage2BedrockMessage(chatMessage chatMessage) bedrockMessage {
var result bedrockMessage
if len(chatMessage.ToolCalls) > 0 {
contents := make([]bedrockMessageContent, 0, len(chatMessage.ToolCalls))
for _, toolCall := range chatMessage.ToolCalls {
params := map[string]interface{}{}
json.Unmarshal([]byte(toolCall.Function.Arguments), &params)
contents = append(contents, bedrockMessageContent{
ToolUse: &toolUseBlock{
Input: params,
Name: toolCall.Function.Name,
ToolUseId: toolCall.Id,
},
})
}
result = bedrockMessage{
Role: chatMessage.Role,
Content: []bedrockMessageContent{{}},
}
params := map[string]interface{}{}
json.Unmarshal([]byte(chatMessage.ToolCalls[0].Function.Arguments), &params)
result.Content[0].ToolUse = &toolUseBlock{
Input: params,
Name: chatMessage.ToolCalls[0].Function.Name,
ToolUseId: chatMessage.ToolCalls[0].Id,
Content: contents,
}
} else if chatMessage.IsStringContent() {
result = bedrockMessage{

View File

@@ -19,6 +19,11 @@ const (
claudeDomain = "api.anthropic.com"
claudeDefaultVersion = "2023-06-01"
claudeDefaultMaxTokens = 4096
// Claude Code mode constants
claudeCodeUserAgent = "claude-cli/2.1.2 (external, cli)"
claudeCodeBetaFeatures = "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219"
claudeCodeSystemPrompt = "You are Claude Code, Anthropic's official CLI for Claude."
)
type claudeProviderInitializer struct{}
@@ -319,13 +324,36 @@ func (c *claudeProvider) TransformRequestHeaders(ctx wrapper.HttpContext, apiNam
util.OverwriteRequestPathHeaderByCapability(headers, string(apiName), c.config.capabilities)
util.OverwriteRequestHostHeader(headers, claudeDomain)
headers.Set("x-api-key", c.config.GetApiTokenInUse(ctx))
if c.config.apiVersion == "" {
c.config.apiVersion = claudeDefaultVersion
}
headers.Set("anthropic-version", c.config.apiVersion)
// Check if Claude Code mode is enabled
if c.config.claudeCodeMode {
// Claude Code mode: use OAuth token with Bearer authorization
token := c.config.GetApiTokenInUse(ctx)
headers.Set("authorization", "Bearer "+token)
headers.Del("x-api-key")
// Set Claude Code specific headers
headers.Set("user-agent", claudeCodeUserAgent)
headers.Set("x-app", "cli")
headers.Set("anthropic-beta", claudeCodeBetaFeatures)
// Add ?beta=true query parameter to the path
currentPath := headers.Get(":path")
if currentPath != "" && !strings.Contains(currentPath, "beta=true") {
if strings.Contains(currentPath, "?") {
headers.Set(":path", currentPath+"&beta=true")
} else {
headers.Set(":path", currentPath+"?beta=true")
}
}
} else {
// Standard mode: use x-api-key
headers.Set("x-api-key", c.config.GetApiTokenInUse(ctx))
}
}
func (c *claudeProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte) (types.Action, error) {
@@ -413,11 +441,30 @@ func (c *claudeProvider) buildClaudeTextGenRequest(origRequest *chatCompletionRe
claudeRequest.MaxTokens = claudeDefaultMaxTokens
}
// Track if system message exists in original request
hasSystemMessage := false
for _, message := range origRequest.Messages {
if message.Role == roleSystem {
claudeRequest.System = &claudeSystemPrompt{
StringValue: message.StringContent(),
IsArray: false,
hasSystemMessage = true
// In Claude Code mode, use array format with cache_control
if c.config.claudeCodeMode {
claudeRequest.System = &claudeSystemPrompt{
ArrayValue: []claudeChatMessageContent{
{
Type: contentTypeText,
Text: message.StringContent(),
CacheControl: map[string]interface{}{
"type": "ephemeral",
},
},
},
IsArray: true,
}
} else {
claudeRequest.System = &claudeSystemPrompt{
StringValue: message.StringContent(),
IsArray: false,
}
}
continue
}
@@ -478,6 +525,22 @@ func (c *claudeProvider) buildClaudeTextGenRequest(origRequest *chatCompletionRe
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
}
// In Claude Code mode, add default system prompt if not present
if c.config.claudeCodeMode && !hasSystemMessage {
claudeRequest.System = &claudeSystemPrompt{
ArrayValue: []claudeChatMessageContent{
{
Type: contentTypeText,
Text: claudeCodeSystemPrompt,
CacheControl: map[string]interface{}{
"type": "ephemeral",
},
},
},
IsArray: true,
}
}
for _, tool := range origRequest.Tools {
claudeTool := claudeTool{
Name: tool.Function.Name,

View File

@@ -0,0 +1,317 @@
package provider
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClaudeProviderInitializer_ValidateConfig(t *testing.T) {
initializer := &claudeProviderInitializer{}
t.Run("valid_config_with_api_tokens", func(t *testing.T) {
config := &ProviderConfig{
apiTokens: []string{"test-token"},
}
err := initializer.ValidateConfig(config)
assert.NoError(t, err)
})
t.Run("invalid_config_without_api_tokens", func(t *testing.T) {
config := &ProviderConfig{
apiTokens: nil,
}
err := initializer.ValidateConfig(config)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no apiToken found in provider config")
})
t.Run("invalid_config_with_empty_api_tokens", func(t *testing.T) {
config := &ProviderConfig{
apiTokens: []string{},
}
err := initializer.ValidateConfig(config)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no apiToken found in provider config")
})
}
func TestClaudeProviderInitializer_DefaultCapabilities(t *testing.T) {
initializer := &claudeProviderInitializer{}
capabilities := initializer.DefaultCapabilities()
expected := map[string]string{
string(ApiNameChatCompletion): PathAnthropicMessages,
string(ApiNameCompletion): PathAnthropicComplete,
string(ApiNameAnthropicMessages): PathAnthropicMessages,
string(ApiNameEmbeddings): PathOpenAIEmbeddings,
string(ApiNameModels): PathOpenAIModels,
}
assert.Equal(t, expected, capabilities)
}
func TestClaudeProviderInitializer_CreateProvider(t *testing.T) {
initializer := &claudeProviderInitializer{}
config := ProviderConfig{
apiTokens: []string{"test-token"},
}
provider, err := initializer.CreateProvider(config)
require.NoError(t, err)
require.NotNil(t, provider)
assert.Equal(t, providerTypeClaude, provider.GetProviderType())
claudeProvider, ok := provider.(*claudeProvider)
require.True(t, ok)
assert.NotNil(t, claudeProvider.config.apiTokens)
assert.Equal(t, []string{"test-token"}, claudeProvider.config.apiTokens)
}
func TestClaudeProvider_GetProviderType(t *testing.T) {
provider := &claudeProvider{
config: ProviderConfig{
apiTokens: []string{"test-token"},
},
contextCache: createContextCache(&ProviderConfig{}),
}
assert.Equal(t, providerTypeClaude, provider.GetProviderType())
}
// Note: TransformRequestHeaders tests are skipped because they require WASM runtime
// The header transformation logic is tested via integration tests instead.
// Here we test the helper functions and logic that can be unit tested.
func TestClaudeCodeMode_HeaderLogic(t *testing.T) {
// Test the logic for adding beta=true query parameter
t.Run("adds_beta_query_param_to_path_without_query", func(t *testing.T) {
currentPath := "/v1/messages"
var newPath string
if currentPath != "" && !strings.Contains(currentPath, "beta=true") {
if strings.Contains(currentPath, "?") {
newPath = currentPath + "&beta=true"
} else {
newPath = currentPath + "?beta=true"
}
} else {
newPath = currentPath
}
assert.Equal(t, "/v1/messages?beta=true", newPath)
})
t.Run("adds_beta_query_param_to_path_with_existing_query", func(t *testing.T) {
currentPath := "/v1/messages?foo=bar"
var newPath string
if currentPath != "" && !strings.Contains(currentPath, "beta=true") {
if strings.Contains(currentPath, "?") {
newPath = currentPath + "&beta=true"
} else {
newPath = currentPath + "?beta=true"
}
} else {
newPath = currentPath
}
assert.Equal(t, "/v1/messages?foo=bar&beta=true", newPath)
})
t.Run("does_not_duplicate_beta_param", func(t *testing.T) {
currentPath := "/v1/messages?beta=true"
var newPath string
if currentPath != "" && !strings.Contains(currentPath, "beta=true") {
if strings.Contains(currentPath, "?") {
newPath = currentPath + "&beta=true"
} else {
newPath = currentPath + "?beta=true"
}
} else {
newPath = currentPath
}
assert.Equal(t, "/v1/messages?beta=true", newPath)
})
t.Run("bearer_token_format", func(t *testing.T) {
token := "sk-ant-oat01-oauth-token"
bearerAuth := "Bearer " + token
assert.Equal(t, "Bearer sk-ant-oat01-oauth-token", bearerAuth)
})
}
func TestClaudeProvider_BuildClaudeTextGenRequest_StandardMode(t *testing.T) {
provider := &claudeProvider{
config: ProviderConfig{
claudeCodeMode: false,
},
}
t.Run("builds_request_without_injecting_defaults", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Stream: true,
Messages: []chatMessage{
{Role: roleUser, Content: "Hello"},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Should not have system prompt injected
assert.Nil(t, claudeReq.System)
// Should not have tools injected
assert.Empty(t, claudeReq.Tools)
})
t.Run("preserves_existing_system_message", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Messages: []chatMessage{
{Role: roleSystem, Content: "You are a helpful assistant."},
{Role: roleUser, Content: "Hello"},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
assert.NotNil(t, claudeReq.System)
assert.False(t, claudeReq.System.IsArray)
assert.Equal(t, "You are a helpful assistant.", claudeReq.System.StringValue)
})
}
func TestClaudeProvider_BuildClaudeTextGenRequest_ClaudeCodeMode(t *testing.T) {
provider := &claudeProvider{
config: ProviderConfig{
claudeCodeMode: true,
},
}
t.Run("injects_default_system_prompt_when_missing", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Stream: true,
Messages: []chatMessage{
{Role: roleUser, Content: "List files"},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Should have default Claude Code system prompt
require.NotNil(t, claudeReq.System)
assert.True(t, claudeReq.System.IsArray)
require.Len(t, claudeReq.System.ArrayValue, 1)
assert.Equal(t, claudeCodeSystemPrompt, claudeReq.System.ArrayValue[0].Text)
assert.Equal(t, contentTypeText, claudeReq.System.ArrayValue[0].Type)
// Should have cache_control
assert.NotNil(t, claudeReq.System.ArrayValue[0].CacheControl)
assert.Equal(t, "ephemeral", claudeReq.System.ArrayValue[0].CacheControl["type"])
})
t.Run("preserves_existing_system_message_with_cache_control", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Messages: []chatMessage{
{Role: roleSystem, Content: "Custom system prompt"},
{Role: roleUser, Content: "Hello"},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Should preserve custom system prompt but with array format and cache_control
require.NotNil(t, claudeReq.System)
assert.True(t, claudeReq.System.IsArray)
require.Len(t, claudeReq.System.ArrayValue, 1)
assert.Equal(t, "Custom system prompt", claudeReq.System.ArrayValue[0].Text)
// Should have cache_control
assert.NotNil(t, claudeReq.System.ArrayValue[0].CacheControl)
assert.Equal(t, "ephemeral", claudeReq.System.ArrayValue[0].CacheControl["type"])
})
t.Run("full_request_transformation", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Stream: true,
Temperature: 1.0,
Messages: []chatMessage{
{Role: roleUser, Content: "List files in current directory"},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Verify complete request structure
assert.Equal(t, "claude-sonnet-4-5-20250929", claudeReq.Model)
assert.Equal(t, 8192, claudeReq.MaxTokens)
assert.True(t, claudeReq.Stream)
assert.Equal(t, 1.0, claudeReq.Temperature)
// Verify system prompt
require.NotNil(t, claudeReq.System)
assert.True(t, claudeReq.System.IsArray)
assert.Equal(t, claudeCodeSystemPrompt, claudeReq.System.ArrayValue[0].Text)
// Verify messages
require.Len(t, claudeReq.Messages, 1)
assert.Equal(t, roleUser, claudeReq.Messages[0].Role)
// Verify no tools are injected by default
assert.Empty(t, claudeReq.Tools)
// Verify the request can be serialized to JSON
jsonBytes, err := json.Marshal(claudeReq)
require.NoError(t, err)
assert.NotEmpty(t, jsonBytes)
})
}
// Note: TransformRequestBody tests are skipped because they require WASM runtime
// The request body transformation is tested indirectly through buildClaudeTextGenRequest tests
// Test constants
func TestClaudeConstants(t *testing.T) {
assert.Equal(t, "api.anthropic.com", claudeDomain)
assert.Equal(t, "2023-06-01", claudeDefaultVersion)
assert.Equal(t, 4096, claudeDefaultMaxTokens)
assert.Equal(t, "claude", providerTypeClaude)
// Claude Code mode constants
assert.Equal(t, "claude-cli/2.1.2 (external, cli)", claudeCodeUserAgent)
assert.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219", claudeCodeBetaFeatures)
assert.Equal(t, "You are Claude Code, Anthropic's official CLI for Claude.", claudeCodeSystemPrompt)
}
func TestClaudeProvider_GetApiName(t *testing.T) {
provider := &claudeProvider{}
t.Run("messages_path", func(t *testing.T) {
assert.Equal(t, ApiNameChatCompletion, provider.GetApiName("/v1/messages"))
assert.Equal(t, ApiNameChatCompletion, provider.GetApiName("/api/v1/messages"))
})
t.Run("complete_path", func(t *testing.T) {
assert.Equal(t, ApiNameCompletion, provider.GetApiName("/v1/complete"))
})
t.Run("models_path", func(t *testing.T) {
assert.Equal(t, ApiNameModels, provider.GetApiName("/v1/models"))
})
t.Run("embeddings_path", func(t *testing.T) {
assert.Equal(t, ApiNameEmbeddings, provider.GetApiName("/v1/embeddings"))
})
t.Run("unknown_path", func(t *testing.T) {
assert.Equal(t, ApiName(""), provider.GetApiName("/unknown"))
})
}

View File

@@ -442,6 +442,9 @@ type ProviderConfig struct {
// @Title zh-CN 豆包服务域名
// @Description zh-CN 仅适用于豆包服务,默认转发域名为 ark.cn-beijing.volces.com
doubaoDomain string `required:"false" yaml:"doubaoDomain" json:"doubaoDomain"`
// @Title zh-CN Claude Code 模式
// @Description zh-CN 仅适用于Claude服务。启用后将伪装成Claude Code客户端发起请求支持使用Claude Code的OAuth Token进行认证。
claudeCodeMode bool `required:"false" yaml:"claudeCodeMode" json:"claudeCodeMode"`
}
func (c *ProviderConfig) GetId() string {
@@ -646,6 +649,7 @@ func (c *ProviderConfig) FromJson(json gjson.Result) {
c.vllmServerHost = json.Get("vllmServerHost").String()
c.vllmCustomUrl = json.Get("vllmCustomUrl").String()
c.doubaoDomain = json.Get("doubaoDomain").String()
c.claudeCodeMode = json.Get("claudeCodeMode").Bool()
c.contextCleanupCommands = make([]string, 0)
for _, cmd := range json.Get("contextCleanupCommands").Array() {
if cmd.String() != "" {

View File

@@ -343,7 +343,7 @@ func RunAzureOnHttpRequestHeadersTests(t *testing.T) {
// 验证Path是否被正确处理
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist")
require.Contains(t, pathValue, "/openai/deployments/test-deployment/chat/completions", "Path should contain Azure deployment path")
require.Equal(t, "/openai/deployments/test-deployment/chat/completions?api-version=2024-02-15-preview", pathValue, "Path should equal Azure deployment path")
// 验证Content-Length是否被删除
_, hasContentLength := test.GetHeaderValue(requestHeaders, "Content-Length")
@@ -443,8 +443,7 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) {
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist")
require.Contains(t, pathValue, "/openai/deployments/test-deployment/chat/completions", "Path should contain Azure deployment path")
require.Contains(t, pathValue, "api-version=2024-02-15-preview", "Path should contain API version")
require.Equal(t, pathValue, "/openai/deployments/test-deployment/chat/completions?api-version=2024-02-15-preview", "Path should contain Azure deployment path")
})
// 测试Azure OpenAI请求体处理不同模型
@@ -577,7 +576,7 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) {
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist")
require.Contains(t, pathValue, "/openai/deployments/deployment-only/chat/completions", "Path should use default deployment")
require.Equal(t, pathValue, "/openai/deployments/deployment-only/chat/completions?api-version=2024-02-15-preview", "Path should use default deployment")
})
// 测试Azure OpenAI请求体处理仅域名配置
@@ -613,7 +612,42 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) {
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist")
require.Contains(t, pathValue, "/openai/deployments/gpt-3.5-turbo/chat/completions", "Path should use model from request body")
require.Equal(t, pathValue, "/openai/deployments/gpt-3.5-turbo/chat/completions?api-version=2024-02-15-preview", "Path should use model from request body")
})
// 测试Azure OpenAI模型无关请求处理仅域名配置
t.Run("azure domain only model independent", func(t *testing.T) {
host, status := test.NewTestHost(azureDomainOnlyConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/files?limit=10&purpose=assistants"},
{":method", "GET"},
})
require.Equal(t, types.HeaderStopIteration, action)
// 验证请求路径是否使用模型占位符
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist")
require.Equal(t, pathValue, "/openai/files?limit=10&purpose=assistants&api-version=2024-02-15-preview", "Path should have api-version appended")
// 设置请求头
action = host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/files?"},
{":method", "GET"},
})
require.Equal(t, types.HeaderStopIteration, action)
// 验证请求路径是否使用模型占位符
requestHeaders = host.GetRequestHeaders()
pathValue, hasPath = test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist")
require.Equal(t, pathValue, "/openai/files?api-version=2024-02-15-preview", "Path should have api-version appended")
})
})
}
@@ -827,10 +861,8 @@ func RunAzureBasePathHandlingTests(t *testing.T) {
require.NotContains(t, pathValue, "/azure-gpt4",
"After body stage: basePath should be removed from path")
// 在 openai 协议下,路径会被转换为 Azure 的路径格式
require.Contains(t, pathValue, "/openai/deployments/gpt-4/chat/completions",
require.Equal(t, pathValue, "/openai/deployments/gpt-4/chat/completions?api-version=2024-02-15-preview",
"Path should be transformed to Azure format")
require.Contains(t, pathValue, "api-version=2024-02-15-preview",
"Path should contain API version")
})
// 测试 basePath prepend 在 original 协议下能正常工作

View File

@@ -442,6 +442,186 @@ func RunBedrockOnHttpResponseHeadersTests(t *testing.T) {
})
}
func RunBedrockToolCallTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// Test single tool call conversion (regression test)
t.Run("bedrock single tool call conversion", func(t *testing.T) {
host, status := test.NewTestHost(bedrockApiTokenConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestBody := `{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "What is the weather in Beijing?"},
{"role": "assistant", "content": "Let me check the weather for you.", "tool_calls": [{"id": "call_001", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\":\"Beijing\"}"}}]},
{"role": "tool", "content": "Sunny, 25°C", "tool_call_id": "call_001"}
],
"tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get weather info", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}}]
}`
action = host.CallOnHttpRequestBody([]byte(requestBody))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetRequestBody()
require.NotNil(t, processedBody)
var bodyMap map[string]interface{}
err := json.Unmarshal(processedBody, &bodyMap)
require.NoError(t, err)
messages := bodyMap["messages"].([]interface{})
// messages[0] = user, messages[1] = assistant with toolUse, messages[2] = user with toolResult
require.Len(t, messages, 3, "Should have 3 messages: user, assistant, user(toolResult)")
// Verify assistant message has exactly 1 toolUse
assistantMsg := messages[1].(map[string]interface{})
require.Equal(t, "assistant", assistantMsg["role"])
assistantContent := assistantMsg["content"].([]interface{})
require.Len(t, assistantContent, 1, "Assistant should have 1 content block")
toolUseBlock := assistantContent[0].(map[string]interface{})
require.Contains(t, toolUseBlock, "toolUse", "Content block should contain toolUse")
// Verify tool result message
toolResultMsg := messages[2].(map[string]interface{})
require.Equal(t, "user", toolResultMsg["role"])
toolResultContent := toolResultMsg["content"].([]interface{})
require.Len(t, toolResultContent, 1, "Tool result message should have 1 content block")
require.Contains(t, toolResultContent[0].(map[string]interface{}), "toolResult", "Content block should contain toolResult")
})
// Test multiple parallel tool calls conversion
t.Run("bedrock multiple parallel tool calls conversion", func(t *testing.T) {
host, status := test.NewTestHost(bedrockApiTokenConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestBody := `{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "What is the weather in Beijing and Shanghai?"},
{"role": "assistant", "content": "Let me check both cities.", "tool_calls": [{"id": "call_001", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\":\"Beijing\"}"}}, {"id": "call_002", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\":\"Shanghai\"}"}}]},
{"role": "tool", "content": "Sunny, 25°C", "tool_call_id": "call_001"},
{"role": "tool", "content": "Cloudy, 22°C", "tool_call_id": "call_002"}
],
"tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get weather info", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}}]
}`
action = host.CallOnHttpRequestBody([]byte(requestBody))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetRequestBody()
require.NotNil(t, processedBody)
var bodyMap map[string]interface{}
err := json.Unmarshal(processedBody, &bodyMap)
require.NoError(t, err)
messages := bodyMap["messages"].([]interface{})
// messages[0] = user, messages[1] = assistant with 2 toolUse, messages[2] = user with 2 toolResult
require.Len(t, messages, 3, "Should have 3 messages: user, assistant, user(toolResults merged)")
// Verify assistant message has 2 toolUse blocks
assistantMsg := messages[1].(map[string]interface{})
require.Equal(t, "assistant", assistantMsg["role"])
assistantContent := assistantMsg["content"].([]interface{})
require.Len(t, assistantContent, 2, "Assistant should have 2 content blocks for parallel tool calls")
firstToolUse := assistantContent[0].(map[string]interface{})["toolUse"].(map[string]interface{})
require.Equal(t, "get_weather", firstToolUse["name"])
require.Equal(t, "call_001", firstToolUse["toolUseId"])
secondToolUse := assistantContent[1].(map[string]interface{})["toolUse"].(map[string]interface{})
require.Equal(t, "get_weather", secondToolUse["name"])
require.Equal(t, "call_002", secondToolUse["toolUseId"])
// Verify tool results are merged into a single user message
toolResultMsg := messages[2].(map[string]interface{})
require.Equal(t, "user", toolResultMsg["role"])
toolResultContent := toolResultMsg["content"].([]interface{})
require.Len(t, toolResultContent, 2, "Tool results should be merged into 2 content blocks in one user message")
firstResult := toolResultContent[0].(map[string]interface{})["toolResult"].(map[string]interface{})
require.Equal(t, "call_001", firstResult["toolUseId"])
secondResult := toolResultContent[1].(map[string]interface{})["toolResult"].(map[string]interface{})
require.Equal(t, "call_002", secondResult["toolUseId"])
})
// Test tool call with text content mixed
t.Run("bedrock tool call with text content mixed", func(t *testing.T) {
host, status := test.NewTestHost(bedrockApiTokenConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestBody := `{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "What is the weather in Beijing?"},
{"role": "assistant", "content": "Let me check.", "tool_calls": [{"id": "call_001", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\":\"Beijing\"}"}}]},
{"role": "tool", "content": "Sunny, 25°C", "tool_call_id": "call_001"},
{"role": "assistant", "content": "The weather in Beijing is sunny with 25°C."},
{"role": "user", "content": "Thanks!"}
],
"tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get weather info", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}}]
}`
action = host.CallOnHttpRequestBody([]byte(requestBody))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetRequestBody()
require.NotNil(t, processedBody)
var bodyMap map[string]interface{}
err := json.Unmarshal(processedBody, &bodyMap)
require.NoError(t, err)
messages := bodyMap["messages"].([]interface{})
// messages[0] = user, messages[1] = assistant(toolUse), messages[2] = user(toolResult),
// messages[3] = assistant(text), messages[4] = user(text)
require.Len(t, messages, 5, "Should have 5 messages in mixed tool call and text scenario")
// Verify message roles alternate correctly
require.Equal(t, "user", messages[0].(map[string]interface{})["role"])
require.Equal(t, "assistant", messages[1].(map[string]interface{})["role"])
require.Equal(t, "user", messages[2].(map[string]interface{})["role"])
require.Equal(t, "assistant", messages[3].(map[string]interface{})["role"])
require.Equal(t, "user", messages[4].(map[string]interface{})["role"])
// Verify assistant text message (messages[3]) has text content
assistantTextMsg := messages[3].(map[string]interface{})
assistantTextContent := assistantTextMsg["content"].([]interface{})
require.Len(t, assistantTextContent, 1)
require.Contains(t, assistantTextContent[0].(map[string]interface{}), "text", "Text assistant message should have text content")
require.Contains(t, assistantTextContent[0].(map[string]interface{})["text"], "sunny", "Text content should contain weather info")
})
})
}
func RunBedrockOnHttpResponseBodyTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// Test Bedrock response body processing

View File

@@ -0,0 +1,317 @@
package test
import (
"encoding/json"
"testing"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/higress-group/wasm-go/pkg/test"
"github.com/stretchr/testify/require"
)
// Claude standard mode config
var claudeStandardConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "claude",
"apiTokens": []string{"sk-ant-api-key-123"},
},
})
return data
}()
// Claude Code mode config
var claudeCodeModeConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "claude",
"apiTokens": []string{"sk-ant-oat01-oauth-token-456"},
"claudeCodeMode": true,
},
})
return data
}()
// Claude Code mode config with custom apiVersion
var claudeCodeModeWithVersionConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "claude",
"apiTokens": []string{"sk-ant-oat01-oauth-token-789"},
"claudeCodeMode": true,
"claudeVersion": "2024-01-01",
},
})
return data
}()
// Claude config without token (should fail validation)
var claudeNoTokenConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "claude",
},
})
return data
}()
func RunClaudeParseConfigTests(t *testing.T) {
test.RunGoTest(t, func(t *testing.T) {
t.Run("claude standard config", func(t *testing.T) {
host, status := test.NewTestHost(claudeStandardConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
t.Run("claude code mode config", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
t.Run("claude config without token fails", func(t *testing.T) {
host, status := test.NewTestHost(claudeNoTokenConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusFailed, status)
})
})
}
func RunClaudeOnHttpRequestHeadersTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
t.Run("claude standard mode uses x-api-key", func(t *testing.T) {
host, status := test.NewTestHost(claudeStandardConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestHeaders := host.GetRequestHeaders()
require.True(t, test.HasHeaderWithValue(requestHeaders, "x-api-key", "sk-ant-api-key-123"))
require.True(t, test.HasHeaderWithValue(requestHeaders, "anthropic-version", "2023-06-01"))
// Should NOT have Claude Code specific headers
_, hasAuth := test.GetHeaderValue(requestHeaders, "authorization")
require.False(t, hasAuth, "standard mode should not have authorization header")
_, hasXApp := test.GetHeaderValue(requestHeaders, "x-app")
require.False(t, hasXApp, "standard mode should not have x-app header")
})
t.Run("claude code mode uses bearer authorization", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestHeaders := host.GetRequestHeaders()
// Claude Code mode should use Bearer authorization
require.True(t, test.HasHeaderWithValue(requestHeaders, "authorization", "Bearer sk-ant-oat01-oauth-token-456"))
// Should NOT have x-api-key in Claude Code mode
_, hasXApiKey := test.GetHeaderValue(requestHeaders, "x-api-key")
require.False(t, hasXApiKey, "claude code mode should not have x-api-key header")
// Should have Claude Code specific headers
require.True(t, test.HasHeaderWithValue(requestHeaders, "x-app", "cli"))
require.True(t, test.HasHeaderWithValue(requestHeaders, "user-agent", "claude-cli/2.1.2 (external, cli)"))
require.True(t, test.HasHeaderWithValue(requestHeaders, "anthropic-beta", "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219"))
require.True(t, test.HasHeaderWithValue(requestHeaders, "anthropic-version", "2023-06-01"))
})
t.Run("claude code mode adds beta query param", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestHeaders := host.GetRequestHeaders()
path, found := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, found)
require.Contains(t, path, "beta=true", "claude code mode should add beta=true query param")
})
t.Run("claude code mode with custom version", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeWithVersionConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestHeaders := host.GetRequestHeaders()
require.True(t, test.HasHeaderWithValue(requestHeaders, "anthropic-version", "2024-01-01"))
})
})
}
func RunClaudeOnHttpRequestBodyTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
t.Run("claude standard mode does not inject defaults", func(t *testing.T) {
host, status := test.NewTestHost(claudeStandardConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
body := `{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 8192,
"stream": true,
"messages": [
{"role": "user", "content": "Hello"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetRequestBody()
var request map[string]interface{}
err := json.Unmarshal(processedBody, &request)
require.NoError(t, err)
// Standard mode should NOT inject system prompt or tools
_, hasSystem := request["system"]
require.False(t, hasSystem, "standard mode should not inject system prompt")
tools, hasTools := request["tools"]
if hasTools {
toolsArr, ok := tools.([]interface{})
require.True(t, ok)
require.Empty(t, toolsArr, "standard mode should not inject tools")
}
})
t.Run("claude code mode injects default system prompt", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
body := `{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 8192,
"stream": true,
"messages": [
{"role": "user", "content": "List files"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetRequestBody()
var request map[string]interface{}
err := json.Unmarshal(processedBody, &request)
require.NoError(t, err)
// Claude Code mode should inject system prompt
system, hasSystem := request["system"]
require.True(t, hasSystem, "claude code mode should inject system prompt")
systemArr, ok := system.([]interface{})
require.True(t, ok, "system should be an array in claude code mode")
require.Len(t, systemArr, 1)
systemBlock, ok := systemArr[0].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "text", systemBlock["type"])
require.Equal(t, "You are Claude Code, Anthropic's official CLI for Claude.", systemBlock["text"])
// Should have cache_control
cacheControl, hasCacheControl := systemBlock["cache_control"]
require.True(t, hasCacheControl, "system prompt should have cache_control")
cacheControlMap, ok := cacheControl.(map[string]interface{})
require.True(t, ok)
require.Equal(t, "ephemeral", cacheControlMap["type"])
})
t.Run("claude code mode preserves existing system prompt", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "api.anthropic.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
body := `{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 8192,
"messages": [
{"role": "system", "content": "You are a custom assistant."},
{"role": "user", "content": "Hello"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetRequestBody()
var request map[string]interface{}
err := json.Unmarshal(processedBody, &request)
require.NoError(t, err)
// Should preserve custom system prompt (not default)
system, hasSystem := request["system"]
require.True(t, hasSystem)
systemArr, ok := system.([]interface{})
require.True(t, ok)
require.Len(t, systemArr, 1)
systemBlock, ok := systemArr[0].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "You are a custom assistant.", systemBlock["text"])
})
})
}
// Note: Response headers tests are skipped as they require complex mocking
// The response header transformation is covered by integration tests

View File

@@ -67,7 +67,7 @@ func genJWTs(keySets map[string]keySet) (jwts jwts) {
Expiry: jwt.NewNumericDate(time.Date(2034, 1, 1, 0, 0, 0, 0, time.UTC)),
NotBefore: jwt.NewNumericDate(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
},
"expried": {
"expired": {
Issuer: "higress-test",
Subject: "higress-test",
Audience: []string{"foo", "bar"},

View File

@@ -8,12 +8,12 @@
{
"alg": "RS256",
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.jqzlhBPk9mmvtTT5aCYf-_5uXXSEU5bQ32fx78XeboCnjR9K1CsI4KYUIkXEX3bk66XJQUeSes7lz3gA4Yzkd-v9oADHTgpKnIxzv_5mD0_afIwEFjcalqVbSvCmro4PessQZDnmU7AIzoo3RPSqbmq8xbPVYUH9I-OO8aUu2ATd1HozgxJH1XnRU8k9KMkVW8XhvJXLKZJmnqe3Tu6pCU_tawFlBfBC4fAhMf0yX2CGE0ABAHubcdiI6JXObQmQQ9Or2a-g2a8g_Bw697PoPOsAn0YpTrHst9GcyTpkbNTAq9X8fc5EM7hiDM1FGeMYcaQTdMnOh4HBhP0p4YEhvA",
"type": "expried"
"type": "expired"
},
{
"alg": "ES256",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.9AnXd2rZ6FirHZQAoabyL4xZNz0jr-3LmcV4-pFV3JrdtUT4386Mw5Qan125fUB-rZf_ZBlv0Bft2tWY149fyg",
"type": "expried"
"type": "expired"
},
{
"alg": "ES256",

View File

@@ -268,7 +268,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "1. Default header with expried ES256",
TestCaseName: "1. Default header with expired ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -289,7 +289,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "2. Default header with expried RS256",
TestCaseName: "2. Default header with expired RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -310,7 +310,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "3. Default params with expried ES256",
TestCaseName: "3. Default params with expired ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -330,7 +330,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "4. Default params with expried RS256",
TestCaseName: "4. Default params with expired RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -350,7 +350,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "5. Custom header with expried ES256",
TestCaseName: "5. Custom header with expired ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -371,7 +371,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "6. Custom header with expried RS256",
TestCaseName: "6. Custom header with expired RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -392,7 +392,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "7. Custom params with expried ES256",
TestCaseName: "7. Custom params with expired ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -412,7 +412,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "8. Custom params with expried RS256",
TestCaseName: "8. Custom params with expired RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -432,7 +432,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "9. Custom cookies with expried ES256",
TestCaseName: "9. Custom cookies with expired ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -453,7 +453,7 @@ var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "10. Custom cookies with expried RS256",
TestCaseName: "10. Custom cookies with expired RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -774,7 +774,7 @@ var WasmPluginsJWTAuthSingleConsumer = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "2. Default hedaer with expried ES256 by single consumer_EC",
TestCaseName: "2. Default hedaer with expired ES256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
@@ -877,7 +877,7 @@ var WasmPluginsJWTAuthSingleConsumer = suite.ConformanceTest{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "7. Default params with expried ES256 by single consumer_EC",
TestCaseName: "7. Default params with expired ES256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{