Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/publish-theme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:

jobs:
publish:
name: 发布 @doudou-start/airgate-theme
name: 发布 @devilgenius/airgate-theme
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand Down Expand Up @@ -44,7 +44,7 @@ jobs:
run: |
mkdir -p ../dist
pnpm pack --pack-destination ../dist
mv ../dist/doudou-start-airgate-theme-*.tgz "../dist/airgate-theme-${GITHUB_REF_NAME}.tgz"
mv ../dist/devilgenius-airgate-theme-*.tgz "../dist/airgate-theme-${GITHUB_REF_NAME}.tgz"

- name: 创建/更新 GitHub Release 并上传资产
if: github.event_name == 'push'
Expand Down
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ formatters:

settings:
goimports:
local-prefixes: github.com/DouDOU-start
local-prefixes: github.com/DevilGenius

issues:
max-issues-per-linter: 0
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 DouDOU-start
Copyright (c) 2026 DevilGenius

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ lint: ## 代码检查(需要安装 golangci-lint)

fmt: ## 格式化代码
@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
62 changes: 30 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
<p><strong>AirGate 插件生态的公共契约与开发工具包</strong></p>

<p>
<a href="https://github.com/DouDOU-start/airgate-sdk/releases"><img src="https://img.shields.io/github/v/release/DouDOU-start/airgate-sdk?style=flat-square" alt="发布版本" /></a>
<a href="https://pkg.go.dev/github.com/DouDOU-start/airgate-sdk"><img src="https://img.shields.io/badge/pkg.go.dev-reference-007d9c?style=flat-square&logo=go" alt="Go 文档" /></a>
<a href="https://github.com/DouDOU-start/airgate-sdk/blob/master/LICENSE"><img src="https://img.shields.io/github/license/DouDOU-start/airgate-sdk?style=flat-square" alt="许可证" /></a>
<a href="https://github.com/DevilGenius/airgate-sdk/releases"><img src="https://img.shields.io/github/v/release/DevilGenius/airgate-sdk?style=flat-square" alt="发布版本" /></a>
<a href="https://pkg.go.dev/github.com/DevilGenius/airgate-sdk"><img src="https://img.shields.io/badge/pkg.go.dev-reference-007d9c?style=flat-square&logo=go" alt="Go 文档" /></a>
<a href="https://github.com/DevilGenius/airgate-sdk/blob/master/LICENSE"><img src="https://img.shields.io/github/license/DevilGenius/airgate-sdk?style=flat-square" alt="许可证" /></a>
<img src="https://img.shields.io/badge/Go-1.25-00ADD8?style=flat-square&logo=go" alt="Go 版本" />
<img src="https://img.shields.io/badge/gRPC-go--plugin-4c1?style=flat-square" alt="gRPC 插件协议" />
</p>
</div>

---

AirGate SDK 是 [AirGate Core](https://github.com/DouDOU-start/airgate-core) 和插件之间的公共契约。它定义插件要实现什么接口、Core 如何启动插件进程、双方如何通过 gRPC 通信,以及插件前端如何复用统一主题和公共组件。
AirGate SDK 是 [AirGate Core](https://github.com/DevilGenius/airgate-core) 和插件之间的公共契约。它定义插件要实现什么接口、Core 如何启动插件进程、双方如何通过 gRPC 通信,以及插件前端如何复用统一主题和公共组件。

简单理解:

Expand All @@ -25,15 +25,15 @@ AirGate SDK 是 [AirGate Core](https://github.com/DouDOU-start/airgate-core) 和
## 安装

```bash
go get github.com/DouDOU-start/airgate-sdk@latest
go get github.com/DevilGenius/airgate-sdk@latest
```

Go 插件通常只需要两个包:

```go
import (
sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
runtime "github.com/DouDOU-start/airgate-sdk/runtimego/grpc"
sdk "github.com/DevilGenius/airgate-sdk/sdkgo"
runtime "github.com/DevilGenius/airgate-sdk/runtimego/grpc"
)
```

Expand All @@ -42,7 +42,7 @@ import (
```json
{
"dependencies": {
"@doudou-start/airgate-theme": "^0.2.1"
"@devilgenius/airgate-theme": "^0.2.1"
}
}
```
Expand All @@ -52,7 +52,7 @@ import (
```json
{
"dependencies": {
"@doudou-start/airgate-theme": "file:../../airgate-sdk/theme"
"@devilgenius/airgate-theme": "file:../../airgate-sdk/theme"
}
}
```
Expand All @@ -65,7 +65,7 @@ import (
| `protocol/proto/` | `airgate.plugin.v1` protobuf 协议和生成代码 | Core / runtime |
| `runtimego/grpc/` | hashicorp/go-plugin、gRPC bridge、proto 转换、Core 反向调用通道 | 插件入口 / Core 加载器 |
| `devkit/devserver/` | 本地开发服务器,无需启动完整 Core 即可调试插件 | 插件作者 |
| `theme/` | `@doudou-start/airgate-theme`:主题 token、样式隔离、Tailwind helper、公共组件 | 插件前端 |
| `theme/` | `@devilgenius/airgate-theme`:主题 token、样式隔离、Tailwind helper、公共组件 | 插件前端 |
| `docs/` | 设计边界和前端样式规范 | 维护者 |

普通插件业务代码不直接依赖 `protocol/proto`。
Expand Down Expand Up @@ -96,8 +96,8 @@ Gateway 插件的核心工作只有三件事:
package main

import (
sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
runtime "github.com/DouDOU-start/airgate-sdk/runtimego/grpc"
sdk "github.com/DevilGenius/airgate-sdk/sdkgo"
runtime "github.com/DevilGenius/airgate-sdk/runtimego/grpc"
)

type Gateway struct{}
Expand Down Expand Up @@ -169,17 +169,15 @@ func (g *Gateway) Forward(ctx context.Context, req *sdk.ForwardRequest) (sdk.For
AccountCost: 0.000035,
Currency: "USD",
Summary: "输入 10 token,输出 5 token",
Attributes: []sdk.UsageAttribute{
{Key: "reasoning_effort", Label: "思考层级", Kind: "reasoning", Value: "high"},
{Key: "resolution", Label: "分辨率", Kind: "resolution", Value: "1024x1024"},
},
Metrics: []sdk.UsageMetric{
{Key: "input_tokens", Label: "输入 token", Kind: "token", Unit: "token", Value: 10},
{Key: "output_tokens", Label: "输出 token", Kind: "token", Unit: "token", Value: 5},
},
CostDetails: []sdk.UsageCostDetail{
{Key: "input", Label: "输入费用", AccountCost: 0.00001, Currency: "USD"},
{Key: "output", Label: "输出费用", AccountCost: 0.000025, Currency: "USD"},
InputTokens: 10,
OutputTokens: 5,
InputPrice: 1,
OutputPrice: 5,
InputCost: 0.00001,
OutputCost: 0.000025,
ReasoningEffort: "high",
Metadata: map[string]string{
"openai.image.size": "1024x1024",
},
},
}, nil
Expand All @@ -199,7 +197,7 @@ func (g *Gateway) Forward(ctx context.Context, req *sdk.ForwardRequest) (sdk.For
```go
package main

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

func main() {
if err := devserver.Run(devserver.Config{Plugin: &Gateway{}}); err != nil {
Expand Down Expand Up @@ -229,7 +227,7 @@ Core 启动插件子进程

- Core 默认只管理插件生命周期、页面入口、静态资源、schema、健康检查和 API 代理。
- Gateway 插件是主请求链路,Core 会主动调用 `Forward`、账号验证和 WebSocket 处理。
- SDK 不内置平台计费规则;网关插件计算标准账号成本并填入 `Usage.AccountCost`、`Usage.Attributes`、`Usage.Metrics`、`Usage.CostDetails`。
- SDK 不内置平台计费规则;网关插件计算标准 token、单价、成本字段并填入 `Usage`,插件专属展示数据放入 `Usage.Metadata`。
- Core 统一入库后,根据用户、分组、模型等倍率写入 `UserCost` / `BillingMultiplier`;倍率规则不进入 SDK。
- 账号管理和使用记录 UI 由插件提供静态资源,Core 只加载页面、插槽和插件 API 代理,不解释平台字段。
- Middleware、扩展路由、后台任务、事件订阅、异步任务处理都属于插件显式暴露的能力;没有暴露就不会被 Core 调度。
Expand Down Expand Up @@ -331,14 +329,14 @@ func (p *Plugin) Init(ctx sdk.PluginContext) error {

## 前端插件 SDK

`theme/` 发布为 npm 公共包 `@doudou-start/airgate-theme`,用于插件前端复用 AirGate 的主题和公共组件。
`theme/` 发布为 npm 公共包 `@devilgenius/airgate-theme`,用于插件前端复用 AirGate 的主题和公共组件。

| 入口 | 用途 |
|---|---|
| `@doudou-start/airgate-theme` | token、CSS 工具、Tailwind bridge、插件前端类型和公共组件统一出口 |
| `@doudou-start/airgate-theme/plugin` | 插件样式隔离、主题同步、Tailwind helper、公共 UI 组件 |
| `@doudou-start/airgate-theme/css` | CSS 变量生成和运行时主题注入 |
| `@doudou-start/airgate-theme/tailwind` | Tailwind 主题桥接 |
| `@devilgenius/airgate-theme` | token、CSS 工具、Tailwind bridge、插件前端类型和公共组件统一出口 |
| `@devilgenius/airgate-theme/plugin` | 插件样式隔离、主题同步、Tailwind helper、公共 UI 组件 |
| `@devilgenius/airgate-theme/css` | CSS 变量生成和运行时主题注入 |
| `@devilgenius/airgate-theme/tailwind` | Tailwind 主题桥接 |

推荐插件前端使用:

Expand All @@ -350,7 +348,7 @@ import {
createPluginTailwindConfig,
ensurePluginStyleFoundation,
useScopedPluginTheme,
} from "@doudou-start/airgate-theme/plugin";
} from "@devilgenius/airgate-theme/plugin";
```

完整样式规则见 [插件前端样式规范](docs/plugin-style-guide.md)。
Expand All @@ -374,7 +372,7 @@ import {
make ci # 运行 Go、proto、前端和主题漂移检查
make proto # 重新生成 protocol/proto
make theme # 重新生成 DevServer 主题 CSS
cd theme && pnpm build # 构建 @doudou-start/airgate-theme
cd theme && pnpm build # 构建 @devilgenius/airgate-theme
```

## License
Expand Down
2 changes: 1 addition & 1 deletion devkit/devserver/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"
"time"

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

// devPluginContext 开发模式的 PluginContext 实现
Expand Down
2 changes: 1 addition & 1 deletion devkit/devserver/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

"github.com/gorilla/websocket"

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

// ProxyHandler 将请求代理给插件
Expand Down
2 changes: 1 addition & 1 deletion devkit/devserver/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"path/filepath"
"testing"

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

type proxyTestGateway struct {
Expand Down
6 changes: 5 additions & 1 deletion devkit/devserver/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"sync"
"time"

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

// SchedulePolicy 调度策略
Expand Down Expand Up @@ -123,6 +123,10 @@ func (s *Scheduler) ReportResult(accountID int64, outcome sdk.ForwardOutcome) {
s.cooldown[accountID] = time.Now().Add(5 * time.Minute)
log.Printf("[调度] 账号 %d 凭证失效,冷却 5 分钟", accountID)

case sdk.OutcomeAccountUnavailable:
s.cooldown[accountID] = time.Now().Add(60 * time.Second)
log.Printf("[调度] 账号 %d 暂时 403,冷却 60 秒", accountID)

default:
delete(s.cooldown, accountID)
}
Expand Down
54 changes: 54 additions & 0 deletions devkit/devserver/scheduler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package devserver

import (
"path/filepath"
"strconv"
"testing"

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

func TestSchedulerReportResultCoolsDownAccountUnavailable(t *testing.T) {
t.Parallel()

store := NewAccountStore(filepath.Join(t.TempDir(), "accounts.json"))
account := store.Create(DevAccount{Name: "oauth", AccountType: "oauth"})
accountKey := strconv.FormatInt(account.ID, 10)
s := NewScheduler(store, ScheduleWeightedRR)
s.ReportResult(account.ID, sdk.ForwardOutcome{Kind: sdk.OutcomeAccountUnavailable})

status := s.Status()
cooldowns, ok := status["cooldowns"].(map[string]string)
if !ok {
t.Fatalf("cooldowns has type %T, want map[string]string", status["cooldowns"])
}
if cooldowns[accountKey] == "" {
t.Fatalf("expected account %s to be in cooldown, got %v", accountKey, cooldowns)
}

s.ReportResult(account.ID, sdk.ForwardOutcome{Kind: sdk.OutcomeSuccess})
status = s.Status()
cooldowns = status["cooldowns"].(map[string]string)
if cooldowns[accountKey] != "" {
t.Fatalf("expected success to clear cooldown, got %v", cooldowns)
}
}

func TestSchedulerReportResultCoolsDownAccountUnavailableForAPIKey(t *testing.T) {
t.Parallel()

store := NewAccountStore(filepath.Join(t.TempDir(), "accounts.json"))
account := store.Create(DevAccount{Name: "api key", AccountType: "apikey"})
accountKey := strconv.FormatInt(account.ID, 10)
s := NewScheduler(store, ScheduleWeightedRR)
s.ReportResult(account.ID, sdk.ForwardOutcome{Kind: sdk.OutcomeAccountUnavailable})

status := s.Status()
cooldowns, ok := status["cooldowns"].(map[string]string)
if !ok {
t.Fatalf("cooldowns has type %T, want map[string]string", status["cooldowns"])
}
if cooldowns[accountKey] == "" {
t.Fatalf("expected api key account to enter cooldown, got %v", cooldowns)
}
}
2 changes: 1 addition & 1 deletion devkit/devserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"strings"
"time"

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

//go:embed static
Expand Down
2 changes: 1 addition & 1 deletion devkit/devserver/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/http/httptest"
"testing"

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

func TestRoutePrefixesDeduplicates(t *testing.T) {
Expand Down
Loading
Loading