Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
22ce5a8
fix: update airgate-sdk checksum
May 17, 2026
d96be84
feat: extend account usage window structure with additional fields an…
May 18, 2026
bbfaa14
Merge remote-tracking branch 'upstream/master' into dev
May 18, 2026
b4a027a
chore: ignore local git attributes
May 18, 2026
a61f372
feat: 添加 useResetTick 钩子以处理重置秒数的更新逻辑
May 18, 2026
d48d5a1
Merge remote-tracking branch 'upstream/master' into dev
May 18, 2026
6debea3
Merge remote-tracking branch 'upstream/master' into dev
May 23, 2026
251d1d0
feat: 优化token计量输出格式
May 25, 2026
300a742
feat: 更新用量模型和计费逻辑
May 27, 2026
35770dd
feat: 更新用量模型,优化计费逻辑和元数据处理
May 27, 2026
817fabe
feat: 重构用量模型,简化计费逻辑和测试用例
May 27, 2026
30c97f2
feat: 独立发版
May 29, 2026
3c59d76
Merge remote-tracking branch 'upstream/master' into dev
May 29, 2026
e8adf5e
chore: refresh sdk v0.2.1 checksums
May 29, 2026
b31f213
chore: require go 1.26.3
May 29, 2026
ba6d6f5
ci: verify master push trigger
May 29, 2026
7886658
ci: recheck master push trigger
May 29, 2026
7ddd168
ci: refresh workflow trigger registration
May 29, 2026
6f4b726
ci: verify registered workflow push
May 29, 2026
a31844b
ci: update actions to node 24 runtime
May 29, 2026
daeff34
fix: 稳定性优化
May 29, 2026
daf2936
chore(deps): 更新 pnpm 版本至 11.5.0
May 31, 2026
6d43808
feat(gateway): 添加SSE流处理的上下文取消和空闲超时机制
Jun 3, 2026
942073e
chore(deps): update airgate-sdk to v0.2.2
Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
name: CI

on:
workflow_dispatch:
push:
branches: [master]
pull_request:
branches: [master]

env:
GOPRIVATE: github.com/DouDOU-start/airgate-sdk
GOPRIVATE: github.com/DevilGenius/airgate-sdk

jobs:
ci:
name: Lint & Test & Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
with:
version: 11.1.1
version: 11.5.0

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
Expand All @@ -29,7 +30,7 @@ jobs:
- name: Build frontend
run: cd web && pnpm install --frozen-lockfile && pnpm run build

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: write

env:
GOPRIVATE: github.com/DouDOU-start/airgate-sdk
GOPRIVATE: github.com/DevilGenius/airgate-sdk

jobs:
build:
Expand All @@ -27,13 +27,13 @@ jobs:
- goos: darwin
goarch: arm64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
with:
version: 11.1.1
version: 11.5.0

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
Expand All @@ -47,7 +47,7 @@ jobs:
rm -rf backend/internal/gateway/webdist
cp -r web/dist backend/internal/gateway/webdist

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
Expand All @@ -63,7 +63,7 @@ jobs:
echo "Injecting PluginVersion=${VERSION}"
mkdir -p bin
cd backend && go build -buildvcs=false -trimpath \
-ldflags "-X 'github.com/DouDOU-start/airgate-claude/backend/internal/gateway.PluginVersion=${VERSION}'" \
-ldflags "-X 'github.com/DevilGenius/airgate-claude/backend/internal/gateway.PluginVersion=${VERSION}'" \
-o ../bin/gateway-claude-${{ matrix.goos }}-${{ matrix.goarch }} .

- name: Generate SHA256
Expand All @@ -85,7 +85,7 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Download all artifacts
uses: actions/download-artifact@v4
Expand All @@ -97,7 +97,7 @@ jobs:
run: ls -la dist/

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ lint: ## 代码检查(需要安装 golangci-lint)
fmt: ## 格式化代码
@cd backend && \
if command -v goimports > /dev/null 2>&1; then \
goimports -w -local github.com/DouDOU-start .; \
goimports -w -local github.com/DevilGenius .; \
else \
$(GO) fmt ./...; \
fi
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

---

AirGate Anthropic 是 [airgate-core](https://github.com/DouDOU-start/airgate-core) 的 Claude 网关插件,基于 [airgate-sdk](https://github.com/DouDOU-start/airgate-sdk) 构建。它以 gRPC 子进程方式运行,负责将 Claude Messages API 请求转发到 Anthropic 上游,支持多种认证方式和连接池复用。
AirGate Anthropic 是 [airgate-core](https://github.com/DevilGenius/airgate-core) 的 Claude 网关插件,基于 [airgate-sdk](https://github.com/DevilGenius/airgate-sdk) 构建。它以 gRPC 子进程方式运行,负责将 Claude Messages API 请求转发到 Anthropic 上游,支持多种认证方式和连接池复用。

## 核心特性

Expand All @@ -20,7 +20,7 @@ AirGate Anthropic 是 [airgate-core](https://github.com/DouDOU-start/airgate-cor
- **进程内 Token 刷新锁** — 基于 sync.Mutex 的 per-account 并发保护,double-check 防止重复刷新,不可重试错误自动分类 + 60 秒冷却窗口
- **TLS 指纹伪装** — OAuth/session_key 账号使用 uTLS 模拟 Claude CLI 2.x 的 JA3 指纹,API Key 账号使用标准 TLS
- **连接池复用** — 标准 TLS 和 uTLS 指纹各自独立连接池,按 proxyURL 分桶缓存 Transport,避免每次请求创建新连接
- **精确成本计算** — 插件内按 Anthropic 定价填充 `UsageMetric` / `UsageCostDetail`,支持 standard/priority/flex 计费层级
- **精确成本计算** — 插件内按 Anthropic 定价填充标准 Usage 成本字段,Claude 专属维度写入 metadata
- **流式 Usage 提取** — SSE 流中实时提取 input_tokens、output_tokens、cache_read_input_tokens、reasoning_output_tokens,记录 FirstTokenMs
- **Console 批量导入** — 支持通过 Session Key 批量一键创建 OAuth/Setup Token 账号
- **完整前端 Widget** — 四种账号类型的表单面板,OAuth 授权引导、Session Key 一键获取、状态展示
Expand Down Expand Up @@ -137,7 +137,7 @@ AirGate Anthropic 是 [airgate-core](https://github.com/DouDOU-start/airgate-cor

| 层 | 技术 |
|---|---|
| 后端 | Go 1.25 · gRPC · gjson · uTLS(JA3 指纹) |
| 后端 | Go 1.26.3 · gRPC · gjson · uTLS(JA3 指纹) |
| 前端 | React 19 · Vite · TypeScript(账号表单 Widget) |
| 插件协议 | hashicorp/go-plugin (gRPC) |
| 上游协议 | Anthropic Messages API · Anthropic OAuth |
Expand All @@ -156,12 +156,12 @@ AirGate Anthropic 是 [airgate-core](https://github.com/DouDOU-start/airgate-cor

### 方式 2:源码运行(开发)

需要 Go 1.25+、Node 22+,以及兄弟目录 `airgate-sdk` 与 `airgate-core`:
需要 Go 1.26.3+、Node 22+,以及兄弟目录 `airgate-sdk` 与 `airgate-core`:

```bash
git clone https://github.com/DouDOU-start/airgate-sdk.git
git clone https://github.com/DouDOU-start/airgate-core.git
git clone https://github.com/DouDOU-start/airgate-claude.git
git clone https://github.com/DevilGenius/airgate-sdk.git
git clone https://github.com/DevilGenius/airgate-core.git
git clone https://github.com/DevilGenius/airgate-claude.git
cd airgate-claude
```

Expand Down Expand Up @@ -225,6 +225,6 @@ airgate-claude/

## 相关项目

- [airgate-core](https://github.com/DouDOU-start/airgate-core) — 核心网关(账号调度、计费、管理后台)
- [airgate-sdk](https://github.com/DouDOU-start/airgate-sdk) — 插件 SDK
- [airgate-openai](https://github.com/DouDOU-start/airgate-openai) — OpenAI 网关插件(参考实现)
- [airgate-core](https://github.com/DevilGenius/airgate-core) — 核心网关(账号调度、计费、管理后台)
- [airgate-sdk](https://github.com/DevilGenius/airgate-sdk) — 插件 SDK
- [airgate-openai](https://github.com/DevilGenius/airgate-openai) — OpenAI 网关插件(参考实现)
4 changes: 2 additions & 2 deletions backend/cmd/devserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"log"
"net/http"

"github.com/DouDOU-start/airgate-sdk/devkit/devserver"
"github.com/DevilGenius/airgate-sdk/devkit/devserver"

"github.com/DouDOU-start/airgate-claude/backend/internal/gateway"
"github.com/DevilGenius/airgate-claude/backend/internal/gateway"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion backend/cmd/fp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (

utls "github.com/refraction-networking/utls"

"github.com/DouDOU-start/airgate-claude/backend/internal/gateway"
"github.com/DevilGenius/airgate-claude/backend/internal/gateway"
)

// snapshot 即将写盘的指纹快照
Expand Down
4 changes: 2 additions & 2 deletions backend/cmd/genmanifest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

"gopkg.in/yaml.v3"

"github.com/DouDOU-start/airgate-claude/backend/internal/gateway"
sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
"github.com/DevilGenius/airgate-claude/backend/internal/gateway"
sdk "github.com/DevilGenius/airgate-sdk/sdkgo"
)

const generatedComment = "# 本文件由 backend/cmd/genmanifest 自动生成,请勿手工修改。\n\n"
Expand Down
6 changes: 3 additions & 3 deletions backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module github.com/DouDOU-start/airgate-claude/backend
module github.com/DevilGenius/airgate-claude/backend

go 1.25.7
go 1.26.3

require (
github.com/DouDOU-start/airgate-sdk v0.2.1
github.com/DevilGenius/airgate-sdk v0.2.2
github.com/imroc/req/v3 v3.57.0
github.com/refraction-networking/utls v1.8.2
github.com/tidwall/gjson v1.18.0
Expand Down
4 changes: 2 additions & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/DouDOU-start/airgate-sdk v0.2.1 h1:MylrFMifIJy5mT1wSFdXcSvAXjIcB6itp4dw8pP4pes=
github.com/DouDOU-start/airgate-sdk v0.2.1/go.mod h1:784vC4lIfCnUdOroWuvn55Dv/b9TucVMpFwuu40gc0Q=
github.com/DevilGenius/airgate-sdk v0.2.2 h1:sw9YbbQGpYQwUfm7BmOpcArqSnasEeAqUNO/TEbE0+0=
github.com/DevilGenius/airgate-sdk v0.2.2/go.mod h1:EnjPPVBiONFv0gcQqiBPqs86HRhcVKICdFaB03yJcI8=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
Expand Down
9 changes: 8 additions & 1 deletion backend/internal/gateway/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ package gateway

import (
"encoding/json"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"

sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
sdk "github.com/DevilGenius/airgate-sdk/sdkgo"
"github.com/tidwall/gjson"
)

const maxErrorResponseBodyBytes = 1 << 20

// classifyHTTPFailure 根据 HTTP 状态码 + 错误文本归一化为 OutcomeKind。
//
// 429 → AccountRateLimited
Expand Down Expand Up @@ -95,6 +98,10 @@ func truncate(s string, maxLen int) string {
return s[:maxLen] + "..."
}

func readLimitedErrorBody(r io.Reader) ([]byte, error) {
return io.ReadAll(io.LimitReader(r, maxErrorResponseBodyBytes+1))
}

func jsonError(msg string) []byte {
b, _ := json.Marshal(map[string]string{"error": msg})
return b
Expand Down
7 changes: 3 additions & 4 deletions backend/internal/gateway/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
Expand All @@ -14,7 +13,7 @@ import (
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"

sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
sdk "github.com/DevilGenius/airgate-sdk/sdkgo"
)

const (
Expand Down Expand Up @@ -402,7 +401,7 @@ func preprocessOAuthBody(body []byte, account *sdk.Account) []byte {
}

// 2. 注入 metadata.user_id(如果缺失)
// CLI >= 2.1.78 使用 JSON 格式;使用 sticky session 30 min 内复用同一 session_id
// CLI >= 2.1.78 使用 JSON 格式;使用 sticky session 3600s 内复用同一 session_id
if !gjson.GetBytes(body, "metadata.user_id").Exists() {
accountUUID := account.Credentials["account_uuid"]
if accountUUID == "" {
Expand Down Expand Up @@ -493,7 +492,7 @@ func normalizeRequestBody(body []byte) []byte {
// unwritten 让 Core canFailover 不被短路;Core 会 failover + 触发状态机
// - UpstreamTransient(5xx):不写 w,让 Core failover
func handleErrorResponse(resp *http.Response, _ http.ResponseWriter, start time.Time) sdk.ForwardOutcome {
respBody, _ := io.ReadAll(resp.Body)
respBody, _ := readLimitedErrorBody(resp.Body)
msg := extractErrorMessage(respBody)
if msg == "" {
msg = truncate(string(respBody), 200)
Expand Down
Loading