Compare commits

..

5 Commits

Author SHA1 Message Date
johnlanni
e52792ce1f docs: update README with Claude Code mode documentation
Add comprehensive documentation for Claude Code mode in both Chinese and English:

- Added claudeCodeMode configuration field description
- Explained Claude Code mode behavior and features
- Added configuration examples for both languages
- Documented OAuth token authentication support
- Explained automatic system prompt and Bash tool injection
2026-02-07 15:11:09 +08:00
johnlanni
13cdcf6181 test(ai-proxy): add integration tests for Claude Code mode
Add integration tests in test/claude.go for Claude provider:

ParseConfig tests:
- claude standard config
- claude code mode config
- claude config without token fails

HttpRequestHeaders tests:
- claude standard mode uses x-api-key
- claude code mode uses bearer authorization
- claude code mode adds beta query param
- claude code mode with custom version

HttpRequestBody tests:
- claude standard mode does not inject defaults
- claude code mode injects default system prompt
- claude code mode injects bash tool
- claude code mode preserves existing system prompt
- claude code mode does not duplicate bash tool
- claude code mode adds bash tool alongside existing tools

All tests run in both go and wasm modes.
2026-02-07 15:05:13 +08:00
johnlanni
14e7aca426 test(ai-proxy): add unit tests for Claude Code mode
Add comprehensive unit tests for Claude Code mode:
- Test claudeProviderInitializer validation and capabilities
- Test header logic for beta=true query parameter
- Test buildClaudeTextGenRequest for standard and Claude Code modes
- Test system prompt injection when missing
- Test Bash tool injection when missing
- Test deduplication of existing system prompt and tools
- Test constants and GetApiName

Note: TransformRequestHeaders and TransformRequestBody tests are skipped
as they require WASM runtime. Core logic is tested indirectly through
buildClaudeTextGenRequest tests.
2026-02-07 08:32:52 +08:00
johnlanni
3714a2bd9c feat(ai-proxy): add default system prompt and Bash tool for Claude Code mode
- Add default Claude Code system prompt if not present in request
- Add default Bash tool if not present in request
- Only inject defaults when claudeCodeMode is enabled and fields are missing
2026-02-07 01:22:47 +08:00
johnlanni
caf910cf48 feat(ai-proxy): add Claude Code mode support for Claude provider
Add claudeCodeMode configuration option to emulate Claude Code client
requests, enabling OAuth token authentication with Claude API.

Changes:
- Add claudeCodeMode config field in ProviderConfig
- Transform request headers for Claude Code mode:
  - Use Bearer token authorization instead of x-api-key
  - Set user-agent to claude-cli/2.1.2 (external, cli)
  - Set x-app: cli header
  - Set anthropic-beta with OAuth and Claude Code features
  - Add ?beta=true query parameter to path
- Add cache_control with ephemeral type to system prompts in Claude Code mode

This allows users to use Claude Code OAuth tokens with Higress AI proxy.
2026-02-07 01:12:33 +08:00
37 changed files with 491 additions and 4132 deletions

View File

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

6
.gitmodules vendored
View File

@@ -21,15 +21,15 @@
[submodule "istio/proxy"] [submodule "istio/proxy"]
path = istio/proxy path = istio/proxy
url = https://github.com/higress-group/proxy url = https://github.com/higress-group/proxy
branch = envoy-1.36 branch = istio-1.19
shallow = true shallow = true
[submodule "envoy/go-control-plane"] [submodule "envoy/go-control-plane"]
path = envoy/go-control-plane path = envoy/go-control-plane
url = https://github.com/higress-group/go-control-plane url = https://github.com/higress-group/go-control-plane
branch = envoy-1.36 branch = istio-1.27
shallow = true shallow = true
[submodule "envoy/envoy"] [submodule "envoy/envoy"]
path = envoy/envoy path = envoy/envoy
url = https://github.com/higress-group/envoy url = https://github.com/higress-group/envoy
branch = envoy-1.36 branch = envoy-1.27
shallow = true shallow = true

View File

@@ -146,7 +146,7 @@ docker-buildx-push: clean-env docker.higress-buildx
export PARENT_GIT_TAG:=$(shell cat VERSION) export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG) export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.2.1/envoy-symbol-ARCH.tar.gz export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.2.0/envoy-symbol-ARCH.tar.gz
build-envoy: prebuild build-envoy: prebuild
./tools/hack/build-envoy.sh ./tools/hack/build-envoy.sh
@@ -200,8 +200,8 @@ install: pre-install
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true' helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
HIGRESS_LATEST_IMAGE_TAG ?= latest HIGRESS_LATEST_IMAGE_TAG ?= latest
ENVOY_LATEST_IMAGE_TAG ?= ca6ff3a92e3fa592bff706894b22e0509a69757b ENVOY_LATEST_IMAGE_TAG ?= cdf0f16bf622102f89a0d0257834f43f502e4b99
ISTIO_LATEST_IMAGE_TAG ?= c482b42b9a14885bd6692c6abd01345d50a372f7 ISTIO_LATEST_IMAGE_TAG ?= a7525f292c38d7d3380f3ce7ee971ad6e3c46adf
install-dev: pre-install install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'

View File

@@ -182,8 +182,6 @@ Higress would not be possible without the valuable open-source work of projects
- Higress Console: https://github.com/higress-group/higress-console - Higress Console: https://github.com/higress-group/higress-console
- Higress Standalone: https://github.com/higress-group/higress-standalone - 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 ### Contributors

View File

@@ -208,8 +208,6 @@ WeChat公式アカウント
- Higressコンソールhttps://github.com/higress-group/higress-console - Higressコンソールhttps://github.com/higress-group/higress-console
- Higressスタンドアロン版https://github.com/higress-group/higress-standalone - 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,8 +221,6 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
- Higress 控制台https://github.com/higress-group/higress-console - Higress 控制台https://github.com/higress-group/higress-console
- Higress独立运行版https://github.com/higress-group/higress-standalone - 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
### 贡献者 ### 贡献者

View File

@@ -1 +1 @@
v2.2.0 v2.1.9

45
go.mod
View File

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

View File

@@ -123,8 +123,6 @@ template:
- name: LITE_METRICS - name: LITE_METRICS
value: "on" value: "on"
{{- end }} {{- end }}
- name: ISTIO_DELTA_XDS
value: "{{ .Values.global.enableDeltaXDS }}"
{{- if include "skywalking.enabled" . }} {{- if include "skywalking.enabled" . }}
- name: ISTIO_BOOTSTRAP_OVERRIDE - name: ISTIO_BOOTSTRAP_OVERRIDE
value: /etc/istio/custom-bootstrap/custom_bootstrap.json value: /etc/istio/custom-bootstrap/custom_bootstrap.json

View File

@@ -144,7 +144,3 @@ rules:
- apiGroups: [""] - apiGroups: [""]
verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ] verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ]
resources: [ "serviceaccounts"] resources: [ "serviceaccounts"]
# istio leader election need
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "update", "patch", "create"]

View File

@@ -173,8 +173,6 @@ spec:
value: "{{ .Values.global.xdsMaxRecvMsgSize }}" value: "{{ .Values.global.xdsMaxRecvMsgSize }}"
- name: ENBALE_SCOPED_RDS - name: ENBALE_SCOPED_RDS
value: "{{ .Values.global.enableSRDS }}" value: "{{ .Values.global.enableSRDS }}"
- name: ISTIO_DELTA_XDS
value: "{{ .Values.global.enableDeltaXDS }}"
- name: ON_DEMAND_RDS - name: ON_DEMAND_RDS
value: "{{ .Values.global.onDemandRDS }}" value: "{{ .Values.global.onDemandRDS }}"
- name: HOST_RDS_MERGE_SUBSET - name: HOST_RDS_MERGE_SUBSET

View File

@@ -9,8 +9,6 @@ global:
xdsMaxRecvMsgSize: "104857600" xdsMaxRecvMsgSize: "104857600"
defaultUpstreamConcurrencyThreshold: 10000 defaultUpstreamConcurrencyThreshold: 10000
enableSRDS: true enableSRDS: true
# -- Whether to enable Istio delta xDS, default is false.
enableDeltaXDS: true
# -- Whether to enable Redis(redis-stack-server) for Higress, default is false. # -- Whether to enable Redis(redis-stack-server) for Higress, default is false.
enableRedis: false enableRedis: false
enablePluginServer: false enablePluginServer: false

View File

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

View File

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

View File

@@ -163,7 +163,6 @@ The command removes all the Kubernetes components associated with the chart and
| global.defaultResources | object | `{"requests":{"cpu":"10m"}}` | A minimal set of requested resources to applied to all deployments so that Horizontal Pod Autoscaler will be able to function (if set). Each component can overwrite these default values by adding its own resources block in the relevant section below and setting the desired resources values. | | global.defaultResources | object | `{"requests":{"cpu":"10m"}}` | A minimal set of requested resources to applied to all deployments so that Horizontal Pod Autoscaler will be able to function (if set). Each component can overwrite these default values by adding its own resources block in the relevant section below and setting the desired resources values. |
| global.defaultUpstreamConcurrencyThreshold | int | `10000` | | | global.defaultUpstreamConcurrencyThreshold | int | `10000` | |
| global.disableAlpnH2 | bool | `false` | Whether to disable HTTP/2 in ALPN | | global.disableAlpnH2 | bool | `false` | Whether to disable HTTP/2 in ALPN |
| global.enableDeltaXDS | bool | `true` | Whether to enable Istio delta xDS, default is false. |
| global.enableGatewayAPI | bool | `true` | If true, Higress Controller will monitor Gateway API resources as well | | global.enableGatewayAPI | bool | `true` | If true, Higress Controller will monitor Gateway API resources as well |
| global.enableH3 | bool | `false` | | | global.enableH3 | bool | `false` | |
| global.enableIPv6 | bool | `false` | | | global.enableIPv6 | bool | `false` | |

View File

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

@@ -233,6 +233,7 @@ Anthropic Claude 所对应的 `type` 为 `claude`。它特有的配置字段如
- 设置 Claude Code 特定的请求头user-agent、x-app、anthropic-beta - 设置 Claude Code 特定的请求头user-agent、x-app、anthropic-beta
- 为请求 URL 添加 `?beta=true` 查询参数 - 为请求 URL 添加 `?beta=true` 查询参数
- 自动注入 Claude Code 的系统提示词(如未提供) - 自动注入 Claude Code 的系统提示词(如未提供)
- 自动注入 Bash 工具定义(如未提供)
这允许在 Higress 中直接使用 Claude Code 的 OAuth Token 进行身份验证。 这允许在 Higress 中直接使用 Claude Code 的 OAuth Token 进行身份验证。
@@ -1239,7 +1240,7 @@ provider:
启用此模式后,插件将自动: 启用此模式后,插件将自动:
- 使用 Bearer Token 认证(而非 x-api-key - 使用 Bearer Token 认证(而非 x-api-key
- 设置 Claude Code 特定的请求头和查询参数 - 设置 Claude Code 特定的请求头和查询参数
- 注入 Claude Code 的系统提示词(如未提供) - 注入 Claude Code 的系统提示词和 Bash 工具(如未提供)
**请求示例** **请求示例**
@@ -1258,6 +1259,7 @@ provider:
插件将自动转换为适合 Claude Code 的请求格式,包括: 插件将自动转换为适合 Claude Code 的请求格式,包括:
- 添加系统提示词:`"You are Claude Code, Anthropic's official CLI for Claude."` - 添加系统提示词:`"You are Claude Code, Anthropic's official CLI for Claude."`
- 添加 Bash 工具定义(用于执行命令)
- 设置适当的认证和请求头 - 设置适当的认证和请求头
### 使用智能协议转换 ### 使用智能协议转换

View File

@@ -199,6 +199,7 @@ When `claudeCodeMode: true` is enabled, the plugin will:
- Set Claude Code-specific request headers (user-agent, x-app, anthropic-beta) - Set Claude Code-specific request headers (user-agent, x-app, anthropic-beta)
- Add `?beta=true` query parameter to request URLs - Add `?beta=true` query parameter to request URLs
- Automatically inject Claude Code system prompt if not provided - Automatically inject Claude Code system prompt if not provided
- Automatically inject Bash tool definition if not provided
This enables direct use of Claude Code OAuth tokens for authentication in Higress. This enables direct use of Claude Code OAuth tokens for authentication in Higress.
@@ -1176,7 +1177,7 @@ provider:
Once this mode is enabled, the plugin will automatically: Once this mode is enabled, the plugin will automatically:
- Use Bearer Token authentication (instead of x-api-key) - Use Bearer Token authentication (instead of x-api-key)
- Set Claude Code-specific request headers and query parameters - Set Claude Code-specific request headers and query parameters
- Inject Claude Code system prompt if not provided - Inject Claude Code system prompt and Bash tool definitions if not provided
**Request Example** **Request Example**
@@ -1195,6 +1196,7 @@ Once this mode is enabled, the plugin will automatically:
The plugin will automatically transform the request into Claude Code format, including: 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."` - Adding system prompt: `"You are Claude Code, Anthropic's official CLI for Claude."`
- Adding Bash tool definition (for command execution)
- Setting appropriate authentication and request headers - Setting appropriate authentication and request headers
### Using Intelligent Protocol Conversion ### Using Intelligent Protocol Conversion

View File

@@ -149,7 +149,6 @@ func TestBedrock(t *testing.T) {
test.RunBedrockOnHttpRequestBodyTests(t) test.RunBedrockOnHttpRequestBodyTests(t)
test.RunBedrockOnHttpResponseHeadersTests(t) test.RunBedrockOnHttpResponseHeadersTests(t)
test.RunBedrockOnHttpResponseBodyTests(t) test.RunBedrockOnHttpResponseBodyTests(t)
test.RunBedrockToolCallTests(t)
} }
func TestClaude(t *testing.T) { func TestClaude(t *testing.T) {

View File

@@ -206,16 +206,7 @@ func (m *azureProvider) transformRequestPath(ctx wrapper.HttpContext, apiName Ap
path = strings.ReplaceAll(path, pathAzureModelPlaceholder, model) path = strings.ReplaceAll(path, pathAzureModelPlaceholder, model)
log.Debugf("azureProvider: model replaced path: %s", path) log.Debugf("azureProvider: model replaced path: %s", path)
} }
if !strings.Contains(path, "?") { path = path + "?" + m.serviceUrl.RawQuery
// 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) log.Debugf("azureProvider: final path: %s", path)
return path return path

View File

@@ -769,15 +769,7 @@ func (b *bedrockProvider) buildBedrockTextGenerationRequest(origRequest *chatCom
case roleSystem: case roleSystem:
systemMessages = append(systemMessages, systemContentBlock{Text: msg.StringContent()}) systemMessages = append(systemMessages, systemContentBlock{Text: msg.StringContent()})
case roleTool: case roleTool:
toolResultContent := chatToolMessage2BedrockToolResultContent(msg) messages = append(messages, chatToolMessage2BedrockMessage(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: default:
messages = append(messages, chatMessage2BedrockMessage(msg)) messages = append(messages, chatMessage2BedrockMessage(msg))
} }
@@ -1068,7 +1060,7 @@ type tokenUsage struct {
TotalTokens int `json:"totalTokens"` TotalTokens int `json:"totalTokens"`
} }
func chatToolMessage2BedrockToolResultContent(chatMessage chatMessage) bedrockMessageContent { func chatToolMessage2BedrockMessage(chatMessage chatMessage) bedrockMessage {
toolResultContent := &toolResultBlock{} toolResultContent := &toolResultBlock{}
toolResultContent.ToolUseId = chatMessage.ToolCallId toolResultContent.ToolUseId = chatMessage.ToolCallId
if text, ok := chatMessage.Content.(string); ok { if text, ok := chatMessage.Content.(string); ok {
@@ -1091,29 +1083,29 @@ func chatToolMessage2BedrockToolResultContent(chatMessage chatMessage) bedrockMe
} else { } else {
log.Warnf("the content type is not supported, current content is %v", chatMessage.Content) log.Warnf("the content type is not supported, current content is %v", chatMessage.Content)
} }
return bedrockMessageContent{ return bedrockMessage{
ToolResult: toolResultContent, Role: roleUser,
Content: []bedrockMessageContent{
{
ToolResult: toolResultContent,
},
},
} }
} }
func chatMessage2BedrockMessage(chatMessage chatMessage) bedrockMessage { func chatMessage2BedrockMessage(chatMessage chatMessage) bedrockMessage {
var result bedrockMessage var result bedrockMessage
if len(chatMessage.ToolCalls) > 0 { 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{ result = bedrockMessage{
Role: chatMessage.Role, Role: chatMessage.Role,
Content: contents, 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,
} }
} else if chatMessage.IsStringContent() { } else if chatMessage.IsStringContent() {
result = bedrockMessage{ result = bedrockMessage{

View File

@@ -21,9 +21,11 @@ const (
claudeDefaultMaxTokens = 4096 claudeDefaultMaxTokens = 4096
// Claude Code mode constants // Claude Code mode constants
claudeCodeUserAgent = "claude-cli/2.1.2 (external, cli)" claudeCodeUserAgent = "claude-cli/2.1.2 (external, cli)"
claudeCodeBetaFeatures = "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219" claudeCodeBetaFeatures = "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219"
claudeCodeSystemPrompt = "You are Claude Code, Anthropic's official CLI for Claude." claudeCodeSystemPrompt = "You are Claude Code, Anthropic's official CLI for Claude."
claudeCodeBashToolName = "Bash"
claudeCodeBashToolDesc = "Run bash commands"
) )
type claudeProviderInitializer struct{} type claudeProviderInitializer struct{}
@@ -550,6 +552,32 @@ func (c *claudeProvider) buildClaudeTextGenRequest(origRequest *chatCompletionRe
claudeRequest.Tools = append(claudeRequest.Tools, claudeTool) claudeRequest.Tools = append(claudeRequest.Tools, claudeTool)
} }
// In Claude Code mode, add Bash tool if not present
if c.config.claudeCodeMode {
hasBashTool := false
for _, tool := range claudeRequest.Tools {
if tool.Name == claudeCodeBashToolName {
hasBashTool = true
break
}
}
if !hasBashTool {
claudeRequest.Tools = append(claudeRequest.Tools, claudeTool{
Name: claudeCodeBashToolName,
Description: claudeCodeBashToolDesc,
InputSchema: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"command": map[string]interface{}{
"type": "string",
},
},
"required": []string{"command"},
},
})
}
}
if tc := origRequest.getToolChoiceObject(); tc != nil { if tc := origRequest.getToolChoiceObject(); tc != nil {
claudeRequest.ToolChoice = &claudeToolChoice{ claudeRequest.ToolChoice = &claudeToolChoice{
Name: tc.Function.Name, Name: tc.Function.Name,

View File

@@ -237,6 +237,104 @@ func TestClaudeProvider_BuildClaudeTextGenRequest_ClaudeCodeMode(t *testing.T) {
assert.Equal(t, "ephemeral", claudeReq.System.ArrayValue[0].CacheControl["type"]) assert.Equal(t, "ephemeral", claudeReq.System.ArrayValue[0].CacheControl["type"])
}) })
t.Run("injects_bash_tool_when_missing", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Messages: []chatMessage{
{Role: roleUser, Content: "List files"},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Should have Bash tool injected
require.Len(t, claudeReq.Tools, 1)
assert.Equal(t, claudeCodeBashToolName, claudeReq.Tools[0].Name)
assert.Equal(t, claudeCodeBashToolDesc, claudeReq.Tools[0].Description)
// Verify input schema
assert.NotNil(t, claudeReq.Tools[0].InputSchema)
assert.Equal(t, "object", claudeReq.Tools[0].InputSchema["type"])
})
t.Run("does_not_duplicate_bash_tool", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Messages: []chatMessage{
{Role: roleUser, Content: "List files"},
},
Tools: []tool{
{
Type: "function",
Function: function{
Name: "Bash",
Description: "Custom bash tool",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string"},
},
},
},
},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Should not duplicate Bash tool
assert.Len(t, claudeReq.Tools, 1)
assert.Equal(t, "Bash", claudeReq.Tools[0].Name)
// Should preserve the original description
assert.Equal(t, "Custom bash tool", claudeReq.Tools[0].Description)
})
t.Run("adds_bash_tool_alongside_existing_tools", func(t *testing.T) {
request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929",
MaxTokens: 8192,
Messages: []chatMessage{
{Role: roleUser, Content: "Hello"},
},
Tools: []tool{
{
Type: "function",
Function: function{
Name: "Read",
Description: "Read files",
Parameters: map[string]interface{}{
"type": "object",
},
},
},
{
Type: "function",
Function: function{
Name: "Write",
Description: "Write files",
Parameters: map[string]interface{}{
"type": "object",
},
},
},
},
}
claudeReq := provider.buildClaudeTextGenRequest(request)
// Should have original tools plus Bash tool
assert.Len(t, claudeReq.Tools, 3)
toolNames := make([]string, len(claudeReq.Tools))
for i, tool := range claudeReq.Tools {
toolNames[i] = tool.Name
}
assert.Contains(t, toolNames, "Read")
assert.Contains(t, toolNames, "Write")
assert.Contains(t, toolNames, "Bash")
})
t.Run("full_request_transformation", func(t *testing.T) { t.Run("full_request_transformation", func(t *testing.T) {
request := &chatCompletionRequest{ request := &chatCompletionRequest{
Model: "claude-sonnet-4-5-20250929", Model: "claude-sonnet-4-5-20250929",
@@ -265,8 +363,9 @@ func TestClaudeProvider_BuildClaudeTextGenRequest_ClaudeCodeMode(t *testing.T) {
require.Len(t, claudeReq.Messages, 1) require.Len(t, claudeReq.Messages, 1)
assert.Equal(t, roleUser, claudeReq.Messages[0].Role) assert.Equal(t, roleUser, claudeReq.Messages[0].Role)
// Verify no tools are injected by default // Verify Bash tool
assert.Empty(t, claudeReq.Tools) require.Len(t, claudeReq.Tools, 1)
assert.Equal(t, "Bash", claudeReq.Tools[0].Name)
// Verify the request can be serialized to JSON // Verify the request can be serialized to JSON
jsonBytes, err := json.Marshal(claudeReq) jsonBytes, err := json.Marshal(claudeReq)
@@ -289,6 +388,8 @@ func TestClaudeConstants(t *testing.T) {
assert.Equal(t, "claude-cli/2.1.2 (external, cli)", claudeCodeUserAgent) 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, "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) assert.Equal(t, "You are Claude Code, Anthropic's official CLI for Claude.", claudeCodeSystemPrompt)
assert.Equal(t, "Bash", claudeCodeBashToolName)
assert.Equal(t, "Run bash commands", claudeCodeBashToolDesc)
} }
func TestClaudeProvider_GetApiName(t *testing.T) { func TestClaudeProvider_GetApiName(t *testing.T) {

View File

@@ -343,7 +343,7 @@ func RunAzureOnHttpRequestHeadersTests(t *testing.T) {
// 验证Path是否被正确处理 // 验证Path是否被正确处理
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path") pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist") require.True(t, hasPath, "Path header should exist")
require.Equal(t, "/openai/deployments/test-deployment/chat/completions?api-version=2024-02-15-preview", pathValue, "Path should equal Azure deployment path") require.Contains(t, pathValue, "/openai/deployments/test-deployment/chat/completions", "Path should contain Azure deployment path")
// 验证Content-Length是否被删除 // 验证Content-Length是否被删除
_, hasContentLength := test.GetHeaderValue(requestHeaders, "Content-Length") _, hasContentLength := test.GetHeaderValue(requestHeaders, "Content-Length")
@@ -443,7 +443,8 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) {
requestHeaders := host.GetRequestHeaders() requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path") pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist") require.True(t, hasPath, "Path header should exist")
require.Equal(t, pathValue, "/openai/deployments/test-deployment/chat/completions?api-version=2024-02-15-preview", "Path should contain Azure deployment path") 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")
}) })
// 测试Azure OpenAI请求体处理不同模型 // 测试Azure OpenAI请求体处理不同模型
@@ -576,7 +577,7 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) {
requestHeaders := host.GetRequestHeaders() requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path") pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist") require.True(t, hasPath, "Path header should exist")
require.Equal(t, pathValue, "/openai/deployments/deployment-only/chat/completions?api-version=2024-02-15-preview", "Path should use default deployment") require.Contains(t, pathValue, "/openai/deployments/deployment-only/chat/completions", "Path should use default deployment")
}) })
// 测试Azure OpenAI请求体处理仅域名配置 // 测试Azure OpenAI请求体处理仅域名配置
@@ -612,42 +613,7 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) {
requestHeaders := host.GetRequestHeaders() requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path") pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath, "Path header should exist") require.True(t, hasPath, "Path header should exist")
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") require.Contains(t, pathValue, "/openai/deployments/gpt-3.5-turbo/chat/completions", "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")
}) })
}) })
} }
@@ -861,8 +827,10 @@ func RunAzureBasePathHandlingTests(t *testing.T) {
require.NotContains(t, pathValue, "/azure-gpt4", require.NotContains(t, pathValue, "/azure-gpt4",
"After body stage: basePath should be removed from path") "After body stage: basePath should be removed from path")
// 在 openai 协议下,路径会被转换为 Azure 的路径格式 // 在 openai 协议下,路径会被转换为 Azure 的路径格式
require.Equal(t, pathValue, "/openai/deployments/gpt-4/chat/completions?api-version=2024-02-15-preview", require.Contains(t, pathValue, "/openai/deployments/gpt-4/chat/completions",
"Path should be transformed to Azure format") "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 协议下能正常工作 // 测试 basePath prepend 在 original 协议下能正常工作

View File

@@ -442,186 +442,6 @@ 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) { func RunBedrockOnHttpResponseBodyTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) { test.RunTest(t, func(t *testing.T) {
// Test Bedrock response body processing // Test Bedrock response body processing

View File

@@ -270,6 +270,47 @@ func RunClaudeOnHttpRequestBodyTests(t *testing.T) {
require.Equal(t, "ephemeral", cacheControlMap["type"]) require.Equal(t, "ephemeral", cacheControlMap["type"])
}) })
t.Run("claude code mode injects bash tool", 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": "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 Bash tool
tools, hasTools := request["tools"]
require.True(t, hasTools, "claude code mode should inject tools")
toolsArr, ok := tools.([]interface{})
require.True(t, ok)
require.Len(t, toolsArr, 1)
bashTool, ok := toolsArr[0].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "Bash", bashTool["name"])
require.Equal(t, "Run bash commands", bashTool["description"])
})
t.Run("claude code mode preserves existing system prompt", func(t *testing.T) { t.Run("claude code mode preserves existing system prompt", func(t *testing.T) {
host, status := test.NewTestHost(claudeCodeModeConfig) host, status := test.NewTestHost(claudeCodeModeConfig)
defer host.Reset() defer host.Reset()
@@ -310,6 +351,111 @@ func RunClaudeOnHttpRequestBodyTests(t *testing.T) {
require.True(t, ok) require.True(t, ok)
require.Equal(t, "You are a custom assistant.", systemBlock["text"]) require.Equal(t, "You are a custom assistant.", systemBlock["text"])
}) })
t.Run("claude code mode does not duplicate bash tool", 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": "user", "content": "Hello"}
],
"tools": [
{
"type": "function",
"function": {
"name": "Bash",
"description": "Custom bash tool",
"parameters": {"type": "object"}
}
}
]
}`
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 not duplicate Bash tool
tools, hasTools := request["tools"]
require.True(t, hasTools)
toolsArr, ok := tools.([]interface{})
require.True(t, ok)
require.Len(t, toolsArr, 1, "should not duplicate Bash tool")
})
t.Run("claude code mode adds bash tool alongside existing tools", 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": "user", "content": "Hello"}
],
"tools": [
{
"type": "function",
"function": {
"name": "Read",
"description": "Read files",
"parameters": {"type": "object"}
}
}
]
}`
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 have both Read and Bash tools
tools, hasTools := request["tools"]
require.True(t, hasTools)
toolsArr, ok := tools.([]interface{})
require.True(t, ok)
require.Len(t, toolsArr, 2, "should have Read tool plus injected Bash tool")
// Verify both tools exist
toolNames := make([]string, 0)
for _, tool := range toolsArr {
toolMap, ok := tool.(map[string]interface{})
if ok {
if name, hasName := toolMap["name"]; hasName {
toolNames = append(toolNames, name.(string))
}
}
}
require.Contains(t, toolNames, "Read")
require.Contains(t, toolNames, "Bash")
})
}) })
} }

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)), 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)), NotBefore: jwt.NewNumericDate(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
}, },
"expired": { "expried": {
Issuer: "higress-test", Issuer: "higress-test",
Subject: "higress-test", Subject: "higress-test",
Audience: []string{"foo", "bar"}, Audience: []string{"foo", "bar"},

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ fi
CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${ROOT}/external/package,destination=/home/package " CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${ROOT}/external/package,destination=/home/package "
CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${ROOT}/external/envoy,destination=/home/envoy " CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${ROOT}/external/envoy,destination=/home/envoy "
BUILD_TOOLS_IMG=${BUILD_TOOLS_IMG:-"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/build-tools-proxy:master-eebcdda8856e2d4f528991d27d4808880cce4c52"} BUILD_TOOLS_IMG=${BUILD_TOOLS_IMG:-"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/build-tools-proxy:release-1.19-ef344298e65eeb2d9e2d07b87eb4e715c2def613"}
BUILD_WITH_CONTAINER=1 \ BUILD_WITH_CONTAINER=1 \
CONDITIONAL_HOST_MOUNTS=${CONDITIONAL_HOST_MOUNTS} \ CONDITIONAL_HOST_MOUNTS=${CONDITIONAL_HOST_MOUNTS} \