mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-02 07:25:26 +08:00
589 lines
18 KiB
Markdown
589 lines
18 KiB
Markdown
# DS2API Deployment Guide
|
|
|
|
Language: [中文](DEPLOY.md) | [English](DEPLOY.en.md)
|
|
|
|
This guide covers all deployment methods for the current Go-based codebase.
|
|
|
|
Doc map: [Index](./README.md) | [Architecture](./ARCHITECTURE.en.md) | [API](../API.en.md) | [Testing](./TESTING.md)
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
- [Recommended deployment priority](#recommended-deployment-priority)
|
|
- [Prerequisites](#0-prerequisites)
|
|
- [1. Download Release Binaries](#1-download-release-binaries)
|
|
- [2. Docker / GHCR Deployment](#2-docker--ghcr-deployment)
|
|
- [3. Vercel Deployment](#3-vercel-deployment)
|
|
- [4. Local Run from Source](#4-local-run-from-source)
|
|
- [5. Reverse Proxy (Nginx)](#5-reverse-proxy-nginx)
|
|
- [6. Linux systemd Service](#6-linux-systemd-service)
|
|
- [7. Post-Deploy Checks](#7-post-deploy-checks)
|
|
- [8. Pre-Release Local Regression](#8-pre-release-local-regression)
|
|
|
|
---
|
|
|
|
## Recommended deployment priority
|
|
|
|
Recommended order when choosing a deployment method:
|
|
|
|
1. **Download and run release binaries**: the easiest path for most users because the artifacts are already built.
|
|
2. **Docker / GHCR image deployment**: suitable for containerized, orchestrated, or cloud environments.
|
|
3. **Vercel deployment**: suitable if you already use Vercel and accept its platform constraints.
|
|
4. **Run from source / build locally**: suitable for development, debugging, or when you need to modify the code yourself.
|
|
|
|
---
|
|
|
|
## 0. Prerequisites
|
|
|
|
| Dependency | Minimum Version | Notes |
|
|
| --- | --- | --- |
|
|
| Go | 1.26+ | Build backend |
|
|
| Node.js | `20.19+` or `22.12+` | Only needed to build WebUI locally |
|
|
| npm | Bundled with Node.js | Install WebUI dependencies |
|
|
|
|
Config source (choose one):
|
|
|
|
- **File**: `config.json` (recommended for local/Docker)
|
|
- **Environment variable**: `DS2API_CONFIG_JSON` (recommended for Vercel; supports raw JSON or Base64)
|
|
|
|
Unified recommendation (best practice):
|
|
|
|
```bash
|
|
cp config.example.json config.json
|
|
# Edit config.json
|
|
```
|
|
|
|
Use `config.json` as the single source of truth:
|
|
- Local run: read `config.json` directly
|
|
- Docker / Vercel: generate `DS2API_CONFIG_JSON` (Base64) from `config.json` and inject it
|
|
|
|
---
|
|
|
|
## 1. Download Release Binaries
|
|
|
|
Built-in GitHub Actions workflow: `.github/workflows/release-artifacts.yml`
|
|
|
|
- **Trigger**: only on Release `published` (no build on normal push)
|
|
- **Outputs**: multi-platform binary archives + `sha256sums.txt`
|
|
- **Container publishing**: GHCR only (`ghcr.io/cjackhwang/ds2api`)
|
|
|
|
| Platform | Architecture | Format |
|
|
| --- | --- | --- |
|
|
| Linux | amd64, arm64, armv7 | `.tar.gz` |
|
|
| macOS | amd64, arm64 | `.tar.gz` |
|
|
| Windows | amd64, arm64 | `.zip` |
|
|
|
|
Each archive includes:
|
|
|
|
- `ds2api` executable (`ds2api.exe` on Windows)
|
|
- `static/admin/` (built WebUI assets)
|
|
- `config.example.json`, `.env.example`
|
|
- `README.MD`, `README.en.md`, `LICENSE`
|
|
|
|
### Usage
|
|
|
|
```bash
|
|
# 1. Download the archive for your platform
|
|
# 2. Extract
|
|
tar -xzf ds2api_<tag>_linux_amd64.tar.gz
|
|
cd ds2api_<tag>_linux_amd64
|
|
|
|
# 3. Configure
|
|
cp config.example.json config.json
|
|
# Edit config.json
|
|
|
|
# 4. Start
|
|
./ds2api
|
|
```
|
|
|
|
### Maintainer Release Flow
|
|
|
|
1. Create and publish a GitHub Release (with tag, for example `vX.Y.Z`)
|
|
2. Wait for the `Release Artifacts` workflow to complete
|
|
3. Download the matching archive from Release Assets
|
|
|
|
---
|
|
|
|
## 2. Docker / GHCR Deployment
|
|
|
|
### 2.1 Basic Steps
|
|
|
|
```bash
|
|
# Pull prebuilt image
|
|
docker pull ghcr.io/cjackhwang/ds2api:latest
|
|
|
|
# Copy env template and config file
|
|
cp .env.example .env
|
|
cp config.example.json config.json
|
|
|
|
# Edit .env and set at least:
|
|
# DS2API_ADMIN_KEY=your-admin-key
|
|
# Optionally set the host port:
|
|
# DS2API_HOST_PORT=6011
|
|
|
|
# Start
|
|
docker-compose up -d
|
|
|
|
# View logs
|
|
docker-compose logs -f
|
|
```
|
|
|
|
The default `docker-compose.yml` directly uses `ghcr.io/cjackhwang/ds2api:latest` and maps host port `6011` to container port `5001`. If you want `5001` exposed directly, set `DS2API_HOST_PORT=5001` (or adjust the `ports` mapping).
|
|
The compose template also defaults to `DS2API_CONFIG_PATH=/data/config.json` with `./config.json:/data/config.json` mounted, so deployments avoid read-only `/app` persistence issues by default.
|
|
|
|
If you want a pinned version instead of `latest`, you can also pull a specific tag directly:
|
|
|
|
```bash
|
|
docker pull ghcr.io/cjackhwang/ds2api:v3.0.0
|
|
```
|
|
|
|
### 2.2 Update
|
|
|
|
```bash
|
|
docker-compose up -d --build
|
|
```
|
|
|
|
### 2.3 Docker Architecture
|
|
|
|
The `Dockerfile` now provides two image paths:
|
|
|
|
1. **Default local/dev path (`runtime-from-source`)**: a three-stage build (WebUI build + Go build + runtime).
|
|
2. **Release path (`runtime-from-dist`)**: the release workflow first creates tag-named release archives, then copies the Linux bundles to `dist/docker-input/linux_amd64.tar.gz` / `linux_arm64.tar.gz`; Docker consumes those prepared inputs directly, without rerunning `npm build`/`go build`.
|
|
|
|
The release path keeps Docker images aligned with release archives and reduces duplicate build work.
|
|
|
|
Container entry command: `/usr/local/bin/ds2api`, default exposed port: `5001`.
|
|
|
|
### 2.4 Development Mode
|
|
|
|
```bash
|
|
docker-compose -f docker-compose.dev.yml up
|
|
```
|
|
|
|
Development features:
|
|
- Source code mounted (live changes)
|
|
- `LOG_LEVEL=DEBUG`
|
|
- No auto-restart
|
|
|
|
### 2.5 Health Check
|
|
|
|
Docker Compose includes a built-in health check:
|
|
|
|
```yaml
|
|
healthcheck:
|
|
test: ["CMD", "/usr/local/bin/busybox", "wget", "-qO-", "http://localhost:${PORT:-5001}/healthz"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
```
|
|
|
|
### 2.6 Docker Troubleshooting
|
|
|
|
If container logs look normal but the admin panel is unreachable, check these first:
|
|
|
|
1. **Port alignment**: when `PORT` is not `5001`, use the same port in your URL (for example `http://localhost:8080/admin`).
|
|
2. **WebUI assets in dev compose**: `docker-compose.dev.yml` runs `go run` in a dev image and does not auto-install Node.js inside the container; if `static/admin` is missing in your repo, `/admin` will return 404. Build once on host: `./scripts/build-webui.sh`.
|
|
|
|
### 2.7 Zeabur One-Click (Dockerfile)
|
|
|
|
This repo includes a `zeabur.yaml` template for one-click deployment on Zeabur:
|
|
|
|
[](https://zeabur.com/templates/L4CFHP)
|
|
|
|
Notes:
|
|
|
|
- **Port**: DS2API listens on `5001` by default; the template sets `PORT=5001`.
|
|
- **Persistent config**: the template mounts `/data` and sets `DS2API_CONFIG_PATH=/data/config.json`. After importing config in Admin UI, it will be written and persisted to this path.
|
|
- **`open /app/config.json: permission denied`**: this means the instance is trying to persist runtime tokens to a read-only path (commonly `/app` inside the image).
|
|
Recommended handling:
|
|
1. Set a writable path explicitly: `DS2API_CONFIG_PATH=/data/config.json` (and mount a persistent volume at `/data`);
|
|
2. If you bootstrap with `DS2API_CONFIG_JSON` and do not need runtime writeback, keep env-backed mode (`DS2API_ENV_WRITEBACK` disabled);
|
|
3. In current versions, login/session tests continue even if persistence fails; Admin API returns a warning that token persistence failed and token is memory-only until restart.
|
|
- **Build version**: Zeabur / regular `docker build` does not require `BUILD_VERSION` by default. The image prefers that build arg when provided, and automatically falls back to the repo-root `VERSION` file when it is absent.
|
|
- **First login**: after deployment, open `/admin` and login with `DS2API_ADMIN_KEY` shown in Zeabur env/template instructions (recommended: rotate to a strong secret after first login).
|
|
|
|
---
|
|
|
|
## 3. Vercel Deployment
|
|
|
|
### 3.1 Steps
|
|
|
|
1. **Fork** the repo to your GitHub account
|
|
2. **Import** the project on Vercel
|
|
3. **Set environment variables** (minimum required: one variable):
|
|
|
|
| Variable | Description |
|
|
| --- | --- |
|
|
| `DS2API_ADMIN_KEY` | Admin key (required) |
|
|
| `DS2API_CONFIG_JSON` | Config content, raw JSON or Base64 (optional, recommended) |
|
|
|
|
4. **Deploy**
|
|
|
|
### 3.1.1 Recommended Input (avoid `DS2API_CONFIG_JSON` mistakes)
|
|
|
|
If you prefer faster one-click bootstrap, you can leave `DS2API_CONFIG_JSON` empty first, then open `/admin` after deployment, import config, and sync it back to Vercel env vars from the "Vercel Sync" page.
|
|
|
|
Recommended: in repo root, copy the template first and fill your real accounts:
|
|
|
|
```bash
|
|
cp config.example.json config.json
|
|
# Edit config.json
|
|
```
|
|
|
|
Do not hand-edit large JSON directly in Vercel. Generate Base64 locally and paste it:
|
|
|
|
```bash
|
|
# Run in repo root
|
|
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
|
echo "$DS2API_CONFIG_JSON"
|
|
```
|
|
|
|
If you choose to preconfigure before first deploy, set these vars in Vercel Project Settings -> Environment Variables:
|
|
|
|
```text
|
|
DS2API_ADMIN_KEY=replace-with-a-strong-secret
|
|
DS2API_CONFIG_JSON=<the single-line Base64 output above>
|
|
```
|
|
|
|
Optional but recommended (for WebUI one-click Vercel sync):
|
|
|
|
```text
|
|
VERCEL_TOKEN=your-vercel-token
|
|
VERCEL_PROJECT_ID=prj_xxxxxxxxxxxx
|
|
VERCEL_TEAM_ID=team_xxxxxxxxxxxx # optional for personal accounts
|
|
```
|
|
|
|
### 3.2 Optional Environment Variables
|
|
|
|
| Variable | Description | Default |
|
|
| --- | --- | --- |
|
|
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Per-account inflight limit | `2` |
|
|
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
|
|
| `DS2API_GLOBAL_MAX_INFLIGHT` | Global inflight limit | `recommended_concurrency` |
|
|
| `DS2API_ENV_WRITEBACK` | When `DS2API_CONFIG_JSON` is present, auto-write to `DS2API_CONFIG_PATH` and switch to file-backed mode after success (`1/true/yes/on`) | Disabled |
|
|
| `DS2API_VERCEL_INTERNAL_SECRET` | Hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
|
|
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL | `900` |
|
|
| `DS2API_RAW_STREAM_SAMPLE_ROOT` | Raw stream sample root for saving/reading samples | `tests/raw_stream_samples` |
|
|
| `VERCEL_TOKEN` | Vercel sync token | — |
|
|
| `VERCEL_PROJECT_ID` | Vercel project ID | — |
|
|
| `VERCEL_TEAM_ID` | Vercel team ID | — |
|
|
| `DS2API_VERCEL_PROTECTION_BYPASS` | Deployment protection bypass for internal Node→Go calls | — |
|
|
|
|
### 3.4 Vercel Architecture
|
|
|
|
```text
|
|
Request ──────┐
|
|
│
|
|
▼
|
|
vercel.json routing
|
|
│
|
|
┌─────┴─────┐
|
|
│ │
|
|
▼ ▼
|
|
api/index.go api/chat-stream.js
|
|
(Go Runtime) (Node Runtime)
|
|
```
|
|
|
|
- **Go entry**: `api/index.go` (Serverless Go)
|
|
- **Stream entry**: `api/chat-stream.js` (Node Runtime for real-time SSE)
|
|
- **Routing**: `vercel.json`
|
|
- **Build command**: `npm ci --prefix webui && npm run build --prefix webui` (automatic)
|
|
|
|
#### Streaming Pipeline
|
|
|
|
Vercel Go Runtime applies platform-level response buffering, so this project uses a hybrid "**Go prepare + Node stream**" path on Vercel:
|
|
|
|
1. `api/chat-stream.js` receives `/v1/chat/completions` request
|
|
2. Node calls Go internal prepare endpoint (`?__stream_prepare=1`) for session ID, PoW, token
|
|
3. Go prepare creates a stream lease, locking the account
|
|
4. Node connects directly to DeepSeek upstream, relays SSE in real-time to client (including OpenAI chunk framing and tools anti-leak sieve)
|
|
5. After stream ends, Node calls Go release endpoint (`?__stream_release=1`) to free the account
|
|
|
|
> This adaptation is **Vercel-only**; local and Docker remain pure Go.
|
|
|
|
#### Non-Stream Fallback and Tool Call Handling
|
|
|
|
- `api/chat-stream.js` falls back to Go entry (`?__go=1`) for non-stream requests only
|
|
- Streaming requests (including requests with `tools`) stay on the Node path and use Go-aligned tool-call anti-leak handling
|
|
- The Node stream path also mirrors Go finalization semantics: empty visible output returns the same shaped error SSE, and empty `content_filter` returns a `content_filter` error
|
|
- WebUI non-stream test calls `?__go=1` directly to avoid Node hop timeout on long requests
|
|
|
|
#### Function Duration
|
|
|
|
`vercel.json` sets `maxDuration: 300` for both `api/chat-stream.js` and `api/index.go` (subject to your Vercel plan limits).
|
|
|
|
### 3.5 Vercel Troubleshooting
|
|
|
|
#### Go Build Failure
|
|
|
|
```text
|
|
Error: Command failed: go build -ldflags -s -w -o .../bootstrap ...
|
|
```
|
|
|
|
**Cause**: Invalid Go build flag settings in Vercel (`-ldflags` not passed as a single argument).
|
|
|
|
**Fix**:
|
|
|
|
1. Open Vercel Project Settings → Build and Development Settings
|
|
2. **Clear** custom Go Build Flags / Build Command (recommended)
|
|
3. If ldflags must be used, set `-ldflags="-s -w"` (ensure it's one argument)
|
|
4. Verify `go.mod` uses a supported version (currently `go 1.26.0`)
|
|
5. Redeploy (recommended: clear cache)
|
|
|
|
#### Internal Package Import Error
|
|
|
|
```text
|
|
use of internal package ds2api/internal/server not allowed
|
|
```
|
|
|
|
**Cause**: Vercel Go entrypoint directly imports `internal/...`.
|
|
|
|
**Fix**: This repo uses a public bridge package: `api/index.go` → `ds2api/app` → `internal/server`.
|
|
|
|
#### Output Directory Error
|
|
|
|
```text
|
|
No Output Directory named "public" found after the Build completed.
|
|
```
|
|
|
|
**Fix**: This repo uses `static` as output directory (`"outputDirectory": "static"` in `vercel.json`). If you manually changed Output Directory in Project Settings, set it to `static` or clear it.
|
|
|
|
#### Deployment Protection Blocking
|
|
|
|
If API responses return Vercel HTML `Authentication Required`:
|
|
|
|
- **Option A**: Disable Deployment Protection for that environment (recommended for public APIs)
|
|
- **Option B**: Add `x-vercel-protection-bypass` header to requests
|
|
- **Option C**: Set `VERCEL_AUTOMATION_BYPASS_SECRET` (or `DS2API_VERCEL_PROTECTION_BYPASS`) for internal Node→Go calls
|
|
|
|
### 3.6 Build Artifacts Not Committed
|
|
|
|
- `static/admin` directory is not in Git
|
|
- Vercel / Docker automatically generate WebUI assets during build
|
|
|
|
---
|
|
|
|
## 4. Local Run from Source
|
|
|
|
### 4.1 Basic Steps
|
|
|
|
```bash
|
|
# Clone
|
|
git clone https://github.com/CJackHwang/ds2api.git
|
|
cd ds2api
|
|
|
|
# Copy and edit config
|
|
cp config.example.json config.json
|
|
# Open config.json and fill in:
|
|
# - keys: your API access keys
|
|
# - accounts: DeepSeek accounts (email or mobile + password)
|
|
|
|
# Start
|
|
go run ./cmd/ds2api
|
|
```
|
|
|
|
Default local access URL: `http://127.0.0.1:5001`; the server actually binds to `0.0.0.0:5001` (override with `PORT`).
|
|
|
|
### 4.2 WebUI Build
|
|
|
|
On first local startup, if `static/admin/` is missing, DS2API will automatically attempt to build the WebUI (requires Node.js/npm; when dependencies are missing it runs `npm ci` first, then `npm run build -- --outDir static/admin --emptyOutDir`).
|
|
|
|
Manual build:
|
|
|
|
```bash
|
|
./scripts/build-webui.sh
|
|
```
|
|
|
|
Or step by step:
|
|
|
|
```bash
|
|
cd webui
|
|
npm install
|
|
npm run build
|
|
# Output goes to static/admin/
|
|
```
|
|
|
|
Control auto-build via environment variable:
|
|
|
|
```bash
|
|
# Disable auto-build
|
|
DS2API_AUTO_BUILD_WEBUI=false go run ./cmd/ds2api
|
|
|
|
# Force enable auto-build
|
|
DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
|
|
```
|
|
|
|
### 4.3 Compile to Binary
|
|
|
|
```bash
|
|
go build -o ds2api ./cmd/ds2api
|
|
./ds2api
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Reverse Proxy (Nginx)
|
|
|
|
When deploying behind Nginx, **you must disable buffering** for SSE streaming to work:
|
|
|
|
```nginx
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Connection "";
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
chunked_transfer_encoding on;
|
|
tcp_nodelay on;
|
|
}
|
|
```
|
|
|
|
For HTTPS, add SSL at the Nginx layer:
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl;
|
|
server_name api.example.com;
|
|
|
|
ssl_certificate /path/to/cert.pem;
|
|
ssl_certificate_key /path/to/key.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Connection "";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
chunked_transfer_encoding on;
|
|
tcp_nodelay on;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Linux systemd Service
|
|
|
|
### 6.1 Installation
|
|
|
|
```bash
|
|
# Copy compiled binary and related files to target directory
|
|
sudo mkdir -p /opt/ds2api
|
|
sudo cp ds2api config.json /opt/ds2api/
|
|
sudo cp -r static/admin /opt/ds2api/static/admin
|
|
```
|
|
|
|
### 6.2 Create systemd Service File
|
|
|
|
```ini
|
|
# /etc/systemd/system/ds2api.service
|
|
|
|
[Unit]
|
|
Description=DS2API (Go)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=/opt/ds2api
|
|
Environment=PORT=5001
|
|
Environment=DS2API_CONFIG_PATH=/opt/ds2api/config.json
|
|
Environment=DS2API_ADMIN_KEY=your-admin-key-here
|
|
ExecStart=/opt/ds2api/ds2api
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
### 6.3 Common Commands
|
|
|
|
```bash
|
|
# Reload service config
|
|
sudo systemctl daemon-reload
|
|
|
|
# Enable on boot
|
|
sudo systemctl enable ds2api
|
|
|
|
# Start
|
|
sudo systemctl start ds2api
|
|
|
|
# Check status
|
|
sudo systemctl status ds2api
|
|
|
|
# View logs
|
|
sudo journalctl -u ds2api -f
|
|
|
|
# Restart
|
|
sudo systemctl restart ds2api
|
|
|
|
# Stop
|
|
sudo systemctl stop ds2api
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Post-Deploy Checks
|
|
|
|
After deployment (any method), verify in order:
|
|
|
|
```bash
|
|
# 1. Liveness probe
|
|
curl -s http://127.0.0.1:5001/healthz
|
|
# Expected: {"status":"ok"}
|
|
|
|
# 2. Readiness probe
|
|
curl -s http://127.0.0.1:5001/readyz
|
|
# Expected: {"status":"ready"}
|
|
|
|
# 3. Model list
|
|
curl -s http://127.0.0.1:5001/v1/models
|
|
# Expected: {"object":"list","data":[...]} (including `*-nothinking` variants)
|
|
|
|
# 4. Admin panel (if WebUI is built)
|
|
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/admin
|
|
# Expected: 200
|
|
|
|
# 5. Test API call
|
|
curl http://127.0.0.1:5001/v1/chat/completions \
|
|
-H "Authorization: Bearer your-api-key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hello"}]}'
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Pre-Release Local Regression
|
|
|
|
Run the full live testsuite before release (real account tests):
|
|
|
|
```bash
|
|
./tests/scripts/run-live.sh
|
|
```
|
|
|
|
With custom flags:
|
|
|
|
```bash
|
|
go run ./cmd/ds2api-tests \
|
|
--config config.json \
|
|
--admin-key admin \
|
|
--out artifacts/testsuite \
|
|
--timeout 120 \
|
|
--retries 2
|
|
```
|
|
|
|
The testsuite automatically performs:
|
|
|
|
- ✅ Preflight checks (syntax/build/unit tests)
|
|
- ✅ Isolated config copy startup (no mutation to your original `config.json`)
|
|
- ✅ Live scenario verification (OpenAI/Claude/Admin/concurrency/toolcall/streaming)
|
|
- ✅ Full request/response artifact logging for debugging
|
|
|
|
For detailed testsuite documentation, see [TESTING.md](TESTING.md). The fixed local PR gates are listed in [TESTING.md](TESTING.md#pr-门禁--pr-gates).
|