sdk

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2026 License: MIT Imports: 14 Imported by: 0

README

AirGate SDK

AirGate 插件生态的接口契约与开发套件

release godoc license go grpc


AirGate SDK 是 airgate-core 插件生态的协议层,定义了插件与 core 之间的全部边界:接口契约、共享类型、gRPC 桥接、本地开发服务器和统一前端主题。

  • Core = 用户、账号、调度、计费、限流、订阅、管理后台 —— 平台无关的通用能力
  • SDK(本仓库)= 插件如何被装载、被调度、被回调的全部规则
  • Plugin = 依赖 SDK 实现接口的独立 Go 进程,提供具体平台的能力

同一份契约在 core 和插件两端使用,保证升级不会偏离。底层走 hashicorp/go-plugin 的 gRPC 模式,每个插件运行在自己的子进程里,崩溃不影响 core 与其他插件。

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

✨ 核心特性

  • 🔌 三类插件模型GatewayPlugin(upstream 适配)/ ExtensionPlugin(路由 + 后台任务)/ MiddlewarePlugin(forward 路径拦截层,旁路观察 / 审计 / 脱敏),详见 ADR-0001
  • 🛂 能力模型 — 插件显式声明所需的 HostService / Middleware capability,Core 侧 gRPC interceptor 按"插件类型 → 允许集合"做最小权限校验(SDK 0.3.0 起强制)
  • 🔁 反向通道 HostService — 插件通过 HostAware 可选接口拿到 Host,直接回调 core 能力(选号 / 探测 / 列分组),走 hashicorp/go-plugin GRPCBroker 子进程隧道,无需 admin HTTP + API key
  • 🧩 最小契约 — 插件只需声明账号格式 / 模型 / 路由,并实现 Forward,core 自动接管账号管理、调度、计费、限流
  • 🎨 前端集成 — 独立页面 (FrontendPages) + 组件嵌入 (FrontendWidgets),通过 WebAssetsProvider 统一打包到二进制
  • 🎭 统一主题 — 内置 @airgate/theme 包提供共享 token、亮暗切换、Tailwind 桥接和插件作用域隔离
  • 🛠 本地开发服务器devserver 包模拟 core 行为,插件无需部署 core 即可端到端测试账号、HTTP/SSE 转发、WebSocket
  • 📦 进程隔离 — 基于 hashicorp/go-plugin gRPC 模式,崩溃隔离、独立热更、独立发版

🧩 三类插件

类型 接口 定位 参考实现
网关插件 GatewayPlugin AI API 代理。声明模型/路由/账号格式 + 实现 Forward,core 自动调度 + 计费 + 限流 airgate-openai
扩展插件 ExtensionPlugin 一切非网关场景。提供路由注册、数据库迁移、后台任务三大基础能力 airgate-epay · airgate-health
中间件插件 MiddlewarePlugin Forward 路径的旁路拦截层:请求/响应记录、审计、脱敏、流量采样、合规标签注入 (示例计划:airgate-audit

三种角色的边界是互斥的:gateway 替代 upstream;extension 并行 扩展(独立路由表 / 定时任务);middleware 拦截 每次 forward 的前后事件,永远不能 block 生产流量(详见 Decision 2 的失败语义)。

网关插件 GatewayPlugin

方法 职责
Platform() 返回业务平台键(如 "openai"
Models() 声明支持的模型 + 单价(core 用于计费)
Routes() 声明 API 端点(如 POST /v1/chat/completions),core 自动注册
Forward(ctx, req) 拿到 core 调度好的账号,转发请求并返回 token 用量 + 账号状态反馈
ValidateAccount(ctx, cred) 添加/导入账号时由 core 调用验证凭证
QueryQuota(ctx, cred) core 定时巡检账号额度
HandleWebSocket(ctx, conn) 处理 WebSocket 双向通信(如 Realtime API)

扩展插件 ExtensionPlugin

能力 方法 说明
自定义路由 RegisterRoutes(r) 注册任意 HTTP API
数据库迁移 Migrate() 创建插件专属表(通过 Config 获取 DSN 自行建连)
后台任务 BackgroundTasks() 声明定时任务,core 负责调度

中间件插件 MiddlewarePlugin

方法 职责
OnForwardBegin(ctx, req) 选完账号 / 还没调 upstream 之前触发。返回 Decision 可放行 / 拒绝 / 追加 header
OnForwardEnd(ctx, evt) upstream 返回之后 / 写 usage_log 之前触发。拿到完整的请求 + 响应元数据

关键设计约定(详见 ADR-0001 Decision 2/3):

  • 失败即跳过OnForwardBegin / OnForwardEnd 返回 error 只 log warn,不阻塞主流程。唯一例外是 OnForwardBegin 显式返回 DecisionDeny
  • LIFO 链顺序:多个 middleware 按 PluginInfo.Priority 升序调 Begin、降序调 End(像 middleware stack 展开)
  • Payload 两段式:默认只传元数据(request_id / user_id / group_id / account_id / platform / model / 用量);声明 CapabilityMiddlewareReadBody 的插件额外收到 request_body / response_body + headers
  • 流式响应的 body 摘要:End 阶段流式响应的 ResponseBody 只给首次非空 chunk 拼装的摘要,完整流式内容留给未来的 OnStreamChunk(ADR-0002)
  • 跨 hook 上下文Metadata 字段是所有 middleware 共享的 KV bag,从 Begin 贯穿到 End

可选能力

所有插件类型都可额外实现以下接口,core 通过类型断言自动检测:

接口 用途
WebAssetsProvider 提供前端静态资源(独立页面 / 嵌入组件)
ConfigWatcher 配置热更新
HealthChecker 自定义健康检查逻辑
RequestHandler 处理 /api/v1/admin/plugins/:name/rpc/* 透传请求
HostAware 通过 ctx.(sdk.HostAware).Host() 拿到反向调用 core 的 Host 客户端

🛂 能力模型(Capability)

SDKVersion = "0.3.0" 起,插件调用 HostService 或使用 middleware 特殊 payload 必须显式声明 capability,否则 Core 的 gRPC interceptor 会返回 PermissionDenied

func (p *MyExtension) Info() sdk.PluginInfo {
    return sdk.PluginInfo{
        ID:   "ext-monitor",
        Type: sdk.PluginTypeExtension,
        Capabilities: []string{
            sdk.CapabilityHostListGroups,
            sdk.CapabilityHostProbeForward,
            sdk.CapabilityHostReportAccountResult,
        },
        // ...
    }
}

当前 capability 清单(Core 按"插件类型 → 允许集合"做交集后得到有效权限):

Capability 用途 允许的插件类型
host.list_groups Host.ListGroups() 列出分组 extension, middleware
host.select_account Host.SelectAccount() 走真实调度选号 extension
host.probe_forward Host.ProbeForward() 黑盒探测 extension(probe 子类)
host.report_account_result Host.ReportAccountResult() 反馈状态机 extension(probe 子类)
middleware.read_body middleware 接收 request_body / response_body middleware

向后兼容:SDK <= 0.2.x 的旧插件不声明 Capabilities 仍然可以跑(通过 sdk_version 字段豁免),但管理后台会显示"兼容模式"警告。>= 0.3.x 起强制校验。

命名规范<domain>.<action>。新增 capability 必须在 ADR 里说明语义 / owner / 允许的插件类型。

🔁 反向通道 HostService

过去插件要回调 core(列分组、选号、探测)只能走 admin HTTP API + admin key —— 管理员要手工生成 key、插件要拼 URL 签 Bearer、同机两个进程也被迫走完整 HTTP+JSON 栈。HostService 通过 hashicorp/go-plugin 的 GRPCBroker 为每个插件子进程架起一条反向 gRPC stream,子进程隧道天然互信。

type MyExtension struct {
    host sdk.Host
}

func (p *MyExtension) Init(ctx sdk.PluginContext) error {
    // HostAware 是可选接口:旧版 Core / devserver / 测试 mock 都可以不实现
    if h, ok := ctx.(sdk.HostAware); ok {
        p.host = h.Host() // 仍可能为 nil,调用方需 nil-check
    }
    return nil
}

func (p *MyExtension) probe(ctx context.Context) {
    if p.host == nil { return }

    groups, err := p.host.ListGroups(ctx)
    if err != nil { /* ... */ }

    for _, g := range groups {
        result, _ := p.host.ProbeForward(ctx, sdk.HostProbeForwardRequest{GroupID: g.ID})
        p.host.ReportAccountResult(ctx, result.AccountID, result.Success, result.ErrorMsg)
    }
}

当前 v1 暴露的 4 个 RPC(克制暴露面,等真实需求再加):

RPC 语义
SelectAccount 走和真实用户请求完全相同的调度路径选号
ProbeForward 黑盒探测 chat completion:跳过 usage_log / 余额扣款,但仍触发 ReportResult
ListGroups 列出所有分组(id / name / platform / 是否独占 / 倍率)
ReportAccountResult 把账号调用结果反馈给 scheduler 的失败计数器 / 状态机

设计原则(详见 ADR-0001 §2):

  • 只加字段不删字段(protobuf 天然向前兼容)
  • 加新 RPC 用新 rpc name,不 hijack 旧的
  • 新能力必须伴随新 capability flag,旧插件不声明就不启用
  • Core 是 trust root:HostService 所有输入做参数校验,credentials / password_hash / admin key 等敏感字段永远不通过 RPC 流向插件

🛠 技术栈

技术
语言 Go 1.25
插件协议 hashicorp/go-plugin (gRPC + protobuf)
序列化 protobuf v3
前端主题 TypeScript · CSS Variables · Tailwind 桥接
开发服务器 net/http + 内嵌 HTML 管理 UI

🚀 快速开始

1. 编写一个最小网关插件

package main

import (
    "context"
    sdk "github.com/DouDOU-start/airgate-sdk"
    "github.com/DouDOU-start/airgate-sdk/grpc"
)

type MyGateway struct{}

func (g *MyGateway) Info() sdk.PluginInfo {
    return sdk.PluginInfo{
        ID:      "gateway-myplatform",
        Name:    "My Platform 网关",
        Version: "1.0.0",
        Type:    sdk.PluginTypeGateway,
        AccountTypes: []sdk.AccountType{{
            Key:   "apikey",
            Label: "API Key",
            Fields: []sdk.CredentialField{
                {Key: "api_key", Label: "API Key", Type: "password", Required: true},
            },
        }},
    }
}

func (g *MyGateway) Init(ctx sdk.PluginContext) error { return nil }
func (g *MyGateway) Start(_ context.Context) error    { return nil }
func (g *MyGateway) Stop(_ context.Context) error     { return nil }

func (g *MyGateway) Platform() string { return "myplatform" }

func (g *MyGateway) Models() []sdk.ModelInfo {
    return []sdk.ModelInfo{{
        ID: "my-model-v1", Name: "My Model V1",
        ContextWindow: 128000, MaxOutputTokens: 16384,
        InputPrice: 1.0, OutputPrice: 3.0,
    }}
}

func (g *MyGateway) Routes() []sdk.RouteDefinition {
    return []sdk.RouteDefinition{
        {Method: "POST", Path: "/v1/chat/completions"},
    }
}

func (g *MyGateway) Forward(ctx context.Context, req *sdk.ForwardRequest) (*sdk.ForwardResult, error) {
    // req.Account — Core 已调度好的账号
    // req.Body / req.Headers — 原始请求
    // req.Writer — 流式写入 SSE
    return &sdk.ForwardResult{
        StatusCode:   200,
        InputTokens:  100, OutputTokens: 50,
        InputCost: 0.0001, OutputCost: 0.00015,
        Model: "my-model-v1",
    }, nil
}

func (g *MyGateway) ValidateAccount(ctx context.Context, cred map[string]string) error { return nil }
func (g *MyGateway) QueryQuota(ctx context.Context, cred map[string]string) (*sdk.QuotaInfo, error) {
    return nil, sdk.ErrNotSupported
}
func (g *MyGateway) HandleWebSocket(ctx context.Context, conn sdk.WebSocketConn) (*sdk.ForwardResult, error) {
    return nil, sdk.ErrNotSupported
}

func main() { grpc.Serve(&MyGateway{}) }

2. 本地开发验证(无需部署 core)

package main

import (
    "log"
    "github.com/DouDOU-start/airgate-sdk/devserver"
)

func main() {
    if err := devserver.Run(devserver.Config{Plugin: &MyGateway{}}); err != nil {
        log.Fatal(err)
    }
}

启动后访问 http://localhost:18080,即可看到管理 UI,支持账号 CRUD、HTTP/SSE 代理转发、WebSocket 升级、插件前端资源服务。命令行参数 -addr / -data / -log 可覆盖默认配置。

3. 构建与发布

go build -o my-plugin .
# 打包:my-plugin.tar.gz 包含二进制 + plugin.yaml

完整范例(含 Makefile / release workflow / 前端嵌入)见 airgate-openai

🏗 架构

┌─────────────────────── Core ────────────────────────┐
│  账号管理 / 调度 / 计费 / 限流 / 订阅 / 管理后台      │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────┐ │
│  │ PluginService│  │GatewayService│  │ ExtService │ │   Core → Plugin
│  │ Middleware-  │  │              │  │            │ │
│  │ Service      │  │              │  │            │ │
│  └──────────────┘  └──────────────┘  └────────────┘ │
│         ▲                                            │
│         │ HostService(反向 stream,经 GRPCBroker)  │   Plugin → Core
└─────────┼────────────────────────────────────────────┘
          │
  ┌───────┴────────────────────────────────────────┐
  │        Plugin subprocess (hashicorp/go-plugin)  │
  │                                                 │
  │  GatewayPlugin / ExtensionPlugin / MiddlewarePl │
  │  Capabilities: [host.list_groups, ...]          │
  └─────────────────────────────────────────────────┘

请求生命周期(含 middleware chain)

用户请求
  │
  ▼
Core 鉴权 + 限流 + 选账号
  │
  ▼
Middleware.OnForwardBegin  (按 Priority 升序依次调)
  │ ├─ Decision=Allow  → 继续
  │ ├─ Decision=Mutate → 追加 header 继续
  │ └─ Decision=Deny   → 直接返回给用户
  ▼
Gateway.Forward()  ──►  上游 AI API
  │
  ▼
Middleware.OnForwardEnd    (按 Priority 降序依次调,LIFO)
  │  拿到完整 metadata + 按需 body
  ▼
Core 写 usage_log / 计费 / 账号状态处置

反向调用(插件 → Core)

Plugin.probe()
  └─ ctx.(HostAware).Host().ListGroups(ctx)
        │
        │  gRPC stream  (GRPCBroker 子进程隧道,无需 admin key)
        ▼
  Core: HostService interceptor
        │
        │  检查 plugin capability set
        │  未声明 → PermissionDenied
        ▼
  Core: groupRepo.List()

账号模型:Core 用一张 accounts 表存所有平台账号,靠 platform + type 区分。SDK Account 是 core 传给插件的最小视图,只包含 ID / Name / Platform / Type / Credentials / ProxyURL —— 调度和计费参数全部留在 core。

🎨 前端集成

插件的前端能力分两种,通过同一套 WebAssetsProvider 资源机制提供:

模式 说明 谁控制布局
独立页面 FrontendPages 插件拥有完整页面,core 分配路由和导航入口 插件
组件嵌入 FrontendWidgets 插件提供组件片段,嵌入 core 已有页面的指定 Slot Core
// 独立页面
FrontendPages: []sdk.FrontendPage{
    {Path: "/dashboard", Title: "仪表盘", Icon: "chart"},
},

// 嵌入到 core 账号管理页的指定插槽
FrontendWidgets: []sdk.FrontendWidget{
    {Slot: sdk.SlotAccountForm,   EntryFile: "widgets/account-form.js"},
    {Slot: sdk.SlotAccountDetail, EntryFile: "widgets/account-detail.js"},
},

宿主边界:Core 拥有路由、导航、弹窗骨架、Slot 位置和生命周期;Widget 只渲染 slot 内部内容,不假设控制整个页面。详见 docs/plugin-style-guide.md

🎭 主题系统 @airgate/theme

SDK 在 frontend/ 目录提供统一的前端主题包,作为 core 和所有插件的颜色/样式唯一来源,支持亮暗切换。

// 插件 package.json
{ "dependencies": { "@airgate/theme": "file:../../airgate-sdk/frontend" } }
import { cssVar, themeStyle } from '@airgate/theme';

color: cssVar('text')                  // → 'var(--ag-text, #e8ecf4)'
backgroundColor: cssVar('bgSurface')   // → 'var(--ag-bg-surface, #1c2237)'

@airgate/theme/plugin 子包额外提供:ensurePluginStyleFoundation() 主题注入、useScopedPluginTheme() 亮暗跟随、createPluginTailwindConfig() Tailwind 桥接,以及 Field / TextInput / Button 等基础 primitives。

规范 说明
唯一 token 源 颜色/阴影/圆角/字体统一来自 @airgate/theme
作用域隔离 插件根节点必须用自己的 scope selector,Tailwind 配 important
不覆盖宿主骨架 插件不得重写 core Modal / Page / Sidebar 全局样式
亮暗天然可用 不写死颜色,所有前景/背景/边框走 token

📁 项目结构

airgate-sdk/
├── plugin.go              # Plugin 基础接口 + PluginInfo + Capability 常量 + 可选接口
├── gateway.go             # GatewayPlugin 接口
├── extension.go           # ExtensionPlugin 接口
├── middleware.go          # MiddlewarePlugin 接口 + MiddlewareRequest/Event/Decision
├── host.go                # HostService 客户端接口(反向通道)+ HostAware 可选接口
├── models.go              # 共享类型:Account / ForwardRequest / ForwardResult
├── billing.go             # 计费相关类型 + 账号用量视图
├── errors.go              # 标准错误(ErrNotSupported 等)
├── log.go                 # 日志桥接
├── grpc/                  # gRPC 桥接层(hashicorp/go-plugin 适配)
│   ├── go_plugin.go       # Serve() 入口 + GRPCBroker 反向 stream
│   ├── host_client.go     # 插件侧的 HostService 客户端封装
│   ├── middleware_*.go    # MiddlewareService client / server
│   └── *_client.go        # 各插件类型的 client / server
├── devserver/             # 本地开发服务器
│   ├── server.go          # Config + Run() 入口
│   ├── accounts.go        # 账号 CRUD(JSON 文件持久化)
│   ├── proxy.go           # HTTP / SSE / WebSocket 代理
│   └── static/            # 内嵌管理 UI
├── frontend/              # @airgate/theme + @airgate/theme/plugin
├── proto/                 # protobuf 定义(5 个 service: Plugin/Gateway/Extension/Middleware/Host)
└── docs/
    ├── adr/               # 架构决策记录(ADR-0001 起)
    └── plugin-style-guide.md

推荐的插件项目结构

my-plugin/
├── backend/
│   ├── main.go                    # gRPC 入口(grpc.Serve(...))
│   ├── cmd/devserver/main.go      # 开发入口(约 20 行)
│   └── internal/gateway/          # 接口实现
├── web/                           # 前端源码(可选)
│   ├── src/{pages,widgets}/
│   └── dist/                      # 构建产物(go:embed 打入二进制)
├── .github/workflows/             # ci.yml + release.yml
├── Makefile
└── plugin.yaml                    # 由代码生成的分发文件

📦 打包与发布

plugin.yaml 是由插件代码生成的分发文件,仅用于安装和市场展示。运行时真相始终在插件代码里,core 不依赖 plugin.yaml 做运行时决策。

id: gateway-myplatform
name: My Platform 网关
version: 1.0.0
type: gateway
min_core_version: "1.0.0"
platform: myplatform
routes:
  - { method: POST, path: /v1/chat/completions }
models:
  - { id: my-model-v1, name: My Model V1, input_price: 1.0, output_price: 3.0 }
account_types:
  - key: apikey
    fields:
      - { key: api_key, label: API Key, type: password, required: true }

打包格式

my-plugin.tar.gz
├── my-plugin           # 插件二进制(前端资源已 go:embed 打入)
└── plugin.yaml         # 分发元信息

发布检查清单

  • go test ./... / go vet ./... 通过
  • 重新生成最新 plugin.yaml
  • 构建多架构二进制(amd64 / arm64)
  • 如有前端,构建并嵌入 dist/
  • 打包并验证完整性

🔧 SDK 开发工具

make lint    # 代码检查
make fmt     # 代码格式化
make test    # 运行测试
make proto   # 重新生成 protobuf 代码

👀 给 Core 开发者

Core 启动插件后的消费流程:

启动插件进程(go-plugin)
  → 通过 GRPCBroker 注册 HostService 反向 stream(若启用)
  → Info()       获取元信息(ID、类型、Capabilities、账号格式、前端声明)
  → Capability 校验:
        有效集 = Info.Capabilities ∩ 插件类型允许集合
        注册到 HostService interceptor 的 per-plugin context
  → Init(ctx)    注入 config + log_level + host_broker_id
  → Start(ctx)

Gateway 插件专属:
  → Platform() / Models() / Routes() / GetWebAssets()
  → ValidateAccount(ctx, cred)  添加账号时
  → QueryQuota(ctx, cred)       定时巡检

Extension 插件专属:
  → Migrate()
  → GetBackgroundTasks() + 调度器按 Interval 触发 RunBackgroundTask(name)
  → HandleRequest / HandleStreamRequest(/api/v1/admin/plugins/:name/rpc/* 透传)

HTTP 请求到达时(forward 路径):
  → Core 鉴权 + 限流 + 调度账号
  → Middleware.OnForwardBegin(按 Priority 升序;Deny → 直接拒绝)
  → Gateway.Forward(ctx, req)
  → Middleware.OnForwardEnd(按 Priority 降序,LIFO)
  → Core 写 usage_log + 处置账号状态(rate_limited / disabled / expired)

插件发起反向调用时:
  → HostService gRPC interceptor 从 context 取出该插件的 capability set
  → 未声明 → status.PermissionDenied
  → 放行 → core 业务层处理

Core 必须遵守的约定:

  • PluginInfo.ID 作为运行时键(API 路径、资源挂载、缓存)
  • Platform() 作为业务键(账号关联、调度、计费)
  • 以插件运行时返回的元信息为准,不依赖 plugin.yaml 做运行时决策
  • 添加账号时必须调用 ValidateAccount,验证失败拒绝保存
  • 账号管理 UI 统一由插件 FrontendWidgets 渲染,core 不做默认表单生成
  • middleware 的失败永远不能 block 生产流量OnForwardBegin/End 返回 error 只 log warn;唯一阻断途径是 OnForwardBegin 返回 Decision{Action: Deny}
  • capability 校验在 interceptor 层强制:core 业务代码不应再做 capability 判断(单一真相源)
  • 插件不得拿到 core 业务数据库的 DSN;core 业务数据一律通过 HostService RPC 暴露(详见 ADR-0001 Decision 1/5

🤝 贡献 / 反馈

📜 License

MIT

Documentation

Index

Constants

View Source
const (
	WSMessageText   = 1
	WSMessageBinary = 2
)

WebSocket 消息类型(与 gorilla/websocket 的 TextMessage / BinaryMessage 对齐)。

View Source
const (
	HeaderRequestID = "X-Request-ID"

	LogFieldRequestID  = "request_id"
	LogFieldUserID     = "user_id"
	LogFieldGroupID    = "group_id"
	LogFieldAccountID  = "account_id"
	LogFieldAPIKeyID   = "api_key_id"
	LogFieldPluginID   = "plugin_id"
	LogFieldModel      = "model"
	LogFieldPlatform   = "platform"
	LogFieldStatus     = "status_code"
	LogFieldDurationMs = "duration_ms"
	LogFieldError      = "error"
	LogFieldMethod     = "method"
	LogFieldPath       = "path"
	LogFieldReason     = "reason"
)

日志相关 HTTP header 与字段名约定。

字段命名一律 snake_case,事件名一律 snake_case,由 slog 统一输出。 全局 module 字段由 InitLogger 注入("core" 或 "plugin.<id>")。

View Source
const (
	DefaultMiddlewareDeadline    = 200 * time.Millisecond
	DefaultMiddlewareChainBudget = 500 * time.Millisecond
)

DefaultMiddlewareDeadline / DefaultMiddlewareChainBudget 单 hook / 整条链的默认超时预算。 Core 侧按此兜底,middleware 超时不得 block 主流程,只会被跳过并 log warn。

View Source
const (
	SlotAccountForm   = "account-form"
	SlotAccountDetail = "account-detail"
)

前端组件插槽。

View Source
const ConfigKeyLogLevel = "_log_level"

ConfigKeyLogLevel Core 通过此配置键把日志级别传给插件。

View Source
const PluginDSNConfigKey = "plugin_dsn"

PluginDSNConfigKey Core 注入"插件专属数据库 DSN"时使用的 config key。 插件作者应通过 GetPluginDSN(ctx) 访问,而非直接拼字符串。

View Source
const SDKVersion = "0.3.0"

SDKVersion 当前 SDK 版本,插件编译时嵌入到 PluginInfo。 0.3.0 起强制 Capability 声明:未声明 capability 的插件调用 HostService 会被拒绝。

Variables

View Source
var ErrInvalidCredentials = errors.New("invalid credentials")

ErrInvalidCredentials ValidateAccount 判定凭证格式/语义不合法时返回。

View Source
var ErrNotSupported = errors.New("not supported")

ErrNotSupported 插件不支持某项可选能力时返回(如 QueryQuota / HandleWebSocket)。

Functions

func ExtractOrGenerateRequestID added in v0.1.1

func ExtractOrGenerateRequestID(headers http.Header) string

ExtractOrGenerateRequestID 从 HTTP header 抽取 X-Request-ID;缺失则生成新 UUID。

用于 core 入口、插件 HTTP 处理器等所有"链路开始"的位置:

  • core 接到客户端请求 → 抽取/生成 → 写回 header → 透传给插件
  • 插件回调入口(webhook 等) → 同样抽取/生成

func GetPluginDSN

func GetPluginDSN(ctx PluginContext) string

GetPluginDSN PluginDSNAware 的便利访问器:ctx 实现了接口就返回 DSN,否则回退读 Config。

func InitLogger

func InitLogger(module, level, format string)

InitLogger 初始化全局 slog。 module 日志来源标识(如 "core"、"plugin.gateway-openai");level: debug/info/warn/error;format: text/json。

text 格式且 stdout 是 TTY 时自动启用 ANSI 颜色(按等级染色 + request_id 高亮); 重定向到文件 / 管道、或设置 NO_COLOR=1 时自动退化为纯文本。

func IsKnownCapability

func IsKnownCapability(c Capability) bool

IsKnownCapability 判断 capability 是否在当前 SDK 版本的已知集合内。

func LogFormat

func LogFormat() string

LogFormat 返回当前日志格式。

func LoggerFromContext added in v0.1.1

func LoggerFromContext(ctx context.Context) *slog.Logger

LoggerFromContext 从 context 取 logger;不存在则返回 slog.Default()。

该函数永远不返回 nil,调用方可直接 .Info/.Error。

func LoggerWithRequestID added in v0.1.1

func LoggerWithRequestID(ctx context.Context) (context.Context, *slog.Logger)

LoggerWithRequestID 为 ctx 派生一个带 request_id 字段的 logger 并写回 ctx。

典型用法(HTTP 中间件):

rid := sdk.ExtractOrGenerateRequestID(r.Header)
ctx = sdk.WithRequestID(ctx, rid)
ctx, logger := sdk.LoggerWithRequestID(ctx)
logger.Info("http_request_received", "method", r.Method, "path", r.URL.Path)

func RemainingSecondsUntil

func RemainingSecondsUntil(resetAt *time.Time, now time.Time) int

RemainingSecondsUntil 返回从 now 到 resetAt 的剩余秒数。 过期或 nil 一律返回 0。

func RequestIDFromContext added in v0.1.1

func RequestIDFromContext(ctx context.Context) string

RequestIDFromContext 从 context 取 request_id;不存在返回空串。

func ResetAtFromBase

func ResetAtFromBase(base time.Time, resetAfterSeconds int) *time.Time

ResetAtFromBase 根据基准时间和 reset_after_seconds 计算绝对重置时间。 负数会被钳制为 0。

func WithLogger added in v0.1.1

func WithLogger(ctx context.Context, logger *slog.Logger) context.Context

WithLogger 把 logger 绑到 context;通常由 HTTP/gRPC 入口构造请求级 logger 后调用。

func WithRequestID added in v0.1.1

func WithRequestID(ctx context.Context, id string) context.Context

WithRequestID 把 request_id 绑到 context(不修改 logger,仅作为独立 value)。

Types

type Account

type Account struct {
	ID          int64             `json:"id"`
	Name        string            `json:"name"`
	Platform    string            `json:"platform"`
	Type        string            `json:"type"`        // 对应 AccountType.Key(apikey / oauth / ...)
	Credentials map[string]string `json:"credentials"` // JSONB 透传,结构由 Type 决定
	ProxyURL    string            `json:"proxy_url"`
}

Account 上游账户(Core 调度后传给插件的最小视图)。

type AccountTodayStats

type AccountTodayStats struct {
	Requests    int64   `json:"requests"`
	Tokens      int64   `json:"tokens"`
	AccountCost float64 `json:"account_cost"`
	UserCost    float64 `json:"user_cost"`
}

AccountTodayStats 账号当天(本地时区自然日)在 usage_logs 中的聚合统计。 由 Core 基于 usage_logs 计算,插件不需要生成。

  • Requests 请求总数(count)
  • Tokens token 总数(input + output + cache_read + cache_creation)
  • AccountCost 账号成本 = SUM(account_cost)(上游账号的真实消耗,不受用户侧倍率影响)
  • UserCost 用户消耗 = SUM(actual_cost)(扣 User.balance 的金额)

type AccountType

type AccountType struct {
	Key         string            `json:"key"`
	Label       string            `json:"label"`
	Description string            `json:"description"`
	Fields      []CredentialField `json:"fields"`
}

AccountType 账号类型声明。

type AccountUsageAccountsResponse

type AccountUsageAccountsResponse struct {
	Accounts map[string]AccountUsageInfo `json:"accounts"`
	Errors   []AccountUsageError         `json:"errors,omitempty"`
}

AccountUsageAccountsResponse 是 usage/accounts 之类账号批量用量接口的通用响应。

type AccountUsageCredits

type AccountUsageCredits struct {
	Balance   float64 `json:"balance"`
	Unlimited bool    `json:"unlimited"`
}

AccountUsageCredits 描述账号的额度信息。

type AccountUsageError

type AccountUsageError struct {
	ID      int64  `json:"id"`
	Message string `json:"message"`
}

AccountUsageError 描述插件在探测账号用量时发现的单账号错误。

type AccountUsageInfo

type AccountUsageInfo struct {
	UpdatedAt  string               `json:"updated_at,omitempty"`
	Windows    []AccountUsageWindow `json:"windows,omitempty"`
	Credits    *AccountUsageCredits `json:"credits,omitempty"`
	TodayStats *AccountTodayStats   `json:"today_stats,omitempty"`
}

AccountUsageInfo 描述单个账号的通用用量视图。

TodayStats 是 Core 本地聚合的当天统计(从 usage_logs 按自然日计算), 和 Windows 是两码事:Windows 反映上游 quota 百分比,TodayStats 反映 本地 gateway 视角的账号当天真实消耗。

type AccountUsageWindow

type AccountUsageWindow struct {
	Key          string  `json:"key,omitempty"`
	Label        string  `json:"label"`
	UsedPercent  float64 `json:"used_percent"`
	ResetAt      string  `json:"reset_at,omitempty"`
	ResetSeconds int     `json:"reset_seconds,omitempty"`
}

AccountUsageWindow 描述账号的单个用量窗口。 插件负责把平台专属窗口语义归一化到这个结构,Core 只做通用展示。

func NewAccountUsageWindow

func NewAccountUsageWindow(key, label string, usedPercent float64, resetAt *time.Time, now time.Time) AccountUsageWindow

NewAccountUsageWindow 构建通用用量窗口,并同时填充 reset_at / reset_seconds。

type BackgroundTask

type BackgroundTask struct {
	Name     string
	Interval time.Duration
	Handler  func(ctx context.Context) error
}

BackgroundTask 后台任务声明(Core 负责调度)。

type Capability

type Capability string

Capability 类型化的能力标识符(命名规范:<domain>.<action>)。 所有 capability 常量必须用此类型以便编译期捕获拼写错误。

运行时授权由 Core 的 gRPC interceptor 强制执行;本类型是 SDK 侧的强类型入口, 并不绕过 Core 的准入校验。

const (
	CapabilityHostListGroups          Capability = "host.list_groups"
	CapabilityHostSelectAccount       Capability = "host.select_account"
	CapabilityHostProbeForward        Capability = "host.probe_forward"
	CapabilityHostReportAccountResult Capability = "host.report_account_result"
	CapabilityHostForward             Capability = "host.forward"
	CapabilityHostListPlatforms       Capability = "host.list_platforms"
	CapabilityHostListModels          Capability = "host.list_models"
	CapabilityHostGetUserInfo         Capability = "host.get_user_info"
	CapabilityHostAssetStorage        Capability = "host.asset_storage"

	CapabilityMiddlewareReadBody Capability = "middleware.read_body"
)

func KnownCapabilities

func KnownCapabilities() []Capability

KnownCapabilities 返回所有已知 capability,按字典序排序。

func (Capability) String

func (c Capability) String() string

type CapabilityValidationReport

type CapabilityValidationReport struct {
	// Effective 当前 plugin type 下实际生效的 capability = 声明 ∩ 类型允许,去重+排序。
	Effective []Capability
	// Unknown SDK 不认识的 capability(多半是拼写错)。
	Unknown []Capability
	// Denied SDK 认识但当前 plugin type 不允许的 capability(配置错误)。
	Denied []Capability
}

CapabilityValidationReport ValidateCapabilities 的输出。

func ValidateCapabilities

func ValidateCapabilities(typ PluginType, declared []Capability) CapabilityValidationReport

ValidateCapabilities 对一组声明做 self-check。授权决策仍由 Core 的 interceptor 负责, 这里只做"声明 vs 已知 vs 类型允许"的纸面检查。

func (CapabilityValidationReport) HasIssues

func (r CapabilityValidationReport) HasIssues() bool

HasIssues 报告是否检测到任何问题。

type ConfigField

type ConfigField struct {
	Key         string `json:"key"`
	Label       string `json:"label"`
	Type        string `json:"type"` // string / int / bool / float / duration / password
	Required    bool   `json:"required"`
	Default     string `json:"default,omitempty"`
	Description string `json:"description,omitempty"`
	Placeholder string `json:"placeholder,omitempty"`
}

ConfigField 配置项声明。

type ConfigWatcher

type ConfigWatcher interface {
	OnConfigUpdate(config PluginConfig) error
}

ConfigWatcher 可选:支持配置热更新的插件实现。

type CostInput

type CostInput struct {
	InputTokens           int
	OutputTokens          int
	CachedInputTokens     int    // cache read tokens
	CacheCreationTokens   int    // cache write 总数(= 5m + 1h;breakdown 缺失时作为 5m 计价的兜底)
	CacheCreation5mTokens int    // cache write(5 分钟 TTL,1.25x input)
	CacheCreation1hTokens int    // cache write(1 小时 TTL,2.00x input)
	ServiceTier           string // "priority" 使用优先级价格
}

CostInput 费用计算输入。

type CostResult

type CostResult struct {
	InputCost         float64
	OutputCost        float64
	CachedInputCost   float64 // cache read 费用
	CacheCreationCost float64 // cache write 费用
}

CostResult 费用计算结果(美元)

func CalculateCost

func CalculateCost(input CostInput, model ModelInfo) CostResult

CalculateCost 根据 token 数和模型价格计算费用 ModelInfo 中的价格单位为"每百万 token",此函数自动转换为每 token

输入约定:

  • input.InputTokens 已经是 **扣除 cached 后** 的非缓存输入(插件负责扣除)
  • input.CachedInputTokens 是命中缓存的 token 数
  • 完整 input_tokens = InputTokens + CachedInputTokens,长上下文阈值基于此

计费顺序(对齐 OpenAI 官方):

  1. 按 service_tier 选单价:standard / priority(配置价,缺省 ×2) / fast(×2.5) / flex|batch(×0.5)
  2. 命中长上下文阈值 → 再乘长上下文倍率(input/cached/output 独立系数)
  3. 三项独立计价后相加,cached 不重复计入 input

func (CostResult) TotalCost

func (r CostResult) TotalCost() float64

TotalCost 返回费用总和

type CredentialField

type CredentialField struct {
	Key          string `json:"key"`
	Label        string `json:"label"`
	Type         string `json:"type"` // text / password / textarea / select
	Required     bool   `json:"required"`
	Placeholder  string `json:"placeholder"`
	EditDisabled bool   `json:"edit_disabled,omitempty"`
}

CredentialField 凭证字段声明。

type DecisionAction

type DecisionAction int32

DecisionAction 对应 proto MiddlewareDecision.Action。

const (
	DecisionAllow  DecisionAction = 0
	DecisionDeny   DecisionAction = 1
	DecisionMutate DecisionAction = 2
)

type ExtensionPlugin

type ExtensionPlugin interface {
	Plugin
	RegisterRoutes(r RouteRegistrar)
	Migrate() error
	BackgroundTasks() []BackgroundTask
}

ExtensionPlugin 通用扩展插件接口:注册自定义路由、执行迁移、声明后台任务。

type ForwardOutcome

type ForwardOutcome struct {
	Kind OutcomeKind

	Upstream UpstreamResponse

	Usage *Usage

	Duration   time.Duration
	RetryAfter time.Duration

	Reason string

	UpdatedCredentials map[string]string
}

ForwardOutcome 是插件对一次 Forward 的完整判决结果。

字段填写约定:

Kind              必填,零值视为 Unknown(Core 保守处理)
Upstream          必填(StatusCode 至少填;Headers/Body 按 Kind 决定是否透传)
Usage             仅 Success(偶尔 ClientError)下非 nil
RetryAfter        仅 AccountRateLimited 下有意义
Duration          插件测得的耗时,Core 仅用于日志
Reason            人类可读原因,Core 仅落日志,不做任何判断
UpdatedCredentials 插件若在 Forward 中刷新了凭证(OAuth 轮转等)通过此字段带回

type ForwardRequest

type ForwardRequest struct {
	Account *Account
	Body    []byte
	Headers http.Header
	Model   string
	Stream  bool

	// Writer 流式响应写入目标。
	// TODO(PR3): 替换为 StreamSink 抽象,让 Core 能在首字节落地前决定是否 failover。
	Writer http.ResponseWriter
}

ForwardRequest 转发请求(Core → 插件)。

type FrontendPage

type FrontendPage struct {
	Path        string `json:"path"`
	Title       string `json:"title"`
	Icon        string `json:"icon"`
	Description string `json:"description"`
	// Audience 决定页面可见范围:
	//   "admin" / ""  仅管理员(默认)
	//   "user"        仅普通登录用户
	//   "all"         所有登录用户
	Audience string `json:"audience,omitempty"`
}

FrontendPage 前端独立页面声明。

type FrontendWidget

type FrontendWidget struct {
	Slot      string `json:"slot"`
	EntryFile string `json:"entry_file"`
	Title     string `json:"title"`
}

FrontendWidget 前端组件嵌入声明。

type GatewayPlugin

type GatewayPlugin interface {
	Plugin

	Platform() string
	Models() []ModelInfo
	Routes() []RouteDefinition

	Forward(ctx context.Context, req *ForwardRequest) (ForwardOutcome, error)

	ValidateAccount(ctx context.Context, credentials map[string]string) error
	QueryQuota(ctx context.Context, credentials map[string]string) (*QuotaInfo, error)

	// HandleWebSocket 处理 WebSocket 双向通信。连接结束后返回 ForwardOutcome。
	// 不支持时返回 ErrNotSupported。
	HandleWebSocket(ctx context.Context, conn WebSocketConn) (ForwardOutcome, error)
}

GatewayPlugin 网关插件接口。

Core 负责:账号调度、并发/RPM 限流、计费、failover。 插件负责:声明模型/路由、转发请求、验证凭证、查询额度。

Forward 的返回契约:

  • ForwardOutcome 表达业务判决(成功 / 客户端错 / 账号限流 / 账号死 / 上游抖动 / 流中断)
  • error 仅表达"插件自身无法完成此次调用"(进程异常、gRPC 层故障),不承担业务语义

详见 outcome.go 的 OutcomeKind 文档。

type HealthChecker

type HealthChecker interface {
	HealthCheck(ctx context.Context) error
}

HealthChecker 可选:Core 定期调用以探测插件存活。

type Host

type Host interface {

	// SelectAccount 走和真实用户请求完全相同的调度路径选出一个账号。
	SelectAccount(ctx context.Context, req HostSelectAccountRequest) (*HostSelectAccountResult, error)

	// ReportAccountResult 把账号调用结果反馈给 scheduler 状态机。
	ReportAccountResult(ctx context.Context, accountID int64, success bool, errMsg string) error

	// ProbeForward 内部组装一次最小 chat completion 请求执行黑盒探测:
	// 跳过 usage_log 与余额扣款,但仍 ReportResult 反哺账号状态机。
	ProbeForward(ctx context.Context, req HostProbeForwardRequest) (*HostProbeForwardResult, error)

	// Forward 非流式业务转发:走完整管线(调度 → 网关 → 计费 → usage_log)。
	Forward(ctx context.Context, req HostForwardRequest) (*HostForwardResponse, error)

	// ForwardStream 流式业务转发:结果通过 callback 逐块回调。
	// callback 返回 error 时 Core 侧中断流。最后一块 Done=true 携带 Usage。
	ForwardStream(ctx context.Context, req HostForwardRequest, callback func(chunk HostForwardChunk) error) error

	// ListGroups 列出 Core 当前所有分组。
	ListGroups(ctx context.Context) ([]HostGroup, error)

	// ListPlatforms 列出已加载的网关平台。
	ListPlatforms(ctx context.Context) ([]HostPlatform, error)

	// ListModels 列出指定平台的模型列表。
	ListModels(ctx context.Context, platform string) ([]ModelInfo, error)

	// GetUserInfo 获取用户基本信息。
	GetUserInfo(ctx context.Context, userID int64) (*HostUserInfo, error)

	// StoreAsset 由 Core 根据全局 storage 设置保存资产并返回可访问 URL。
	StoreAsset(ctx context.Context, req HostStoreAssetRequest) (*HostStoredAsset, error)

	// GetAssetURL 根据 object key 返回当前配置下的可访问 URL。
	GetAssetURL(ctx context.Context, objectKey string) (string, error)

	// GetAssetBytes 根据 object key 返回资产原始内容。
	GetAssetBytes(ctx context.Context, objectKey string) (*HostAssetBytes, error)
}

Host Core 暴露给插件的反向调用接口(plugin → core)。

通过 hashicorp/go-plugin 的 GRPCBroker 架起子进程隧道,插件无需 admin HTTP / Bearer 鉴权。

在插件 Init 里通过 HostAware 拿到:

func (p *MyPlugin) Init(ctx sdk.PluginContext) error {
    if h, ok := ctx.(sdk.HostAware); ok {
        p.host = h.Host() // 可能为 nil
    }
    return nil
}

type HostAssetBytes added in v0.1.3

type HostAssetBytes struct {
	Data        []byte
	ContentType string
}

HostAssetBytes 资产读取结果。

type HostAware

type HostAware interface {
	// Host 返回 Host 客户端;可能为 nil(Core 版本不支持 / 未启用)。
	Host() Host
}

HostAware 可选接口:PluginContext 实现它就能暴露 Host。老 dev server / 测试 mock 可忽略。

type HostForwardChunk added in v0.1.1

type HostForwardChunk struct {
	Data       []byte
	Done       bool
	StatusCode int
	Headers    http.Header
	Usage      HostForwardUsage
}

HostForwardChunk 流式转发的单块数据。

type HostForwardRequest added in v0.1.1

type HostForwardRequest struct {
	UserID  int64
	GroupID int64
	Model   string
	Method  string
	Path    string
	Headers http.Header
	Body    []byte
	Stream  bool
}

HostForwardRequest 业务转发入参。

type HostForwardResponse added in v0.1.1

type HostForwardResponse struct {
	StatusCode int
	Headers    http.Header
	Body       []byte
	Usage      HostForwardUsage
}

HostForwardResponse 非流式转发结果。

type HostForwardUsage added in v0.1.1

type HostForwardUsage struct {
	InputTokens  int64
	OutputTokens int64
	Cost         float64
	Model        string
}

HostForwardUsage 转发的 token / 费用摘要。

type HostGroup

type HostGroup struct {
	ID             int64
	Name           string
	Platform       string
	IsExclusive    bool
	RateMultiplier float64
}

HostGroup Host.ListGroups 返回的分组摘要。

type HostPlatform added in v0.1.1

type HostPlatform struct {
	Name        string
	DisplayName string
}

HostPlatform 已加载的网关平台。

type HostProbeForwardRequest

type HostProbeForwardRequest struct {
	GroupID int64
	Model   string
}

HostProbeForwardRequest 黑盒探测入参。

type HostProbeForwardResult

type HostProbeForwardResult struct {
	Success    bool
	AccountID  int64
	Platform   string
	Model      string
	StatusCode int64
	LatencyMs  int64
	ErrorKind  string
	ErrorMsg   string
}

HostProbeForwardResult 黑盒探测结果。

type HostSelectAccountRequest

type HostSelectAccountRequest struct {
	GroupID           int64
	Model             string
	SessionID         string
	ExcludeAccountIDs []int64
}

HostSelectAccountRequest 调度选号入参。

type HostSelectAccountResult

type HostSelectAccountResult struct {
	AccountID   int64
	AccountName string
	Platform    string
}

HostSelectAccountResult 调度选号结果。

type HostStoreAssetRequest added in v0.1.2

type HostStoreAssetRequest struct {
	UserID        int64
	Scope         string
	ContentType   string
	Data          []byte
	FileExtension string
}

HostStoreAssetRequest 资产存储入参。

type HostStoredAsset added in v0.1.2

type HostStoredAsset struct {
	AssetID     string
	ObjectKey   string
	PublicURL   string
	SizeBytes   int64
	ContentType string
}

HostStoredAsset 资产存储结果。

type HostUserInfo added in v0.1.1

type HostUserInfo struct {
	UserID   int64
	Username string
	Email    string
	Role     string
	Balance  float64
	Status   string
}

HostUserInfo 用户基本信息。

type MiddlewareDecision

type MiddlewareDecision struct {
	Action DecisionAction

	// DenyStatusCode / DenyMessage 仅 DecisionDeny 使用;默认 403。
	DenyStatusCode int32
	DenyMessage    string

	// SetHeaders 仅 DecisionMutate 使用:追加或覆盖的请求头。
	SetHeaders http.Header

	// Metadata 无论 Action 是什么都会 merge 进请求上下文的 bag。
	Metadata map[string]string
}

MiddlewareDecision OnForwardBegin 的返回值。nil 等价于 DecisionAllow 且不改任何东西。

type MiddlewareEvent

type MiddlewareEvent struct {
	RequestID      string
	UserID         int64
	GroupID        int64
	AccountID      int64
	Platform       string
	Model          string
	Stream         bool
	InputTokensEst int64

	StatusCode        int32
	Duration          time.Duration
	InputTokens       int64
	OutputTokens      int64
	CachedInputTokens int64
	FirstTokenMs      int64
	ErrorKind         string // "" / "upstream_5xx" / "timeout" / "no_account" / ...
	ErrorMsg          string

	InputCost       float64
	OutputCost      float64
	CachedInputCost float64

	Metadata map[string]string

	// ResponseBody / ResponseHeaders 仅在插件声明 CapabilityMiddlewareReadBody 时填充。
	// 流式响应下 ResponseBody 只含摘要(首个非空 chunk)。
	ResponseBody    []byte
	ResponseHeaders http.Header
}

MiddlewareEvent OnForwardEnd 的入参。

type MiddlewarePlugin

type MiddlewarePlugin interface {
	Plugin

	OnForwardBegin(ctx context.Context, req *MiddlewareRequest) (*MiddlewareDecision, error)
	OnForwardEnd(ctx context.Context, evt *MiddlewareEvent) error
}

MiddlewarePlugin 中间件插件接口。

PluginInfo.Type = PluginTypeMiddleware 时注册为"请求/响应拦截层"。Core 在每次 forward 的 前后回调 OnForwardBegin / OnForwardEnd,按 PluginInfo.Priority 升序进 Begin、降序出 End。

失败语义:Begin/End 返回 error 只会被 log warn,不 block 主流程。唯一例外是 OnForwardBegin 返回 DecisionDeny——此时请求被拒绝,用户看到的错误文案来自 Decision。

Payload:默认只给元数据;插件声明 CapabilityMiddlewareReadBody 才会收到 request_body / response_body。流式响应的 response_body 只给摘要。

type MiddlewareRequest

type MiddlewareRequest struct {
	RequestID      string
	UserID         int64
	GroupID        int64
	AccountID      int64
	Platform       string
	Model          string
	Stream         bool
	InputTokensEst int64

	// Metadata 贯穿 Begin/End 的 KV bag,多个 middleware 之间共享。
	Metadata map[string]string

	// RequestBody / RequestHeaders 仅在插件声明 CapabilityMiddlewareReadBody 时填充。
	RequestBody    []byte
	RequestHeaders http.Header
}

MiddlewareRequest OnForwardBegin 的入参。

type ModelInfo

type ModelInfo struct {
	ID                   string  `json:"id"`
	Name                 string  `json:"name"`
	ContextWindow        int     `json:"context_window"`
	MaxOutputTokens      int     `json:"max_output_tokens"`
	InputPrice           float64 `json:"input_price"`             // $/1M token
	OutputPrice          float64 `json:"output_price"`            // $/1M token
	CachedInputPrice     float64 `json:"cached_input_price"`      // cache read,$/1M token
	CacheCreationPrice   float64 `json:"cache_creation_price"`    // cache write 5m TTL(1.25x input)
	CacheCreation1hPrice float64 `json:"cache_creation_1h_price"` // cache write 1h TTL(2.00x input)

	InputPricePriority       float64 `json:"input_price_priority,omitempty"`
	OutputPricePriority      float64 `json:"output_price_priority,omitempty"`
	CachedInputPricePriority float64 `json:"cached_input_price_priority,omitempty"`

	InputPriceFast       float64 `json:"input_price_fast,omitempty"`
	OutputPriceFast      float64 `json:"output_price_fast,omitempty"`
	CachedInputPriceFast float64 `json:"cached_input_price_fast,omitempty"`

	InputPriceFlex       float64 `json:"input_price_flex,omitempty"`
	OutputPriceFlex      float64 `json:"output_price_flex,omitempty"`
	CachedInputPriceFlex float64 `json:"cached_input_price_flex,omitempty"`

	// 长上下文阶梯(仅 gpt-5.4 家族启用;判定基于完整 input_tokens = 非缓存+缓存命中)
	LongContextThreshold        int     `json:"long_context_threshold,omitempty"`
	LongContextInputMultiplier  float64 `json:"long_context_input_multiplier,omitempty"`
	LongContextOutputMultiplier float64 `json:"long_context_output_multiplier,omitempty"`
	LongContextCachedMultiplier float64 `json:"long_context_cached_multiplier,omitempty"`
}

ModelInfo 插件声明的模型信息(Core 缓存用于调度/展示,不再做计费)。

定价档位(对齐 OpenAI 官方):

  • 标准档:InputPrice / OutputPrice / CachedInputPrice
  • Priority 档:*Priority 字段;未配置时 CalculateCost 以标准价 × 2 兜底
  • Fast 档:*Fast 字段;未配置时 CalculateCost 以标准价 × 2.5 兜底
  • Flex / Batch 档:*Flex 字段;未配置时以标准价 × 0.5 兜底
  • 长上下文档(仅 gpt-5.4 家族):完整 input_tokens 超过 LongContextThreshold 且非 priority 时整次请求按倍率计费

type OutcomeKind

type OutcomeKind int

OutcomeKind 一次 Forward 调用的判决类型。

插件必须在返回的 ForwardOutcome 里显式声明 Kind;零值 OutcomeUnknown 等于 "插件未声明",Core 会按"可疑上游错误"保守处理(不透传响应给客户端、也不把账号标死)。 这是 SDK 契约的核心:插件只做判决,Core 只做执行。

const (
	// OutcomeUnknown 保留给零值:插件未声明判决。Core 将保守处理。
	OutcomeUnknown OutcomeKind = iota

	// OutcomeSuccess 上游返回 2xx,Usage 必填。
	OutcomeSuccess

	// OutcomeClientError 4xx,错在客户端请求本身(model 不存在、context 过长、参数非法)。
	// 换账号救不回来,Core 会把 Upstream 原样透传给客户端,不罚账号。
	OutcomeClientError

	// OutcomeAccountRateLimited 账号被上游限流(通常 429),冷却一段时间后可恢复。
	// Core 会把账号打入 RateLimited 状态 + 尝试 failover 到其它账号。
	OutcomeAccountRateLimited

	// OutcomeAccountDead 账号凭证失效(401/403,或上游语义化消息),需要人工处理。
	// Core 会把账号打入 Disabled 状态 + 尝试 failover。
	OutcomeAccountDead

	// OutcomeUpstreamTransient 上游抽风(5xx、连接抖动、超时),账号本身没问题。
	// Core 尝试 failover;累计多次后由状态机决定是否升级为 AccountDead。
	OutcomeUpstreamTransient

	// OutcomeStreamAborted 流式响应已经开始写入客户端,中途断开。
	// 不能 failover(字节已经发出去了),也不能把账号直接标死。
	OutcomeStreamAborted
)

func (OutcomeKind) IsAccountFault

func (k OutcomeKind) IsAccountFault() bool

IsAccountFault 本次判决是否归咎于账号自身(RateLimited / Dead)。 Core 据此决定是否推进账号状态机。

func (OutcomeKind) IsSuccess

func (k OutcomeKind) IsSuccess() bool

IsSuccess 是否成功完成(2xx)。

func (OutcomeKind) ShouldFailover

func (k OutcomeKind) ShouldFailover() bool

ShouldFailover 是否允许换账号重试。 ClientError 不应 failover(换号也救不回来);StreamAborted 不能 failover(已写入); Success / Unknown 显然不该 failover。

func (OutcomeKind) String

func (k OutcomeKind) String() string

String 返回人类可读名称,用于日志。

type Plugin

type Plugin interface {
	Info() PluginInfo
	Init(ctx PluginContext) error
	Start(ctx context.Context) error
	Stop(ctx context.Context) error
}

Plugin 所有插件的基础接口。

type PluginConfig

type PluginConfig interface {
	GetString(key string) string
	GetInt(key string) int
	GetBool(key string) bool
	GetFloat64(key string) float64
	GetDuration(key string) time.Duration
	// GetAll 返回 JSONB 原始 map。
	GetAll() map[string]string
}

PluginConfig 配置读取接口。

type PluginContext

type PluginContext interface {
	Logger() *slog.Logger
	Config() PluginConfig
}

PluginContext Core 注入给插件的最小上下文:Logger + Config。 其它能力(Host 反向调用、插件专属 DB DSN 等)通过可选接口(HostAware / PluginDSNAware)暴露, 避免给 PluginContext 加方法造成 breaking change。

type PluginDSNAware

type PluginDSNAware interface {
	// PluginDSN 返回 DSN;空字符串 = 未启用插件 DB。调用方需 nil/empty check。
	PluginDSN() string
}

PluginDSNAware 可选接口:实现它表示能拿到 Core 注入的插件专属 DB DSN。 DSN 已预设 search_path 到独立 schema(plugin_<id>),核心业务表在 PostgreSQL 层被 REVOKE。

type PluginInfo

type PluginInfo struct {
	ID                 string           `json:"id"`
	Name               string           `json:"name"`
	Version            string           `json:"version"`
	SDKVersion         string           `json:"sdk_version"`
	Description        string           `json:"description"`
	Author             string           `json:"author"`
	Type               PluginType       `json:"type"`
	Dependencies       []string         `json:"dependencies"`
	ConfigSchema       []ConfigField    `json:"config_schema"`
	AccountTypes       []AccountType    `json:"account_types"`
	FrontendPages      []FrontendPage   `json:"frontend_pages"`
	FrontendWidgets    []FrontendWidget `json:"frontend_widgets"`
	InstructionPresets []string         `json:"instruction_presets"`
	Capabilities       []Capability     `json:"capabilities"`
	// Priority 仅对 type=middleware 生效:Begin 升序、End 降序(LIFO)。默认 100。
	Priority int32 `json:"priority"`
}

PluginInfo 插件元信息。

type PluginType

type PluginType string

PluginType 插件扮演的角色。

gateway    上游适配器(airgate-openai / claude 等)
extension  后台任务 + 自定义 HTTP 路由(airgate-health / epay 等)
middleware forward 路径上的旁路拦截层(审计 / 脱敏 / 记账等)
const (
	PluginTypeGateway    PluginType = "gateway"
	PluginTypeExtension  PluginType = "extension"
	PluginTypeMiddleware PluginType = "middleware"
)

type QuotaInfo

type QuotaInfo struct {
	Total     float64           `json:"total"`
	Used      float64           `json:"used"`
	Remaining float64           `json:"remaining"`
	Currency  string            `json:"currency"`   // 如 "USD"
	ExpiresAt string            `json:"expires_at"` // ISO 8601
	Extra     map[string]string `json:"extra"`
}

QuotaInfo 账号额度信息。

type RequestHandler

type RequestHandler interface {
	HandleRequest(ctx context.Context, method, path, query string, headers http.Header, body []byte) (statusCode int, respHeaders http.Header, respBody []byte, err error)
}

RequestHandler 可选:Core 将 /api/v1/admin/plugins/:name/rpc/* 透传给插件自行路由。

type RouteDefinition

type RouteDefinition struct {
	Method      string `json:"method"`
	Path        string `json:"path"`
	Description string `json:"description"`
}

RouteDefinition 网关插件声明的 API 端点。

type RouteRegistrar

type RouteRegistrar interface {
	Handle(method, path string, handler http.HandlerFunc)
	Group(prefix string) RouteRegistrar
}

RouteRegistrar 扩展插件使用的路由注册器。

type UpstreamResponse

type UpstreamResponse struct {
	StatusCode int
	Headers    http.Header
	Body       []byte
}

UpstreamResponse 上游返回的原始 HTTP 快照。

语义:Success / ClientError 时 Core 会把 Body + Headers 原样透传给客户端。 其他 Kind 下 Upstream 仅作为诊断信息保留,不透传。 StreamAborted 场景 Body 通常为空(字节已经流给客户端)。

type Usage

type Usage struct {
	InputTokens           int
	OutputTokens          int
	CachedInputTokens     int
	CacheCreationTokens   int
	CacheCreation5mTokens int
	CacheCreation1hTokens int
	ReasoningOutputTokens int

	InputCost         float64
	OutputCost        float64
	CachedInputCost   float64
	CacheCreationCost float64

	InputPrice           float64
	OutputPrice          float64
	CachedInputPrice     float64
	CacheCreationPrice   float64
	CacheCreation1hPrice float64

	Model        string
	ServiceTier  string
	FirstTokenMs int64

	// ImageSize 图像生成请求的实际出图尺寸("WxH",例如 "1024x1024"、"3840x2160")。
	// 网关侧按 1K/2K/4K 三档计费,把分档来源(实际尺寸)记下来,admin 后台 usage_log
	// 显示费用时旁边带上 size,用户能直观看出"为什么这次扣了 0.40"。非图像请求留空。
	ImageSize string
}

Usage 单次调用的 token / 费用统计。

只有 OutcomeSuccess 下 Usage 必填;OutcomeClientError 如果上游也计费(如部分重置 context 后仍计 token)可填;其他 Kind 下应为 nil。

费用字段(*Cost)由插件根据单价 × token 计算后传回,Core 不再关心模型定价。 单价字段(*Price)纯粹透传存储,便于 usage_log 审计。

type WebAssetsProvider

type WebAssetsProvider interface {
	GetWebAssets() map[string][]byte
}

WebAssetsProvider 可选:插件通过此接口提供前端静态资源。

type WebSocketConn

type WebSocketConn interface {
	ReadMessage() (messageType int, data []byte, err error)
	WriteMessage(messageType int, data []byte) error
	ConnectInfo() *WebSocketConnectInfo
	Close(code int, reason string) error
}

WebSocketConn 抽象的 WebSocket 连接,由 gRPC 双向流在 SDK 侧适配。

type WebSocketConnectInfo

type WebSocketConnectInfo struct {
	Path         string
	Query        string
	Headers      http.Header
	RemoteAddr   string
	ConnectionID string
	Account      *Account
}

WebSocketConnectInfo WebSocket 连接建立时的元信息(由 Core 在 CONNECT 帧里下发)。

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL